理系学生日記

おまえはいつまで学生気分なのか

.terraform.hcl.lockでChecksumエラーが発生する問題と対策

Terraformをローカルで実行するときには正常でも、いざGitHub ActionsやGitLab CI/CDでTerraformを実行すると以下のようなエラーと遭遇するケースがあります。

│ Error: Failed to install provider
│ 
│ Error while installing hashicorp/aws v3.68.0: the current package for
│ registry.terraform.io/hashicorp/aws 3.68.0 doesn't match any of the
│ checksums previously recorded in the dependency lock file

この理由が一体なんなのか、その回避策にどういうことがあるのかを記載します。

Terraformのロックファイル

他の多くの言語と同様に、Terraformにはロックファイルの仕組みがあります。

Terraformはこのロックファイル(.terraform.lock.hcl)に記述されているProviderのハッシュ値と、initによって取得したProviderのハッシュ値を比較・検証します。

$ cat .terraform.lock.hcl
# This file is maintained automatically by "terraform init".
# Manual edits may be lost in future updates.

provider "registry.terraform.io/hashicorp/aws" {
  version     = "3.70.0"
  constraints = "3.70.0"
  hashes = [
    "h1:E5IKHXzPGGSizZM5rHKzNCzpwQ7lWPXmmJnms82uzDk=",
    "h1:jn4ImGMZJ9rQdaVSbcCBqUqnhRSpyaM1DivqaNuP+eg=",
  ]
}

ハッシュ値の種類

曲者はここで記録されるハッシュ値に種類があることです。 歴史的経緯から、Terraformが記録するハッシュ値には以下の2種類があります。

  • zh:: Terraform Providerのzip形式のファイルに対するHashing Schemeです。現状はレガシーな位置付けです。Providerに関してはTerraform Provider Registry Protocolでzip形式での配布を前提としているため、それにハッシュ値が付与されています。
  • h1:: 現在推奨されるHashing Schemeです。zip形式ではなく、その「内容」に対するハッシュ値を表現します。

h1:の方が推奨される理由は、例えばファイルシステム上に「展開」されたProviderからもハッシュ値が計算できることです。 これにより、都度Terraform Provider Registryに問い合わせせずとも、ハッシュ値の計算・検証ができることになります。

詳細についてはNew provider package checksumsを参照。

何が問題になるのか

何が問題になるのかというと、h1:のHashing Schemeの場合、「利用しているプラットフォーム」に対するハッシュ値しか.terraform.lock.hclに記録されないことがあることです。

これは考えてみると当たり前ですね。Macのローカルファイルシステムからハッシュ値を計算・記録する場合、(どこかに問い合わせない限り)LinuxやWindowsのハッシュ値は計算できません。 これが生じるケースとしては、例えばplugin_cache_dir利用やTF_PLUGIN_CACHE_DIR環境変数等でProviderのキャッシュを使っているとき等です。

$ cat ~/.terraformrc
plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"

結果として、Macで.terraform.lock.hclを生成するとMac用のTerraform Providerのハッシュ値しか記録されません。 その後、Linux等の別のプラットフォームでterraform initを実行すると、ハッシュ値不正で冒頭のエラーになってしまします。 例えば、GitHub Actions等でLinuxをベースとして使っているとき等ですね。

解決策1: 全プラットフォーム用のハッシュ値を記録しておく

zh:のHashing Schemeであれば全プラットフォーム分のハッシュ値が記録できるので、そちらの使用を強制する方法があります。 例えば以下のような方法です。

  • TF_PLUGIN_CACHE_DIR等のキャッシュを無効にする
  • terraform providers lockコマンドによって、Providerのハッシュ値をロックファイルに記録する

問題は、結局のところ時間がかかること。 毎回Registryへ問い合わせするので、それなりの時間がかかります。

$ time terraform providers lock -platform=darwin_amd64 -platform=linux_amd64
- Fetching hashicorp/aws 3.70.0 for darwin_amd64...
- Obtained hashicorp/aws checksums for darwin_amd64 (signed by HashiCorp)
- Fetching hashicorp/aws 3.70.0 for linux_amd64...
- Obtained hashicorp/aws checksums for linux_amd64 (signed by HashiCorp)

Success! Terraform has updated the lock file.

Review the changes in .terraform.lock.hcl and then commit to your
version control system to retain the new checksums.

terraform providers lock -platform=darwin_amd64 -platform=linux_amd64  10.13s user 4.17s system 29% cpu 49.158 total

Terraformで環境を管理する場合はルートモジュールが多くなるため、そのモジュールごとにこれだけの時間がかかると、全体としては多大な時間が犠牲になってしまいます。

解決策2: Registryのローカルミラーを構築し、そこから必要なハッシュ値を取得する

解決策1の問題は、都度Registryへの問い合わせが走ることでした。そうであれば、Registryをローカルに構築すれば良いことになります。 それをおこなってくれるのがterraform providers mirrorです。

  1. terraform providers mirrorでローカルミラーを構成
  2. terraform providers lock-fs-mirror-platformオプションを用い、必要なハッシュ値をローカルで計算する

上記手順で、高速に「必要なハッシュ値を持つ.terraform.lock.hcl」を構築できます。 このあたりは以下のスライドのスクリプトをご参照ください。