2020-10-16 追記)
GitHub Actionsでset-env, add-path が非推奨になった
https://qiita.com/shonansurvivors/items/f256a0443d346f09448e
こちらの記事を参考に適宜書き換えてください(手抜き ^^;
はじめに
サイドカー構成で動くECS(Fargate)へGitHubActions+ecspresso
を使用してデプロイするCD設定手順を紹介します。
(CDとはContinuousDevelopment,継続的デプロイメントのことです)
参考)CircleCI+ecspresso版の記事はこちら
ecspressoを使う理由
- Goで作られている(ワンバイナリ)ので利用が簡単
- 環境別にタスク定義設定を二重管理したくない
- 設定ファイルにgo-configが使えるので環境ごとの差異を変数化して挿入できる
- サイドカー構成のデプロイに対応している
- AWS公式Actionsでも対応しようと思えばできるが無理やり感あったので
前提
システム構成
-
ALB + ECS(Fargate)
という構成のWebアプリをデプロイします- ALB+ECS部分の構築はこの記事の本質とズレるのでCloudFormationを使います
- 本番/QA環境用にAWSアカウントが2つある前提です
- マージするブランチによってデプロイ先環境を切り替えます
- developブランチマージ -> QA環境へデプロイ
- masterブランチマージ -> 本番環境へデプロイ
- マージするブランチによってデプロイ先環境を切り替えます
-
こちらのリポジトリをForkしてください
- (リクエストヘッダを表示するだけの雑なアプリです)
- もちろん構成が同じであれば自作アプリでもOKです
ディレクトリ構成
デプロイするアプリのGitリポジトリ-ディレクトリ構成例です。
今回デプロイするアプリの名称は仮にfargate-example
とします。
.
├── .github
│ └── workflows
│ └── deploy.yml # GitHubActionsワークフロー定義ファイル(デプロイ用) *後述*
├── .gitignore
├── README.md
├── cfn-alb-ecs.yml # ALB+ECSを作るCloudFormationテンプレート
├── deploy
│ ├── ecs-task-def.json
│ └── ecspresso.yml
├── docker-compose.yml
├── deploy
│ ├── ecs-task-def.json # タスク定義JSON *後述*
│ └── ecspresso.yml # ecspresso設定ファイル *後述*
├── nginx # nginxコンテナ定義(Goアプリにリクエスト中継するためのサイドカー)
│ ├── Dockerfile
│ ├── goapp.conf.tmpl
│ └── nginx.conf
└── goapp # goappコンテナ定義(nginx経由でリクエストを処理するGoアプリ)
├── Dockerfile
├── Makefile
├── go.mod
├── go.sum
└── main.go
作業環境
- ecspressoがインストール済みであること
- macOSであれば
brew install kayac/tap/ecspresso
でインストールできます
- macOSであれば
- dockerが動く環境があること
- macOSであれば
Docker for Mac
をインストールしてください
- macOSであれば
- aws-cliが使えるようにQA環境/本番環境用のcredentials設定が済んでいること
- 一部aws-cliでAWSリソースを作る例にしていますが、もちろんマネジメントコンソールで実施しても問題ありません
具体的な値について
この記事内では下記値で記載します。
手順を実施する際には環境に合わせて読み替えてください。
- AWSアカウントID
- QA環境:
123456789012
- 本番環境:
234567890123
- QA環境:
- AWSリージョン:
ap-northeast-1
- アプリ名:
fargate-example
- GitHubリポジトリ名/ECSクラスタ・サービスの名称も同じ
- 分かりやすさのため、ECRリポジトリ名/IAMロール名のprefixとしてもアプリ名を使用します(後述)
アプリ動作環境構築
ECRリポジトリ作成
aws-cliを使用して{アプリ名}-{コンテナ名}
という名前でコンテナ数分作成します。
今回の例では nginx/goappの2つ必要です。
# aws-cliのプロファイル、およびリージョンを指定
export AWS_PROFILE={プロファイル名}
export AWS_REGION=ap-northeast-1
aws ecr create-repository --repository-name fargate-example-nginx
{
"repository": {
"repositoryArn": "arn:aws:ecr:ap-northeast-1:123456789012:repository/fargate-example-nginx",
"registryId": "123456789012",
"repositoryName": "fargate-example-nginx",
"repositoryUri": "123456789012.dkr.ecr.ap-northeast-1.amazonaws.com/fargate-example-nginx",
"createdAt": "2020-06-17T09:36:06+00:00",
"imageTagMutability": "MUTABLE",
"imageScanningConfiguration": {
"scanOnPush": false
}
}
}
あとで使うのでrepositoryUri
を控えておくこと・
同様にgoappも実行してください。
ECRリポジトリへ仮コンテナイメージPush
ECSクラスタ構築及び動作確認で必要となるので、暫定でプレーンのnginxコンテナイメージをECRにPushしておきます。
# nginxコンテナイメージ取得
docker pull nginx:1.17-alpine
# aws-cliのプロファイル、およびリージョンを指定
export AWS_PROFILE={プロファイル名}
export AWS_REGION=ap-northeast-1
ACCOUNT_ID=$(aws sts get-caller-identity | jq -r .Account)
# コンテナイメージをQA環境のECRにPush
aws ecr get-login-password |\
docker login --username AWS --password-stdin https://${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com
docker tag nginx:1.17-alpine ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/fargate-example-nginx:latest
docker push ${ACCOUNT_ID}.dkr.ecr.ap-northeast-1.amazonaws.com/fargate-example-nginx:latest
プロファイル名を切り替えてQA/本番の2回実行する。
CloudFormationスタック作成
CloudFormationの画面にアクセスし、スタック作成を開始します。
cfn-alb-ecs.ymlを指定して、次へ

