Initial work on CDK+fargate.

This commit is contained in:
Luke Curley 2023-02-01 05:22:35 -08:00
parent 4c5246e5fa
commit be3f44c7b1
17 changed files with 9615 additions and 0 deletions

8
deploy/.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
*.js
!jest.config.js
*.d.ts
node_modules
# CDK asset staging directory
.cdk.staging
cdk.out

6
deploy/.npmignore Normal file
View File

@ -0,0 +1,6 @@
*.ts
!*.d.ts
# CDK asset staging directory
.cdk.staging
cdk.out

14
deploy/README.md Normal file
View File

@ -0,0 +1,14 @@
# Welcome to your CDK TypeScript project
This is a blank project for CDK development with TypeScript.
The `cdk.json` file tells the CDK Toolkit how to execute your app.
## Useful commands
* `npm run build` compile typescript to js
* `npm run watch` watch for changes and compile
* `npm run test` perform the jest unit tests
* `cdk deploy` deploy this stack to your default AWS account/region
* `cdk diff` compare deployed stack with current state
* `cdk synth` emits the synthesized CloudFormation template

16
deploy/bin/deploy.ts Normal file
View File

@ -0,0 +1,16 @@
#!/usr/bin/env node
import 'source-map-support/register';
import * as cdk from '@aws-cdk/core';
import { WarpStack } from '../lib/warp';
const app = new cdk.App();
new WarpStack(app, 'warp-us-east-1', {
env: { account: '864566598233', region: 'us-east-1' },
});
new WarpStack(app, 'warp-us-west-2', {
env: { account: '864566598233', region: 'us-west-2' },
});
app.synth();

105
deploy/cdk.context.json Normal file
View File

@ -0,0 +1,105 @@
{
"vpc-provider:account=864566598233:filter.isDefault=true:region=us-east-1:returnAsymmetricSubnets=true": {
"vpcId": "vpc-0eb5406e69a058c21",
"vpcCidrBlock": "172.31.0.0/16",
"availabilityZones": [],
"subnetGroups": [
{
"name": "Public",
"type": "Public",
"subnets": [
{
"subnetId": "subnet-098223d57b90bc5ba",
"cidr": "172.31.0.0/20",
"availabilityZone": "us-east-1a",
"routeTableId": "rtb-00247d339210de603"
},
{
"subnetId": "subnet-0f85396c1e654088a",
"cidr": "172.31.80.0/20",
"availabilityZone": "us-east-1b",
"routeTableId": "rtb-00247d339210de603"
},
{
"subnetId": "subnet-0da18611f81d0ee65",
"cidr": "172.31.16.0/20",
"availabilityZone": "us-east-1c",
"routeTableId": "rtb-00247d339210de603"
},
{
"subnetId": "subnet-04ec9401177026153",
"cidr": "172.31.32.0/20",
"availabilityZone": "us-east-1d",
"routeTableId": "rtb-00247d339210de603"
},
{
"subnetId": "subnet-09990358c6611a867",
"cidr": "172.31.48.0/20",
"availabilityZone": "us-east-1e",
"routeTableId": "rtb-00247d339210de603"
},
{
"subnetId": "subnet-09eefe16a158ac97d",
"cidr": "172.31.64.0/20",
"availabilityZone": "us-east-1f",
"routeTableId": "rtb-00247d339210de603"
}
]
}
]
},
"vpc-provider:account=864566598233:filter.isDefault=true:region=us-west-2:returnAsymmetricSubnets=true": {
"vpcId": "vpc-0e1c6b8dc284b5ea8",
"vpcCidrBlock": "172.31.0.0/16",
"availabilityZones": [],
"subnetGroups": [
{
"name": "Public",
"type": "Public",
"subnets": [
{
"subnetId": "subnet-0b20049ea38baab49",
"cidr": "172.31.16.0/20",
"availabilityZone": "us-west-2a",
"routeTableId": "rtb-02fa3d3e8c3c073a2"
},
{
"subnetId": "subnet-035cb5403ca3a6fd0",
"cidr": "172.31.32.0/20",
"availabilityZone": "us-west-2b",
"routeTableId": "rtb-02fa3d3e8c3c073a2"
},
{
"subnetId": "subnet-08a943f1d7ffe1269",
"cidr": "172.31.0.0/20",
"availabilityZone": "us-west-2c",
"routeTableId": "rtb-02fa3d3e8c3c073a2"
},
{
"subnetId": "subnet-05ce9ae279372b111",
"cidr": "172.31.48.0/20",
"availabilityZone": "us-west-2d",
"routeTableId": "rtb-02fa3d3e8c3c073a2"
}
]
}
]
},
"acknowledged-issue-numbers": [
19836
],
"availability-zones:account=864566598233:region=us-east-1": [
"us-east-1a",
"us-east-1b",
"us-east-1c",
"us-east-1d",
"us-east-1e",
"us-east-1f"
],
"availability-zones:account=864566598233:region=us-west-2": [
"us-west-2a",
"us-west-2b",
"us-west-2c",
"us-west-2d"
]
}

