Cloud Build の実行結果を Slack 通知する構成 - Cloud Run を利用

feature-image

Hugo のサイトをビルドしてサーバーへデプロイするまでの一連のフローを Cloud Build にて実現していますが、デプロイ結果の通知をやっていなかったので設定することにしました。ここでは、GCP環境での実現にあたってどのような手段が良いのかを調べたので、選定経緯を含めて記したいと思います。

ここでの説明は Hugo に依存しませんので、GCP の Cloud Build を使っている構成であれば一通り適用可能です。

最終構成(結論)

GCPオフィシャルドキュメントで説明されている Cloud Build Notifier を Cloud Run 環境上で動かす方式を選定しました。Cloud Build Notifier は、Cloud Run 環境上でコンテナとして実行される Docker イメージです。

調べる前は Cloud Functions (GCF) にエンドポイントを作って通知することになるかなと想像していました。もちろんこれでも問題ないのですが、Cloud Functions を使う場合はコードの管理が少なからず発生しますので、あまり時間をかけたいところではありません。

今回説明する方式は Cloud Run & Docker イメージを用いた構成のため、よりパッケージ化された構成で実現できました。手順が少ないかというと Secret Manager を使ったりPub/Subを使ったり、その他各種機能のAPIを有効化したりで工数面の比較のみだとどっちもどっちな印象はありますが、クラウドネイティブ感が出ているので試すことができてよかったなという印象です。

アーキテクチャ的には Clouf Functions でアプリを動かすか、それとも Cloud Run 上でアプリを動かすかの違いであり、本質的には同じものと考えられます。

Slack 通知を構成する  |  Cloud Build のドキュメント  |  Google Cloud

このチュートリアルに従うことで、以下の GCP サービスを体験することができます。

  • Cloud Build ← 主役
  • Cloud Pub/Sub
  • Cloud Run
    • Cloud Build Notifier #これそのものはDockerイメージにすぎない
  • Secret Manager
    • HashiCorp の Vaultみたいにシークレット情報の一元管理とキーのライフサイクルをマネジメントしてくれるサービス。
    • 興味はあったものの使う機会を作れていなかったので好都合でした。
  • Cloud Storage
    • Cloud Build Notifier の設定ファイルを1つ配置するために利用

以降はそれぞれ噛み砕きつつ記載します。やっていることは結局 公式ガイドの内容をなぞっているだけですが、ところどころで理解の補足を書いています。

Cloud Run とは

Cloud Run は、Google のスケーラブルなインフラストラクチャ上でコンテナを直接実行できるマネージド コンピューティング プラットフォームと説明されています。

Picture Source:  priyankavergadia/GCPSketchnote

Docker による構成管理に慣れている場合は、Cloud Functions を使わずに Cloud Run に寄せてしまうのもひとつの解決策だと思いますが、お手軽にコードのデプロイだけしたい場合は Cloud Functions も有力です。スキルセットに応じて選択するのがよさそうです。

責任分界点は、以下の通り定義されています。

Cloud Run サービスでは、信頼性の高い HTTPS エンドポイントを実行するために必要なインフラストラクチャが提供されます。コードが TCP ポートでリッスンし、HTTP リクエストを処理することを確認するのは、お客様の責任となります

Cloud Run とは  |  Cloud Run のドキュメント  |  Google Cloud

Cloud Run とは  |  Cloud Run のドキュメント  |  Google Cloud

Slack 通知を実現するアーキテクチャ概要

概要理解を目的としていますので、言葉の節々は厳密には不正確です。大枠の把握として捉えてください。 ここを抑えておくと、チュートリアルの理解の助けになると思います。

  • Cloud Build が Cloud Pub/Sub に対してイベントを通知
    • イベントは、「ビルド成功したよ!」とか「失敗したよ!」といった情報
  • Cloud Pub/Sub からのイベント連絡を待ち受けている Cloud Build Slack Notifier (サブスクライバー)が検知
  • Cloud Build Slack Notifier がSlackに対し通知

実行環境として Cloud Run を用いているものの、アーキテクチャ上は存在感が薄いので、さほど意識しなくても自然と使っている感じです。Cloud Run そのものの料金は、少々使う程度ならば無料内に収まると思われます。

料金  |  Cloud Run  |  Google Cloud(2021/11/14時点)

  • 毎月最初の 180,000 vCPU 秒は無料
  • 毎月最初の 360,000 GiB 秒は無料
  • 毎月 200 万リクエストは無料
  • 北米内の下り(外向き)は毎月 1 GiB まで無料

CPU、メモリ、リクエスト、そしてトラフィックという4つの指標で課金計算されるようですね。

