概要
インフラの勉強がてら、Programatic Terraform on AWSを読んでみたので、感想やエラーの対処などをまとめておこうと思います。
「Terraform 入門」とかで調べるとこちらの書籍の評判がものすごく良いことがわかります。
ただ、「実践Terraform」という商業誌版があったんですね...!😂
同人誌版と比べて追加された章もあるようなので、新規に購入される方にはこちらがおすすめです。
全体的な感想
わかりづらいところがありません😌
Terraformのファイル・コマンド・文法など基本的なところの説明から始まって、AWSリソースの作成に移り、TerraformやAWSのベストプラクティスにまで紹介されています。
リソースの作成においても、各サービスを段階的に積み上げながら作成していくので、つまづくことがありませんでした。
また、各サービスの設計に関してもわかりやすく説明されており、AWS自体の勉強になります。
注意点としては、途中からALB・ECS(Fargate)などで構成された全体的なシステムを構築していくのですが、こちらはできるだけ細かくデプロイしていった方が良さそうです(依存関係もあるので全部そうできるかはわかりませんが)
私は全てが終わってからまとめてデプロイしようとしてエラーになり、前の箇所にもどって「あれ、これなんだっけ?」と忘れるようなところが多かったからです。
一つ一つ小さくデプロイしながら、動作確認・リソースの削除を繰り返して行った方が、エラーにつまづくのが早いため、手戻りが少なそうです。
Terraformに関する感想
逆にAWSある程度触ったり、資格の勉強で包括的に知識を入れていたりすると、復習にもなるし
「GUIでぽちぽちやってよくわかってなかったけど、裏ではこんなリソースが出来ているのか」
と整理もできる内容になっています。
「Pragmatic Terraform on AWS」が神本だったので紹介する
私はまさにこれで、コンソールでぽちぽちやっていても、細かい設定項目などがよくわかっていなかったんですよね。
それが全て均一にコード化されるので、学習にも良いし、何よりリソースを把握しやすいです。
そして、インターフェイスというか記法がシンプルで、デプロイはterraform apply
・削除は terraform remove
のコマンド一発で済んでしまうのもすごいですね。。。
遭遇したエラーなど
環境変数の参照
variable my_domain {
type = "string"
default = "${MY_DOMAIN}"
}
最初「こんな感じかな?」と適当に上のようにやったらダメで
variable my_domain {
}
変数の箱を用意した上で、環境変数としてTF_VAR_
で始まる変数名をセットすると、その値を参照してくれるようでした。
$ export TF_VAR_my_domain=example.com
ちなみに、変数の値はコマンドラインでは-var
オプション、他にもterraform.tfvars
という専用のファイルで値を上書きできるようですが、優先順位としては環境変数が一番低いようです。
Terraform loads variables in the following order, with later sources taking precedence over earlier ones:
・Environment variables
・The terraform.tfvars file, if present.
・The terraform.tfvars.json file, if present.
・Any *.auto.tfvars or *.auto.tfvars.json files, processed in lexical order of their filenames.
Any -var and -var-file options on the command line, in the order they are provided. (This includes variables set by a Terraform Cloud workspace.)
Variable Definition Precedence
S3関係の設定方法の変更
Error: Value for unconfigurable attribute
│
│ with aws_s3_bucket.private,
│ on main.tf line 28, in resource "aws_s3_bucket" "private":
│ 28: resource "aws_s3_bucket" "private" {
│
│ Can't configure a value for "server_side_encryption_configuration": its value will be decided automatically based on the result of applying this configuration.
resource "aws_s3_bucket" "private" {
bucket = "private-pragmatic-terraform-on-aws-mk-private"
versioning {
enabled = true
}
server_side_encryption_configuration {
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
}
要は「**
aws_s3_bucket
がデカくなりすぎて大変だから、細かく分けようぜ!」ということです。
御存知の通りS3は非常に多機能です。そしてTerraformではその機能の殆どをaws_s3_bucket
**のattributeで設定していました。
v4では各Attribute毎にresourceが用意されているので、こちらを使います。
Terraform AWS Provider Version 4がリリースされました
最近(2022/2)のアップデート(v3→v4)でS3関係の定義の仕方が変わったらしく、 aws_s3_bucket
リソースの属性として定義していたものを、別途独立したリソースとして定義する必要があるようです。
resource "aws_s3_bucket" "private" {
bucket = "private-pragmatic-terraform-on-aws-mk-private"
}
resource "aws_s3_bucket_server_side_encryption_configuration" "private" {
bucket = aws_s3_bucket.private.id
rule {
apply_server_side_encryption_by_default {
sse_algorithm = "AES256"
}
}
}
他にも、Can't configure a value for
と出てきたらこれを疑っても良いかもしれません。
ALBのアクセスログを保存するS3バケットの設定
Error: failure configuring LB attributes: InvalidConfigurationRequest: Access Denied for bucket: alb-log-pragmatic-terraform-on-aws-mk. Please check S3bucket permission
│ status code: 400, request id: f99bb29a-e2c2-4c0e-b08a-0a822179c108
│
│ with aws_lb.example,
│ on main.tf line 66, in resource "aws_lb" "example":
│ 66: resource "aws_lb" "example" {
variable aws_account_id {}
...
resource "aws_s3_bucket_policy" "alb_log" {
bucket = aws_s3_bucket.alb_log.id
policy = data.aws_iam_policy_document.alb_log.json
}
data "aws_iam_policy_document" "alb_log" {
statement {
effect = "Allow"
actions = ["s3:PutObject"]
resources = ["arn:aws:s3:::${aws_s3_bucket.alb_log.id}/*"]
principals {
type = "AWS"
identifiers = [var.aws_account_id] →この値を自分アカウントIDにしてしまっていた
}
}
}
...
resource "aws_lb" "example" {
name = "example"
load_balancer_type = "application"
internal = false
idle_timeout = 60
enable_deletion_protection = false
subnets = [
aws_subnet.public_0.id,
aws_subnet.public_1.id,
]
access_logs {
bucket = aws_s3_bucket.alb_log.id
enabled = true
}
security_groups = [
module.http_sg.security_group_id,
module.https_sg.security_group_id,
module.http_redirect_sg.security_group_id,
]
}
ロードバランサーのリージョンに対応するAWSアカウントIDとは、要するに、S3のバケットポリシーで使用するアカウントIDのことのようです。
AWSアカウントを作成時に割り当てられるアカウントIDとは別物です。
執筆時点で、東京リージョンのアカウントIDは、582318560864でした。
ALBのアクセスログをS3バケットへ保存するように設定して、terraform実行すると権限エラー
ALBの場合は、AWSが管理しているアカウントから書き込みます。そこで、14行目で書き込みを行うアカウントID(582318560864)を指定しています。なお、このアカウントIDはリージョンごとに異なります*2。
Programatic Terraform p.32
本文にも書いてありましたが、こちらはリージョンごとの固定値なんですね。
自分のアカウントIDかと思って変数に書き出しましたが、固定値として値をそのまま書き込んでしまって良さそうです。
data "aws_iam_policy_document" "alb_log" {
statement {
effect = "Allow"
actions = ["s3:PutObject"]
resources = ["arn:aws:s3:::${aws_s3_bucket.alb_log.id}/*"]
principals {
type = "AWS"
identifiers = ["582318560864"] →この値を自分アカウントIDにしてしまっていた
}
}
}
ACMのdomain_validation_options
のデータ型が変更
Error: Invalid index
│
│ on main.tf line 321, in resource "aws_route53_record" "example_certificate":
│ 321: name = aws_acm_certificate.example.domain_validation_options[0].resource_record_name
│
│ Elements of a set are identified only by their value and don't have any separate index or key to select with, so it's only possible to perform operations across all elements of the set.
resource "aws_route53_record" "example_certificate" {
name = aws_acm_certificate.example.domain_validation_options[0].resource_record_name
type = aws_acm_certificate.example.domain_validation_options[0].resource_record_type
records = [aws_acm_certificate.example.domain_validation_options[0].resource_record_value]
zone_id = data.aws_route53_zone.example.id
ttl = 60
}
しかし、最新版のawsプロバイダーではList型からSet型へ変わってしまうことで、以下のように差分が発生します。
[TerraformのバージョンアップでInvalid indexが出た時の対処](TerraformのバージョンアップでInvalid indexが出た時の対処)
こちらもTerraformのバージョンアップ時の変更に伴うようですね。
確かに、公式を見ると、データ型がSet
になっています。
Set of domain validation objects which can be used to complete certificate validation. Can have more than one element, e.g., if SANs are defined. Only set if
DNS
-validation was used.
domain_validation_options
確かにSet
型のようです。
同じページにSet
型でループを回して、domain_validation_options
の要素数分resource
を作成するサンプルが載っていました。
resource "aws_route53_record" "example_certificate" {
for_each = {
for dvo in aws_acm_certificate.example.domain_validation_options : dvo.domain_name => {
name = dvo.resource_record_name
record = dvo.resource_record_value
type = dvo.resource_record_type
zone_id = data.aws_route53_zone.example.id
}
}
allow_overwrite = true
name = each.value.name
records = [each.value.record]
ttl = 60
type = each.value.type
zone_id = each.value.zone_id
}
以下の参考リンク先を見ていただけるとわかるのですが、Set
型をMap
型に変換してfor_each
を併用することで、domain_validation_options
の要素の分だけACM検証用のレコードを作成しています。
for_eachはresourceやmoduleでしか書けず、イメージとしてはresourceブロックごと繰り返すという感じになります。
each.key
やeach.value
でkey-valueを参照でき、setの場合はどちらも同じ値になります。
Terraformでのloop処理の書き方(for, for_each, count)
listからmapを生成することもできます。
Terraformでのloop処理の書き方(for, for_each, count)
map
連想配列です。他の言語でいうところの、Hash, HashMap ですね。
AWS Terraform 基本コード まとめ
## 終わりに
リソースを作成したら、削除することを忘れないようにしましょう。
こちらの本はまったく関係ないのですが、試しに作ってみたリソースでだいぶ費用が発生してしまったことがありました😅
※本書にも記載がありますが、ALBやRDSを削除する場合は、deletion_protection
をfalse
(ついでにskip_final_snapshot
も必要に応じて)に変更した上で、一旦terraform apply
する必要があります。設定変更を反映した上で、terraform destroy
する必要があるということですね。