40
deploy/cdk.json Normal file
View File

@ -0,0 +1,40 @@
{
"app": "npx ts-node --prefer-ts-exts bin/deploy.ts",
"watch": {
"include": [
"**"
],
"exclude": [
"README.md",
"cdk*.json",
"**/*.d.ts",
"**/*.js",
"tsconfig.json",
"package*.json",
"yarn.lock",
"node_modules",
"test"
]
},
"context": {
"@aws-cdk/aws-lambda:recognizeLayerVersion": true,
"@aws-cdk/core:checkSecretUsage": true,
"@aws-cdk/core:target-partitions": [
"aws",
"aws-cn"
],
"@aws-cdk-containers/ecs-service-extensions:enableDefaultLogDriver": true,
"@aws-cdk/aws-ec2:uniqueImdsv2TemplateName": true,
"@aws-cdk/aws-ecs:arnFormatIncludesClusterName": true,
"@aws-cdk/aws-iam:minimizePolicies": true,
"@aws-cdk/core:validateSnapshotRemovalPolicy": true,
"@aws-cdk/aws-codepipeline:crossAccountKeyAliasStackSafeResourceName": true,
"@aws-cdk/aws-s3:createDefaultLoggingPolicy": true,
"@aws-cdk/aws-sns-subscriptions:restrictSqsDescryption": true,
"@aws-cdk/aws-apigateway:disableCloudWatchRole": true,
"@aws-cdk/core:enablePartitionLiterals": true,
"@aws-cdk/aws-events:eventsTargetQueueSameAccount": true,
"@aws-cdk/aws-iam:standardizedServicePrincipals": true,
"@aws-cdk/aws-ecs:disableExplicitDeploymentControllerForCircuitBreaker": true
}
}

8
deploy/jest.config.js Normal file
View File

@ -0,0 +1,8 @@
module.exports = {
testEnvironment: 'node',
roots: ['<rootDir>/test'],
testMatch: ['**/*.test.ts'],
transform: {
'^.+\\.tsx?$': 'ts-jest'
}
};

167
deploy/lib/warp.ts Normal file
View File

