Skip to content

Instantly share code, notes, and snippets.

@cajames
Last active July 26, 2023 08:40
Show Gist options
  • Save cajames/3daec680b1101c8358e2ff30dfadd52a to your computer and use it in GitHub Desktop.
Save cajames/3daec680b1101c8358e2ff30dfadd52a to your computer and use it in GitHub Desktop.
Sample Fargate + EFS CDK Stack. Written up here: https://caj.ms/writing/aws-fargate-with-efs
import cdk = require("@aws-cdk/core");
import { Vpc, Port } from "@aws-cdk/aws-ec2";
import {
Cluster,
ContainerImage,
AwsLogDriver,
FargatePlatformVersion,
NetworkMode,
CfnService,
} from "@aws-cdk/aws-ecs";
import { Certificate } from "@aws-cdk/aws-certificatemanager";
import { ApplicationLoadBalancedFargateService } from "@aws-cdk/aws-ecs-patterns";
import { LogGroup, RetentionDays } from "@aws-cdk/aws-logs";
import { HostedZone } from "@aws-cdk/aws-route53";
import {
AwsCustomResource,
AwsCustomResourcePolicy,
PhysicalResourceId,
} from "@aws-cdk/custom-resources";
import {
FileSystem,
LifecyclePolicy,
PerformanceMode,
ThroughputMode,
} from "@aws-cdk/aws-efs";
export class FargateEfsStack extends cdk.Stack {
public serviceUrl: string;
constructor(
scope: cdk.App,
id: string,
props?: cdk.StackProps
) {
super(scope, id, props);
const vpc = Vpc.fromLookup(...);
const cluster = Cluster.fromClusterAttributes(...);
const hostedZone = HostedZone.fromLookup(...);
const certificateArn = cdk.Fn.importValue("...");
const certificate = Certificate.fromCertificateArn(
this,
"Certificate",
certificateArn
);
const containerImage = "..."; // For example something from a registry
// Create a public ALB Fargate Service, with a task definition, which
// we'll change in later steps.
const fargateService = new ApplicationLoadBalancedFargateService(
this,
"FargateService",
{
serviceName: id,
cluster,
certificate,
// need platform version 1.4.0 to mount EFS volumes
platformVersion: FargatePlatformVersion.VERSION1_4,
publicLoadBalancer: true,
domainName: "...",
domainZone: hostedZone,
taskImageOptions: {
image: ContainerImage.fromRegistry(containerImage),
family: id,
containerName: "app",
containerPort: 9999,
logDriver: new AwsLogDriver({
streamPrefix: "app",
logGroup: new LogGroup(this, "LogGroup", {
logGroupName: `/app/${id}`,
retention: RetentionDays.TWO_MONTHS,
}),
}),
},
}
);
// Create the file system
const fileSystem = new FileSystem(this, "AppEFS", {
vpc,
lifecyclePolicy: LifecyclePolicy.AFTER_14_DAYS,
performanceMode: PerformanceMode.GENERAL_PURPOSE,
throughputMode: ThroughputMode.BURSTING,
});
const volumeConfig = {
name: "efs-volume",
// this is the main config
efsVolumeConfiguration: {
fileSystemId: fileSystem.fileSystemId,
},
};
const mountPoints = [
{
containerPath: "/root",
sourceVolume: volumeConfig.name,
readOnly: false,
},
];
/*
This object is the final task definition, which includes the
EFS volume configurations. This definiton is created as a Custom Resource through
the aws-sdk. This is a stop-gap measure that will be replaced when this
capability is fully supported in CloudFormation and CDK.
*/
const customTaskDefinitionJson = {
containerDefinitions: [
{
essential: true,
image: containerImage,
logConfiguration: {
logDriver:
fargateService.taskDefinition.defaultContainer?.logDriverConfig
?.logDriver,
options:
fargateService.taskDefinition.defaultContainer?.logDriverConfig
?.options,
},
memory: 8192,
mountPoints,
name: fargateService.taskDefinition.defaultContainer?.containerName,
portMappings: [
{
containerPort: 9999,
protocol: "tcp",
},
],
},
],
cpu: "2048",
executionRoleArn: fargateService.taskDefinition.executionRole?.roleArn,
family: fargateService.taskDefinition.family,
memory: "8192",
networkMode: NetworkMode.AWS_VPC,
requiresCompatibilities: ["FARGATE"],
tags: [
],
taskRoleArn: fargateService.taskDefinition.taskRole.roleArn,
volumes: [volumeConfig],
};
const createOrUpdateCustomTaskDefinition = {
service: "ECS",
action: "registerTaskDefinition",
outputPath: "taskDefinition.taskDefinitionArn",
parameters: customTaskDefinitionJson,
physicalResourceId: PhysicalResourceId.fromResponse(
"taskDefinition.taskDefinitionArn"
),
};
const customTaskDefinition = new AwsCustomResource(
fargateService,
"CustomFargateTaskDefinition",
{
onCreate: createOrUpdateCustomTaskDefinition,
onUpdate: createOrUpdateCustomTaskDefinition,
policy: AwsCustomResourcePolicy.fromSdkCalls({
resources: AwsCustomResourcePolicy.ANY_RESOURCE,
}),
}
);
fargateService.taskDefinition.executionRole?.grantPassRole(
customTaskDefinition.grantPrincipal
);
fargateService.taskDefinition.taskRole.grantPassRole(
customTaskDefinition.grantPrincipal
);
// Need to add permissions to and from the file system to the target,
// or else the task will timeout trying to mount the file system.
fargateService.service.connections.allowFrom(fileSystem, Port.tcp(2049));
fargateService.service.connections.allowTo(fileSystem, Port.tcp(2049));
/*
After creating the task definition custom resouce, update the
fargate service to use the new task definition revision above.
This will get around the current limitation of not being able to create
ecs services with task definition arns.
*/
(fargateService.service.node.tryFindChild(
"Service"
) as CfnService)?.addPropertyOverride(
"TaskDefinition",
customTaskDefinition.getResponseField("taskDefinition.taskDefinitionArn")
);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment