インフラ

terraform providerのバージョン制約とalias設計|lockファイルの運用判断

手元では通るのにCIだけ落ちる、あるいは半年ぶりのinitで意図せずproviderが上がって差分が膨らむ。Terraformの事故の多くは、リソースの書き方ではなくproviderの版とプラグインの設定側から出てきます。この記事ではproviderのソースアドレスの読み方から始め、バージョン制約の桁をどこまで許すかを決める基準、.terraform.lock.hclが何を記録していてCIで不一致が出るのはなぜか、aliasでマルチリージョンとマルチアカウントを分ける設計、そしてprovider設定を消すときの順序まで扱います。版に依存する記述は2026年8月14日にHashiCorp公式ドキュメントとTerraform Registryで確認した内容です。

まとめ:桁の決め方とaliasの置き場所で運用が決まる

結論を先に置きます。providerの運用で決めるべきことは2つしかありません。1つ目が、バージョン制約の桁をどこまで開けるか。2つ目が、alias付きの設定をどの層に置くか。この2つを決めずに書き始めると、後から全モジュールに手が入る改修になります。

桁の決め方は、providerの更新頻度と破壊的変更の入り方から逆算します。hashicorp/awsのように短い周期でマイナーが刻まれるproviderでは、~> 6.0のようにマイナーまで開けておき、実際の固定は.terraform.lock.hclに任せるのが扱いやすい構成。制約は「入ってほしくない版を弾く柵」、lockファイルは「今日使う版の記録」と役割を分けて考えると迷いません。逆に制約でパッチまで閉じてしまうと、脆弱性修正を取り込むたびにコード改修が要ります。

aliasは、リージョンやアカウントをまたぐ構成で必ず出てきます。設定ブロックはルートモジュールに集約し、子モジュールへはproviders引数で明示的に渡す。子モジュール側はconfiguration_aliasesで受け口を宣言します。ここでルール違反をしても最初は動いてしまい、モジュールにfor_eachを付けたい日や、アカウントを1つ畳む日に初めて詰まる。判断の分かれ目は、同じstateに複数アカウントの実体を同居させてよいかどうかです。

providerの仕組みとソースアドレスの読み方を先に押さえる

providerは、Terraform本体とは別に配布されるプラグインです。本体の版と切り離して動く前提を掴んでおくと、後の判断が速くなります。

providerが担当する範囲とソースアドレス3要素の構造の確認

Terraform本体が持っているのは、設定ファイルの構文解析と依存グラフの構築、状態の管理まで。AWSのAPIを叩いてEC2を作る処理は、本体ではなくproviderが担います。だからproviderの版が変わればリソースのスキーマも変わり、同じ.tfが通らなくなることがある。

providerの識別子がソースアドレスです。公式は「A provider’s source address is its global identifier.」と定義し、構造を「Source addresses consist of three parts delimited by slashes (/), as follows: [<HOSTNAME>/]<NAMESPACE>/<TYPE>」と説明しています。ホスト名は省略でき、その場合はregistry.terraform.ioが既定。つまりhashicorp/awsと書けばregistry.terraform.io/hashicorp/awsを指します。Azure側のhashicorp/azurermを使う構成の初期設定は、TerraformでAzureを構築する手順とazurerm 5系の認証・backend設計で扱っています。

名前空間の部分は配布元です。hashicorpはHashiCorpが公式に配布するもの、それ以外はベンダーやコミュニティが公開しているもの。同じawsという型名でも名前空間が違えば別物なので、Registryで探すときは名前空間まで見てください。なおterraform_remote_stateを提供する組み込みproviderだけは例外で、ソースアドレスはterraform.io/builtin/terraformという特別な形になります。宣言的な構成管理そのものの考え方はIaCとは?Infrastructure as Codeの仕組み・メリットと導入判断を解説で整理しました。

ローカル名とsourceが食い違って起きる取り違えを防ぐ書き方

ソースアドレスと並んで押さえたいのがローカル名です。公式は「Local names are module-specific, and are assigned when requiring a provider. Local names must be unique per-module.」と記しています。ローカル名はモジュールごとに付ける短い別名で、モジュールの外まで共有されるものではありません。

そして「Outside of the required_providers block, Terraform configurations always refer to providers by their local names.」とあるとおり、required_providersの外側では、リソース型の接頭辞もproviderブロックの名前もローカル名で書きます。aws_instanceawsはローカル名であって、名前空間のhashicorpではない。ここが取り違えの入口です。

