Terraformを学ぶ:6段階ロードマップ

この記事にはアフィリエイト広告が含まれています。 冒頭まとめ Terraformのエラーを検索して1件ずつ直しているのに、翌日は別のエラーで止まる。この繰り返しから抜けるには、覚える順序を変える必要があります。 Terraformのエラーの多くは、4つの境界のどこかで起きています。設定ファイルと状態ファイルの境界、状態ファイルと実際のインフラの境界、計画時に確定している値と適用後に決まる値の境界、そして自分の作業と他人の作業の境界です。エラー文はこの境界のどれで止まったかを示していますが、境界の存在を知らないと文言が読めません。 DockerやKubernetesとの最大の違いは、Terraformが3つの世界を突き合わせている点にあります。あなたが書いた設定、Terraformが記録している状態、そして実際に存在するインフラです。この3つがずれたときにエラーになります。どれとどれがずれているのかを特定できれば、直す場所は自動的に決まります。 学ぶ順序は、初期化とプロバイダー、状態ファイル、計画と適用、参照と依存関係、変数とモジュール、トラブルシューティングの6段階です。各段階には「次へ進む目安」を置きました。飛ばした段階は、後の段階のエラーとして別の顔で現れます。 個別に直すだけでは理解しにくい理由 検索で見つかる対処は、多くの場合その環境で有効だった手順です。なぜ有効だったかは書かれていないことがあります。 たとえば、エラーが出たときに terraform state rm を実行したら通った、という手順があります。これはTerraformの管理下から対象を外す操作なので、確かにエラーは消えます。しかし実際のインフラは残ったままです。次に terraform apply を実行すると、Terraformはその資源が存在しないものとして作りに行き、名前の重複で失敗します。状態ファイルが何であるかを知らないまま実行すると、症状が別の形に移るだけになります。 同じことが -lock=false でも起きます。ロックを取得できないエラーを消すために付けるという手順は広く共有されていますが、これはロックの仕組みそのものを止める操作です。 エラー文も同じです。Terraformのエラーは、設定の文法が誤っているのか、値が計画時に決まらないのか、状態と実物がずれているのかを示しています。この区別は、次に説明する全体像を知っていれば読み取れます。 最初に理解するべきTerraformの全体像 先に3つの世界の関係を押さえます。ここを飛ばすと、後のすべての段階で判断がぶれます。 公式ドキュメントによれば、Terraformは管理下のインフラと設定について状態を保存しなければなりません。この状態は、実世界の資源を設定へ対応付けること、メタデータを追跡すること、大規模なインフラでの性能を改善することに使われます。既定では terraform.tfstate という名前のローカルファイルに保存されます。そしてTerraformは、どの変更を加えるかを決めるために状態を使い、あらゆる操作の前に更新処理を行って状態を実際のインフラへ合わせます(State)。 つまり流れはこうです。あなたが設定を書く。Terraformが状態を読み、実物と突き合わせて更新する。設定と状態を比較して差分を出す。その差分を適用する。 terraform plan の説明も同じ構造です。公式ドキュメントによれば、計画の作成時にTerraformは、既存の遠隔オブジェクトの現在の状態を読んで状態が最新であることを確認し、現在の設定と以前の状態を比較して差異を記録し、適用すれば遠隔オブジェクトが設定と一致するようになる変更操作の集合を提案します(terraform plan)。 この3つの世界を分けて考えられるようになると、エラーの読み方が変わります。設定の書き間違いなら設定を直す。状態と実物がずれているなら状態を合わせる。実物が想定と違うなら実物を確認する。混ぜると迷います。 まずは手元の環境を確認してください。 terraform version terraform providers 学習ステップ1:初期化とプロバイダー 何を理解する段階か:Terraform本体が何もしないこと、実際の操作はプロバイダーが行うことです。 なぜエラー解決に必要か:エラー文の多くはプロバイダーが返しています。Terraform本体の問題とプロバイダーの問題を分けられないと、調べる先を間違えます。 最低限覚える概念:公式ドキュメントによれば、terraform init はTerraformの設定ファイルを含む作業ディレクトリを初期化するコマンドで、新しい設定を書いた後、あるいはバージョン管理から既存の設定を複製した後に最初に実行すべきものです。何度実行しても安全で、既存の設定や状態を削除することはありません。 ここで押さえるべきは、プロバイダーがバージョンを持つことです。同じ設定でも、プロバイダーの版が違えば挙動が変わります。ロックファイルが版を固定するのはこのためです。 実際に試すコマンド: # 作業ディレクトリを初期化する(プロバイダーの取得と設定の準備) terraform init # 使われているプロバイダーと版を確認する terraform providers # プロバイダーを更新する(ロックファイルの内容が変わる) terraform init -upgrade # 設定の文法と内部整合を検査する(遠隔操作を伴わない) terraform validate terraform validate は遠隔への問い合わせをしません。文法の誤りや、存在しない引数名を書いた場合は、ここで先に見つかります。計画を走らせる前に通しておくと切り分けが速くなります。 次の段階へ進む目安:エラー文を見て、Terraform本体が出したものかプロバイダーが出したものかを区別できることです。 関連して発生しやすいエラー:Unsupported argument は、その位置に存在しない引数名を書いた場合です。プロバイダーの版を上げた後に出ることがあり、その場合は該当の版の資料を確認します。 ...

