tech.guitarrapc.cóm

Technical updates

pulumi でstateが事故ったときに過去のバージョンのstateを当てなおす

terraformもそうですが、Infrastructure as Codeとかやってるとstateの壊れる日が来て軽く絶望します。

Pulumiで、誤った操作からstateのリソースが200あまり消えたときにどのように復旧したのかをメモしておきます。

TL;DR

  • アンドキュメントなREST API api.pulumi.comを使うことで指定したバージョンのstateがダウンロードできる
  • pulumi state importは神
  • pulumi refreshはpreviewと違って認証さえ通れば実行されてしまうので注意
  • なお、terraformでは同じことは起こらない.... Pulumi独特の仕様に起因する

状況

  • pulumiをCircleCIで実行中に、AWSアカウント情報の入れ替え中にビルドが実行される
  • pulumi refreshが実行されたため、StateがなにもリソースがないAWSアカウントと同期されて過半数が消える
  • pulumi previewで大量の差分が出てup実行前に停止

この状況は、本来管理しているAWSアカウント (Aとします) と同期していたPulumiコードが、そのコードで管理していないAWSアカウント (Bとします) とrefreshで状態を同期しようとして、Bには何も管理しているリソースがないためにstateファイルからリソース情報が消えました。

refreshであるため、実際のAWSアカウントにはA/Bともに影響が出ないのですが、stateファイルがコードと大幅に変わってしまっています。

復旧目標

pulumi refresh前のState状態に戻す。

前提情報

pulumiのstateについて使うべきではない言葉なので修正してくださいと何がどういうことかわからないまま取り組んでしまうのでおさらいです。

State とは

Pulumiのstateは、コードなどで表現されたあるべき状態がJSONとしてシリアライズされた状態です。 terraformでいうところのtfstateに相当します。

stateファイルはPulumiの各種言語ホスト (Language Host) から生成され、クラウドやk8sプロバイダーに対してその状態になっているかチェック、適用します。

Pulumi state

www.pulumi.com

ポイントとなるのは、「コードとstate」及び「stateとプロバイダー」がどのように一致をみているかです。 図の通り、プロバイダーはawsやk8sなどの適用対象となります。

コードとstateのマッチング

urnによって一致をみています。 その言語上でどのような書き方をしようと、コードからキーとなるurnをstateへ拾いに行ってマッチングします。

  • コードのurnがstateにあれば、コードとstateで求める状態に変更がないかチェック (up-to-date / change)
  • コードのurnがstateになければ、新規で追加 (create)
  • コードのurnが消えて、stateにだけあれば削除 (delete)

state とプロバイダーのマッチング

stateに含まれる、そのリソースがプロバイダーで一意に特定されるidを見て合致を見ます。 ただし、stateとプロバイダーの同期をrefreshを使って明示的に行った時だけです。

stateには必ず、対象のプロバイダーでリソースを一意に絞れるキーが含まれれます。 AWSであれば、多くの場合はarnであったりidやnameがそれに相当します。

さて、Pulumiがterraformと違ってやりにくいのが、Pulumiはコードとstateを見るというフローであることです。 terraformは、stateとプロバイダーが自動的に同期をしていましたが、Pulumiでは明示的に同期 (refresh) 操作しない限りstateとプロバイダーの一致は取りません。

この辺りは以前書いた通りで、同じような動作が欲しければpulumi refreshpulumi preview --refrepshpulumi up --refreshを行うことになります。

tech.guitarrapc.com

State が変化するタイミング

「stateとコード」「stateとプロバイダー」それぞれについてどのようにマッチングしているのかは確認できました。 では、どのような操作をするとStateが変化するのでしょうか?

terraform なら tfstate が変化するのは apply や destroy、refresh のタイミングです。

Pulumiは、stateとコード、stateとプロバイダーのそれぞれのタイミングで変化します。

  • stateとコード: pulumi uppulumi destroyでstateに変化が適用される
  • stateとプロバイダー: pulumi refreshpulumi xxxx --refreshでstateに変化が適用される

State を誤って更新しかねないタイミング

CI/CDをしていると、refreshやupは動作する環境で実行されるはずなので通常は事故が起こりにくいといえます。 多くの場合は、クラウド上のリソース同期を維持すためrefreshを挟んで操作するのではないでしょうか?

  • pulumi refresh
  • pulumi preview
  • approve
  • pulumi refresh
  • pulumi up

