#author("2025-05-26T16:32:23+09:00","default:dex","dex")
#author("2025-06-02T18:11:53+09:00","default:dex","dex")
#contents
#ls2

*Terraform [#n7dda3d6]

-https://www.terraform.io/
-- [[CHANGELOG.md:https://github.com/hashicorp/terraform/blob/master/CHANGELOG.md]]
-- HashiCorp製ツール(Vagrant, Packer, Serf等の開発元)
-- AWS, Heroku等の複数のクラウドサービスに対応している

''個人的な印象'':
-- Terraformの不具合の多さを理解しつつ、Terraformの不具合報告/修正等の''オープンソース活動''を行える余裕がある人向け。
-- https://github.com/hashicorp/terraform/issues を見ると分かるが1000 issue以上が作成されており、不具合の修正が間に合っていない印象
-- terraform本体と、provider(AWS, Azure, ...)は別。providerが最新のterraformに対応していない場合もあるので、要チェック
-- ''新規作成・破棄''を繰り返す用途向き
-- 既存リソースの管理はコードを書いて、リソースID毎にimportコマンドの実行が必要なので大変。import非対応のリソースもある。''向いていない''
-- 状態をステートファイル(terraform.tfstate)に持ち、これを元に差分更新する。
---無くすと大変。更新ができなくなる。(既存リソースのimportも一部は出来るが、数が多いと大変)
---ステートファイルはAWS S3等に置く事はできる。
---ステートファイルの管理が面倒。リソースの差分がうまく比較できないようで、''リソースの更新には不向き''。
-- ドキュメント・サンプルが不足している。値の説明が書いてあるだけで、具体的にどのような値を入れれば良いのか不明なリソースが沢山ある。
--- ドキュメントに機能追加/変更されたバージョンが書いてないため、いつのバージョンが対応してるのかわからない
--- ドキュメントにはimportが書いて無いが、実はインポートできるリソースもある
-- マイナーバージョンが0.0.1変わっても、仕様が大きく変わり、コードの互換性が無くなる事も多い。
--- コードの互換性がない場合もあり、修正にも時間がかかるため、tfenv等で複数バージョンの管理が必要。
--- 0.11と0.12でコードの互換性が無くなった。 0.12upgradeコマンドである程度は修正してくれるが、リソース名はそのままなので手動修正が必要
-- 開発方針がdevしかなく、stable等の安定版が無い。

- 構文
-- 同じリソースを作成する方法が複数存在する場合があり混乱する。リソースA + attach + リソースBが変更には強い感じはある。v0.12からfor eachやdynamic blockが追加された事でループ処理ができ、どちらを使えば良いのかさらに混乱するようになった。
--- 例: aws_instance + ebs + attachと分ける方法、aws_instanceだけの方法(ebs拡張が効かない)
--- 例: aws_security_group, aws_security_group_rule。 混在させると1回目のapplyでは成功するように見えるが、2回目のapplyでは片方消える。
-- ループ処理が分かりにくい。v0.11: list内にmapがあると要素が取れなかったりする。
-- if等の制御構造が無い。count=0でリソースを作成しないように、三項演算子で制御は一応できるが分かりにくい。
-- AWS
--- aws_instance count=4で作った後に、count=0,1をterminateする方法が %%destroy -target 指定するしか無さそう。しかし、planで毎回count=0,1が新規作成扱いになるため辛い。state rm/importでやり直せば可能だが、リソースID毎にimportが必要なため面倒。%%-> ''0.12でfor_eachが追加''された
--- 対応しているリソースの新規立ち上げは楽だが、非対応の項目がかなりあるため、自分が必要としている機能をサポートしているか要確認
--遭遇したトラブル
--- v0.11.13: EC2セキュリティグループの書き方が複数あり、古い書き方+新しい書き方(aws_security_group_rule)を混在した場合、applyは成功する。2回目のplanでも差分が出て、どちらかが消える。
--- v0.0.1上がるとコードに無いheroku configを消すように仕様変更
--- API GatewayのSSL証明書の登録が成功したように見えるが、実は成功していない。
--- destroy時は削除確認があるが、EC2セキュリティグループの変更で、EC2が作り直される時には警告はない。plan時に予期しない変更が無いか強く確認が必要。さもなければ簡単にリソースが消える
--- セキュリティグループに変更を加えると、EC2を作り直そうとする
--- ELBにリスナー追加しようとすると、リスナー全て消えた
--- SSL証明書のアップロードが成功したように見えて壊れている
--- EC2にEBSを追加しようすると、EC2を作り直そうとする
--- %%aws_instanceリソースでEBSを追加できる。作成後、aws_instanceリソース内のroot, EBSのサイズ等を変更できない。%% awscliでは可能。provider.aws v5 ではaws_instance内のroot volumeサイズは変更可能だった。ebs blockは不可。

-記事
-- [[Terraformをしばらく書いて覚えた個人的なTipsについて | Developers.IO:https://dev.classmethod.jp/articles/my-terraform-tips/]]
--[[Terraformのエッセンスが凝縮された「Pragmatic Terraform on AWS」が素晴らしい | DevelopersIO:https://dev.classmethod.jp/cloud/aws/bookreview-terraformon-aws-md/]]
--[[Terraform職人入門: 日々の運用で学んだ知見を淡々とまとめる - Qiita:https://qiita.com/minamijoyo/items/1f57c62bed781ab8f4d7]]
--[[Terraform Best Practices in 2017 - Qiita:https://qiita.com/shogomuranushi/items/e2f3ff3cfdcacdd17f99]]
--[[【30分で動かすシリーズ】まだCloudFormationで消耗してるの?TerraformでAWS環境を構築してみる(その1:TerraformでAWS環境を構築する準備) | サーバーワークス エンジニアブログ:http://blog.serverworks.co.jp/tech/2016/09/26/terraform-aws-provisioning-01/]]
--[[【参考訳】Terraform 0.7 · Pocketstudio Technology Log:https://pocketstudio.net/2016/08/06/terraform-0-7-translate/]]
--[[TerraformでGithubのチーム管理を自動化して事故った話 | ツチノコブログ:http://tsuchinoko.dmmlabs.com/?p=4265]]
--[[Terraform 0.4 (参考訳) | Pocketstudio.jp log3:http://pocketstudio.jp/log3/2015/04/04/terraform-0-4-release-translated/]]

----
** digger: GitHub actions上で動くCI/DI [#d018edbe]

- [[GitHub - diggerhq/digger: Digger is an open source IaC orchestration tool. Digger allows you to run IaC in your existing CI pipeline:https://github.com/diggerhq/digger]]

記事:
- [[Digger + GitHub Actionsで作るTerraform/OpenTofuのCI/CD #Terraform - Qiita:https://qiita.com/minamijoyo/items/b61806b570d9d1257f0b]]

----
** 改善 [#w205984a]

記事:
- [[コードレビューでよくあったコメント9選 〜Terraform編〜:https://zenn.dev/mixi/articles/terraform-code-review-tips]]


----
** terraformでDB user作成 [#m7039ec1]

注意点:
- 通常RDSは、private subnetにあるため、localhostからは接続できない。そのため、ssh踏み台(bastion-host)経由でsshポートフォワーディングする。
#geshi(bash){{
ssh -F ssh-config -fNg -L 5432:<RDS ID>.****.ap-northeast-1.rds.amazonaws.com:5432 bastion-host
}}
- postgresqlでSSL有効の場合、以下のエラーが出る。
-- docを参照して「scheme = "awspostgres"」を入れると同じエラーが出たのでコメントアウト。(cyrilgdn/postgresql v1.22.0)
#geshi(text){{
Error: Error connecting to PostgreSQL server 127.0.0.1 (scheme: awspostgres): tls: failed to verify certificate: x509: cannot validate certificate for 127.0.0.1 because it doesn't contain any IP SANs
}}
-- /etc/hostsを書き換えてlocalhostへ接続するように
#geshi(text){{
sudo vim /etc/hosts
--
127.0.0.1       <RDS ID>.****.ap-northeast-1.rds.amazonaws.com
--
}}

記事:
- [[TerraformでAWS RDSのDBユーザを管理する #PostgreSQL - Qiita:https://qiita.com/yoshuuua/items/ebeefffc04d966d238e4]]


----
** Infracos Cloud: terraformコードからコスト見積もり/差分を出してくれるSaaS [#jecc5f64]

- [[Infracost Plans & Pricing:https://www.infracost.io/pricing/]]
-- 有料
-- trialは1000回/月まで無料?

- [[GitHub - infracost/infracost: Cloud cost estimates for Terraform in pull requests&#128176;&#128201; Shift FinOps Left!:https://github.com/infracost/infracost?tab=readme-ov-file]]


----
** ガイドライン [#ked73464]

- [[Terraform設計ガイドライン | Future Enterprise Arch Guidelines:https://future-architect.github.io/arch-guidelines/documents/forTerraform/terraform_guidelines.html]]
- 公式ガイドラインを含んでいて分かりやすい

- [[Style Guide - Configuration Language | Terraform | HashiCorp Developer:https://developer.hashicorp.com/terraform/language/style]]
-- [[Terraform 公式がスタイルガイドを出したので読んで要約した #Terraform - Qiita:https://qiita.com/ogatango/items/9cd57fe8c48b1c03b2bd]]



----
** terraform本体のコードリーディング [#f87e6c17]

記事:
- [[Terraformの実装コードを、動かしながら読む | フューチャー技術ブログ:https://future-architect.github.io/articles/20240326a/index.html]]

----
** mysql user/password等を作成する [#d4cb6ac2]

- [[hashicorp製mysql repo:https://github.com/hashicorp/terraform-provider-mysql]]は更新停止している。そのrepoをforkし、機能追加したものが複数ある
-- [[mysql_user | Resources | petoju/mysql | Terraform | Terraform Registry:https://registry.terraform.io/providers/petoju/mysql/latest/docs/resources/user]]
-- importに失敗した。(mysql_user, mysql_grant)

- [[Docs overview | okkez/mysql | Terraform | Terraform Registry:https://registry.terraform.io/providers/okkez/mysql/latest/docs]]
-- [[MySQL8 以降の新しい機能に一部対応した terraform-provider-mysql を開発しました:https://zenn.dev/okkez/articles/ec92caf8494765]]
-- import成功まで確認した。(mysql_user, mysql_grant)

- RDSは基本VPC内にあるので、localからは直接アクセスできない。そのため、sshポートフォワード等が必要になる
-- [[RDS内のデータベースやユーザーをterraformで管理する #Terraform - Qiita:https://qiita.com/yuta_vamdemic/items/698e149edf8ea89c24dc]]


----
** OpenTofu: OSS版terraform [#vc752629]

- [[Memo/OpenTofu]]


----
** optional(type): 省略可能な変数定義 [#o436e48d]

- terraform v1.3以降でoptional(type)構文が使える
#geshi(text){{
variable "with_optional_attribute" {
  type = object({
    a = string                # a required attribute
    b = optional(string)      # an optional attribute
    c = optional(number, 127) # an optional attribute with default value
  })
}
}}

記事:
- [[Terraform 1.3でoptional()によるobject attributes(variable)へのデフォルト値設定が楽になる!:https://zenn.dev/jrsyo/articles/83bbcff7e08ab8]]


----
** さくらのクラウド(さくらインターネット) [#qa158d9b]

- [[Docs overview | sacloud/sakuracloud | Terraform | Terraform Registry:https://registry.terraform.io/providers/sacloud/sakuracloud/latest/docs]]


記事:
- [[さくらの開発チームにおけるTerraform/Ansibleの活用 | さくらのナレッジ:https://knowledge.sakura.ad.jp/38635/]]
- [[さくらのクラウドでTerraformを使ってみる | さくらのナレッジ:https://knowledge.sakura.ad.jp/31560/]]


----
** 変数の外部定義と優先度 [#rdb510e2]

上から順に読み込まれ、同名は上書きされる

[[Input Variables - Configuration Language | Terraform | HashiCorp Developer:https://developer.hashicorp.com/terraform/language/values/variables#variable-definition-precedence]]
- 環境変数
- terraform.tfvars
- terraform.tfvars.json
- *.auto.tfvars, *.auto.tfvars.json
- -var "var1=val1", -var-file file.tfvars


----
** Pluralith: terraformコードから構成図を作図するサービス [#g23a647a]

- https://app.pluralith.com/
-- まだアルファ版
-- local実行は無料、IaCでの実行は有料

記事:
- [[Terraform構成をビジュアライズできるツール Pluralithを使ってAWS構成図を自動作成してみる | DevelopersIO:https://dev.classmethod.jp/articles/terraform-visualise-pluralith/]]


----
** blockを動的に定義し、パラメータ化する [#x6e03098]

- 例: heroku_appのorganization blockはデフォルトは省略可で、任意でパラメータを与える。moduleにする時にパラメータにしたい

#geshi(){{{
variable "heroku_app_organization" {
  description = ""
  type = object({
    name = string
  })
  default = {
    name = null
  }
}

resource "heroku_app" "main" {
...

  dynamic "organization" {
    for_each = var.heroku_app_organization.name == null ? [] : [var.heroku_app_organization]
    content {
      name = organization.value.name
    }
  }
}
}}}


----
** 機密情報を暗号化して保存する [#v15c706f]

- git repoに機密情報(password, secret, token等)をplain textで保存するのはセキュリティ上リスクが高い
- tfファイルに直接機密情報は書かない
- secrets.tfvars に分けて書く。または、secrets.json, yaml等別ファイルにする
- AWSの場合
-- KMS: secrets.yaml.encrypted 暗号化。secrets.yaml.encryptedはgitにコミットして良い。data.aws_kms_secrets でdecodeして取得
-- secret manager: 機密情報をyaml, jsonにして保存。 data.aws_secretsmanager_secret_version で取得

sensitiveの変更を出力したい時:
- [[Memo/Terraform#g133d3f5]]
- [[terraform planでsensitive属性が原因で非表示になる差分を見る方法:https://zenn.dev/shonansurvivors/articles/4940cbea023970]]


記事:
- [[A comprehensive guide to managing secrets in your Terraform code | by Yevgeniy Brikman | Gruntwork:https://blog.gruntwork.io/a-comprehensive-guide-to-managing-secrets-in-your-terraform-code-1d586955ace1]]

- [[Terraform で秘密情報を扱う &#8211; もばらぶエンジニアブログ:https://engineering.mobalab.net/2021/03/25/handling-secrets-with-terraform/]]


----
** 排他仕様の変数に代入 [#i8900921]

- nullを代入すると、変数は未定義扱いとなった
- try()で変数が未定義の場合、nullを返すように
- 例: aws_kinesis_firehose_delivery_stream で domain_arn, cluster_endpoint は排他
#geshi(text){{
...
  elasticsearch_configuration {
    domain_arn = try(each.value.es_cluster_endpoint, null) == null ? each.value.es_domain_arn : null
    cluster_endpoint = try(each.value.es_cluster_endpoint, null)
...
}}


----
** data.http: httpリクエストを送る [#mbceb1c5]

- [[http_http | Data Sources | hashicorp/http | Terraform Registry:https://registry.terraform.io/providers/hashicorp/http/latest/docs/data-sources/http]]
-- HEAD, GET, POSTメソッド対応

- 例: JSONファイルをpostする。plan時でもpostされる。
#geshi(text){{
data "http" "example" {
  url    = "https://example.com/api1"
  method = "POST"
  request_headers = {
    Content-Type = "application/json"
  }

  request_body = file("/path/to/example.json")
}

output "http_os_example_result" {
  value = "status_code: ${data.http.example.status_code}, response_body: ${data.http.example.response_body}"
}
}}

----
** tfcmt: plan結果を整形してCI/DIの改善 [#r26c9516]

- https://github.com/suzuki-shunsuke/tfcmt

記事:
- [[tfcmt で Terraform の CI/CD を改善する:https://zenn.dev/shunsuke_suzuki/articles/improve-terraform-cicd-with-tfcmt]]


----
** templatefile(): ファイル中の変数を展開 [#g122d345]

- [[templatefile - Functions - Configuration Language | Terraform | HashiCorp Developer:https://developer.hashicorp.com/terraform/language/functions/templatefile]]
-- file() の代わりに使えば、変数を展開できる
-- [[data.template_file:https://registry.terraform.io/providers/hashicorp/template/latest/docs/data-sources/file]] と違い、分けて書く必要がないのでシンプル

- 例:
#geshi(hcl){{
resource "elasticsearch_opensearch_ism_policy" "example" {
  policy_id = "example"
  body      = templatefile(
    "${path.module}/files/opensearch/ism-policy-example.json",
    {
      min_index_age = "30d",
      min_size = "50gb",
    }
  )
}
}}

----
** moved: リソース名変更等のリファクタリング [#nbb6597a]

- [[Refactoring | Terraform | HashiCorp Developer:https://developer.hashicorp.com/terraform/language/modules/develop/refactoring]]
-- terraform v1.1から利用可能

- movedを使わない場合以前のリソース名の変更
-- 「terraform state mv <old resource> <new resource>」 -> ソース変更
-- または「terraofrm state rm <old resource>」-> ソース変更 -> 「terraofrm import <new resource> <resource name>」


記事:
- [[movedブロックを使ってリファクタリングしてみた | DevelopersIO:https://dev.classmethod.jp/articles/terraform-moved-block-resource-refactoring/]]


---
** rangeでloopさせる [#i56d08c2]

- terraform v1.3
- resourceを複数作成したい場合、for_eachが使えるがmapか、string型のlistしか受け付けない。formatlistでstring型に変換する。
#geshi(text){{
for_each = toset(formatlist("%s", range(1, 10)))
}}


----
** for: ループ [#u7ce101d]

- [[For Expressions - Configuration Language | Terraform by HashiCorp:https://www.terraform.io/language/expressions/for]]
-- 戻り値の型指定が決まっている。array「[for ...]」、hash「{for ...}」
-- 複数の配列を1行の中で返す等はできない?
-- mapをループする場合「[for key, value ...]」

- 例: 複雑な変数から、s3 policyでよくある ["arn:aws:s3:::example1", "arn:aws:s3:::example1/*"] を作る。
#geshi(text){{
locals {
  var1 = {
    bucket1 = {
      s3_bucket_arn = "arn:aws:s3:::example1"
    }
    bucket2 = {
      s3_bucket_arn = "arn:aws:s3:::example2"
    }
  }
}

data "aws_iam_policy_document" "s3" {
  statement {
    effect = "Allow"
    actions = [
      "s3:GetObject",
    ]
    resources = concat([for value in local.var1 : value.s3_bucket_arn], [for value in local.var1 : "${value.s3_bucket_arn}/*"])
  }
}
}}


----
** v1.0以降の対応 [#w500bda9]

- [[Upgrading to Terraform v1.0 | Terraform by HashiCorp:https://www.terraform.io/language/upgrade-guides/1-0]]

- v0.13でapplyしてるtfsateが必要

- 例: v0.11からv1.0へアップデートしたい場合、かなり手間がかかる
-- v0.11 -> v0.12
#geshi(bash){{
rm versions.tf
echo 'latest:^0.12' > .terraform-version
rm -rf .terraform
teraform init
terraform 0.12upgrade
teraform plan
# コードのエラー修正
}}
-- v0.12 -> v0.13
#geshi(bash){{
echo 'latest:^0.13' > .terraform-version
rm -rf .terraform
teraform init
terraform 0.13upgrade
teraform plan
# コードのエラー修正

# applyしないとv1.0でエラーが出る
teraform apply
}}
-- v0.13 -> v1.0
#geshi(bash){{
echo 'latest:^1.0' > .terraform-version
rm -rf .terraform
teraform init
teraform plan
# コードのエラー修正
}}


----
** URLからOIDC fingerprintを取得 [#f01aad21]

- [[data.http:https://registry.terraform.io/providers/hashicorp/http/latest/docs/data-sources/http]] URLの結果を取得 
- [[data.tls_certificate:https://registry.terraform.io/providers/hashicorp/tls/latest/docs/data-sources/tls_certificate]] TLSのsha1 fingerprintを取得

記事:
- [[Terraform だけを使って GitHub Actions OIDC ID プロパイダの thumbprint を計算する方法:https://zenn.dev/yukin01/articles/github-actions-oidc-provider-terraform]]

----
** 機密情報を出力したい時 [#g133d3f5]

- [[How-to output sensitive data with Terraform &#8211; HashiCorp Help Center:https://support.hashicorp.com/hc/en-us/articles/5175257151891-How-to-output-sensitive-data-with-Terraform]]
-- password, token, access key等、標準出力に表示しないほうが良い場合。
-- どの値を隠すべきかの参考に
--- AWS: https://github.com/secretlint/secretlint/tree/master/packages/@secretlint/secretlint-rule-aws

- v0.15から「sensitive = true」を付けないと、apply時にエラーになった。planやapply時は「example_password = <sensitive>」表示になる。
#geshi(text){{
output example_password {
  value = aws_db_instance.example.password
  sensitive = true
}
}}
-- 生の値を表示したい場合:
#geshi(text){{
# json
terraform output -json
terraform output example_password 

# raw value
terraform output -raw example_password
}}


- 常時表示したい場合。
- [[nonsensitive - Functions - Configuration Language - Terraform by HashiCorp:https://www.terraform.io/docs/language/functions/nonsensitive.html]]

#geshi(text){{
output example_password {
  value = nonsensitive(aws_db_instance.example.password)
}
}}

記事
- [[Terraform 0.15の変更点を調べた | DevelopersIO:https://dev.classmethod.jp/articles/terraform-015/]]


----
** セキュリティチェック [#gcdc931d]

- 記事
-- [[Terraform, Dockerfile, KubernetesなどIaCの脆弱な設定をCI/CDで検知する - knqyf263's blog:https://knqyf263.hatenablog.com/entry/2021/07/13/063729]]


----
** untaint: 失敗したstate fileを解決済みとしてマークする [#k34623e9]

- [[Command: untaint - Terraform by HashiCorp:https://www.terraform.io/docs/commands/untaint.html]]

例:
- RDSは作成に時間がかかり、timeout(create: 40min)する事がある。そうすると、stateファイルが汚染(taint)?状態になり、terraform planで余計な差分(destroy/add)が出る状態になる


解決:
#geshi(bash){{
terraform untaint aws_db_instance.main
}}

----
** 算術演算子 [#ld3d780d]

- 簡単な計算(+, -, *, /, %, -1)ができる

- [[Arithmetic Operators:https://www.terraform.io/docs/configuration/expressions.html#arithmetic-operators]]
#geshi(text){{
byte = "${100*1024*1024}" # 100MB
}}

----
** plan時に色を付けない [#p590953b]

- plan結果をテキストファイルにしたい場合、デフォルトでは色(制御文字列)がテキストファイルに出て見難い

- -no-color を付ける
#geshi(bash){{
terraform plan -no-color > plan.txt
}}

----
** GitHub actionsでの連携 [#af3fe099]

- 記事
-- [[GitHub Actionsでsetup-terraformを試す | Developers.IO:https://dev.classmethod.jp/articles/try-github-actions-setup-terraform/]]


----
** console: 対話型で式を検証 [#va0ed923]

- [[Command: console - Terraform by HashiCorp:https://www.terraform.io/docs/commands/console.html]]

- 対話型
#geshi(bash){{
terraform console

> concat(["a", "b"], ["c"])
[
  "a",
  "b",
  "c",
]
> exit
}}

- stdinから渡す
#geshi(bash){{
echo 'concat(["a", "b"], ["c"])' | terraform console
}}

----
** dynamic block: リソース内の同名blockのループ [#qe9a135b]

- [[dynamic blocks:https://www.terraform.io/docs/configuration/expressions.html#dynamic-blocks]]
-- リソース内で同じ名前のブロックが複数ある場合、for_eachでループできる。
-- list(string)はfor_each = toset(var.name)で指定、.keyで参照する。''項目が増減すると、リソースの生成/破棄が発生する。''
-- map(string)は、.key, .valueで参照できる
-- 複雑なlist(map(string))は、.value.key2で参照できる

- 環境
-- Terraform v0.12.21

- 例: aws_db_parameter_groupの場合、"parameter {}" ブロックを複数設定する
#geshi(text){{
variable "mysql_57_parameters" {
  default = {
    character_set_server = "utf8"
    character_set_client = "utf8"
  }
}

resource "aws_db_parameter_group" "mysql_57" {
  name   = "mysql-57"
  family = "mysql5.7"

  dynamic "parameter" {
    for_each  = var.mysql_57_parameters
    content {
      name = parameter.key
      value = parameter.value
    }
  }
}
}}
-- plan結果
#geshi(bash){{
terraform plan -target aws_db_parameter_group.mysql_57
...
  # aws_db_parameter_group.mysql_57 will be created
  + resource "aws_db_parameter_group" "mysql_57" {
      + arn         = (known after apply)
      + description = "Managed by Terraform"
      + family      = "mysql5.7"
      + id          = (known after apply)
      + name        = "mysql-57"
      + name_prefix = (known after apply)

      + parameter {
          + apply_method = "immediate"
          + name         = "character_set_client"
          + value        = "utf8"
        }
      + parameter {
          + apply_method = "immediate"
          + name         = "character_set_server"
          + value        = "utf8"
        }
    }
}}

- 例: aws_security_group に使う。aws_security_group_rule の方が差分を確認できて良いかもしれない。
-- メリット:
--- 内部的にソートしているようで、記述の順序を気にして無く良い。
-- デメリット:
--- 一部だけ変えても、delete/new扱い。changeで表示されない。
--- 一つでも必要な項目は、全変数に入れる必要がある。(パラメータが無かったら省略するという構文が無いように見える)

#geshi(text){{{
variable "sg_ssh_ingress" {
  default = [
    {
      cidr_blocks = ["xxx.xxx.xxx.xxx/32"]
      description = "ssh from AAA"
      security_groups = []
      self        = false
    },
    {
      cidr_blocks = ["xxx.xxx.xxx.xxx/32"]
      description = "ssh from BBB"
      security_groups = []
      self        = false
    },
...
  ]
}

resource "aws_security_group" "bastion" {
...
  dynamic "ingress" {
    for_each = var.sg_ssh_ingress
    content {
      from_port       = 22
      to_port         = 22
      protocol        = "tcp"
      cidr_blocks     = ingress.value.cidr_blocks
      security_groups = ingress.value.security_groups
      description     = ingress.value.description
      self            = ingress.value.self
    }
  }
...
}
}}}


----
** for_each: ループ [#o3334932]

v0.12.6以降のループ処理について。

countの問題点:
- countが変わると、count.indexに紐付いたidも変わるため、リソースが壊れる。countが増える分には対応できるが、countが減る場合に問題が出る
- 例: count=4で、count.index=0,1だけを削除/変更したい場合に対応できない。count.index=2,3が削除/変更される。

[[Manage Similar Resources with For Each | Terraform - HashiCorp Learn:https://learn.hashicorp.com/tutorials/terraform/for-each#define-a-map-to-configure-each-project]]:
- 変数を元に、リソース自体をループできる。keyを静的なuniq idで指定すれば、要素数が変わっても問題が無い。
- map型は each.key, each.valueで参照可能
- list型は [[toset():https://www.terraform.io/docs/configuration/functions/toset.html]]を指定する。each.key, each.valueで同じ値が参照可能。''項目数が増減すると、リソースの生成/破棄が発生する。''
-- plan&applyでkeyが動的な値だとエラーが出るようになった。
-- [[Terraform の for_each では静的な値を利用する|デロイト トーマツ ウェブサービス株式会社(DWS)公式ブログ:https://blog.mmmcorp.co.jp/2024/02/27/terraform-%E3%81%AE-for_each-%E3%81%A7%E3%81%AF%E9%9D%99%E7%9A%84%E3%81%AA%E5%80%A4%E3%82%92%E5%88%A9%E7%94%A8%E3%81%99%E3%82%8B/]]


- 記事
-- [[Terraformで配列をloopする時はfor_eachを使った方がいい - cloudfishのブログ:https://cloudfish.hatenablog.com/entry/2020/02/19/183548]]
-- [[Terraform0.12 のfor_eachで幸せになれるってよ - Qiita:https://qiita.com/micci184/items/e0723cce6c18b81a9de0]]

- 環境
-- Terraform v0.12.24

- for_eachの例1: リソース自体が2回実行される
#geshi(text){{{
locals {
  users = [
    "user01",
    "user02",
  ]
}

resource null_resource user {
  for_each   = toset(local.users)
  triggers  = {
    user = each.value
  }
}
}}}
-- 実行結果
#geshi(bash){{{
terraform plan -target null_resource.user
...
  # null_resource.user["user01"] will be created
  + resource "null_resource" "user" {
      + id       = (known after apply)
      + triggers = {
          + "user" = "user01"
        }
    }

  # null_resource.user["user02"] will be created
  + resource "null_resource" "user" {
      + id       = (known after apply)
      + triggers = {
          + "user" = "user02"
        }
    }
}}}


- for_eachでindex値が欲しい場合
#geshi(text){{
index(local.users, each.value)
}}

----
*** key, value以上の複数のパラメータを指定したい場合 [#u57ce057]

- valueの参照は以下のいずれかで可能
-- each.value.home
-- lookup(each.value, "home")

- example.tf
#geshi(text){{
variable users {
  type = map(map(string))
  default = {
    user01 = {
      home = "/home/user01"
      shell = "/bin/bash"
    }
    www01 = {
      home = "/var/www/html"
      shell = "/bin/false"
    }
  }
}

resource null_resource user {
  for_each   = var.users
  triggers  = {
    user = each.key
    home = each.value.home
    shell = each.value.shell
  }
}
}}
-- 実行結果
#geshi(shell){{
terraform plan
...
  # null_resource.user["user01"] will be created
  + resource "null_resource" "user" {
      + id       = (known after apply)
      + triggers = {
          + "home"  = "/home/user01"
          + "shell" = "/bin/bash"
          + "user"  = "user01"
        }
    }

  # null_resource.user["www01"] will be created
  + resource "null_resource" "user" {
      + id       = (known after apply)
      + triggers = {
          + "home"  = "/var/www/html"
          + "shell" = "/bin/false"
          + "user"  = "www01"
        }
    }

Plan: 2 to add, 0 to change, 0 to destroy.
}}

----
*** for_eachリソースをoutputしたい場合 [#nc77c772]

- resource["string"].attr が必要。

#geshi(shell){{
output "user_home" {
  value = [ for value in null_resource.user : value.home ]
}
}}

----
*** list(map) 型をfor_eachでループ [#m9003245]

- 「for_each = local.ses_dkim」はエラーになる
#geshi(text){{
locals {
  # ses dkimのdataリソースで参照したほうが良いが、まだ存在しない
  ses_dkim = [
    {
      "type"  = "CNAME"
      "name"  = "dummy1._domainkey.example.com"
      "value" = "dummy1.dkim.amazonses.com"
    },
    {
      "type"  = "CNAME"
      "name"  = "dummy2._domainkey.example.com"
      "value" = "dummy2.dkim.amazonses.com"
    },
    {
      "type"  = "CNAME"
      "name"  = "dummy3._domainkey.example.com"
      "value" = "dummy3.dkim.amazonses.com"
    },
  ]
}

resource "aws_route53_record" "ses_dkim" {
  for_each = { for i in local.ses_dkim : i.name => i }
  zone_id = data.aws_route53_zone.example_com.zone_id
  name    = each.value.name
  type    = each.value.type
  ttl     = "600"
  records = [each.value.value]
}
}}


----
** Registry: 公開moduleの使用 [#h3a18059]


- [[Terraform Module Registry:https://registry.terraform.io/]]
-- HashiCorp Verified Modulesマークが付いたものが参考になる

- 記事
-- [[Terraform RegistryのModuleを使ってAWSリソースを作成してみた | Developers.IO:https://dev.classmethod.jp/etc/terraform_registry_module/]]

----
** 肥大化した.terraformディレクトリの削除 [#v1451677]

- 「terraform init」すると、依存providerを.terraformディレクトリへダウンロードする。
- aws providerだけでも150MBある。複数のディレクトリでterraform initすると、サイズが膨れ上がる。
#geshi(bash){{
find . -name .terraform -type d | xargs du -sh
}}

- .terraformディレクトリをすべて消す。「terraform init」で復帰するので問題ない
#geshi(bash){{
# 対象ディレクトリ一覧
find . -name .terraform -type d

# 削除実行
find . -name .terraform -type d | xargs -i rm -rf {}
}}

----
** CodePipelineから実行 [#mc004805]

- 個人的に確認したいポイント
-- stateファイルをs3 backendに置く
-- stateファイルのロック処理 -> DynamoDB table
-- コードの書き方によっては、毎回「force new resource」が発生し、既存リソースを破壊してしまうので、その確認が必要と思う

- 記事
-- [[CodePipeline で承認プロセスを設けた Terraform workspace の CI/CD パイプライン実装 | Developers.IO:https://dev.classmethod.jp/cloud/aws/terraform-workspace-cicd-pipeline-with-aws-codepipeline/]]
-- [[CodePipeline で簡単 Terraform CI/CD パイプラインの実装 | Developers.IO:https://dev.classmethod.jp/cloud/aws/simple-terraform-cicd-pipeline-with-aws-codepipeline/]]

----
** public ipの取得 [#e0009f57]

- サービスを提供しているサイト
-- https://ifconfig.co/ip
-- http://checkip.amazonaws.com/

- 記事
-- [[Terraformて&#12441;自分のパブリックIPを使う方法 | Developers.IO:https://dev.classmethod.jp/cloud/reference-my-pubic-ip-in-terraform/]]

----
** リファクタリング [#tffc2b28]

- 記事
-- [[実録!Terraform セルフリファクタリング | Developers.IO:https://dev.classmethod.jp/cloud/aws/terraform-self-refactoring/]]

----
** planやapplyの高速化 [#cdf2e141]

リソースが増えてくると、planやapplyがどんどん遅くなる。

- [[Command: plan - Terraform by HashiCorp:https://www.terraform.io/docs/commands/plan.html]]

- 並列度の指定: デフォルト10
#geshi(bash){{
export TF_CLI_ARGS_plan="--parallelism=20"
export TF_CLI_ARGS_apply="--parallelism=20"
}}

----
** listやmapの入れ子 [#s4a88ee4]

v0.11とv0.12では挙動がまったく異なる場合がある。また、関数も入れ子だと動かない場合がある。~

- 例: list[ list[ map {} ] ] のケース
#geshi(text){{{
list[
  list[
    map {
    },
    map {
    }
  ]
]
}}}

- 例: aws_instanceのebs_block_deviceのvolume_idを取得
- v0.12:
#geshi(text){{{
aws_instance.web[count.index].ebs_block_device[*].volume_id[*]
}}}
- v0.11:
#geshi(text){{{
[count.index]
# idが欲しい場合
id = aws_instance.web.*.id[count.index]

# 一度localへ入れる場合
locals {
  web_ebs1 = "${flatten(aws_instance.web.*.ebs_block_device)}"
}
...
${lookup(local.web_ebs1[count.index], "volume_id")}

}}}

- flatten()
-- v0.11: list[list[map{}]] -> list[map{}] 構造になる。

- element()
-- v0.11: list[map{}] 構造はエラー

- slice()
-- v0.11: list[map{}] 構造は成功する

----
** Terraform Cloud: [#l0b8f68d]

- https://www.hashicorp.com/products/terraform/

- 記事
-- [[5人まで無料! Terraform Cloudを使ってみた | DevelopersIO:https://dev.classmethod.jp/cloud/aws/terraform-cloud/]]
-- [[Terraform Cloud は AWS の credentials を持たせずに tfstate だけ管理することができる | DevelopersIO:https://dev.classmethod.jp/cloud/aws/manage-tfstate-terraform-cloud/]]

----
** graph: リソースの依存関係を画像で出力 [#r3a030ed]

Cycleエラーの解消などに利用できる。

- 記事
-- [[TerraformでCycleエラーが起きてるリソースだけを画像で表示して修正する | DevelopersIO:https://dev.classmethod.jp/etc/terraform-securitygroup-cycle-fix/]]

----
** count: ループ処理 [#d723c375]

EC2をN台作る等、同じ種類のリソースを複数定義する時に使える。

- [[count: Multiple Resource Instances By Count:https://www.terraform.io/docs/configuration/resources.html#count-multiple-resource-instances-by-count]]

- 「<resource>.count = 2」のように設定する。「count=0」にするとリソースは生成されない。
- 「<resource>.count」 はドキュメントに無くても設定できる。
- 「count.index」でループカウンタとして参照できる。

- 記事
-- [[terraform で入れ子ループ - Qiita:https://qiita.com/raki/items/ddb58b9b71f1e8d412a5]]
-- [[Terraformで超サクッとループでリソースを用意する方法 | DevelopersIO:https://dev.classmethod.jp/cloud/aws/terraform-network-variable/]]

----
** csv, json, yaml を読む [#aa769be5]

- [[csvdecode - Functions - Configuration Language | Terraform | HashiCorp Developer:https://developer.hashicorp.com/terraform/language/functions/csvdecode]]


- [[yamldecode - Functions - Configuration Language - Terraform by HashiCorp:https://www.terraform.io/docs/configuration/functions/yamldecode.html]]
-- v0.12以降で使える
-- jsonとは違い、コメントが書けるので「=jsonencode(yamldecode(file("vars.yaml")))」のようにjsonに変換させる事も可能。
-- vars.yml
#geshi(yaml){{{
string01: string01
string02: string02
list01:
- "aaa"
- "bbb"
dict01:
  key1: val1
  key2: val2
}}}
-- example.tf
#geshi(text){{{
output "external_yamldecode" {
  value = yamldecode(file("./vars.yml"))
}
}}}
-- 実行
#geshi(bash){{
terraform init
terraform plan
terraform apply
...
Outputs:

external_yamldecode = {
  "dict01" = {
    "key1" = "val1"
    "key2" = "val2"
  }
  "list01" = [
    "aaa",
    "bbb",
  ]
  "string01" = "string01"
  "string02" = "string02"
}
}}

- [[jsondecode - Functions - Configuration Language - Terraform by HashiCorp:https://www.terraform.io/docs/configuration/functions/jsondecode.html]]
-- v0.11以降

----
** data.external: 外部コマンドの結果を取り込む [#f126b743]

- [[External Data Source - Terraform by HashiCorp:https://www.terraform.io/docs/providers/external/data_source.html]]
-- stging型はOKだが、listやdict型は非対応なようだ。v0.11.14, v0.12.6で確認。
-- 暫定対応として、listはスペースやカンマ区切りでjoinして返す等。dictは不明。
-- [[Feature Request - Allow list/array in 'query' in 'external' data source &#183; Issue #2 &#183; terraform-providers/terraform-provider-external:https://github.com/terraform-providers/terraform-provider-external/issues/2]]
-- read-from-yaml.py
#geshi(python){{{
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# read yaml file to json
#
# Requirements:
#   python 2.7
#   pip install pyyaml

from __future__ import print_function
import sys
import json
import yaml
from pprint import pprint

def read_stdin():
    return {x.strip() for x in sys.stdin}

def main():
    """
    main
    """

    try:
        stdin_lines = read_stdin()
        for line in stdin_lines:
            if line:
                jsondata = json.loads(line)

        f = open(jsondata["in_file"], "r")
        data = yaml.load(f, Loader=yaml.SafeLoader)
        f.close()
        sys.stdout.write(json.dumps(data))
    except Exception, e:
        import traceback
        traceback.print_exc()
        sys.exit(1)

if __name__ == '__main__':
    main()

# vim: ts=4 sw=4 expandtab
}}}

-- read-from-yaml.py 単体の実行結果
#geshi(bash){{{
echo '{"in_file":"./vars.yml"}' | ./read-from-yaml.py
{"string02": "string02", "string01": "string01"}
}}}

-- example.tf
#geshi(text){{{
data "external" "yaml" {
  program = ["python", "${path.module}/read-from-yaml.py"]
  query = {
    in_file = "./vars.yml"
  }
}

output "external_yaml" {
  value = "${data.external.yaml.result}"
}

output "external_yamldecode" {
  value = yamldecode(file("./vars.yml"))
}
}}}

-- terraform実行
#geshi(bash){{
terraform init
terraform plan
terraform apply
...
Outputs:

external_yaml = {
  string01 = string01
  string02 = string02
}
}}


----
** data.remote_state: 他のtfstateのリソースを参照する [#h11a9202]

- [[Terraform: terraform_remote_state - Terraform by HashiCorp:https://www.terraform.io/docs/providers/terraform/d/remote_state.html]]
-- outputリソースのvalueを取得できる。
#geshi(text){{
output "name" {value="..."}
}}
- 参照先
#geshi(text){{
data.terraform_remote_state.example.outputs.name
}}

- 記事
-- [[【Terraform】terraform_remote_stateデータソースの使い方 | DevelopersIO:https://dev.classmethod.jp/cloud/aws/how-to-use-terraform-remote-state/]]

----
** Atlantis: terraformをpull requestベースで駆動する [#w4a0983f]

- [[Terraform Pull Request Automation | Atlantis:https://www.runatlantis.io/]]

- 記事
-- [[TerraformをPull Request上のコマンドで実行!Atlantisを試してみた | DevelopersIO:https://dev.classmethod.jp/cloud/aws/atlantis-terraform/]]


----
** providerのバージョン固定 [#d7b872f2]

- [[Providers - Configuration Language - Terraform by HashiCorp:https://www.terraform.io/docs/configuration/providers.html]]

- [[Releases &#183; terraform-providers/terraform-provider-heroku:https://github.com/terraform-providers/terraform-provider-heroku/releases]]
-- 2.0.0から互換性がなくなって、heroku_appに書いたconfig_varsが差分として出るようになった。
-- 1.9.0に固定したい場合
#geshi(){{
provider "heroku" {
    version = "= 1.9.0"
...
}
}}

----
** providerの更新 [#k7a27d84]

- https://developer.hashicorp.com/terraform/cli/commands/init#upgrade-1
- .terraformに保存されているので、最新に更新したい場合

#geshi(bash){{
terraform init -upgrade

# or
rm -rf .terraform
terraform init
}}

----
** terraformバージョンの固定 [#w79789b2]

terraformはバージョンが0.0.1でも違うとコードの互換性が無くなる事が多い。~
そのためバージョンを固定したい場合がある。

- [[Specifying a Required Terraform Version:https://www.terraform.io/docs/configuration/terraform.html#specifying-a-required-terraform-version]]

- versions.tf: v0.12以上を使うように明示する。このファイル名は「terraform 0.12upgarade」で自動的に作られる
#geshi(text){{
terraform {
  required_version = ">= 0.12.0"
}
}}

- v0.11.0以上、0.12.0未満を指定。ただし、v0.12.3で試した所、構文チェックをバージョンチェックよりも先に行うようで、v0.11の構文エラーだけ表示されて、バージョンのエラーは出ない。
#geshi(text){{
terraform {
  required_version = ">= 0.11.0, < 0.12.0"
}
}}

----
** tfenv: 複数バージョンの切替 [#b13dc1f4]

- [[tfutils/tfenv: Terraform version manager:https://github.com/tfutils/tfenv]]
- 記事
-- [[tfenvでTerraformのバージョン管理をする - Qiita:https://qiita.com/kamatama_41/items/ba59a070d8389aab7694]]

- [[Memo/Linux/anyenv]]がtfenvに対応しているので便利。

- CentOS 7.xの場合: [[Memo/Linux/Bash#a4d5a585]] が設定済みである事
#geshi(bash){{
git clone https://github.com/tfutils/tfenv.git ~/.tfenv
echo '[[ -d ~/.tfenv/bin ]] && export PATH="${PATH}:~/.tfenv/bin"' > ~/.bash.d/tfenv.sh
chmod +x ~/.bash.d/tfenv.sh
source ~/.bashrc

# v0.11.xの最新をインストール
tfenv install latest:^0.11

# カレントディレクトリをv0.11.xにする。 .terraform-version が書き換わる。~/で実行すればデフォルト値になる。
cd ~/
tfenv use 0.11.14
}}

- v0.12.xと併用
#geshi(bash){{
# 特定ディレクトリだけ0.12に変更
tfenv install latest:^0.12
# カレントディレクトリの .terraform-version が0.12.xへ変わる

# 特定ディレクトリのバージョンを0.12.xへ固定
echo latest:^0.12 > .terraform-version
}}

terraformはサイズが大きく、バージョンアップも頻繁なので古いバージョンを消したい。
- 最新の0.12だけ残し、古い0.12だけ削除
#geshi(bash){{
tfenv list | grep -o -E '0.12.[0-9]+' | sort -n | head -n -1 | xargs -i bash -c 'tfenv uninstall {}'
}}

----
** v0.14 [#dc590c2e]

- [[Upgrading to Terraform v0.14 - Terraform by HashiCorp:https://www.terraform.io/upgrade-guides/0-14.html]]
-- terraform plan時に差分のみ表示されるようになった。v0.14.7: ブロックの中の属性が一つでも変わったら、ブロック全体が差分として表示される。
-- terraform init時に「.terraform.lock.hcl」が作成されるようになった。commitする必要がある

- 記事
-- [[terraform 0.14.0 terraform.lock.hclはレビュー時に理解必須なので要約してみた - Qiita:https://qiita.com/nyamada43/items/b8becb672ad572897c25]]
-- [[Terraform 0.14 GA!したので新機能幾つか試してみた | Developers.IO:https://dev.classmethod.jp/articles/terraform-0-14-ga/]]

----
*** upgrade [#t5ca7261]

- 0.13まであった「terraform 0.XXupgrade」のコマンドは無い
- 0.12から0.14への直接アップグレードはできない? 0.13をインストールしてupgradeする必要がありそう。[[Memo/Terraform#sbbca418]]

----
** v0.13 [#a73a4500]

- [[Upgrading to Terraform v0.13 - Terraform by HashiCorp:https://www.terraform.io/upgrade-guides/0-13.html]]
-- 「terraform 0.13upgrade」がある。0.12から0.13への構文変換に使える。0.12upgradeは無くなっている

- 記事
-- [[祝GA! Terraform 0.13 新機能を使ってみた | Developers.IO:https://dev.classmethod.jp/articles/terraform013/]]
-- [[Announcing HashiCorp Terraform 0.13 General Availability:https://www.hashicorp.com/blog/announcing-hashicorp-terraform-0-13/]]


----
*** エラーの修正 [#fd34a683]

- init時のエラー
#geshi(text){{
terraform init
This configuration or its associated state refers to the unqualified provider "aws".
}}
-- 修正
#geshi(text){{
terraform state replace-provider 'registry.terraform.io/-/aws' 'registry.terraform.io/hashicorp/aws'
}}

----
*** upgrade [#sbbca418]

- upgrade
#geshi(bash){{
echo latest:^0.13 > .terraform-version
rm versions.tf
terraform 0.13upgrade
}}

- versions.tf が自動的に変更された
#geshi(text){{
terraform {
  required_version = ">= 0.13"
  required_providers {
    aws = {
      source = "hashicorp/aws"
    }
  }
}
}}

----
** v0.12 [#r373b848]

v0.11.x以下とコードの互換性が無くなかった。

- [[Upgrading to Terraform 0.12 - Terraform by HashiCorp:https://www.terraform.io/upgrade-guides/0-12.html]]
-- 以下コマンドでソースコードを0.12に対応してくれる。ただし、幾つかのエラーは解決しなかったので、手動で直す。
#geshi(bash){{
terraform 0.12upgrade
}}
-- versions.tf が作成され、version >= 0.12以上に指定される。

- リソース名に"."は使えない。「0-9, A-Z, a-z, _」にする。
-- 0.11: output "public_dns.web_01" {
-- 0.12: output "public_dns_web_01" {

- 「=」を明示的に追加する
-- 0.11: tags { ... }
-- 0.12: tags = { ... }

- リソース名をダブルクオートで括らない
-- 0.11: value = "${aws_lb.example.dns_name}"
-- 0.12: value = aws_lb.example.dns_name

- リスト型を[]の中に入れない
-- 0.11: value = ["${var.example_list}"]
-- 0.12: value = var.example_list

- 複数リソースの参照
-- 0.11: "${aws_instance.example.*.id}"
-- 0.12: aws_instance.example[*].id

- 複数リソースの件数
-- 0.11: "${length(aws_instance.example.*.id)}"
-- 0.12: length(aws_instance.example) or length(aws_instance.example[*].id)

- 複数リソース中の単一リソースの参照。indexでアクセスできない箇所でもlengthが使えるようになった。
-- 0.11: "${aws_instance.example.*.id[0]}"
-- 0.12: aws_instance.example[0].id

- 記事
-- [[Terraform v0.12で変わるHCLの記述について - Qiita:https://qiita.com/dd511805/items/6e8dd1cf8335d244cf78]]
-- [[Terraform 0.12がリリースされたのでアップグレードしてみた | DevelopersIO:https://dev.classmethod.jp/tool/terraform-upgrade-from-0-11-to-0-12/]]
-- [[Terraform 0.11→0.12で追加された新機能 | DevelopersIO:https://dev.classmethod.jp/tool/terraform-0-12-new-features/]]

----
** ベストプラクティス [#b3890718]

- [[Welcome - Terraform Best Practices:https://www.terraform-best-practices.com/]]
-- ネーミングルール等分かりやすい

- [[Terraform Recommended Practices - Terraform by HashiCorp:https://www.terraform.io/docs/enterprise/guides/recommended-practices/index.html]]
-- ディレクトリ構造の例: [[Code structure examples:https://www.terraform-best-practices.com/examples/terraform]] Small, Medium, Largeと3パターンあった

- 記事
-- [[Terraform 運用ベストプラクティス 2019 ~workspace をやめてみた等諸々~ - 長生村本郷Engineers'Blog:https://kenzo0107.github.io/2019/04/16/2019-04-17-terraform-2019-workspace/]]

-- [[【Terraform】モジュールに関する個人的ベストプラクティス - Qiita:https://qiita.com/bigwheel/items/a9e8dd4e12062ac12fe8]]
-- [[【Terraform】moduleのアンチパターンとそれに対するベストプラクティス5選 - Qiita:https://qiita.com/bigwheel/items/2b420183639416b5c6bb]]
-- [[Serverless×Terraformモジュール設計のベストプラクティスの検討~IoTデータ収集基盤の例~ | Developers.IO:https://dev.classmethod.jp/articles/investigation-of-serverless-and-terraform-module-structure/]]
-- [[Terraformのベストなプラクティスってなんだろうか | Future Tech Blog - フューチャーアーキテクト:https://future-architect.github.io/articles/20190903/]]



----
** module: リソースのモジュール化 [#aefebc48]

同一のリソースを名前やアカウント、リージョンだけ作成したい時に、コードを共通化できる。

- [[Modules | Terraform - HashiCorp Learn:https://learn.hashicorp.com/terraform/getting-started/modules.html]]
-- main.tfからmodule/側の変数を参照したい場合、module側で"output ID1 {...}"のように定義する。outputで定義すると、stateファイル内にそのID/valueが入り、terraform_remote_state で参照できる。

不便な点:
- module内のリソース名は変わっていなくても、module名を変えただけで、リソースの作り直しが発生する。-> [[move {}:https://developer.hashicorp.com/terraform/cli/state/move]]リソースが使えるようになった。
- module内でdepends_onが使えないので、moduleが使うリソースは先にできていないと実行に失敗する。v0.12.xで対応予定?
-- [[depends_on cannot be used in a module &#183; Issue #10462 &#183; hashicorp/terraform:https://github.com/hashicorp/terraform/issues/10462]]
-- リソース内からmoduleへの依存は書ける。
#geshi(text){{
aws_elb "example" {
...
  depends_on = [
    "module.example"
  ]
}}
- v0.11, v0.12: moduleを呼び出し元でループ処理(count, for_each)出来ない。module内でのループはできる。

moduleを使わない方法:
- [[Terraformでmoduleを使わずに複数環境を構築する:https://zenn.dev/smartround_dev/articles/5e20fa7223f0fd]]


記事:
- [[terraform_module_ Beginner - Speaker Deck:https://speakerdeck.com/yonasou/terraform-module-beginner]]
- [[Terraformモジュール構成のベストプラクティス - ENECHANGE Developer Blog:https://tech.enechange.co.jp/entry/2024/07/22/170825]]
- [[[Terraform]Module間の値の受け渡しについて | Developers.IO:https://dev.classmethod.jp/articles/terraform_module_coordination/]]


----
*** module内で別providerの参照 [#h6d80742]

- [[Passing Providers Explicitly:https://www.terraform.io/docs/configuration/modules.html#passing-providers-explicitly]]

- module側:
-- providerが一つだけなら、定義不用。複数ある場合、aliasで分ける。
-- modules/tunnel.tf
#geshi(text){{{
provider "aws" {
  alias = "src"
}
}}}

- 呼び出し側: 
#geshi(text){{{
provider "aws" {
  alias  = "usw1"
  region = "us-west-1"
}

module "example" {
  source    = "../modules/tunnel"
  providers = {
    aws.src = "aws.usw1"
  }
}
}}}

----
*** module内で作成されたリソースの参照 [#lc97d58b]

- module内でoutputで出力した値が、呼び出し元から参照できる
- [[Output Value Documentation:https://www.terraform.io/docs/configuration/outputs.html#accessing-child-module-outputs]]

- module/server/ec2.tf
#geshi(text){{{
variable "var1" {
  default = "var1"
}

resource "aws_instance" "server" {
...
}

output "private_ip" {
  value = aws_instance.server.private_ip
}
}}}

- module呼び出し側
#geshi(text){{{
# 変数の参照
"${module.server.var1}"

# 動的リソースの参照
"${module.server.private_ip}"
}}}


----
** 便利な関数 [#s7ffc12b]

- [[Interpolation Syntax - Terraform by HashiCorp:https://www.terraform.io/docs/configuration/interpolation.html]]

- compact(list): listから空の要素を削除する。
#geshi(bash){{
security_groups = ["${compact(list("sg-xxA", "sg-xxb", ""))}]"
}}

- replace(string, search, replace): stringをsearchで検索して、replaceで置換する。searchには[[正規表現:https://github.com/google/re2/wiki/Syntax]] も使え、"/search/" のように指定する。
-- 例: "arn:aws:iam::123456789012:role/example_global"のような文字列が欲しい場合。[[data.aws_iam_role.example.arn:https://www.terraform.io/docs/providers/aws/d/iam_role.html]]が使える場合は、そちらが良い。
#geshi(text){{{
# data.aws_caller_identity.current.arn = arn:aws:iam::123456789012:user/user01

locals {
  role_arn = "${replace(data.aws_caller_identity.current.arn, "/user\\/.+/", "")}role/example_global"
}
}}}


----
**VSCode(Visual Studio Code)拡張 [#q87022cd]

- [[Terraform - Visual Studio Marketplace:https://marketplace.visualstudio.com/items?itemName=4ops.terraform]]
-- 2023-04: WSL2環境でも、シンタックスハイライトは動作した

- [[HashiCorp Terraform - Visual Studio Marketplace:https://marketplace.visualstudio.com/items?itemName=HashiCorp.terraform]]
-- 2023-04: WSL2環境では、シンタックスハイライトが動作せず

----
**無停止での更新・デプロイ/Rolling Update [#ea5bcdff]

戦略:
- 新しいインスタンスを追加して、古いインスタンスは削除。terraform自体、updateが苦手。

-記事
--[[Zero Downtime Updates with HashiCorp Terraform:https://www.hashicorp.com/blog/zero-downtime-updates-with-terraform]]
--[[Terraformで始めるRolling deployment - Qiita:https://qiita.com/neko-neko/items/8ad86e733861bb784d2d]]

----
** 複雑な変数の定義と参照 [#r2303300]

- terraform v0.11.x ではmapにlistを入れたりできないため、複雑な変数を定義する場合、複数のmapを組み合わせて、lookup()等で参照する方法がある。
- yamlだとこんな感じの変数をterraformで定義したい。
#geshi(yaml){{
tables:
  key01:
    attr01: 10
  key02:
    attr01: 20
}}
-- example.tf: terraformでは複数リソースはcountでループするため、indexをわざわざ定義している
#geshi(text){{
variable "tables" {
  default = {
    "0" = "key01"
    "1" = "key02"
  }
}

output "tables.count" { value = "${length(var.tables)}" } # 2
output "tables.key0" { value = "${lookup(var.tables, 0)}" } # key01


variable "tables_attr01" {
  default = {
    "key01" = "10"
    "key02" = "20"
  }
}

output "tables_attr01.key01.val.way1" { value = "${lookup(var.tables_attr01, "key01")}" } # 10
output "tables_attr01.key01.val.way2" { value = "${lookup(var.tables_attr01, lookup(var.tables, 0))}" } # 10
}}
-- 実行結果
#geshi(bash){{
terraform apply
...
Outputs:

tables.count = 2
tables.key0 = key01
tables_attr01.key01.val.way1 = 10
tables_attr01.key01.val.way2 = 10
}}

----
**三項演算子/条件によりリソースの作成を制御する [#z25e1527]

- v0.8.11現在、if文は無い。
- 「<resource>.count」 はドキュメントに無くても設定できる。「count=0」にするとリソースは生成されない。

-[[Interpolation Syntax - Terraform by HashiCorp:https://www.terraform.io/docs/configuration/interpolation.html#conditionals]]
> CONDITION ? TRUEVAL : FALSEVAL


- iam_role等のglobalに一つだけあれば良いケースもこれで対応できる。
-- module側で 「create_iam_role = true」等のフラグを用意しておく。
#geshi(text){{{
# moduleを同一アカウントで、複数回使う場合、2回目以降はfalseにしておく。
variable create_iam_role { default = true }

resource "aws_iam_role" "example_global" {
  count = "${var.create_iam_role ? 1 : 0}"
  name = "example_global"
...
}}}

- "var.something"がTRUEの時に、1台のインスタンスを立てる
#geshi(c){{{
resource "aws_instance" "vpn" {
  count = "${var.something ? 1 : 0}"
}
}}}

- 指定S3 bucketのreadonly用roleを作成。"var.s3_r_bucket_arns" が空の時にはpolicyを作成しない。 aws_iam_role_policy.countはドキュメントに無いが設定できる
#geshi(c){{{
# example: ["arn:aws:iam::1234567890:root", "..."]
variable account_id_arns {
  default = []
}

# example: ["arn:aws:s3:::example1, "..."]
variable s3_r_bucket_arns {
  default = []
}

variable aws_iam_role_name { default = "assume-role-s3" }

resource "aws_iam_role" "assume-role-s3" {
  name               = "${var.aws_iam_role_name}"
  assume_role_policy = "${data.aws_iam_policy_document.assume-role.json}"
}

resource "aws_iam_role_policy" "s3-r" {
  name   = "s3-r"
  role   = "${aws_iam_role.assume-role-s3.id}"
  policy = "${data.aws_iam_policy_document.s3-r.json}"
  count  = "${ length(var.allowed_r_s3_bucket_arns) > 0 ? 1 : 0 }"
}

data "aws_iam_policy_document" "assume-role" {
  # assume role
  statement {
    actions = [
      "sts:AssumeRole",
    ]

    effect = "Allow"

    principals = {
      type        = "AWS"
      identifiers = "${var.account_id_arns}"
    }
  }
}

data "aws_iam_policy_document" "s3-r" {
  statement {
    actions = [
      "s3:ListBucket",
    ]

    resources = ["${var.s3_r_bucket_arns}"]
  }

  statement {
    actions = [
      "s3:GetObject",
    ]

    resources = ["${split( ",", "${join("/*,", var.s3_r_bucket_arns)}/*" )}"]
  }
}
}}}

----
**fmt: 自動整形 [#lb0faa62]

- カレントディレクトリの全ファイルを自動整形する
#geshi(bash){{
terraform fmt
}}

----
**複数個リソースの参照 [#u395aec4]

- terraformのいつのバージョンからか以下のような2つ以上のリソースの場合「.1.」の記述がエラー(not found for variable)になった。
#geshi(text){{
output "web-0001.public_dns"     { value = "${aws_instance.web.1.public_dns}" }
}}

- outputは'*'で複数リソースをJSONで出力可能:
#geshi(text){{
output "web.public_dns"     { value = "${aws_instance.web.*.public_dns}" }
}}

- [[element():https://www.terraform.io/docs/configuration/functions/element.html]] 添え字が要素数を超えていた場合、初めからループする。例:AZが3つで、EC2が4台以上ある時、EC2の4台目はAZ1を使って欲しい。
#geshi(text){{
subnet_id     = element(data.aws_subnet_ids.main_private.ids[*], count.index)
}}

- 添え字が要素数を超えた場合、エラーになる
#geshi(text){{
aws_instance.web.id[count.index]
}}

----
** workspace: stateを分ける [#cc2de2f5]

stateはbackend指定でS3に置けるが、通常default一つしか無い。~
dev, stg, prodのように分けてstateを管理できる。~
awsのregion毎に分けても良い。

-[[State: Workspaces - Terraform by HashiCorp:https://www.terraform.io/docs/state/workspaces.html]]

- dev
#geshi(bash){{
terraform workspace new dev
terraform workspace select dev
}}

- "${terraform.workspace}"で現在のworkspace名の参照が出来る

----
** locals: ローカル変数 [#r105258e]

-[[Configuring Local Values - Terraform by HashiCorp:https://www.terraform.io/docs/configuration/locals.html]]

- NG: variableのdefaultで別変数を参照すると「default may not contain interpolations」エラーになる
#geshi(text){{
variable "cidr_whitelist" {
  type = "list"
  default = [
    { "value" = "${var.cidr_office}" type = "IPV4" },
  ]
}}

- OK: locals は別変数の参照が可能
#geshi(text){{
locals {
  cidr_whitelist = [
    { "value" = "${var.cidr_office}" type = "IPV4" },
  ]
}
}}

- localsを複数定義すれば、前参照もできる。
#geshi(text){{{
locals {
  owner    = "taro.yamada"
  site_id  = "example-dev"
}

locals {
  common_tags = {
    owner    = local.owner
    site_id  = local.site_id
  }
}

aws_instance web {
...
  tags = merge(local.common_tags, {
        Name  = "${local.prefix}-web-01"
        role  = "web"
  })
}
}}}

----
** terraform-landscape: planの結果を見やすく [#c027fb68]

-[[GitHub - coinbase/terraform-landscape: Improve Terraform's plan output to be easier to read and understand:https://github.com/coinbase/terraform-landscape]]
--plan結果の色づけ、JSONを差分表示
-- terraform v0.11 まで対応。v0.12はterraform側のdiffが大きく変わり、非対応

-記事
--[[terraform-landscapeでterraform planのdiffをクッソ見やすく整形する - Qiita:https://qiita.com/minamijoyo/items/6b4544ecbeaec675055d]]

----

** 配列の要素の末尾に文字列を追加 [#n9cd5ef5]

配列の要素の末尾に文字列を結合したい場合がある。

-元変数: ["arn:aws:s3:::example1","arn:aws:s3:::example2"]
-求める出力: ["arn:aws:s3:::example1/*","arn:aws:s3:::example2/*"]

-記事
--[[[terraform] [小ネタ] 配列の各要素の末尾に文字列を追加した配列を返す - Qiita:https://qiita.com/sonodar/items/a2cf4557c59eae9fb0d7]]

-Terraform v0.11.7
-例:[[Amazon S3: 特定の S3 バケットへの読み取りと書き込みアクセスを許可する:https://docs.aws.amazon.com/ja_jp/IAM/latest/UserGuide/reference_policies_examples_s3_rw-bucket.html]] のポリシーを生成したい場合

#geshi(c){{
data "aws_iam_policy_document" "s3-r" {
  statement {
    actions = [
      "s3:ListBucket",
    ]

    resources = ["${var.s3_r_bucket_arns}"]
  }

  statement {
    actions = [
      "s3:GetObject",
    ]

    resources = ["${split( ",", "${join("/*,", var.s3_r_bucket_arns)}/*" )}"]
  }
}
}}

- 例: web-01~NNのPublic IP(IPv4)を、他のセキュリティグループに登録したい。セキュリティグループはCIDR形式(0.0.0.0/0)が必要
-- terraform v0.11.14
#geshi(text){{
data "aws_instances" "web" {
  instance_tags = {
    "Name" = "web-*"
  }

  instance_state_names = ["running", "stopped"]
}

# 参照する場合
"${split( ",", "${join("/32,", data.aws_instances.web.public_ips)}/32")}",
}}


----

** Terratest: インフラ自動テストツール [#mda42349]

Terraformで起動、チェック実行、破棄までを自動化

-記事
--[[構成ファイルの通りにインフラは立ち上がったのか? インフラ自動テストツール「Terratest」がオープンソースで公開 - Publickey:https://www.publickey1.jp/blog/18/_terratest.html]]

----

** タイムアウトの延長 [#d0b46da3]

リソースの作成/削除等で、timeoutする場合がある。

-[[Timeouts:https://www.terraform.io/docs/configuration/resources.html#timeouts]]
-aws_db_instanceの場合
#geshi(php){{
resource "aws_db_instance" "timeout_example" {
  name              = "mydb"
  # ...
  timeouts {
    create = "60m"
    delete = "2h"
  }
}
}}

----

** alicloud(aliyun alibaba) [#o9e2a179]

中国のalicloud用プロバイダー

-[[Provider: alicloud - Terraform by HashiCorp:https://www.terraform.io/docs/providers/alicloud/index.html]]

-記事
--[[Alibaba Cloud 日本リージョンがTerraformに対応したので試した - Qiita:https://qiita.com/mosuke5/items/a65683ce6569bffd7ef0]]

----

** ヒアドキュメントでJSON等を綺麗に書く [#e83aaf4e]

- [[Expressions - Configuration Language - Terraform by HashiCorp:https://www.terraform.io/docs/configuration/expressions.html]]
> "heredoc"

-terraformでは引用が「"」のみで、JSON等を書こうとすると、「\"」のエスケープが必要で見難い
-[[JSON Formatter & Validator:https://jsonformatter.curiousconcept.com/]] JSONの検証と整形

- ヒアドキュメント「var = <<EOT ~ EOT」が使える
- 「<<-」だと、インデントを他の変数と同じに合わせる

- 例: 
#geshi(php){{
resource "aws_iam_policy" "policy" {
    name = "test_policy"
    path = "/"
    description = "My test policy"
    policy = <<EOT
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Action": [
        "ec2:Describe*"
      ],
      "Effect": "Allow",
      "Resource": "*"
    }
  ]
}
EOT
}
}}

- ヒアドキュメント内で他リソース"${aws_s3_bucket.example.arn}"等の展開はできる。ただし、terraform v0.11 planでは値は表示されなかったが、apply後は正常だった。
- iam_policyは [[AWS: aws_iam_policy_document - Terraform by HashiCorp:https://www.terraform.io/docs/providers/aws/d/iam_policy_document.html]] を使うとJSONが出力できるのでお勧め。

----

** 変数にmap(連想配列)を使う [#pf63099e]

-type = "map" は省略できる
-aws.tf
#geshi(php){{
# https://aws.amazon.com/marketplace/fulfillment?productId=b7ee8a69-ee97-4a49-9e68-afaee216db2e&ref=cns_srchrow&versionTitle=1708
# "${lookup(var.aws_ami_ids, "ap-northeast-1_centos7_2017-10-12")}"
variable "aws_ami_ids" {
  default = {
    "us-east-1_centos7_2017-10-12"      = "ami-db48ada1"
    "us-west-2_centos7_2017-10-12"      = "ami-e535c59d"
    "ap-southeast-1_centos7_2017-10-12" = "ami-1fbad07c"
    "ap-northeast-1_centos7_2017-10-12" = "ami-e4599a82"
    "eu-central-1_centos7_2017-10-12"   = "ami-2540f74a"
    "eu-west-1_centos7_2017-10-12"      = "ami-5f76b626"
  }
}

variable "aws_region" { default = "ap-northeast-1" }
locals {
  aws_ami_id_centos7 = "${lookup(var.aws_ami_ids, "${var.aws_region}_centos7_2017-10-12")}"
}

output "aws_ami_ids" { value = "${var.aws_ami_ids}" }
output "aws_ami_id_centos7" { value = "${local.aws_ami_id_centos7}" }
}}

-実行結果
#geshi(bash){{
terraform apply
...
aws_ami_id_centos7 = ami-e4599a82
aws_ami_ids = {
  ap-northeast-1_centos7_2017-10-12 = ami-e4599a82
  ap-southeast-1_centos7_2017-10-12 = ami-1fbad07c
  eu-central-1_centos7_2017-10-12 = ami-2540f74a
  eu-west-1_centos7_2017-10-12 = ami-5f76b626
  us-east-1_centos7_2017-10-12 = ami-db48ada1
  us-west-2_centos7_2017-10-12 = ami-e535c59d
}}

----
** import block: import対象リソースをコードに書けるように [#j7947a2d]

- [[Import - Configuration Language | Terraform | HashiCorp Developer:https://developer.hashicorp.com/terraform/language/import]]
- terraform v1.5から
- importコマンドでtfsateにimportするより手間がかからず、コードの変更としてgit等に記録されるのでわかりやすいかも。


----
**import: 既存のリソースからtfstateを作成する [#p91885ac]

- import blockの方が手軽かも

-[[Command: import - Terraform by HashiCorp:https://www.terraform.io/docs/commands/import.html]]
-- 既存リソースからtfstateファイルを生成する。
-- importが実装されていないリソースも多い。
-- リソースIDを一つ一つ指定しなければいけないので面倒。
-- v0.9.4: route53をimportしようとするとcrashした。

- インポートするリソースを間違った場合、stateから削除してから、再インポート
-- [[Command: state - Terraform by HashiCorp:https://www.terraform.io/docs/commands/state/index.html]]

#geshi(bash){{
terraform state list
terraform state rm <terraform address>
terraform import <terraform address> <resource id>
}}

-記事
-- [[terraformingとAWS CLIとTerraform importを使って、tfファイルを修正するところまでのメモ | DevelopersIO:https://dev.classmethod.jp/cloud/aws/terraforming-terraform-import/]]
-- [[既存のAWS環境を後からTerraformでコード化する | DevelopersIO:https://dev.classmethod.jp/cloud/aws/aws-with-terraform/]]

----
**変数 [#db654cdb]

-[[Input Variables - Terraform by HashiCorp:https://www.terraform.io/intro/getting-started/variables.html]]

- 機密情報をtfファイルに書かないようにしたい場合。「-var "key=value"」が複数使える。ただし、パスワードをこの方法にしてしまうと、apply時に毎回同じパスワードを入力するハメになるのでお勧めしない。
#geshi(bash){{
cat var.tf
--
variable api_key {}
--
api_key=***
terraform plan -var "api_key=$api_key"
}}

- 別ファイルにしたい場合: 「-var-file=~/.secret.tfvars」
#geshi(test){{
api_key = "****"
}}

- パスワード等、ランダム文字列で良い場合は[[生成>Memo/Terraform#t775cf01]]できる。

- v0.11: listをマージしたい場合、 そのまま書ける
#geshi(test){{
security_groups = ["sg-xxxx1", "${var.list_sg}"]
}}

----
**バージョン変更によるアップグレード方法 [#e530fc0c]

-[[Upgrading to Terraform 0.9 - Terraform by HashiCorp:https://www.terraform.io/upgrade-guides/0-9.html]]
-[[Upgrading to Terraform 0.7 - Terraform by HashiCorp:https://www.terraform.io/upgrade-guides/0-7.html]]

----
**lifecycle: 指定リソースの差分を無視する [#y3b1fa14]

EBSを追加したり、SGを追加しようとするとEC2を作り直してしまう場合がある。

-[[Configuring Resources - Terraform by HashiCorp:https://www.terraform.io/docs/configuration/resources.html#lifecycle]]
--create_before_destroy: 既存リソースが有った場合、削除してから作成する
--prevent_destroy: リソース保護。削除する時にエラーになる
--ignore_changes: 差分があっても無視する

- 後から追加したroot_block_deviceの差分を無視したい。
#geshi(){{{
lifecycle {
  ignore_changes = ["root_block_device"]
}
}}}

----
**デバッグ [#g55bbc43]

-[[Debugging - Terraform by HashiCorp:https://www.terraform.io/docs/internals/debugging.html]]

-デバッグログを出す
#geshi(bash){{
TF_LOG=DEBUG terraform plan
}}


----
**output: 出力だけを見る [#f57703a8]

- [[Command: output - Terraform by HashiCorp:https://www.terraform.io/docs/cli/commands/output.html]]

public dns, IP等、後で参照したい情報を出力するのに便利。~
refreshまたはapplyした後でしか表示されない。(tfstateファイルの中身を表示しているのでは)~

#geshi(bash){{
terraform output

# json
terraform output -json
}}

- 例: 必要な項目だけをjqでtsv出力
#geshi(bash){{
terraform output -json example_domain_validation_options | jq -r ".[] | [.resource_record_name, .resource_record_type, .resource_record_value] | @tsv"

_1234567890abcdef1234567890abcdef.example.com    CNAME   _1234567890abcdef1234567890abcde.abcdefghij.acm-validations.aws.
}}


----
***outputで複数リソースの値を出力 [#h4f1d320]

[[Output Variables:https://www.terraform.io/intro/getting-started/outputs.html]]では1つの値しか出力できないようだ。~

-いつのバージョンからかJSONで出力できるようになっていた
#geshi(){{
output "web.private_ip" { value = "${aws_instance.web.*.private_ip}" }

Outputs:
web.private_ip = [
    i-1111,
    i-2222
]
}}

-複数EC2のprivate ipをカンマ区切りで出力
#geshi(){{
output "web.private_ip" { value = "${join(",",aws_instance.web.*.private_ip)}" }
}}

----
**backend: S3等にtfstateファイルを置く [#v2184c91]

- [[Backend Type: s3 | Terraform | HashiCorp Developer:https://developer.hashicorp.com/terraform/language/backend/s3]]
-- terraform 1.10からs3 backend上でのファイルでのロックが可能になった。今までのdynamodbは不要になった。
-- [[Terraform S3 Backend でステートロックのための DynamoDB が不要になる use_lockfile = true - kakakakakku blog:https://kakakakakku.hatenablog.com/entry/2025/01/17/012216]]

- 記事
-- [[Backend の S3 や DynamoDB 自体を terraform で管理するセットアップ方法 - Qiita:https://qiita.com/saiya_moebius/items/a8f8aa3683c2347d607c]]


- v0.9から backendへ変更: [[Backend Type: s3 - Terraform by HashiCorp:https://www.terraform.io/docs/backends/types/s3.html]]

- terraform.tf:
-- このファイル内で変数は使えなかった。「configuration cannot contain interpolations」エラー
#geshi(){{{
terraform {
  backend "s3" {
    profile = "myprofile"
#    shared_credentials_file = "~/.aws/config" # v0.9.3 で試しても失敗
    region  = "ap-northeast-1"
    bucket  = "mybucket"
    key     = "web/terraform.tfstate" # path/file で1バケットに複数のtfstateを置ける
    use_lockfile = false # v1.10以降。CIや複数人使用時にロックしたい場合
  }
}
}}}

- 実行: terraform v0.9.3
#geshi(bash){{
aws --profile myprofile configure

# ~/.aws/credentials に該当キーが出来ているのが重要。

terraform init

terraform plan
}}


----
** -target: 指定したリソースだけplan/apply [#gc122720]

- 「-target=resource 」オプションがある。複数指定したい場合は「-target=resource1 -target=resource2」とする
#geshi(bash){{
terraform plan -target=aws_instance.web[0]
}} 
-[[Command: plan - Terraform by HashiCorp:https://www.terraform.io/docs/commands/plan.html]]

- tfファイルから「-target <resource.name>」形式で抽出
#geshi(bash){{
cat example.tf| perl -ane 'if(/resource\s+"([^\"]+)"\s+"([^\"]+)"/){print "-target $1.$2 \\\n";}'
}}

----
**変数で配列が扱えない [#s1c6ae0c]

-記事
--[[Terraformで複数台のEC2インスタンスを構築する場合のTIPS | Developers.IO:http://dev.classmethod.jp/cloud/tips-for-creating-multiple-aws_instance-resources/]]

- %%変数に配列が使用できない。(v0.6.6で確認)%% v0.7以降で使用可能

- [v0.7以降]
#geshi(){{{
# example.tf
variable "security_groups" {                                                                                                                                                                                                              
  default = ["sg-xx", "sg-x,sg-xxx"]
}

resource "aws_instance" "web" {
  ...
  security_groups = "${var.security_groups}"
}
}}}



- [[Store arrays in variables  Issue #57  hashicorp/terraform  GitHub:https://github.com/hashicorp/terraform/issues/57]]で要望は上がっているようだ。
- [v0.7未満] 擬似的に配列を設定する
#geshi(){{{
# example.tf
variable "security_groups" {                                                                                                                                                                                                              
  default = "sg-xx,sg-x,sg-xxx"
}

resource "aws_instance" "web" {
  ...
  security_groups = "${split(",", var.security_groups)}"
}
}}}

----
** terraformer: 既存のインフラのインポート。GoogleCloudPlatform製 [#ec80ccb5]

- [[GoogleCloudPlatform/terraformer: CLI tool to generate terraform files from existing infrastructure (reverse Terraform). Infrastructure to Code:https://github.com/GoogleCloudPlatform/terraformer]]

- 記事
-- [[既存の環境からterraformのファイルを出力するterraformerを使ってみた | DevelopersIO:https://dev.classmethod.jp/cloud/terraformer-aws-check/]]

----
**Terraforming: 既存のインフラのインポート [#o8c54939]

※''terraform v0.7.0''からimport機能が実装されたため、Terraformingは不要かもしれない。

terraform.tfstate や tf形式のソースを生成する [#f45434d2]

- https://github.com/dtan4/terraforming
-- Terraformすべてのリソースには対応していない

- 記事
--[[Terraforming で既存のインフラを Terraform 管理下におく - Qiita:http://qiita.com/dtan4/items/345c56281ab0e87d6646]]

- CentOS6.xの場合。ruby 2.1.0以上なので、rbenvやrvmで新しいrubyを使えるようにする
#geshi(bash){{
rvm use 2.1.5
ruby --version
ruby 2.1.5p273 (2014-11-13 revision 48405) [x86_64-linux]

rvmsudo gem install terraforming aws-sdk
ln -s ~/.aws/config ~/.aws/credentials

# なぜか --profileオプションが動作せず
export AWS_ACCESS_KEY_ID=****
export AWS_SECRET_ACCESS_KEY=****
export AWS_REGION=ap-northeast-1
terraforming ec2 > terraforming.ec2.tf
terraforming ec2 --tfstate >terraforming.terraform.tfstate
}}

----
**複数バージョンの切り替え [#v55bc3c4]

- [[tfenv>Memo/Terraform#b13dc1f4]]が複数バージョンの切り替えが楽

- バージョンによって動作しない機能があり、古いバージョンに切り替えたい時がある。
-- 0.7.1: terraformコマンドが1ファイルになった
-- 0.6.0: CentOS6.x ReleaseMediaが起動しないのが直った?
-- 0.5.3: AWS EC2でCentOS6.x ReleaseMediaが起動しない
-- 0.3.7: 1回目は成功するが、2回目は、AWS EC2セキュリティグループが壊れる

-記事
--[[tfenvでTerraformのバージョン管理をする - Qiita:https://qiita.com/kamatama_41/items/ba59a070d8389aab7694]] tfenvというツールもある

- CentOS6.x 64bitの場合
#geshi(bash){{
TERRAFORM_VER=0.8.5
sudo mkdir -p /opt/terraform.${TERRAFORM_VER}
sudo wget -O /opt/terraform.${TERRAFORM_VER}/terraform_${TERRAFORM_VER}_linux_amd64.zip https://releases.hashicorp.com/terraform/${TERRAFORM_VER}/terraform_${TERRAFORM_VER}_linux_amd64.zip
sudo unzip -d /opt/terraform.${TERRAFORM_VER}/ /opt/terraform.${TERRAFORM_VER}/terraform_${TERRAFORM_VER}_linux_amd64.zip

# v0.7.x以上の場合
sudo alternatives --install /usr/local/bin/terraform terraform /opt/terraform.${TERRAFORM_VER}/terraform 70

# v0.6.x以下の場合
echo alternatives --install /usr/local/bin/terraform terraform /opt/terraform.${TERRAFORM_VER}/terraform 60 \\ > /tmp/install-terraform.sh
find /opt/terraform.${TERRAFORM_VER}/ -name 'terraform-*' -type f -printf ' --slave /usr/local/bin/%f %f %p \\\n' >> /tmp/install-terraform.sh
sudo bash /tmp/install-terraform.sh
}}

-バージョンの切り替え
#geshi(bash){{
alternatives --display terraform
sudo alternatives --set terraform /opt/terraform.${TERRAFORM_VER}/terraform

# 削除
sudo alternatives --set terraform /opt/terraform.${TERRAFORM_VER}/terraform
}}


----
**不具合 [#rd936ab4]

- https://github.com/hashicorp/terraform/issues
-- 1つのpolicyに1つのIAMしか関連付けが出来ない。例えばJPリージョンのIAM:s3-jp-rwにAmazonS3FullAccessを割り当てる。同様にUSリージョンにIAM:s3-us-rw を作ろうとすると、IAM:s3-jp-rwのpolicyがデタッチされる
--- または、以下の様にusersを配列にする。ただし、jpとusでディレクトリを分けている場合、相互に修正する必要があり面倒
#geshi(bash){{{
resource "aws_iam_policy_attachment" "s3-rw" {
    name = "s3-jp-rw"
    users = ["s3-jp-rw","s3-us-rw"]
    policy_arn = "arn:aws:iam::aws:policy/AmazonS3FullAccess"
}
}}}
---[[aws_iam_policy_attachment only one per policy &#183; Issue #4165 &#183; hashicorp/terraform &#183; GitHub:https://github.com/hashicorp/terraform/issues/4165]]
---[[provider/aws : IAM policy attachment/detach bug ? &#183; Issue #6045 &#183; hashicorp/terraform &#183; GitHub:https://github.com/hashicorp/terraform/issues/6045]]

- v0.6.16: v0.6.15と同様にawsリソースを作り直す不具合有り
- v0.6.15: awsリソースを全部作り直そうとする不具合がある。destroy, apply, refresh, planで確認
- v0.6.14: heroku_app SSL証明書更新でクラッシュした
- v0.4.2: awsのroot_block_deviceでvolume_size等を変更しようとすると「logicalType cannot be modified on root device」のエラーが出る。v0.3.7へダウングレードした。
- v0.4.2: aws_instance の *_block_device が毎回違うように表示される
- v0.4.2: aws_security_group の 自己参照、他セキュリティグループIDの指定がうまくいかない
- v0.4.2: --target=リソース名で、ハイフン(-)に対応していない。 例:--target=aws_route53_record.web-01 はNG、web_01ならOK

----
**インストール [#ia31a63e]

- [[tfenv>Memo/Terraform#b13dc1f4]]が複数バージョンの切り替えが楽

-[[Download Terraform - Terraform by HashiCorp:http://www.terraform.io/downloads.html]]

- CentOS6.x /usr/local/binにインストールする
#geshi(bash){{
wget -O terraform_0.3.7_linux_amd64.zip https://dl.bintray.com/mitchellh/terraform/terraform_0.3.7_linux_amd64.zip
sudo unzip terraform_0.3.7_linux_amd64.zip -d /usr/local/bin/

terraform version
Terraform v0.3.7
}}

トップ   編集 差分 バックアップ 添付 複製 名前変更 リロード   新規 一覧 検索 最終更新   ヘルプ   最終更新のRSS