🔔

Cloud Buildの結果をSlackに通知する

2022/12/22に公開

はじめに

こんにちは、株式会社トレタのサーバーサイドエンジニアの @shiroemons です。

https://tech.toreta.in/entry/2022/12/22/notify-slack-of-cloud-build-results

前回は、Cloud Buildでpsqldefを使用してCloud SQLにマイグレーションする方法を紹介しました。

https://zenn.dev/shiroemons/articles/80f70e2eb155b3

Cloud Buildは便利ですが、結果を確認しに行かないといけないのが少々手間です。

そこで今回は、Cloud Buildの結果をSlackに通知する方法を紹介したいと思います。

今回のゴール

  • 成功の場合の通知画面

    • 成功ステータス(SUCCESS)と ✅ が表示されている
  • 失敗の場合の通知画面

    • 成功以外のステータス(FAILUREなど)と ❌ が表示されている

Slackアプリ

アプリを新規作成

専用のSlackがあるわけではないので、以下より新規作成してください。

https://api.slack.com/apps

アプリ名とアイコンは各自で設定してください。
ここでは、アプリ名を「Cloud Build 結果通知」としています。
アイコンは、こちらのSVGとPNGのアイコンのものを設定しています。

Webhook URLを生成

  1. 作成したSlackアプリのIncoming Webhooksにアクセスする。
  2. [Add New Webhook to Workspace]ボタンをクリックする。
  3. 通知するチャンネルを選択する。
  4. [許可する]ボタンをクリックする。
  5. Webhook URLが生成される。

Google Cloud 側の作業

https://cloud.google.com/build/docs/configuring-notifications/configure-slack?hl=ja

上記の資料を元に作業を行います。

必要なAPIを有効化

始める前に の[API を有効にする]ボタンから有効にできます。

Secret Manager

  • Webhook URLを保存します。
    • ここでは、シークレット名を cloudbuild_slack_notifier_webhook_url で保存しました。

IAM

  • <プロジェクト番号>-compute@developer.gserviceaccount.com に対して、
    • ロール Secret Manager のシークレット アクセサー を付与します。
    • ロール Storage オブジェクト閲覧者 を付与します。

Google Cloud側の作業は以上です。

ローカル環境での作業

macOSを対象として記載しています。

事前準備

  1. Google Cloud SDKをインストールする。

    brew install --cask google-cloud-sdk
    # Homebrew のバージョンが 2.6 以前の場合
    # brew cask install google-cloud-sdk
    
  2. アカウント連携する。

    gcloud auth login
    
  3. gcloud configの設定する。

    • 対象プロジェクトに設定されているか確認します。
    gcloud config list
    
  4. プロジェクトを設定する。

    • 対象プロジェクトを設定します。
    gcloud config set project <プロジェクトID>
    
  5. デフォルトのリージョンを設定する。

    • リージョンをasia-northeast1(東京)に設定します。
    gcloud config set run/region asia-northeast1
    

Slack Notifier の clone

公式がGitHubで公開している GoogleCloudPlatform/cloud-build-notifiers をcloneします。

git clone git@github.com:GoogleCloudPlatform/cloud-build-notifiers.git

clone したディレクトリへ移動します。

cd cloud-build-notifiers

config ファイル(slack.yaml)の作成

  • exampleファイルからコピーして作成します。

    cp ./slack/slack.yaml.example ./slack/slack.yaml
    
  • configファイル(slack.yaml)をカスタマイズします。

    • カスタマイズの差分は、こちらです。
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: cloud-build-notifiers/v1
kind: SlackNotifier
metadata:
-  name: example-slack-notifier
+  name: cloudbuild-slack-notifier
spec:
  notification:
-    filter: build.status == Build.Status.SUCCESS
+    filter: build.status in [Build.Status.SUCCESS, Build.Status.FAILURE, Build.Status.INTERNAL_ERROR, Build.Status.TIMEOUT]
    params:
      buildStatus: $(build.status)
+      buildTriggerName: $(build.substitutions['TRIGGER_NAME'])
+      buildRepository: $(build.substitutions['REPO_NAME'])
+      buildBranch: $(build.substitutions['BRANCH_NAME'])
+      buildCommit: $(build.substitutions['SHORT_SHA'])
    delivery:
      webhookUrl:
        secretRef: webhook-url
    template:
      type: golang
