DockerイメージのビルドからECS Serviceの更新までのCD PipelineをGitHub Actionsで構築したいと思います。
※ イメージタグの管理方法、それからECS Task DefinitionとServiceの更新はIaCのツールによって変わるので具体的なコマンドは省略します。
フロー
PlantUMLでCD Pipelineのフローを可視化したのが以下です。
BuildとDeploy Stageに分けている理由はいくつかあります。
GitHub Actionsではneedsに含まれているジョブがスキップされたら、そのジョブもスキップされてしまいます。
なのでECS更新ジョブでDockerイメージのビルド・プッシュジョブがneedsに含めてしまうと、ECRにイメージが存在していたらECS更新ジョブもスキップされてしまいます。
だとするならば、ECRにイメージが存在しているかチェックするのを止めて毎回新イメージをビルド・プッシュすればいいですが、イメージ以外のパラメータを更新をしたい場合に不便です。
このような理由から2つのStageに分けます。
CD Pipeline on GitHub Actions
前提
- Dockerイメージ名(タグを含む)をファイルで管理している
Workflow
Build Stage
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/push-ecr-action@v1.1
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' }}
になります。