トレタ開発者ブログ

飲食店向け予約/顧客台帳サービス「トレタ」など、飲食業界のVertical SaaS企業です。

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

この記事はトレタ Advent Calendar 2022の22日目の記事です。

はじめに

こんにちは、3年連続で22日に記事を書くサーバーサイドエンジニアの @shiroemons です。

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

tech.toreta.in

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

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

今回のゴール

  • 成功の場合の通知画面

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

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

Slackアプリ

アプリを新規作成

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

api.slack.com

アプリ名とアイコンは各自で設定してください。

ここでは、アプリ名を「Cloud Build 結果通知」としています。

アイコンは、アーキテクチャ図用のアイコンのライブラリ - Google Cloud PlatformのSVGとPNGのアイコンのものを設定しています。

Webhook URLを生成

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

Google Cloud 側の作業

cloud.google.com

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

必要な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/<バージョン>
  • カスタマイズの内容は以下の通りです。
    • 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"
}

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通知したいと悩んでいる方の助けになれば幸いです。

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

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

corp.toreta.in

参考

過去の記事

tech.toreta.in

トレタ予約番を支える技術

こんにちは、トレタ予約番事業で開発リーダーをしている北川です。

トレタ2022年アドベントカレンダー19日目の記事として、トレタ予約番サービスについて今まであまり公表していなかった裏側の仕組みについて紹介したいと思います。

トレタ予約番とは

まずトレタ予約番というサービスの紹介です。

トレタ予約番は、飲食店の電話に自動応答ロボットが24時間応答し、予約の受付/変更/キャンセルを対応してくれるサービスです。

飲食店にとっては今まで営業時間外にかかってきて取りこぼしていた予約電話を受けられるようになったり、多忙な営業時間の電話応答はトレタ予約番に任せつつ、スタッフ対応が必要な電話だけトレタ予約番からスタッフに繋ぐことで、業務負荷の軽減に役立てていただいています。

自動応答の電話というと宅配便の受取時間変更の電話のようなIVRをイメージされるかと思いますが、大きな違いとしてはユーザーはプッシュボタンでの操作ではなく、音声で操作を行う点です。

それを実現するために、音声認識や音声合成などの技術要素を色々と利用しているので、その仕組みについて紹介していきます。

toreta.in

具体的なトレタ予約番に電話したときの様子はこちらのサービスサイトに動画があるので、是非見て聞いてみてください。

会話の仕組み

電話サービス

まず、電話のサービスにはAWSのAmazon Connectを利用しています。

Amazon Connectはコールセンター向けの電話サービスです。

電話を受電した際にどのコールスタッフに繋ぐかなど応答時のフローを柔軟に設定することができ、AWS Lambdaを使うことでプログラマブルな制御を入れることができるのも特徴です。

Amazon ConnectにはCCPというコントロールパネルのSDKや、CCPと連携するためのAmazon Connect Streamsというライブラリが提供されています。これらを使い、電話の応答・切断・転送などをプログラムで制御します。

aws.amazon.com

音声認識

通話中の音声データから、音声認識を行いテキストデータに変換します。

音声認識にはGCPのSpeech-to-Textを利用しています。Speech-to-TextのAPIに音声データを入力してほぼリアルタイムに音声認識結果のテキストデータを取得することができます。

もちろん日本語に対応していますし、GCP以外の音声認識エンジンをいくつか精度検証を行いましたが現時点ではGCPが頭ひとつ抜けて精度がよい印象です。

後述していますが、専用の学習モデルに切り替えられたりチューニングなどで音声認識の向上を行うことも可能です。

cloud.google.com

自然言語処理

音声認識で取得したテキストデータは文字起こしされた文章なので、そこから意味を解釈して必要なデータを抽出するのが自然言語処理です。

例えば、予約の人数について「2名です」という発話であれば「2」という数値データを抽出します。

人によって「2名」や「2人」など語彙の揺れがあるので、自然言語処理によってそれらの揺れを吸収します。

自然言語処理についてはトレタ予約番を共同開発しているユニロボット株式会社のunirobot cloudを使用しています。

www.unirobot.com

会話シナリオ

予約電話の会話は基本的にシナリオがあり、大まかに以下のステップを踏みながら会話を進めます。

  1. 日時の質問
  2. 人数の質問
  3. 連絡先の確認
  4. 予約内容の確認

日時の質問であればユーザーが日時を答えることが期待値であり、自然言語処理を行なった結果が日時として認識できれば次のステップに進みます。日時以外の内容であれば再度日時を言い直すように応答します。

また、ユーザーの発話内容をサーバー側の処理を通すことでシステムの応答内容を分岐させる場合もあります。

例えばユーザーが「23時」と答えた場合、日時としては正しいですが店舗の営業時間外であれば正しく日時として、正しくないことを伝え再度日時を質問します。

最後に予約確認まで進めば、トレタの予約台帳のAPIに対して予約登録を行い、登録完了を伝えて通話終了となります。

音声合成

シナリオに沿ってシステムの回答を音声合成サービスを通して音声データとして作成します。大体は決まった文言ではありますが、ユーザーの発話内容を復唱したりするため毎会話ごとに音声合成を行なっています。

現在は株式会社エーアイのAITalkを利用しています。日本語がとても自然なのと、イントネーションや感情値など細かいチューニングが行えるので、明るく活発な印象にすることができています。

www.ai-j.jp

サービス向上のとりくみ

基本的な動きとしては前述の通りですが、より自然に、よりユーザーにストレスがないように細かいチューニングを日々行なっています。

サービスの質に直結する音声認識精度の向上に向けた取り組みについていくつか紹介します。

学習モデルのチューニング

現在使用しているGCPのSpeechToTextには認識精度を向上させるためのチューニング方法がいくつか提供されています。

例えば、音声文字変換モデルで電話の通話に最適化された phone_call モデルがあります。通話音声は音の帯域が狭く音質が悪いためこのモデルを利用するのは有効的です。

cloud.google.com

その他にも PhraseSet を設定することで任意の単語の精度を上げることができます。例えば「はい」「いいえ」の回答を要求する会話の部分において、「いいえ」を「家(いえ)」と誤認識してしまうことがありましたが、「はい」「いいえ」をフレーズ設定することで正しく認識する確率が上がりました。

意味を汲み取る

正しく音声認識をしても文脈で意味が異なるケースがあります。

例えば、予約の日時を聞いた時の回答が「7時」であった場合、予約の文脈であれば「午前7時」ではなく「午後7時(19時)」が正しいと考えるのが一般的です。

厳密にはその店舗の営業時間を参照し、7時なのか19時なのかを判別し正しいと思われる方で音声認識の結果を補正します。

他にも、例えば予約の日時を「7月(しちがつ)」と認識した場合、その時の日付が1月であれば「1月(いちがつ)」を「7月(しちがつ)」と誤認識してしまった可能性が高いです。

「2月(にがつ)」「4月(しがつ)」などの様に、日時としては正しいが聞き間違い(誤認識)をしてしまうことは多々あります。 その場合はその時の日付や時間帯なども考慮して、音声認識した結果を改めるようにしています。

もっと難しい判定としては「大丈夫」などのどちらにも捉えられるワードがあります。

「この内容でよろしいですか?」「大丈夫です」の場合、おそらくYesと思われます。

「店舗にお繋ぎしますか?」「大丈夫です」の場合、おそらくNoと思われます。

このように曖昧な表現は各会話ごとにどう解釈させるかを定義として入れて対応しています。

さいごに

トレタ予約番は正式ローンチして1年以上経つサービスですが、AIの業界は日進月歩の世界なのでその時その時でよりよい音声認識エンジンであったり合成音声エンジンを切り替えたり、多くの試行錯誤やチューニングを行なっています。

いろいろと知見も溜まり新しい機能やシステムのリプレイスを考えたり、やりたいことはまだまだあるので興味を持たれたエンジニアの方は是非話を聞きにいらしてみてください。

corp.toreta.in

Cloud Buildからpsqldefを使用してCloud SQL for PostgreSQLにマイグレーションする

この記事はトレタ Advent Calendar 2022の15日目の記事です。

はじめに

こんにちは、サーバーサイドエンジニアの @shiroemons です。

前回の記事に書いた通り、認定資格を取得してからGoogle Cloudを頻繁に活用するようになりました。

tech.toreta.in

現在のプロジェクトでは、Cloud Buildを用いてdocker buildやCloud Runへのデプロイを行っています。

また、データベース(Cloud SQL)へのマイグレーションもCloud Buildから行っています。

今回は、Cloud Buildからpsqldefというマイグレーションツールを使用して、Cloud SQL for PostgreSQLにマイグレーションする方法を紹介します。

ただし、Cloud SQL for PostgreSQLなどの設定は完了している前提のため、省略しています。

psqldefとは

SQLで羃等にDBスキーマ管理ができるツール「sqldef」のPostgreSQL用のツールです。

github.com

PostgreSQL用の他に、MySQL用のmysqldefやSQLite3用のsqlite3defなども存在します。

sqldefについての細かい説明は、ここでは省略します。

Cloud Buildとは

Cloud Buildは、Google Cloud 上でビルドを実行するサービスです。

Cloud Buildでは、ビルド構成ファイルと呼ばれる設定ファイルにビルドやデプロイの方法(指示)を記述します。記述した指示に基づいてタスクを実行し、ビルドやデプロイを行います。

Cloud SQLへのマイグレーションで使用した構成ファイルの紹介と説明をします。

Cloud Buildの構成ファイル

Cloud SQLへのマイグレーションで使用したCloud Buildの構成ファイルはこちらになります。

  • cloudbuild-migration.yaml
steps:
  - name: gcr.io/cloud-builders/wget
    args:
      - 'https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64'
      - '-O'
      - ./cloud_sql_proxy
    id: cloud_sql_proxy download
  - name: gcr.io/cloud-builders/gcloud
    args:
      - '-c'
      - |
        chmod +x ./cloud_sql_proxy && ./cloud_sql_proxy --version
    id: cloud_sql_proxy version
    entrypoint: bash
  - name: gcr.io/cloud-builders/wget
    args:
      - >-
        https://github.com/k0kubun/sqldef/releases/latest/download/psqldef_linux_amd64.tar.gz
    id: psqldef download
  - name: gcr.io/cloud-builders/gcloud
    args:
      - '-c'
      - |
        tar -zxvf psqldef_linux_amd64.tar.gz && ./psqldef --version
    id: psqldef version
    entrypoint: bash
  - name: gcr.io/cloud-builders/gcloud
    args:
      - '-c'
      - >
        ./cloud_sql_proxy -instances=$_INSTANCE_CONNECTION_NAME=tcp:$_DATABASE_PORT & sleep $_SLEEP_SEC;
        ./psqldef --dry-run --file $_SCHEMA_FILE --host $_DATABASE_HOST --port $_DATABASE_PORT --user $_DATABASE_USER --password $$DATABASE_PASS $_DATABASE_NAME
    id: psqldef dry-run
    entrypoint: bash
    secretEnv:
      - DATABASE_PASS
  - name: gcr.io/cloud-builders/gcloud
    args:
      - '-c'
      - >
        ./cloud_sql_proxy -instances=$_INSTANCE_CONNECTION_NAME=tcp:$_DATABASE_PORT & sleep $_SLEEP_SEC;
        ./psqldef --file $_SCHEMA_FILE --host $_DATABASE_HOST --port $_DATABASE_PORT --user $_DATABASE_USER --password $$DATABASE_PASS $_DATABASE_NAME
    id: psqldef execute
    entrypoint: bash
    secretEnv:
      - DATABASE_PASS
substitutions:
  _SCHEMA_FILE: ./schema.sql
  _DATABASE_HOST: 127.0.0.1
  _DATABASE_PORT: '5432'
  _DATABASE_NAME: db_name
  _DATABASE_USER: db_user
  _DATABASE_PASSWORD_KEY: database_password
  _INSTANCE_REGION: asia-northeast1
  _INSTANCE_ID: database
  _INSTANCE_CONNECTION_NAME: '${PROJECT_ID}:${_INSTANCE_REGION}:${_INSTANCE_ID}'
  _SLEEP_SEC: '5'
availableSecrets:
  secretManager:
    - versionName: >-
        projects/$PROJECT_ID/secrets/${_DATABASE_PASSWORD_KEY}/versions/latest
      env: DATABASE_PASS

(代入変数部分を変更して動作することを確認しています。)

各ビルドステップの説明

  • よく使用するフィールドについて簡単に説明します。
    • name: クラウドビルダーの指定(Docker..etc)
    • args: ビルダーに渡す引数のリスト
    • id: ビルドステップに対して一意の識別子
    • entrypoint: エントリポイントを指定 (bash etc)
    • secretEnv: Cloud KMS暗号鍵を使用して暗号化された環境変数のリスト
  • 詳しい説明は、 ビルド構成ファイルの構造 を参照ください。

ステップ1: Cloud SQL Proxyのダウンロード

  - name: gcr.io/cloud-builders/wget
    args:
      - 'https://dl.google.com/cloudsql/cloud_sql_proxy.linux.amd64'
      - '-O'
      - ./cloud_sql_proxy
    id: cloud_sql_proxy download
  • Cloud SQLに接続する際、Cloud SQL Proxyを使用して接続します。
    • Cloud SQL Proxy を使用するには、Cloud SQL Admin API を有効にする必要があります。
    • こちらから有効にできます。
  • Cloud SQL Proxyをwgetでダウンロードします。

ステップ2: Cloud SQL Proxyの実行権限付与とCloud SQL Proxyのバージョン確認

  - name: gcr.io/cloud-builders/gcloud
    args:
      - '-c'
      - |
        chmod +x ./cloud_sql_proxy && ./cloud_sql_proxy --version
    id: cloud_sql_proxy version
    entrypoint: bash
  • ダウンロードしたCloud SQL Proxyに実行権限を付与します。
  • どのバージョンを使用したかわかるようにバージョンを確認します。
  • ファイルの存在確認の意図もあります。

ステップ3: psqldef の最新版をダウンロード

  - name: gcr.io/cloud-builders/wget
    args:
      - >-
        https://github.com/k0kubun/sqldef/releases/latest/download/psqldef_linux_amd64.tar.gz
    id: psqldef download
  • マイグレーションに必要なpsqldef(最新版)をwgetでダウンロードします。

ステップ4: psqldef の解凍とpsqldef のバージョン確認

  - name: gcr.io/cloud-builders/gcloud
    args:
      - '-c'
      - |
        tar -zxvf psqldef_linux_amd64.tar.gz && ./psqldef --version
    id: psqldef version
    entrypoint: bash
  • ダウンロードしたpsqldefは圧縮されているため解凍します。
  • どのバージョンを使用したかわかるようにバージョンを確認します。
  • ファイルの存在確認の意図もあります。

ステップ5: psqldef の dry-run 実行

  - name: gcr.io/cloud-builders/gcloud
    args:
      - '-c'
      - >
        ./cloud_sql_proxy -instances=$_INSTANCE_CONNECTION_NAME=tcp:$_DATABASE_PORT & sleep $_SLEEP_SEC;
        ./psqldef --dry-run --file $_SCHEMA_FILE --host $_DATABASE_HOST --port $_DATABASE_PORT --user $_DATABASE_USER --password $$DATABASE_PASS $_DATABASE_NAME
    id: psqldef dry-run
    entrypoint: bash
    secretEnv:
      - DATABASE_PASS
  • Cloud SQLに接続する場合、Cloud SQL Proxyを先に起動しておく必要があります。
  • Cloud SQL Proxyの起動に一定時間かかるため sleep を入れています。
  • 設定に必要な値は、すべて代入変数で定義しておきます。
    • 代入変数を用いることでビルド構成ファイル自体の変更をしなくてもよくなります。
  • psqldefを本実行する前に、dry-runで実行してエラーがないことを確認しておきます。
  • DBのパスワードは、Secret Managerに保存して使用しています。

ステップ6: psqldef の 実行

  - name: gcr.io/cloud-builders/gcloud
    args:
      - '-c'
      - >
        ./cloud_sql_proxy -instances=$_INSTANCE_CONNECTION_NAME=tcp:$_DATABASE_PORT & sleep $_SLEEP_SEC;
        ./psqldef --file $_SCHEMA_FILE --host $_DATABASE_HOST --port $_DATABASE_PORT --user $_DATABASE_USER --password $$DATABASE_PASS $_DATABASE_NAME
    id: psqldef execute
    entrypoint: bash
    secretEnv:
      - DATABASE_PASS

内容は、dry-runとほぼ同じです。psqldefのdry-runオプションを外した内容です。

代入変数(substitutions)の説明

substitutions:
  _SCHEMA_FILE: ./schema.sql
  _DATABASE_HOST: 127.0.0.1
  _DATABASE_PORT: '5432'
  _DATABASE_NAME: db_name
  _DATABASE_USER: db_user
  _DATABASE_PASSWORD_KEY: database_password
  _INSTANCE_REGION: asia-northeast1
  _INSTANCE_ID: database
  _INSTANCE_CONNECTION_NAME: '${PROJECT_ID}:${_INSTANCE_REGION}:${_INSTANCE_ID}'
  _SLEEP_SEC: '5'
代入変数名 説明
_SCHEMA_FILE psqldef でマイグレーションするスキーマファイル
_DATABASE_HOST DBのホスト
_DATABASE_PORT DBのポート
_DATABASE_NAME DB名
_DATABASE_USER DBのユーザー名
_DATABASE_PASSWORD_KEY DBのパスワードを Secret Manager に保存した際のシークレット名
_INSTANCE_REGION Cloud SQLのインスタンスのリージョン
_INSTANCE_ID Cloud SQLのインスタンスID
_INSTANCE_CONNECTION_NAME Cloud SQLの接続名(環境変数や代入変数で値を作成する)
_SLEEP_SEC Cloud SQL Proxyの起動を待つスリープ(秒数を指定する)

数値の指定の場合、クォートが必要です。そのため、DBのポートやスリープの秒数にシングルクォートを付けています。

availableSecretsの説明

availableSecrets:
  secretManager:
    - versionName: >-
        projects/$PROJECT_ID/secrets/${_DATABASE_PASSWORD_KEY}/versions/latest
      env: DATABASE_PASS
  • availableSecretsは、Cloud BuildでSecret Managerのシークレットを使用する時のフィールドです。

DBのパスワードを、Secret Managerに保存しており、そこから最新の設定内容を取得するようにしています。

Cloud Build 実行に必要なロール

  • 今回のCloud Build実行には以下の2つのロールが必要です。
    • Cloud SQL クライアント
    • Secret Manager のシークレット アクセサー

実行ログ

構成ファイルの代入変数の値を適宜変更し、実行すると以下のように成功します。

実行ログ

振り返り

Cloud Buildでsqldefを用いてマイグレーションする方法をネットで探しても出てこず苦労しました。

Cloud SQL ProxyとpsqldefをwaitForを使用して分けて実行させてみたりと試行錯誤の連続でした。

Cloud BuildでCloud SQL Proxyを用いてCloud SQLへの接続がどうしてもうまく行かず、Google Cloudのサポートも利用しました。

Cloud SQL Proxyの起動には、少し時間を要するためスリープを入れることで接続できない問題は解決しました。

さいごに

この記事が、Cloud Buildからpsqldefもしくはmysqldefを使用してマイグレーションを検討している方の助けになれば幸いです。

次回の トレタ Advent Calendar 2022 の22日目は、Cloud Buildの結果をSlackに通知する方法を紹介したいと思います。

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

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

corp.toreta.in

© Toreta, Inc.

Powered by Hatena Blog