Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions infra/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ for them to build on, and exports `JarlSiteBucketName`, `JarlDistributionId` and
`/ssr/*` behaviour pointing at its own origin while every other path keeps hitting S3;
`JarlDomainStack` hands over the hosted zone and the certificate the distribution is created with.

> **Mid-cutover: the `/ssr/*` behaviour is deliberately absent.** CloudFront refuses to update a VPC
> origin that a distribution is still associated with, so detaching the behaviour and repointing the
> origin cannot happen in one deploy. Until the next deploy restores it, `/ssr/*` falls through to the
> distribution's static 404, and `JarlSsr` deploys *after* `JarlStaticSite`, inverting the order below.

`JarlStaticSite` therefore deploys last, whichever way the wiring runs: its template references
`JarlSsr`'s origin and `JarlDomain`'s certificate, so CloudFormation needs both in place before it can
be created or updated. `cdk deploy --all` works this out from the templates; deploying stacks one at a
Expand Down
6 changes: 4 additions & 2 deletions infra/bin/jarl-infra.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,7 @@ const staticSite = new JarlStaticSiteStack(app, "JarlStaticSite", {
certificate: domain.certificate,
});

// Instantiated last but deployed before JarlStaticSite, whose template references this origin.
new JarlSsrStack(app, "JarlSsr", { ...stackPropsIn(primaryRegion), distribution: staticSite.distribution });
const ssr = new JarlSsrStack(app, "JarlSsr", stackPropsIn(primaryRegion));

// The distribution has to let go of the VPC origin before CloudFront will let this stack update it.
ssr.addDependency(staticSite);
31 changes: 5 additions & 26 deletions infra/lib/jarl-stacks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,13 @@ import {
FunctionEventType,
HttpVersion,
OriginProtocolPolicy,
OriginRequestPolicy,
PriceClass,
ResponseHeadersPolicy,
ViewerProtocolPolicy,
VpcOrigin as VpcOriginResource,
VpcOriginEndpoint,
} from "aws-cdk-lib/aws-cloudfront";
import { S3BucketOrigin, VpcOrigin } from "aws-cdk-lib/aws-cloudfront-origins";
import { S3BucketOrigin } from "aws-cdk-lib/aws-cloudfront-origins";
import {
AmazonLinuxCpuType,
BlockDeviceVolume,
Expand Down Expand Up @@ -261,14 +260,9 @@ systemctl daemon-reload
systemctl enable ${ssrServiceName}
`;

export interface JarlSsrStackProps extends StackProps {
/** {@link JarlStaticSiteStack.distribution} — this stack adds its behaviour to it rather than creating a front. */
readonly distribution: Distribution;
}

/** EC2 instance running the docs SSR server, attached to the distribution as a second origin. */
/** EC2 instance running the docs SSR server, and the CloudFront VPC origin that reaches it. */
export class JarlSsrStack extends Stack {
constructor(scope: Construct, id: string, props: JarlSsrStackProps) {
constructor(scope: Construct, id: string, props: StackProps) {
super(scope, id, props);

// No NAT gateway: one would cost more per month than the instance it serves, so outbound
Expand Down Expand Up @@ -333,6 +327,8 @@ export class JarlSsrStack extends Stack {

// The server terminates nothing, so CloudFront has to speak plain HTTP to it; the default
// policy would follow the viewer onto port 443, where nothing listens.
// No distribution behaviour points here yet: CloudFront refuses to update a VPC origin while a
// distribution is associated with it, so attaching this one is a deploy of its own.
const vpcOrigin = new VpcOriginResource(this, "SsrVpcOrigin", {
endpoint: VpcOriginEndpoint.ec2Instance(instance),
httpPort: ssrPort,
Expand Down Expand Up @@ -382,23 +378,6 @@ export class JarlSsrStack extends Stack {
Port.tcp(ssrPort),
);

// JarlStaticSiteStack's errorResponses are distribution-wide: a 403/404 from the server still
// serves the static build's 404.html, not anything from this origin.
props.distribution.addBehavior(
ssrPathPattern,
VpcOrigin.withVpcOrigin(vpcOrigin, {
// CloudFront makes three connection attempts, so the default 10 seconds is 30 before a 504.
connectionTimeout: Duration.seconds(2),
}),
{
viewerProtocolPolicy: ViewerProtocolPolicy.REDIRECT_TO_HTTPS,
allowedMethods: AllowedMethods.ALLOW_GET_HEAD_OPTIONS,
cachePolicy: CachePolicy.CACHING_DISABLED,
originRequestPolicy: OriginRequestPolicy.ALL_VIEWER,
compress: true,
},
);

// The deploy role's baseline permissions (assume-cdk-roles, DescribeStacks, site-bucket S3,
// CloudFront invalidation) were granted out of band alongside the role itself; this adds
// only what rolling the SSR server needs, as its own policy on the same role.
Expand Down
Loading