はじめに
MiiTel Analytics Platformチームの小門です。
RevCommではサービス基盤にAWSとして利用していますが、IaCには主にTerraformを用いています。
基本的にTerraformコードはGitHubで管理され、プルリクエストを介してCI/CDを自動実行してリソースの構築、構成変更を行います。
最近、Terraformコードを管理するリポジトリを新設したり既存のコードをリファクタリングする機会があったためナレッジを共有します。
この記事では、Terraform v1.5以降に導入された機能であるimport
/removed
/moved
ブロックを活用する方法を紹介します。
動機
IaCの活用度合いはサービスや会社、チームの状況に大きく左右されると思います。 必ずしもプロジェクトの初期からIaCが整備されるとは限らないし、サービスや組織の変化に応じてコード管理の都合も変わることでしょう(弊社が正にそうです)。
Terraformは機能が豊富なため上記のような事情に柔軟にアプローチできます。
例えば既存のリソースをIaC管理に取り込む場合はterraform import
、逆に特定のリソースをIaC管理から除外する場合はterraform state rm
などのコマンドがあります。
しかしIaCという特性上、手動コマンド実行によるtfstateを操作するのはチーム開発において不都合があります。 例えばチームの誰かによって同じタイミングでCI/CDが起動されると、お互いの変更が競合したりどちらかの変更が後勝ちするような可能性が考えられます。
このような場合でもTerraformの機能を有効活用することでチーム開発に支障をきたさずリファクタリングすることができます。
サンプルコード
AWSリソースを管理するための最小のサンプルとして、ECSクラスターを1つ管理するケースを考えてみます。
初めのディレクトリ構成:
. └── provider.tf
provider.tf
terraform { required_providers { aws = { source = "hashicorp/aws" version = "~> 5.0" } } } provider "aws" { region = "us-east-1" }
検証コードのバージョン
- Terraform v.1.9.8
- AWS provider: v5.78.0
import
ブロック
import
ブロックはTerraform v1.5以降で利用可能です。
terraform cliのterraform import [ADDR] [ID]
と等価です。
import { to = [ADDR] id = "[ID]" }
IaC管理されていないAWSリソースであるECSクラスターrevcomm-2024-adventcalendar
を新たにIaC管理に含めます。
※terraform cliだとterraform import aws_ecs_cluster.foo revcomm-2024-adventcalendar
# main.tf resource "aws_ecs_cluster" "foo" { name = "revcomm-2024-adventcalendar" } # import_ecs_cluster.tf import { to = aws_ecs_cluster.foo id = "revcomm-2024-adventcalendar" }
. ├── import_ecs_cluster.tf ├── main.tf └── provider.tf
※好みですが、import
ブロックはいずれ削除可能なためmain.tf
ではなく別ファイルにすることで後で丸ごと消せるようにしています。
この状態でterraform plan
を実行すると以下のようになります。
$ terraform plan ... Plan: 1 to import, 0 to add, 0 to change, 0 to destroy.
1 to import
かつ0 to add, 0 to change
のため新たにリソースは作成されず、また変更もされないことが分かります。
import
ブロックはコマンドの手動実行ではなくコード変更によってIaCの管理対象を操作することができます。
※以降のコードはterraform apply
を実行 && import_ecs_cluster.tf
を削除した状態とします。
removed
ブロック
removed
ブロックはTerraform v1.7以降で利用可能です。
その名の通りリソースをIaC管理から外す(リソース実体は削除しない)ためのもので、import
ブロックとは逆の用途です。
terraform cliのterraform state rm
と等価です。
terraform state rm [ADDR]
removed { from = [ADDR] lifecycle { destroy = false } }
lifecycleブロックでdestroy = false
としてリソースを削除しないようにできます。
# main.tf removed { from = aws_ecs_cluster.foo lifecycle { destroy = false } } # resource "aws_ecs_cluster" "foo" { # name = "revcomm-2024-adventcalendar" # }
また(IaC管理から)削除するリソースのTerraformコードも合わせて削除する必要があります。
removed
ブロックも後から削除可能なため、私のチームでは初めコメントアウトに留め、その後removed
ブロック自体の削除時にresourceブロックも削除する形で運用しています。
planの実行結果は以下のようになります。
$ terraform plan # aws_ecs_cluster.foo will no longer be managed by Terraform, but will not be destroyed # (destroy = false is set in the configuration) ... Plan: 0 to add, 0 to change, 0 to destroy.
0 to add, 0 to change, 0 to destroy
のため、実際のリソースに変更は起きません。
movedブロック
removed
ブロックはTerraform v1.7以降で利用可能です。
Terraformコード上の管理名(ADDR)をリネームする場合に使用します。
terraform cliのterraform state mv
と等価です。
terraform state rm [SOURCE] [DESTINATION]
moved { from = [SOURCE] to = [DESTINATION] }
上記までの例で、ECSクラスターrevcomm-2024-adventcalendar
のTerraformコード上の管理名を(敢えて)foo
としていました。
しかし、この命名では役割が分かりづらいためいずれ問題が起きることでしょう。
これをfoo
からapi
にリネームするリファクタリングを安全に行うことができます。
+ moved { + from = aws_ecs_cluster.foo + to = aws_ecs_cluster.api + } - resource "aws_ecs_cluster" "foo" { + resource "aws_ecs_cluster" "api" { name = "revcomm-2024-adventcalendar" }
planの実行結果は以下のようになります。
$ terraform plan # aws_ecs_cluster.foo has moved to aws_ecs_cluster.api ... Plan: 0 to add, 0 to change, 0 to destroy.
aws_ecs_cluster.foo
をaws_ecs_cluster.api
に変更しつつ、実際のリソースに影響がないため安全にリファクタリングを実行できます。
まとめ
Terraformのリファクタリングをチーム開発の中でも安全に実施する方法と簡単なケーススタディを紹介しました。
特にimport
ブロックとremoved
ブロックを併用することでリポジトリを跨いだリファクタリング、IaCコードの分割などが行えるのがとても気に入っています。