TL;DR
- Terraform では、コードを書き、
planで差分を確かめ、applyで適用するサイクルを回してインフラを構築する。 - 構成要素はモジュール、プロバイダ、ステートで、ワークスペースを使うと同じコードから複数の環境を作れる。
内容
インフラ構築のサイクル
Terraform でのインフラ構築は、コーディング、動作確認、環境への適用、環境の破棄という流れで進む。
コーディングでは、まず provider ブロックを書く。Terraform は、書いたコードを各サービスの API やコマンドとして実行するためにプロバイダを使う。
provider "aws" {}次に terraform init を実行してプロバイダを取得し、設定ファイル(.tf)にリソースを書く。
resource "terraform_data" "sample" {
provisioner "local-exec" {
command = "echo \"Hello, Terraform\""
}
}ファイルの分け方や粒度に決まりはない。aws_instance だけをまとめるように Terraform のリソース単位で分けることも、ロードバランサ、ターゲットグループ、API サーバのリソースをまとめるように機能単位で分けることもある。terraform fmt でコードを整形し、terraform validate で構文を検査できる。tflint などのサードパーティ製ツールを使えば静的解析もできる。
動作確認では terraform plan で変更内容を確かめる。意図したリソースが作成・削除されるか、パラメータの変更が意図どおりかに特に注意する。
問題がなければ terraform apply で環境に適用する。plan と同じように変更内容が表示されるので、確認して yes を入力する。必要に応じて、Terratest などのツールで意図どおりの構成になったかを確かめるとよい。環境全体は terraform destroy で破棄できる。
モジュール
モジュールは、まとめて使うリソースの集まりで、コードの再利用性を高める。作ったモジュールは Terraform Registry に登録したり、GitHub などのバージョン管理システムに置いたりして配布できる。ローカルのモジュールは source にディレクトリを指定して読み込む。
module "sample" {
source = "./モジュールの配置ディレクトリ"
変数名 = 値
}モジュール内で作った値は output で外に渡し、module.<モジュール名>.<output 名> で参照する。
locals {
output_value = module.sample.output_value
}モジュールの設計
モジュールを使うと、特定のアーキテクチャパターンやクラウドサービスの集まりを 1 つの単位として扱えるので、同じ構成を複製したり、サービスごとに分割したりできる。最適な構成を考えるときは、マイクロサービスの考え方が参考になる。
リソース間の依存は、パラメータの受け渡しによって暗黙に表される(depends_on で明示もできる)。受け渡しには、モジュール内部でのものとモジュール間でのものがある。Terraform では、モジュール同士を疎結合にして独立性を高めるために、モジュール間の受け渡しをできるだけ減らすことが大事である。output の数が増えてきたら、モジュールの単位を見直したり統合したりしたほうがよい。
分割の粒度も、受け渡すパラメータが少なくなるように決める。たとえば LB、Fargate、DB(RDS)からなる構成では、LB と Fargate の間で多くのパラメータを受け渡すので、3 つを別々のモジュールにするより、LB と Fargate をまとめ、DB を別のモジュールにするほうがよい。
プロバイダ
主要なクラウドやそのサービスの機能は、プロバイダとして実装されている。多くは Terraform Registry に登録されている。プロバイダを明示しなくても、リソースの種類から必要なプロバイダが読み込まれる。たとえば aws_vpc を書けば AWS プロバイダが使われ、そのとき AWS CLI の default プロファイルの設定が使われる。
ステート
ステートは、terraform apply で構築したインフラを管理するファイルである。apply を実行すると、Terraform はステートに記録されたリソースの状態と .tf ファイルの内容との差分を埋めるようにリソースを操作する。デフォルトではローカルに保存される。
terraform state で直接操作することもできるが、基本は plan と apply で操作する。他で管理しているリソースを取り込むなど、コードを大きく変えるときだけ、terraform state rm と terraform import の組み合わせや terraform state mv を使う。
ワークスペース
ワークスペースは、ステートを分けて、リソースを独立して管理する仕組みである。何も指定しなければ default ワークスペースが使われる。主なコマンドは次のとおり。
terraform workspace new <ワークスペース名>: 作成terraform workspace show: 現在のワークスペースを表示terraform workspace list: 一覧を表示terraform workspace select: 切り替えterraform workspace delete <ワークスペース名>: 削除
最大のメリットは、同じコードで開発、ステージング、本番などの複数の環境を立ち上げられることである。terraform.workspace を使えば、環境ごとに値を変えられる。
resource "aws_instance" "sample_instance" {
ami = local.instance_setting[terraform.workspace].ami
instance_type = local.instance_setting[terraform.workspace].type
tags = {
Name = "${terraform.workspace}-instance"
Env = terraform.workspace
}
}
locals {
instance_setting = {
stg = {
type = "t3.medium"
ami = "ami-xxxxxxxxxxxxxxxxx"
}
prod = {
type = "r5.large"
ami = "ami-yyyyyyyyyyyyyyyyy"
}
}
}リソースへの権限を環境ごとに分けられるのも利点である。たとえば、ステージングは開発者も変更できるが本番は管理者だけ、という運用は S3 のポリシーで実現できる。
一方で、どの環境も同じ構成にする必要がある。環境ごとの違いを無理にロジックで吸収しようとすると、コードが複雑になりがちである。回避策は 2 つある。1 つはワークスペースを使わずにディレクトリを分けてコードを分けること、もう 1 つはワークスペース名に応じてリソースを作るかどうかを切り替えることである。
resource "何かしらのリソース" "リソース名" {
count = terraform.workspace == "dev" ? 0 : 1
}