Terraform

Terraformのみでtfstateをモジュールごとに分割して管理する

こちらの、Terragruntを使用したモジュール分割との比較記事となります。


本記事では、Terragruntを使用していないので、多少冗長な設定が増えていたり実行に手間がかかったりします。

ぜひTerragruntを使用した記事と比較しながら読んでみてください。

スポンサーリンク

前提

Terraformインストール方法

過去記事で紹介した、tfenvを使用してインストールします。

Terraformソースコード

Terraformモジュール化についての記事のコードを流用します。

完成版のソースコードはこちらのGitHubにコミットしました。

Terragruntを使用したコードはこちらになります。

tfstate管理方法

今回はTerraform,Terragrunt以外の設定の手間を省くため、tfstateはローカルファイルでの管理とします。

実際の現場では、S3や、Terraform Cloudを利用しての運用になると思います。

本題

ディレクトリ・ファイルの構成

GitHubにコミットしているものをtreeコマンドで表示したものが以下になります。

実際にコミットしているファイルだけでなくTerraform実行時に自動で作成されるディレクトリ・ファイルも含めて記載しました。

また environments/dev と environments/prod の内容は同じなのでGitHubにはdev配下にのみファイルを用意しました。

$ tree -a terraform
terraform
├── environments
│   ├── dev           ・・・ 開発環境ディレクトリ。この配下でTerraform を実行。
│   │   ├── .terraform-version ・・・ tfenv設定ファイル。Terraformバージョンを固定。
│   │   ├── hello-app-server ・・・ 便宜上、記事内ではリソースディレクトリと呼びます。
│   │   │   ├── .terraform ・・・ プロバイダなどの実行時に必要なデータ保存ディレクトリ。
│   │   │   │   │                terraform init時に自動生成。
│   │   │   │   ├── modules
│   │   │   │   │   └── modules.json
│   │   │   │   ├── providers
│   │   │   │   │   └── registry.terraform.io
│   │   │   │   │       └── hashicorp
│   │   │   │   │           └── aws
│   │   │   │   │               └── 3.58.0
│   │   │   │   │                   └── darwin_amd64
│   │   │   │   │                       └── terraform-provider-aws_v3.58.0_x5
│   │   │   │   └── terraform.tfstate
│   │   │   ├── .terraform.lock.hcl ・・・ プロバイダなどのチェックサムが記載。
│   │   │   │                             terraform init時に自動生成。コミットを推奨。
│   │   │   ├── base.tf      
│   │   │   ├── main.tf
│   │   │   ├── outputs.tf
│   │   │   ├── test-key
│   │   │   ├── test-key.pub
│   │   │   └── variables.tf
│   │   ├── network
│   │   │   ├── .terraform
│   │   │   │   └── ...
│   │   │   ├── .terraform.lock.hcl
│   │   │   ├── base.tf
│   │   │   ├── main.tf
│   │   │   └── outputs.tf
│   │   ├── security
│   │   │   │   └── ...
│   │   │   ├── .terraform.lock.hcl
│   │   │   ├── base.tf
│   │   │   ├── main.tf
│   │   │   ├── outputs.tf
│   │   │   └── variables.tf
│   │   └── tfstate.d           ・・・ tfstate保存ディレクトリ。
│   │       ├── .gitkeep
│   │       ├── hello-app-server       
│   │       │   └── terraform.tfstate
│   │       ├── network
│   │       │   └── terraform.tfstate
│   │       └── security
│   │           └── terraform.tfstate
│   └── prod               ・・・ 本番環境ディレクトリ。dev配下と同じファイルを配置。
│       └── .gitkeep
└── modules                ・・・ モジュール本体。機能ごとにそれぞれ分けたもの。
    ├── hello-app-server
    │   ├── main.tf
    │   ├── outputs.tf
    │   └── variables.tf
    ├── network
    │   ├── main.tf
    │   └── outputs.tf
    └── security
        ├── main.tf
        ├── outputs.tf
        └── variables.tf
$

environments/dev配下の主要ファイル説明