@ -0,0 +1,167 @@
import * as cdk from '@aws-cdk/core';
import * as ec2 from "@aws-cdk/aws-ec2"; // Allows working with EC2 and VPC resources
import * as iam from "@aws-cdk/aws-iam"; // Allows working with IAM resources
import * as ecs from "@aws-cdk/aws-ecs";
import * as cwl from "@aws-cdk/aws-logs";
import * as elb from "@aws-cdk/aws-elasticloadbalancingv2"
import * as ecr_assets from '@aws-cdk/aws-ecr-assets';
import * as route53 from "@aws-cdk/aws-route53";
import * as route53_targets from "@aws-cdk/aws-route53-targets"
import * as path from "path"; // Helper for working with file paths
export class WarpStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props: cdk.StackProps) {
super(scope, id, props);
//1. Create VPC
const vpc = new ec2.Vpc(this, 'VPC');
//2. Creation of Execution Role for our task
const execRole = new iam.Role(this, 'warp-exec-role', {
roleName: 'warp-exec-role', assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com')
})
//3. Adding permissions to the above created role...basically giving permissions to ECR image and Cloudwatch logs
execRole.addToPolicy(new iam.PolicyStatement({
actions: [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents"
], effect: iam.Effect.ALLOW, resources: ["*"]
}));
//4. Create the ECS fargate cluster
const cluster = new ecs.Cluster(this, 'warp-cluster', { vpc, clusterName: "warp-cluster" });
//5. Create a task definition for our cluster to invoke a task
const taskDef = new ecs.FargateTaskDefinition(this, "warp-task", {
family: 'warp-task',
executionRole: execRole,
taskRole: execRole,
volumes: [{ name: "cert" }],
});
//6. Create log group for our task to put logs
const log_group = cwl.LogGroup.fromLogGroupName(this, 'warp-log-group', '/ecs/warp-task');
const log = new ecs.AwsLogDriver({
logGroup: log_group ? log_group : new cwl.LogGroup(this, 'warp-log-group', { logGroupName: '/ecs/warp-task' }),
streamPrefix: 'ecs'
})
// ?? Build the docker images
const player_image = new ecr_assets.DockerImageAsset(this, 'warp-player-image', {
directory: path.join(__dirname, '/../../player'),
});
const server_image = new ecr_assets.DockerImageAsset(this, 'warp-server-image', {
directory: path.join(__dirname, '/../../server'),
});
/*
const media_image = new ecr_assets.DockerImageAsset(this, 'warp-media-image', {
directory: path.join(__dirname, '/../../media'),
});
*/
//7. Create container for the task definition from ECR image
var player_container = taskDef.addContainer("warp-player-container", {
image: ecs.ContainerImage.fromDockerImageAsset(player_image),
logging: log,
portMappings: [{
containerPort: 80,
hostPort: 80,
protocol: ecs.Protocol.TCP
}, {
containerPort: 443,
hostPort: 443,
protocol: ecs.Protocol.TCP
}],
})
var server_container = taskDef.addContainer("warp-server-container", {
image: ecs.ContainerImage.fromDockerImageAsset(server_image),
logging: log,
portMappings: [{
containerPort: 443,
hostPort: 443,
protocol: ecs.Protocol.UDP
}],
})
//9. Create the NLB using the above VPC.
const nlb = new elb.NetworkLoadBalancer(this, 'warp-nlb', {
loadBalancerName: 'warp-nlb',
vpc,
internetFacing: true,
});
// ?? Create a DNS entry for the nlb
const zone = route53.HostedZone.fromHostedZoneAttributes(this, "warp-zone", {
hostedZoneId: "Z0166044CZ3H7MIDXT7P", // Unfortunately we need to hard-code the route53 zone
zoneName: "quic.video",
});
const record = new route53.ARecord(this, "warp-record", {
zone: zone,
target: route53.RecordTarget.fromAlias(new route53_targets.LoadBalancerTarget(nlb)),
})
// Route based on latency.
const recordSet = (record.node.defaultChild as route53.CfnRecordSet);
recordSet.region = props.env?.region;
recordSet.setIdentifier = props.env?.region;
//10. Add a listener on a particular port for the NLB
const http_listener = nlb.addListener('warp-http-listener', {
protocol: elb.Protocol.TCP,
port: 80,
});
const https_listener = nlb.addListener('warp-https-listener', {
protocol: elb.Protocol.TCP_UDP,
port: 443,
});
//11. Create your own security Group using VPC
const sg = new ec2.SecurityGroup(this, 'warp-player-sg', {
securityGroupName: "warp-player-sg",
vpc: vpc,
allowAllOutbound: true,
});
//12. Add IngressRule to access the docker image
sg.addIngressRule(ec2.Peer.ipv4('0.0.0.0/0'), ec2.Port.tcp(80), 'HTTP');
sg.addIngressRule(ec2.Peer.ipv4('0.0.0.0/0'), ec2.Port.tcp(443), 'HTTPS');
sg.addIngressRule(ec2.Peer.ipv4('0.0.0.0/0'), ec2.Port.udp(443), 'HTTP/3');
//13. Create Fargate Service from cluster, task definition and the security group
const service = new ecs.FargateService(this, 'warp-service', {
cluster: cluster,
taskDefinition: taskDef,
assignPublicIp: true,
serviceName: "warp-service",
securityGroups: [sg],
});
//14. Add fargate service to the listener
http_listener.addTargets('warp-http-target', {
targetGroupName: 'warp-http-target',
protocol: elb.Protocol.TCP,
port: 80,
targets: [service],
deregistrationDelay: cdk.Duration.seconds(300)
});
https_listener.addTargets('warp-https-target', {
targetGroupName: 'warp-https-target',
protocol: elb.Protocol.TCP_UDP,
port: 443,
targets: [service],
deregistrationDelay: cdk.Duration.seconds(300)
});
}
}

