Eloy's devlog
docker   docker-compose   AWS   deploy   CI   CD   ECS   ECR   Fargate   CloudFormation

AWS ECS에 docker 컨테이너를 배포 및 관리 - 2

summary

Docker compose 로 만든 어플리케이션을 AWS 환경에 지속적으로 통합, 배포하는 방법에 대해 알아본다.

AWS ECS에 docker 컨테이너를 배포 및 관리 두 번째 포스팅이다. 만약 이전 포스팅을 확인하지 못했다면 포스팅 링크를 통해 확인하고 본 포스팅을 읽기 바란다.

*이전 포스팅에서 만들었던 guest-book 어플리케이션을 가지고 진행한다.

*전체 소스코드는 링크에서 확인할 수 있다.

*reference

concept

AWS 환경에 docker 어플리케이션을 배포하는 컨셉은 다음과 같다.

  1. 기본 인프라를 구축한다.
    • VPC 등 인터넷 관련 인프라
    • ECS를 활용한 컨테이너 오케스트레이션 인프라
  2. Code Pipeline을 구축한다.
    1. github 레포지토리의 master 브랜치에 변동사항이 생긴다.
    2. Code Build가 docker의 각 서비스의 이미지를 이미지화 하고, 빌드된 이미지를 ECR에 업로드한다.
    3. compose 파일을 CloudFormation 파일로 변환한다.
    4. 기존 어플리케이션 인프라와 비교해 Change Set을 생성한다.
    5. 수동 승인 과정을 거쳐 ECS에 배포된다.

본 포스팅에서는 두 번째 항목인 Code Pipeline 구축에 대해서 다룬다.

사전 준비물

파이프라인을 클라우드 포메이션으로 생성하기 위해 아래와 같은 사전 준비물이 필요하다.

각 준비물을 어떻게 준비하는지 확인해보자.

github personal access token

github personal access token은 파이프라인에서 브랜치의 변동사항을 감지하기 위해 필요하다. github의 공식문서를 통해 발급하고 값을 따로 기억해두자. (code pipeline 에서는 repo 항목의 권한만 있으면 된다)

github-access-token-option

설정 후 generate버튼을 눌러 토큰을 발급하고, 값을 따로 저장해 둔다.

베이스 이미지 ECR

프로젝트의 파이프라인 중 build 섹션에서는 아래와 같은 작업을 수행한다.

이 때 Docker 파일에 저장된 베이스 이미지를 pull 한 뒤 레이어를 쌓아 서비스의 이미지를 생성하게 되는데, 이 베이스 이미지를 dockerhub가 아닌 ECR에서 땡겨온다. 이는 pipeline에서 사용하는 docker context의 타입이 ecs 타입이기 때문이다. ecs 타입의 docker context를 사용하는 경우 docker pull 실행시 dockerhub가 아닌 aws ECR 에서 이미지를 떙겨온다.

이를 위해 기존에 베이스 이미지로 사용하던 이미지를 dockerhub로 부터 pull 받아 ECR로 업로드 해야한다. guest-book-app에서 사용하는 redis 이미지를 pull 받은 뒤, ECR에 업로드 해 보자.

pull-docker-image

먼저 docker pull redis:latest 명령어로 redis의 최신 이미지를 pull 받자.

pull 을 받은 뒤, aws 콘솔에 접속하여 ECR 으로 접속하여 redis라는 이름으로 private 레포지토리를 생성한다. 생성 한 뒤 콘솔에서 레포지토리의 url을 복사헤두자.

create-ecr

다시 터미널로 돌아와 이전에 pull 받은 redis:latest 이미지를 docker tag 커맨드를 통해 ECR 형식으로 다시 태깅해주자.

docker tag redis:latest ${ECR 레포지토리 url}/redis:latest

태깅이 완료되면 프라이빗 레지스트리 인증을 통해 ECR 에 대해 도커 클라이언트 인증을 진행한다. 본 인증은 12시간 동안 유효하다는 점도 참고하자.

login-ecr

로그인이 완료되면 docker push ${ECR 레포지토리 url}/redis:latest 명령어를 통해 ECR에 redis:latest 이미지를 푸쉬하자.

push-result-to-ecr

성공적으로 push가 완료되면 콘솔에서 위와 같이 확인 할 수 있다. (frontend, backend 서비스에서 사용하는 node 이미지도 같은 방식으로 업로드 하면된다)

생성된 인프라 리소스 id, arn 확인

우리가 만들 code pipeline에서 기존에 만든 infra resource를 사용할 것이기 때문에 아래의 리소스 정보가 필요하다.

리소싀 정보는 aws cloudformation 콘솔에서 확인할 수 있다. 이전에 생성한 guest-book-compose-infra 에 들어가서 출력항목을 확인하자.

infra-cloudformation-output

(ECS cluster arn은 콘솔에서 해당 리소스에 접근하여 확인하도록 한다)

Code pipeline Cloudformation

프로젝트의 루트 디렉토리에 /pipeline/cloudformation.yaml 파일을 생성한다.

AWSTemplateFormatVersion: "2010-09-09"
Description: Change Set CodeDeploy Pipeline

Parameters:
  GitHubOAuthToken:
    Type: String
    NoEcho: true

  GitHubOwner:
    Type: String
    AllowedPattern: "[A-Za-z0-9-]+"

  GitHubRepo:
    Type: String
    AllowedPattern: "[A-Za-z0-9-]+"

  GitHubBranch:
    Type: String
    Default: master
    AllowedPattern: "[A-Za-z0-9-]+"

  ApplicationStackName:
    Description: Name of the not yet created Application CFN Stack
    Type: String
    Default: guest-book-app
    AllowedPattern: (^[a-z][a-z0-9-]+$)

  ChangeSetName:
    Description: Name of Cfn Change Set
    Type: String
    Default: guest-book-app-change-set
    AllowedPattern: (^[a-z][a-z0-9-]+$)

  ExistingAwsVpc:
    Description: Name of existing AWS Vpc
    Type: AWS::EC2::VPC::Id

  ExistingLoadbalancer:
    Description: ARN of existing Application Loadbalancer
    Type: String

  ExistingEcsCluster:
    Description: Name of existing Ecs Cluster
    Type: String

  NodeECRArn:
    Description: Node ECR arn
    Type: String

  NginxECRArn:
    Description: Nginx ECR arn
    Type: String

  RedisECRArn:
    Description: Redis ECR arn
    Type: String

