🧬

TerraformのAWS Providerのバージョンアップで S3の破壊的変更と向き合った話

2022/08/05に公開

スターフェスティバル株式会社 の バックエンドエンジニアの @ikkitang です。

今回はタイトルの通り、Terraform のバージョンアップに伴って変更になった Terraform の新しい S3 の管理方法を既存のリポジトリに適用してみた話を書いてみたいと思います。意外と情報を集めるのに困ったりしたので、このドキュメントが誰かの役に立てば幸いです。

AWS Provider のバージョンアップと向き合う

Terraform で AWS のリソースを管理する際は AWS Providerを使用するかと思います。

弊社では執筆時現在、v3.x のバージョンが使われておりますが、AWS Provider のメジャーバージョンは v4.x がリリースされています。弊社のインフラチーム全体として v3.x の最新バージョンへの追従も中々実現出来ていなくて、バージョンアップの課題感を感じていた所でした。(執筆時には、v3 の最新まであげていたのですが、なんと、執筆中にインフラチームが一部の Terraform 環境の AWS Provider を v4.x 系へアップデートしていました!!w はやい!!)

バージョンアップを計画するにあたって v3.x から v4.x へアップデートする際の注意点としては公式からAWS Provider の v4 upgrade guide が提供されています。こちらを見ていくと v4.x 系では主に S3 を管理する方法について大きな破壊的な変更がリリースされました。

凄く短い例ですが変更例としてはこういったものです。

before

resource "aws_s3_bucket" "b" {
  bucket = "my-tf-test-bucket"

  versioning {
    enabled = true
  }
}

after

resource "aws_s3_bucket" "b" {
  bucket = "my-tf-test-bucket"
}

resource "aws_s3_bucket_versioning" "versioning_b" {
  bucket = aws_s3_bucket.b.id
  versioning_configuration {
    status = "Enabled"
  }
}

例からも想像付くように これまで aws_s3_bucket の引数で設定できていた項目が aws_s3_bucket_*** という形で別リソースとして定義する必要があるという事になりました。背景は こちらの aws_s3_bucket リソースのリファクタリング 記事を参考にして貰えればよいかとは思いますが、 "バケットの管理と運用に関連する多数の API をコールする必要があり aws_s3_bucket の負担があがっていた" みたいな課題感から分割に至ったとの事です。

さて、話が反れてしまいましたが、公式ドキュメントによって AWS Provider を v3 から v4 にアップグレードするためには、「Terraform 管理の S3 リソースをすべて新しい書き方にしないといけない」という課題がある事がわかりました。

可能であれば アップグレードはサクッと終わらせたい。とはいえ、弊社では複数の S3 バケットを利用されており またそれらが Terraform で管理されている状況でした。手数が多い作業になりそうという所で開発の手を止めずに作業をすすめるにはどうすべきか社内のインフラチームのメンバーに方針を相談した所、「v3.75 を使うと段階的に移行できる」という事を教えてもらえました。

そこで、v3.75 までまず上げて新しい S3 の書き方に移行してから v4 系をすすめるという方針と決めて、手を動かす事にしました。

AWS Provider v3.75 での S3 リソースの管理方法について

先程 v3.75 を使うと段階的に移行できる という話があるといいましたが、v3.74 と v3.75 の違いとしては、v4.x 系で導入された新しい S3 リソースの管理方法がバックポートされている点です。

更に、v4.x 系と v3.75 の違いは、v4.x 系では S3 の書き方は 新しい書き方しか受け入れない のに対して、v3.75 では「新しい管理方法でも従来の管理方法でもどちらでも記述する事ができる」 という特徴があります。これによって、段階的な移行手段を取る事ができます。

前置きが長くなりましたが、この記事では、v3.75 を採用して S3 の書き方を調整いく中でハマった所についてまとめていこうと思っています。

ちなみに、v4 系でも同じように v4.9.0 ではエラーにならないようになったらしいです。参考: 【Terraform】AWS Provider v4.9.0 の S3 リファクタリングの挙動を試してみた

エラーにならなくなった経緯は issue で結構な議論があった末でのアップデートがあったみたいです。議論面白いので https://github.com/hashicorp/terraform-provider-aws/issues/23106 をご覧になってみるといいかもしれません。 「そうは言っても S3 なんて AWS 使ってたら 100%使うサービスなのに (過言) import 作業辛すぎるのでどうにかならないか?」みたいな事書いてありました。(笑)

v3.75 における S3 管理の注意点

という事で、v3.75 で aws_s3_bucket の引数で設定していた項目を aws_s3_bucket_*** に変換する作業を行っていたのですが、以下のように不思議な挙動をする事がありました。

before

