はじめに
Terraformは本体やproviderなどバージョン管理の対象が複数存在し、
バージョンが変わることでコードが動作しなくなる可能性
や、想定外のエラー
が発生することあります。
そのため業務でTerraformを利用していると、バージョン管理は避けては通れません。
なので適切なバージョン管理を行い、各環境やチームでの一貫性を保つことは非常に重要ですが、
各担当者の知識のムラ
やプロジェクトの様々な歴史的背景
により、不完全な管理となっていることも珍しくありません…。
プロジェクトA: バージョン1.1.0のみ利用可能
プロジェクトB: バージョン1.5.0以上はすべて利用可能
プロジェクトC: バージョン1.8.0以上、1.9.0未満の範囲は利用可能
.
.
プロジェクトX: そもそもバージョンを制限してない
そこで本記事ではそんな困った場面に遭遇したTerraformユーザーに向けて、
バージョン管理
とバージョンを制限する方法
について解説します。
Terraformのバージョン管理について
Terraform本体のバージョン管理は開発環境の一貫性を保つために不可欠ですが、
前述の通りプロジェクトやチームごとにバージョンが異なる
のはよくあることで、頻繁にバージョンの切り替えが必要となります。
そのためもし特定のバージョンを直にインストールしているのであれば、
管理ツールを利用することを推奨します。
専用バージョン管理ツール(tfenv、tenv)
tfenvはTerraform専用のバージョン管理ツールで、
特定のプロジェクトやディレクトリに対応するTerraformのバージョンを簡単に切り替えられるのが特徴です。
後継としてtenvが提供されており、
OpenTofu、Terraform、Terragrunt、AtmosなどTerraform関連に特化した様々なツールのバージョン管理が可能です。
またGo言語で書き直されておりマルチプラットフォームで動くので、tenvをオススメ
します。
・Terraformに特化した管理機能
・.terraform-version、または.tfswitchrcを使用した自動切り替え
汎用バージョン管理ツール (asdf、mise)
asdfやmiseは複数のプログラミング言語やツールを一元管理できる汎用ツールで、Terraformのバージョン管理にも対応しています。
特に他の言語やツールを利用している場合は、同時にバージョン管理できるので便利です。
またmiseはasdfのRust版のような立ち位置であり、
互換性があり軽快に動くので特にこだわりがなければmiseをオススメ
します。
・複数ツールを一括管理可能
・様々なプラグインに対応しており拡張性が高い
・.tool-versionsファイルによるプロジェクトごとの設定
asdf、miseのコマンド一覧については以下記事にまとめているので、よければご参照ください。
バージョンを指定できる対象について
Terraformでバージョンを指定できる対象は、大きく分けて2種類あります。
terraform
Terraform本体のバージョンを指定することで環境ごとの互換性を保ち、意図しない動作やエラーを防ぐことができます。
特にメジャーバージョンやマイナーバージョンをまたぐと、
構文の変更
や新機能の追加
などコードの修正が必要となる場合があるので必ず指定
しましょう。
具体的にはterraformブロック
内のrequired_version
ブロックで指定が可能で、
以下の例であれば1.10.0
のバージョンの使用を強制します。
terraform {
required_version = "1.10.0"
}
provider
providerは特定のクラウドやサービスとのやり取りをするためのプラグイン
であり、
リソースの操作をTerraformから実行できるようにします。
providerが提供するリソースやデータソースの仕様はバージョンによって変わることがあるため、こちらも指定することが推奨されます。
具体的にはterraformブロック
内のrequired_providers
ブロックで指定が可能です。
以下の例であればawsでは5.80.0
のバージョンの使用を強制、
google cloudでは6.10.0
のバージョンの使用を強制します。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "5.80.0"
}
google = {
source = "hashicorp/google"
version = "6.10.0"
}
}
}
バージョンの指定方法について
Terraformではバージョンを柔軟に指定でき、用途に合った指定方法を選ぶ必要があります。
またセマンティック バージョニング
に則っており、バージョンをX.Y.Zの形式で表し、
Xはメジャーバージョン
、Yはマイナーバージョン
、Zはパッチバージョン
を意味します。
メジャーバージョン、マイナーバージョンはコードの修正を伴う大きな変更が発生する可能性があり、
パッチバージョンはバグ修正
などが主なため、基本的にコードへの影響は発生しません。
バージョンX.Y.Zのみ
特定のバージョンのみを使用する場合に指定します。
パッチバージョンも含め、完全に一致していることを強制
されるため最も厳しく制限できますが、他のmoduleの指定と競合
することが多いためほとんど利用されません。
terraform {
required_version = "1.5.0"
}
バージョンX.Y.Z以上
指定したバージョン以上を許可する場合に使用します。
許可される範囲が広くほとんど制限できないため、実際のプロダクトでは利用されない印象です。
terraform {
required_version = ">= 1.5.0"
}
バージョンX.Y.Z、およびそのマイナーバージョン
指定したパッチバージョン範囲のみ許可します。
パッチバージョンであればアップデートしてもまず影響はないため、
実際のプロダクトでは一番よく利用する指定方法です。
何らかの特別な理由がなければ、基本的にこの形式にしておけば問題ない
かと思います。
terraform {
required_version = "~> 1.5.0"
}
バージョンX.X.X以上、バージョンY.Y.Y未満または以下
範囲指定で特定のバージョン区間を許可する場合に使用します。
他のmoduleとの兼ね合いで、マイナーバージョンの範囲はある程度許可したい
場合などに利用します。
terraform {
required_version = ">= 1.5.0, < 1.10.0"
}
terraform {
required_version = ">= 1.5.0, <= 1.10.0"
}
バージョンを指定できる箇所について
Terraformではバージョンを指定できる箇所が複数存在し、それぞれ優先度
や適用範囲
が異なります。
今回は以下のディレクトリ構成を例に説明します。
.
├── exsamples
│ └── main.tf
└── modules
├── s3
│ └── main.tf
└── vpc
└── main.tf
なおterraformブロック
、providerブロック
は通常個別のファイル(provider.tf、versions.tf)などに切り出すことが多いですが、
本記事では他のリソースとまとめてmain.tfに記述します。
root module
root module(terraform initを実行するディレクトリ)では、
そのプロジェクト全体でのterraform、providerのバージョンを指定
できるため、必ず記述
しましょう。
後述のmoduleでバージョンが指定されていない場合は、root moduleの指定が適用されます。
terraform {
required_version = "~> 1.10.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.80.0"
}
}
}
provider "aws" {
region = "ap-northeast-1"
}
module "s3" {
source = "../modules/s3"
}
module "vpc" {
source = "../modules/vpc"
}
module(ローカル)
ローカルモジュールでバージョンを指定することで、再利用する際に意図しない挙動を防止できます。
そのため他プロジェクトと共通で利用するモジュール
などであれば、指定しておくのがbetterです。
terraform {
required_version = "~> 1.10.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 5.80.0"
}
}
}
resource "aws_s3_bucket" "example" {
bucket_prefix = "example-s3-bucket-"
}
module(リモート)
Terraform RegistryやGithubリポジトリから取得するリモートモジュールの場合も、バージョンを指定することで意図しない更新を避けられます。
ただしこのバージョンはリモートリポジトリのバージョン
のことであり、
terraform、providerのバージョンはmodule側の指定に委ねられる
ので、こちらも指定するのがbetterです。
具体的にはsourceで参照先のリポジトリ
、versionで参照先のリポジトリのタグ
を指定します。
以下であればterraform-aws-modules/vpc/awsの5.10.0
を指定しています。
module "vpc" {
source = "terraform-aws-modules/vpc/aws"
version = "5.10.0"
name = "example-vpc"
cidr = "10.0.0.0/16"
azs = ["ap-northeast-1a", "ap-northeast-1c", "ap-northeast-1d"]
private_subnets = ["10.0.1.0/24", "10.0.2.0/24", "10.0.3.0/24"]
public_subnets = ["10.0.101.0/24", "10.0.102.0/24", "10.0.103.0/24"]
}
参照しているリモートリポジトリ側のterraform、providerの指定は以下の通りです。
公式のリモートリポジトリは様々な利用者がいるため、互換性の問題が発生しないようにバージョン○○以上
といった緩い指定になっていることが多いです。
terraform {
required_version = ">= 1.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.46"
}
}
}
複数の箇所でバージョンを指定した場合の挙動について
required_versionやrequired_providersがroot module、複数のmoduleで指定されている
場合は全ての条件を満たすバージョン
が利用可能となります。
ただし、それぞれの指定が矛盾している場合はエラーが発生します。
例➀.複数の箇所でterraformバージョンが指定されている場合
以下であれば、1.4.0 以上、1.5.0 未満
が利用可能なTerraformのバージョンとなります。
terraform {
required_version = ">= 1.3.0, < 1.5.0"
}
terraform {
required_version = ">= 1.4.0, < 1.6.0"
}
以下のような場合は共通する範囲が無く矛盾しており、
root moduleのバージョン指定に合わせていますが、terraform init の段階でmodule側でエラーが発生します。
terraform {
required_version = ">= 1.3.0, < 1.5.0"
}
terraform {
required_version = ">= 1.6.0"
}
│ Error: Unsupported Terraform Core version
│
│ on ../modules/s3/main.tf line 2, in terraform:
│ 2: required_version = ">= 1.6.0"
│
│ Module module.s3 (from ../modules/s3) does not support Terraform version 1.4.0. To proceed, either choose another supported Terraform version or update this version constraint. Version constraints
│ are normally set for good reason, so updating the constraint may lead to other errors or unexpected behavior.
例➁.複数の箇所でproviderバージョンが指定されている場合
以下であれば、4.15.0 以上 4.20.0 未満
が利用可能なaws providerのバージョンとなります。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 4.10.0, < 4.20.0"
}
}
}
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 4.15.0, < 4.25.0"
}
}
}
以下のような場合は共通する範囲が無く矛盾しているため、terraform init の段階でエラーが発生します。
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 4.10.0, < 4.15.0"
}
}
}
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 4.16.0"
}
}
}
│ Error: Failed to query available provider packages
│
│ Could not retrieve the list of available versions for provider hashicorp/aws: no available releases match the given constraints >= 4.10.0, < 4.15.0, >= 4.16.0
例➂.複数の箇所でterraformバージョン、providerバージョンが指定されている場合
以下であれば1.4.0 以上 1.5.0 未満
が利用可能なTerraformのバージョン、
4.15.0 以上 4.20.0 未満
が利用可能なaws providerのバージョンとなります。
Root Module: >= 1.3.0, < 1.5.0
Module A : >= 1.4.0, < 1.6.0
Root Module: aws >= 4.10.0, < 4.20.0
Module A : aws >= 4.15.0, < 4.25.0
両方の条件が満たされる場合、Terraformは両方の条件を満たすバージョンのみを選択します。
両方の条件が矛盾する場合 いずれかの制約が満たされない場合、エラーとなります。
結局どのようにバージョンを指定すればよいのか…?
ここまでの説明が少し長くなってしまいましたが、最終的に以下のポイントを押さえてバージョンを指定すれば大きな問題が起きることはないでしょう。
- root moduleでは必ずバージョンを指定して、プロジェクト全体の一貫性を保つ
- 他プロジェクトと共有するmodule(ローカル)であればバージョンを指定する
- module(リモート)が特定のバージョンを要求する場合、root moduleと矛盾しない範囲でバージョンを指定する
Terraformのバージョン管理は初めは複雑に感じるかもしれませんが、適切な設定を行うことでその後のチーム開発や運用が格段に楽になります。
この記事が、皆様のTerraform Lifeを少しでも快適にするお役に立てれば幸いです。