二つ目のリージョンを足したらモジュールがプロバイダを受け取れなかった
目標
同じプロバイダーを2つ設定してリソースごとに選び、モジュールにその設定を渡し、1つ抜けたときに何が止めるのかを見たあと、バージョン制約を狭めていきながら、initがいつ通過していつ拒否するかを、自分で確認します。
なぜ重要なのか
インフラコードが、1つのリージョン・1つのアカウントの中にとどまる期間は、思ったより短いです。災害復旧用の2つ目のリージョン、監査ログを別に集めるアカウント、公開レジストリとプライベートレジストリ。どちらの場合も、同じプロバイダーを、異なる設定で2つ持つことになります。このとき、リソースがどの設定で作られたかは、コードではなく状態に残り、その記録が間違っていると、あとで見当違いの場所を削除します。モジュールは、さらに一歩進みます。モジュールが自分のプロバイダー設定を自分で作ると、そのモジュールは2回使えなくなるため、場所だけを宣言し、呼び出す側が埋めるように設計します。バージョン制約は、これらすべての土台です。制約をどう書くかが、「明日の朝、CIが新しいバージョンを取得してよいか」を決め、ロックファイルが、その決定の根拠を残します。
ステップ
/root/tfa-alias/main.tfにrequired_version = ">= 1.6.0"と、required_providersのrandom(source = "hashicorp/random"、version = "3.9.0")を置き、デフォルトのプロバイダー設定ブロック1つとrandom_pet.root(length 2)を宣言して、init・applyしてください。/root/tfa-alias/aliased.tfを新しく作成して、alias = "seeded"の2つ目のrandomプロバイダー設定と、その設定で作られるrandom_pet.aliased(length 4)を宣言してください。main.tfのrandom_pet.rootは、デフォルトの設定のままにします。applyしたあと、状態ファイルで、2つのリソースのプロバイダーアドレスがどう違うかを確認してください。/root/tfa-alias/modules/site/main.tfにconfiguration_aliases = [random.east, random.west]を宣言し、それぞれで作るrandom_pet.east・random_pet.west(どちらもlength 2)と、同じ名前の出力を2つ置いてください。/root/tfa-alias/site.tfで、そのモジュールを呼び出しながら、providersマップをrandom.east = randomとrandom.west = random.seededで埋め、モジュールの2つの出力を、ルートの出力site_east・site_westとして引き上げたあと、init・applyしてください。/root/tfa-alias/site.tfのprovidersマップから、random.westの行だけを削除して、tofu initを回し、エラーを/root/tfa-alias/missing-provider.txtに保存してください(成功してはいけません)。そのあと、削除した行を元に戻して、再びinit・applyし、プランがきれいな状態で終えてください。/root/tfa-alias/probe/too-new/main.tfに、randomをversion = ">= 4.0.0"で制約したrequired_providersだけを置いて、そのディレクトリでinitしてください。出力は/root/tfa-alias/probe/too-new/init.txtに保存します。このディレクトリには、ロックファイルができていてはいけません。- 2つのディレクトリで、同じプロバイダーを、互いに異なる制約にして、結果を比べてください。
/root/tfa-alias/probe/minor/main.tfには、randomの制約を~> 3.8で置きます。/root/tfa-alias/probe/patch/main.tfには、randomの制約を~> 3.8.0で置きます。 どちらもinitしたあと、/root/tfa-alias/probe/verdict.tsvに、minor・patchの2行を、タブ区切りの3列(<이름>(プレースホルダーは名前です)、okまたはfail、インストールされたバージョンまたはnone)で書きます。 /root/tfa-alias/probe/core/main.tfにrequired_version = ">= 999.0.0"だけを置いて、initし、エラーを/root/tfa-alias/probe/core/init.txtに保存し、いまのPodの実際のコアバージョン(tofu versionの1行目の数字だけ、例:1.2.3の形)を、/root/tfa-alias/probe/core/version.txtに1行で書いてください。/root/tfa-alias/report.tsvに、4行を、タブ区切りの2列で書いてください。lock_version= ルートのロックファイルに書かれたrandomのバージョン、lock_constraints= そのロックファイルに書かれた制約の文字列、provider_configs= ルートの設定のrandomプロバイダー設定ブロックの数、aliased_resources= 状態で、エイリアスの設定で作られたリソースの数(モジュールの中まで数えます)。
参考
- Podのプロバイダーミラーには、random 3.9.0の1つのバージョンだけが入っています。そのため、制約を満たせない場合を、本物のエラーとして見られます。
- チルダ制約は、一番右の桁だけが上がれるようにします。桁を2つ書いたときと、3つ書いたときで、許可する範囲が違います。
- よくある間違い: リソースに、モジュール用のメタ引数であるprovidersを使うことです。リソースはprovider、モジュールはprovidersです。
- よくある間違い: ステップ5・6・7の探索用ディレクトリを、ルートの中に作って、ルートの設定ファイルを上書きしてしまうことです。probe/の下に、別に作ってください。
- ロックファイル自体の構造とハッシュ検証は、Terraform/OpenTofu基礎で扱います。ここでは、エイリアスと制約が、ロックファイルに何を残すかだけを見ます。
- Provider Configuration・Provider Requirements・Providers Within Modules・Version Constraints・Dependency Lock File
コアとプロバイダーのバージョンを固定する
/root/tfa-alias/main.tfにrequired_version = ">= 1.6.0"と、required_providersのrandom(source = "hashicorp/random"、version = "3.9.0")を置き、デフォルトのプロバイダー設定ブロック1つとrandom_pet.root(length 2)を宣言して、init・applyしてください。
required_versionは、コア(OpenTofu自体)のバージョンを、required_providersのversionは、プラグインのバージョンを制約します。この2つは別のものなので、片方だけ書いてもinitはできます。ロックファイルが何を書いておくかを、apply後に開いて確認してください。
同じプロバイダーを2つ設定する
/root/tfa-alias/aliased.tfを新しく作成して、alias = "seeded"の2つ目のrandomプロバイダー設定と、その設定で作られるrandom_pet.aliased(length 4)を宣言してください。main.tfのrandom_pet.rootは、デフォルトの設定のままにします。applyしたあと、状態ファイルで、2つのリソースのプロバイダーアドレスがどう違うかを確認してください。
リソースでエイリアスの設定を選ぶメタ引数は、providerです(providersではありません。それはモジュール用です)。状態ファイルの各リソースには、どのプロバイダー設定で作られたかが文字列で書かれており、エイリアスが付いたほうだけ、末尾が違います。
モジュールにプロバイダー設定を渡す
/root/tfa-alias/modules/site/main.tfにconfiguration_aliases = [random.east, random.west]を宣言し、それぞれで作るrandom_pet.east・random_pet.west(どちらもlength 2)と、同じ名前の出力を2つ置いてください。/root/tfa-alias/site.tfで、そのモジュールを呼び出しながら、providersマップをrandom.east = randomとrandom.west = random.seededで埋め、モジュールの2つの出力を、ルートの出力site_east・site_westとして引き上げたあと、init・applyしてください。
configuration_aliasesは、「このモジュールは、こういう名前の設定を、呼び出す側から受け取る」という宣言です。モジュール内でproviderブロックを作らない理由は、モジュールが自分の認証情報を自分で決めると、再利用が不可能になるためです。providersマップの左側はモジュール内の名前、右側はルートの設定です。
渡すものを1つ抜かすと、何が止めるか
/root/tfa-alias/site.tfのprovidersマップから、random.westの行だけを削除して、tofu initを回し、エラーを/root/tfa-alias/missing-provider.txtに保存してください(成功してはいけません)。そのあと、削除した行を元に戻して、再びinit・applyし、プランがきれいな状態で終えてください。
モジュールが要求した設定の場所を、呼び出す側が埋めないと、ツールが、どの名前を受け取れなかったかをピンポイントで言ってくれます。エラーメッセージにその名前がそのまま出るかを見てください。出力は標準エラー出力に出るので、2>&1で一緒に受け取る必要があります。
ミラーにないバージョンを要求してみる
/root/tfa-alias/probe/too-new/main.tfに、randomをversion = ">= 4.0.0"で制約したrequired_providersだけを置いて、そのディレクトリでinitしてください。出力は/root/tfa-alias/probe/too-new/init.txtに保存します。このディレクトリには、ロックファイルができていてはいけません。
このPodのプロバイダーミラーには、randomが1つのバージョンしか入っていません。制約を満たすバージョンがないとき、ツールが「何を探して見つからなかったのか」をそのまま言ってくれるかを確認してください。失敗したinitは、ロックファイルを残しません。
チルダ制約の2通りが、異なるものを許可する
2つのディレクトリで、同じプロバイダーを、互いに異なる制約にして、結果を比べてください。
/root/tfa-alias/probe/minor/main.tfには、randomの制約を~> 3.8で置きます。
/root/tfa-alias/probe/patch/main.tfには、randomの制約を~> 3.8.0で置きます。
どちらもinitしたあと、/root/tfa-alias/probe/verdict.tsvに、minor・patchの2行を、タブ区切りの3列(<이름>(プレースホルダーは名前です)、okまたはfail、インストールされたバージョンまたはnone)で書きます。
チルダ制約は、一番右の桁だけを上げられるように許可します。桁をいくつ書いたかが、そのまま「どこまで上がってよいか」です。インストールされたバージョンは、そのディレクトリのロックファイルから読み取ります。
コアのバージョン制約は、プラグインより先に止める
/root/tfa-alias/probe/core/main.tfにrequired_version = ">= 999.0.0"だけを置いて、initし、エラーを/root/tfa-alias/probe/core/init.txtに保存し、いまのPodの実際のコアバージョン(tofu versionの1行目の数字だけ、例: 1.2.3の形)を、/root/tfa-alias/probe/core/version.txtに1行で書いてください。
コアのバージョン制約は、プロバイダーを取得する前に検査します。そのため、required_providersがまったくなくても止まります。エラーメッセージが「いま使っているバージョン」をそのまま言ってくれるので、version.txtと同じ数字かを比べてみてください。
何がどの設定で作られたかを表にして出す
/root/tfa-alias/report.tsvに、4行を、タブ区切りの2列で書いてください。lock_version = ルートのロックファイルに書かれたrandomのバージョン、lock_constraints = そのロックファイルに書かれた制約の文字列、provider_configs = ルートの設定のrandomプロバイダー設定ブロックの数、aliased_resources = 状態で、エイリアスの設定で作られたリソースの数(モジュールの中まで数えます)。
ロックファイルには、選ばれたバージョンと、そのときの制約が一緒に書かれているため、あとで「なぜこのバージョンが選ばれたのか」をたどれます。エイリアスで作られたリソースは、状態のprovider文字列の末尾が違います。jqで数えてみてください。