🐈

terraform を 0.11 から 1.5.3 までアップグレードした

2023/09/11に公開

先日、しばらく放置されていたプロジェクトで使用している terraform のアップグレードを 0.11 から 1.5.3 まで行いました。基本的には公式のアップグレードガイドで詳細に説明されているので、これに従えばいいのですが、AWS provider のアップグレードなどもしないといけないのと、staging に加えて本番環境のアップグレードも並行してやる必要があるので、そのときにやったことを書きます。

terraform コマンドのバージョン管理

まず、アップグレードしていくためには頻繁に terraform のバージョンを変えるので、通常は tfenv を使ってバージョンを簡単に切り替えられるようにします。しかし、0.11 を入れようとしたらエラーになってしまいました。エラーの原因を詳しく調査はしていないのですが、おそらく Apple Silicon環境(M2 MacBook Pro)なのが原因で、x86環境でしか動作しないのだと思います。

しかたないので、terraform は公式が提供している Docker image を使うことにしました。terraform のバージョンは tag を変えるだけなので、切り替えは楽です。また、x86環境で動かすためには Rosseta2 が必要なので、あらかじめインストールしておきます。

ここで、いちいち docker run でいろいろオプションを付与しつつ terraform コマンドを実行するのは面倒なので、docker run のラッパースクリプトを作成します。以下の内容を terraform という名前で作成して、実行権限を付与します。

bin/terraform:

#!/bin/bash
ROOT_DIR=$(cd $(dirname $0); pwd -P | xargs dirname)

exec docker run \
  -it \
  --rm \
  --workdir $(pwd -P) \
  -v "${ROOT_DIR}:${ROOT_DIR}" \
  -v "${AWS_CONFIG_FILE}:/root/.aws/config" \
  -v "${AWS_SHARED_CREDENTIALS_FILE}:/root/.aws/credentials" \
  -e "AWS_CONFIG_FILE=/root/.aws/config" \
  -e "AWS_SHARED_CREDENTIALS_FILE=/root/.aws/credentials" \
  -e "AWS_PROFILE=${AWS_PROFILE}" \
  --platform linux/amd64 \
  my-project-terraform $*

このとき、プロジェクトルートのディレクトリをホスト側と同じパスでマウントして、ワーキングディレクトリを現在いるディレクトリに設定することで、通常のterraform コマンドと同じように使えます。

また、AWSのクレデンシャルファイルもマウントし、環境変数にパスを設定しておくことで、AWS provider でクレデンシャルの設定をしなくてもよくなるのでそうします。

あとは他の provider で使用するモジュールなどがあれば、terraformの Docker image を直接指定せずに、Dockerfile を作り、必要なモジュールをインストールしておきます。Docker image をビルドするのには docker build -t <image-name> コマンドで行いますが、都度入力するのも面倒なので、これもラッパースクリプトをbuild-imageという名前で作成し、実行権限を付与しておきます。

Dockerfile:

FROM --platform=linux/amd64 hashicorp/terraform:1.5.3

RUN apk update && apk add ruby ruby-json ansible

bin/build-image:

#!/bin/bash
ROOT_DIR=$(cd $(dirname $0); pwd -P | xargs dirname)

cd $ROOT_DIR
docker build -t my-project-terraform .

各種環境変数は、.envrc に記述しておき、direnv でプロジェクトディレクトリに移動したときに自動で読み込むようにしておきます。ついでに、ラッパースクリプトがあるディレクトリに PATH を通しておくと便利なのでそうします。

.envrc:

export AWS_CONFIG_FILE=${AWS_CONFIG_FILE:-$HOME/.aws/config}
export AWS_SHARED_CREDENTIALS_FILE=${AWS_SHARED_CREDENTIALS_FILE:-$HOME/.aws/credentials}
export AWS_PROFILE=profile-name

PATH_add bin

バージョンを変えるときは、Dockerfile の terraform Docker image のバージョンを変更して、build-image を実行すればOKです。

