- 物理機リージョン/AZ
- ハイパーバイザVM exit で捕まえる
- VMゲストOS今ここ
- コンテナnamespace / cgroup今ここ
- プロセスアプリ / DB
「宣言的だから何度流しても同じ」という言い方が、どこまで成り立つのかを見ます。
まず結論
同じ宣言を2回流しても2回目は何もしない。これが成り立つのは、道具が記録と比べられる範囲だけです。
Terraform の provisioner が作ったファイルを消しても、次の apply は差が無いと答えます。管理対象のリソースとしては、そのファイルは宣言にも記録にも無いからです。
terraform_data に local-exec を付け、apply でファイルを1つ書かせました。そのファイルを手で消して、もう一度 apply するとどうなるでしょうか。
当てることが目的ではありません。外した予測ほど記憶に残るので、直感で選んでください。
apply が揃えると約束しているもの
Terraform の文書は、apply がすることを5つ挙げ、最後をこう書きます。
Updates the state file so that the configuration, real infrastructure, and state match.
揃えるのは 構成・実物・state の3つ です。ただし下の実演が示すとおり、ここでの「実物」は provider が読んで state に書ける属性です。そこに現れないものは、比べる相手になりません。
その極端な例が terraform_data です。
The terraform_data resource type implements the standard resource lifecycle, but does not directly take any other actions.
ライフサイクルだけを持ち、それ以外は何もしない型です。文書はこれを provisioner の入れ物として紹介しています(serves as a container for arbitrary operations taken by the provisioner "local-exec" block)。入れ物の中のコマンドが外に何を残しても、記録の中で値を持つのは入れ物の id だけです。
- ・2回目が何もしないのは、上の2段で差が無いとき
- ・3段目で何が起きても、2回目の答えは変わらない
ファイルを消しても、差は無いと答える
実演の構成はこれだけです。
resource "terraform_data" "setup" {provisioner "local-exec" { command = "echo created >> ${path.module}/side-effect.log" }}
1回目で side-effect.log ができます。2回目は No changes でした。ここまでは期待どおりです。そこでファイルを消して、3回目を流しました。
$ terraform apply -auto-approve # 1stPlan: 1 to add, 0 to change, 0 to destroy.terraform_data.setup (local-exec): Executing: ["/bin/sh" "-c" "echo created >> ./side-effect.log"]terraform_data.setup: Creation complete after 0s [id=001a3f90-1674-e629-558f-274fe7a732b7]Apply complete! Resources: 1 added, 0 changed, 0 destroyed.$ rm side-effect.log; terraform apply -auto-approve # 3rd, after rmterraform_data.setup: Refreshing state... [id=001a3f90-1674-e629-558f-274fe7a732b7]No changes. Your infrastructure matches the configuration.Terraform has compared your real infrastructure against your configurationand found no differences, so no changes are needed.Apply complete! Resources: 0 added, 0 changed, 0 destroyed.$ ls side-effect.logls: l3-tf/side-effect.log: No such file or directory
compared your real infrastructure と言いながら、ファイルは無いままです。嘘ではありません。比べた「実物」は terraform_data の記録で、そこにファイルは載っていないからです。
戻すには、記録された実体を作り直すしかありません。-replace=terraform_data.setup を付けると 1 added, 0 changed, 1 destroyed となり、local-exec が再び走ってファイルが戻りました。id は ec43ce7d-... に変わっています。2回流して2回とも同じ結果でした(id の値は回ごとに異なる)。
ファイルを消した後の apply が `found no differences` と答えたのはなぜですか。
何と比べるかで、2回目の答えが決まる
Kubernetes でも同じ形が見えます。Deployment は「この中身の Pod(コンテナをまとめた実行単位)をいくつ動かすか」を書いたオブジェクトで、API サーバに記録されます。
2回目に何が起きるかは、比べる相手で決まります。
- 名前と比べる: API 規約は
All compatible Kubernetes APIs MUST support "name idempotency"と書き、同名の POST には 409 を返せと求めます。create の2回目はここで止まります。 - 今の実物と前回の宣言と比べる: apply はファイルを今の実物(live)と、前回の宣言(注釈
last-applied-configuration)の両方と比べます。値を設定するかは live と、消すかは注釈と比べて決めます(calculates this patch request using the configuration file, the live configuration, and the last-applied-configuration annotation)。create はこの注釈を残しません。
名前という歯止めにも外があります。metadata.generateName で名前をサーバに作らせると、the name returned to the client will be different than the name passed。名前が毎回変わるので、409 の歯止めは掛かりません。
第12期で扱う「冪等」は、HTTP メソッドの意図された効果の話です。ここで見ているのは層が違い、宣言と記録を比べて差を埋める道具が、どこまでを比べているかの話です。
create 2回と apply 2回
同じマニフェスト(busybox:1.36 の Deployment、replicas 1)を、k3s v1.37.0 で流しました。
$ kubectl create -f l3-dep-ok.yamldeployment.apps/web created$ kubectl create -f l3-dep-ok.yaml # againError from server (AlreadyExists): error when creating "/l3-dep-ok.yaml": deployments.apps "web" already existsexit=1$ kubectl apply -f l3-dep-ok.yaml # apply after createWarning: resource deployments/web is missing the kubectl.kubernetes.io/last-applied-configuration annotation … The missing annotation will be patched automatically.deployment.apps/web configured$ kubectl apply -f l3-dep-ok.yamldeployment.apps/web created$ kubectl apply -f l3-dep-ok.yaml # againdeployment.apps/web unchangedexit=0
create の2回目は名前でぶつかり、終了コード1です。apply の2回目は unchanged で終了コード0。create の後の apply は警告を出して注釈を補い、configured で通りました。公式文書は混用を not supported と書きますが(Last modified August 24, 2023)、この版で失敗はしていません。「失敗する」と覚えるのは誤りで、原文は「サポートしない」です。2回流して2回とも同じでした。
kubectl create で作った Deployment に、同じファイルで kubectl apply を流すとどうなりましたか(kubectl v1.37.0)。
Terraform: terraform_data と local-exec
値を持つのは id だけです。local-exec が書いたファイルを消しても、次の apply は No changes と答えます。やり直すには -replace で実体ごと作り直します。
もの 記録にあるか 2回目の扱いterraform_data ある(id) 差なし → 何もしないside-effect.log ない 消えても気づかない
Kubernetes: create と apply
create は名前と比べ、同名なら 409 で止まります。apply は今の実物と前回の宣言の両方と比べ、差が無ければ unchanged です。generateName は名前が毎回変わり、名前の歯止めが掛かりません。
操作 比べる相手 2回目の結果create 名前 AlreadyExistsapply 今の実物と前回の宣言 unchanged
片方は黙って通り、片方はエラーで止まります。2回目の結果を決めているのは何でしょうか。
それぞれの道具が、2回目に「何と」比べているかを探してください。
この章のまとめ
- apply が揃えるのは 構成・実物・state。実物とは provider が読んで記録できる範囲。
- terraform_data の記録で値を持つのは id だけ。local-exec が書いたファイルを消しても No changes。
- やり直すには
-replaceで実体ごと作り直す。id は変わる。 - Kubernetes の create は 名前 と比べ、同名なら AlreadyExists。apply は 今の実物と前回の宣言 の両方と比べ、差が無ければ unchanged。
- create の後の apply は警告付きで通る。公式は混用を
not supportedと書くが、「失敗する」ではない。 - generateName は名前が毎回変わり、名前の歯止めが掛からない。
- 「何度流しても同じ」は、記録と比べられる範囲でだけ成り立つ。
自分の現場で確かめる
読んだだけでは身につきません。手元の環境で実際に見てみてください。
理解度チェック
読んだ直後は「分かった」と感じやすいものです。実際に解いて確かめてください。
「宣言的な道具だから、同じ定義を何度流しても安全だ」という主張の扱いとして適切なのはどれですか。
用語集(4語)
- terraform_data
- provider を要らない組み込みのリソース型。標準のライフサイクルを持つが、それ以外の動作はしない。provisioner の置き場所に使われる。
- provisioner(local-exec)
- リソースの作成に合わせて手元でコマンドを走らせる仕組み。コマンドが外に残したものは state に記録されない。
- name idempotency
- Kubernetes の API 規約が求める性質。既存と同じ名前での作成要求には 409(AlreadyExists)を返す。
- last-applied-configuration
- kubectl apply が前回送った宣言を残す注釈。次の apply で、ファイルから消えたフィールドを見つけるのに使う。create は残さない。
出典(一次ソース)
- Terraform — Resource behavior(取得 2026-09-28、実演は Terraform 1.16.4) ↗
- Terraform — terraform_data resource reference(取得 2026-09-28) ↗
- Kubernetes — Declarative Management of Kubernetes Objects Using Configuration Files(Last modified August 24, 2023) ↗
- Kubernetes community — API Conventions, Idempotency(main、取得 2026-09-28) ↗
- hashicorp/terraform — LICENSE(Business Source License 1.1) ↗
- Linux Foundation — Linux Foundation Launches OpenTofu(2023-09-20) ↗
- opentofu/opentofu — LICENSE(Mozilla Public License 2.0) ↗
- CNCF — OpenTofu(Sandbox、2025-04-23 受理) ↗