前提条件

  • gcloud コマンドが利用できること
  • Cloud Build そのものの設定(cloudbuild.yml などを用いたビルド動作の設定)が済んでいること
    • この記事では、Cloud Build 実行後の結果を通知するための情報整理なのでフェーズが少々異なります
  • Slack の Inccoming Webhook を予め用意しておくこと
    • むかーしから Inccoming Webhook を使っていて、かつたまにしか使わない場合、ふと気づくとSlackno画面構成が変わっていてこまることが度々あります。
      1. https://api.slack.com/apps/ にアクセスして、自身のSlackアプリを選択(なければ作成)する
      2. 「Incoming Webhooks」を選択
      3. 「Webhook URL」の欄にURLがある

チュートリアルの補足

すべて 公式チュートリアル通りなので全てをなぞることはせず、軽く補足をするに留めます。

チュートリアルには度々以下のような表現が出てきますが、赤い鉛筆の部分は編集可能で、いくつかで使いまわしをする文字列なので、入力しておくとその後のコピペが楽で便利だと思います。

(↑の鉛筆マーク)

以下の変数は頻出ですので準備しておきましょう。

  • project-id
    • gcloud config list で現在の設定を確認可能。project キー部分にある値。
  • project-number
    • Cloud プロジェクト番号。手軽に確認するためのワンライナーは以下の通り。
    • gcloud projects describe $GOOGLE_CLOUD_PROJECT --format json | jq ".projectNumber | tonumber"
    • ここで GOOGLE_CLOUD_PROJECT は、最初似確認した project-id を意図しています。

Cloud Run の実行準備

Cloud Run の実行が初めてと想定される場合は、以下のコマンドを叩いて確認しておくとよいでしょう。 gcloud run services list は現在の設定状況(デプロイ状態)を確認できますが、初めての実行の場合は以下の通りAPIを有効化するか確認されます。(確認していませんが、初操作がいきなりデプロイでも同様の確認が出るのだろうとは思います)

$ gcloud run services list
API [run.googleapis.com] not enabled on project [123projectnumber890]. Would you like to enable and retry (this will take a few minutes)? (y/N)?  y

Enabling service [run.googleapis.com] on project [123projectnumber890]...
Operation "operations/acf.p2-123projectnumber890-88887777-0000-1111-2222-33334444aaaa" finished successfully.
Listed 0 items.

なおこのチュートリアルを通じて一通りのセットアップが終わると、実行結果は以下のように表示されます。

   SERVICE                  REGION    URL                                                      LAST DEPLOYED BY   LAST DEPLOYED AT
✔  yourname-slack-notifier  us-westX  <https://yourname-slack-notifier-XXXXXXXXXX-XX.a.run.app>  [email protected]  2021-11-13T08:42:27.796066Z

IAM関連の設定

いくつかIAMの権限設定がありますが、それぞれ以下の目的に対して設定するものです。

  • “Compute Engine のデフォルトのサービス アカウント” が、 Secret Manager を参照して情報を取得できるように権限付与する
  • Cloud Run サービスアカウントが Cloud Storage バケットを参照するために “Compute Engine のデフォルトのサービス アカウント” にストレージオブジェクト閲覧権限を付与する
  • プロジェクトに認証トークンを作成する Pub/Sub 権限を付与する
  • cloud-run-pubsub-invoker サービスアカウントに Cloud Run Invoker 権限を付与する

Notifier 構成ファイルを作成

apiVersion: cloud-build-notifiers/v1
kind: SlackNotifier
metadata:
  name: example-slack-notifier
spec:
  notification:
    filter: build.status == Build.Status.SUCCESS
    delivery:
      webhookUrl:
        secretRef: webhook-url
  secrets:
  - name: webhook-url
    value: projects/project-id/secrets/secret-name/versions/latest

Notifier の設定ですが、Kubernetesそのものって感じのコンフィグです。spec.notification.filter に記載されている条件式で通知対象の挙動を変更できるようです。詳しくはマニュアルに複数のパターンが記載されています。

Notifier を Cloud Run にデプロイ

 gcloud run deploy service-name \\
   --image=us-east1-docker.pkg.dev/gcb-release/cloud-build-notifiers/slack:latest \\
   --no-allow-unauthenticated \\
   --update-env-vars=CONFIG_PATH=config-path,PROJECT_ID=project-id

image はGoogleが提供している Docker Image のレジトリーです。 config-path は、格納した設定ファイルの場所を示すCloud Storage のバケットを含むフルパスを記載します。gs://...

Cloud Build から通知する先は cloud-builds トピック

Cloud Build では、ビルドの作成時、ビルドの動作状態への移行時、ビルドの完了時など、ビルドの状態が変化したときに Google Pub/Sub トピックにメッセージを公開します。Cloud Build がビルド更新メッセージを公開する Pub/Sub トピックは cloud-builds です。

とのことで、トピック名は固定のようです。

なお トピックサブスクリプション という単語が出てきますが、トピックが入り口、サブスクリプションが出口(サブスクライバー≒アプリケーションがイベント待受をする場所)と捉えれば概ね困らないかと思います。サブスクリプションも自由に名前をつけられるのがかえって面倒ですが、cloud-builds-subscriber みたいな名前にするなど、適当で構いません。

