init とロックファイルが守るもの
一言でいうと
.terraform.lock.hclは、「何を許可し、何を選び、それが本当にそれで間違いないか」を書いた契約書で、initは、毎回この契約書を確認するコマンドです。
なぜこのファイルが必要だったのか
プロバイダーは、設定ファイルの中にはありません。名前とバージョンの制約だけが書かれていて、実物は、initが取得してきます。そのため、同じリポジトリを受け取った2人が、別々の時点でinitを回すと、別々のパッケージを手にする可能性があります。その間にプロバイダーが新しいバージョンを出せば、1人は古いバージョン、もう1人は新しいバージョンで、プランを立てます。プランが違えば、レビューは意味を失います。
バージョンの制約を書けばよいのではないか、と言われるかもしれませんが、制約だけでは足りません。制約は、たいてい範囲を許可します。範囲の中で、どれが選ばれたかは、制約には書かれず、選んだパッケージが、昨日取得したあのパッケージと同じバイトかどうかも、わかりません。そのため、3つのことを別々に書くファイルができました。
provider "registry.opentofu.org/hashicorp/local" {
version = "2.9.0"
constraints = "2.9.0"
hashes = [
"h1:rxomJjDwOo+YZ+WIPc25FqEgsz9orh/2MCyUcZmFjvw=",
]
}
versionは選んだもの、constraintsは許可したもの、hashesは、そのパッケージの指紋です。constraintsの行は、設定にバージョンの制約を書いたときにだけ現れます。制約を書いていないプロバイダーのブロックには、そもそもありません。
どう動くのか
initは、順番に次のように動きます。設定から、必要なプロバイダーと制約を集め、ロックファイルにすでに選んだものがあれば、それをそのまま使おうとし、なかったり、制約に合わなかったりすれば、新しく選びます。選んだパッケージをインストールする前に、ロックファイルのハッシュと比較して、合わなければ、インストールを拒否します。
拒否は、次のように見えます。
Error: Failed to install provider
Error while installing hashicorp/local 2.9.0: the current package for
registry.opentofu.org/hashicorp/local 2.9.0 doesn't match any of the
checksums previously recorded in the dependency lock file
このエラーが出たら、-upgradeを付けても解決しません。バージョンの選択がそのままなら、同じ検査にまた引っかかるからです。手を加えたハッシュが原因なら、契約書を捨てて作り直すのが、誠実な復旧です。
バージョンの制約を絞る側には、人があまり知らない穴が1つあります。ロックファイルのconstraints行は、その項目が最初に作られるときに書かれます。 すでに選んだバージョンが、新しい制約にも依然として合っていれば、initは、選び直す理由がないので、ロックファイルをそもそも書かず、そのため、あとから付けた制約は、契約書に現れません。-upgradeを付けても、選択がそのままなら同じです。許可の範囲を契約書に反映するには、その項目を新しく作らせる必要があります。ロックファイルを削除して作り直すほうが、誠実です。
範囲に合うものが1つもなければ、話が違います。そのときは、選び直そうとして、選べるものがないと言います。
Could not resolve provider hashicorp/local: no available releases match the
given constraints 2.5.0
制約の書き方は、2つがよくあります。1つは、正確なバージョンを固定することで、もう1つは、パッチバージョンだけを開けておくチルダ矢印演算子です。
version = "2.9.0" # 정확히 이것만
version = "~> 2.9" # 2.x 안에서 2.9 이상, 3.0 미만
initが作るもう1つは、.terraform/providers/ディレクトリです。ここには、レジストリのアドレス・ネームスペース・名前・バージョン・プラットフォームの順に深くなるパスに、実際のバイナリが置かれます。これは、プラットフォームごとに違う実行ファイルなので、コミットしません。initがいつでも作り直してくれるので、失っても損はありません。逆に、ロックファイルは、人がレビューする必要がある変更なので、必ずコミットします。プロバイダーのバージョンが上がったことは、コードの変更と同じくらい重要な出来事です。
ロックファイルを、initなしで作ったり直したりするコマンドもあります。tofu providers lockは、パッケージをインストールせずに、チェックサムだけを計算して書きます。社内ミラーだけを見る環境なら、-fs-mirrorでそのミラーを指せばよく、複数のプラットフォームで同じリポジトリを使うチームは、-platformを複数回与えて、プラットフォームごとのハッシュをあらかじめ埋めておきます。Linuxでだけinitしたロックファイルを、Macで使うと、「このプラットフォームのハッシュがない」と止められますが、その予防策がこれです。
現場での姿
最もよくある事故は、ロックファイルを.gitignoreに入れてあるリポジトリです。各自が別のバージョンを取得するようになり、ある日、プロバイダーがデフォルト値を変えると、1人のapplyだけがリソースを置き換えます。2つ目は、マージの競合が起きたときに、ロックファイルを手で直すことです。ハッシュの行を人が編集すると、十中八九食い違い、それ以降、そのブランチは誰もinitできなくなります。競合は、手で解かずに、片方を選んでから作り直すのが定石です。
3つ目は、CIだけが失敗する場合です。開発者のノートパソコンには、キャッシュが残っていて、ロックファイルが少し食い違っても通り抜けますが、毎回空のワークスペースから始まるCIは、すぐに止まります。そのため、ロックファイルが変わったコミットは、必ずCIを一度回してみてから、マージします。
次のラボですること
Podのオフラインミラーで、8つのステップを進めます。最初のinitが作ったロックファイルを開いて、バージョンとハッシュの数を読み、取得したバージョンを制約として固定して、constraints行ができることを確認し、インストールされたパッケージの実際のパスを探します。続けて、ロックファイルなしでプランを立ててみて、ミラーにないバージョンを固定して拒否されてみて、initなしでロックファイルだけを作り直してみます。最後の2つのステップでは、ハッシュをわざと壊して、インストールが止まるのを確認して復旧したあと、制約のないプロバイダーを見つけ出す点検スクリプトを、自分で作ります。