Last active
November 21, 2022 12:20
-
-
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 ๐
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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') | |
); | |
} | |
} |
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
unfortunately the Docker volume mounts to efs are not working as expected ๐ข