このフローで事故が起こるとすると、例えばプロバイダーの認証が切り替わって別のプロバイダーときにCIが走ってrefreshが走ると、stateが吹き飛びます。 previewの時点で大量の差分出るので気づくでしょうが、refreshしているのでstateは後の祭りです。

例えばAWS アカウント A から アカウントB に認証が変わってしまったなど、発生する状況はいくつもあるでしょう。

あまりやることはないでしょうが、ローカルで実行しているときとはこういったアカウントの事故はよりカジュアルに起こりかねません。

過去バージョンの State を何とかして得る

refresh前のstateにすればいいので、Pulumi Webでバージョンがとられているログから戻すべきバージョンはわかっています。

ドキュメントを見ると「現在のバージョンのStateに関する記述」はあるものの、「過去バージョンのStateに関して記述」がありません。 ではどうやってとるのか調べましょう。

NO: Pulumi Stack の State からは過去バージョンが export できない

pulumiは、pulumi stack exportをするとstateが丸ごとエキスポートできます。 ということは、以前書いた通りstack経由でstateのExport > Importを行えばstateは戻るはずです。

tech.guitarrapc.com

やった! セーフ! と思いきや、こういった事故の時にpulumi stack exportが使えないことにすぐに気づきます。 過去バージョンをExportできないのです。

pulumi stack exportなんて使えない子。

NO: Pulumi Web の アクティビティの過去実行履歴からState はダウンロードできない

Pulumi Webにはアクティビティ画面があり、過去の実行ログが見えます。 Terraform Cloudの感覚だと、こういったところからstateがダウンロードできそうですが、残念ながらPulumiの実行ログ画面からはstateをダウンロードできません。

CHANGESのログ右上のメニューはログの表示のみ

TIMELINEはログのみ

CONFIGURATIONは実行時のコンフィグを表示するだけ

まさかの過去バージョンの実行ログがあっても Stateが取れない

NOT YET: pulumi stack に 過去バージョンの stack export機能が追加されるかも

こういうリクエストは当然すぐ出てくるもので、現在Issueにあります。

pulumi stack exportで過去バージョンを指定できるようにできないかというものです。 最新バージョンのStackだけExportできても、いざというときには何の役にも立たないですからね。

github.com

CLIでオプションでもいいけど、Pulumi Web からダウンロードしたくないですか? Terraform Cloud のように。

OK: REST API を使って過去バージョンのstate をダウンロードする

Stack ExchangeやIssueでも質問がなく、困ってしまったのでStack Exchangeに投げたところ中の人から回答がありました。

stackoverflow.com

PulumiにはアンドキュメントながらREST APIがあります。 これを使うことで過去バージョンのStackがexportできます。

現在ドキュメントにしようIssue が立っています。

github.com

以下のようなフォーマットでエキスポートできます。

curl -H "Authorization: token $PULUMI_TOKEN" https://api.pulumi.com/api/stacks/<org>/<project>/<stack>/export/<version>

PULUMI_TOKENはいいとして、URLのパスはhttps://app.pulumi.com/でACTIVITYから該当処理の履歴を見ているときのURLからわかります。

例えば、Pulumi Webでその実行履歴が[https://app.pulumi.com/hoge/aws-sandbox/master/updates/352]というURLなら、APIは[https://api.pulumi.com/api/stacks/hoge/aws-sandbox/master/export/352]となります。

Export したstate から復旧する

過去バージョンのstateがREST APIからダウンロードできたら、話は簡単です。

  • pulumi stack import < downloadしたstate.jsonでインポート
  • pulumi refreshでstateとプロバイダーの同期
  • pulumi previewでコードとstateに差分が出ないことを確認

お疲れさまでした。

まとめ

同じようなstate事故は、terraformだとあっという間に解決だけど、Pulumi厳しい....です。 過去バージョンのstateの取り扱いは楽になる動きはあるので期待。

そもそもpulumiのstateとプロバイダーの同期が一段介していて、terraformに比べても事故りやすい..... ので、いい加減refreshの仕組み、もうちょっとなんとかならないですか (terraformだと発生しないだけどなぁ)

おまけ

Stateファイルを解析するのに使っていたStateファイルの構造です。

gist.github.com