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 プロバイダーに対してその状態になっているかチェック、適用を行います。

f:id:guitarrapc_tech:20200115181646p:plain
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 の Activityの過去実行履歴からState はダウンロードできない

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

f:id:guitarrapc_tech:20200115184310p:plain
CHANGESのログ右上のメニューはログの表示のみ

f:id:guitarrapc_tech:20200115184333p:plain
TIMELINEはログのみ

f:id:guitarrapc_tech:20200115184424p:plain
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