【Terraform】plan / apply と状態ファイル (tfstate) の関係

記事タイトルとURLをコピーする

こんにちは🐱
カスタマーサクセス部の山本です。

本記事ではTerraform のplan / apply と状態ファイル (tfstate) の関係について、をテーマに書いていきます。
基本的には Terraform のドキュメントを読めばわかるものの、若干分かりにくいなと感じたため、記事にしました。
AWS の ECS タスク定義を作成するサンプルコードも交えつつ、解説できればと思います。
まず簡単に図を示して、下で解説します。

対象読者

Terraform で AWS や他のクラウド環境を構築・運用している方。

【図解】 状態ファイル (tfstate) の確認・更新タイミング

terraform plan

1

⬇️

2

terraform apply

1

⬇️

2

terraform plan -refresh-only

terraform apply -refresh-only

ignore_changes属性のリソースしかない場合の terraform plan

1 ⬇️

2

ignore_changes属性のリソースしかない場合の terraform apply

1 ⬇️

2

解説

状態ファイル (tfstate)

Terraform で AWS 環境を構築する際の非常にざっくりした流れです。

  1. コード (tf) に作成するリソースの情報を記述
  2. init(初回のみ) / plan / apply を実行
  3. 実際の AWS 環境にリソースができる

環境作成後、コードを変更してもう一回 plan / apply すると、基本的には差分のみを AWS 環境に反映してくれます。

  1. コード (tf) に更新するリソースの情報を記述
  2. plan / apply を実行
  3. 実際の AWS 環境のリソースが更新される

二回目以降の plan / apply 時に差分のみを更新するために、Terraform は plan / apply を実行した際の実際のAWS 環境の状態を保持しています。 apply を 一回以上実行した環境には、状態ファイル (tfstate) が存在します。ファイル名:terraform.tfstate
状態ファイル (tfstate)では、コード (tf) に記載した 1 つのリソース (resource) と、実環境に作成したリソースを 1 対 1 でマッピングしています。

参考1:State | Terraform | HashiCorp Developer
参考2:State | Terraform | HashiCorp Developer

Terraform expects that each remote object is bound to only one resource instance in the configuration. If a remote object is bound to multiple resource instances, the mapping from configuration to the remote object in the state becomes ambiguous, and Terraform may behave unexpectedly. Terraform can guarantee a one-to-one mapping when it creates objects and records their identities in the state. When importing objects created outside of Terraform, you must make sure that each distinct object is imported to only one resource instance.

plan

plan (terraform plan) の役割を説明します。plan は基本的にはオプションなしで実行するように設計されています。

  1. 状態ファイル (tfstate)が 1 対 1 でマッピングしている AWS リソースの現在の状態を確認する。例えば、Terraform で作成した EC2 インスタンス (ID:i-xxxx)のインスタンスタイプを、利用者が Terraform 以外の方法で、変えたかどうかなどを確認する。
  2. 現在のコード (tf) と、1 で調べた AWS リソースの現在の状態を元に、apply 時に適用する内容を表示する。 表示のみ。

参考:Command: plan | Terraform | HashiCorp Developer

The terraform plan command creates an execution plan, which lets you preview the changes that Terraform plans to make to your infrastructure. By default, when Terraform creates a plan it:

Reads the current state of any already-existing remote objects to make sure that the Terraform state is up-to-date.
Compares the current configuration to the prior state and noting any differences.
Proposes a set of change actions that should, if applied, make the remote objects match the configuration.

apply

apply(terraform apply) の役割を説明します。apply も基本的にはオプションなしで実行するように設計されています。
apply は plan の結果を実行する役割です。

  1. plan で表示した内容で実際の AWS 環境を更新する。
  2. AWS 環境の最新情報を元に、状態ファイル (tfstate) に更新する

参考1:Command: apply | Terraform | HashiCorp Developer

The terraform apply command executes the actions proposed in a Terraform plan.

参考2 (2024/3/15 追加):Apply Terraform configuration | Terraform | HashiCorp Developer

  1. Lock your workspace's state, so that no other instances of Terraform will attempt to modify your state or apply changes to your resources. If Terraform detects an existing lock file (.terraform.tfstate.lock.info), it will report an error and exit.
  2. Create a plan, and wait for you to approve it. Alternatively, you can provide a saved plan created with the terraform plan command, in which case Terraform will not prompt for approval.
  3. Execute the steps defined in the plan using the providers you installed when you initialized your configuration. Terraform executes steps in parallel when possible, and sequentially when one resource depends on another.
  4. Update your workspace's state with a snapshot of the new state of your resources.
  5. Unlock your workspace's state.
  6. Report the changes it made, as well as any output values defined in your configuration.

plan / apply における -refresh-only オプション

plan と apply には -refresh-only オプションがあります。 状態ファイル (tfstate) の内容が、現在の AWS 環境と照らし合わせて正しいのか、確認するためのものです。AWS 上で手動でリソースの設定を変えたりしていないか?を確認して、状態ファイル (tfstate) に反映できます。

plan については、plan の項目で説明した 1 のみを実行します。また、その際に状態ファイル (tfstate) と 現在の AWS 環境の差分を表示します。
apply については、apply の項目で説明した 2 のみを実行します。

つまり、状態ファイル (tfstate) を現在の AWS 環境と比較し(plan)、現在の AWS 環境に合わせるように更新(apply)します。

  • terraform plan -refresh-only:状態ファイル (tfstate) と 現在の AWS 環境の差分を調べて表示する。
  • terraform apply -refresh-onlyterraform plan -refresh-only を行い、検出した差分を解消するために状態ファイル(tfstate)を更新する。

動作検証のために、状態ファイル (tfstate) 内の CPU の値を、実際の AWS 環境と異なる値 (1024 ➡️ 3072) にわざと書き換えて、terraform plan -refresh-onlyした画面です。 Note: Objects have changed outside of Terraform とメッセージが出て、状態ファイル (tfstate) と 現在の AWS 環境の差分が表示されます。

続けて、terraform apply -refresh-only を実行した画面です。

わざと書き換えた状態ファイル (tfstate)が実際の AWS 環境と同じ状態に戻りました。(3072 ➡️ 1024)

参考:Command: plan | Terraform | HashiCorp Developer

Refresh-only mode: creates a plan whose goal is only to update the Terraform state and any root module output values to match changes made to remote objects outside of Terraform. This can be useful if you've intentionally changed one or more remote objects outside of the usual workflow (e.g. while responding to an incident) and you now need to reconcile Terraform's records with those changes.

plan に-refresh-only オプション がない時

-refresh-only を指定しない plan (terraform plan)では、状態ファイル (tfstate) と 現在の AWS 環境の差分がある場合に、その差分については表示されません
現在のコード (tf) と、AWS リソースの現在の状態を元に、apply 時に適用する内容を表示するのみです。

terraform refresh

以前は terraform refresh コマンドがありました。
plan / apply における -refresh-only オプションと同等のコマンドです。
上で説明した通り、現在のバージョンの Terraform では plan / apply をオプションなしで実行すると 状態ファイル (tfstate) の確認・更新が実行されるため非推奨になっています。

参考:Command: refresh | Terraform | HashiCorp Developer

You shouldn't typically need to use this command, because Terraform automatically performs the same refreshing actions as a part of creating a plan in both the terraform plan and terraform apply commands. This command is here primarily for backward compatibility, but we don't recommend using it because it provides no opportunity to review the effects of the operation before updating the state.

その他、参考:Use refresh-only mode to sync Terraform state | Terraform | HashiCorp Developer

ignore_changes 属性

Terraform では、特定のリソースに対して、 ignore_changes 属性を付与できます。
最初に Terrraform で作ったリソースを、他の方法、例えば CI/CD パイプラインや AWS マネジメントコンソールでの作業で更新するためにある属性です。
コード (tf) と現在の AWS 環境に差分があっても、plan / apply をする際に、特定のリソースに関しては、コード (tf) の内容を AWS 環境に実際に反映しないようになります。

参考:The lifecycle Meta-Argument - Configuration Language | Terraform | HashiCorp Developer

The ignore_changes feature is intended to be used when a resource is created with references to data that may change in the future, but should not affect said resource after its creation. In some rare cases, settings of a remote object are modified by processes outside of Terraform, which Terraform would then attempt to "fix" on the next run. In order to make Terraform share management responsibilities of a single object with a separate process, the ignore_changes meta-argument specifies resource attributes that Terraform should ignore when planning updates to the associated remote object.

引用の太字で示した箇所が重要で、ignore_changes 属性を付けたリソースに関して、plan / apply 時の実行内容は以下になります。

terraform plan

  1. 状態ファイル (tfstate)が 1 対 1 でマッピングしている AWS リソースの現在の状態を確認する。例えば、Terraform で作成した EC2 インスタンス (ID:i-xxxx)のインスタンスタイプを、利用者が Terraform 以外の方法で、変えたかどうかなどを確認する。

terraform apply

  1. AWS 環境の最新情報を元に、状態ファイル (tfstate) に更新する

状態ファイル (tfstate)のみを更新する、ということですね。
plan については、plan の項目で説明した 1 のみを実行します。
apply については、apply の項目で説明した 2 のみを実行します。
ほとんど -refresh-onlyオプションと同様の動きです。(ただし、状態ファイル (tfstate) と 現在の AWS 環境の差分は表示しません。)

検証に利用したコード

task-definition.tf

cpu と memory 、ignore_changes を更新しながら検証しました。

resource "aws_ecs_task_definition" "latest-trial" {
  family                   = "task1"
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"
  cpu                      = 2048
  memory                   = 4096

  container_definitions = jsonencode([
    {
      name       = "task"
      image      = "public.ecr.aws/nginx/nginx:1.25"
      essenntial = "true"
      portMappings = [{
        name          = "nginx-80-tcp"
        containerPort = 80
        protocol      = "tcp"
        appProtocol   = "http"
      }]
    }
  ])
  track_latest = true
  # lifecycle { ignore_changes = [container_definitions]}
}

provider.tf

provider "aws" {
    profile = "default"
    region = "ap-northeast-1"
}

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 5.37.0"
    }
  }
}

終わりに

plan / apply と状態ファイル (tfstate) の関係を解説してみました。
私自身、曖昧な部分があったので、公式ドキュメントを読み込んで良かったです。

では。

山本 哲也 (記事一覧)

カスタマーサクセス部のエンジニア(一応)

好きなサービス:ECS、ALB

趣味:トレラン、登山(たまに)