2026年8月9日 · ErrorLog

Invalid for_each argument:原因と解決策

冒頭まとめ Invalid for_each argument が拒む理由は3系統です。apply の後でないと決まらない値、map でも文字列の集合でもない値、機密の印が付いた値。別々の制限に見えますが、由来は1つです。 for_each に渡した値は、最終的に文字列をキーとする対応表へ変換されます。そしてそのキーが、そのままインスタンスのアドレスになります。公式ドキュメントは、Terraform がインスタンスを map のキーまたは集合の要素で識別すること、アドレスが <種別>.<名前>[<キー>] の形になることを明記しています。aws_iam_user.the-accounts["Todd"] の角括弧の中身が、渡した値から来ているという意味です。 アドレスには3つの条件が要ります。計画の時点で確定していること、並び順に左右されないこと、画面と記録に平文で出せることです。未確定値は1つ目を、list は2つ目を、機密値は3つ目を満たしません。制限を1つずつ避けようとすると行き詰まりますが、キーの条件として捉えると3つとも同じ方向で解けます。 つまりこのエラーは、値の書き方を咎めているのではありません。インスタンスの名前として使えない値が渡された、と言っています。直す先は式ではなく、何をキーにするかという設計です。 エラーの概要 出力は3つの部分でできています。先頭の要約、対象の位置、そして理由を述べる本文です。次は実際に報告された表示です。 Error: Invalid for_each argument on .terraform/modules/aks_primary/resource-tls.tf line 7, in resource "tls_private_key" "this": 7: for_each = var.ssh_key == null ? { "key" = {} } : {} ├──────────────── │ var.ssh_key has a sensitive value Sensitive values, or values derived from sensitive values, cannot be used as for_each arguments. If used, the sensitive value could be exposed as a resource instance key. 罫線の部分は補助情報で、式の中のどの変数が問題なのかを示します。ここに名前が出ていれば、追う対象はその変数です。 ...

2026年8月7日 · ErrorLog

Terraform Unsupported argument:原因と解決策