事故が起きるのは、同じ型名で配布元が違うproviderを混ぜたときと、ローカル名を独自に付け替えたとき。たとえばmycorpという名前空間の派生providerをawsというローカル名で宣言すると、コード上は本家と見分けが付かなくなります。required_providerssourceを読むまで判別できないため、レビューで見落とされる。原則として、providerが推奨するローカル名をそのまま使い、付け替えが要る場合はコメントで理由を残してください。

required_providersをどのモジュールに書くかの判断

required_providersterraformブロックの中に置きます。ここで迷うのが、ルートだけに書くのか、子モジュールにも書くのか。答えは「providerのリソースを使うモジュールすべてに書く」です。

理由はローカル名がモジュール単位だからです。子モジュールがaws_s3_bucketを持つなら、そのモジュール自身がawsというローカル名とhashicorp/awsの対応を宣言していないと、名前の解決が呼び出し側の書き方に依存します。宣言があれば、そのモジュール単体で読んだときにどのproviderを前提としているかが確定する。

一方で、子モジュールに書くのはrequired_providersまでで、providerブロック本体は書きません。公式は「we strongly recommend against defining provider blocks in child modules」と明記し、再利用されるモジュールについては「A module intended to be called by one or more other modules must not contain any provider blocks.」と禁止しています。理由はfor_eachcountdepends_onと両立しないため。モジュールの分割粒度と入出力の設計はTerraform moduleの分割粒度とoutput設計|自作とRegistryの判断で判断基準を示しました。

バージョン制約の桁をどこまで許すかをproviderごとに決める

制約の書き方そのものは単純です。難しいのは、どこまで開けるかという設計判断のほうです。

バージョン制約で使う演算子と許される更新範囲を桁の単位で確認する

required_providersversionには、比較演算子を並べた式を書きます。使えるのは等号と不等号、そして悲観的制約演算子。公式は後者について「The ~> operator is a convenient shorthand for allowing the rightmost component of a version to increment.」と説明しています。いちばん右の桁だけが上がることを許す、という意味です。

書き方 許される版 使いどころ
= 6.12.0 その版のみ 再現性を最優先する場面
~> 6.12.0 6.12系のパッチ 修正だけ取り込む
~> 6.12 6系のマイナー以上 標準の書き方
>= 6.0, < 7.0 6系の全体 下限も示したい場面
>= 6.0 上限なし 再利用モジュール側

桁の数に注意してください。~> 6.12.0は3桁指定なので上がるのはパッチだけですが、~> 6.12は2桁指定なのでマイナーまで上がります。この2つは見た目が近いのに許容範囲が10倍以上違う。制約を書き換えるPRでは、桁が変わっていないかを差分で確認する運用にしておくと安全です。

制約の桁をproviderの更新頻度と破壊的変更の有無から決める

ここからが判断の本体です。桁を決める材料は、そのproviderが「どれくらいの速さで」「どこに」変更を入れるかという実績にあります。

実測してみます。hashicorp/awsは2025年6月18日にv6.0.0が公開され、2026年8月14日時点の最新は6.59.0(2026年8月12日公開)。約14か月で59回のマイナーが刻まれています。hashicorp/googleは7.44.0、hashicorp/azurermは5.0.1で、主要な3つでメジャー番号すら揃っていない。つまり「providerは一律にこう固定する」という決め方は成立しません。

実務での既定はこう置きます。マイナーが速く刻まれ、かつマイナーでの破壊的変更が原則入らないproviderは、2桁の~> 6.12でメジャーだけ閉じる。新機能の追い付きが速く、リリースノートの読み込みに時間を割けないproviderは、3桁の~> 6.12.0でパッチだけ開ける。社内で再利用されるモジュール側は>= 6.0のように下限だけを示し、上限はルートの構成に決めさせます。モジュールで上限を締めると、利用側がproviderを上げられなくなるためです。

逆に避けたいのが、全providerを=で完全固定する運用。制約で固定してしまうと、パッチを1つ取り込むだけでもコード変更とレビューが要ります。版の固定はlockファイルの仕事なので、制約側は柵として最小限に留めるほうが運用が軽い。制約を開けたうえでlockで留める構成なら、更新のPRはlockファイルの差分だけで済みます。

providerのメジャー更新を取り込む手順と切り戻しを用意する条件

メジャー更新は例外扱いにしてください。~> 6.12のまま7系は入りませんが、いつかは上げる日が来ます。

手順は4段です。まず対象providerのアップグレードガイドを読み、削除された引数と既定値が変わった箇所を洗い出す。次に検証用の作業ディレクトリで制約を7系へ広げ、terraform init -upgradeを実行してplanを取る。3段目で差分を1件ずつ判定し、置き換えが発生するリソースが無いかを確認します。ここで「must be replaced」が出るなら、本番へ持ち込む前に置き換えの可否を決めなければなりません。plan出力の記号とサマリ行の読み方はterraform planの差分の読み方|init・apply・destroyの安全手順にまとめています。