アップグレード作業の方針

基本的には、公式のアップグレードガイドが各バージョンごとに用意されているので、それに従います。

Upgrading to Terraform v1.5 | Terraform | HashiCorp Developer

しかし、実際にはTerraform本体だけではなく、AWSなどの provider も並行してアップグレードする必要もあるのと、すでに稼働しているproduction環境に terraform apply をしていくのは怖いので、terraform apply ではくて terraform refresh でリモートステートを更新していくことで対応します。

また、0.14 にアップグレードした後は、一気に最新(1.5.x)までアップグレードしてもよいですが、AWSなどのproviderの更新などもあるので、少々面倒ですが、マイナーバージョンごとにアップグレードしていくのが無難なので、そうします。

アップグレードする際は 0.11 -> 0.120.12 -> 0.13 が破壊的変更が多いので少々大変ですが、専用のコマンドが用意されているのでそれを使います。

Terraform 本体だけではなく AWS providerなども古いと、新機能に対応していないために誤ったdiffを表示してきたりするので、terraform apply はしないで terraform refresh をしてリモートステートのみを更新していきます。そのため、基本的には以下のフローを繰り返していきます。

# 1. terraform バージョンアップ

# 2. 初期化
terraform init -reconfigure

# 3. うまく動作するか確認
terraform plan

# 4. エラーが表示されれば修正

# 5. remote state を更新
terraform refresh

staging や production など複数の環境がある場合は、すべての環境について上記の手順をバージョンごとに行なっていきます。

  1. staging を 0.12 にアップグレード
  2. production を 0.12 にアップグレード
  3. staging を 0.13 にアップグレード
  4. production を 0.13 にアップグレード
  5. ...

アップグレード作業の詳細

細かくメモはしていないのですが、おおまかには以下のような感じでアップグレードしていきました。

  • terraform 0.11 -> 0.12
    • AWS provider を 1.60.0 -> 2.68 にアップグレード
    • terraform 0.12upgrade を実行することでもろもろ修正
    • versions.tf が追加された
  • terraform 0.12.29 -> 0.13.1
    • terraform 0.13upgrade を実行することでもろもろ修正
  • AWS provider 2.68 -> 3.x
    • いくつか修正
  • AWS provider 3.x -> 5.8.0
    • いくつか修正
  • terraform 0.13.1 -> 0.14.11
    • .terraform.lock.hcl が追加された
  • terraform 0.14.11 -> 0.15.5
    • とくに変更なし
  • terraform 0.15.5 -> 1.0.11
    • とくに変更なし
  • terraform 1.0.11 -> 1.1.9
    • とくに変更なし
  • terraform 1.1.9 -> 1.2.9
    • とくに変更なし
  • terraform 1.2.9 -> 1.3.9
    • とくに変更なし
  • terraform 1.3.9 -> 1.4.6
    • とくに変更なし
  • terraform 1.4.6 -> 1.5.3
    • とくに変更なし

terraform 0.11 -> 0.12

まず、terraform 0.12 にアップグレードしようとしたら、AWS provider が 1.60.0 のままだとエラーになってしまったので、2.68 にアップグレードしました。最新のバージョンでもだめなようです。

そして、公式アップグレードガイドに従って、terraform 0.12upgrade コマンドを実行して、tfファイル群を書き換えました。このバージョンからは versions.tf が追加され、ここにterraform のバージョンを記述するようです。

versions.tf:

terraform {
  required_version = ">= 0.12"
}

また、この時点では AWS provider が古くて、s3の暗号化関連に対応していないせいで、以下のようなdiffが出るのですが、一旦はこのままにします。

Terraform used the selected providers to generate the following execution plan. Resource actions are indicated with the following symbols:
  ~ update in-place