冒頭まとめ An argument named "..." is not expected here. を見たとき、多くの人は指し示された行の書き方が間違っていると考えます。しかし、この文言は設定ファイルのボディをあらかじめ決められたスキーマと照合した結果として出るものです。エラーが指すのは「引数名を書いた位置」であり、その引数を受け付けない側の情報は行番号のどこにも現れません。 したがって、直すべき対象は行ではなく、期待される引数の集合を決めている側です。これは大きく3系統に分かれます。resource / data / provider ブロックなら、いま初期化済みの provider が持つスキーマ。module ブロックなら、子 module 側に書かれた variable 宣言。terraform や lifecycle のような固定ブロックなら Terraform 本体です。 この区別を飛ばすと、典型的な失敗に入ります。module 呼び出しで拒否されたのに、呼び出し側(ルート)の variables.tf へ同名の変数を追加してしまう例が繰り返し報告されています。宣言が必要なのは子 module の側なので、これでは通りません。また、公式ドキュメントどおりに書いたのに拒否されるという報告も多く、この場合はドキュメントの版と実際にインストールされている provider の版がずれています。 判断の起点は、エラー出力の on ... line N, in ... の行です。ファイルパスと、その後ろのブロック種別を読めば、どの系統が拒否したかがほぼ決まります。末尾に Did you mean が付いているかどうかも、そのまま分岐材料になります。 エラーの概要 エラーは次の形で出ます。まず、module 呼び出しで未宣言の入力を渡した場合です。 Error: Unsupported argument on main.tf line 25, in module "sh": 25: num = 4 An argument named "num" is not expected here. 読むべきは3箇所です。1つ目は on に続くファイルパス。2つ目は in に続くブロック種別と名前。ここが in module "sh" なので、期待集合を決めているのは子 module の variable 宣言であり、provider は無関係です。3つ目は最後の行のサフィックス。この例では何も付いていません。 ...

2026年8月6日 · ErrorLog

Terraformロックファイル:原因と解決策

冒頭まとめ Inconsistent dependency lock file は、現在のTerraform設定が必要とするプロバイダーと、.terraform.lock.hcl に記録された選択が一致しないときに出ます。 Error: Inconsistent dependency lock file The following dependency selections recorded in the lock file are inconsistent with the current configuration: - provider registry.terraform.io/hashicorp/aws: locked version selection 5.100.0 doesn't match the updated version constraints "~> 6.0" To update the locked dependency selections to match a changed configuration, run: terraform init -upgrade この場合は、required_providers や子モジュールの変更に対して、ロックファイルが古いままです。設定変更を行った作業環境で次を実行し、差分を確認して .terraform.lock.hcl も一緒に版管理へ入れます。 terraform init -upgrade git diff -- .terraform.lock.hcl ただし、CIで発生するロックファイル関連の失敗が、すべて init -upgrade で直るわけではありません。開発環境とCIのOSやCPUが違い、対象環境の検査値がロックファイルにない場合は、次のような別のエラーになります。 ...

2026年8月5日 · ErrorLog

Terraform の 422 エラー:原因と解決策

冒頭まとめ Terraform で 422 Unprocessable Entity を受け取ったとき、まず押さえるべきことがあります。このエラーは Terraform が作ったものではありません。Terraform 本体には、要求の内容を検証して 422 を返す仕組みがありません。目にする 422 は、必ずどこかから中継されたものです。 出どころは2つに分かれます。プロバイダが相手先の窓口を叩いた結果として返ってきたものと、HCP Terraform や Terraform Enterprise の窓口を直接叩いた結果として返ってきたものです。前者の場合、応答の中身は相手先の流儀に従います。GitHub の窓口なら GitHub の書式、別のサービスならそのサービスの書式です。したがって、読み方は相手先の規則で決まります。Terraform の文書をいくら読んでも、応答の中身の意味は書かれていません。 もう1つ、共通する性質があります。Terraform の各層は、422 を再試行しません。レジストリ向けの通信で使われている仕組みは、再試行の対象を429と、501を除く500番台に限っています。AWS 向けの実装が再試行の対象として定義しているのも 500・502・503・504 です。どこにも 422 は含まれていません。これは怠慢ではなく設計です。要求を直さない限り結果が変わらないエラーなので、送り直す意味がありません。 エラーの概要 プロバイダ経由の場合、相手先の応答がそのまま、あるいは整形されて表示されます。GitHub を相手にした場合の例です。 Error: PATCH https://api.github.com/repos/example/sample: 422 Validation Failed [{Resource:Repository Field:default_branch Code:invalid Message:Cannot update default branch for an empty repository.}] 同じ相手先でも、詳細が空のことがあります。この場合、手がかりは短い文言だけです。 { "message": "Validation Failed", "errors": [], "documentation_url": "https://docs.github.com/rest/reference/repos#update-a-repository" } 別のサービスを相手にした場合は、書式が変わります。 Bad response statusCode [422]. Status [422 Unprocessable Entity]. Body: [baseType=error, code=InvalidBodyContent, message=must have at least one node pool] HCP Terraform の窓口を直接叩いた場合は、また別の形になります。整形の規約に沿った構造で、status と title と detail が並びます。 ...