再度 次へ
押して
AWS CloudFormation によって IAM リソースがカスタム名で作成される場合があることを承認します。
のチェックボックスにチェック入れて スタックの作成
を押してしばらく待ちます
スタック一覧に CREATE_COMPLETE
が表示されていれば完了です。(4,5分かかります)

出力タブをクリックし、ALB URL
に表示されているリンクをクリックしてみてください。
上記の作業をQA/本番両環境で実施してください。
↑ここまでがアプリ動作環境の構築手順です。
↓ここから先がGitHubActionsを使ったCD設定の手順・解説です。
デプロイ用IAMユーザ作成
GitHubActionsからECSにデプロイするための権限を持ったアクセスキーが必要になるため、ここでIAMユーザを作成します。
まずIAMコンソールを開き、下記の通りに入力して次のステップ
ボタンを3回押し最後にユーザーの作成
ボタンを押します。
IAMアクセスキーとシークレットアクセスキーが表示されるので、控えておいてください。
次にIAMユーザー一覧から作成したユーザーを選択し、インラインポリシーの追加
をクリックします。
JSONタブをクリックし下記を入力
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": "ecs:RegisterTaskDefinition",
"Resource": "*"
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": [
"ecs:UpdateService",
"iam:PassRole",
"ecs:DescribeServices"
],
"Resource": [
"arn:aws:ecs:ap-northeast-1:123456789012:service/fargate-example/fargate-example",
"arn:aws:iam::123456789012:role/fargate-example-task-role",
"arn:aws:iam::123456789012:role/ecsTaskExecutionRole"
]
}
]
}
putImageToECR
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "VisualEditor0",
"Effect": "Allow",
"Action": [
"ecr:CompleteLayerUpload",
"ecr:UploadLayerPart",
"ecr:InitiateLayerUpload",
"ecr:BatchCheckLayerAvailability",
"ecr:PutImage"
],
"Resource": [
"arn:aws:ecr:ap-northeast-1:123456789012:repository/fargate-example-*"
]
},
{
"Sid": "VisualEditor1",
"Effect": "Allow",
"Action": "ecr:GetAuthorizationToken",
"Resource": "*"
}
]
}
上記をQA環境/本番環境用の2回実施してください。(AWSアカウントIDの書き換えを忘れずに)
GitHubSecrets登録
GitHubのSettingsメニューからSecretsを選択し、シークレットを登録します。
登録した値は、GitHubActions内からセキュアに参照することができます。

