Backlog の Git リポジトリをソースとした Docker イメージのビルドパイプラインを作ってみた

「Backlog Git から Docker イメージをビルドするには」というお題でCI/CDパイプラインを考えてみました。
2023.02.25

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

こんにちは!AWS事業本部コンサルティング部のたかくに(@takakuni_)です。

今回は、Backlog の Git リポジトリ(以後、Backlog Git)をソースとして、 Docker イメージをビルドパイプラインを作ってみようと思います。

コミットIDをイメージタグにしたい

皆さんは Backlog Git のコミットID を Docker イメージのタグとして利用したいなと思ったことはありますか? 私はあります。

CodePipeline のソースに、Backlog Git が指定できないな...でも、Backlog Git で CI/CD をやってみたいな」という方もいらっしゃるのではないでしょうか。

では、さっそくやってみようと思います。

構成図

構成図は次の通りです。

また、ざっくりした仕組みは以下になります。

  1. Backlog Git の WebフックURL に API Gateway の URL を指定し、 push 時に API リクエストを送信
  2. API Gateway は CodeBuild プロジェクトを開始させ、 CodeBuild は Backlog Git に対してgit clone
  3. クローンした成果物を元に Docker イメージのビルド、 ECR へのプッシュ、アーティファクトの配置
  4. アーティファクトの配置をトリガーに CodePipeline を開始する
  5. 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つの設定が必要です。

  1. HTTP ヘッダーにX-Amz-Target: 'CodeBuild_20161006.StartBuild',Content-Type: 'application/x-amz-json-1.1'を含める
  2. 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 は以下の通りです。

  1. git-remote-codecommitをインストール
  2. 非対話形式に SSH でログインするため、 Backlog のホスト情報を登録
  3. Backlog Git に SSH で git clone
  4. Docker イメージタグに Git のコミットID(Backlog GUI と同じ桁数)を利用
  5. Docker イメージのビルド、 ECR へのプッシュ
  6. Blue/Green デプロイメントのためにアーティファクト作成およびオブジェクト配置
  7. (オプション) S3にgit-remote-codecommitのキャッシュ配置

buildspec.yaml

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 や別シークレットで分けて管理もいいと思います。

code_pipeline.tf

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にあたる部分を埋めます。

variables.tf

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_keylocal_fileリソースを利用して、秘密鍵を Secrets Manager 、公開鍵をローカルに保管するように設定しています。

ローカルに保管された公開鍵を Backlog に登録していきます。

Webhook URL の登録

API Gateway コンソールから API リクエストを送るための URL を取得します。

取得した URL を利用して、 WebフックURL を登録します。

Backlog Git へのソースコードの配置

GitHub に上がっている次のフォルダ(codesries_assetdocker)を 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_)でした!