2026年7月29日 · ErrorLog

Terraform の 502 エラー:原因と解決策

冒頭まとめ 502 Bad Gateway は、要求を取り次いだ中継役が、その先から正常な応答を得られなかったことを示します。同じ 5xx でも、待ちきれずに諦めた場合は 504 です。応答が得られなかったのか、待ち時間が尽きたのかという違いで、疑うべき場所も変わります。 Terraform では、502 の扱いに1つ特徴があります。本体のソースを読むと、レジストリへの問い合わせの再試行回数を設定する処理の説明に、「502 のような再試行可能なエラーに対して行う再試行の回数」と書かれています。つまり Terraform は、502 を再試行で吸収すべきものとして名指しで想定しています。 ところが、その既定値は1回です。合計2回で諦める設計になっており、失敗時の文言に「2回試した」と出るのはこのためです。想定しているにもかかわらず、既定では吸収できる幅が非常に狭い、という状態になっています。この回数は環境変数で増やせます。 もう1つ、読み方の注意点があります。エラー文に現れる URL は、要求の宛先であって、502 を作った相手ではありません。社内のプロキシがその先へ繋げずに 502 を返している場合でも、文言にはレジストリの URL が並びます。「レジストリが落ちている」と判断する前に、応答を作ったのが誰かを確かめてください。 エラーの概要 terraform init の段階では、再試行の回数を含む形になります。 Error: Failed to query available provider packages Could not retrieve the list of available versions for provider example/example: the request failed after 2 attempts, please try again later: 502 Bad Gateway returned from https://registry.terraform.io/v1/providers/... 「2回試した」という数字は、既定の再試行回数が1回であることに対応します。この数字が2以外になっていれば、環境変数で回数が変更されているということです。 プロバイダのファイルを取得する段階でも起きます。この場合、宛先はレジストリではなく配布元です。 Error: Failed to install provider Error while installing example/example v1.2.3: unsuccessful request to https://releases.example.com/terraform-provider-example_1.2.3_linux_amd64.zip: 502 Bad Gateway terraform plan や terraform apply の途中で出る場合は、プロバイダがクラウドの窓口を叩いた結果です。この場合、どの資源の処理で起きたかが示されます。応答の本文が各社のエラー形式ではなく、簡素な HTML であれば、作ったのは窓口ではなく手前の中継役です。 ...

2026年7月29日 · ErrorLog

Terraform の 504 エラー:原因と解決策

