はじめに
Terraform の state(構成の記録)を、チームで共有できるよう S3 に置きたいと思いました。さらに、同時に apply して壊れないよう ロックもかけたい。
意気込んで書き始めたところで、素朴な疑問にぶつかります。
「その置き場(S3 など)を Terraform で作るなら、その state はどこに置くの?」😅
置き場を作るための state の置き場が無い。いわゆる鶏と卵でした。
環境
- Terraform(AWS プロバイダ 5 系)
- リモート state: Amazon S3(+ ロック)
- リージョン: ap-northeast-1
起きたこと
最初は何も考えず、本体のコードにいきなり「S3 を backend に使う」と書いて terraform init しようとしました。
ところが、その S3 バケット自体がまだ存在しません。「じゃあ先に S3 を作る apply をすればいい」と思ったのですが、その apply の state を、これから作る S3 に置こうとしてしまうので、ぐるぐる回って始められませんでした。
原因
リモート state は「置き場が先に存在している」ことが前提です。
置き場そのものを、その置き場を使う構成で作ろうとすると、初回が成り立ちません。ここが鶏と卵でした。
対処
置き場を作るだけの小さなスタック(bootstrap)を別ディレクトリに作り、そこだけローカル state で動かすことで抜け出せました。
terraform/
├── bootstrap/ # state置き場を作る(ローカルstate・最初の1回だけ)
│ └── main.tf # S3バケット(+ロック用テーブル)
└── (本体のコード) # backend で、その置き場を使う
まず bootstrap で置き場を作成します(ここはローカル state のまま)。
cd bootstrap
terraform init
terraform apply
state には機密が平文で入りうるので、置き場の S3 はバージョニング・暗号化・パブリックアクセス遮断もこのとき有効にしておきました。
そのうえで、本体側に backend を書いてリモート state へ移行します。
terraform {
backend "s3" {
bucket = "myapp-tfstate-xxxx"
key = "app/terraform.tfstate"
region = "ap-northeast-1"
dynamodb_table = "myapp-tflock"
encrypt = true
}
}
※ 以前は S3 backend のロックに DynamoDB を使う構成が一般的でしたが、Terraform のバージョンによっては S3 ネイティブのロック(
use_lockfile) が使えます。DynamoDB ベースのロックは非推奨になりつつあるため、新しく作る場合は公式ドキュメントで現在の推奨設定を確認してください。
cd ..
terraform init -migrate-state # ローカルstate → S3 へコピー(確認に yes)
S3 に state が出来ていれば成功です。
aws s3 ls s3://myapp-tfstate-xxxx/app/
# terraform.tfstate が見えればOK
※ bootstrap で作った置き場は state の保管先なので、壊さず残します(ほぼ無料)。本体を
destroyしても、置き場はそのままで大丈夫です。
学び
- リモート state には「置き場が先に要る」。だから置き場づくりだけは、別スタック+ローカル state で先にやる(bootstrap パターン)。
- state には機密が入りうるので、置き場の S3 は暗号化・バージョニング・パブリック遮断をセットで。
- ロックがあると、2人が同時に
applyして state が壊れる事故を防げる(ロックの方式はバージョンで変わるので、今の推奨を確認する)。 - 「最初の1回だけ手で用意するもの」と「以降コードで回すもの」を分けて考えると、鶏と卵がほどけました。
おわりに
「置き場を作るための置き場が無い」ような循環に出会ったら、最初の一歩だけ別に切り出すと前に進めることがあります😅 リモート state を初めて設定する人の参考になれば嬉しいです🙌