下記4つの設定を登録してください。
シークレット名 | 説明 |
---|---|
AWS_ACCESS_KEY_ID_QA | QA環境のアクセスキー |
AWS_SECRET_ACCESS_KEY_QA | QA環境のシークレットアクセスキー |
AWS_ACCESS_KEY_ID | 本番環境のアクセスキー |
AWS_SECRET_ACCESS_KEY | 本番環境のシークレットアクセスキー |
自動デプロイ設定
GitHubActionsワークフローファイルをベースに解説、及び修正が必要な箇所について説明します。
name: deploy
on:
push:
branches:
- dummy
#- master
#- develop
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: set environment variables for QA
if: github.ref == 'refs/heads/develop'
env:
AWS_ACCOUNT_ID: 123456789012
run: |
echo "::set-env name=AWS_ACCOUNT_ID::${AWS_ACCOUNT_ID}"
- name: set environment variables for Production
if: github.ref == 'refs/heads/master'
env:
AWS_ACCOUNT_ID: 234567890123
run: |
echo "::set-env name=AWS_ACCOUNT_ID::${AWS_ACCOUNT_ID}"
- name: configure aws credentials for QA
if: github.ref == 'refs/heads/develop'
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_QA }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_QA }}
aws-region: ap-northeast-1
- name: configure aws credentials for Production
if: github.ref == 'refs/heads/master'
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ap-northeast-1
- name: checkout
uses: actions/checkout@v2
- name: login to ecr
id: login-ecr
uses: aws-actions/amazon-ecr-login@v1
- name: build, tag, and push image to ecr
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t fargate-example-nginx:latest nginx
docker tag fargate-example-nginx:latest $ECR_REGISTRY/fargate-example-nginx:latest
docker tag fargate-example-nginx:latest $ECR_REGISTRY/fargate-example-nginx:$IMAGE_TAG
docker push $ECR_REGISTRY/fargate-example-nginx:latest
docker push $ECR_REGISTRY/fargate-example-nginx:$IMAGE_TAG
docker build -t fargate-example-goapp:latest goapp
docker tag fargate-example-goapp:latest $ECR_REGISTRY/fargate-example-goapp:latest
docker tag fargate-example-goapp:latest $ECR_REGISTRY/fargate-example-goapp:$IMAGE_TAG
docker push $ECR_REGISTRY/fargate-example-goapp:latest
docker push $ECR_REGISTRY/fargate-example-goapp:$IMAGE_TAG
- name: download ecspresso
env:
ver: v0.13.6
run: |
curl -sLo ecspresso.zip \
https://github.com/kayac/ecspresso/releases/download/$ver/ecspresso-$ver-linux-amd64.zip
unzip ecspresso.zip
mv $(ls -1 ecspresso-*-linux-amd64) ecspresso
working-directory: ./deploy
- name: deploy to ecs
run: ./ecspresso deploy --config ecspresso.yml --tasks=-1
working-directory: ./deploy
解説1)環境の分岐
- name: set environment variables for QA
if: github.ref == 'refs/heads/develop'
env:
AWS_ACCOUNT_ID: 123456789012
run: |
echo "::set-env name=AWS_ACCOUNT_ID::${AWS_ACCOUNT_ID}"
if: github.ref == 'refs/heads/develop'
の部分でマージされたブランチを判定しています。
上記の場合は、マージされたブランチによって、環境変数AWS_ACCOUNT_ID
の値を変えています。
解説2)aws-cliのcredentials設定
- name: configure aws credentials for QA
if: github.ref == 'refs/heads/develop'
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID_QA }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY_QA }}
aws-region: ap-northeast-1
AWS公式のconfigure-aws-credentials
というActionsを使用して、アクセスキー認証情報を環境変数にセットします。
以降、その権限でaws-cliを使うことができるようになります。
ここでも前述の分岐が使われており、マージされたブランチによってQA/本番環境のどのAWSリソースにアクセスするかを切り替えています。
解説3)docker build,tag,push
- name: build, tag, and push image to ecr
id: build-image
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
IMAGE_TAG: ${{ github.sha }}
run: |
docker build -t fargate-example-nginx:latest nginx
docker tag fargate-example-nginx:latest $ECR_REGISTRY/fargate-example-nginx:latest
docker tag fargate-example-nginx:latest $ECR_REGISTRY/fargate-example-nginx:$IMAGE_TAG
docker push $ECR_REGISTRY/fargate-example-nginx:latest
docker push $ECR_REGISTRY/fargate-example-nginx:$IMAGE_TAG
docker buildしてtag付けしてECRにPushしています。
latest
と$IMAGE_TAG
の2つタグ付け&Pushしている理由は、運用上個別に戻したい場合に備えて$IMAGE_TAG
をつけています。
常に最新でよければlatestだけでも問題ないです。
解説4)ecspressoによるデプロイ
- name: download ecspresso
env:
ver: v0.13.6
run: |
curl -sLo ecspresso.zip \
https://github.com/kayac/ecspresso/releases/download/$ver/ecspresso-$ver-linux-amd64.zip
unzip ecspresso.zip
mv $(ls -1 ecspresso-*-linux-amd64) ecspresso
working-directory: ./deploy
- name: deploy to ecs
run: ./ecspresso deploy --config ecspresso.yml --tasks=-1
working-directory: ./deploy
download ecspresso
の部分でecspressoバイナリをダウンロードしています。
deploy to ecs
の部分でecspressoを実行しています。
--tasks=-1を指定することで、現在のタスク実行数を引き継いでくれます。(つまり、オートスケールに対応できます)
修正1)対象ブランチ指定
ブランチマージ時にワークフローが動く条件となるブランチを指定します。
下記差分の通りに修正します。
on:
push:
branches:
- - dummy
- #- master
- #- develop
+ - master
+ - develop
これにより、develop/masterブランチへのマージでこのワークフローが動きます。
修正2)ECSに渡す環境変数を修正
ecspresso(+その先のコンテナ)に渡す環境変数を、環境(QA/本番)に応じて切り替えることができます。
ワークフローファイルの下記の部分を実際のAWSアカウントIDに修正します。
また、コンテナに渡す環境変数を追加したい場合は以下のGOAPP_TEST_VARを参考に追加してください。
Actionsでstepを跨いで有効な環境変数を設定する場合は ::set-env name=~
を使います。 参考
- name: set environment variables for QA
if: github.ref == 'refs/heads/develop'
env:
- AWS_ACCOUNT_ID: 123456789012
+ AWS_ACCOUNT_ID: 123456789012
+ GOAPP_TEST_VAR: "test var in qa"
run: |
echo "::set-env name=AWS_ACCOUNT_ID::${AWS_ACCOUNT_ID}"
+ echo "::set-env name=GOAPP_TEST_VAR::${GOAPP_TEST_VAR}"
- name: set environment variables for Production
if: github.ref == 'refs/heads/master'
env:
- AWS_ACCOUNT_ID: 234567890123
+ AWS_ACCOUNT_ID: 234567890123
+ GOAPP_TEST_VAR: "test var in prod"
run: |
echo "::set-env name=AWS_ACCOUNT_ID::${AWS_ACCOUNT_ID}"
+ echo "::set-env name=GOAPP_TEST_VAR::${GOAPP_TEST_VAR}"
Actionsで指定された環境変数が、ecspresso実行時にタスク定義JSONに渡されるように修正します。
"environment": [
{
"name": "GOAPP_HOST",
"value": "127.0.0.1"
},
+ {
+ "name": "GOAPP_TEST_VAR",
+ "value": "{{ must_env `GOAPP_TEST_VAR` }}"
+ },
{
"name": "TZ",
"value": "Asia/Tokyo"
}
],
上記のように、GitHubActionsのワークフローに、ブランチ(環境)ごとに環境変数を定義することで、ビルドコマンドやデプロイ先を切り替えたり、コンテナに渡す環境変数を切り替えることができます。
これらの修正が完了したら、developブランチにマージ(orプッシュ)してください。
GitHubサイト上のActions
タブを表示するとワークフローが動いていると思います。
下記は、デプロイまで正常修正した場合の表示です。(スキップされたステップ以外は緑の✔︎がつきます)