-      uri: gs://example-gcs-bucket/slack.json
+      uri: gs://<プロジェクトID>-notifiers-config/slack.json

  secrets:
  - name: webhook-url
-    value: projects/example-project/secrets/example-slack-notifier-webhook-url/versions/latest
+    value: projects/<プロジェクトID>/secrets/<シークレット名>/versions/<バージョン>
カスタマイズ後のslack.yaml
# Copyright 2020 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

apiVersion: cloud-build-notifiers/v1
kind: SlackNotifier
metadata:
  name: cloudbuild-slack-notifier
spec:
  notification:
    filter: build.status in [Build.Status.SUCCESS, Build.Status.FAILURE, Build.Status.INTERNAL_ERROR, Build.Status.TIMEOUT]
    params:
      buildStatus: $(build.status)
      buildTriggerName: $(build.substitutions['TRIGGER_NAME'])
      buildRepository: $(build.substitutions['REPO_NAME'])
      buildBranch: $(build.substitutions['BRANCH_NAME'])
      buildCommit: $(build.substitutions['SHORT_SHA'])
    delivery:
      webhookUrl:
        secretRef: webhook-url
    template:
      type: golang
      uri: gs://<プロジェクトID>-notifiers-config/slack.json

  secrets:
  - name: webhook-url
    value: projects/<プロジェクトID>/secrets/<シークレット名>/versions/<バージョン>
  • カスタマイズの内容は以下の通りです。
    • metadata.name
      • example から cloudbuild に変更します。
    • spec.notification.filter
      • デフォルトのままだと成功しか通知しないため、失敗やタイムアウトも通知するように変更します。
    • spec.notification.params
      • Slack通知時に情報が足りないのため以下の情報を追加します。
        • トリガー名、リポジトリ名、ブランチ名、短いコミットハッシュ
    • spec.notification.template.uri
      • プロジェクトごとに変更が必要
      • Slack Notifierのデプロイ時に自動で配置されるパスに変更します。
      • ※※※注意事項: ここの設定はSlack Notifier の デプロイ後に使用する値です。デプロイ時には使用されないことに注意※※※
    • secrets.value
      • プロジェクトごとに変更が必要
      • WebhookURLを設定したSecret Managerのパスを設定します。
      • バージョンは、基本的に latest のままで良いと思います。

Slackテンプレートファイル(slack.json) をカスタマイズ

  • デフォルトのSlackテンプレートのままだと味気なく情報量も少ないのでカスタマイズします。
  • 以下のslack.jsonの内容を ./slack/slack.json に上書きする。
[
  {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": "Cloud Build build state *{{.Params.buildStatus}}*. {{ if eq .Params.buildStatus `SUCCESS` }}✅{{ else }}❌{{ end }}"
    }
  },
  {
    "type": "section",
    "text": {
      "type": "mrkdwn",
      "text": "*Trigger:* {{.Params.buildTriggerName}}"
    }
  },
  {
    "type": "section",
    "fields": [
      {
        "type": "mrkdwn",
        "text": "*ProjectId:*\n{{.Build.ProjectId}}"
      },
      {
        "type": "mrkdwn",
        "text": "*Repository:*\n{{.Params.buildRepository}}"
      },
      {
        "type": "mrkdwn",
        "text": "*Branch:*\n{{.Params.buildBranch}}"
      },
      {
        "type": "mrkdwn",
        "text": "*Commit:*\n{{.Params.buildCommit}}"
      }
    ]
  },
  {
    "type": "divider"
  },
  {
    "type": "actions",
    "elements": [
      {
        "type": "button",
        "text": {
          "type": "plain_text",
          "text": "View Build Logs"
        },
        "value": "click_me_123",
        "url": "{{.Build.LogUrl}}"
      }
    ]
  }
]

Setup シェル(setup.sh)の修正

  • Cloud Storageのロケーションを設定する。
    • デフォルトのままだと、マルチリージョンのusが設定されます。
    • upload_config 関数を修正し、デュアルリージョンのasia1に設定しています。