4段目が切り戻しの用意です。制約とlockファイルを元に戻すコミットを、更新のコミットと分けて作っておく。lockはterraform init -upgradeで書き換わるため、更新前の内容をVCSから復元できる状態にしておけば戻せます。なお、この一連の更新をPRとして自動生成する仕組みは別途あり、対応範囲はRenovateによるTerraform依存自動更新の仕組みと対応範囲で扱いました。

.terraform.lock.hclで版を固定するときの運用の境目

制約が柵なら、lockファイルは実際に選ばれた版の記録です。役割を分けて理解すると、CIの失敗も切り分けやすくなります。

lockファイルが記録する内容とh1とzhのハッシュ形式の違い

公式は「The lock file is always named .terraform.lock.hcl」とし、terraform initのたびに作成または更新されると記載しています。置かれるのはルートモジュールの作業ディレクトリ。VCSへの登録については「should include this file in your version control repository so that you can discuss potential changes to your external dependencies via code review」と明記されており、コミットが前提です。

記録されるのは、選択された版番号、制約、そしてハッシュの一覧。ハッシュには2つの方式があります。zh:は配布元レジストリに登録された公式の.zipパッケージそのもののSHA256で、公式の表現では「a SHA256 hash of each of the official .zip packages indexed in the origin registry」。h1:はアーカイブではなく中身から計算する方式で、「computed from the contents of the provider distribution package, rather than of the .zip archive」と説明されています。

この差が効いてきます。h1:は「can be calculated for an official .zip file, an unpacked directory with the same contents, or a recompressed .zip file」とあるとおり、展開済みディレクトリや再圧縮された.zipからでも同じ値が出る。社内ミラーやキャッシュを挟む構成でも検証が通るのはこのためです。

CIでハッシュが合わないときにplatform不足を切り分ける

実務でいちばん多い相談が「手元のMacでは通るのにCIのLinuxで落ちる」という症状です。原因はほぼハッシュのプラットフォーム不足で、providerのバイナリはOSとCPUの組み合わせごとに別物だから起きます。

手元でだけinitを回すと、lockには自分の環境ぶんのハッシュしか書かれません。そのlockをコミットしてCIへ渡すと、CI側は自分の環境のハッシュを見つけられず検証に失敗する。切り分けの手順は、まずlockファイル内の該当providerのハッシュ行を開き、CIの環境に対応する値が並んでいるかを見ることです。

対処は、必要なプラットフォームのハッシュを事前に書き込むこと。公式は「pre-populate hashes for a chosen set of platforms using the terraform providers lock command」として、-platformオプションを案内しています。開発機とCIの組み合わせぶんを列挙して1回実行しておけば、以後の環境差での失敗は止まります。Apple Silicon機とx86のCI、あるいはコンテナ内で実行するCIといった構成では、この事前登録を初期設定の手順に含めてください。実行環境を集中管理する側の選択肢についてはHCP Terraformとは?旧Terraform Cloudの料金・機能とHCPでの位置づけ|使い方も解説で整理しています。

lockファイルを更新する操作と更新しない操作の境目を運用で決める

どの操作でlockが書き換わるのかを、チームで揃えておく必要があります。公式の記述が判断の基準になります。「If a particular provider already has a selection recorded in the lock file, Terraform will always re-select that version for installation, even if a newer version has become available.」

つまり、lockに記録があれば、新しい版が出ていてもterraform initは同じ版を選び直します。これが再現性の担保。上書きするには「You can override that behavior by adding the -upgrade option.」とあるとおり、明示的に-upgradeを付けます。

運用としては、通常のinitはlockを変えない操作、init -upgradeはlockを変える操作として扱い、後者は必ず単独のPRに切り出してください。機能追加のPRにlockの更新が紛れ込むと、planの差分がコード由来なのか版由来なのか判別できなくなります。CI側でlockファイルの差分を検知したら別レビュアを要求する、といった仕掛けを入れておくと確実です。

aliasでマルチリージョンとマルチアカウントを分ける設計手順

1つのproviderに対して設定を複数持ちたい場面は、規模が出てくると必ず訪れます。

既定の設定とaliasを分けて書きproviderメタ引数で選ぶ

同じproviderで設定を分ける仕組みがaliasです。providerブロックにalias引数を足すと、名前付きの追加設定になります。公式は既定の扱いを「If there are multiple aliases for a provider, the provider block without an alias argument is the default configuration for that provider.」と定義しており、aliasを持たないブロックが既定です。

