Amazon ECS Deployment with CloudFormation: Difference between revisions

From NovaOrdis Knowledge Base
Jump to navigation Jump to search
 
(34 intermediate revisions by the same user not shown)
Line 11: Line 11:
=Procedure=
=Procedure=


Declare the [[Amazon_ECS_Concepts#Task_Definition|task definition]]:
Declare a set of configuration parameters that abstract out operational details, such as project name, etc. Then declare the [[Amazon_ECS_Concepts#Task_Definition|task definition]]:
 
==Prerequisites==
 
Parameters:
  ProjectID:
    Type: String
    Default: themyscira
    Description: |
      The key that uniquely identifies a resource consumer (service, tool that requires AWS resources, etc.).
      The project ID is used as root when assembling the names of associated resources.
  Image:
    Type: String
  Tag:
    Type: String


==AWS::ECS::TaskDefinition==
==AWS::ECS::TaskDefinition==


  Resources:
  Resources:
  ...
   [[Amazon_ECS_Concepts#Task_Definition|TaskDefinition]]:
   [[Amazon_ECS_Concepts#Task_Definition|TaskDefinition]]:
     <font color=teal>Type: AWS::ECS::TaskDefinition</font>
     <font color=teal>Type: AWS::ECS::TaskDefinition</font>
     Properties:
     Properties:
       [[Amazon_ECS_Concepts#Family|Family]]: 'themyscira'
       [[Amazon_ECS_Concepts#Family|Family]]: !Ref ProjectID
       [[Amazon_ECS_Concepts#Compatibilities|RequiresCompatibilities]]: ['FARGATE']
       [[Amazon_ECS_Concepts#Compatibilities|RequiresCompatibilities]]: ['FARGATE']
       [[Amazon_ECS_Concepts#Task_Role|TaskRoleArn]]: !GetAtt TaskRole.Arn
       [[Amazon_ECS_Concepts#Task_Role|TaskRoleArn]]: !GetAtt TaskRole.Arn
Line 27: Line 42:
       [[Amazon_ECS_Concepts#Task_CPU|Cpu]]: '2048'
       [[Amazon_ECS_Concepts#Task_CPU|Cpu]]: '2048'
       [[Amazon_ECS_Concepts#Container_Definition|ContainerDefinitions]]:
       [[Amazon_ECS_Concepts#Container_Definition|ContainerDefinitions]]:
       - [[Amazon_ECS_Concepts#Container_Name|Name]]: 'themyscira'
       - [[Amazon_ECS_Concepts#Container_Name|Name]]: !Sub '${ProjectID}-container'
         [[Amazon_ECS_Concepts#Container_Image|Image]]: !Sub ${Image}:${Tag}
         [[Amazon_ECS_Concepts#Container_Image|Image]]: !Sub ${Image}:${Tag}
         Essential: 'true'
         [[Amazon_ECS_Concepts#Essential_Flag|Essential]]: 'true'
         Cpu: '2048'
         [[Amazon_ECS_Concepts#Container_Memory|Memory]]: '4096'
         Memory: '4096'
        [[Amazon_ECS_Concepts#Container_CPU|Cpu]]: '2048'
         Environment:
         [[Amazon_ECS_Concepts#Container_Port_Mappings|PortMappings]]:
        - HostPort: 10002
          ContainerPort: 10002
         [[Amazon_ECS_Concepts#Container_Environment|Environment]]:
         - Name: SPRING_PROFILES_ACTIVE
         - Name: SPRING_PROFILES_ACTIVE
           Value: 'something'
           Value: 'something'
          
         [[Amazon_ECS_Concepts#Container_Log_Configuration|LogConfiguration]]:
        PortMappings:
        - HostPort: 10002
          ContainerPort: 10002
        LogConfiguration:
           LogDriver: "awslogs"
           LogDriver: "awslogs"
           Options:
           Options:
             awslogs-group: 'some-group'
             awslogs-group: !Ref ServiceLogGroup
             awslogs-region: !Sub ${AWS::Region}
             awslogs-region: !Sub ${AWS::Region}
             awslogs-stream-prefix: 'some-prefix'
             awslogs-stream-prefix: 'task'


==Dependencies==
TaskRole and TaskExecutionRole, a service-specific ServiceLogGroup will also have to be declared, see [[#Dependencies|Dependencies]] below.
 
===Container Image Tag Considerations===
 
If "latest" is used as container image tag above (as ${Tag}), I've noticed that under circumstances that yet have to be elucidated, the task does not get recycled during a CodePipeline deployment phase, as if the image change does not get detected. This happens even if the underlying image tagged as "latest" undeniably changes from the current version used by the task. The workaround is to use a unique tag ID for each image, as the one provided the CodeBuild's [[AWS_CodeBuild_Concepts#CODEBUILD_RESOLVED_SOURCE_VERSION|CODEBUILD_RESOLVED_SOURCE_VERSION]]. This is the essence of it:


Declare the dependencies: tasks, etc.
1) Tag the image with CODEBUILD_RESOLVED_SOURCE_VERSION at build phase, and pass CODEBUILD_RESOLVED_SOURCE_VERSION to the deployment phase as part of the [[AWS_CodePipeline_Concepts#TemplateConfiguration|TemplateConfiguration]].


<font color=darkgray>TODO</font>
<syntaxhighlight lang='yaml'>
...
phases:
  install:
    commands:
      ...
      - if [ -z "${CODEBUILD_RESOLVED_SOURCE_VERSION}" ]; then echo "required CODEBUILD_RESOLVED_SOURCE_VERSION variable not set" 1>&2; exit 1; else echo "CODEBUILD_RESOLVED_SOURCE_VERSION=${CODEBUILD_RESOLVED_SOURCE_VERSION}"; fi
      ...   
  post_build:
    commands:
      ...
      - docker tag ${ECR_REPOSITORY_URI}:latest ${ECR_REPOSITORY_URI}:${CODEBUILD_RESOLVED_SOURCE_VERSION}
      - docker push ${ECR_REPOSITORY_URI}:${CODEBUILD_RESOLVED_SOURCE_VERSION}
      ...
      - echo "{\"Parameters\":{... \"CodebuildResolvedSourceVersion\":\"${CODEBUILD_RESOLVED_SOURCE_VERSION}\"}}" > ${DEPLOYMENT_STACK_CONFIG_FILE}
artifacts:
  files:
    - ${DEPLOYMENT_STACK_CONFIG_FILE}
</syntaxhighlight>


2) Declare as deployment stack parameter and used it in the Task definition:


=Organizatorium=
<syntaxhighlight lang='yaml'>
...
Parameters:
  ...
  CodebuildResolvedSourceVersion:
    Type: String
    Description: >
      The identifier for the version of a build's source code. For GitHub, it is the commit ID.
...
Resources:
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ...
      ContainerDefinitions:
        - Name: !Sub ${ApplicationName}-container
          ...
          Image: !Sub ${EcrRepositoryUri}:${CodebuildResolvedSourceVersion}
</syntaxhighlight>


==AWS::ECS::Service==
==AWS::ECS::Service==


  Resources:
  Resources:
  ...
   ServiceDefinition:
   ServiceDefinition:
     Type: AWS::ECS::Service
     Type: AWS::ECS::Service
     DependsOn: LoadBalancerListener
     DependsOn:
      - LoadBalancerListener
     Properties:
     Properties:
       ServiceName: themyscira
       [[Amazon_ECS_Concepts#Service_Name|ServiceName]]: !Ref ProjectID
       LaunchType: FARGATE
       [[Amazon_ECS_Concepts#Launch_Type_2|LaunchType]]: FARGATE
       Cluster: 'some-cluster'
       [[Amazon_ECS_Concepts#Cluster_2|Cluster]]: 'playground'
       TaskDefinition: !Ref TaskDefinition
       [[Amazon_ECS_Concepts#Task_Definition_2|TaskDefinition]]: !Ref TaskDefinition
       DesiredCount: 1
       [[Amazon_ECS_Concepts#Desired_Count|DesiredCount]]: 1
       HealthCheckGracePeriodSeconds: 60
       [[Amazon_ECS_Concepts#Health_Check_Grace_Period|HealthCheckGracePeriodSeconds]]: 60
      [[Amazon_ECS_Concepts#Service_Load_Balancing|LoadBalancers]]:
      - ContainerName: !Sub '${ProjectID}-container'
        ContainerPort: 10002
        TargetGroupArn: !Ref TargetGroup
       NetworkConfiguration:
       NetworkConfiguration:
         AwsvpcConfiguration:
         AwsvpcConfiguration:
Line 78: Line 139:
       ServiceRegistries:
       ServiceRegistries:
       - RegistryArn: !GetAtt ServiceDiscovery.Arn
       - RegistryArn: !GetAtt ServiceDiscovery.Arn
      LoadBalancers:
      - ContainerName: 'some-name'
        ContainerPort: 10002
        TargetGroupArn: !Ref TargetGroup


==Create a Cluster==
The service depends on load balancing infrastructure.
 
==Dependencies==
 
<font color=darkgray>TODO:
* TaskRole
* TaskExecutionRole
* ServiceLogGroup
* Load balancing infrastructure
</font>
 
=Create a Cluster=
 
{{External|[https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-cluster.html AWS::ECS::Cluster]}}
 
Resources:
  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      [[Amazon_ECS_Concepts#Cluster_Name|ClusterName]]: <''cluster-name''>

Latest revision as of 18:35, 24 April 2019

External

Internal

Procedure

Declare a set of configuration parameters that abstract out operational details, such as project name, etc. Then declare the task definition:

Prerequisites

Parameters:
  ProjectID:
    Type: String
    Default: themyscira
    Description: |
     The key that uniquely identifies a resource consumer (service, tool that requires AWS resources, etc.).
     The project ID is used as root when assembling the names of associated resources.
  Image:
    Type: String
  Tag:
    Type: String

AWS::ECS::TaskDefinition

Resources:
  ...
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      Family: !Ref ProjectID
      RequiresCompatibilities: ['FARGATE']
      TaskRoleArn: !GetAtt TaskRole.Arn
      ExecutionRoleArn: !GetAtt TaskExecutionRole.Arn
      NetworkMode: 'awsvpc'
      Memory: '4096'
      Cpu: '2048'
      ContainerDefinitions:
      - Name: !Sub '${ProjectID}-container'
        Image: !Sub ${Image}:${Tag}
        Essential: 'true'
        Memory: '4096'
        Cpu: '2048'
        PortMappings:
        - HostPort: 10002
          ContainerPort: 10002
        Environment:
        - Name: SPRING_PROFILES_ACTIVE
          Value: 'something'
        LogConfiguration:
          LogDriver: "awslogs"
          Options:
            awslogs-group: !Ref ServiceLogGroup
            awslogs-region: !Sub ${AWS::Region}
            awslogs-stream-prefix: 'task'

TaskRole and TaskExecutionRole, a service-specific ServiceLogGroup will also have to be declared, see Dependencies below.

Container Image Tag Considerations

If "latest" is used as container image tag above (as ${Tag}), I've noticed that under circumstances that yet have to be elucidated, the task does not get recycled during a CodePipeline deployment phase, as if the image change does not get detected. This happens even if the underlying image tagged as "latest" undeniably changes from the current version used by the task. The workaround is to use a unique tag ID for each image, as the one provided the CodeBuild's CODEBUILD_RESOLVED_SOURCE_VERSION. This is the essence of it:

1) Tag the image with CODEBUILD_RESOLVED_SOURCE_VERSION at build phase, and pass CODEBUILD_RESOLVED_SOURCE_VERSION to the deployment phase as part of the TemplateConfiguration.

...
phases:
  install:
    commands:
      ...
      - if [ -z "${CODEBUILD_RESOLVED_SOURCE_VERSION}" ]; then echo "required CODEBUILD_RESOLVED_SOURCE_VERSION variable not set" 1>&2; exit 1; else echo "CODEBUILD_RESOLVED_SOURCE_VERSION=${CODEBUILD_RESOLVED_SOURCE_VERSION}"; fi
      ...     
  post_build:
    commands:
      ...
      - docker tag ${ECR_REPOSITORY_URI}:latest ${ECR_REPOSITORY_URI}:${CODEBUILD_RESOLVED_SOURCE_VERSION}
      - docker push ${ECR_REPOSITORY_URI}:${CODEBUILD_RESOLVED_SOURCE_VERSION}
      ...
      - echo "{\"Parameters\":{... \"CodebuildResolvedSourceVersion\":\"${CODEBUILD_RESOLVED_SOURCE_VERSION}\"}}" > ${DEPLOYMENT_STACK_CONFIG_FILE}
artifacts:
  files:
    - ${DEPLOYMENT_STACK_CONFIG_FILE}

2) Declare as deployment stack parameter and used it in the Task definition:

...
Parameters:
  ...
  CodebuildResolvedSourceVersion:
    Type: String
    Description: >
      The identifier for the version of a build's source code. For GitHub, it is the commit ID.
...
Resources:
  TaskDefinition:
    Type: AWS::ECS::TaskDefinition
    Properties:
      ...
      ContainerDefinitions:
        - Name: !Sub ${ApplicationName}-container
          ...
          Image: !Sub ${EcrRepositoryUri}:${CodebuildResolvedSourceVersion}

AWS::ECS::Service

Resources:
  ...
  ServiceDefinition:
    Type: AWS::ECS::Service
    DependsOn:
      - LoadBalancerListener
    Properties:
      ServiceName: !Ref ProjectID
      LaunchType: FARGATE
      Cluster: 'playground'
      TaskDefinition: !Ref TaskDefinition
      DesiredCount: 1
      HealthCheckGracePeriodSeconds: 60
      LoadBalancers:
      - ContainerName: !Sub '${ProjectID}-container'
        ContainerPort: 10002
        TargetGroupArn: !Ref TargetGroup
      NetworkConfiguration:
        AwsvpcConfiguration:
          AssignPublicIp: DISABLED
          SecurityGroups:
          - !Ref ServiceSecurityGroup
          Subnets:
            - 'blue-subnet'
            - 'green-subnet'
      ServiceRegistries:
      - RegistryArn: !GetAtt ServiceDiscovery.Arn

The service depends on load balancing infrastructure.

Dependencies

TODO:

  • TaskRole
  • TaskExecutionRole
  • ServiceLogGroup
  • Load balancing infrastructure

Create a Cluster

AWS::ECS::Cluster
Resources:
  Cluster:
    Type: AWS::ECS::Cluster
    Properties:
      ClusterName: <cluster-name>