ビルド通知へのサブスクリプション  |  Cloud Build のドキュメント  |  Google Cloud

Secret Manager

今回のチュートリアルでは画面操作でしたのでコマンドはありませんでしたが、以下の通り別のガイドも存在しています。

Secret Manager では、シークレットをバイナリ blob またはテキスト文字列として保存、管理、アクセスできます。適切な権限を使用して、シークレットのコンテンツを表示できます。

Secret Manager は、実行時にアプリケーションが必要とする構成情報(データベース パスワード、API キー、TLS 証明書など)を保存するのに便利です。

クイックスタート  |  Secret Manager のドキュメント  |  Google Cloud

Kubernetes なんかを使っていると ConfigMapSecret の構成ファイル管理が煩雑になってくるほか、そもそもシークレット系の情報をテキストファイルで管理するのはもろもろ気分がよくありませんので、この手のシークレットマネージャーに一元化していくのが望ましいと思います。(まだ実用的なレベルで活用したことがないので表面的な意見に留めます)

作業で用いるコマンド例

通知機能構成ファイルを Cloud Storage バケットにアップロード

# 初回のみ。バケット名は任意なので適当に置換
gsutil mb gs://cloudrun-settings/

# ファイルをバケットにコピーする
gsutil cp cloudbuild-slack-notifier.yml gs://cloudrun-settings/cloudbuild-slack-notifier.yml

Cloud Run 有効化

# Slackイメージのバージョンは年月によって変わっているので、最新情報はドキュメントを参照して置き換え
gcloud run deploy cloudbuild-slack-notifier \
   --image=us-east1-docker.pkg.dev/gcb-release/cloud-build-notifiers/slack:slack-1.14.0 \
   --no-allow-unauthenticated \
   --max-instances=1 \
   --update-env-vars=CONFIG_PATH=gs://cloudrun-settings/cloudbuild-slack-notifier.yml,PROJECT_ID=$PROJECT_ID

サブスクリプション (Subscription) 作成

Cloud Run Invoker 権限を付与したサービスアカウント名を変数に入れて起きます。好みですが、手順の使いまわしがしやすいため。コマンドの出力時のスペースの都合で awk の実行結果に差が生じやすいので、awk -F'[[:space:]][[:space:]]+' としてスペースが2連続する部分をデリミタとします。

# Cloud Run Invoker 権限を付与したサービスアカウント名を取得
invoker_iam=$(gcloud iam service-accounts list | grep invoker | awk -F'[[:space:]][[:space:]]+' '{print $2}'); print $invoker_iam
  • $invoker_iam に値が入っていることを確認し、Subscription を作成します。
  • --push-endpoint にはデプロイした CloudRun アプリのURLをセットするため、 gcloud run services describe cloudbuild-slack-notifier --format="value(status.url)" で取得しています。
# サブスクリプションを作成。サブスクリプション名は任意。
gcloud pubsub subscriptions create run-cloudbuild-slack-notifier \
   --topic=cloud-builds \
   --push-endpoint=$(gcloud run services describe cloudbuild-slack-notifier --format="value(status.url)") \
   --push-auth-service-account=$invoker_iam

ただし、上記のデフォルトで作成すると、一定期間のビルドがない場合にサブスクリプションが消えてしまいます。久しぶりに利用したときに動かずに困惑してしまうので、無期限とする場合は以下のオプションを設定します。

--expiration-period="never

CloudRun操作

  • gcloud run services list
    • デプロイしているアプリの一覧
  • cloud run services delete $APP_NAME
    • アプリを削除する
SERVICE_URL=$(gcloud run services describe cloudbuild-slack-notifier --format="value(status.url)"); echo $SERVICE_URL

一通り設定したつもりだけど通知が行われない場合

  • Cloud Run の GCPコンソールにて、デプロイしたアプリ(サービス)からログを確認できるので見てみる。
  • 認証が足りていない場合は以下のエラーが出る。サービスに対して Invoker 権限を付与する必要がある。

The request was not authenticated. Either allow unauthenticated invocations or set the proper Authorization header. Read more at https://cloud.google.com/run/docs/securing/authenticating Additional troubleshooting documentation can be found at: https://cloud.google.com/run/docs/troubleshooting#unauthorized-client

ROLE付与コマンド

# `cloudbuild-slack-notifier` は Run にデプロイしたサービス名を書く
gcloud run services add-iam-policy-binding cloudbuild-slack-notifier \
  --member=serviceAccount:cloud-run-pubsub-invoker@$(gcloud config get-value project).iam.gserviceaccount.com \
  --role=roles/run.invoker

まとめ

Cloud Functions でサクッと Slack 通知機能を作ってしまうのが手っ取り早かったとは思いますが、同じ技術の繰り返しも面白くないので新しい構成にトライしました。GCPのドキュメントがしっかりしているので特に困る部分はないのですが、手順をなぞる上で意味の確認などで時間をとった部分もありますので、復習も兼ねて記録しました。

少しでも理解の補助になればと思います。