リソース側での指定はproviderメタ引数で行います。公式は「You can make a resource, data source, or module use an alternate provider configuration by setting the provider meta-argument to a <PROVIDER_NAME>.<ALIAS> reference.」と記載。ここで参照するのはローカル名とaliasの組で、クォートは不要です。

典型的な用途は3つ。1つ目がリージョン分割で、東京と大阪、あるいはグローバル向けのバージニア北部を分けるケース。2つ目がアカウント分割で、assume_roleの引き受け先を変えた設定を並べる形。3つ目が権限分離で、読み取り専用と変更可能を別ロールに分ける構成です。いずれも既定を「主となる環境」に置き、例外だけaliasにするほうが読みやすくなります。

モジュールへ渡すproviders引数とconfiguration_aliases

子モジュールへの受け渡しには、暗黙と明示の2経路があります。公式は暗黙側を「For convenience in simple configurations, a child module automatically inherits default provider configurations from its parent.」と説明。継承されるのは既定の設定だけで、alias付きは自動では渡りません。

明示側がmoduleブロックのproviders引数です。公式は「The providers argument within a module block is similar to the provider argument within a resource, but is a map rather than a single string because a module may contain resources from many different providers.」と説明しており、キーに子モジュール側での名前、値に呼び出し側の設定を書くマップになります。

受け取る子モジュール側の宣言がconfiguration_aliasesです。公式は「To use an aliased provider configuration in a child module, the child module must declare the alias using the configuration_aliases argument in the required_providers block.」と明記しています。required_providersの中でconfiguration_aliases = [aws.secondary]のように受け口を並べる。これを書かずにprovidersだけ渡すとエラーになります。ここは競合記事でも抜けやすい箇所で、alias付きモジュールを作るときの必須手順として覚えてください。なお、明示渡しを始めたモジュールは暗黙の継承をしなくなるため、既定の設定もprovidersマップに含めて渡す必要があります。

aliasをrootに集約するかstateごと分けるかの判定基準

設計として本当に問うべきなのは、aliasを増やすか、stateを分けるかです。aliasは同じstateの中に複数の設定先を同居させる手段なので、同居させてよいかどうかが分岐点になります。

aliasに寄せてよいのは、リソース間に参照関係があり、同時に変更したい単位が1つに収まる場合。たとえばグローバルなCDNの証明書がバージニア北部でなければ発行できず、本体は東京に置く、という構成では両者を分けるほうが手間が増えます。VPCピアリングやレプリケーションのように、両側の識別子を相互に参照する構成も同様です。

stateを分けるべきなのは、変更のタイミングと責任者が別のとき。本番と検証、あるいは事業部ごとにアカウントを持ち、それぞれの担当者がapplyする構成で1つのstateに束ねると、誰かのapplyが他方の実体まで巻き込みます。権限設計の観点でも、1つの実行主体が全アカウントへの変更権を持つことになるため望ましくない。stateやディレクトリの分け方を含む環境分離の方式比較はterraform workspaceの使い方と環境分離の判断基準|分割方式の比較で扱いました。

判定の順序としては、まず「同じPRで同時に変更したいか」を問い、続けて「同じ人がapplyするか」を問う。両方がはいならalias、どちらかがいいえならstate分割です。この線引きを含めたマルチアカウント構成の初期設計は、インフラ構築(AWS・Google Cloud・Azure)として請け負っています。

provider構成を変えるときに壊れる場所と使い分けの判定基準

最後に、providerまわりで起きる事故のうち、順序を守れば避けられるものを扱います。

providerブロックを消す前にリソースを片付ける順序の確認

使わなくなったaliasを整理する、あるいはアカウントを1つ畳む。このときproviderブロックだけ先に消すと、Terraformは動けなくなります。公式は理由をこう説明しています。「Provider configurations are used for all operations on associated resources, including destroying remote objects and refreshing state.」

破棄も更新の一種であり、破棄するにもproviderの設定が要る。設定が消えていると、Terraformは「そのリソースを破棄するための接続先が分からない」状態になります。公式も、その設定に属するリソースをすべて破棄してからでなければ設定ブロックを外せない旨を記載しています。

したがって順序は、対象リソースを削除してapplyまで通す、stateから該当リソースが消えたことを確認する、そのうえでproviderブロックを削除する、の3段です。実体を残したままTerraformの管理から外したい場合は、破棄ではなくremovedブロックを使い、その内側で破棄を抑止します。stateの中身を確認する手順はTerraform stateとは?tfstateの構造とS3バックエンド・移動削除の安全手順にまとめました。