冒頭まとめ 504 Gateway Timeout は、要求を取り次いだ中継役が、その先からの応答を待ちきれずに返すエラーです。Terraform で目にする場合、出どころは3つに分かれます。本体がレジストリや配布元へ問い合わせたとき、プロバイダがクラウドの窓口へ要求したとき、そして経路上のプロキシや振り分け装置が返したときです。どの段階で出たかによって、調整できる場所も、調整すべき値も変わります。 先に押さえるべき性質が1つあります。504 は「失敗した」という通知ではありません。「待っていた側が待つのをやめた」という通知です。要求を受け取った先が処理を続けている可能性は残ります。作成の操作でこれを受けると、クラウド側には資源が出来上がっているのに Terraform は失敗として扱う、という食い違いが起きます。したがって、504 を受けたあとに最初にやるべきは再実行ではなく、実際に出来ているかどうかの確認です。 再試行の作法も段階ごとに違います。本体がレジストリへ問い合わせる部分は、既定で1回しか再試行しません。合計2回で諦める設計で、失敗時の文言に「2回試した」と出るのはこのためです。この回数は環境変数で増やせます。一方、プロバイダがクラウドの窓口を叩く部分は、各社の実装に従います。たとえば AWS の実装では 500・502・503・504 が再試行の対象と定義され、既定の試行回数は3回です。AWS のプロバイダはこれを25回まで引き上げています。 もう1つ、待ち時間の作法にも癖があります。共通して使われている再試行の仕組みでは、待ち時間は1秒から始めて倍々に増え、上限は30秒です。応答に含まれる待機の指示は 429 と 503 のときだけ読み取られ、504 では読み取られません。 エラーの概要 段階ごとに出方が違います。まず、terraform init の段階でレジストリや配布元から返る場合です。 Error: Failed to install provider Error while installing example/example v1.2.3: unsuccessful request to https://releases.example.com/terraform-provider-example_1.2.3_linux_amd64.zip: 504 Gateway Timeout レジストリへの問い合わせ自体が繰り返し失敗した場合は、再試行の回数を含む形になります。 Error: Failed to query available provider packages Could not retrieve the list of available versions for provider example/example: the request failed after 2 attempts, please try again later: 504 Gateway Timeout returned from https://registry.terraform.io/v1/providers/... この「2回試した」という数字は、既定の再試行回数が1回であることに対応しています。ソースでは、この文言を組み立てる処理が再試行の上限に達したときに呼ばれ、試行回数を添えるようになっています。 ...

2026年7月28日 · ErrorLog

Terraform の crash エラー:原因と解決策

冒頭まとめ Terraform の crash は、設定の誤りではなくソフトウェア側の不具合を示します。ただし「Terraform が落ちた」と一口に言っても、中身は2種類あります。Terraform 本体が落ちた場合と、プロバイダのプラグインが落ちた場合です。文言も、確認する場所も、報告する相手も違います。ソースを読むと、この2つには別々の出力文が定義されています。本体が落ちた場合は TERRAFORM CRASH という帯で囲まれた文が出て、報告先は Terraform 本体です。プラグインが落ちた場合は Stack trace from the <プラグイン名> plugin: に続けてスタックトレースが出て、末尾に Error: The <プラグイン名> plugin crashed! が付き、報告先はそのプラグインの保守者です。 実務で頻度が高いのは後者です。そして厄介なのは、プラグインが落ちると、そのプラグインを使っていた他の処理が巻き添えで失敗し、Plugin did not respond や Request cancelled というエラーが大量に並ぶことです。これらは結果であって原因ではありません。原因は、その下に1つだけ出ているスタックトレースです。 もう1つ、先に否定しておくべき手順があります。「crash.log を確認する」という案内が今も広く出回っていますが、このファイルは現在作られません。ソースを比べると、1.0 まではファイルを書き出したうえで、その場所と、機密情報が含まれうるという警告まで表示していました。1.1 以降、その処理も文言も消えています。公式の該当ページにも現在は記述がありません。つまり、スタックトレースは標準エラー出力に流れるだけで、取り逃がすと消えます。最初にやるべきは、出力の保全です。 エラーの概要 出力は3つの形に分かれます。以下は実際に報告された内容をもとにした形です。 プラグインが落ちた場合。巻き添えのエラーが並び、その後にスタックトレースと締めの1文が出ます。 Error: Request cancelled The plugin6.(*GRPCProvider).UpgradeResourceState request was cancelled. Error: Plugin did not respond The plugin encountered an error, and failed to respond to the plugin6.(*GRPCProvider).ReadResource call. The plugin logs may contain more details. Stack trace from the terraform-provider-example_v1.2.3 plugin: panic: runtime error: invalid memory address or nil pointer dereference [signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0xe19f58] ... Error: The terraform-provider-example_v1.2.3 plugin crashed! 本体が落ちた場合。帯で囲まれた文が出て、その後に panic: とスタックトレースが続きます。 ...

2026年7月28日 · ErrorLog

Terraform の state lock エラー:原因と解決策

