🍛

Terraform Modules で trocco の国際化に対応する

2024/02/21に公開

trocco SRE の小川です。
1年前から手作りスパイスカレー沼にハマっていますが、最近懇意にしているバーのマスターにお裾分けしたところ好評だったようで、「昼間うちでカレー屋やっちゃいなよ」と提案いただきました。
エンジニアとカレー屋の二足の草鞋は履けるのでしょうか。
さて、今回はカレーにまつわるお話をしたいと思います。

🇮🇳 はじめに 🇮🇳

trocco が韓国に続き、インドへの進出を果たしました 🎉 ( プレスリリース )
trocco の基盤は AWS 上に構築されており Terraform で構成管理していますが、以前の管理方法ではプロダクトの国際化に基盤が出遅れることが予想されました。
というのも海外進出にあたり複製する必要のあるリソースもあり、現状のコードでは国際化した基盤の管理が厳しくなることが自明であったためです。
そこで、表題の通り Terraform Modules を利用したリファクタリングを行い、trocco の国際化に十分についていける基盤の構成管理を進めました。

厳しいところ

├── production
│   ├── aqua.yaml
│   ├── database.tf
│   ├── ecr.tf
...
│   ├── main.tf
│   ├── variables.tf
│   └── versions.tf
└── staging
    ├── aqua.yaml
    ├── database.tf
    ├── ecr.tf
...
    ├── main.tf
    ├── variables.tf
    └── versions.tf

ディレクトリ構成は以上の形をとっていました。
production/staging のような環境単位でディレクトリを作り、それぞれのディレクトリにリソースの定義を行なっていく形です。

staging で検証したリソースの定義を production にコピペした上で、パラメータを微調整して production へリソースを展開する運用でした。
この流れ自体は自然なものですが、構成管理ツールとしての Terraform の視点では、DRY でないコードの運用を強いるものでした。

ということで、trocco が海外進出を果たすおりには、新たにディレクトリを作成してコピペする作業を行うことが危惧されました。
当然海外リージョンにリソースを作成して終わりではなく、今後アップデートしていく必要があるため、毎度コピペするのはナンセンスですしミスの元にもなります。

どうリファクタしていくか

Terraform の利用の継続を軸にいくつか検討しました。
Terraform はすでに導入していて、tfaction を利用して CI/CD の仕組みを構築しており、それをなるべく流用したかったためです。
また Pulumi などの IaC を導入する際に、どの言語を採用するかの決定をする手間を省きたかったためです。

案1

王道?パターン。
結局これを採用しています。

├── envs
│   ├── ap-northeast-1
│   │   ├── production
│   │   │   └── main.tf
│   │   └── staging
│   │       └── main.tf
│   └── ap-south-1
│       └── production
└── modules
    ├── acm
    │   └── README.md
    ├── aurora
    │   └── README.md
...
    ├── sqs
    │   └── README.md
    └── ssm
        └── README.md

modules ディレクトリ以下に AWS リソース単位でモジュールを作成していきます。
envs の main.tf で modules "acm" {} のように呼び出して利用する構成です。

案2

1 より細かめにディレクトリを切っていく構成。

├── acm
│   ├── module
│   │   └── README.md
│   └── env
│       ├── ap-northeast-1
│       │   ├── production
│       │   └── staging
│       └── ap-south-1
│           └── production
├── aurora
│   ├── module
│   │   └── README.md
│   └── env
│       ├── ap-northeast-1
│       │   ├── production
│       │   └── staging
│       └── ap-south-1
│           └── production
...
└── ssm
    ├── module
    │   └── README.md
    └── env
        ├── ap-northeast-1
        │   ├── production
        │   └── staging
        └── ap-south-1
            └── production

それぞれのディレクトリの役割が明確で、ディレクトリ間で output の値を data.terraform_remote_state で受け渡しすることで依存関係の制御も可能となる構成です。
依存関係が明確なため、ディレクトリA -> ディレクトリB という依存関係のある修正を行う際に、ディレクトリAの PR を Apply し、その後ディレクトリBの Apply というように PR を細かくできるようなメリットもあります。
ただ、Apply のタイミング次第ではエラーになるケースがあったり、管理する state が増えるなどのデメリットもありそうな構成です。

案3 Terragrunt

Terragrunt への乗り換え案です。

ライセンス問題があったり、Terragrunt 自体のキャッチアップが必要など、限られた時間の中で移行することにリスクを感じたため見送りました。
また tfaction を継続して利用したかったこともあります。
ただ、依存関係をコントロールできるなど、Terraform のラッパーツールとして器用なことができそうな印象でした。

実際にリファクタしていく

tfmigrate を利用して、リファクタ前後の tfstate の整合性を担保しながらモジュール化を進めました。
詳細は後述しますが、モジュール化した定義と旧 Terraform が二重定義となってしまうのを回避するためです。

Terraform にはこれを避けるための import/moved/removed block という機能があります。
ただ trocco は tfaction を利用しており、tfaction で tfmigrate を使用すると、tfmigrate に差分があった場合に CI の結果をエラーに倒してくれます。
つまり既存リソースの migrate に不備があった際、何か破壊的な操作が行われることを PR の時点で回避可能になります。
そのため tfstate の調整に tfmigrate を使用しました。