provider既定値の埋め戻しが差分として出続けるときの扱い

providerに由来する差分でよく相談されるのが、書いていない属性に毎回差分が出るという症状です。原因は、こちらが省略した引数へproviderやクラウド側の既定値が入り、コード上の空との差として提示されること。

ここでの判断は2択です。既定値が今後も変わらない前提で運用できるなら、その値をコードに明示して差分を消す。providerの更新で既定値が変わる可能性を織り込むなら、明示せずに差分を無視する側へ倒す。前者を選ぶと、provider更新時に既定値が変わってもコードが優先されるため挙動が安定します。

後者を選ぶ場合の書き方と、無視してよい属性の見極めはterraform lifecycleの使い分け|ignore_changes・depends_on・movedの判断で扱いました。なお、providerブロックにタグの既定値をまとめて宣言できる引数を持つproviderもあり、これを使うと個々のリソースへの記述を減らせます。ただし既定値の宣言はproviderの設定単位なので、alias付きの設定にも同じ宣言を書かないと、aliasを使うリソースにだけタグが付かないという抜けが出ます。

症状からproviderの設定と制約の対処を引く判定基準の一覧

ここまでを、現場で引ける形に畳みます。症状ごとに既定の対処を決めておくと、急ぐ日でも判断がぶれません。

症状 まず疑う原因 既定の対処 避ける手
CIだけinitで落ちる ハッシュのplatform不足 providers lockで事前登録 lockの削除
意図せず版が上がった 制約の桁が2桁 桁を確認しlockで留める 制約での完全固定
脆弱性修正が入らない 制約でパッチまで閉じた 桁を1つ開ける lockの手編集
子モジュールでalias不明 受け口の宣言漏れ configuration_aliases追加 子でのprovider定義
モジュールにfor_each不可 子のproviderブロック ルートへ移して明示渡し モジュールの複製
provider設定を消せない 関連リソースが残存 破棄後にブロック削除 stateの直接編集
書いてない属性に差分 既定値の埋め戻し 明示するか無視を決める 都度の手直し
別アカウントを巻き込む 1つのstateに同居 stateを分けて権限も分離 aliasの追加
同名だが挙動が違う 名前空間の取り違え sourceの名前空間を確認 ローカル名の付替え

「避ける手」の列は、実際に選ばれがちで後から効いてくる操作を並べました。とくにlockファイルの削除と手編集は、その場は通っても再現性を捨てる行為です。initが通らないときに消したくなりますが、消せば次のinitで最新版が選ばれ、版由来の差分が紛れ込みます。

よくある質問

terraform providerについて、実務で問い合わせの多い5点をまとめました。

.terraform.lock.hclはgitにコミットすべきですか?

ルートモジュールのものはコミットしてください。公式もVCSへの登録を推奨しており、理由は外部依存の変更をコードレビューで扱えるようにするためです。再利用される子モジュール側にはlockファイルを置く必要がありません。lockが効くのはinitを実行する作業ディレクトリ、つまりルートモジュールの単位だからです。

versionを制約で固定するのとlockで固定するのは何が違いますか?

制約は許容範囲の宣言、lockは選ばれた版の記録です。制約を=で1点に絞ると、パッチの取り込みにもコード変更とレビューが要ります。制約はメジャーやマイナーの柵として置き、実際の版はlockで留める。この分担にしておけば、更新のPRはlockファイルの差分だけで完結します。

子モジュールにproviderブロックを書いてはいけない理由は?

公式が再利用されるモジュールについて明確に禁止しており、for_eachcountdepends_onと両立しないためです。書いても単純な構成では動いてしまうため、モジュールを繰り返したい日に初めて詰まります。設定はルートに置き、providers引数で渡す形に統一してください。

terraform init -upgradeはどのタイミングで実行しますか?

版を上げると決めたときだけです。通常のinitはlockに記録済みの版を選び直すため、意図せず上がることはありません。-upgradeを付けた実行は単独のPRに切り出し、planの差分を版由来のものとして確認する。機能追加の作業と混ぜないことが、差分を読める状態に保つ条件になります。

providerのaliasはいくつまで増やしてよいですか?

数の上限ではなく、同じstateに同居させてよいかで判断してください。目安として、同じPRで同時に変更したい範囲に収まり、かつapplyする人が同一なら同居させてよい。どちらかが崩れるならstateを分けます。aliasが5つ6つと増えている構成は、たいてい分割の判断を先送りしている状態です。

関連記事

資料請求

RELATED POSTS 関連記事