tfstateを分割管理するためのTips
はじめに
この記事はterraform Advent Calendar 2019の4日目です。
Terraformのstateを分割すると享受できるメリットがある一方、stateを分割することで発生する課題もあります。そこで、Terraformのstateを分割管理する中で考慮したことをまとめます。
stateを分割する影響
メリット
- plan/applyが高速化する
- 各ステートの命名がシンプルになる(
this
を使った命名がしやすい)- 例えば
resource "aws_security_group" "this" {}
- 例えば
デメリット
- 管理するファイルが増える
- 同じような設定が増える
どのような単位でstateを分割するか
リソースのライフサイクルごとに分割すると運用が楽です。
例えば、作成後削除することのないネットワークやデータベースと、状況により台数が増減したりリソースを入れ替えるWebサーバーは分割します。
state間のデータ共有
別のstateに情報を提供するには、stateファイルをS3のようなリモートに保存し、提供する情報をoutput
として出力します。
別ののstateの情報を利用する側は、terraform_remote_state
のデータソースを使い、output
で出力された情報を参照します。
terraform {
backend "s3" {
bucket = "bucket"
key = "network.tfstate"
}
}
resource "aws_subnet" "this" {
vpc_id = aws_vpc.this.id
availability_zone = var.availability_zone
cidr_block = var.cidr_block
}
output "subnet_id" {
value = aws_subnet.this.id
}
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "bucket"
key = "network.tfstate"
}
}
resource "aws_instance" "this" {
subnet_id = data.terraform_remote_state.network.outputs.subnet_id
}
state間で共有する情報は、極力リソースを特定する最小限の情報にします。AWSの場合、リソースの種類によりますが、id
やarn
になります。
id
やarn
以外のリソースの属性を利用する場合はデータソースを使って参照します。
データソースを経由することで、提供側と利用側の依存を減らし、利用側が常に最新の値を参照できるようになります。リモートステート経由で属性値を参照すると、ステートに記録された値と実際の値に差分がある場合に不整合が発生する可能性があります。
次のコードはaws_subnet
のデータソースを利用してサブネットの属性値のcidr_block
を参照する例です。
data "terraform_remote_state" "network" {
backend = "s3"
config = {
bucket = "bucket"
key = "network.tfstate"
}
}
data "aws_subnet" "this" {
subnet_id = data.terraform_remote_state.network.outputs.subnet_id
}
resource "aws_security_group" "this" {
vpc_id = data.aws_subnet.this.vpc_id
ingress {
cidr_blocks = [data.aws_subnet.this.cidr_block]
from_port = 80
to_port = 80
protocol = "tcp"
}
}
コードをDRYにする
共通の変数をDRYに
├── state1
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── state2
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── state3
├── main.tf
├── outputs.tf
└── variables.tf
上記のようなプロジェクト構造で共通の変数environment
がある場合、愚直に書くと次のようになります。
variable "environment" {
default = "dev"
}
variable "variable1" {
default = 1
}
variable "environment" {
default = "dev"
}
variable "variable2" {
default = 2
}
variable "environment" {
default = "dev"
}
variable "variable1" {
default = 3
}
このようなパターンでは、ディレクトリごとの環境変数を管理するdirenvで変数に設定する値をDRYにできます。
TerraformはプレフィックスがTF_VAR_の環境変数を変数への入力として扱います。
例えば、TF_VAR_environment
の値はvariable "environment" {}
に設定されます。
この機能を利用し、direnvで変数へ設定する値を管理します。
├── .envrc # 共通の環境変数を設定
├── state1
│ ├── .envrc # state1固有の環境変数を設定
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
├── state2
│ ├── .envrc # state2固有の環境変数を設定
│ ├── main.tf
│ ├── outputs.tf
│ └── variables.tf
└── state3
├── .envrc # state3固有の環境変数を設定
├── main.tf
├── outputs.tf
└── variables.tf
variable "environment" {}
variable "variable1" {}
variable "environment" {}
variable "variable2" {}
variable "environment" {}
variable "variable1" {}
export TV_VAR_environment=dev # 共通の入力environmentを設定
source_up # 上位の.envrcを継承
export TV_VAR_variable1=1 # 個別の入力variable1を設定
source_up # 上位の.envrcを継承
export TV_VAR_variable2=2 # 個別の入力variable2を設定
source_up # 上位の.envrcを継承
export TV_VAR_variable3=3 # 個別の入力variable3を設定
direnvで設定する環境変数は.envrcに設定します。上位のディレクトリの.envrcを継承する場合はsource_up
を設定します。
変数以外もDRYに
最新のTerraform v0.12ではterraform
ブロックの中で動的な値を利用する手段は提供されていません。
そのため、各stateでリモートステートを保存するkeyだけ変えれば良くても、次のようにterraform
ブロックをすべて設定する必要があります。
terraform {
backend "s3" {
bucket = "bucket"
key = "path/to/terraform.tfstate"
region = "ap-notheast-1"
encrypt = true
}
}
このようなTerraform自体では提供されていない機能をカバーするラッパーとしてTerragruntがあります。
Terragruntの使い方自体は公式のドキュメントに譲りますが、次のような機能が利用できます。
- リモートステートの設定をテンプレート化
- terraformコマンドのデフォルトの引数を設定
- 例えば
plan
コマンドにデフォルトで--parallelism
オプションを設定して高速化
- 例えば
- リモートステート間の依存関係を定義
- すべてのstateに対して
plan
,apply
,destroy
を実行