Monday, October 11, 2021

Creating ECS service using CloudFormation




In this post we will review a CloudFormation stack to create an ECS service. 

Using ECS we can quickly deploy our docker based service on the cloud. In this example we will use the ECS Fargate mode which is a serverless deployment.


We start with a VPC deployment, including 2 subnets and and internet gateway allowing access to the internet. 



vpc:
Type: AWS::EC2::VPC
Properties:
CidrBlock: 10.0.0.0/16
Tags:
- Key: Name
Value: backend-vpc

subnet1:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref vpc
CidrBlock: 10.0.1.0/24
AvailabilityZone: us-east-1a
Tags:
- Key: Name
Value: backend-subnet1

subnet2:
Type: AWS::EC2::Subnet
Properties:
VpcId: !Ref vpc
CidrBlock: 10.0.2.0/24
AvailabilityZone: us-east-1b
Tags:
- Key: Name
Value: backend-subnet2

internetGateway:
Type: AWS::EC2::InternetGateway
DependsOn: vpc
Properties:
Tags:
- Key: Name
Value: backend-igw

attachGateway:
Type: AWS::EC2::VPCGatewayAttachment
Properties:
VpcId: !Ref vpc
InternetGatewayId: !Ref internetGateway

routeTable1:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref vpc
Tags:
- Key: Name
Value: cuustomer-route-table1

routeTable2:
Type: AWS::EC2::RouteTable
Properties:
VpcId: !Ref vpc
Tags:
- Key: Name
Value: backend-route-table2

routeTableAssociate1:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref subnet1
RouteTableId: !Ref routeTable1

routeTableAssociate2:
Type: AWS::EC2::SubnetRouteTableAssociation
Properties:
SubnetId: !Ref subnet2
RouteTableId: !Ref routeTable2

publicRoute1:
Type: AWS::EC2::Route
DependsOn: attachGateway
Properties:
RouteTableId: !Ref routeTable1
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref internetGateway

publicRoute2:
Type: AWS::EC2::Route
DependsOn: attachGateway
Properties:
RouteTableId: !Ref routeTable2
DestinationCidrBlock: 0.0.0.0/0
GatewayId: !Ref internetGateway

vpcSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref vpc
GroupDescription: vpcSecurityGroup
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: backend-vpc-security-group



An optional, but recommended step, is to add a bastion server, that is a server we can bash into, and check connections within the vpc.



Parameters:
LatestAmiId:
Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2

Resources:

bastionSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
VpcId: !Ref vpc
GroupDescription: bastionSecurityGroup
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
Tags:
- Key: Name
Value: bastion-vpc-security-group

bastionServer1:
Type: AWS::EC2::Instance
Properties:
InstanceType: t2.micro
ImageId: !Ref LatestAmiId
SubnetId: !Ref subnet1
KeyName: ec2
SecurityGroupIds:
- !Ref bastionSecurityGroup
Tags:
- Key: Name
Value: bastion-server1

elasticIP1:
Type: AWS::EC2::EIP
Properties:
Domain: vpc
InstanceId: !Ref bastionServer1
Tags:
- Key: Name
Value: bastion-elastic-ip1


The last thing we need to do is to create a load balancer allowing access to the service, and an ECS service that maintains our desired amount of containers. Note that the task definition includes the tag of the image that we want to run. In this case we use an image from AWS ECR. To view the logs of the containers, we configure a CloudWatch log group.

Notice that the security group enables access to the containers both from the load balancer, and from the bastion server.




loadBalancerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: loadBalancerSecurityGroup
VpcId: !Ref vpc
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 80
ToPort: 80
CidrIp: 0.0.0.0/0

loadBalancer:
Type: AWS::ElasticLoadBalancingV2::LoadBalancer
Properties:
Name: backend-alb
Scheme: internet-facing
SecurityGroups:
- !Ref loadBalancerSecurityGroup
Subnets:
- !Ref subnet1
- !Ref subnet2

targetGroup:
Type: AWS::ElasticLoadBalancingV2::TargetGroup
Properties:
Name: backend-vpc-target-group
Port: 8080
Protocol: HTTP
TargetType: ip
VpcId: !Ref vpc

loadBalancerListener:
Type: AWS::ElasticLoadBalancingV2::Listener
Properties:
LoadBalancerArn: !Ref loadBalancer
Port: 80
Protocol: HTTP
DefaultActions:
- TargetGroupArn: !Ref targetGroup
Type: forward

taskExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: backend-task-role
AssumeRolePolicyDocument:
Statement:
- Effect: Allow
Principal:
Service: ecs-tasks.amazonaws.com
Action: 'sts:AssumeRole'
ManagedPolicyArns:
- arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

cloudwatchLogsGroup:
Type: 'AWS::Logs::LogGroup'
Properties:
LogGroupName: backend-log-group
RetentionInDays: 3

taskDefinition:
Type: AWS::ECS::TaskDefinition
Properties:
NetworkMode: awsvpc
ExecutionRoleArn: !Ref taskExecutionRole
RequiresCompatibilities:
- FARGATE
Cpu: 256
Memory: 512
ContainerDefinitions:
- Name: origin
Image: MY-ACCOUNT-NUMBER.dkr.ecr.us-east-1.amazonaws.com/MY-IMAGE:latest
PortMappings:
- ContainerPort: 8080
Protocol: tcp
Environment:
- Name: PORT
Value: 8080
LogConfiguration:
LogDriver: awslogs
Options:
awslogs-group: !Ref cloudwatchLogsGroup
awslogs-region: us-east-1
awslogs-stream-prefix: origin

ecsCluster:
Type: AWS::ECS::Cluster
Properties:
ClusterName: backend-cluster
CapacityProviders:
- FARGATE
- FARGATE_SPOT
DefaultCapacityProviderStrategy:
- CapacityProvider: FARGATE
Weight: 1
- CapacityProvider: FARGATE_SPOT
Weight: 1

containerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: containerSecurityGroup
VpcId: !Ref vpc
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref loadBalancerSecurityGroup
- IpProtocol: tcp
FromPort: 8080
ToPort: 8080
SourceSecurityGroupId: !Ref bastionSecurityGroup

ecsService:
Type: AWS::ECS::Service
DependsOn: loadBalancerListener
Properties:
Cluster: !Ref ecsCluster
DesiredCount: 2
TaskDefinition: !Ref taskDefinition
LaunchType: FARGATE
NetworkConfiguration:
AwsvpcConfiguration:
AssignPublicIp: ENABLED
Subnets:
- !Ref subnet1
- !Ref subnet2
SecurityGroups:
- !Ref containerSecurityGroup
LoadBalancers:
- ContainerName: origin
ContainerPort: 8080
TargetGroupArn: !Ref targetGroup



Final Notes


In ECS fargate mode, we do not need to manage the EC2 instances, but we still need to manage the VPC. Somehow I expected the VPC to be auto managed by AWS in Fargate mode.

Also notice the the CloudWatch logs will "suffer" from a few minutes delay.

No comments:

Post a Comment