Skip to content

Commit

Permalink
feat(nginx-web): add component for ecs web service that uses nginx as…
Browse files Browse the repository at this point in the history
… a sidecar for end-to-end encryption
  • Loading branch information
briancaffey committed Mar 26, 2023
1 parent b0a5825 commit 2d93bac
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 1 deletion.
6 changes: 5 additions & 1 deletion src/components/ad-hoc/app/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as pulumi from "@pulumi/pulumi";
import { IamResources } from "../../internal/iam/ecs";
import { RedisEcsResources } from "../../internal/ecs/redis";
import { WebEcsService } from "../../internal/ecs/web";
import { WebEcsServiceWithNginx } from "../../internal/ecs/webWithNginx";
import { ManagementCommandTask } from "../../internal/ecs/managementCommand";
import { WorkerEcsService } from "../../internal/ecs/celery";
import { registerAutoTags } from "../../../util";
Expand Down Expand Up @@ -68,6 +69,7 @@ export class AdHocAppComponent extends pulumi.ComponentResource {
// TODO: lookup from ecr.getRepo / ecr.getImage?
const backendImage = `${accountId}.dkr.ecr.us-east-1.amazonaws.com/backend`;
const frontendImage = `${accountId}.dkr.ecr.us-east-1.amazonaws.com/frontend`;
const nginxImage = `${accountId}.dkr.ecr.us-east-1.amazonaws.com/backend-nginx`;

// ECS Cluster and Cluster Capacity Providers
const ecsClusterResources = new EcsClusterResources("EcsClusterResources", {
Expand Down Expand Up @@ -158,7 +160,9 @@ export class AdHocAppComponent extends pulumi.ComponentResource {
serviceDiscoveryNamespaceId: props.serviceDiscoveryNamespaceId,
}, { parent: this });

const apiService = new WebEcsService("ApiWebService", {
const apiService = new WebEcsServiceWithNginx("ApiWebService", {
nginxImage: nginxImage,
nginxPort: 443,
name: "gunicorn",
command: ["gunicorn", "-t", "1000", "-b", "0.0.0.0:8000", "--log-level", "info", "backend.wsgi"],
envVars,
Expand Down
204 changes: 204 additions & 0 deletions src/components/internal/ecs/webWithNginx/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import * as aws from "@pulumi/aws"
import * as pulumi from "@pulumi/pulumi";
import { CwLoggingResources } from "../../cw";

interface WebEcsServiceWithNginxProps {
name: string;
command: pulumi.Input<string[]>,
envVars?: pulumi.Output<{ "name": string, "value": string }[]>;
logRetentionInDays?: number;
port: number;
cpu?: string;
memory?: string;
//health check
healthCheckPath: string;
healthCheckInterval?: number;
healthCheckHealthyThreshold?: number;
// alb
listenerArn: pulumi.Output<string>;
pathPatterns?: string[];
hostName: pulumi.Output<string>;
// from base stack
appSgId: pulumi.Output<string>;
privateSubnets: pulumi.Output<string[]>;
vpcId: pulumi.Output<string>;
// inputs from this stack
image: string;
ecsClusterId: pulumi.Output<string>;
executionRoleArn: pulumi.Output<string>;
taskRoleArn: pulumi.Output<string>;

// nginx props
nginxImage: string;
nginxPort: number;
}

export class WebEcsServiceWithNginx extends pulumi.ComponentResource {
private memory: string;
private cpu: string;
private logRetentionInDays: number;
private healthCheckInterval: number;
private healthCheckHealthyThreshold: number;
private pathPatterns: string[];
public readonly listenerRule: aws.alb.ListenerRule;
public readonly serviceName: pulumi.Output<string>;

/**
* Creates a load balanced fargate service and associated CloudWatch resources
* @param name The _unique_ name of the resource.
* @param props Props to pass to AdHocBaseEnv component
* @param opts A bag of options that control this resource's behavior.
*/
constructor(name: string, props: WebEcsServiceWithNginxProps, opts?: pulumi.ResourceOptions) {
const stackName = pulumi.getStack();
const region = aws.getRegionOutput();
super(`pulumi-contrib:components:${props.name}WebEcsServiceWithNginx`, name, props, opts);

// set defaults
this.cpu = props.cpu ?? "256";
this.memory = props.memory ?? "512";
this.logRetentionInDays = props.logRetentionInDays ?? 1;
this.healthCheckInterval = props.healthCheckInterval ?? 5;
this.healthCheckHealthyThreshold = props.healthCheckHealthyThreshold ?? 2;
this.pathPatterns = props.pathPatterns ?? ["/*"];

const cwLoggingResources = new CwLoggingResources(`${props.name}CwLoggingResources`, {
name: props.name,
logRetentionInDays: this.logRetentionInDays
}, { parent: this });

const cwLoggingResourcesForNginx = new CwLoggingResources(`${props.name}CwLoggingResourcesNginx`, {
name: `${props.name}-nginx`,
logRetentionInDays: this.logRetentionInDays
}, { parent: this });

// aws ecs task definition
const taskDefinition = new aws.ecs.TaskDefinition(`${props.name}TaskDefinition`, {
containerDefinitions: pulumi.jsonStringify([
{
name: `${props.name}-nginx`,
image: props.nginxImage,
essential: true,
logConfiguration: {
logDriver: "awslogs",
options: {
"awslogs-group": cwLoggingResourcesForNginx.cwLogGroupName,
"awslogs-region": region.name,
"awslogs-stream-prefix": `${props.name}-nginx`
}
},
portMappings: [
{
containerPort: props.nginxPort,
hostPort: props.nginxPort,
protocol: "tcp"
}
],
},
{
name: props.name,
image: props.image,
command: props.command,
environment: props.envVars,
essential: true,
logConfiguration: {
logDriver: "awslogs",
options: {
"awslogs-group": cwLoggingResources.cwLogGroupName,
"awslogs-region": region.name,
"awslogs-stream-prefix": props.name
}
},
portMappings: [
{
containerPort: props.port,
hostPort: props.port,
protocol: "tcp"
}
],
}
]),
taskRoleArn: props.taskRoleArn,
executionRoleArn: props.executionRoleArn,
family: `${stackName}-${props.name}`,
networkMode: "awsvpc",
requiresCompatibilities: ["FARGATE"],
cpu: this.cpu,
memory: this.memory,
}, { parent: this });

const targetGroup = new aws.alb.TargetGroup(`${props.name}TargetGroup`, {
name: `${stackName}-${props.name}-tg`,
port: props.nginxPort,
protocol: "HTTPS",
targetType: "ip",
vpcId: props.vpcId,
healthCheck: {
timeout: 2,
protocol: "HTTPS",
port: "traffic-port",
path: props.healthCheckPath,
matcher: "200-399",
interval: this.healthCheckInterval,
unhealthyThreshold: 3,
healthyThreshold: this.healthCheckHealthyThreshold,
},
tags: {
Name: `${stackName}-${props.name}-tg`
}
}, { parent: this });

const ecsService = new aws.ecs.Service(`${props.name}WebService`, {
name: `${stackName}-${props.name}`,
cluster: props.ecsClusterId,
taskDefinition: taskDefinition.arn,
desiredCount: 1,
enableExecuteCommand: true,
capacityProviderStrategies: [
{
capacityProvider: "FARGATE_SPOT",
weight: 100,
},
{
capacityProvider: "FARGATE",
weight: 0,
},
],
loadBalancers: [{
targetGroupArn: targetGroup.arn,
containerName: `${props.name}-nginx`,
containerPort: props.nginxPort
}],
networkConfiguration: {
assignPublicIp: true,
securityGroups: [props.appSgId],
subnets: props.privateSubnets
}
}, {
parent: this,
ignoreChanges: ['taskDefinition', 'desiredCount']
});
this.serviceName = ecsService.name;

const listenerRule = new aws.alb.ListenerRule(`${props.name}ListenerRule`, {
listenerArn: props.listenerArn,
actions: [{
type: "forward",
targetGroupArn: targetGroup.arn
}],
conditions: [
{
pathPattern: {
values: this.pathPatterns
}
},
{
hostHeader: {
values: [props.hostName]
}
}
]
}, { parent: this });
this.listenerRule = listenerRule;
}
}

0 comments on commit 2d93bac

Please sign in to comment.