modules配下と同じ粒度で、network、security、hello-app-serverをディレクトリを作成しています。便宜上、本記事内では、これらをリソース、リソースディレクトリと呼称します。

この単位でtfstateが分割されますが、分割のデメリットの1つとして、1部分だけ設定が異なるが、ほぼ同じ内容の設定ファイルが複数必要になってきます。

.terraform-version

こちらの記事で紹介したtfenvのバージョン固定用ファイルです。

base.tf

Terraformのベース部分となる基本的な設定をまとめました。(ファイル名は任意です)

networkでは以下のようなっています。

terraform {
  required_version = "1.0.6"
  backend "local" {
    path = "../tfstate.d/network/terraform.tfstate"
  }
}

provider "aws" {
  region  = "ap-northeast-1"
  profile = "default"
}

tfstateはデフォルトではカレントディレクトリに作成されますが、今回はTerragruntとの比較のため1階層上のtfstate.dディレクトリ配下に作成します。

main.tf

モジュールを相対パスで記載します。

networkでは以下のようなっています。

module "network" {
  source = "../../../modules/network"
}


serurityやhello-app-serverのmain.tfでは、別のモジュールの出力値を受け取るために、data “terraform_remote_state” を指定します

以下例ではnetworkモジュールで出力した、vpc-id をsecurityリソースで受け取っています。

module "security" {
  source = "../../../modules/security"
  my-ip  = var.my-ip
  vpc-id = data.terraform_remote_state.network.outputs.vpc-id
}

data "terraform_remote_state" "network" {
    backend = "local"

    config = {
        path = "../tfstate.d/network/terraform.tfstate"
    }
}

outputs.tf

当該モジュールで作成したリソース情報を、別リソースに渡す場合に必要となります。

modulesディレクトリ配下の各outputs.tfで定義した変数名を更に、ここでoutputします。moduleのoutputと、リソースでのoutputの2段階必要になります

networkでは以下のようなっています。

output "vpc-id" {
  value = module.network.vpc-id
  # 以下の記述はNG
  #value = module.network.aws_vpc.test-vpc.id
}

output "subnet-east-id" {
  value = module.network.subnet-east-id
}

output "subnet-west-id" {
  value = module.network.subnet-west-id
}


以上、分割の上でのポイントでした。



Terraformの実行

この記事のようにtfstate分割していない場合は、1回のterraform applyの実行で全リソースが作成されましたが、分割した場合はそれぞれのリソースディレクトリで実行する必要があります

これも分割の1つのデメリットとなります。

事前準備

Githubからサンプルコードをダウンロードして、こちらの記事の通り、自分のIPアドレスへ書き換えます。

またEC2へ接続するためのssh鍵の作成も行います。

$ git clone git@github.com:zoo200/blog.git
$
$ cd manage-tfstate-per-module-only-with-terraform/environments/dev
$
## default のIPを自分のIPアドレスに変更
$ vi security/variables.tf
variable "my-ip" {
  type = string
  default = "192.168.1.1/32"
}
$ 
## ssh鍵を作成します
$ ssh-keygen -N "" -f hello-app-server/test-key
Generating public/private rsa key pair.
...
$
Terraform実行

実行ディレクトリの environments/dev 配下の各リソースディレクトリで、ディレクトリ移動 → terrafom init → terraform apply → yes の入力 をひたすら繰り返します。

## networkリソース作成
$ cd network
$ 
$ terraform init
..
$
$ terraform apply
...
  Enter a value: yes  ・・・ yesを入力
...
$
## securityリソース作成
$ cd ../security
$
$ terraform init
...
$
$ terraform apply
...
  Enter a value: yes  ・・・ yesを入力
...
$
## hello-app-serverリソース作成
$ cd ../hello-app-server
$
$ terraform init
...
$
$ terraform apply
...
  Enter a value: yes  ・・・ yesを入力
...
$

以上、Terragruntを使用した場合との違いを理解する助けになれば幸いです。

今回は以上です〜ノシ

参考

(*ゝω・)ノ ァリガトネー

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

関連書籍