upload_config() {
  # We allow this `mb` command to error since we rely on the `cp` command hard-
  # erroring if there's an actual problem (since `mb` fails if the bucket
  # already exists).
-  gsutil mb "${DESTINATION_BUCKET_URI}"
+  gsutil mb -l asia1 "${DESTINATION_BUCKET_URI}"

  gsutil cp "${SOURCE_CONFIG_PATH}" "${DESTINATION_CONFIG_PATH}" ||
    fail "failed to copy config to GCS"

  if [ ! -z "${SOURCE_TEMPLATE_PATH}" ]; then
    gsutil cp "${SOURCE_TEMPLATE_PATH}" "${DESTINATION_TEMPLATE_PATH}" ||
      fail "failed to copy template to GCS"
  fi

}
  • スケールできる最大数を設定する。
    • deploy_notifier 関数を修正し、最大1台までに設定しています。
deploy_notifier() {
  gcloud run deploy "${SERVICE_NAME}" \
    --image="${IMAGE_PATH}" \
    --no-allow-unauthenticated \
+    --max-instances=1 \
    --update-env-vars="CONFIG_PATH=${DESTINATION_CONFIG_PATH},PROJECT_ID=${PROJECT_ID}" ||
    fail "failed to deploy notifier service -- check service logs for configuration error"
}
  • サブスクリプションの有効期限を無期限に設定する
create_pubsub_subscription() {
  gcloud pubsub subscriptions create "${SUBSCRIPTION_NAME}" \
    --topic=cloud-builds \
    --push-endpoint="${SERVICE_URL}" \
-    --push-auth-service-account="${INVOKER_SA}"
+    --push-auth-service-account="${INVOKER_SA}" \
+    --expiration-period="never"
}

Slack Notifier の デプロイ

すべての設定が完了したのでsetupシェルを実行します。

./setup.sh slack ./slack/slack.yaml -t ./slack/slack.json

以下のように最後に出力されれば成功です。

+ echo '** NOTIFIER SETUP COMPLETE **'
** NOTIFIER SETUP COMPLETE **
  • デプロイで実行される内容
    1. Cloud Storageに「<project id>-notifiers-config」を作成
    2. slack.yamlとslack.jsonを作成したCloud Storageにアップロード
    3. Cloud Runに「slack-notifier」をデプロイ
    4. 必要なIAMの設定
    5. サービスアカウント「Cloud Run Pub/Sub Invoker」を作成
    6. 必要なIAMの設定
    7. Cloud Pub/Subトピックに「cloud-builds」を作成
    8. Cloud Pub/Subサブスクリプションに「slack-subscription」を作成

失敗した場合は、ログやCloud Runのログを確認してください。

2回目以降の実行の場合は、作成済みのものは ERROR と表示されますがとくに問題ありません。

動作確認

Slack Notifierのデプロイ成功後は、実際にCloud Buildを動かし、動作確認を行いましょう。

以下のような通知がSlackに届いていれば成功/完成です。

苦労したところ

Google Cloudに公式ガイドがあるのは嬉しいですね。Slack通知ようのアプリもあるのもありがたいです。

ただ既存のままだと情報量が少なくそっけなかったので、slack.jsonをカスタマイズして必要そうな情報も表示することができました。

カスタマイズを行うにあたり参照に記載したリンクからCloud BuildやSlackについて調べながら進めました。

苦労したところは、成功や失敗のステータスを絵文字(✅や❌)で表現するところです。

"Cloud Build build state *{{.Params.buildStatus}}*. {{ if eq .Params.buildStatus `SUCCESS` }}{{ else }}{{ end }}"

Go Templateの記載で解決することができました。

さいごに

今回は、Cloud Buildの結果をSlackに通知するための設定方法を紹介しました。

トレタでは、Cloud Buildを使用しているプロジェクトに今回の設定方法を共有し、
Cloud Buildの結果がSlack通知で届くように設定しています。

Cloud Buildの結果をSlack通知したいと悩んでいる方の助けになれば幸いです。

なお、トレタではエンジニアの募集を全方位で行なっております。

コロナ禍を乗り越えた飲食店の新しい姿を探求する仲間をお待ちしております。

https://corp.toreta.in/recruit/engineer/

参考

Discussion