ALBのURLに再度アクセスしてみると、アプリが更新され表示が変わっている(リクエストヘッダ一覧が表示されている)と思います。
あとは developをmasterブランチにマージすると本番環境へのデプロイが動くと思います。
おまけ
既存ECSクラスタをCDする場合
今回は、ALB+ECSを作成するところからの手順でしたが、既存のECSクラスタをGitHubActionsのCDに対応させたい場合があります。
その場合は、ecspresso initを実行してください。既存環境の情報を吸い上げて設定ファイルを作成してくれます。
$ ecspresso init --region ap-northeast-1 --cluster fargate-example --service fargate-example --config ecspresso.yml
2020/06/22 17:47:39 fargate-example/fargate-example save service definition to ecs-service-def.json
2020/06/22 17:47:39 fargate-example/fargate-example save task definition to ecs-task-def.json
2020/06/22 17:47:39 fargate-example/fargate-example save config to ecspresso.yml
ecs-task-def.jsonに対して、go-configの形式で環境変数の埋め込めばOKです。
ecs-service-def.jsonは今回の使い方(タスク入れ替えのみ)であれば不要なので消して問題ありません。
デプロイに時間かかる場合
ALBのヘルスチェック閾値を調整するとecspressoがタスクを追加した時にターゲットグループへの追加が早くなります。
登録解除の遅延を短くした場合にも、デプロイが早くなります。
(運用上問題のない値にしてください)
まとめ
GitHubActionsは、環境変数の扱いなど少しクセありますが、慣れてしまえばCircleCIに劣らず使いやすいと思います。
またecspressoはシンプルでサイドカーにも対応していて使いやすいのでこれからも使っていこうと思います。