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プロバイダーに対してその状態になっているかチェック、適用します。
ポイントとなるのは、「コードと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 refresh
やpulumi preview --refrepsh
、pulumi up --refresh
を行うことになります。
State が変化するタイミング
「stateとコード」「stateとプロバイダー」それぞれについてどのようにマッチングしているのかは確認できました。 では、どのような操作をするとStateが変化するのでしょうか?
terraform なら tfstate が変化するのは apply や destroy、refresh のタイミングです。
Pulumiは、stateとコード、stateとプロバイダーのそれぞれのタイミングで変化します。
- stateとコード:
pulumi up
、pulumi destroy
でstateに変化が適用される - stateとプロバイダー:
pulumi refresh
、pulumi 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は戻るはずです。
やった! セーフ!
と思いきや、こういった事故の時にpulumi stack export
が使えないことにすぐに気づきます。
過去バージョンをExportできないのです。
pulumi stack export
なんて使えない子。
NO: Pulumi Web の アクティビティの過去実行履歴からState はダウンロードできない
Pulumi Webにはアクティビティ画面があり、過去の実行ログが見えます。 Terraform Cloudの感覚だと、こういったところからstateがダウンロードできそうですが、残念ながらPulumiの実行ログ画面からはstateをダウンロードできません。
まさかの過去バージョンの実行ログがあっても Stateが取れない
NOT YET: pulumi stack に 過去バージョンの stack export機能が追加されるかも
こういうリクエストは当然すぐ出てくるもので、現在Issueにあります。
pulumi stack export
で過去バージョンを指定できるようにできないかというものです。
最新バージョンのStackだけExportできても、いざというときには何の役にも立たないですからね。
CLIでオプションでもいいけど、Pulumi Web からダウンロードしたくないですか? Terraform Cloud のように。
OK: REST API を使って過去バージョンのstate をダウンロードする
Stack ExchangeやIssueでも質問がなく、困ってしまったのでStack Exchangeに投げたところ中の人から回答がありました。
PulumiにはアンドキュメントながらREST APIがあります。 これを使うことで過去バージョンのStackがexportできます。
現在ドキュメントにしようIssue が立っています。
以下のようなフォーマットでエキスポートできます。
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ファイルの構造です。