先日、うっかりTerraformのステート情報を壊してしまい、以前のステートへのリカバリが必要になりました。そのときはS3とDynamoDBを直接操作して戻したのですが、後から改めて調べてみると、もっと簡単な方法があったことがわかりました。検索してみてもズバリな記事は意外と見つからなかったので、書き残しておくことにします。
実のところ公式ドキュメントにもリカバリについての記述はあるのですが、特定のバックエンドについて書かれたものではないため、この記事ではS3バックエンドでのリカバリ作業が具体的にイメージできることを目指します。
動作確認はTerraform 1.2.6で実施しました。
リカバリ作業の概要
作業は以下のステップで実施します。
- ステートのS3バケットとキーを特定する
- 戻し先となるバージョンをダウンロードする
- terraform state pushで書き戻す
以降、各ステップについて記載します。
ステートのS3バケットとキーを特定する
S3バックエンドの設定は以下のようになっていると思います。
backend "s3" { bucket = "tfstate12345678901234567890" key = "example-tfstate-recovery/terraform.tfstate" dynamodb_table = "tfstate-lock" region = "ap-northeast-1" # ... }
基本的には、上記のbucketとkeyがそのままステートファイルのあるS3バケット,キーになります。ただし、workspaceがdefault以外である場合は env:/workspace名/ というプレフィックスが付きます*1。
例えばworkspaceがstagingのとき env:/staging/example-tfstate-recovery/terraform.tfstateが実際のキーとなります。
戻し先となるバージョンをダウンロードする
バケットとキーを特定できたので、バージョン一覧から戻したい時点のものを選択してダウンロードします( S3バックエンドのドキュメントで推奨されている通り、S3バケットでバージョニングが有効化されていて、かつ戻し先となるバージョンが残っていることを前提としています*2 )。どのバージョンか確定できない時は、とりあえず可能性の高そうなものを選択しておきます。あとで誤りが判明した場合でも、またここに戻って別のバージョンを試していくことができます。
なお、ステートには機密情報が含まれる可能性がありますので、このダウンロードしてきたファイルは慎重な取り扱いが必要です。無闇に共有したりせず、このあと使い終わったら速やかに削除します。
terraform state pushで書き戻す
さて、ダウンロードしたものをそのままアップロードして復旧、、としてしまいそうになりますが、それをすると面倒なことになる*3のでやりません。代わりに、terraform state pushコマンドに以降の作業を任せます。terraform state pushはローカルファイルをバックエンド(ここではS3)に送り込むためのコマンドです。
これをまずは普通に実行してみます(以降、ダウンロードしたファイルはterraform.tfstateというファイル名で保存したものとして進めます)。すると、下記のようにserialが過去に戻っているというエラーで失敗するはずです。serialはステート内にある数値で、0から始まって更新のたびに増加していくものです。serialが小さくなるということはステートが過去のものに戻っていることを表し、普通のことではないのでストップがかかりますが、それがまさに今やろうとしていることなので、このエラーは問題ありません。
% terraform state push terraform.tfstate Failed to write state: cannot import state with serial 99 over newer state with serial 100
一方、下記のようにserialではなくlineageが一致しないというエラーが出るときは、このまま進んではいけません。lineageはステート内にあるUUIDで、ステートが作られたときにランダム生成され、以後は不変の値です。lineageが異なるということは、ローカルとリモート(S3)のステートがバージョン違いの関係にあるのではなく、無関係の別のステートであることを表します。おそらく誤った場所(バケット,キー)からステートをダウンロードしてきてしまっているので、確認してやり直します。
% terraform state push terraform.tfstate Failed to write state: cannot import state with lineage "5f14158d-8d9c-633b-2f0e-2b1e5621f9e7" over unrelated state with lineage "da13749d-0ee8-45fd-f6bb-191293fca452"
確認が問題なければ、今度は-forceを付けて実行します。-forceはserialとlinageのチェックをスキップするためのオプションです。本当はserialのチェックだけをスキップしたいのですがそういうオプションがないので、まず-forceなしで実行してlineageチェックに問題ないことを確認してから-forceで実行するという手順を踏んでいます。
% terraform state push -force terraform.tfstate
以上でステートの過去のバージョンへの戻しは完了となります。繰り返しになりますが、ステートには機密情報が含まれる可能性がありますのでダウンロードしたファイルを削除するのを忘れないようにします。