This repository contains a set of configuration to setup a CI/CD pipeline for an AWS ECS Cluster. All configuration powered by AWS Cloud Development Kit. Hope you have fun ^^ Let's get started
In this repo, I give a step-by-step guide for deploying applications to ECS Fargate use AWS CodePipeline with AWS CDK. Hope you have fun ^^ Let's get started. The image below gives an overview of what we are going to create using CDK.
Install or update the [AWS CDK CLI] from npm (requires Node.js ≥ 14.15.0). We recommend using a version in Active LTS
npm i -g aws-cdk
Bootstrap Cdk assets if you run Cdk the first time:
cdk bootstrap
Deploy this to your account:
cdk deploy
To be able to connect to the ECS cluster you need to create an Application Load Balancer in front of the ECS service.
Create a new vpc with 2 public subnets:
const vpc = new ec2.Vpc(this, "ecs-devops-sandbox-vpc", {
vpcName: "ecs-devops-sandbox-vpc",
maxAzs: 2,
cidr: "10.0.0.0/16",
subnetConfiguration: [
{
name: "ecs-devops-sandbox-subnet-public",
subnetType: ec2.SubnetType.PUBLIC,
cidrMask: 24,
},
{
name: "ecs-devops-sandbox-subnet-private",
subnetType: ec2.SubnetType.PRIVATE_ISOLATED,
cidrMask: 24,
},
],
});
Create a new Application Load Balancer:
const elb = new elbv2.ApplicationLoadBalancer(
this,
"ecs-devops-sandbox-elb",
{
vpc: vpc,
vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC },
internetFacing: true,
}
);
Create new target group with health check config for containers to be deployed to:
const targetGroupBlue = new elbv2.ApplicationTargetGroup(this, "target-group", {
targetType: elbv2.TargetType.IP,
protocol: elbv2.ApplicationProtocol.HTTP,
port: 8080,
vpc: vpc,
healthCheck: {
// My custom health check
path: "/api/v1/health",
},
});
Create a new HTTP listener for HTTP requests around the world on port 80:
const listener = elb.addListener("ecs-devops-sandbox-listener", {
port: 80,
open: true,
});
listener.addTargetGroups("ecs-devops-sandbox-target-group", {
targetGroups: [targetGroupBlue],
});
Create a new Security Group and attach it to Application Load Balancer:
const elbSG = new ec2.SecurityGroup(this, "ecs-devops-sandbox-elb-sg", {
vpc: vpc,
allowAllOutbound: true,
});
// Allow access from around the world
elbSG.addIngressRule(
ec2.Peer.anyIpv4(),
ec2.Port.tcp(80),
"Allow HTTP traffic from the world"
);
// Attach the ELB to the Security Group
elb.addSecurityGroup(elbSG);
This section helps to create all the resources in ECS and connects them to the application load balancer. It creates the following resources:
Create a new ECS cluster:
const cluster = new ecs.Cluster(this, "ecs-devops-sandbox-cluster", {
clusterName: "ecs-devops-sandbox-cluster",
vpc: vpc,
});
Create a new ECS execution role help your cluster has permission for pulling image from AWS ECR and pushing logs to AWS Cloudwatch:
const executionRole = new iam.Role(
this,
"ecs-devops-sandbox-execution-role",
{
assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
roleName: "ecs-devops-sandbox-execution-role",
}
);
executionRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
resources: ["*"],
actions: [
"ecr:GetAuthorizationToken",
"ecr:BatchCheckLayerAvailability",
"ecr:GetDownloadUrlForLayer",
"ecr:BatchGetImage",
"logs:CreateLogStream",
"logs:PutLogEvents",
],
})
);
You also need to create a task role that help the task and its containers can access AWS Resources through IAM Role:
const taskRole = new iam.Role(this, "ecs-devops-sandbox-task-role", {
assumedBy: new iam.ServicePrincipal("ecs-tasks.amazonaws.com"),
roleName: "ecs-devops-sandbox-task-role",
description: "ECS Task Role",
});
// Allow sendEmail
taskRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
resources: ["*"],
actions: ["ses:SendEmail"],
})
);
Create a new ECS task definition:
const taskDefinition = new ecs.FargateTaskDefinition(
this,
"ecs-devops-sandbox-task-definition",
{
cpu: 256,
memoryLimitMiB: 512,
executionRole: executionRole,
family: "ecs-devops-sandbox-task-definition",
taskRole: taskRole,
}
);
Create a new docker container including the image to use:
const container = taskDefinition.addContainer(
"ecs-devops-sandbox-container",
{
image: ecs.ContainerImage.fromRegistry("amazon/amazon-ecs-sample"),
memoryReservationMiB: 512,
environment: {
SANDBOX_ELB_DNS: elb.loadBalancerDnsName,
},
// Store the logs in cloudwatch
logging: new ecs.AwsLogDriver({ streamPrefix: "ecs-devops-sandbox" }),
}
);
Mapping port for containers:
container.addPortMappings({ containerPort: 80 });
Create a new Security groups to allow connections from the application load balancer to the fargate containers:
const serviceSG = new ec2.SecurityGroup(
this,
"ecs-devops-sandbox-service-sg",
{
vpc: vpc,
allowAllOutbound: true,
}
);
serviceSG.connections.allowFrom(
elbSG,
ec2.Port.allTcp(),
"Allow traffic from the ELB"
);
Create a new ECS Fargate Service user for deploying tasks:
const service = new ecs.FargateService(this, "ecs-devops-sandbox-service", {
cluster: cluster,
taskDefinition: taskDefinition,
securityGroups: [serviceSG],
assignPublicIp: true,
desiredCount: 1,
serviceName: "ecs-devops-sandbox-service",
});
Attach ECS Fargate Service to Target Group that we've created before:
service.attachToApplicationTargetGroup(targetGroupBlue);
Create a new Scalable Target for tasks based on CPU and Memory Utilization:
const scalableTarget = service.autoScaleTaskCount({
maxCapacity: 3,
minCapacity: 1,
});
scalableTarget.scaleOnCpuUtilization("ecs-devops-sandbox-cpu-scaling", {
targetUtilizationPercent: 50,
});
scalableTarget.scaleOnMemoryUtilization(
"ecs-devops-sandbox-memory-scaling",
{
targetUtilizationPercent: 50,
}
);
To perform blue/green deployment with ECS and CodePipeline, you need to create some extra resources:
Create a new Target Group for Green Environment that be used by CodePipeline:
const targetGroupGreen = new elbv2.ApplicationTargetGroup(
this,
"target-group-green",
{
targetType: elbv2.TargetType.IP,
protocol: elbv2.ApplicationProtocol.HTTP,
port: 8080,
vpc: vpc,
healthCheck: {
// My custom health check
path: "/api/v1/health",
},
}
);
You also need a service role that can be used by AWS CodeDeploy to perform actions in your ECS Cluster (for information, please read this documenation: https://docs.aws.amazon.com/AmazonECS/latest/developerguide/codedeploy_IAM_role.html):
const ecsCodeDeployRole = new iam.Role(this, "ecs-codedeploy-role", {
assumedBy: new iam.ServicePrincipal("codedeploy.amazonaws.com"),
roleName: "ecs-codedeploy-role",
description: "ECS CodeDeploy Role",
});
ecsCodeDeployRole.addManagedPolicy(
iam.ManagedPolicy.fromAwsManagedPolicyName("AWSCodeDeployRoleForECS")
);
ecsCodeDeployRole.addToPolicy(
new iam.PolicyStatement({
effect: iam.Effect.ALLOW,
resources: [taskRole.roleArn, executionRole.roleArn],
actions: ["iam:PassRole"],
})
);