Terraform will perform the following actions:

  # aws_s3_bucket.test-bucket will be updated in-place
  ~ resource "aws_s3_bucket" "test-bucket" {
        id                          = "test-bucket-XXXXXXXXXX"
        tags                        = {}
        # (10 unchanged attributes hidden)

      - server_side_encryption_configuration {
          - rule {
              - bucket_key_enabled = false -> null

              - apply_server_side_encryption_by_default {
                  - sse_algorithm = "AES256" -> null
                }
            }
        }

        # (1 unchanged block hidden)
    }

Plan: 0 to add, 1 to change, 0 to destroy.

Terraformでs3暗号化の差分が出続ける

あとになって気が付いたのですが、2.70.2 までアップグレードすれば、s3の暗号化関連のdiffは出なくなるようです。

あとは細かなワーニング等を修正して、terraform refresh して完了です。

terraform 0.12 -> 0.13

公式アップグレードガイドに従って、terraform 0.13upgrade コマンドを実行して、tfファイル群を書き換えました。versions.tf には使用する provider も記述する必要があるようです。

versions.tf:

terraform {
-  required_version = ">= 0.12"
+  required_version = ">= 0.13"
+  required_providers {
+    aws = {
+      source = "hashicorp/aws"
+    }
+    external = {
+      source = "hashicorp/external"
+    }
+  }
}

AWS provider 2.68 -> 3.x

AWS provider を 3.x にアップグレードできそうだったのでしました。AWS provider の version 指定は versions.tf で記述しろと警告がでたのでそうします。また external provider も同様に修正します。

main.tf:

- provider "aws" {
-   version = "2.68"
- }

- provider "external" {
-   version = "< 2.1.0"
- }

versions.tf:

terraform {
  required_version = ">= 0.13"
  required_providers {
    aws = {
      source  = "hashicorp/aws"
+      version = "~> 3.0"
    }
    external = {
      source  = "hashicorp/external"
+      version = "< 2.1.0"
    }
  }
}

aws_s3_bucket のaclの設定は、aws_s3_bucket_acl で記述しろと警告されたので修正します。

s3.tf:

resource "aws_s3_bucket" "bucket" {
  bucket = "xxx-bucket"
- acl    = "private"
}

+ resource "aws_s3_bucket_acl" "bucket" {
+   bucket = "xxx-bucket"
+   acl    = "private"
+ }

AWS provider 3.x -> 5.8.0

AWS provider をこの時点での最新の 5.8.0 にアップグレードできそうだったのでしました。

aws_eipvpc = truedomain = "vpc" に修正しろと警告がでたので修正しました。

ec2.tf:

resource "aws_eip" "xxx" {
  instance = aws_instance.xxx.id
-  vpc      = true
+  domain = "vpc"

terraform 0.13.1 -> 0.14.11

terraform init -reconfigure して初期化したら .terraform.lock.hcl というファイルが生成されるようになりました。いわゆる lock ファイルで、使用している provider のバージョン情報が詳細に記述されているようです。

.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     = "5.8.0"
  constraints = "~> 5.0"
  hashes = [
    ...
  ]
}

provider "registry.terraform.io/hashicorp/external" {
  version     = "2.0.0"
  constraints = "< 2.1.0"
  hashes = [
    ...
  ]
}

provider "registry.terraform.io/hashicorp/template" {
  version = "2.2.0"
  hashes = [
    ...
  ]
}

terraform 0.14.11 -> 1.5.3

0.14 以降もマイナーバージョンごとにアップグレードしましたが、公式アップグレードガイドに示されているように、特になにも修正する必要はありませんでした。

  1. terraform 0.14.11 -> 0.15.5
  2. terraform 0.15.5 -> 1.0.11
  3. terraform 1.0.11 -> 1.1.9
  4. terraform 1.1.9 -> 1.2.9
  5. terraform 1.2.9 -> 1.3.9
  6. terraform 1.3.9 -> 1.4.6
  7. terraform 1.4.6 -> 1.5.3

最後に

以上で 0.11 -> 1.5.3 へのアップグレードが完了しましたが、基本的には公式アップグレードガイドで詳細に説明されているので、思ったよりは大変ではありませんでした。

ハートレイルズ

Discussion