2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Cloud Buildのデプロイ失敗をDiscordに通知する仕組みを構築する

Last updated at Posted at 2025-02-25

概要

Cloud Buildでのデプロイ失敗を検知し、Discordに通知する仕組みを構築してみたので備忘録としてまとめます。Cloud Run、Cloud Pub/Sub、Cloud Loggingを組み合わせて、自動通知システムを実現します。

アーキテクチャ

今回やりたいことのイメージ図です

前提条件

  • GCPプロジェクトの作成済み
  • 必要な権限の設定
    • Cloud Run 管理者
    • Cloud Build 管理者
    • Cloud Logging 管理者
    • Cloud Pub/Sub 管理者
  • Discord Webhookの設定済み

手順

1. Cloud Pub/Sub トピックの作成

  • トピックID: cloud-run-deploy-errors
  • デフォルトのサブスクリプションを追加する
  • Googleが管理する暗号鍵

create_topic.png

2. 通知サービスの実装

deploy-notify.ts

import express from 'express'
import { fetch } from 'undici'

const app = express()
const port = process.env.PORT || 8080

interface DiscordEmbed {
    title?: string
    description?: string
    color?: number
    fields?: Array<{
        name: string
        value: string
        inline?: boolean
    }>
    timestamp?: string
}

const sendDeploymentNotification = async (
    message: { message: { data: string } },
    context: { eventId: string; timestamp: string },
): Promise<void> => {
    try {
        if (!message.message?.data) {
            throw new Error('Pub/Sub メッセージデータが存在しません')
        }

        const data = Buffer.from(message.message.data, 'base64').toString()
        const logEntry = JSON.parse(data)

        // Cloud Buildのエラー情報を取得
        const buildId = logEntry.resource?.labels?.build_id || '不明なビルドID'
        const projectId = logEntry.resource?.labels?.project_id || '不明なプロジェクト'
        const triggerId = logEntry.resource?.labels?.build_trigger_id || '不明なトリガー'
        const errorCode = logEntry.protoPayload?.status?.code
        const serviceName = logEntry.protoPayload?.serviceName || '不明なサービス'
        const methodName = logEntry.protoPayload?.methodName || '不明なメソッド'
        const timestamp = logEntry.timestamp || context.timestamp

        // エラーメッセージの生成
        const errorMessage = `ビルドエラーが発生しました。\nエラーコード: ${errorCode}\nメソッド: ${methodName}`

        const discordWebhookUrl = process.env.DISCORD_WEBHOOK_URL as string
        if (!discordWebhookUrl) {
            throw new Error('Discord Webhook URL が設定されていません。')
        }

        const embed: DiscordEmbed = {
            title: '⚠️ Cloud Build 失敗',
            description: errorMessage,
            color: 0xdc3545,
            fields: [
                {
                    name: 'プロジェクト',
                    value: projectId,
                    inline: true,
                },
                {
                    name: 'ビルドID',
                    value: buildId,
                    inline: true,
                },
                {
                    name: 'トリガーID',
                    value: triggerId,
                    inline: true,
                },
                {
                    name: 'サービス',
                    value: serviceName,
                    inline: true,
                },
            ],
            timestamp: timestamp,
        }

        await fetch(discordWebhookUrl, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify({ embeds: [embed] }),
        })

        console.log('Discord 通知送信完了:', JSON.stringify(embed))
    } catch (error) {
        console.error('通知送信失敗:', error)
        throw error
    }
}

// JSONパーサーを追加
app.use(express.json())

// ヘルスチェック用エンドポイント
app.get('/', (_req, res) => {
    res.status(200).send('OK')
})

// Pub/Subメッセージを受け取るエンドポイント
app.post('/', async (req, res) => {
    try {
        await sendDeploymentNotification(req.body, {
            eventId: req.body.message?.id || '',
            timestamp: new Date().toISOString(),
        })
        res.status(200).send('OK')
    } catch (error) {
        console.error('Error processing message:', error)
        res.status(500).send('Internal Server Error')
    }
})

app.listen(port, () => {
    console.log(`Server is running on port ${port}`)
})

Dockerfile

FROM node:22.13.1-slim

WORKDIR /app

# pnpmのインストール
RUN npm install -g pnpm@10.2.0

# package.jsonをコピー
COPY package.json ./
COPY deploy-notify.ts ./
COPY tsconfig.json ./

# 依存関係のインストール
RUN pnpm install --no-frozen-lockfile

# ビルド
RUN pnpm run build

ENV NODE_ENV=production
ENV PORT=8080
ENV DISCORD_WEBHOOK_URL=""

EXPOSE 8080
CMD [ "pnpm", "start" ]

cloudbuild.yaml

steps:
  # Build the container image
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'build'
      - '-t'
      - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/deploy-notify:$COMMIT_SHA'
      - '-f'
      - 'Dockerfile'
      - '.'
    dir: 'functions'

  # Push the container image to Artifact Registry
  - name: 'gcr.io/cloud-builders/docker'
    args:
      - 'push'
      - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/deploy-notify:$COMMIT_SHA'

  # Deploy container image to Cloud Run
  - name: 'gcr.io/google.com/cloudsdktool/cloud-sdk'
    entrypoint: gcloud
    args:
      - 'run'
      - 'deploy'
      - 'deploy-notify'
      - '--image'
      - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/deploy-notify:$COMMIT_SHA'
      - '--region'
      - '${_REGION}'
      - '--platform'
      - 'managed'
      - '--allow-unauthenticated'

  # Clean up old images
  - id: delete-old-images
    name: asia-docker.pkg.dev/gcr-cleaner/gcr-cleaner/gcr-cleaner-cli:latest
    args:
      - --repo=${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/deploy-notify
      - --keep=1
      - --tag-filter-any=.*

options:
  logging: CLOUD_LOGGING_ONLY

images:
  - '${_REGION}-docker.pkg.dev/$PROJECT_ID/${_REPOSITORY}/deploy-notify:$COMMIT_SHA'

3. Cloud Runサービスの作成

Cloud Run > 関数を作成

  • Github(monorepoで外出しの関数もリポジトリ内で管理したいので)
    • リポジトリを選択
    • ブランチを選択
    • ビルドタイプ:Dockerfile
  • サービス名:notify-deploy-error
  • リージョン:asia-northeast1(東京)

cloudrun-service.png

ここでEventarc トリガーの設定もまとめてできます

  • 認証
  • 認証が必要

trigger.png

他の設定項目はデフォルトでサービスを作成します
作成したらAPIサーバーなどのコンテナ同様に環境変数を設定します(今回はDiscordのWebhookURL)

4. Eventarc トリガーの設定

Cloud Runサービスと Pub/Subトピックを接続するために、Eventarcトリガーを設定します。

  1. Cloud Run > deploy-notify サービスを選択
  2. トリガー タブを選択
  3. トリガーを作成をクリック
  4. 以下の設定を行う:
    • トリガー名: deploy-notify-trigger
    • イベントプロバイダー: Cloud Pub/Sub
    • イベント: google.cloud.pubsub.topic.v1.messagePublished
    • トピック: cloud-run-deploy-errors
    • サービスアカウント: デフォルトのCompute Engine サービスアカウント

5. Cloud Logging シンクの設定

  1. Logging > ログルーターに移動
  2. シンクを作成をクリック
  3. 以下の設定を行う:
    • シンク名: deploy-notify-sink
    • シンクの宛先:
      • シンクサービス: Cloud Pub/Sub
      • シンクの宛先: cloud-run-deploy-errors
    • シンクに含めるログの選択:
    resource.type="build"
    resource.labels.project_id="${project_id}"
    severity>=ERROR
    

シンクに含めるログの選択のときにログをプレビューからデプロイエラーのログを拾えていることを確認してください

6. 動作確認

Cloud Buildで意図的にエラーを発生させ、Discordに通知が送信されることを確認します

error.png

推奨設定

  • Cloud Build トリガー設定

    • デプロイエラー用のコンテナトリガーと、APIサーバーなどのトリガーが分かれているため、含まれるファイルフィルタと無視されるファイルフィルタを適切に設定する
    • 例えば、Cloud Run関数専用のディレクトリを指定することで、同じリポジトリの他のコード変更によって不要なビルドがトリガーされるのを防ぐ

まとめ

本記事では、Cloud Buildのデプロイ失敗を検知し、Discordに通知する仕組みを構築しました。
Cloud Run関数にリブランディングされてから初めて触れましたが、Gitとの連携や設定の手軽さが大きく向上していると感じました。
今後、機会があれば、より複雑なアーキテクチャの構築にも挑戦してみたいと思います。

2
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?