Created
July 10, 2021 02:04
-
-
Save esalaza/73387324bcf4a69296e6e343945d839b to your computer and use it in GitHub Desktop.
This file contains hidden or 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
| # Template for a load-balanced ECS Fargate Web service (only plain HTTP currently) deployed from | |
| # an ECR image which will be published from CI/CD. The ECS Fargate service will also be updated | |
| # from CI/CD. Env vars are supplied to the Web Service via S3 env files. Permissions are added to | |
| # the supplied CI/CD user to be able to use ECR/ECS. | |
| # | |
| # Based on https://github.com/1Strategy/fargate-cloudformation-example/blob/master/fargate.yaml | |
| # | |
| # Pre-requisites: | |
| # - VPC with 2 public subnets | |
| # - User which will push to ECR and update the ECS service (through CI/CD, 'buildkite-dev' by | |
| # default) | |
| # Notes | |
| # - The name of the stack must not contain upperletter characters, otherwise, the ECR repo step | |
| # will fail | |
| # - Sample ECR instructions (from CI/CD): | |
| # - IMAGE_REPO="<account-id>.dkr.ecr.us-east-1.amazonaws.com/<repo-name>" | |
| # - IMAGE_BUILD_TAGGED="${IMAGE_REPO}:${BUILDKITE_BUILD_NUMBER}" | |
| # - IMAGE_LATEST_TAGGED="${IMAGE_REPO}:latest" | |
| # - docker build -f .../Dockerfile -t $IMAGE_BUILD_TAGGED . | |
| # - docker tag $IMAGE_BUILD_TAGGED $IMAGE_LATEST_TAGGED | |
| # - docker push $IMAGE_BUILD_TAGGED | |
| # - docker push $IMAGE_LATEST_TAGGED | |
| # - Sample ECS instructions (from CI/CD): | |
| # - aws ecs update-service --cluster <cluster-name> --service <service-name> --force-new-deployment --no-cli-pager | |
| # - aws ecs wait services-stable --cluster <cluster-name> --services <service-name> | |
| # TODO | |
| # - HTTPS | |
| # - Database and bastion server, S3 bucket for frontend | |
| # - Auto-scaling | |
| # - No --force-new-deployment | |
| # - ...a lot | |
| AWSTemplateFormatVersion: 2010-09-09 | |
| Description: Infrastructure for the backend Web service | |
| Parameters: | |
| VPC: | |
| Type: AWS::EC2::VPC::Id | |
| PublicSubnetA: | |
| Type: AWS::EC2::Subnet::Id | |
| PublicSubnetB: | |
| Type: AWS::EC2::Subnet::Id | |
| CicdUser: | |
| Type: String | |
| Default: buildkite-dev | |
| S3EnvVarsBucket: | |
| Type: String | |
| Default: arn:aws:s3:::<s3-bucket-name> | |
| S3EnvVarsFile: | |
| Type: String | |
| Default: backend.env | |
| ContainerPort: | |
| Type: Number | |
| Default: 5000 | |
| LoadBalancerHttpPort: | |
| Type: Number | |
| Default: 80 | |
| HealthCheckPath: | |
| Type: String | |
| Default: / | |
| LogGroupName: | |
| Type: String | |
| Default: /company/project/backend | |
| Resources: | |
| ECRRepository: | |
| Type: AWS::ECR::Repository | |
| Properties: | |
| RepositoryName: !Sub ${AWS::StackName}-repository | |
| RepositoryPolicyText: | |
| Version: "2012-10-17" | |
| Statement: | |
| - Sid: AllowPushPull | |
| Effect: Allow | |
| Principal: | |
| AWS: | |
| - !Sub arn:aws:iam::${AWS::AccountId}:user/${CicdUser} | |
| Action: | |
| - "ecr:BatchCheckLayerAvailability" | |
| - "ecr:BatchGetImage" | |
| - "ecr:CompleteLayerUpload" | |
| - "ecr:GetDownloadUrlForLayer" | |
| - "ecr:InitiateLayerUpload" | |
| - "ecr:PutImage" | |
| - "ecr:UploadLayerPart" | |
| Cluster: | |
| Type: AWS::ECS::Cluster | |
| Properties: | |
| ClusterName: !Sub ${AWS::StackName}-ecs-cluster | |
| TaskDefinition: | |
| Type: AWS::ECS::TaskDefinition | |
| Properties: | |
| # Name of the task definition. Subsequent versions of the task definition are grouped together under this name. | |
| Family: !Sub ${AWS::StackName}-ecs-cluster-task | |
| # awsvpc is required for Fargate | |
| NetworkMode: awsvpc | |
| RequiresCompatibilities: | |
| - FARGATE | |
| # 256 (.25 vCPU) - Available memory values: 0.5GB, 1GB, 2GB | |
| # 512 (.5 vCPU) - Available memory values: 1GB, 2GB, 3GB, 4GB | |
| # 1024 (1 vCPU) - Available memory values: 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB | |
| # 2048 (2 vCPU) - Available memory values: Between 4GB and 16GB in 1GB increments | |
| # 4096 (4 vCPU) - Available memory values: Between 8GB and 30GB in 1GB increments | |
| Cpu: "512" | |
| # 0.5GB, 1GB, 2GB - Available cpu values: 256 (.25 vCPU) | |
| # 1GB, 2GB, 3GB, 4GB - Available cpu values: 512 (.5 vCPU) | |
| # 2GB, 3GB, 4GB, 5GB, 6GB, 7GB, 8GB - Available cpu values: 1024 (1 vCPU) | |
| # Between 4GB and 16GB in 1GB increments - Available cpu values: 2048 (2 vCPU) | |
| # Between 8GB and 30GB in 1GB increments - Available cpu values: 4096 (4 vCPU) | |
| Memory: 1GB | |
| # A role needed by ECS. | |
| # "The ARN of the task execution role that containers in this task can assume. All containers | |
| # in this task are granted the permissions that are specified in this role.". "There is an | |
| # optional task execution IAM role that you can specify with Fargate to allow your Fargate | |
| # tasks to make API calls to Amazon ECR." | |
| ExecutionRoleArn: !GetAtt ExecutionRole.Arn | |
| # "The Amazon Resource Name (ARN) of an AWS Identity and Access Management (IAM) role that | |
| # grants containers in the task permission to call AWS APIs on your behalf." | |
| TaskRoleArn: !Ref TaskRole | |
| ContainerDefinitions: | |
| - Name: !Sub ${AWS::StackName}-backend-container | |
| Image: !GetAtt ECRRepository.RepositoryUri | |
| EnvironmentFiles: | |
| - Value: !Sub ${S3EnvVarsBucket}/${S3EnvVarsFile} | |
| Type: "s3" | |
| PortMappings: | |
| - ContainerPort: !Ref ContainerPort | |
| # Send logs to CloudWatch Logs | |
| LogConfiguration: | |
| LogDriver: awslogs | |
| Options: | |
| awslogs-region: !Ref AWS::Region | |
| awslogs-group: !Ref LogGroup | |
| awslogs-stream-prefix: ecs-backend | |
| LogGroup: | |
| Type: AWS::Logs::LogGroup | |
| Properties: | |
| LogGroupName: !Ref LogGroupName | |
| RetentionInDays: 7 | |
| # A role needed by ECS | |
| ExecutionRole: | |
| Type: AWS::IAM::Role | |
| Properties: | |
| RoleName: !Sub ${AWS::StackName}-ecs-execution-role | |
| AssumeRolePolicyDocument: | |
| Statement: | |
| - Effect: Allow | |
| Principal: | |
| Service: ecs-tasks.amazonaws.com | |
| Action: "sts:AssumeRole" | |
| ManagedPolicyArns: | |
| - "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy" | |
| Policies: | |
| - PolicyDocument: | |
| Version: "2012-10-17" | |
| Statement: | |
| - Effect: Allow | |
| Action: | |
| - "s3:GetObject" | |
| Resource: | |
| - !Sub ${S3EnvVarsBucket}/${S3EnvVarsFile} | |
| - Effect: Allow | |
| Action: | |
| - "s3:GetBucketLocation" | |
| Resource: | |
| - !Ref S3EnvVarsBucket | |
| PolicyName: AllowS3GetObject | |
| # A role for the containers | |
| TaskRole: | |
| Type: AWS::IAM::Role | |
| Properties: | |
| RoleName: !Sub ${AWS::StackName}-ecs-executtaskion-role | |
| AssumeRolePolicyDocument: | |
| Statement: | |
| - Effect: Allow | |
| Principal: | |
| Service: ecs-tasks.amazonaws.com | |
| Action: "sts:AssumeRole" | |
| Service: | |
| Type: AWS::ECS::Service | |
| # This dependency is needed so that the load balancer is setup correctly in time | |
| DependsOn: | |
| - ListenerHTTP | |
| Properties: | |
| ServiceName: !Sub ${AWS::StackName}-ecs-service | |
| Cluster: !Ref Cluster | |
| TaskDefinition: !Ref TaskDefinition | |
| DeploymentConfiguration: | |
| MinimumHealthyPercent: 100 | |
| MaximumPercent: 200 | |
| DesiredCount: 2 | |
| # This may need to be adjusted if the container takes a while to start up | |
| HealthCheckGracePeriodSeconds: 30 | |
| LaunchType: FARGATE | |
| NetworkConfiguration: | |
| AwsvpcConfiguration: | |
| # change to DISABLED if you're using private subnets that have access to a NAT gateway | |
| AssignPublicIp: ENABLED | |
| Subnets: | |
| - !Ref PublicSubnetA | |
| - !Ref PublicSubnetB | |
| SecurityGroups: | |
| - !Ref ContainerSecurityGroup | |
| LoadBalancers: | |
| - ContainerName: !Sub ${AWS::StackName}-backend-container | |
| ContainerPort: !Ref ContainerPort | |
| TargetGroupArn: !Ref TargetGroup | |
| ContainerSecurityGroup: | |
| Type: AWS::EC2::SecurityGroup | |
| Properties: | |
| GroupDescription: !Sub ${AWS::StackName}-ecs-service-container-sg | |
| VpcId: !Ref VPC | |
| SecurityGroupIngress: | |
| - IpProtocol: tcp | |
| FromPort: !Ref ContainerPort | |
| ToPort: !Ref ContainerPort | |
| SourceSecurityGroupId: !Ref LoadBalancerSecurityGroup | |
| LoadBalancerSecurityGroup: | |
| Type: AWS::EC2::SecurityGroup | |
| Properties: | |
| GroupDescription: !Sub ${AWS::StackName}-load-balancer-security-group | |
| VpcId: !Ref VPC | |
| SecurityGroupIngress: | |
| - IpProtocol: tcp | |
| FromPort: !Ref LoadBalancerHttpPort | |
| ToPort: !Ref LoadBalancerHttpPort | |
| CidrIp: 0.0.0.0/0 | |
| ListenerHTTP: | |
| Type: AWS::ElasticLoadBalancingV2::Listener | |
| Properties: | |
| DefaultActions: | |
| - TargetGroupArn: !Ref TargetGroup | |
| Type: forward | |
| LoadBalancerArn: !Ref LoadBalancer | |
| Port: !Ref LoadBalancerHttpPort | |
| Protocol: HTTP | |
| TargetGroup: | |
| Type: AWS::ElasticLoadBalancingV2::TargetGroup | |
| Properties: | |
| HealthCheckIntervalSeconds: 10 | |
| # will look for a 200 status code by default unless specified otherwise | |
| HealthCheckPath: !Ref HealthCheckPath | |
| HealthCheckTimeoutSeconds: 5 | |
| UnhealthyThresholdCount: 2 | |
| HealthyThresholdCount: 2 | |
| Name: !Sub ${AWS::StackName}-target-group | |
| Port: !Ref ContainerPort | |
| Protocol: HTTP | |
| TargetGroupAttributes: | |
| - Key: deregistration_delay.timeout_seconds | |
| Value: "60" # default is 300 | |
| TargetType: ip | |
| VpcId: !Ref VPC | |
| LoadBalancer: | |
| Type: AWS::ElasticLoadBalancingV2::LoadBalancer | |
| Properties: | |
| LoadBalancerAttributes: | |
| # this is the default, but is specified here in case it needs to be changed | |
| - Key: idle_timeout.timeout_seconds | |
| Value: "60" | |
| Name: !Sub ${AWS::StackName}-load-balancer | |
| # "internal" is also an option | |
| Scheme: internet-facing | |
| SecurityGroups: | |
| - !Ref LoadBalancerSecurityGroup | |
| Subnets: | |
| - !Ref PublicSubnetA | |
| - !Ref PublicSubnetB | |
| # Permission error if this doesn't exist: An error occurred (AccessDeniedException) when calling | |
| # the UpdateService operation: User: arn:aws:iam::<id>:user/buildkite-dev is not authorized | |
| # to perform: ecs:UpdateService/ecs:DescribeServices on resource: | |
| # arn:aws:ecs:us-east-1:<id>:service/<cluster>/<service> | |
| ECSServiceUpdatePolicy: | |
| Type: AWS::IAM::ManagedPolicy | |
| Properties: | |
| ManagedPolicyName: !Sub ${AWS::StackName}-ecs-update-policy | |
| PolicyDocument: | |
| Version: "2012-10-17" | |
| Statement: | |
| - Effect: Allow | |
| Action: | |
| - "ecs:RegisterTaskDefinition" | |
| - "ecs:UpdateService" | |
| - "ecs:DescribeServices" | |
| Resource: | |
| - !Ref Service | |
| Users: [!Sub '${CicdUser}'] | |
| Outputs: | |
| LoadBalancerDNSName: | |
| Description: The DNS Name of the load balancer | |
| Value: !GetAtt LoadBalancer.DNSName |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment