ECS Service의 CD Pipeline을 GitHub Actions로 구축

Docker 이미지 빌드부터 ECS 서비스 업데이트까지 CD Pipeline을 GitHub Actions로 빌드하고 싶습니다.

※ 이미지 태그의 관리 방법, 그리고 ECS Task Definition과 Service의 갱신은 IaC의 툴에 따라 바뀌므로 구체적인 명령은 생략합니다.

흐름



PlantUML에서 CD Pipeline의 흐름을 시각화한 것은 다음과 같습니다.



Build와 Deploy Stage로 나누는 몇 가지 이유가 있습니다.
GitHub Actions에서는 needs에 포함된 작업을 건너뛰면 해당 작업도 건너뜁니다.
따라서 ECS 업데이트 작업에서 Docker 이미지의 빌드 푸시 작업이 needs에 포함되면 ECR에 이미지가 존재하면 ECS 업데이트 작업도 건너 뜁니다.
그렇다면 ECR에 이미지가 존재하는지 확인하는 것을 멈추고 매번 새 이미지를 빌드 푸시하면 됩니다만, 이미지 이외의 파라미터를 갱신을 하고 싶은 경우에 불편합니다.
이런 이유로 두 개의 스테이지로 나눕니다.

CD Pipeline on GitHub Actions



전제


  • Docker 이미지 이름 (태그 포함)을 파일로 관리합니다.

    워크플로우



    빌딩 스테이지


    name: Build Stage
    
    on:
      push:
        tags:
          - 'v*'
    
    # 複数のイメージをBulid・Pushする場合のために、共通パラメータはworkflowレベルで環境変数化
    # secretsを環境変数化してもログ上ではマスキングされるので問題なし
    env:
      DOCKER_BUILDKIT: 1
      SLACK_CHANNEL: '#random'
      AWS_DEFAULT_REGION: ap-northeast-1
      AWS_ACCOUNT_ID: ${{ secrets.AWS_ACCOUNT_ID }}
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    
    jobs:
      precheck:
        name: Check image existence
        runs-on: ubuntu-20.04
        outputs:
          # ECRに存在していればTrue、存在していなければFalse
          sample: ${{ steps.sample.outputs.existence }}
        steps:
          - uses: actions/checkout@v2
    
          - name: Check sample image
            id: sample
            run: # ファイルで管理しているイメージがECRに存在しているかチェックする処理
    
      sample:
        name: Push sample image
        runs-on: ubuntu-20.04
        needs: [precheck]
        # ECRに存在していなければ実行
        if: ${{ ! needs.precheck.outputs.sample }}
        steps:
          - uses: actions/checkout@v2
    
          - name: Get image name
            id: getter
            # $(...)でファイルからイメージ名(タグを含む)を取得
            # 具体的な処理の記述は省略
            run: echo "::set-output name=image::$(...)"
    
          - name: Build image
            env:
              DOCKER_CONTENT_TRUST: 1
            run: docker build -t ${{ steps.getter.outputs.image }} .
    
          # Trivyによってイメージをスキャン
          - uses: aquasecurity/trivy-action@master
            with:
              image-ref: ${{ steps.getter.outputs.image }}
              severity: 'HIGH,CRITICAL'
              exit-code: '1'
              ignore-unfixed: true
    
          - uses: hands-lab/[email protected]
            with:
              image: ${{ steps.getter.outputs.image }}
              aws-access-key-id: ${{ env.AWS_ACCESS_KEY_ID }}
              aws-secret-access-key: ${{ env.AWS_SECRET_ACCESS_KEY }}
              aws-region: ${{ env.AWS_DEFAULT_REGION }}
              aws-account-id: ${{ env.AWS_ACCOUNT_ID }}
    
          # 成否をSlackへ通知
          - uses: lazy-actions/slatify@master
            if: ${{ always() }}
            with:
              type: ${{ job.status }}
              job_name: ':ecr: *Push sample image to ECR*'
              channel: ${{ env.SLACK_CHANNEL }}
              url: ${{ secrets.CRM_SLACK_WEBHOOK }}
    

    precheck 작업에서 ECR에 동일한 이미지 이름(태그 포함)이 있는지 확인합니다.
    그 샘플 스크립트를 일단 써 둡니다.
    \$ repo에 ECR 리포지토리 이름,\$ tag에 이미지 태그를 할당하면
    GitHub Actions는 빈 문자이면 False이고, 그렇지 않으면 True입니다. 다음 스크립트의 경우 ECR에 해당하는 이미지가 없으면 공문자가 되므로 위의 workflow에서도 그대로 사용할 수 있습니다.
    aws ecr batch-get-image \
        --repository-name "$repo" \
        --image-ids "imageTag=$tag" \
        --query 'images[].imageId.imageTag' \
        --output text
    

    sample 작업은 precheck 작업의 출력에 따라 건너뜁니다.
    ECR에 존재하지 않는 태그이면 실행합니다.
    그리고는 이미지를 빌드하고 푸시하고 있을 뿐입니다.
    말하자면 trivy의 공식 GitHub Action에서 이미지 스캔을하고 있습니다. exit-code: '1' 를 설정해 취약성이 있으면 푸시하지 않는다 = workflow 실패라고 하고 있습니다.

    Deploy Stage


    name: Deploy Stage
    
    on:
      workflow_run:
        # Build Stage完了後にDeploy Stageを実行
        workflows:
          - Build Stage
        types:
          - completed
    
    env:
      DOCKER_BUILDKIT: 1
      SLACK_CHANNEL: '#random'
      AWS_DEFAULT_REGION: ap-northeast-1
      AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
      AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    
    jobs:
      deploy:
        name: Deployment
        runs-on: ubuntu-20.04
        # Build Stageが成功している場合のみ実行
        if: ${{ github.event.workflow_run.conclusion == 'success' }}
        steps:
          - uses: actions/checkout@v2
            with:
              # Build Stageと同じコミットSHAにチェックアウト
              # refの指定がなければデフォルトブランチにチェックアウトします
              ref: ${{ github.event.workflow_run.head_sha }}
    
          - name: Update ECS Task Definition and Service
            run: # 具体的な処理は省略
    
          # 成否をSlackへ通知
          - uses: lazy-actions/slatify@master
            if: ${{ always() }}
            with:
              type: ${{ job.status }}
              job_name: ':ecs: *Deploy*'
              channel: ${{ env.SLACK_CHANNEL }}
              url: ${{ secrets.CRM_SLACK_WEBHOOK }}
    

    Build Stage가 완료된 후 Deploy Stage를 호출하려면 workflow_run라는 이벤트 트리거를 사용합니다.
    workflow_run은 의존원인 workflow의 성공 여부에 관계없이 발화하므로 Deploy Stage에서 Build Stage의 성공 여부를 명시적으로 확인해야 합니다. 그것이 if: ${{ github.event.workflow_run.conclusion == 'success' }} 입니다.

    Reference


  • htps : // / cs. 기주 b. 코 m / 엔 / f Ree-p 로- m @ st / A c Chion s / Reffensense / E-on ts - t t t Righ r-rkf ぉws # rkf ぉ w_ 룬
  • htps : // 기주 b. 코 m / 아쿠아 세쿠 ty / 티 vy 아 c 치온
  • 좋은 웹페이지 즐겨찾기