Help us understand the problem. What is going on with this article?

tfstateを分割管理するためのTips

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の場合、リソースの種類によりますが、idarnになります。
idarn以外のリソースの属性を利用する場合はデータソースを使って参照します。
データソースを経由することで、提供側と利用側の依存を減らし、利用側が常に最新の値を参照できるようになります。リモートステート経由で属性値を参照すると、ステートに記録された値と実際の値に差分がある場合に不整合が発生する可能性があります。

次のコードは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がある場合、愚直に書くと次のようになります。

state1/variables.tf
variable "environment" {
    default = "dev"
}

variable "variable1" {
    default = 1
}
state2/variables.tf
variable "environment" {
    default = "dev"
}

variable "variable2" {
    default = 2
}
state3/variables.tf
variable "environment" {
    default = "dev"
}

variable "variable1" {
    default = 3
}

このようなパターンでは、ディレクトリごとの環境変数を管理するdirenvで変数に設定する値をDRYにできます。

TerraformはプレフィックスがTF_VAR_の環境変数を変数への入力として扱います。
例えば、TF_VAR_environmentの値はvariable "environment" {}に設定されます。
この機能を利用し、direnvで変数へ設定する値を管理します。

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
state1/variables.tf
variable "environment" {}

variable "variable1" {}
state2/variables.tf
variable "environment" {}

variable "variable2" {}
state3/variables.tf
variable "environment" {}

variable "variable1" {}
.envrc
export TV_VAR_environment=dev # 共通の入力environmentを設定
state1/.envrc
source_up                     # 上位の.envrcを継承

export TV_VAR_variable1=1     # 個別の入力variable1を設定
state2/.envrc
source_up                     # 上位の.envrcを継承

export TV_VAR_variable2=2     # 個別の入力variable2を設定
state3/.envrc
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コマンドのデフォルトの引数を設定
  • リモートステート間の依存関係を定義
  • すべてのstateに対してplan, apply, destroyを実行
Why do not you register as a user and use Qiita more conveniently?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away