とことんDevOps | 日本仮想化技術が提供するDevOps技術情報メディア

DevOpsに関連する技術情報を幅広く提供していきます。

日本仮想化技術がお届けするとことんDevOpsでは、DevOpsに関する技術情報や、日々のDevOps業務の中での検証結果など、DevOpsのお役立ち情報をお届けします。
主なテーマ: DevOps、CI/CD、アジャイル開発、コンテナ開発など

開催予定の勉強会

各種SNSのフォローもよろしくお願いいたします。

Terraform + S3バックエンドでステートを過去のバージョンに戻す

先日、うっかりTerraformのステート情報を壊してしまい、以前のステートへのリカバリが必要になりました。そのときはS3とDynamoDBを直接操作して戻したのですが、後から改めて調べてみると、もっと簡単な方法があったことがわかりました。検索してみてもズバリな記事は意外と見つからなかったので、書き残しておくことにします。

実のところ公式ドキュメントにもリカバリについての記述はあるのですが、特定のバックエンドについて書かれたものではないため、この記事ではS3バックエンドでのリカバリ作業が具体的にイメージできることを目指します。

動作確認はTerraform 1.2.6で実施しました。

リカバリ作業の概要

作業は以下のステップで実施します。

  1. ステートのS3バケットとキーを特定する
  2. 戻し先となるバージョンをダウンロードする
  3. 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

以上でステートの過去のバージョンへの戻しは完了となります。繰り返しになりますが、ステートには機密情報が含まれる可能性がありますのでダウンロードしたファイルを削除するのを忘れないようにします。

*1:env:はworkspace_key_prefixのデフォルト値

*2:戻し先のステートファイルを持っていれば、バージョニングの有効/無効に関係なくterraform state pushで戻すことは可能です

*3:ACL,暗号化の考慮やDynamoDBとの整合性をとる作業が必要