これは、なにをしたくて書いたもの?
Terragruntを学ぶシリーズ。
今回は、Terragruntを使って環境ごとに作るTerraformの構成ファイルを減らしてみます。
Terragruntで、環境ごとの構成ファイルを減らす
Terraformを使って、複数の環境を構築、管理するのにはいくつかの方法があります。
Terraform自身が提供するのは、Workspaceですね。
State: Workspaces - Terraform by HashiCorp
WorkspaceはStateを切り替えることができるもので、こちらとVariablesなどで環境差異を表現することになります。
他には、割り切って環境ごとにTerraform構成ファイルを独立して管理する方法もあります。この方法だと、ファイルのコピーが増えることに
なりますね。
ちょうど、今回使うTerragruntのドキュメントにも例があります。
Quick start / Promote immutable, versioned Terraform modules across environments
こういう感じのものです。環境ごとにファイルが分離されていますし、Stateもそれぞれ独立して管理されます。
├── prod │ ├── app │ │ ├── main.tf │ │ └── outputs.tf │ ├── mysql │ │ ├── main.tf │ │ └── outputs.tf │ └── vpc │ ├── main.tf │ └── outputs.tf ├── qa │ ├── app │ │ ├── main.tf │ │ └── outputs.tf │ ├── mysql │ │ ├── main.tf │ │ └── outputs.tf │ └── vpc │ ├── main.tf │ └── outputs.tf └── stage ├── app │ ├── main.tf │ └── outputs.tf ├── mysql │ ├── main.tf │ └── outputs.tf └── vpc ├── main.tf └── outputs.tf
Terragruntの場合では、環境ごとにファイルを独立させる考えを取りつつも、リソース定義はモジュール化して参照する方法を提示しています。
さらに、モジュールに対するVariableの設定をTerraformの構成ファイルなしで実現できます。
そのあたりが書かれているのが、こちらのドキュメントです。
Quick start / Promote immutable, versioned Terraform modules across environments
Terragruntを使うと、リソース定義をモジュールにまとめた上で、こういった形で.tf
ファイルなしで表現できます。
├── prod │ ├── app │ │ └── terragrunt.hcl │ ├── mysql │ │ └── terragrunt.hcl │ └── vpc │ └── terragrunt.hcl ├── qa │ ├── app │ │ └── terragrunt.hcl │ ├── mysql │ │ └── terragrunt.hcl │ └── vpc │ └── terragrunt.hcl └── stage ├── app │ └── terragrunt.hcl ├── mysql │ └── terragrunt.hcl └── vpc └── terragrunt.hcl
今回は、こちらの機能を見ていこうと思います。
環境
今回の環境は、こちらです。
$ terraform version Terraform v0.14.7 $ terragrunt -v terragrunt version v0.28.7
Terraform Providerは、MySQL用のものを使用します。
Provider: MySQL - Terraform by HashiCorp
使用するMySQLは8.0.23とし、172.17.0.2および172.17.0.3で動作しているものとします。
また、今回はGitリポジトリが必要になります。Gitリポジトリのソフトウェアはあまり関係ありませんが、GitLab 13.9を使います。
GitLabは192.168.0.3で動作しているものとします。
お題
MySQL Providerを使った、以下の2つのモジュールを定義します。
- データベース
- ユーザーおよび権限
これらを環境別のディレクトリから参照し、それぞれ使用するモジュールは同じ、構築する情報(=Variable)は異なる状態を実現してみます。
環境はdevelopment
とproduction
の2つとし、リソース構築先のMySQLはdevelopment
用を172.17.0.2、production
用を172.17.0.3とします。
また、今回はTerragruntを使わないパターンは作りません。長くなるので…。
では、始めていきましょう。
Terraformモジュールを作成する
今回は、Terraformモジュールがないと始まりません。
GitLab上にリポジトリを作成して(名前はmysql-terraform-module
にしました)、こちらのモジュールを登録します。
git clone
して
$ git clone http://192.168.0.3/kazuhira/mysql-terraform-module.git $ cd mysql-terraform-module
モジュール用のディレクトリを作成。
$ mkdir -p modules/{database,users}
以下のような構成にします。
$ tree . └── modules ├── database │ ├── main.tf │ ├── outputs.tf │ ├── variables.tf │ └── versions.tf └── users ├── main.tf ├── outputs.tf ├── variables.tf └── versions.tf 3 directories, 8 files
ファイルは、それぞれこんな感じです。
データベース用。
modules/database/main.tf
resource "mysql_database" "this" { name = var.database_name default_character_set = var.default_character_set default_collation = var.default_collation }
必須なのは、データベース名だけにしました。
modules/database/variables.tf
variable "database_name" { type = string } variable "default_character_set" { type = string default = "utf8mb4" } variable "default_collation" { type = string default = "utf8mb4_ja_0900_as_cs_ks" }
modules/database/outputs.tf
output "database_name" {
value = mysql_database.this.name
}
modules/database/versions.tf
terraform { required_version = ">= 0.14.7" required_providers { mysql = { source = "terraform-providers/mysql" version = ">= 1.9.0" } } }
ユーザーおよび権限用。
管理用のユーザーと、アプリケーションユーザーの2種類を作成します。
modules/users/main.tf
resource "mysql_user" "administrator" { user = var.administrator_username plaintext_password = var.administrator_password host = var.administrator_allow_host } resource "mysql_grant" "administrator" { user = mysql_user.administrator.user host = mysql_user.administrator.host database = var.database_name privileges = ["ALL"] } resource "mysql_user" "application_user" { user = var.application_user_username plaintext_password = var.application_user_password host = var.application_user_allow_host } resource "mysql_grant" "application_user" { user = mysql_user.application_user.user host = mysql_user.application_user.host database = var.database_name privileges = ["SELECT", "INSERT", "UPDATE", "DELETE"] }
必須のものは、データベース名と、各ユーザーのパスワードにしました。
modules/users/variables.tf
variable "database_name" { type = string } variable "administrator_username" { type = string default = "admin" } variable "administrator_password" { type = string } variable "administrator_allow_host" { type = string default = "%" } variable "application_user_username" { type = string default = "appuser" } variable "application_user_password" { type = string } variable "application_user_allow_host" { type = string default = "%" }
modules/users/outputs.tf
output "administrator_username" { value = mysql_user.administrator.user } output "administrator_password" { value = mysql_user.administrator.plaintext_password sensitive = true } output "administrator_allow_host" { value = mysql_user.administrator.host } output "application_user_username" { value = mysql_user.application_user.user } output "application_user_password" { value = mysql_user.application_user.plaintext_password sensitive = true } output "application_user_allow_host" { value = mysql_user.application_user.host }
modules/users/versions.tf
terraform { required_version = ">= 0.14.7" required_providers { mysql = { source = "terraform-providers/mysql" version = ">= 1.9.0" } } }
作成したモジュールを、Gitリポジトリに登録します。
$ git add modules $ git commit -m 'add, modules' $ git push origin master
タグもつけておきましょう。
$ git tag v0.0.1 $ git push origin v0.0.1
ここまでは、単にTerraformモジュールを作成しただけですね。
環境別にモジュールを利用し、かつTerraform構成ファイルは作成しない
ここから、Terragruntが出てきます。
まずは環境用のディレクトリと、各モジュールを使うためのディレクトリを作成しましょう。
$ mkdir -p environments/{development,production} $ mkdir -p environments/development/{database,users} $ mkdir -p environments/production/{database,users}
最終的に、できあがったディレクトリおよびファイルは、こんな感じです。いずれのディレクトリにもファイルはterragrunt.hcl
しか
存在しません。
$ tree environments environments ├── development │ ├── database │ │ └── terragrunt.hcl │ ├── terragrunt.hcl │ └── users │ └── terragrunt.hcl └── production ├── database │ └── terragrunt.hcl ├── terragrunt.hcl └── users └── terragrunt.hcl 6 directories, 6 files
development
と名付けた方の環境から見ていきましょう。
$ cd environments/development
このディレクトリにあるterragrunt.hcl
ファイルの中身は、こんな感じです。
terragrunt.hcl
generate "provider" { path = "provider.tf" if_exists = "overwrite_terragrunt" contents = <<EOF provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" } EOF }
Providerの定義をまとめる機能を使っています。データベース用、ユーザーおよび権限用のモジュールがありますが、接続先のMySQLサーバーは
どちらも同じなので。
Terragruntを使って、TerraformのProvider定義をまとめてみる - CLOVER🍀
データベース側に移動します。
$ cd database
こちらに配置しているterragrunt.hcl
の中身です。
terragrunt.hcl
include { path = find_in_parent_folders() } terraform { source = "git::http://192.168.0.3/kazuhira/mysql-terraform-module.git//modules/database?ref=v0.0.1" } inputs = { database_name = "development_database" }
Providerの定義を参照するために、上位ディレクトリにあるterragrunt.hcl
ファイルを参照するようにしています。
include { path = find_in_parent_folders() }
terraform
ブロックのsource
属性は、このディレクトリで使うモジュールを指定します。
terraform {
source = "git::http://192.168.0.3/kazuhira/mysql-terraform-module.git//modules/database?ref=v0.0.1"
}
Configuration Blocks and Attributes / terraform
記述方法自体は、通常のTerraformと同じですね。//
以下で、Gitリポジトリ内のモジュールを指定しています。タグはref
で指定します。
Module Sources - Terraform by HashiCorp
モジュールに与えるVariableは、inputs
で指定します。
inputs = {
database_name = "development_database"
}
Configuration Blocks and Attributes / inputs
ここまでで、このディレクトリについては準備ができたので、apply
してみましょう。
$ terragrunt apply
リソースができました。
mysql_database.this: Creating... mysql_database.this: Creation complete after 0s [id=development_database] Apply complete! Resources: 1 added, 0 changed, 0 destroyed. Outputs: database_name = "development_database"
ちなみに、この時のディレクトリ内のファイルやディレクトリは、このようになっています。
$ ll 合計 20 drwxrwxr-x 3 xxxxx xxxxx 4096 2月 24 00:05 ./ drwxrwxr-x 4 xxxxx xxxxx 4096 2月 23 22:05 ../ -rw-r--r-- 1 xxxxx xxxxx 1194 2月 24 00:05 .terraform.lock.hcl drwxrwxr-x 3 xxxxx xxxxx 4096 2月 24 00:05 .terragrunt-cache/ -rw-rw-r-- 1 xxxxx xxxxx 218 2月 23 23:15 terragrunt.hcl
続いて、ユーザーおよび権限側へ。
$ cd ../users
terragrunt.hcl
は、こちら。使い方自体は、データベース側と同じです。
terragrunt.hcl
include { path = find_in_parent_folders() } terraform { source = "git::http://192.168.0.3/kazuhira/mysql-terraform-module.git//modules/users?ref=v0.0.1" } inputs = { database_name = "development_database" administrator_password = "admin_password" application_user_password = "appuser_password" }
apply
しておきましょう。
$ terragrunt apply
リソースができました。
mysql_user.administrator: Creating... mysql_user.application_user: Creating... mysql_user.application_user: Creation complete after 0s [id=appuser@%] mysql_grant.application_user: Creating... mysql_user.administrator: Creation complete after 0s [id=admin@%] mysql_grant.administrator: Creating... mysql_grant.application_user: Creation complete after 0s [id=appuser@%:`development_database`] mysql_grant.administrator: Creation complete after 0s [id=admin@%:`development_database`] Apply complete! Resources: 4 added, 0 changed, 0 destroyed. Outputs: administrator_allow_host = "%" administrator_password = <sensitive> administrator_username = "admin" application_user_allow_host = "%" application_user_password = <sensitive> application_user_username = "appuser"
production
側の内容も見てみましょう。くどくなるので、apply
の実行の様子は載せません。
$ cd ../../production
Providerの定義をしている、terragrunt.hcl
。development
の時とは、接続先が異なります。
terragrunt.hcl
generate "provider" { path = "provider.tf" if_exists = "overwrite_terragrunt" contents = <<EOF provider "mysql" { endpoint = "172.17.0.3:3306" username = "root" password = "password" } EOF }
データベース側。
database/terragrunt.hcl
include { path = find_in_parent_folders() } terraform { source = "git::http://192.168.0.3/kazuhira/mysql-terraform-module.git//modules/database?ref=v0.0.1" } inputs = { database_name = "production_database" }
ユーザーおよび権限側。
users/terragrunt.hcl
include { path = find_in_parent_folders() } terraform { source = "git::http://192.168.0.3/kazuhira/mysql-terraform-module.git//modules/users?ref=v0.0.1" } inputs = { database_name = "production_database" administrator_password = "rZRuLj0xx2Z1M" application_user_password = "gSudLAJt4icZL" }
これで、同じモジュールを使って、リソースを構築するための設定が異なるという状態を、Terraform構成ファイルなしで実現できました。
Stateやprovider.tfは?
development
のデータベース側の定義に戻ってみます。
$ cd ../../development/database
先ほどTerragruntを使ってapply
した時にディレクトリを、provider.tf
がありませんでした。今回はTerraform Backendを特に設定して
いないので、Stateがファイルで管理されているはずなのですが、それもありません。
$ ll 合計 20 drwxrwxr-x 3 xxxxx xxxxx 4096 2月 24 00:05 ./ drwxrwxr-x 4 xxxxx xxxxx 4096 2月 23 22:05 ../ -rw-r--r-- 1 xxxxx xxxxx 1194 2月 24 00:05 .terraform.lock.hcl drwxrwxr-x 3 xxxxx xxxxx 4096 2月 24 00:05 .terragrunt-cache/ -rw-rw-r-- 1 xxxxx xxxxx 218 2月 23 23:15 terragrunt.hcl
provider.tf
については、親ディレクトリにあるterragrunt.tf
の内容からして、こちらのディレクトリに作成されているのでは?と思うのですが。
実は、こんなディレクトリにできていたりします。
.terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/provider.tf
# Generated by Terragrunt. Sig: nIlQXj57tbuaRZEa provider "mysql" { endpoint = "172.17.0.2:3306" username = "root" password = "password" }
こういう状態です。
$ tree .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4 .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4 └── modules ├── database │ ├── main.tf │ ├── outputs.tf │ ├── provider.tf │ ├── terraform.tfstate │ ├── terragrunt.hcl │ ├── variables.tf │ └── versions.tf └── users ├── main.tf ├── outputs.tf ├── variables.tf └── versions.tf 3 directories, 11 files
Stateまで、こちらに入っているんですね。
find
すると、こんな感じになっています。
※ .git
ディレクトリ除く
.terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/.terragrunt-source-version .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/users/outputs.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/users/versions.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/users/main.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/users/variables.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/outputs.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/provider.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/terraform.tfstate .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/versions.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/main.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/terragrunt.hcl .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/.terraform.lock.hcl .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/.terragrunt-module-manifest .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/variables.tf .terragrunt-cache/ro7akS-zy30K33w0IWWX0m8k7Jk/yKALuQR6CZfAIYeyffGdzMPkAo4/modules/database/.terraform/providers/registry.terraform.io/terraform-providers/mysql/1.9.0/linux_amd64/terraform-provider-mysql_v1.9.0_x4
実は、この構成だとterragrunt apply
などを実行する時に.terragrunt-cache
配下の一時ディレクトリに移動しているようなのです。
Keep your Terraform code DRY / Important gotcha: working with relative file paths
このため、-var-file
やterragrunt.hcl
内で書くパスは、絶対パスにしておかないとうまくいかないようです。
モジュールが更新された場合
こちらの内容です。
Keep your Terraform code DRY / Important gotcha: Terragrunt caching
source
の指定をリモートにした場合、Terragruntはモジュールのダウンロードを1度しか行いません。
terraform init -upgrade
、terragrunt init -upgrade
などしても、意味がありません。
モジュールを再度ダウンロードするには、--terragrunt-source-update
オプションを使用します。
$ terragrunt apply --terragrunt-source-update
ローカルのモジュールを使う場合
--terragrunt-source
オプションを使用することで、一時的にsource
で指定しているモジュールの参照先を変更できるようです。
Keep your Terraform code DRY / Working locally
これで、ローカルのモジュールを利用して作業ができます、と。
まとめ
Terragruntを使った、環境ごとのリソース定義方法(?)を見てみました。
どうなんでしょうね、確かにすごくすっきりした構成になりますし、モジュールをリポジトリ管理して参照するのも良いと思うのですが。
terragrunt.hcl
を使って変更できる範囲が、ちょっと少ないような…。
環境差異がVariablesで吸収できる範囲なら、有効なのでしょうか?
Workspaceを使わない、この考え方自体は押さえておいた方がよいかなと思います。