https://qiita.com/uraura/items/f20b21baf7f28101a9d9 の続編です.
おおざっぱな説明だけで具体的な構成の話がなくてよくわからん って感じなので,続きを書きます.
ディレクトリ構成
.
├── 10_network
│ ├── main.tf
│ ├── output.tf
│ └── variable.tf
├── 10_network.tf
├── 20_backend
│ ├── main.tf
│ ├── output.tf
│ └── variable.tf
├── 20_backend.tf
├── 25_app_backend
│ ├── main.tf
│ ├── output.tf
│ └── variable.tf
├── 25_app_backend.tf
├── 25_app_datastore
│ ├── main.tf
│ ├── output.tf
│ └── variable.tf
├── 25_app_datastore.tf
├── README.md
├── terraform.tf
└── variables.tf
ディレクトリ名のプレフィックスの数値自体に意味はありません.ディレクトリと同じ名前のファイルがあり,それにmodule
が定義されています.
数値の大小で依存関係を表していて,数値の大きいmoduleは数値の小さいmoduleに依存しています.言いかえると,数値の小さいmoduleのoutput
を数値の大きいmoduleは利用できる,ということです.
各moduleにどういうリソースが入ってるかは下図の通りです.
各層に何を入れるか,は厳密にわけるのは難しいのでフィーリングな部分もあります👻が,大きな方針は
- アプリケーションによらない,AWSでシステム運用するのに必要なリソースを
network
層に - アプリケーションで共通で使用するようなリソースを
backend
層に - アプリケーション固有のリソースを
app_backend
層に - アプリケーションがデータストアを必要とするのであれば
app_datastore
層に
です.backendとかapp_xxxとか名称は試行錯誤してたらこんな感じになってたので深い意味はあまりない...😔
app_backend
とapp_datastore
が分かれているのは,特にマイクロサービス的な構成をとると,データストアを必要としないサーバーもあったりするためです.
module
20_backendを見てみます.
20_backend.tf
module "backend" {
source = "./20_backend"
}
output "backend_aws_iam_role_lambda" {
value = "${module.backend.aws_iam_role_lambda}"
}
output "backend_aws_key_pair_ec2_key" {
value = "${module.backend.aws_key_pair_ec2_key}"
}
output "backend_aws_s3_bucket_config" {
value = "${module.backend.aws_s3_bucket_config}"
}
トップレベルにあるtfファイルには,moduleの定義と,そのmoduleからのoutputを定義します.
ポイントは
-
このmoduleは外部にどういう値をエクスポートしているのかが明確になる
-
terraform output
で見れるので,実際はわざわざソースを見る必要はない
-
-
output
する必要ないリソースのことは完全に無視できる(当該moduleをいじるときだけ気にすればいい)
20_backend/variable.tf
data "terraform_remote_state" "aws_main" {
backend = "s3"
environment = "${terraform.workspace}"
config {
bucket = "tfstate"
key = "aws/main/terraform.tfstate"
region = "ap-northeast-1"
}
}
Terraform Providerを利用するための設定です.
Terraformのworkspaceを利用して,環境(dev/stg/prd)を表現しています.environment
に渡すことにより,1つのtfstateで環境を考慮していい感じにやってくれます.
20_backend/main.tf
locals {
network_aws_subnet_jump = "${data.terraform_remote_state.aws_main.network_aws_subnet_jump}"
network_aws_route53_zone_main = "${data.terraform_remote_state.aws_main.network_aws_route53_zone_main}"
network_aws_security_group_localnet = "${data.terraform_remote_state.aws_main.network_aws_security_group_localnet}"
}
data "aws_route53_zone" "main" {
zone_id = "${local.network_aws_route53_zone_main}"
}
data "aws_security_group" "localnet" {
id = "${local.network_aws_security_group_localnet}"
}
ここは実際のリソースが定義されています.
locals
内にある,${data.terraform_remote_state.aws_main.network_aws_route53_zone_main}
が,Terraform Providerを利用して値を取ってきてるところです.
ここは20_backend
なので,それより下位の10_network
のoutput
だけを利用しています.
とりあえずファイルは1つにして全部おしこんでいますが,リソース数によっては
- ファイルを分ける
- サブモジュールとする
- そもそも階層を増やす
などを考える必要がありそうです.
ファイルを分けるとリソースをどのファイルへ書く?問題が起きますし,サブモジュールにすると見通しが...?階層増えるとそれはそれでメンドいか...?など,まだ自分の中でもベストアンサーはありません🙇
local
余談ですが,local
は便利なので積極的に使うと良いと思います.
internal_flag
がtrueの場合に何かリソースを生成する,みたいな定義がある場合に,わざわざフラグで判定しなくてもよくなります.
module "foo" {
internal_flag = true
# internal_flagがONの場合に作られるリソースの設定値
config = {
name = "xxx"
}
}
# internal_flagによってリソースを作ったり作らなかったりする
resource "aws_lb" "bar" {
count = "${internal_flag ? 1 : 0}"
:
}
↓↓↓
module "foo" {
config = {
name = "xxx"
}
}
# moduleにconfigがあるかどうかでフラグを計算
# デフォルト値は{}にしておく必要がある
locals {
internal_flag = "${length(keys(var.config)) != 0}"
}
# configがあるかによってリソースを作ったり作らなかったりする
resource "aws_lb" "bar" {
count = "${internal_flag ? 1 : 0}"
:
}
のようになり,moduleの外で設定しなければならない値が減って良い感じになります.
20_backend/output.tf
output "aws_s3_bucket_config" {
value = "${aws_s3_bucket.config.id}"
}
外部で使用したいリソースのID等をひたすらoutputするだけです.
20_backend.tfにも書いてるじゃないか!と思うかもしれませんが,Terraform Providerを使うにはrootレベルでoutputされている必要があるため,moduleのoutputをそのままrootでoutputする,という感じになってしまっています.ちょっと面倒ですがまぁ仕方ないかな...と.
outputする値は,data
が使える場合は識別子(ARNやresource id等)のみ,使えない場合は↓な感じでmapにしています.
output "aws_iam_access_key_ci" {
value = {
id = "${aws_iam_access_key.ci.id}"
}
}
他のmodule
25_app_backend
や25_app_datastore
も,上記と同様の方針でファイルを構成しています.
違うのは,25層は下位の10,20層のどちらのoutputも使用できる点ぐらい.
plan/apply
する
現時点での構成では,moduleわけはしたもののリポジトリ単位でわかれているわけではないので,そのままplan
などとすると全モジュール読みこまれてしまい時間がかかる問題は解決されません.
ですが,module分割を行ったことにより,module単位で実行することは可能になります.
terraform plan -target=module.backend
などと引数を付けて実行することで,指定したモジュールだけ(今回の構成でいけば指定した層だけ)を対象にして実行できるので,下位層に変更がないことが分かってる場合は大幅に実行速度が上がります.
注意しなければならない点
- 下位層の
output
追加と,上位層での利用を同時に追加したら死ぬ- 依存関係があるので,先に下位層の
output
追加をapplyまでしてからでないと上位層でいきなり参照してはいけません
- 依存関係があるので,先に下位層の
-
output
追加だけだとplan
してもno changes
になってしまうのでapply
を忘れがちになってしまう
今までハマったのはこれぐらい....?もっとあったかも...
あとは,今は単一リポジトリでやっていますが,複数のリポジトリに完全に分離してしまった場合,**下位層の変更を上位層はどうやって検知すればいいのか?**という問題があるような気がしています.
そのへんは試行錯誤中なのでまたそのうち😶