Lambdaカクテル

京都在住Webエンジニアの日記です

Invite link for Scalaわいわいランド

mackerel-sql-metric-collectorをCloud SQLと組み合わせてCloud Buildから呼び出し、Mackerelにクエリ結果を送信する

ちょっとしたSQLクエリの結果をMackerelのサービスメトリックに投稿したいということが仕事であった。

Google Cloudではちょっとした処理にはCloud Functionsを利用するか、Cloud Runを利用するのが定石なのだけれど、今回は本当に1つSQLを発射したら終わる内容を投稿したいだけなので、このためだけにコードを書いてコンテナを建てたくはない。管理するものが大幅に増えるからだ。

どうしたものかと思っていると、id:cohalz に良いやつを教えてもらった。それがmackerel-sql-metric-collectorというやつである。

mackerel-sql-metric-collector

mackerel.io

github.com

これを利用すると、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イメージになっているので、取得して実行するだけでいい。

github.com

ただ、どういうわけか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イメージが用意されていたらありがたいんだがなー、と思っていたところ、チームに聞いたら実はあるとのことだった。

gallery.ecr.aws

あとでREADMEに追加していただけるとのことだった。ハオ。

参考文献

cloud.google.com

cloud.google.com

medium.com

www.memory-lovers.blog

★記事をRTしてもらえると喜びます
Webアプリケーション開発関連の記事を投稿しています.読者になってみませんか?