はじめに
こんにちは、またお会いしましたね。24年度新卒のエンジニアリング部のお涼です。
4月には今年の新卒が入り、とうとう私も2年目ですが、今日も「ちょっとわかってきた気がする」という感覚を頼りに日々勉強しています。
先日、とあるプロジェクトでGitLab CI/CDを使って、ECSへのデプロイを自動化する設定を書きました。
その際、ちまちまとスクリプトを書いたんですが、結局ほとんどそれらは書く必要がなく、GitLabの公式イメージとして用意されていました。それを取り込むだけで良かったわけです。
使えるものは積極的に使おう。ということで、今回は、用意されていた公式イメージをちゃんと読まずに全部コマンド書いちゃった私が、GitLabの公式イメージを活用して、ECR/ECSへのデプロイを『楽して自動化』する技を紹介します。
「自動化したいけど書き方がわからない」「全部手書きでやってるけど、もっと楽できる?」そんな方のお役に立てたらうれしいです。
前提
1:この記事では、gilab-ci.ymlの書き方は解説しません。
2:ECS、ECR自体の説明や、以下のリソース準備の手順は解説しません。
- ネットワークの設定
- ECR
- リポジトリ
- ECS
- クラスター
- サービス
- タスク定義
3:今回、以下の環境変数を事前に設定しています。
キー | 値 |
---|---|
ROLE_ARN | IAMロールのARN |
AWS_DEFAULT_REGION | AWSのリージョン |
CI_AWS_ECS_CLUSTER | AWS ECSクラスター名 |
CI_AWS_ECS_SERVICE | ECSのサービス名 |
CI_AWS_ECS_TASK_DEFINITION | ECSのタスク定義名 |
REPOSITORY_URL | ECRのリポジトリ名 |
さっそくイメージの説明
さて、早速ECSへのデプロイに使用できるイメージを紹介します。
AWSへのデプロイで使用可能なイメージは以下のcloud-deployリポジトリに公開されています。
https://gitlab.com/gitlab-org/cloud-deploy
AWSのベースイメージ
まずは先ほどのリポジトリの『ci』フォルダを確認してみましょう。jobの実行環境に使用できる、AWS CLIを含むイメージが提供されています。

このaws-baseイメージで参照されている『”aws/base/Dockerfile”』を辿ってみましょう。

AWS CLIをインストールする処理が書かれていますね。
つまり私たちは、以下のようにジョブの実行環境のimageをaws-baseに指定することで、既にAWS CLIがインストールされたイメージを使用してジョブを実行できるのです。
例:
#アプリケーションのコンテナをビルドしECRにプッシュするjob
build:
stage: build
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
services:
- docker:dind
script:
- aws ecr get-login-password | docker login --username AWS --password-stdin $REPOSITORY_URL
- docker build --tag $REPOSITORY_URL:$CI_COMMIT_SHA .
- docker push --quiet $REPOSITORY_URL:$CI_COMMIT_SHA
これで『pip install awscli』など書く必要がなくなります。aws コマンドを使用したいあらゆるジョブで活用できますね。
ECSデプロイ用のイメージ
続いて、先程のリポジトリの『ci』フォルダに戻ります。今度はaws-ecs.gitlab-ci.ymlを見てみましょう。これはECSへデプロイするジョブで使用できるイメージです。

このaws-ecs で参照されている『”aws/ecs/Dockerfile”』を辿ってみましょう。

すると、aws/src/bin/ecs というファイルを参照しているため、さらに辿ってみます。