resource "aws_s3_bucket" "b" {
  bucket = "s3-website-test.hashicorp.com"
  acl    = "public-read"

  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["PUT", "POST"]
    allowed_origins = ["https://s3-website-test.hashicorp.com"]
    expose_headers  = ["ETag"]
    max_age_seconds = 3000
  }
}

after

resource "aws_s3_bucket" "b" {
  bucket = "s3-website-test.hashicorp.com"
}

resource "aws_s3_bucket_cors_configuration" "cors_b" {
  bucket = aws_s3_bucket.example.bucket

  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["PUT", "POST"]
    allowed_origins = ["https://s3-website-test.hashicorp.com"]
    expose_headers  = ["ETag"]
    max_age_seconds = 3000
  }
}

コードを上記のように書いた所で terraform state rm aws_s3_bucket.b して aws_s3_bucket.baws_s3_bucket_cors_configuration.cors_b を再度 import し直して、terraform importを叩いた所、以下のように aws_s3_bucket.b から cors_rule が消えるという差分が出てしまう事がわかりました。

  # aws_s3_bucket.b will be updated in-place
  ~ resource "aws_s3_bucket" "b" {
        id                          = "s3-website-test.hashicorp.com"

      - cors_rule {
          - allowed_headers = [
              - "*",
            ] -> null
          - allowed_methods = [
              - "PUT",
              - "POST",
            ] -> null
          - allowed_origins = [
              - "https://s3-website-test.hashicorp.com",
            ] -> null
          - expose_headers  = [
            - "ETag"
          ] -> null
          - max_age_seconds = 3000 -> null
        }
    }

これは実際に apply すると消えてしまうのですが、v3.75 系の注意点として aws_s3_bucket を分割した場合に ignore を指定する必要があります。 これは v4.x 系では不要なのですが、v3.x 系では 旧式の書き方と新しい書き方の両方ができるがゆえの対応のようでした。( 単純に aws_s3_bucket_cors_configuration をググっても最新の AWS Provider のドキュメントが検索される為、そこには書いて無かったです。 https://registry.terraform.io/providers/hashicorp/aws/3.75.0/docs/resources/s3_bucket をご覧になるのをおすすめします!)

例えば、上記のコードでは、 after の部分を以下のようにする必要があります。 lifecycle の部分がそうです。

resource "aws_s3_bucket" "b" {
  bucket = "s3-website-test.hashicorp.com"

  lifecycle {
    ignore_changes = [
      cors_rule
    ]
  }
}

resource "aws_s3_bucket_cors_configuration" "cors_b" {
  bucket = aws_s3_bucket.example.bucket

  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["PUT", "POST"]
    allowed_origins = ["https://s3-website-test.hashicorp.com"]
    expose_headers  = ["ETag"]
    max_age_seconds = 3000
  }
}

ignore 対応表

という事で僕調べですが、 ignore の対応表を書いておきます。
無い場合は、3.75.2 の aws_s3_bucket のドキュメント から見てみてください。

resource ignore
aws_s3_bucket_acl grant
aws_s3_bucket_cors_configuration cors_rule
aws_s3_bucket_lifecycle_configuration lifecycle_rule
aws_s3_bucket_logging logging
aws_s3_bucket_object_lock_configuration object_lock_configuration[N].rule
aws_s3_bucket_replication_configuration replication_configuration
aws_s3_bucket_server_side_encryption_configuration server_side_encryption_configuration
aws_s3_bucket_website_configuration website

まとめ

  • Terraform の AWS Provider v4.x 系 では S3 の管理方法に大幅な破壊的アップデートが入った
  • v3.75 を使う事で 従来の書き方 もしくは v4.x 系で採用された新しい書き方の両方で対応する事ができる為に段階的に移行する事ができる
  • ただし、v3.75 のバージョンアップして、従来の書き方から新しい書き方に変更した時は aws_s3_bucket 側で分割したプロパティに対して ignore する対応を入れないと、AWS 側のリソースが消えてしまうので注意が必要

弊社採用頑張っています

https://stafes.notion.site/stafes/d0996a00d77d418280982797c7e16001

最近、幸いな事に採用によりエンジニアの方が沢山入ってくださっています。
直近でもインフラエンジニアの方がジョインしてくださって、インフラ領域がちゃんと複数名のチームとして発足されて、色々手を伸ばせなかった事がどんどん進んでいます。今回の Terraform のバージョンアップ対応もその一つです。

どんな事やってるの?という方向けに カジュアル面談もさせて頂いていて、 @ikkitang 宛に DM とかでご連絡頂ければ対応させてもらいますので、是非とも!

カジュアル面談前に雰囲気を掴んでみたい、という方には こちらの スタフェス公式 Note とかで プレイヤーレベルの方のインタビューや経営層の方のインタビューなどが掲載されているので見て頂くと良いかもです。

スタフェステックブログ

Discussion