grasys blog

terraform destroy で tfstate ファイルを GCS バケットごと削除してしまった話

はじめまして、エンジニアの HK です。

今回は、私が新人の頃に Terraform の勉強中にやってしまった失敗について紹介していこうと思います。

実際の作業中であれば、間違いなく問題となってしまう失敗なので、みなさんは私の失敗を糧に(?)同じ失敗をしないようにしていただければと思っています。

また、私が行った対処法も紹介しますので、同じ失敗をしてしまった方はぜひ参考にしてみてください。

失敗の背景

弊社ではインフラの構築に Terraform を使っています。

そして、Terraform のリソース状況を共有するために tfstate ファイルを GCS( Google Cloud Storage )のバケットに保存することがあります。

その練習として、実際に tfstate ファイルを GCS のバケットで管理することに挑戦していました。

tfstate ファイルを GCS 上で管理する方法については、以下の公式ドキュメントなどを参考にしてください。

Google Cloud 公式ドキュメント

当時の環境を再現する

まずは GCS のバケットを作り、当時の環境を再現します。

今回は下記のコードで tfstate ファイルを管理するための GCS バケットを作成します。

resource "google_storage_bucket" "sample-state" {
  name = "sample-state"
  location = "asia-northeast1"
  force_destroy = true
  storage_class = "STANDARD"
}

terraform {
  backend "gcs" {
    bucket = "sample-state"
    prefix = "statefile"
  }
}

上記のコードで apply すると以下のように GCS バケットに tfstate が保存されます。
※既にローカルに tfstate がある場合は事前に terraform init をする必要があります。

起こったトラブル

そして、ここからが今回の本題となります。

学習の一環で Terraform のリソースを削除したくなった私は、terraform destroy を実行しました。

しかし、terraform destroy は失敗し、下記のエラーが出てきました。

╷
│ Error: Failed to save state
│
│ Error saving state: Failed to upload state to gs://sample-state/statefile/default.tfstate: googleapi: Error 404: The specified bucket does not exist., notFound
╵
╷
│ Error: Failed to persist state to backend
│
│ The error shown above has prevented Terraform from writing the updated state to the configured backend. To allow for recovery, the state has been written to the file
│ "errored.tfstate" in the current working directory.
│
│ Running "terraform apply" again at this point will create a forked state, making it harder to recover.
│
│ To retry writing this state, use the following command:
│     terraform state push errored.tfstate
│
╵

下部にある terraform state push errored.tfstate コマンドを実行してみると以下の結果が返ってきます。

% terraform state push errored.tfstate
Failed to load destination state: 2 errors occurred:
	* writing "gs://sample-state/statefile/default.tflock" failed: googleapi: Error 404: The specified bucket does not exist., notFound
	* storage: object doesn't exist

つまりどういうことかというと、現在の作業ディレクトリ内に tfstate が格納されている GCS バケット自体も含まれていたため、バケットが消えたタイミングで参照するべき tfstate を見失い、terraform destroy が中断されてしまったというわけです。

このままでは消したかったリソースも消せない上に、tfstate が途中で消えたせいで現状どこまでファイルが削除できてどのファイルが削除できなかったがわからなくなってしまったため、とても困りました。

実施した対処法

このとき私が行った対応は、ローカルに生成される terraform.tfstate.backup ファイルを使い復旧を試みることでした。

terraform.tfstate.backup ファイルには terraform destroy が実行される直前までのリソース状況が書いてあるため、以下の gsutil コマンドで terraform.tfstate.backup ファイルを tfstate としてリモートにアップして状態を再現した上で、改めてリソースの洗い出しと削除を行いました。

#削除したバケットを手動で再作成(この際、バケット名は削除したものと同じである必要があります)
% gsutil mb gs://sample-state

#再作成したバケットにterraform.tfstate.backupをアップロード
% gsutil cp terraform.tfstate.backup gs://sample-state/statefile/default.tfstate

#terraform.tfstate.backup内のリソース状況と実際に残っているリソースを照合
% terraform plan

上記の手順で destroy で消せたリソースと消せなかったリソースを洗い出し、消せていなかったリソースをコンソールから消すことで、なんとかリソースの削除を完了できました。

おわりに

みなさんも Terraform のリソース管理を GCS バケット上で行うときは、必ずバックアップファイルをとっておきましょう。そして、terraform destroy を実行するときは細心の注意を払いましょう。

また、今回のようなケースで事前に取れる対策としては、tfstate を異なる作業ディレクトリにあるバケットで管理することや、terraform destroy を実行する前に、tfstate のバックエンドをローカルに移しておくことなどがあります。

それでは、また。


採用情報
お問い合わせ