この記事はエムスリー SRE がお届けするブログリレーの6日目です。
エムスリー エンジニアリンググループ新卒2年目の四方田 (@k-yomo) です。 現在はBIR(ビジネスインテリジェンス&リサーチ)というチームでソフトウェアエンジニア兼チームSREとして、マイクロサービスの開発や、クラウド環境の整備などを行なっています。
最近はTerraform Providerを作り始めたり等しています。
エムスリーではInfrastructure as Codeが普及しており、所属しているチームのエンジニアも今では全員Terraformを書いてクラウドインフラリソースの変更を行っています。 今回は、Terraform Google ProviderのCloud Schedulerが毎回replaceされてしまう問題を解決した話をご紹介します。
Cloud Schedulerがreplaceされてしまう問題
Cloud Schedulerは一言で言うとGCPが提供するcronのクラウド版です。cron形式(* * * * *
)で実行間隔を指定して、定期的にHTTPリクエストを送ったりPub/SubメッセージをPublishしたりする事ができます。
このCloud Schedulerは以下のような記述でTerraformでも定義ができます。
resource "google_cloud_scheduler_job" "test" { name = "test-http-job" description = "test http job" schedule = "*/8 * * * *" time_zone = "America/New_York" attempt_deadline = "320s" retry_config { retry_count = 1 } http_target { http_method = "POST" uri = "https://example.com/ping" body = base64encode("{\"foo\":\"bar\"}") } }
しかし上記の設定で作成したリソースを、例えばretry_count
を1から2に変更しようとすると、apply時にgoogle_cloud_scheduler_job.test must be replaced
の文言と共に再生成 (削除 & 作成) が行われてしまいます。
もちろんGCSのバケット名など、更新不可能で値を変更するには再生成が必要なものもありますが、Cloud SchedulerはWebコンソール上ではJob名以外の全ての値を再生成なしに更新することが可能なため、Terraformでも設定値の更新ができて然るべきです。
Cloud Schedulerは再生成されてしまっても比較的問題ないリソースかなと思いますが、それでも不要な再生成が行われる事は実行間隔のリセット等が起こるため一部のユースケースに影響を与えますし、terraform plan / apply時に出力されるmust be replacedの赤字は心臓にも悪いです。
Terrafromの構成
実際の修正に移る前に、Terraformはどのような構成で、どのように動いているのかを見ていきましょう。
https://learn.hashicorp.com/tutorials/terraform/provider-use?in=terraform/providers
Terraformの構成は上記の図のように、大まかにTerraformコア
、Terraformプラグイン
、対象API
の3つに分けられます。
Terraformコア
: Terraformの設定ファイル (.tf, .tfvars等) を読み取り、リソースの依存グラフを構築、各リソースの更新をプロバイダーにリクエストする。Terraformプラグイン
: Terraformコアからのリクエストに応じて、APIコールを行う。対象API
: 実際にリソースのCRUDを行う (各サービスが提供)
Terraformを利用していて何か問題や不具合、不足している機能があった時に、上記構成のどの部分に該当するのか、をまず考えます。Terraformの共通の事象であればTerraformコア、いちリソースに関する事象であればProviderかAPIに原因がありそう、と当たりをつける等です。
例えば、Terraformで定義できないリソースが存在する場合は、API自体が存在しないかそのAPIのTerraform Providerが該当リソースをサポートしていない、のどちらかに当たりをつける事ができます。
Cloud Schedulerの更新をサポートする
今回のCloud Schedulerのrecreateに関する問題は、Cloud Schedulerのリソースのみに起きている事象なため、上記のTerraformの構成の内、ProviderもしくはAPIのどちらかに原因がありそうです。
調べたところ、下記のCloud SchedulerのAPIはPATCH
リクエストでの設定値の更新をサポートしており、gcloudコマンドではgcloud scheduler jobs update
が使えている事から今回の問題の原因がGoogle Providerにあると当たりをつけました。
cloud.google.com
Google Providerに原因があると当たりがついたところで、Google Providerのリポジトリにて関連するIssueがないか確認したところ、既に該当の不具合を報告している下記のIssueが見つかりました。
そこで、Cloud Schedulerのリソース定義 を見てみると、下記の記述になっており、Create
, Read
, Delete
のみで確かにUpdate
が存在しません。
func resourceCloudSchedulerJob() *schema.Resource { return &schema.Resource{ Create: resourceCloudSchedulerJobCreate, Read: resourceCloudSchedulerJobRead, Delete: resourceCloudSchedulerJobDelete, Importer: &schema.ResourceImporter{ State: resourceCloudSchedulerJobImport, }, Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(4 * time.Minute), Delete: schema.DefaultTimeout(4 * time.Minute), }, CustomizeDiff: validateAuthHeaders, Schema: map[string]*schema.Schema{ ~~~~ } } }
上記コードにUpdate
を追加して、Cloud Schedulerの更新をサポートすれば良いのですが、TerraformのGoogle Providerのコードは自動生成されており、実際の設定の記述は、設定を一元管理しTerraformやAnsibleといった各DevOpsツールに対応するコードを生成するmagic-modulesというライブラリで管理されています。
magic-modulesでは、YAMLで設定を一元管理し、RubyのスクリプトでYAMLをパースして各DevOpsツールに対応するコードを生成するような構成をとっています。
そのため最終的には、magic-modulesにYAMLで記述されているCloud Schedulerの設定を修正するPull Requestを投げるに至りました。
上記のPRでは、Cloud Schedulerの更新をサポートし、各設定値の変更でreplaceが走らないような修正を行っています。
まとめ
今回はTerraformでCloud Schedulerが不要にreplaceされてしまう問題を発端にTerraformの構成や、Google Providerについても合わせて見ていきました。
今回取り上げたCloud Schedulerの修正については、PRも無事Mergeされたため、次のリリースからCloud SchedulerのUpdateがサポートされることになります!
また、上記のようなケースに限らず、本記事がTerraformに関する不具合修正や改善を行う際の一助になりましたら幸いです。
We're hiring
エムスリーではTerraform等のIaCツールを使ってクラウドインフラを効率よく安全に管理し、システムの信頼性を向上させていく仲間を大募集しています!
エムスリー 全体のシステムの信頼性に責任を持つコアSRE、各チーム内のシステムを担当するチームSRE、またSREやインフラにも興味があるエンジニア、どのポジションも大募集中なので、興味がある方、是非お声掛け頂けると幸いです!