ECSタスク定義を更新するためのスクリプトですね〜〜〜。内容を見ていきましょう。
まず最初に目に入るupdate_task_definition関数にECSタスク定義を更新する処理が書いてあります。ほかにも色々な関数が定義されていて、一番最後にこれが。↓
option=$1
case $option in
update-task-definition) update_task_definition ;
stop-task) stop_task ;;
get-task-hostname) get_task_hostname ;;
*) exit 1 ;;
esac
このスクリプト、なかなかの働き者です。引数に応じて、ECSタスクに対して以下の3つの操作をこなしてくれます:
- update-task-definition(タスク定義の更新)
- stop-task(タスクの停止)
- get-task-hostname(ホスト名の取得)
たとえば、ジョブの中でECSのタスク定義を更新したいと思ったら、その処理は全部 update_task_definition 関数を使えば良いのです。
ジョブで ci/aws-ecs.gitlab-ci.yml イメージを使って、引数にupdate_task_definitionって渡してあげるだけで、ECSタスク定義の更新ができます。
私は最初、このリポジトリにおいてあるGitLabの公式イメージをちゃんと読み解かなかったので、処理を一から書きました。『最初から用意されてたの?もっと早く言ってよ!』と言いたくなりましたが、ちゃんと読んでなかった自分のせいです。
それでは、ECSデプロイのジョブが、公式イメージを使用することで、どれくらいスッキリしたかみてみましょう。
before
deploy_to_ecs:
image: docker:latest
script:
- |
aws ecs describe-task-definition --task-definition "$CI_AWS_ECS_TASK_DEFINITION" > current-task-def.json
cat current-task-def.json | \
jq 'del(.taskDefinition.revision, .taskDefinition.status, .taskDefinition.requiresAttributes, .taskDefinition.compatibilities, .taskDefinition.registeredAt, .taskDefinition.registeredBy, .taskDefinition.taskDefinitionArn) | .taskDefinition' | \
jq --arg image "$REPOSITORY_URL:latest" '.containerDefinitions[0].image = $image' > new-task-def.json
NEW_TASK_DEF_ARN=$(aws ecs register-task-definition --cli-input-json file://new-task-def.json | jq -r '.taskDefinition.taskDefinitionArn')
echo "New task definition registered: $NEW_TASK_DEF_ARN"
aws ecs update-service --cluster "$CI_AWS_ECS_CLUSTER" --service "$CI_AWS_ECS_SERVICE" --task-definition "$NEW_TASK_DEF_ARN"
after
deploy_to_ecs:
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-ecs:latest
script:
- ecs update-task-definition
やった〜〜〜〜。こんなにイケメンになりました。脅威の15行→1行です。
さらに応用編として、これを『.deploy_to_ecs』として再利用可能なテンプレートジョブにすれば、複数環境へのデプロイジョブも、見通しよく書くことが可能です。
ちなみに$CI_TEMPLATE_REGISTRY_HOSTはGitLabで既に定義済みの変数です。値はCI/CDテンプレートが使用するレジストリのホストで、デフォルトはregistry.gitlab.com です。
公式ドキュメント:Predefined CI/CD variables reference | GitLab Docs
全体像はこんな感じ
コンテナビルド〜デプロイまでの、公式イメージを活かした全体像はこんな感じでしょうか。
stages:
- build
- pre_deploy
- deploy
- post_deploy
variables:
GITLAB_URI: https://gitlab.com
".retrieve-aws-credential": // OIDCで認証情報を取得するテンプレートジョブ
before_script: &retrieve-aws-credential
export $(printf "AWS_ACCESS_KEY_ID=%s AWS_SECRET_ACCESS_KEY=%s AWS_SESSION_TOKEN=%s"
$(aws sts assume-role-with-web-identity --role-arn ${ROLE_ARN} --role-session-name
"GitLabRunner-${CI_PROJECT_ID}-${CI_PIPELINE_ID}" --web-identity-token ${GITLAB_OIDC_TOKEN}
--duration-seconds 3600 --query 'Credentials.[AccessKeyId,SecretAccessKey,SessionToken]'
--output text));
".aws-base": // コンテナイメージをaws-baseに指定するテンプレートジョブ
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
extends:
- .retrieve-aws-credential
id_tokens:
GITLAB_OIDC_TOKEN:
aud: $GITLAB_URI
.deploy_to_ecs: // ECSデプロイのテンプレートジョブ
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-ecs:latest
extends:
- .aws-base
dependencies: []
script:
- ecs update-task-definition
build: // ビルド&ECRプッシュ
stage: build
extends: ".aws-base"
services:
- docker:dind
variables:
DOCKER_HOST: tcp://docker:2375/
DOCKER_TLS_CERTDIR: ""
script:
- aws ecr get-login-password | docker login --username AWS --password-stdin $REPOSITORY_URL
- docker build --tag $REPOSITORY_URL:$CI_COMMIT_SHA .
- docker push --quiet $REPOSITORY_URL:$CI_COMMIT_SHA
deploy_dev: // 開発環境デプロイ
stage: deploy
environment:
name: development
extends:
- ".deploy_to_ecs"
rules:
- if: $CI_COMMIT_BRANCH == "develop"
deploy_staging: // ステージング環境デプロイ
stage: deploy
environment:
name: staging
extends:
- ".deploy_to_ecs"
rules:
- if: $CI_COMMIT_BRANCH =~ /^staging\//
- if: $CI_COMMIT_BRANCH == "master"
- if: $CI_COMMIT_BRANCH =~ /^hotfix\//
deploy_production: // 本番環境デプロイ
stage: deploy
environment:
name: production
extends:
- ".deploy_to_ecs"
rules:
- if: $CI_COMMIT_TAG
おお〜〜〜っ。いい感じですね〜〜〜。
以前の私のように、公式イメージの存在を知らずにスクリプトが膨大になっている方。ぜひとも用意されたイメージを使用してみてください。
さいごに
ということで、今回はGitLabの公式イメージを使ってECR/ECSデプロイをラクにする方法をご紹介しました。
地味に面倒な処理も、公式イメージの活用やジョブの再利用でずいぶんカッコよくなります。そして純粋に、書くのラクです。
他にもGitLabでは、CICDパイプラインで使えるあらゆるジョブの公式テンプレートが以下のリポジトリにまとまっています。そちらもぜひ活用してみてください。
https://gitlab.com/gitlab-org/gitlab/-/tree/master/lib/gitlab/ci/templates
この記事が、どこかの yml 職人の肩こり軽減につながれば嬉しいです。ではまた・・・👋
※解説を省略する代わりに、リソース準備の参考ドキュメントを貼っておきます。