既存 Terraform との共存

リファクタを進めていくにあたり、新しいモジュール化されたディレクトリと、古い Terraform を別に管理することも考えましたが、この方法は採用しませんでした。

├── production
│   ├── database.tf
│   └── ecr.tf
├── staging
│   ├── database.tf
│   └── ecr.tf
├── envs
│   ├── ap-northeast-1
│   │   ├── production
│   │   │   └── main.tf
│   │   └── staging
│   │       └── main.tf
│   └── ap-south-1
│       └── production
└── modules

少し分かりにくいですが上記のような構成とする形です。
production/staging から modules + envs にリソース定義を移し、production/staging から state rm を、modules + envs には import をし続ける方法です。
新旧が一目瞭然としてスッキリしますが、旧 Terraform へモジュール化以外の修正が入るたび、新しいディレクトリに import する必要が出てくるため複数人での開発に耐えられないと判断しました。

そのためディレクトリを分けることはせず以下のように共存させるようにして進めました。
移行が進んでいない定義がされたファイルはアンスコで始めるようにしました。
これにより tfstate を新旧で共有できるため、ファイル数が多くごちゃつく感じはありますが、あっちは rm したっけ?こっちは import したっけ?のような事故は減らせました。

├── envs
│   ├── ap-northeast-1
│   │   ├── production
│   │   │   ├── _database.tf
│   │   │   ├── _ecr.tf
│   │   │   └── main.tf
│   │   └── staging
│   │       ├── _database.tf
│   │       ├── _ecr.tf
│   │       └── main.tf
│   └── ap-south-1
│           └── main.tf
└── modules
    └── acm
        └── README.md

また、output を利用することで module.xxx.yyy のように参照可能ですが、旧 Terraform でこの値を参照する場合は module.xxx._yyy となるようにアンスコで output を定義するようにして混乱を減らすようにしました。

モジュールを作成する

例えば resource "aws_s3_bucket" "example" {} という定義をモジュール化する場合には以下のような修正を加えます。

# modules/s3/main.tf
resource "aws_s3_bucket" "example" {
  bucket = "my-tf-test-bucket"
}

# envs/ap-northeast-1/production/main.tf
module "s3" {
  source "../../../s3"
}

この状態だと、新旧でバケットの定義が重複するため旧 Terraform から S3 の定義を削除します。

# envs/ap-northeast-1/production/_s3.tf
- resource "aws_s3_bucket" "example" {
-   bucket = "my-tf-test-bucket"
- }

tfmigrate で tfstate を調整する

このまま Apply すると、aws_s3_bucket.example (旧 Terraform 側の)が削除され、module.aws_s3_bucket.example (モジュール化したもの)が新規作成となってしまいます。
つまり destroy -> create の形となってしまい、S3 が一時的に削除される恐れがあります。

migration "state" "migrate_s3" {
  actions = [
    "mv aws_s3_bucket.example module.aws_s3_bucket.example",
  ]
}

trocco では envs/ap-northeast-1/production/tfmigrate に上記のような migrate ファイルを配置して、Recreate のような挙動ではなく、terraform import による破壊的な作業がないようにしています。

ここまでで S3 のモジュール化が完了

あるリソースをモジュール化する手順は以上なので、あとはすでに定義されたリソース分だけこの作業を繰り返すことで、リファクタが完了することになります。
trocco では、モジュール化の必要性が少ないものや、そもそもリポジトリを変えた方が良いリソース定義(技術的負債のようなもの達)もあり、100% のモジュールへの移行はできておらず、まだアンスコがちらほらあります。

ここまでリファクタをすすめてみて

晴れて課題であった海外進出の厳しさを超克できました。
モジュール化を実践していなかった場合、多くのファイルをコピーし、resource ブロックのパラメータをちまちまいじって PR を作成することになっていました。
この作業自体はモジュール化を進めるよりは簡単ではありますが、今後も海外展開先が増えた場合に同じ作業をするのは骨が折れます。
また AWS リソースの修正や追加作業を行う時には、リージョン数分のリソース定義をしなければならないのか、という問題もあります。

モジュール化したことで、modules ディレクトリに resource を定義さえしてしまえば、envs ディレクトリ側では modules ブロックを追加するか、ブロックのパラメータを少しいじるだけで足りるようになりました。
当然、production/staging で定義が大きく異なるリソースがあったりして、モジュール化して共通化することが難しいものがありもしましたが、その困難を乗り越えるだけの価値があったと振り返って思います。

🇮🇳 We are hiring 🇮🇳

troccoを開発するprimeNumberでは、一緒に信頼性向上を実現してくれるSREを絶賛募集中です!
まずはカジュアル面談でお会いしましょう 🙏
データ統合自動化SaaS trocco®のSRE【資金調達総額14億円/2023年海外展開本格始動/導入数200社以上/リモート可】

株式会社primeNumber

Discussion