はじめに
GitHub Actions(以下GHA)を使ってECS Fargate(AWS)へ自動デプロイしたので手順を記します。
対象
こちらのDjangoアプリをデプロイします。
今回はmainブランチへのマージをトリガーにECRへのコンテナイメージのプッシュとECSのサービスの更新が確認できれば良いため、リソースは最小構成にします。
手順
1. GHAからAWSへの連携
連携する際のクレデンシャルとしてSecrets又はOpenID Connectがありますが、セキュリティ面からOpenID Connectを選択します。
概要 | メリット | デメリット | |
---|---|---|---|
Secrets | GitHub上に保存したワードでAWSにアクセスする | ・設定が容易 | ・長期保存のため漏洩した際の影響が大きい ・ローテーション作業が必要 |
OpenID Connect | GitHubからトークンを取得し、AWSとの間でトークンと一時クレデンシャルを交換し、一時クレデンシャルでAWSにアクセスする | ・一時保存のため漏洩した際の影響が小さい ・ローテーション作業が不要 |
・設定が複雑(ただし初回のみ) |
OpenID Connect Providerの作成
トークンと一時クレデンシャルを交換するために必要な設定です。
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list 1234567890123456789012345678901234567890
IAMポリシーの作成・ロールへのアタッチ
どこから(GitHubレポジトリ)どこに(AWS)アクセスできるかを制御します。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "sts:AssumeRoleWithWebIdentity",
"Principal": {
"Federated": "arn:aws:iam::${AWS_ID}:oidc-provider/${PROVIDER_URL}"
},
"Condition": {
"StringLike": {
"${PROVIDER_URL}:sub": "repo:${GITHUB_REPOSITORY}:*"
}
}
}
]
}
ワークフローの作成
下記のdeploy.ymlに認証アクションを組み込み(Get tmp credentials)、一時クレデンシャルを取得します。その際に必要なパラメータ(AWSアカウントIDなど)についてはSecrets(GitHub上で設定)に登録して使用します。こちらはクレデンシャルではないため、万が一漏れても影響は限定的です。
2. GHAからAWSへコンテナデプロイ
IAMポリシーの作成・ロールへのアタッチ
ECR, ECSへアクセスするため、ECRへのイメージのプッシュやECSのサービス更新権限等を加味したIAMポリシーを作成し、ロールへアタッチします。
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ecr:GetAuthorizationToken",
"ecr:BatchGetImage",
"ecr:BatchCheckLayerAvailability",
"ecr:CompleteLayerUpload",
"ecr:GetDownloadUrlForLayer",
"ecr:InitiateLayerUpload",
"ecr:PutImage",
"ecr:UploadLayerPart",
"ecs:DescribeTaskDefinition",
"ecs:RegisterTaskDefinition",
"ecs:UpdateService",
"ecs:DescribeServices"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"iam:PassRole"
],
"Resource": "*",
"Condition": {
"StringLike": {
"iam:PassedToService": "ecs-tasks.amazonaws.com"
}
}
}
]
}
ワークフローの作成
name: Deploy
on:
pull_request:
branches:
- main
types: [closed]
env: # AWSの認証アクションへ指定する入力パラメータを環境変数として定義
ROLE_ARN: arn:aws:iam::${{ secrets.AWS_ID }}:role/${{ secrets.ROLE_NAME }}
SESSION_NAME: gh-oidc-${{ github.run_id }}-${{ github.run_attempt }}
jobs:
deploy:
if: github.event.pull_request.merged == true
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write # OIDCトークンの取得を許可
env:
ECS_CLUSTER_NAME: django-tw-cluster
ECS_SERVICE_NAME: django-tw-service
steps:
- uses: actions/checkout@v4
- name: Get tmp credentials # AWSの認証アクション
uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: ${{ env.ROLE_ARN }}
role-session-name: ${{ env.SESSION_NAME }}
aws-region: ap-northeast-1
- name: Login to ECR
id: login-ecr
uses: aws-actions/amazon-ecr-login@v2
- name: web push to ECR
id: build-web
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: django-tw/web
run: |
docker build -t $ECR_REPOSITORY -f Dockerfile .
docker tag $ECR_REPOSITORY:latest $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
- name: db push to ECR
id: build-db
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: django-tw/db
run: |
docker build -t $ECR_REPOSITORY -f Dockerfile.db .
docker tag $ECR_REPOSITORY:latest $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
- name: nginx push to ECR
id: build-nginx
env:
ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
ECR_REPOSITORY: django-tw/nginx
run: |
docker build -t $ECR_REPOSITORY -f Dockerfile.nginx .
docker tag $ECR_REPOSITORY:latest $ECR_REGISTRY/$ECR_REPOSITORY:latest
docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest
- name: Deploy to ECS
run: |
aws ecs update-service --service $ECS_SERVICE_NAME --cluster $ECS_CLUSTER_NAME --force-new-deployment
解説
- mainブランチにマージされたらECS Fargateへデプロイされるようにするため、イベント(on)とif文を工夫します
- AWSの一時クレデンシャルを取得してからデプロイする必要があるため、Get tmp credentialsで認証アクションを用いて一時クレデンシャルを取得します(コメント部分)
- メインの処理はビルドとデプロイです。ビルドはLogin to ECR + xxx push to ECR(xxxはコンテナ名)、デプロイはDeploy to ECSで記述しています。これらのビルドとデプロイは各々アクションに切り出してコンポーネント化できそうですが、今回はわかりやすくシェルコマンドで実装しました
おわりに
無事デプロイできました。次はTerraformでインフラを定義します。
参考文献
全体的に以下を参考とさせていただきました。