冒頭まとめ Terraform の Error acquiring the state lock は、状態ファイルが壊れたという意味ではありません。状態ファイルを同時に書き換えられないよう Terraform が取る鍵を、今回は取れなかったという通知です。原因は突き詰めると2つしかありません。他の実行が本当に鍵を持っているか、鍵を持ったまま終われなかった実行の跡が残っているかです。 判別の材料は、エラー出力に必ず付く Lock Info の7項目です。ID・Path・Operation・Who・バージョン・Created・Info が並び、Terraform のソースでもこの並びの雛形として定義されています。このうち Created(鍵を取った時刻、協定世界時)と Who(利用者名@ホスト名)を読めば、待つべきか外すべきかはほぼ決まります。 先に押さえておきたい性質が1つあります。Terraform は既定では鍵の取得を再試行しません。-lock-timeout の既定値は 0 で、ソースでは指定された時間をそのまま期限として文脈を作るため、0 の場合は最初の1回で失敗が確定します。逆に -lock-timeout=5m のように指定すると、1秒から始めて倍々に伸ばし、最大16秒間隔で、指定時間まで再試行を続けます。「待てば通ったはずのエラー」を即座の失敗として受け取っていることが、実際には少なくありません。 境界も引いておきます。-lock=false は解決ではなく回避です。Terraform 自身のエラー本文にも、ほとんどのコマンドで無効化できるが推奨しないと書かれています。また Error releasing the state lock は逆向きのエラーで、鍵を外す側の失敗です。 エラーの概要 実際の出力は次の形です。冒頭の見出しと、定型の説明文、そして Lock Info が続きます。 Error: Error acquiring the state lock Error message: operation error S3: PutObject, https response error StatusCode: 412, api error PreconditionFailed: At least one of the pre-conditions you specified did not hold Lock Info: ID: 3f6a1c9e-1f2b-4a55-9d1c-0a7e2b9c4d51 Path: my-tfstate-bucket/prod/terraform.tfstate.tflock Operation: OperationTypeApply Who: deploy@runner-07 Version: 1.11.4 Created: 2026-07-28 02:14:22.5 +0000 UTC Info: Lock Info の7項目は、Terraform のソースに文字列の雛形として定義されています。ID は鍵の識別子で、後述の解除コマンドに渡す値です。Path は鍵の置き場所で、どのバックエンドのどの状態ファイルかが分かります。Operation は実行しようとした操作(OperationTypeApply など)、Who は利用者名とホスト名を @ でつないだもの、バージョンは鍵を取った側の Terraform のバージョン、Created は鍵を取った時刻で協定世界時、Info は呼び出し側が付ける補足で、空のことが多い項目です。 ...

2026年7月28日 · ErrorLog

Terraform の 409 エラー:原因と解決策

エラーの概要 Terraform の 409 エラーは、Terraform が作成・更新しようとするリソースが既にクラウド環境に存在し、状態ファイル(tfstate)に記録された期待値と実際のリソース状態に競合が生じていることを示します。このエラーは特にマルチユーザー環境や手動でリソースを作成した後に Terraform で管理を開始する場合に発生しやすくなります。 実際のエラーメッセージ例 Error: Error creating XXX: XXX (xxx): InvalidParameterException: Resource already exists on main.tf line 42, in resource "aws_instance" "web": 42: resource "aws_instance" "web" { Error: ConflictException { "error": "conflict", "message": "The resource with name 'my-bucket' already exists", "status_code": 409 } よくある原因と解決手順 原因 1:手動で作成したリソースを Terraform で管理しようとしている クラウド管理コンソールやコマンドラインで直接作成したリソースに対して、Terraform コードで同じリソースを定義すると、Terraform はそのリソースが「新規作成される対象」だと判断します。しかし実際にはリソースが存在するため、作成時に 409 エラーで競合が検出されます。 この場合、terraform import コマンドを使い、既存のリソースを Terraform の管理下に移す必要があります。 Before(エラーが起きるコード): resource "aws_s3_bucket" "data_bucket" { bucket = "my-existing-bucket" } AWS マネジメントコンソールで my-existing-bucket が既に存在している場合、terraform apply 実行時に 409 エラーが発生します。 ...

2026年6月10日 · ErrorLog