Resources:

  GuestBookEcr:
    Type: AWS::ECR::Repository
    Properties:
      RepositoryName: !Join
        - "-"
        - - !Sub '${AWS::StackName}'
          - 'ecr'

  PipelineRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codepipeline.amazonaws.com
        Version: "2012-10-17"

  PipelineRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: PipelineRoleDefaultPolicy
      Roles:
        - Ref: PipelineRole
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - s3:GetObject*
              - s3:GetBucket*
              - s3:List*
              - s3:DeleteObject*
              - s3:PutObject*
              - s3:Abort*
            Effect: Allow
            Resource:
              - Fn::GetAtt:
                  - PipelineArtifactsBucket
                  - Arn
              - Fn::Join:
                  - ""
                  - - Fn::GetAtt:
                        - PipelineArtifactsBucket
                        - Arn
                    - /*
          - Action:
              - codebuild:StartBuild
              - codebuild:BatchGetBuilds
            Effect: Allow
            Resource:
              - Fn::GetAtt:
                  - ExtractBuild
                  - Arn
              - Fn::GetAtt:
                  - ImageBuild
                  - Arn
          - Action:
              - cloudformation:CreateChangeSet
              - cloudformation:DescribeStacks
              - cloudformation:DescribeChangeSet
              - cloudformation:ExecuteChangeSet
            Effect: Allow
            Resource:
              - !Join
                - ""
                - - "arn:aws:cloudformation:"
                  - Ref: AWS::Region
                  - ":"
                  - Ref: AWS::AccountId
                  - ":stack/"
                  - Ref: ApplicationStackName
                  - "/*"
          - Action:
              - iam:PassRole
            Effect: Allow
            Resource:
              - !GetAtt [ExtractBuildRole, Arn]

  ImageBuildRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
        Version: "2012-10-17"

  ImageBuildRolePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: ImageBuildRoleDefaultPolicy
      Roles:
        - Ref: ImageBuildRole
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - s3:GetObject*
              - s3:GetBucket*
              - s3:List*
              - s3:PutObject*
            Effect: Allow
            Resource:
              - Fn::GetAtt:
                  - PipelineArtifactsBucket
                  - Arn
              - Fn::Join:
                  - ""
                  - - Fn::GetAtt:
                        - PipelineArtifactsBucket
                        - Arn
                    - /*
          - Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Effect: Allow
            Resource:
              - Fn::Join:
                  - ""
                  - - "arn:"
                    - Ref: AWS::Partition
                    - ":logs:"
                    - Ref: AWS::Region
                    - ":"
                    - Ref: AWS::AccountId
                    - :log-group:/aws/codebuild/
                    - Ref: ImageBuild
                    - :*
          - Action:
              - ecr:GetAuthorizationToken
            Effect: Allow
            Resource:
              - "*"
          - Action:
              - ecr:BatchGetImage
              - ecr:GetDownloadUrlForLayer
              - ecr:BatchCheckLayerAvailability
            Effect: Allow
            Resource:
              - !Ref NodeECRArn
              - !Ref NginxECRArn
              - !Ref RedisECRArn
          - Action:
              - ecr:BatchCheckLayerAvailability
              - ecr:CompleteLayerUpload
              - ecr:InitiateLayerUpload
              - ecr:PutImage
              - ecr:UploadLayerPart
            Effect: Allow
            Resource:
              - !GetAtt GuestBookEcr.Arn

  ExtractBuildRole:
    Type: AWS::IAM::Role
    Properties:
      ManagedPolicyArns:
        - "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryReadOnly"
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: codebuild.amazonaws.com
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service: cloudformation.amazonaws.com
        Version: "2012-10-17"

  ExtractBuildRoleDefaultPolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: ExtractBuildRoleDefaultPolicy
      Roles:
        - Ref: ExtractBuildRole
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - logs:CreateLogGroup
              - logs:CreateLogStream
              - logs:PutLogEvents
            Effect: Allow
            Resource:
              - Fn::Join:
                  - ""
                  - - "arn:"
                    - Ref: AWS::Partition
                    - ":logs:"
                    - Ref: AWS::Region
                    - ":"
                    - Ref: AWS::AccountId
                    - :log-group:/aws/codebuild/
                    - Ref: ExtractBuild
                    - :*
          - Action:
              - s3:GetObject*
              - s3:GetBucket*
              - s3:List*
              - s3:PutObject*
            Effect: Allow
            Resource:
              - Fn::GetAtt:
                  - PipelineArtifactsBucket
                  - Arn
              - Fn::Join:
                  - ""
                  - - Fn::GetAtt:
                        - PipelineArtifactsBucket
                        - Arn
                    - /*
          - Action:
              - ecr:GetAuthorizationToken
            Effect: Allow
            Resource:
              - "*"
          - Action:
              - ecr:BatchGetImage
              - ecr:GetDownloadUrlForLayer
              - ecr:BatchCheckLayerAvailability
            Effect: Allow
            Resource:
              - !GetAtt GuestBookEcr.Arn

  ComposeRolePolicy:
    Type: AWS::IAM::Policy
    Properties:
      PolicyName: ComposeRolePolicy
      Roles:
        - Ref: ExtractBuildRole
      PolicyDocument:
        Version: "2012-10-17"
        Statement:
          - Action:
              - cloudformation:*
              - ecs:ListAccountSettings
              - ecs:CreateCluster
              - ecs:CreateService
              - ecs:DeleteCluster
              - ecs:DeleteService
              - ecs:DeregisterTaskDefinition
              - ecs:DescribeClusters
              - ecs:DescribeServices
              - ecs:DescribeTasks
              - ecs:ListTasks
              - ecs:RegisterTaskDefinition
              - ecs:UpdateService
              - ec2:AuthorizeSecurityGroupIngress
              - ec2:DescribeVpcs
              - ec2:DescribeVpcAttribute
              - ec2:DescribeSubnets
              - ec2:DescribeRouteTables
              - ec2:CreateSecurityGroup
              - ec2:CreateTags
              - ec2:DescribeSecurityGroups
              - ec2:DeleteSecurityGroup
              - ec2:RevokeSecurityGroupIngress
              - elasticfilesystem:CreateAccessPoint
              - elasticfilesystem:CreateFileSystem
              - elasticfilesystem:CreateMountTarget
              - elasticfilesystem:DeleteAccessPoint
              - elasticfilesystem:DeleteFileSystem
              - elasticfilesystem:DeleteMountTarget
              - elasticfilesystem:DescribeAccessPoints
              - elasticfilesystem:DescribeBackupPolicy
              - elasticfilesystem:DescribeFileSystems
              - elasticfilesystem:DescribeFileSystemPolicy
              - elasticfilesystem:DescribeMountTargets
              - elasticfilesystem:ModifyMountTarget
              - elasticfilesystem:ModifyMountTargetSecurityGroups
              - iam:AttachRolePolicy
              - iam:CreateRole
              - iam:DeleteRole
              - iam:DeleteRolePolicy
              - iam:DetachRolePolicy
              - iam:PassRole
              - iam:PutRolePolicy
              - elasticloadbalancing:*
              - application-autoscaling:*
              - servicediscovery:*
              - logs:CreateLogGroup
              - logs:DescribeLogGroups
              - logs:FilterLogEvents
              - logs:DeleteLogGroup
              - route53:CreateHostedZone
              - route53:DeleteHostedZone
              - route53:GetHealthCheck
              - route53:GetHostedZone
              - route53:ListHostedZonesByName
            Effect: Allow
            Resource:
              - "*"

  # CodeBuild to Build the Container Image
  ImageBuild:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Join
        - "-"
        - - !Sub ${AWS::StackName}
          - "ImageBuild"
      Artifacts:
        Type: CODEPIPELINE
        EncryptionDisabled: false
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/standard:5.0
        PrivilegedMode: true
        Type: LINUX_CONTAINER
      ServiceRole: !Ref ImageBuildRole
      Source:
        Type: CODEPIPELINE
        BuildSpec: |
          version: 0.2
          phases:
            pre_build:
              commands:
                - echo Logging in to Amazon ECR...
                - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
            build:
              commands:
                - echo Building the Docker image...
                - cd application/frontend/
                - docker build -t ${IMAGE_URI}:frontend.${IMAGE_TAG} .
                - cd ../backend/
                - docker build -t ${IMAGE_URI}:backend.${IMAGE_TAG} .
                - cd ../nginx/
                - docker build -t ${IMAGE_URI}:nginx.${IMAGE_TAG} .
            post_build:
              commands:
                - echo Pushing the Docker image...
                - docker push ${IMAGE_URI}:frontend.${IMAGE_TAG}
                - docker push ${IMAGE_URI}:backend.${IMAGE_TAG}
                - docker push ${IMAGE_URI}:nginx.${IMAGE_TAG}

  # Code Build to Extract Cfn
  ExtractBuild:
    Type: AWS::CodeBuild::Project
    Properties:
      Name: !Join
        - "-"
        - - !Sub ${AWS::StackName}
          - "ExtractBuild"
      Artifacts:
        Type: CODEPIPELINE
        EncryptionDisabled: false
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/amazonlinux2-x86_64-standard:3.0
        PrivilegedMode: true
        Type: LINUX_CONTAINER
      ServiceRole: !Ref ExtractBuildRole
      Source:
        Type: CODEPIPELINE
        BuildSpec: |
          version: 0.2
          phases:
           install:
             commands:
               - mv /usr/local/bin/docker /usr/bin/docker
               - curl -L https://raw.githubusercontent.com/docker/compose-cli/main/scripts/install/install_linux.sh | sh
               - cd ./application
           pre_build:
             commands:
               - echo Logging in to Amazon ECR...
               - aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin ${IMAGE_URI}
               - echo Creating Docker Compose Context
               - curl "http://169.254.170.2${AWS_CONTAINER_CREDENTIALS_RELATIVE_URI}" > creds.json
               - cat creds.json
               - export AWS_ACCESS_KEY_ID=$(cat creds.json | jq -r .AccessKeyId)
               - export AWS_SECRET_ACCESS_KEY=$(cat creds.json | jq -r .SecretAccessKey)
               - export AWS_SESSION_TOKEN=$(cat creds.json | jq -r .Token)
               - docker context create ecs GuestBookecs --from-env
               - docker context use GuestBookecs
               - docker context ls
           build:
             commands:
               - echo Convert Compose File
               - docker --debug compose -p guest-book-app -f docker-compose.yml -f docker-compose.prod.yml convert > cloudformation.yml
          artifacts:
            base-directory: ./application
            files:
              - cloudformation.yml


  PipelineArtifactsBucket:
    Type: AWS::S3::Bucket
    Properties:
      VersioningConfiguration:
        Status: Enabled

  # Code Pipeline
  Pipeline:
    Type: AWS::CodePipeline::Pipeline
    Properties:
      Name: "guest-book-pipeline"
      ArtifactStore:
        Type: S3
        Location: !Ref "PipelineArtifactsBucket"
      RoleArn: !GetAtt PipelineRole.Arn
      Stages:
        - Name: Source
          Actions:
            - Name: Source
              InputArtifacts: []
              ActionTypeId:
                Category: Source
                Owner: ThirdParty
                Version: "1"
                Provider: GitHub
              OutputArtifacts:
                - Name: SourceCode
              Configuration:
                Owner: !Ref GitHubOwner
                Repo: !Ref GitHubRepo
                Branch: !Ref GitHubBranch
                PollForSourceChanges: false
                OAuthToken: !Ref GitHubOAuthToken
        - Name: Build
          Actions:
            - Name: BuildContainerImage
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: "1"
              Configuration:
                ProjectName: !Ref ImageBuild
                EnvironmentVariables: !Sub |
                  [
                    {
                      "name": "AWS_ACCOUNT_ID",
                      "value": "${AWS::AccountId}",
                      "type": "PLAINTEXT"
                    },
                    {
                      "name": "IMAGE_URI",
                      "value": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${GuestBookEcr}",
                      "type": "PLAINTEXT"
                    },
                    {
                      "name": "IMAGE_TAG",
                      "value": "#{codepipeline.PipelineExecutionId}",
                      "type": "PLAINTEXT"
                    }
                  ]
              InputArtifacts:
                - Name: SourceCode
              OutputArtifacts:
                - Name: ImageBuild
        - Name: Compose2Cloudformation
          Actions:
            - Name: ExtractCFN
              ActionTypeId:
                Category: Build
                Owner: AWS
                Provider: CodeBuild
                Version: "1"
              Configuration:
                ProjectName: !Ref ExtractBuild
                EnvironmentVariables: !Sub |
                  [
                    {
                      "name": "AWS_ACCOUNT_ID",
                      "value": "${AWS::AccountId}",
                      "type": "PLAINTEXT"
                    },
                    {
                      "name": "IMAGE_URI",
                      "value": "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${GuestBookEcr}",
                      "type": "PLAINTEXT"
                    },
                    {
                      "name": "IMAGE_TAG",
                      "value": "#{codepipeline.PipelineExecutionId}",
                      "type": "PLAINTEXT"
                    },
                    {
                      "name": "AWS_ECS_CLUSTER",
                      "value": "${ExistingEcsCluster}",
                      "type": "PLAINTEXT"
                    },
                    {
                      "name": "AWS_VPC",
                      "value": "${ExistingAwsVpc}",
                      "type": "PLAINTEXT"
                    },
                    {
                      "name": "AWS_ELB",
                      "value": "${ExistingLoadbalancer}",
                      "type": "PLAINTEXT"
                    }
                  ]
              InputArtifacts:
                - Name: SourceCode
              OutputArtifacts:
                - Name: ExtractedCfn
        - Name: DeployStage
          Actions:
            - Name: CreateChangeSet
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: "1"
              InputArtifacts:
                - Name: ExtractedCfn
              Configuration:
                ActionMode: CHANGE_SET_REPLACE
                RoleArn: !GetAtt [ExtractBuildRole, Arn]
                StackName: !Ref ApplicationStackName
                ChangeSetName: !Ref ChangeSetName
                TemplatePath: "ExtractedCfn::cloudformation.yml"
                Capabilities: CAPABILITY_IAM
              RunOrder: 1
            - Name: ApproveChangeSet
              ActionTypeId:
                Category: Approval
                Owner: AWS
                Provider: Manual
                Version: "1"
              RunOrder: 2
            - Name: ExecuteChangeSet
              ActionTypeId:
                Category: Deploy
                Owner: AWS
                Provider: CloudFormation
                Version: "1"
              Configuration:
                ActionMode: CHANGE_SET_EXECUTE
                StackName: !Ref ApplicationStackName
                ChangeSetName: !Ref ChangeSetName
                RoleArn: !GetAtt [ExtractBuildRole, Arn]
              RunOrder: 3

  GithubWebhook:
    Type: 'AWS::CodePipeline::Webhook'
    Properties:
      Authentication: GITHUB_HMAC
      AuthenticationConfiguration:
        SecretToken: !Ref GitHubOAuthToken
      RegisterWithThirdParty: 'true'
      Filters:
        - JsonPath: "$.ref"
          MatchEquals: refs/heads/{Branch}
      TargetPipeline: !Ref Pipeline
      TargetAction: Source
      TargetPipelineVersion: !GetAtt Pipeline.Version

Outputs:
  GuestBookEcr:
    Description: ECR Repository to store the guest book App Image
    Value: !Sub "${AWS::AccountId}.dkr.ecr.${AWS::Region}.amazonaws.com/${GuestBookEcr}"
  GuestBookEcrName:
    Description: ECR Repository to store the guest book App Image
    Value: !Ref GuestBookEcr

논리적인 순서에 따라 cloudformation 템플릿을 살펴보자.

1) github 레포지토리의 master 브랜치에 변동사항이 생긴다

Resources-GithubWebhook 에서 깃헙 웹훅을 설정하여 pipeline 이 트리거 되도록 했다. (Target: Source)

Resources-Pipeline-Stages의 첫 번째 아이템(Name: Source)에서 확인 할 수 있다. Parameters에서 설정한 GitHubOwner, GitHubRepo, GitHubOAuthToken을 Configuration으로 사용한다. InputArtifact를 SourceCode로 설정해 이후 단계에서 InputArtifacts로 사용할 수 있게 한다.

2) Code Build가 docker의 각 서비스의 이미지를 이미지화 하고, 빌드된 이미지를 ECR에 업로드한다.

Resources-Pipeline-Stages의 두 번째 아이템(Name: Build)에서 확인 할 수 있다. Action은 Resources-ImageBuild 에 정의하였으며, 필요한 정책과 권한을 연결하였다.

이 단계는 변경된 소스코드를 기반으로 새로운 docker 이미지를 생성하고, ECR에 push 하는 과정을 포함한다.

3) compose 파일을 CloudFormation 파일로 변환한다

Resources-Pipeline-Stages의 세 번째 아이템(Name: Compose2Cloudformation)에서 확인 할 수 있다. Action은 Resources-Compose2Cloudformation 에 정의하였으며, 필요한 정책과 권한을 연결하였다.

docker context를 ecs로 생성하여 사용하기 위해 AWS 관련 정보를 불러와 환경변수로 지정한다. compose 파일을 cloudformation 템플릿으로 변환하여 change set을 생성한다. (docker compose 시 -p 옵셥으로 어플리케이션의 이름을 지정할 수 있다 - logGroup 등 자동으로 생성되는 AWS 리소스가 컴포즈 어플리케이션의 이름을 참조한다) (참고로 logGroup의 경우 docker-compose/${어플리케이션 이름}의 규칙으로 생성된다)

4) 기존 어플리케이션 인프라와 비교해 Change Set을 생성한다

5) 수동 승인 과정을 거쳐 ECS에 배포된다

Resources-Pipeline-Stages의 네 번째 아이템(Name: DeployStage)에서 확인 할 수 있다.

총 세 가지 단계를 거쳐 배포된다.

  1. 기존 어플리케이션 cloudformation 템플릿과 비교하여 Change set 생성
  2. Change set 적용을 수동승인
  3. 수동승인 완료되면 cloudformation 업데이트(배포)

Pipeline c AWS-cli로 생성

위의 과정을 이해 했다면 실제로 파이프라인을 생성해보자. pipeline 디렉토리로 이동 한 뒤, 아래 명령어를 실행해보자.

aws cloudformation create-stack \
--stack-name guest-book-pipeline \
--template-body file://cloudformation.yaml \
--capabilities CAPABILITY_IAM \
--parameters \
ParameterKey=GitHubOAuthToken,ParameterValue=${깃헙에서 발급한 토큰} \
ParameterKey=GitHubOwner,ParameterValue=${깃헙 오너 이름} \
ParameterKey=GitHubRepo,ParameterValue=${깃헙 레포지코리 이름} \
ParameterKey=NodeECRArn,ParameterValue=${AWS에 생성한 Node ECR arn} \
ParameterKey=NginxECRArn,ParameterValue=${AWS에 생성한 Nginx ECR arn} \
ParameterKey=RedisECRArn,ParameterValue=${AWS에 생성한 Redis ECR arn} \
ParameterKey=ExistingAwsVpc,ParameterValue=${AWS에 생성한 vpc id} \
ParameterKey=ExistingEcsCluster,ParameterValue=${AWS에 생성한 ECS cluseter arn} \
ParameterKey=ExistingLoadbalancer,ParameterValue=${AWS에 생성한 ELB arn}

ParameterValue 값에 본인이 생성한 리소스의 값들을 올바르게 넣고 실행시키면 정상적으로 스택이 생성되고, 리소스를 생성한다.

Pipeline 실행

리소스가 처음 생성된 이후에 pipeline은 최초 실패할 것이다. 이는 전체 리소스가 생성되기 이전에 pipeline이 실행됐기 때문이므로 낙심할 필요는 없다. 레포지토리에 변동사항을 만든 뒤, origin이 변동되면 Pipeline이 자동으로 실행될 것이다. (수동 승인 과정을 포함하였으므로, 파이프라인 콘솔에서 승인대기중인 과정애서 승인을 눌러야 최종 배포되는 점 참고하자)

pipeline-complete

Pipeline이 성공적으로 모두 완료되면, 생성했던 ELB 주소로 접속해보자.

deploy-result

guest-book 어플리케이션이 정상적으로 실행되는 것을 알 수 있다.

end

이번 포스팅과 이전 포스팅을 통해 AWS 환경에 다중 도커 컴포넌트 서비스를 CI/CD 하는 과정에 대해서 알아보았다. 실제로는 CI 과정만을 다뤘다고 볼 수 있는데, CD 는 파이프라인에 테스트과정을 포함하면 되므로 직접 구현해 보자. 또한, docker compose ecs auto-scaling문서를 통해 auto-scaling 설정도 가능하다는 점을 참고하자.