Skip to content

Instantly share code, notes, and snippets.

@phillippbertram
Last active November 21, 2022 12:20
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save phillippbertram/080af4c27b6c826568e03fb7d5b8f6f0 to your computer and use it in GitHub Desktop.
Save phillippbertram/080af4c27b6c826568e03fb7d5b8f6f0 to your computer and use it in GitHub Desktop.
Example AWS-CDK Configuration for Wordpress running on Containers using AWS Fargate with shared EFS Volume. Hope it will Help, until https://github.com/aws/containers-roadmap/issues/825 is implemented ๐Ÿ˜Š
import * as cdk from '@aws-cdk/core';
import * as ec2 from '@aws-cdk/aws-ec2';
import * as ecs_patterns from '@aws-cdk/aws-ecs-patterns';
import * as ecs from '@aws-cdk/aws-ecs';
import * as rds from '@aws-cdk/aws-rds';
import * as efs from '@aws-cdk/aws-efs';
import * as custom_resources from '@aws-cdk/custom-resources';
// tslint:disable-next-line:no-var-requires
require('dotenv').config();
const DB_PORT = +(process.env.DB_PORT as string);
const DB_NAME = process.env.DB_NAME as string;
const DB_USER = process.env.DB_USER as string;
const DB_PASSWORD = process.env.DB_PASSWORD as string;
const wordpressRegistryName = 'wordpress'; // wordpress:php7.4-fpm-alpine'
export class WordpressWebsiteStack extends cdk.Stack {
constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
super(scope, id, props);
// GENERAL
const vpc = new ec2.Vpc(this, 'Vpc', {
// 2 is minimum requirement for cluster
maxAzs: 2,
// only create Public Subnets in order to prevent aws to create
// a NAT-Gateway which causes additional costs.
// This will create 1 public subnet in each AZ.
subnetConfiguration: [
{
name: 'Public',
subnetType: ec2.SubnetType.PUBLIC,
},
]
});
// DATABASE CONFIGURATION
// Security Group used for Database
const wordpressSg = new ec2.SecurityGroup(this, 'WordpressSG', {
vpc: vpc,
description: 'Wordpress SG',
});
// Database Cluster for Wordpress database
const dbCluster = new rds.DatabaseCluster(this, 'DBluster', {
clusterIdentifier: 'wordpress-db-cluster',
instances: 1,
defaultDatabaseName: DB_NAME,
engine: rds.DatabaseClusterEngine.AURORA, // TODO: AURORA_MYSQL?
port: DB_PORT,
masterUser: {
username: DB_USER,
password: cdk.SecretValue.plainText(DB_PASSWORD)
},
instanceProps: {
instanceType: ec2.InstanceType.of(ec2.InstanceClass.BURSTABLE2, ec2.InstanceSize.SMALL),
vpc,
securityGroup: wordpressSg,
}
});
// FARGATE CONFIGURATION
// ECS Cluster which will be used to host the Fargate services
const ecsCluster = new ecs.Cluster(this, 'ECSCluster', {
vpc: vpc,
});
// FARGATE CONTAINER SERVICE
const wordpressContainerPort = 80; // WORKAROUND
const fargateService = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'WordpressFargateService', {
cluster: ecsCluster, // Required
desiredCount: 1, // for now it is okay to have only one desired task running
cpu: 512, // Default is 256
memoryLimitMiB: 1024, // Default is 512
// WORKAROUND
// needs platform version 1.4.0 to mount EFS volumes
platformVersion: ecs.FargatePlatformVersion.VERSION1_4,
// because we are running tasks using the Fargate launch type in a public subnet, we must choose ENABLED
// for Auto-assign public IP when we launch the tasks.
// This allows the tasks to have outbound network access to pull an image.
// @see https://aws.amazon.com/premiumsupport/knowledge-center/ecs-pull-container-api-error-ecr/
assignPublicIp: true,
taskImageOptions: {
image: ecs.ContainerImage.fromRegistry(wordpressRegistryName),
containerName: 'wordpress',
containerPort: wordpressContainerPort, // WORKAROUND: This is part of the workaround (see below)
// WORKAROUND: removed as part of the workaround (see below)
// environment: {
// WORDPRESS_DB_HOST: dbCluster.clusterEndpoint.socketAddress,
// WORDPRESS_DB_USER: DB_USER,
// WORDPRESS_DB_PASSWORD: DB_PASSWORD,
// WORDPRESS_DB_NAME: DB_NAME,
// },
},
});
// Allow connection between Fargate service and wordpress database
fargateService.service.connections.addSecurityGroup(wordpressSg);
fargateService.service.connections.allowTo(wordpressSg, ec2.Port.tcp(DB_PORT));
// ===============================================================================================
// !!! EXCLUSION ZONE - ENTER AT YOUR OWN RISK !!!
//
// FARGATE EFS WORKAROUND
// @see https://gist.github.com/cajames/3daec680b1101c8358e2ff30dfadd52a
// ===============================================================================================
const efsSecurityGroupPort = 2049;
// Create the file system
const fileSystem = new efs.FileSystem(this, 'WordpressEFS', {
vpc,
lifecyclePolicy: efs.LifecyclePolicy.AFTER_14_DAYS, // transition files to the Infrequent Access (IA) storage class
performanceMode: efs.PerformanceMode.GENERAL_PURPOSE,
throughputMode: efs.ThroughputMode.BURSTING,
});
const volumeConfig = {
name: 'WordpressEfsVolume',
// this is the main config
efsVolumeConfiguration: {
fileSystemId: fileSystem.fileSystemId,
},
};
const mountPoints = [
{
containerPath: '/var/www/html/wp-content',
sourceVolume: volumeConfig.name,
readOnly: false,
},
];
// This object is the final task definition, which includes the
// EFS volume configurations. This definition 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: wordpressRegistryName,
logConfiguration: {
logDriver:
fargateService.taskDefinition.defaultContainer?.logDriverConfig
?.logDriver,
options:
fargateService.taskDefinition.defaultContainer?.logDriverConfig
?.options,
},
memory: 8192,
mountPoints,
name: fargateService.taskDefinition.defaultContainer?.containerName,
portMappings: [
{
containerPort: wordpressContainerPort,
protocol: 'tcp',
},
],
environment: [
{ name: 'WORDPRESS_DB_HOST', value: dbCluster.clusterEndpoint.socketAddress },
{ name: 'WORDPRESS_DB_USER', value: DB_USER },
{ name: 'WORDPRESS_DB_PASSWORD', value: DB_PASSWORD },
{ name: 'WORDPRESS_DB_NAME', value: DB_NAME },
],
},
],
cpu: '2048',
executionRoleArn: fargateService.taskDefinition.executionRole?.roleArn,
family: fargateService.taskDefinition.family,
memory: '8192',
networkMode: ecs.NetworkMode.AWS_VPC,
requiresCompatibilities: ['FARGATE'],
taskRoleArn: fargateService.taskDefinition.taskRole.roleArn,
volumes: [volumeConfig],
};
const createOrUpdateCustomTaskDefinition = {
service: 'ECS',
action: 'registerTaskDefinition',
outputPath: 'taskDefinition.taskDefinitionArn',
parameters: customTaskDefinitionJson,
physicalResourceId: custom_resources.PhysicalResourceId.fromResponse(
'taskDefinition.taskDefinitionArn'
),
};
const customTaskDefinition = new custom_resources.AwsCustomResource(
fargateService,
'CustomFargateTaskDefinition',
{
onCreate: createOrUpdateCustomTaskDefinition,
onUpdate: createOrUpdateCustomTaskDefinition,
policy: custom_resources.AwsCustomResourcePolicy.fromSdkCalls({
resources: custom_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, ec2.Port.tcp(efsSecurityGroupPort));
fargateService.service.connections.allowTo(fileSystem, ec2.Port.tcp(efsSecurityGroupPort));
// After creating the task definition custom resource, 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 ecs.CfnService)?.addPropertyOverride(
'TaskDefinition',
customTaskDefinition.getResponseField('taskDefinition.taskDefinitionArn')
);
}
}
@phillippbertram
Copy link
Author

unfortunately the Docker volume mounts to efs are not working as expected ๐Ÿ˜ข

@phillippbertram
Copy link
Author

As aws/containers-roadmap#825 is closed, you can now find an example for a production ready working version here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment