ちょっとしたSQLクエリの結果をMackerelのサービスメトリックに投稿したいということが仕事であった。
Google Cloudではちょっとした処理にはCloud Functionsを利用するか、Cloud Runを利用するのが定石なのだけれど、今回は本当に1つSQLを発射したら終わる内容を投稿したいだけなので、このためだけにコードを書いてコンテナを建てたくはない。管理するものが大幅に増えるからだ。
どうしたものかと思っていると、id:cohalz に良いやつを教えてもらった。それがmackerel-sql-metric-collector
というやつである。
mackerel-sql-metric-collector
これを利用すると、SQLを叩いてMackerelに投稿するまでの部分がGoのシングルバイナリに突っ込まれていて、あとは実行したいクエリと投稿したい値のテンプレートを渡すだけで、あとは勝手にやってくれる。
例えば、顧客別の注文数をサービスメトリックcount.${顧客名}
として投稿したいときはこんな感じのテンプレートを用意すればいい:
- keyPrefix: fooUsage service: BarService valueKey: count.#{customer}: count sql: |- SELECT customer, COUNT(1) FROM fooOrder GROUP BY customer;
あとはコマンドを実行すればよい:
% ./mackerel-sql-metric-collector \ --query-file ./post_usage_to_mackerel_params.yaml \ --dsn="postgres://***"
これは便利そう。
Cloud Build
mackerel-sql-metric-collector
を実行さえすればよいことが分かったけれど、次はGoogle Cloudのどこで実行するかが問題になった。たったこれだけのためにCloud Runを設定するのはもったいない気がしたのでもっと軽量なソリューションを探して検討した。
- Cloud Functions
- やりたいことは既にあるバイナリにファイルを渡すだけなので、若干やりたいことがズレてる
- Workflow
- 直接Bashを起動できない。既にあるHTTPエンドポイントを叩くくらいが限度なので使いづらい
- Cloud Build
- Bashを呼べる。ワークスペースとしてファイルシステムを使えるので便利そう
雑にBashを書いてリカバーしやすいほうが良かろうということで今回はCloud Buildを利用して呼び出すことにした。
ビルド構成ファイル
いったん先に全部書いてしまおう。ビルド構成ファイルとして、以下のようなyamlを書く:
steps: - id: getBinary name: bash script: | #!/usr/bin/env bash wget https://github.com/mackerelio-labs/mackerel-sql-metric-collector/releases/download/v0.3.0/mackerel-sql-metric-collector_linux_x86_64.tar.gz tar zxvf mackerel-sql-metric-collector_linux_x86_64.tar.gz ls mackerel-sql-metric-collector chmod 777 /workspace/.cloudsql volumes: - name: cloudsql path: /workspace/.cloudsql - id: proxy:start name: 'gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.6.1' args: - '--unix-socket' - '/workspace/.cloudsql' - '$PROJECT_ID:asia-northeast1:FOOBAR_DBNAME' allowExitCodes: [143] # docker stopされてSIGTERMが来ても問題なく止まるため volumes: - name: cloudsql path: /workspace/.cloudsql - id: postMetrics name: bash script: | #!/usr/bin/env bash sleep 10; ./mackerel-sql-metric-collector \ --query-file ./post_usage_to_mackerel_params.yaml \ --dsn="postgres://host=/workspace/.cloudsql/$PROJECT_ID:asia-northeast1:FOOBAR-CLOUDSQL-INSTANCE-NAME port=5432 user=FOOBAR_USERNAME password=${DBPASS} dbname=FOOBAR_DBNAME" secretEnv: ['DBPASS', 'MACKEREL_APIKEY'] env: - 'PROJECT_ID=$PROJECT_ID' volumes: - name: cloudsql path: /workspace/.cloudsql waitFor: ['getBinary'] - id: proxy:stop name: 'gcr.io/cloud-builders/docker' entrypoint: 'sh' args: - '-c' - 'docker ps -q --filter ancestor="gcr.io/cloud-sql-connectors/cloud-sql-proxy:2.6.1" | xargs docker stop' waitFor: ['postMetrics'] availableSecrets: secretManager: - versionName: projects/$PROJECT_ID/secrets/db_password/versions/latest env: 'DBPASS' - versionName: projects/$PROJECT_ID/secrets/mackerel_api_key/versions/latest env: 'MACKEREL_APIKEY'
つまづきポイント
つまづいたポイントがいくつかあったので共有しておく。
Cloud SQL Proxy
Cloud BuildからCloud SQLに接続するにはcloud-sql-proxy
を利用する必要がある。これ自体はDockerイメージになっているので、取得して実行するだけでいい。
ただ、どういうわけかCloud Build上ではTCPを利用した接続がうまくいかない(Proxyが起動しているはずなのにconnection refusedになる)ため、UNIXドメインソケットで代用している。ハマった。
ドメインソケットはワークスペースの下に.cloudsql
ディレクトリを用意して行っている。ディレクトリはvolumes
を利用して確保させ、ジョブ間で使いまわせるようにしている。また、デフォルトではパーミッションが足りなくてソケットを生やせないので、chmod 777
してなんとかしている。ここでかなりハマった。
並行実行
通常、Cloud Buildは上から下にジョブを実行していく。しかしながらCloud SQL Proxyは接続する間はずっと起動している必要があるので、永遠にジョブが終了しない。
Proxyを待たずに後続のジョブを実行させるには、waitFor
を利用する。これがあるとき、Cloud Buildは、そこに列挙されたジョブだけを待機するようになる。waitFor: ['-']
と書くと、何も待たずにすぐ起動してくれる。
これを利用してProxyとmackerel-sql-metric-collector
を同時に起動する。ただし本当に同時に起動するとソケットの準備が間に合わないことがあるので、適当にsleep
させて時間を稼いでいる。しっかりやるならファイル存在チェックでループさせるといいと思う。ドメインソケットはそのへん簡単だ。
env
bashで実行している箇所には環境変数を経由して値を渡せるのだが、Code Build上の置換がそのまま使えるわけではない。いったんenv
で'PROJECT_ID=$PROJECT_ID'
のように書いて受け取らないといけない。このへんはよくハマる。
変数がないときはコケてほしいので、一般的なプラクティスとしてset -u
しておくのが良いと思う。
終了コード
処理が完了したらProxyコンテナは終了させられるのだが、終了時にSIGTERMが送られて終了コード143(SIGTERMによる終了)が返る。これ自体は良いのだがランタイムが「オッ異常終了しとるな」と親切心から失敗扱いにしてしまうし、ワークフローも失敗する。
これでは困るので、終了コードを無視してもらう。
Cloud Buildで特定の終了コードを無視させるには、allowExitCodes: [143]
と書いたら良い。
コンテナもあるっぽい
最初からmackerel-sql-metric-collectorのDockerイメージが用意されていたらありがたいんだがなー、と思っていたところ、チームに聞いたら実はあるとのことだった。
あとでREADMEに追加していただけるとのことだった。ハオ。