Backlog の Git リポジトリをソースとした Docker イメージのビルドパイプラインを作ってみた
こんにちは!AWS事業本部コンサルティング部のたかくに(@takakuni_)です。
今回は、Backlog の Git リポジトリ(以後、Backlog Git)をソースとして、 Docker イメージをビルドパイプラインを作ってみようと思います。
コミットIDをイメージタグにしたい
皆さんは Backlog Git のコミットID を Docker イメージのタグとして利用したいなと思ったことはありますか?私はあります。
「CodePipeline のソースに、Backlog Git が指定できないな...でも、Backlog Git で CI/CD をやってみたいな」という方もいらっしゃるのではないでしょうか。
では、さっそくやってみようと思います。
構成図
構成図は次の通りです。
また、ざっくりした仕組みは以下になります。
- Backlog Git の WebフックURL に API Gateway の URL を指定し、 push 時に API リクエストを送信
- API Gateway は CodeBuild プロジェクトを開始させ、 CodeBuild は Backlog Git に対して
git clone
- クローンした成果物を元に Docker イメージのビルド、 ECR へのプッシュ、アーティファクトの配置
- アーティファクトの配置をトリガーに CodePipeline を開始する
- CodeDeploy が項番4のアーティファクトを元に ECS へデプロイ
コード
今回利用したコードは以下に格納されています。
適宜実装したい環境に合わせてコードの変更してご利用いただければ幸いです。
ここからは、どのような実装であるか解説していこうと思います。
Backlog から API Gateway の部分
Backlog では git push
時に指定した URL に POST リクエストを行う Webフック機能が提供されています。
API Gateway で POST リクエストを受け付け、後続の処理を行うように作ってみました。
API Gatewayの実装
CodeBuild へのリクエスト
API Gateway は、 CodeBuild に HTTP API を利用して StartBuild
アクションを実行します。
「どの CodeBuild のプロジェクトを開始するのか」を指定するには、リクエストで利用する HTTP ヘッダー周りに追加で2つの設定が必要です。
- HTTP ヘッダーに
X-Amz-Target: 'CodeBuild_20161006.StartBuild'
,Content-Type: 'application/x-amz-json-1.1'
を含める Content-Type: 'application/x-www-form-urlencoded'
に対して、{"projectName": "CodeBuildのプロジェクト名"}
を含め開始するプロジェクトを指定
IP制限
Backlog Git が利用する IP は公開されているため、気持ちばかりの IP 制限を API Gateway に付与してあげました。
Backlog ヘルプセンター Webhook 送信サーバーの IP アドレスを教えてください
ただ、以下のようにアドレスの変更もあるため、IP制限をかける / かけないは注意が必要です。
なお、 IP アドレス制限は AWS WAF ではなくリソースベースポリシーを利用しています。
ログ
今回はテスト用のためログ記録を有効化していませんが、実行ログを取るのもオススメです。
「AWS Foundational Security Best Practices コントロール」でも、ログ記録を有効にするよう推奨されているため、ぜひ有効化してみてください。
CodeBuild の実装
buildspec
CodeBuild で利用する buildspec は以下の通りです。
git-remote-codecommit
をインストール- 非対話形式に SSH でログインするため、 Backlog のホスト情報を登録
- Backlog Git に SSH で
git clone
- Docker イメージタグに Git のコミットID(Backlog GUI と同じ桁数)を利用
- Docker イメージのビルド、 ECR へのプッシュ
- Blue/Green デプロイメントのためにアーティファクト作成およびオブジェクト配置
- (オプション)S3に
git-remote-codecommit
のキャッシュ配置
version: 0.2
env:
variables:
DOCKER_BUILDKIT: "1"
AWS_PAGER: ""
phases:
install:
commands:
- pip3 install git-remote-codecommit
pre_build:
commands:
- eval $(ssh-agent)
- ssh-add - <<< "$BACKLOG_GIT_CREDENTIAL"
- ssh-keyscan $BACKLOG_SPACE_ID.git.$BACKLOG_DOMAIN_NAME >> ~/.ssh/known_hosts
build:
commands:
- echo Cloning repository ...
- git clone $BACKLOG_SPACE_ID@$BACKLOG_SPACE_ID.git.$BACKLOG_DOMAIN_NAME:/$BACKLOG_PROJECT_KEY/$BACKLOG_REPOSITORY_NAME.git
- cd $BACKLOG_REPOSITORY_NAME
- COMMIT_HASH=$(git log -n 1 --pretty=format:"%H" | cut -c 1-10)
- IMAGE_TAG=${COMMIT_HASH:=latest}
- echo Build started on `date`
- echo Building Docker image...
- docker image build -f docker/Dockerfile -t $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ECR_REPOSITORY_NAME:$IMAGE_TAG docker
post_build:
commands:
- echo Build completed on `date`
- echo Logging in to Amazon ECR...
- aws ecr get-login-password --region $AWS_DEFAULT_REGION | docker login --username AWS --password-stdin $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com
- echo Pushing Docker image...
- docker image push $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ECR_REPOSITORY_NAME:$IMAGE_TAG
- printf '{"ImageURI":"%s"}' $AWS_ACCOUNT_ID.dkr.ecr.$AWS_DEFAULT_REGION.amazonaws.com/$ECR_REPOSITORY_NAME:$IMAGE_TAG > $CODEBUILD_SRC_DIR/imageDetail.json
- cd codesries_asset
- sed -i -e "s#<TASK_FAMILY>#${TASK_FAMILY}#" taskdef.json
- sed -i -e "s#<TASK_EXECUTION_ROLE_ARN>#${TASK_EXECUTION_ROLE_ARN}#" taskdef.json
- sed -i -e "s#<CONTAINER_NAME>#${CONTAINER_NAME}#" taskdef.json
- sed -i -e "s#<AWS_DEFAULT_REGION>#${AWS_DEFAULT_REGION}#" taskdef.json
- sed -i -e "s#<LOG_GROUP_NAME>#${LOG_GROUP_NAME}#" taskdef.json
- sed -i -e "s#<LOG_STREAM_PREFIX>#${LOG_STREAM_PREFIX}#" taskdef.json
- sed -i -e "s#<CONTAINER_NAME>#${CONTAINER_NAME}#" appspec.yaml
- cp -p taskdef.json $CODEBUILD_SRC_DIR/
- cp -p appspec.yaml $CODEBUILD_SRC_DIR/
artifacts:
files:
- imageDetail.json
- taskdef.json
- appspec.yaml
cache:
paths:
- /root/.cache/pip/**/*
Backlog の認証情報
CodeBuild では Backlog Git に対して SSH でgit clone
するように設定しました。
Backlog Git へのgit clone
で利用するパラメーターとして以下を利用しました。
- スペースID
- ドメイン
- プロジェクトキー
- レポジトリ名
- 秘密鍵
パラメーターの数が多いため、 Key/Value 形式で取得できる Secrets Manager を利用して1つのシークレットにまとめて保管することにしました。
閲覧権限を分けたいやコスト要件もあるため、実際は Parameter Store や別シークレットで分けて管理もいいと思います。
resource "aws_secretsmanager_secret" "backlog_info" {
name = "${local.prefix}/backlog_info"
recovery_window_in_days = 0
policy = templatefile("${path.module}/iam_policy_document/resource_secretsmanager.json", {
role_arn = aws_iam_role.codebuild.arn
})
}
resource "aws_secretsmanager_secret_version" "backlog_info" {
secret_id = aws_secretsmanager_secret.backlog_info.id
secret_string = jsonencode({
space_id = var.backlog.space_id
domain_name = var.backlog.domain_name
project_key = var.backlog.project_key
repository_name = var.backlog.repository_name
ssh_key = tls_private_key.backlog.private_key_pem
})
lifecycle {
ignore_changes = [
secret_string
]
}
}
CodePipeline
後続の CodePipeline は S3 をトリガーにパイプラインの開始を行いました。
当初、 ECR へのプッシュをトリガーにしようと考えていましたが、設定パラメータを見るとイメージタグを固定する必要があり断念しました。
(buildspec でイメージタグを書き換えるパターンもありですが、なるべく仕組みをシンプルにしたい結果、 S3 オブジェクト配置をトリガーにしてみました。)
ImageTag
必須 いいえイメージに使用するタグ。
>
注記
ImageTag の値を指定しない場合、デフォルト値は latest になります。
AWS CodePipeline ユーザーガイド Amazon ECR
今回のコードを利用してみて、オブジェクトを配置したが CodePipeline がトリガーされない方は以下を併せてご覧ください。
動作検証
では実際に動作検証してみましょう。
Terraform
variables.tf
を確認するといくつか変数が定義されています。変数backlog
にあたる部分を埋めます。
variable "system" {
type = string
default = "backlog"
}
variable "env" {
type = string
default = "cicd"
}
variable "cidr" {
default = {
vpc = "192.168.0.0/16"
public_a = "192.168.0.0/24"
public_c = "192.168.1.0/24"
private_a = "192.168.2.0/24"
private_c = "192.168.3.0/24"
}
}
variable "backlog" {
default = {
space_id = "" # htts://sample.backlog.jp の sample の部分
domain_name = "" # backlog.jp または backlog.com
project_key = "" # https://backlog.com/ja/enterprise-help/userguide/userguide353/
repository_name = "" # Backlog Git のレポジトリ名
webhook_ip = [
"54.248.107.22/32",
"54.248.105.89/32",
"54.238.168.195/32",
"52.192.66.90/32",
"54.65.251.183/32",
"54.250.148.49/32",
"35.166.55.243/32",
"50.112.242.159/32",
"52.199.112.83/32",
"35.73.201.244/32",
"35.72.166.154/32",
"35.73.143.41/32",
"35.74.201.20/32",
"52.198.115.185/32",
"35.165.230.177/32",
"18.236.6.123/32",
] # https://support-ja.backlog.com/hc/ja/articles/360035645534-Webhook-%E9%80%81%E4%BF%A1%E3%82%B5%E3%83%BC%E3%83%90%E3%83%BC%E3%81%AE-IP-%E3%82%A2%E3%83%89%E3%83%AC%E3%82%B9%E3%82%92%E6%95%99%E3%81%88%E3%81%A6%E3%81%8F%E3%81%A0%E3%81%95%E3%81%84
}
}
準備ができたら Terraform でデプロイします。
terraform init
terraform plan
terraform apply
Backlog への公開鍵の登録
今回 Terraform の tls_private_key
とlocal_file
リソースを利用して、秘密鍵を Secrets Manager 、公開鍵をローカルに保管するように設定しています。
ローカルに保管された公開鍵を Backlog に登録します。
Webhook URL の登録
API Gateway コンソールから API リクエストを送るための URL を取得します。
取得した URL を利用して、 WebフックURL を登録します。
Backlog Git へのソースコードの配置
GitHub に上がっている次のフォルダ(codesries_asset
とdocker
)を Backlog Git レポジトリに配置します。
BACKLOG_GIT_REPO
├── codesries_asset
│ ├── appspec.yaml
│ └── taskdef.json
└── docker
└── Dockerfile
codesries_asset
フォルダでは、Blue Green デプロイのためのファイル。docker
フォルダは Docker イメージを作成するためのファイルが格納されいます。
このフォルダ周りの構成を変えたい場合は、 buildspec と調整しながらカスタマイズしてください。
cp -pR backlog-git-docker-build/BACKLOG_GIT_REPO/* {BACKLOG_GITのレポジトリ名}/
cd {BACKLOG_GITのレポジトリ名}/
git add .
git commit -m 'ADD docker image build asset.'
git push
Docker イメージを確認
コードのプッシュが終わると CodeBuild が起動していると思います。プッシュから時間が立っていなければ、ステータスが進行中かと思います。
ステータスが成功になると、 ECR を見てみましょう。初めの要件であった、Backlog Git のコミットID と ECR のイメージタグが一致してると思います。
Backlog Git 側
ECR 側
※ イメージタグ first
は、 Terraform の初回デプロイで利用したイメージになります。
Blue/Green デプロイ
少し忙しいですが、後続の CodePipeline が起動していると思います。
テストポートへの新バージョンの配置は5分間で設定しているため、必要に応じて時間を調節してください。
後片付け
後片付けが必要な方は、backlog-git-docker-build
フォルダに移動後、terraform destroy
で片付け可能です。
cd backlog-git-docker-build
terraform destroy
Backlog に登録した公開鍵、WebフックURLも必要に応じて削除してください。
参考
今回、以下のブログを大変参考にさせていただきました!ありがとうございます!
まとめ
以上、「Backlog の Git リポジトリをソースとした Docker イメージのビルドパイプラインを作ってみた」でした。
実際にテストして動きをご確認いただいた方が、イメージつきやすいと思うのでぜひお試しいただけると嬉しいです。
この記事がどなたかの参考になれば幸いです。
AWS事業本部コンサルティング部のたかくに(@takakuni_)でした!