【gcloud】300秒以上かかるCloudSQLのインポート/エクスポートを終わるまで待つ方法
結論
エクスポート
gcloud sql export sql ${srcInstanceName} gs://${bucketName}/${fileName} \
--project=${projectID} \
--database=${srcDatabaseName} \
--async \
--quiet \
--offload
operationID=$(gcloud sql operations list \
--project=${projectID} \
--instance=${srcInstanceName} \
--filter="TYPE:EXPORT AND NOT STATUS:DONE" \
--format="value(NAME)" \
--limit=1)
gcloud sql operations wait ${operationID} --project=${projectID} --timeout=unlimited
インポート
gcloud sql import sql ${destInstanceName} gs://${bucketName}/${fileName} \
--project=${projectID} \
--database=${destDatabaseName}
--user=${destDatabaseUserName} \
--async \
--quiet \
operationID=$(gcloud sql operations list \
--project=${projectID} \
--instance=${destInstanceName} \
--filter="TYPE:IMPORT AND NOT STATUS:DONE" \
--format="value(NAME)" \
--limit=1)
gcloud sql operations wait ${operationID} --project=${projectID} --timeout=unlimited
エクスポート/インポートは一部のフラグが異なるだけです。
ポイントは、以下の手順をちゃんと踏むことです。
-
gcloud sql export|export
を--async
で非同期実行する - 実行した(まだ完了していない)エクスポート/インポートのオペレーションIDを特定する
-
gcloud sql operations wait ${operationID}
で完了するまで待つ
--async
をつけない同期実行だとタイムアウトで終了してしまいますが、この方法なら完了するまで待つことができます。
背景
自分がいま所属しているチームは毎週リリースをしていて、リリースの前日に「本番環境と同じ構成・スペックの環境(= ミラー環境)でのE2Eテスト」を実施することで検証を行なっています。
このミラー環境を用意するとき、本番環境のCloudSQLからデータをエクスポート → ミラー環境にインポート…というステップをGitHub Actionsを用いて実行していたのですが、先日このステップが失敗するようになりました。
ミラー環境を用意するGithub Actionsの実行履歴
ログを見ると、同じコマンドを実行しているのに失敗するようになっているのが確認できました。
2024/6/5のexport sql
コマンド:
op=$(gcloud sql export sql production-db-replica gs://production-mirror-data/sql-data/$(TZ=JST-9 date '+%Y-%m-%d')-my_database.gz \
--project my-project-production \
--database=my_database \
--quiet \
--async \
--offload)
gcloud sql operations wait --project my-project-production --timeout unlimited "${op}"
shell: /usr/bin/bash -e {0}
env:
# -- 環境変数 --
Serverless exports cost extra. See the pricing page for more information: https://cloud.google.com/sql/pricing.
Waiting for [https://sqladmin.googleapis.com/sql/v1beta4/projects/my-project/operations/e2c09062-ec1a-4268-b6cc-08850000002b]...
.....................................................done.
NAME TYPE START END ERROR STATUS
e2c09062-ec1a-4268-b6cc-08850000002b EXPORT 2024-06-05T05:05:12.719+00:00 2024-06-05T05:22:04.936+00:00 - DONE
2024/6/12のexport sql
コマンド:
op=$(gcloud sql export sql production-db-replica gs://production-mirror-data/sql-data/$(TZ=JST-9 date '+%Y-%m-%d')-my_database.gz \
--project my-project-production \
--database=my_database \
--quiet \
--async \
--offload)
gcloud sql operations wait --project my-project-production --timeout unlimited "${op}"
shell: /usr/bin/bash -e {0}
env:
# -- 環境変数 --
Serverless exports cost extra. See the pricing page for more information: https://cloud.google.com/sql/pricing.
ERROR: (gcloud.sql.operations.wait) Invalid value: exportContext:
databases:
- my_database
fileType: SQL
kind: sql#exportContext
offload: true
sqlExportOptions:
mysqlExportOptions:
masterData: 0
schemaOnly: false
uri: gs://production-mirror-data/sql-data/2024-06-12-my_database.gz
insertTime: '2024-06-12T05:04:51.298Z'
kind: sql#operation
name: 289944ef-3741-42da-8115-82180000002b
operationType: EXPORT
selfLink: https://sqladmin.googleapis.com/sql/v1beta4/projects/my-project-production/operations/289944ef-3741-42da-8115-82180000002b
startTime: '2024-06-12T05:04:53.348Z'
status: RUNNING
targetId: production-db-replica
targetLink: https://sqladmin.googleapis.com/sql/v1beta4/projects/my-project-production/instances/production-db-replica
targetProject: my-project-production
user: production-mirror-setupper@my-project-production-mirror.iam.gserviceaccount.com
Error: Process completed with exit code 1.
Invalid value: exportContext:
以下を見ればわかるのですが、 gcloud sql export sql
を実行した時の出力に exportContext
が含まれるようになっています。
6/5から6/12の間に仕様変更が入ったのかな?と思ってリリースノートを見てみたのですが、479.0.0 (2024-06-04) から 480.0.0 (2024-06-11) のCloud SQLの内容には関連しそうな記述を見つけられませんでした。
原因探しは一旦やめて、解決するためにどうするか考えることにしました。
status: RUNNING
のログを見るに gcloud sql export sql --async
自体は成功しているので、gcloud sql operations wait
に本当は何を渡さないといけないのかを調べました。
gcloud sql operations wait | Google Cloud CLI Documentation
OPERATION
[OPERATION
…]An identifier that uniquely identifies the operation.
オペレーションっていうのは多分エクスポートやインポートのジョブのことかな?と思って実行してみるとこんな感じの結果に。
gcloud sql operations list --project=my-project-production --instance=production-db-replica --filter="TYPE:EXPORT"
NAME TYPE START END ERROR STATUS
d7c2d9fb-b094-4d11-88bb-8bf60000002b EXPORT 2024-06-19T07:53:15.366+00:00 2024-06-19T08:10:09.761+00:00 - DONE
a7de7f48-ac7b-467b-a633-70560000002b EXPORT 2024-06-19T05:05:01.952+00:00 2024-06-19T05:20:25.432+00:00 - DONE
289944ef-3741-42da-8115-82180000002b EXPORT 2024-06-12T05:04:53.348+00:00 2024-06-12T05:21:35.085+00:00 - DONE
...
NAME
って書いてるけど、UUIDっぽいしこれだろ!と思い、
TYPE:EXPORT
かつ現在実行中のオペレーションのNAME
だけを取得するようにlistのコマンドを書き直して、
gcloud sql operations list --project=my-project-production --instance=production-db-replica --filter="TYPE:EXPORT AND NOT STATUS:DONE" --format='value(NAME)'
従来のスクリプトと組み合わせるとうまくいきました。
gcloud sql export sql production-db-replica gs://production-mirror-data/sql-data/$(TZ=JST-9 date '+%Y-%m-%d')-my_database.gz \
--project my-project-production \
--database=my_database \
--quiet \
--async \
--offload
operationID=$(gcloud sql operations list --project=my-project-production --instance=production-db-replica --filter="TYPE:EXPORT AND NOT STATUS:DONE" --format='value(NAME)')
gcloud sql operations wait --project my-project-production --timeout unlimited "${operationID}"
Serverless exports cost extra. See the pricing page for more information: https://cloud.google.com/sql/pricing.
exportContext:
databases:
- my_database
fileType: SQL
kind: sql#exportContext
offload: true
sqlExportOptions:
mysqlExportOptions:
masterData: 0
schemaOnly: false
uri: gs://production-mirror-data/sql-data/2024-06-12-my_database.gz
insertTime: '2024-06-19T08:42:45.241Z'
kind: sql#operation
name: 1e5fc16e-7b70-49e2-8ec7-bfe30000002b
operationType: EXPORT
selfLink: https://sqladmin.googleapis.com/sql/v1beta4/projects/my-project-production/operations/1e5fc16e-7b70-49e2-8ec7-bfe30000002b
startTime: '2024-06-19T08:42:47.050Z'
status: RUNNING
targetId: production-db-replica
targetLink: https://sqladmin.googleapis.com/sql/v1beta4/projects/my-project-production/instances/production-db-replica
targetProject: my-project-production
user: production-mirror-setupper@my-project-production-mirror.iam.gserviceaccount.com
Waiting for [https://sqladmin.googleapis.com/sql/v1beta4/projects/my-project-production/operations/1e5fc16e-7b70-49e2-8ec7-bfe30000002b]...done.
NAME TYPE START END ERROR STATUS
1e5fc16e-7b70-49e2-8ec7-bfe30000002b EXPORT 2024-06-19T08:42:47.050+00:00 2024-06-19T08:47:15.807+00:00 - DONE
たらたら書いてきましたが、結局エクスポートのスクリプトのbefore/afterはこんな感じです。
before:
op=$(gcloud sql export sql production-db-replica gs://production-mirror-data/sql-data/$(TZ=JST-9 date '+%Y-%m-%d')-my_database.gz \
--project my-project-production \
--database=my_database \
--quiet \
--async \
--offload)
gcloud sql operations wait --project my-project-production --timeout unlimited "${op}"
after:
gcloud sql export sql production-db-replica gs://production-mirror-data/sql-data/$(TZ=JST-9 date '+%Y-%m-%d')-my_database.gz \
--project my-project-production \
--database=my_database \
--quiet \
--async \
--offload
operationID=$(gcloud sql operations list --project=my-project-production --instance=production-db-replica --filter="TYPE:EXPORT AND NOT STATUS:DONE" --format='value(NAME)')
gcloud sql operations wait --project my-project-production --timeout unlimited "${operationID}"
別解
この記事を書いているときに改めてCLIのリリースノートを確認してみると、2024.5.29リリースのCLI v478.0.0 にて出力が変わっていたことに気づきました。時系列的に関係ないだろと思って読んでいなかった…。
こんな感じの修正でよかったっぽいです。
op=$(gcloud sql export sql ${srcInstanceName} gs://${bucketName}/${fileName} \
--project=${projectID} \
--database=${srcDatabaseName} \
--quiet \
--async \
--offload \
--format="value(selfLink)" # または`--format="value(name)"`
)
gcloud sql operations wait ${op} --project=${projectID} --timeout=unlimited
まぁでも、「エクスポート/インポート開始 → 実行中のオペレーションを取得 → 終わるまで待機」と明示的に書いている方が保守しやすいのではと思います。
おわりに
この内容は所属している会社の社内LT会でも話したのですが、その時に「gcloudは自動化には適さないので(仕様が固まっている)SDKやAPIを使った方がいい」というコメントをいただきました。
確かにな…と思いつつ移行には着手していませんが、
CI/CDパイプラインを組む際には仕様変更があっても壊れにくい作りにしたり、そもそも仕様変更が入りにくいツールを選定して、デリバリーを守るのが大切だなという学びがありました。
参考
Discussion