Terraform 基礎 #7 リソースのライフサイクル制御 — 置き換えの条件、create_before_destroy、prevent_destroy

読了 5分

#2 で plan の -/+ 記号に出会いました。消してから作り直すという意味で、あのときは「必ず立ち止まって確認すること」とだけ言いました。今回はその置き換えを正面から扱います。どの変更が置き換えを引き起こすのか、置き換えが避けられないときに中断を減らす方法、そしてそもそも置き換えと削除が起きないように防ぐ方法です。道具はすべて lifecycle というネストしたブロック 1 つに集まっています。本番のデータベースを Terraform で管理するようになった瞬間から、今回の内容が安全装置のすべてになるので、基礎シリーズの中でいちばん実戦的な回と言えます。

置き換えを決めるのは provider です #

同じリソースでも、属性によって変更の重みが違います。EC2 インスタンスで instance_type を変えると update(正確には停止後の変更)として処理されますが、AMI を変えると置き換えです。稼働中のサーバーの OS イメージを差し替えることはできないからです。どの属性が置き換えを強制するかは provider がリソースのスキーマに定義してあり、暗記する必要はありません。plan が教えてくれるからです。

plan 出力
  # aws_instance.web must be replaced
-/+ resource "aws_instance" "web" {
      ~ ami = "ami-0abc..." -> "ami-0def..." # forces replacement
    }

# forces replacement という注釈が付いた属性が置き換えの原因です。plan の出力からこの注釈を探す習慣が、今回の出発点になります。置き換えの対象がステートレスな Web サーバーなら受け入れれば済みますが、データを抱えたリソースなら、次の道具が必要になります。

create_before_destroy — 順序を逆転させて空白をなくします #

デフォルトの置き換え順序は消してから作るです。古いものが消えてから新しいものが起動するまでの間、そのリソースは存在しないので、サービス中のリソースならその時間ぶんの中断が発生します。lifecycle ブロックで順序を逆転できます。

main.tf
resource "aws_instance" "web" {
  ami           = data.aws_ami.al2023.id
  instance_type = "t3.micro"

  lifecycle {
    create_before_destroy = true
  }
}

これで、置き換えが必要なときは新しいものを先に作って確認してから古いものを消します。ただしタダではありません。一時的にせよ 2 つが共存するので、名前が一意でなければならないリソース(S3 バケット、IAM ロールなど)は名前の衝突で作成に失敗します。名前にランダムなサフィックスを付ける name_prefix 引数を一緒に使うのがセットのパターンです。起動と停止ではなく置き換えでしか更新できないリソース(launch template ベースの構成など)で、特によく使うことになります。

prevent_destroy — 消えてはいけないものに掛けるロック #

本番のデータベースやログのバケットのように、削除そのものが事故になるリソースにはロックを掛けておきます。

main.tf
resource "aws_db_instance" "main" {
  # ...

  lifecycle {
    prevent_destroy = true
  }
}

このリソースを消す計画(destroy はもちろん、置き換えの削除段階も含みます)が作られた瞬間、Terraform がエラーを出して止まります。リファクタリング中にうっかりリソースブロックの名前を変えて置き換えが計画されるケース(#5 の state mv を忘れたケース)のような事故を、最後の段階で防いでくれるセーフティネットです。1 つだけ限界を知っておく必要があります。この保護は Terraform の中でだけ有効です。コンソールから手で消すことは防げませんし、ブロック自体をコードから削除すれば保護も一緒に消えます。それでも、データを持つリソースにこの 2 行を入れるコストはほぼゼロで、防いでくれる事故は大きいので、習慣にする価値は十分にあります。

ignore_changes — コードの外の変更と共存する #

#5 でドリフトは元に戻すべきズレだと言いましたが、例外があります。他のシステムがその属性を変えることが正常な動作である場合です。オートスケーリングがインスタンス数を調整する Auto Scaling グループの desired_capacity が典型です。Terraform のコードには初期値の 2 が書いてあるのに、スケーリングが 4 に増やしていたら、次の apply がこれを 2 に戻して、問題なく動いているサーバーを減らしてしまいます。

main.tf
resource "aws_autoscaling_group" "web" {
  desired_capacity = 2
  # ...

  lifecycle {
    ignore_changes = [desired_capacity]
  }
}

ignore_changes に列挙した属性は、実際の値がコードと違っても plan が差分として扱いません。最初の作成時だけコードの値が使われ、以降の変更は外部のシステムに任せるわけです。ただし、これはその属性に対して Terraform の目を閉じることなので、範囲は最小限に絞るべきで、ignore_changes = all のような全面無視は、そのリソースの管理を事実上放棄するのと同じです。

置き換えを強制する — -replace フラグ #

逆に、コードはそのままなのに置き換えが必要なときがあります。インスタンスが原因不明の不調に陥って、新しく作り直したい場合です。以前は terraform taint というコマンドでリソースに「汚染」マークを付けましたが、この方式は廃止予定になり、今は plan と apply のフラグに置き換えられました。

実行
terraform apply -replace="aws_instance.web"

指定したリソースだけを置き換える計画が作られ、plan の出力に置き換えの理由が明示されるので、taint 方式より意図が明確です。サーバーを直す代わりに丸ごと入れ替える運用スタイルと相性の良い道具です。

まとめ #

今回扱った内容です。

  • どの属性が置き換えを強制するかは provider のスキーマが決め、plan の # forces replacement という注釈で確認します
  • create_before_destroy は置き換えの順序を逆転させて空白をなくします。名前が一意でなければならないリソースは name_prefix とセットで使います
  • prevent_destroy は削除の計画そのものをエラーで止めるロックです。データを持つリソースには習慣として掛けておきます
  • ignore_changes は、外部のシステムが変えることが正常な属性を plan から除外します。範囲は最小限に絞ります
  • 強制的な置き換えは -replace フラグで行います。taint コマンドは廃止予定です

次回(#8 モジュールの基礎)では、大きくなっていくコードを整理する道具のモジュールを扱います。繰り返されるリソースのまとまりに名前を付けて再利用する方法と、他の人が作った実績のあるモジュールを持ってきて使う方法です。

X