6020
deploy/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

32
deploy/package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "deploy",
"version": "0.1.0",
"bin": {
"deploy": "bin/deploy.js"
},
"scripts": {
"build": "tsc",
"watch": "tsc -w",
"test": "jest",
"cdk": "cdk"
},
"devDependencies": {
"@types/jest": "^29.2.3",
"@types/node": "18.11.9",
"aws-cdk": "^2.54.0",
"jest": "^29.3.1",
"ts-jest": "^29.0.3",
"ts-node": "^10.9.1",
"typescript": "~4.9.3"
},
"dependencies": {
"@aws-cdk/aws-ec2": "^1.191.0",
"@aws-cdk/aws-ecs": "^1.191.0",
"@aws-cdk/aws-ecs-patterns": "^1.191.0",
"@aws-cdk/aws-iam": "^1.191.0",
"@aws-cdk/aws-s3-assets": "^1.191.0",
"aws-cdk-lib": "^2.63.0",
"constructs": "^10.0.0",
"source-map-support": "^0.5.21"
}
}

View File

@ -0,0 +1,17 @@
// import * as cdk from 'aws-cdk-lib';
// import { Template } from 'aws-cdk-lib/assertions';
// import * as Deploy from '../lib/deploy-stack';
// example test. To run these tests, uncomment this file along with the
// example resource in lib/deploy-stack.ts
test('SQS Queue Created', () => {
// const app = new cdk.App();
// // WHEN
// const stack = new Deploy.DeployStack(app, 'MyTestStack');
// // THEN
// const template = Template.fromStack(stack);
// template.hasResourceProperties('AWS::SQS::Queue', {
// VisibilityTimeout: 300
// });
});

30
deploy/tsconfig.json Normal file
View File

@ -0,0 +1,30 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "commonjs",
"lib": [
"es2020"
],
"declaration": true,
"strict": true,
"noImplicitAny": true,
"strictNullChecks": true,
"noImplicitThis": true,
"alwaysStrict": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": false,
"inlineSourceMap": true,
"inlineSources": true,
"experimentalDecorators": true,
"strictPropertyInitialization": false,
"typeRoots": [
"./node_modules/@types"
]
},
"exclude": [
"node_modules",
"cdk.out"
]
}

3108
deploy/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

19
player/Dockerfile Normal file
View File

@ -0,0 +1,19 @@
FROM ubuntu
# Install dependencies
RUN apt-get update && apt-get install -y nginx certbot python3-certbot-nginx
# Contains the final certificates created by certbot
VOLUME /etc/letsencrypt
# Application configuration for our site
COPY nginx/quic.video.conf /etc/nginx/conf.d/quic.video.conf
# The script to init and run nginx
COPY nginx/run.sh /run/warp-player.sh
# Copy over the web contents
COPY dist/* /var/www/quic.video
# Run the shell script
CMD /run/warp-player.sh

View File

@ -0,0 +1,6 @@
server {
listen 80;
listen [::]:80;
server_name quic.video;
root /var/www/quic.video;
}

9
player/nginx/run.sh Executable file
View File

@ -0,0 +1,9 @@
#!/bin/bash
set -euxo pipefail
# Try to generate a certificate that expires in 90 days.
# This will listen on port 80 and serve a challenge file, proving we own the domain.
certbot --nginx --email kixelated@gmail.com -d quic.video --agree-tos
# The certbot nginx plugin will automatically append the certs to the configuration.
nginx

10
server/Dockerfile Normal file
View File

@ -0,0 +1,10 @@
FROM golang
WORKDIR /usr/src/warp
ENV GOPRIVATE=*
COPY . .
RUN go build -v -o /usr/local/bin/warp-server .
CMD [ "warp-server" ]