Cloud Build Pub/Subトリガーで別プロジェクトのトピックをサブスクライブする

Cloud Buildでは、リポジトリの更新以外にCloud Pub/Subへのメッセージをトリガーにしてビルドを開始することができます。
今回、複数プロジェクト間でPub/Subトリガーをサブスクライブする設定をしてみましたので、設定するにあたってつまづいたポイントなどを共有したいと思います。

## 何をしたかったのか

ビルド用プロジェクトでDockerイメージをビルドして、ビルド完了をトリガーにしてサービス用プロジェクトでデプロイしようとしました。

![8c2a549f-7d60-4d4e-9ad4-27f1334a2347.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609129/75c3b5d0-22ed-36a4-886f-f808cb9a34f6.png)

このような場合、以下のように、ビルド用プロジェクトから、サービス用プロジェクトのCloud Buildをトリガーするか、サービス用プロジェクトのPub/Subトピックにメッセージを送る構成で説明されている資料をよく見ました。

![c3ea8377-bc84-48b6-9b73-9ff61e92e8bb.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609129/fbf7b8b8-7b2c-4138-d0b4-94762e4d0f8e.png)

しかしながら、サービス用プロジェクトに穴を開けたくない、ビルド用プロジェクトはサービス用プロジェクトのことを知っていて欲しくないと思いました。そこで、Cloud Pub/Subを間に入れ、サービス用プロジェクトからトピックをサブスクライブしたいと思いました。

![5cd3b223-1519-44b7-8825-afffbe1261dc.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609129/f2f4a833-06b9-fbb2-8860-433215a1c31e.png)

Pub/Subに流れたメッセージを元に、デプロイ先の環境種別(開発環境/ステージング環境/本番環境)、デプロイに使うべきイメージのIDなどを通知し、そのメッセージを起点としてサービス用プロジェクトのデプロイ用Cloud Buildトリガーを起動するすという形にしてみました。

## Pub/Subトリガーの設定

GCPコンソールでCloud Build トリガーを設定する際、イベントで「Pub/Subメッセージ」を選択することでPub/Subメッセージを受信してビルドを開始するトリガーを設定することができます。
この時、「プロジェクトを切り替え」を選ぶことで他のプロジェクトのトピックを選択することができます。

![c6f4337f-1ef4-48ae-a02e-a9305034e441.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609129/f89a6853-2f7e-556c-61c2-d9f566fd3f0f.png)

他のプロジェクトのトピックを選択した場合「選択したプロジェクトのトピックへのアクセス権限を付与してください」と表示され「付与」をクリックすることで、権限が付与されてサブスクライブができる状態になります。

![72765301-8eb1-4d95-8de5-ccb9c41b26e2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/609129/29ddbdf4-c54d-5fe2-022d-ae0c8821eddf.png)

この「付与」ボタンを押すと、サブスクライブ先のプロジェクトのIAMに、サブスクライブする側のCloud Build サービスエージェントのサービスアカウントに対するPub/Subサブスクライバーのロールが付与されます。
今回だと、ビルド用プロジェクトのIAMに、サービス用プロジェクトのサービスアカウントのPub/Subサブスクライバーのロールが追加されます。
ただ、この操作ではサブスクライブ先(ビルド用プロジェクト)にある全てのPub/Subトピックに対して許可されてしまいますので、気になる場合は設定の前に、サブスクライブを許可するトピック個別に対するIAMを設定しておくと良いでしょう。

ちなみに、Cloud Buildサービスエージェントのサービスアカウントのアドレスは`service-(projectNumber)@gcp-sa-cloudbuild.iam.gserviceaccount.com`というものです。
今回苦労したのは、コンソールを使わずにTerraformのコードを書いていたことで、この権限をどのサービスアカウントに付与すればいいかわからなかったことでした。

あともう1点、サービス用プロジェクトが増えるたびにビルド用プロジェクトのIAMを設定しないといけない点も気になります。これは、サービス用プロジェクトのCloud Buildサービスエージェントを集約したGoogleグループを作り、そのグループに対してPub/Subサブスクライバーのロールを与えれば良いのではないかと思っています。

また、今回はビルド用プロジェクトのアーティファクトを使ってデプロイを実施するので、トリガーされたサービス用プロジェクトではリポジトリのコードを使いませんが、Webのコンソールから設定した場合は、ソースリポジトリの入力が必須となってしまいます。
ビルドプロジェクトの空のリポジトリを設定しておく方法もありますが、Terraformを使って設定する場合はソースリポジトリを設定せずとも設定が可能でした(ただし、この場合はビルドステップもcloudbuild.yamlに書くことはできず、Terraformの中で書くことになりました)。

## Pub/Subメッセージに含まれる情報をビルド内で使用する

今回の構成では、Pub/Subメッセージにはデプロイするべきコンテナイメージのタグなどが含まれており、その値をCloud Buildのビルドステップ中に使用する必要があります。

Cloud Buildのガイドには[Pub/Sub イベントに応答してビルドを自動化する](https://cloud.google.com/build/docs/automate-builds-pubsub-events?hl=ja)というページがあり、Artifact RegistryやCloud Storageなどの例がある一方、これら以外の使い方の解説がなく、カスタムのメッセージの場合にどうすれば良いかぱっと見でわからず苦労しました。

実際はPub/Sub メッセージに含まれている値は[ペイロードバインディング](https://cloud.google.com/build/docs/configuring-builds/use-bash-and-bindings-in-substitutions?hl=ja)を用いて中身を参照することができ、Pub/Subメッセージの本文は `$(body.message.***)`、属性は `$(body.attributes.***)` で取得できます。

トリガーの構成で代入変数にこれを指定することもできますし、cloudbuild.yamlに以下のように設定することもできます。

```yaml
steps:
- name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
  args: [ 'gsutil', 'cp', 'gs://${_BUILD_PROJECT}-packages/${_DEPLOY_STAGE}/commits/${_COMMIT_SHA}/package.zip', 'package.zip' ]
substitutions:
  _BUILD_PROJECT: $(body.message.buildProject)
  _COMMIT_SHA: $(body.message.commit)
  _DEPLOY_STAGE: $(body.message.stage)
```