Terraform
#GCP #infra
今一度文法とかのドキュメント読む
Overview - Configuration Language | Terraform by HashiCorp
code:terraform.tf
<BLOCK TYPE> "<BLOCK LABEL>" "BLOCK LABEL" {
# block body
<IDENTIFIER> = <EXPRESSION> # Argument
}
Attribute ≒ Argument
参照できるが値を割り当てられない Attribute があるので、Argument と区別して呼ぶ
terraform fmt
Dependency Lock File (.terraform.lock.hcl) - Configuration Language | Terraform by HashiCorp
メタ引数
depends_on
depends_on = [ RESOURCE... ]
count
繰り返しを指定できる、Name = "Server ${count.index}" のように参照して名前にしたり
for_each
Map や Set を取る、each.key, each.value で参照
provider
使う provider を block 内で切り替えられる、provider = <PROVIDER>.<ALIAS>
lifecycle
create_before_destroy(bool), prevent_destroy(bool), ignore_changes(list of attribute names)
Resource Behavior - Configuration Language | Terraform by HashiCorp
作成 → 破棄 → in-place 更新 → in-place で更新できないものを破棄して再作成
<RESOURCE TYPE>.<NAME>.<ATTRIBUTE> で参照
Most resource dependencies are handled automatically. Terraform analyses any expressions within a resource block to find references to other objects, and treats those references as implicit ordering requirements when creating,
多くのリソースは参照していたら依存しているとみなされる
Provisioners Overview - Configuration Language | Terraform by HashiCorp
chef や puppet を使って構成できる
data リソース
ローカルのみの代表的なデータリソースはテンプレートとファイル
template_file | Data Sources | hashicorp/template | Terraform Registry
local_file | Data Sources | hashicorp/local | Terraform Registry
Provider
リソースタイプを追加する
Official / Verified / Community
Provider Configuration - Configuration Language | Terraform by HashiCorp
provider "google" { ... } ← Configuration
version はもう obsolete、requirements のほうに書く
Provider Requirements - Configuration Language | Terraform by HashiCorp
terraform { required_providers { ... } } ← こっちがRequirements
別名付けられる & configuration はその名前に対して設定する
Version Constraints - Configuration Language | Terraform by HashiCorp
google と google-beta Google Provider Versions | Guides | hashicorp/google | Terraform Registry
変数
Input Variables - Configuration Language | Terraform by HashiCorp
variable block
validation, sensitive, nullable なども設定できる
Output Values - Configuration Language | Terraform by HashiCorp
output でモジュールの出力値を親に公開できる
module.<MODULE_NAME>.<OUTPUT_NAME>
ルートモジュールは CLI に特定の値を出せる
terraform_remote_state データソースで他の state の output にアクセスできる
sensitive で出力を抑制
Local Values - Configuration Language | Terraform by HashiCorp
locals { ... }
? 使い分けどうする?
設定値として公開したいもの → variables
特定プロジェクトの設定(=ルートモジュール)なら公開したいものはない
編集するポイントですよという表明にはなる
複数の .tf にまたがる値 → variables / 特定の .tf でしか使わないもの → locals
env や region や project やらは variables
跨がらないものはまさに locals
値を組み立てた値 → locals
"${var.project_name}-${var.environment}" みたいなのは local に束縛するかなあ
モジュール
ルートモジュール = メインのやつ
module <MODULE_NAME> { source = "...", version = "..." }
Module Sources | Terraform by HashiCorp
普段使うのは Terraform レジストリ, GitHub ぐらいか、GCS や S3, HTTP などもある
providers メタ変数、親のどの provider が子で使えるか
Expressions - Configuration Language | Terraform by HashiCorp
string, number, bool, list, map
jsonencode
ストリップマーカー ~
組み込み関数 Functions - Configuration Language | Terraform by HashiCorp
Conditional Expressions - Configuration Language | Terraform by HashiCorp
三項演算子が書ける
For Expressions - Configuration Language | Terraform by HashiCorp
python のリスト内包表記みたいな感じ
Splat Expressions - Configuration Language | Terraform by HashiCorp
var.list[*].id と書くと [for o in var.list : o.id] と同じ
Dynamic Blocks - Configuration Language | Terraform by HashiCorp
dynamic "NAME" { for_each = var.settings ... } のようにしてブロック自体の繰り替えしを定義できる
Terraformerとしてコードを書いて思うこと | フューチャー技術ブログ
関数
そんなに使わない
jsonencode
yamlencode
入力に JSON や YAML を要求するやつに対してたまに使う
エスケープしながら JSON 書いたり heredoc でやる必要はない
formatformat("Hello, %s!", "Ander")
lookup lookup(map, key, default)
何か設定をまとめて map に入れておいたのを読み取る
templatefile
テンプレートエンジン
IP 系関数いろいろある cidrnetmask
defaults defaults(input, default) で input がない場合 null の場合に default を使う
toset よく使う
locals { targets = toset(["foo", "bar", "baz"])} などして for_each にわたす
Terraform Settings - Configuration Language | Terraform by HashiCorp
terraform { ... } のブロック
required_provider など
backend state を保存する場所を定義する
Backend Type: gcs | Terraform by HashiCorp
State
.tf と state から plan を作る
refreshできる
実態 → state へ反映する
terraform refresh = terraform apply -refresh-only -auto-approve
* terraform apply -refresh-only 差分を出力されるし確認も挟めるしこっちがいい
Use Refresh-Only Mode to Sync Terraform State | Terraform - HashiCorp Learn
terraform_remote_state The terraform_remote_state Data Source | Terraform by HashiCorp
別の state (≒ backend) の output を読む data リソース
State: Locking | Terraform by HashiCorp
ほとんどの backend はロックをサポートしている
terraform force-unlock で強制解除できる
State: Workspaces | Terraform by HashiCorp
1つの backend で複数の環境をさわったりする
»When to use Multiple Workspaces - State: Workspaces | Terraform by HashiCorp
In particular, organizations commonly want to create a strong separation between multiple deployments of the same infrastructure serving different development stages (e.g. staging vs. production) or different internal teams. In this case, the backend used for each deployment often belongs to that deployment, with different credentials and access controls. Named workspaces are not a suitable isolation mechanism for this scenario.
Instead, use one or more re-usable modules to represent the common elements, and then represent each instance as a separate configuration that instantiates those common elements in the context of a different backend.
State: Sensitive Data | Terraform by HashiCorp
機密情報が入っていたら state 自体を機密情報として扱う
Protect Sensitive Input Variables | Terraform - HashiCorp Learn
? Secret Manager > tfvars に書き出してから実行する? なにか良い仕組みはある?
Import | Terraform by HashiCorp
Import: Usage | Terraform by HashiCorp
リソースを書いて名前を決める
CLI Terraform CLI Documentation | Terraform by HashiCorp
設定ファイル
カレントディレクトリの *.tf を読んでくれる
ディレクトリ構成
Creating Modules - Terraform by HashiCorp
Terraform - リポジトリ構造と活用範囲を考える - Qiita
_ or -
Naming conventions - Terraform Best Practices
Use _ (underscore) instead of - (dash) in all: resource names, data source names, variable names, outputs.
backend
tfstate を保管する先
これ自体を terraform で作ると destroy のときに巻き込まれる
まあ destroy しないなら作ってもいいかな
null_resource
Provisioners Without a Resource - Terraform by HashiCorp
terraform非対応リソースをlocal-execで管理する - Qiita
data
Data Sources - Configuration Language - Terraform by HashiCorp
参照用のリソース
function
jsonencode - Functions - Configuration Language - Terraform by HashiCorp
json を設定値に埋めたい時に jsonencode({"foo": "bar"}) などする
heredoc
Expressions - Configuration Language - Terraform by HashiCorp
code:heredoc.tf
block {
value = <<-EOT
hello
world
EOT
}
${} などをエスケープするには $${} のように $ を重ねる
local
state の確認
terraform show
特定のリソースだけ plan & apply
-target={resrouce} を使う
$ terraform plan -target=google_project_iam_member.roles_workload
テンプレート
templatefile - Functions - Configuration Language - Terraform by HashiCorp
今は組み込みの function がある
setproduct
setproduct - Functions - Configuration Language - Terraform by HashiCorp
for_each = setproduct(var.foo_set var.bar_set) はできない
The given "for_each" argument value is unsuitable: "for_each" supports maps and sets of strings, but you have provided a set containing type tuple.
backend を変数にしたい
けどできない
Terraformの「ここはvariable使えないのか...」となった所 - Qiita
サービスアカウントキーの発行を Makefile にしたりしているから、そこで
terraform init -backend-config="bucket=$(TF_BACKEND_BUCKET)" とかしてもいいかもなあ
for_each リスト
The given "for_each" argument value is unsuitable: the "for_each" argument
must be a map, or set of strings, and you have provided a value of type tuple.
var や local で ["a", "b"] を使って宣言してるのは tuple
toset(["a", "b"]) する
provider バージョンの更新
書き換えて再度 init すると入る
書き換えて terraform plan だけだとだめ
tfstate おかしくなってしまったとき
既存のリソースをterraform管理にする手順 - FFFT
中身を空(resource と name だけ)にして import していく
衝突させないように
import コマンドは各リソースのドキュメントの下部に import セクションがある
https://www.terraform.io/docs/providers/google/r/storage_bucket.html#import など
import でミスったら terraform rm ... で消す?
コマンド:state rm-Terraform by HashiCorp
リソース名の rename
これを適当にやって tfstate がおかしくなってしまった
Terraformのresource名を変更 - Qiita
.tf を編集して name 部分を変更
terraform state mv {resouce}.{old_name} {resouce}.{new_name}
terraform plan して差分を見る
Terraform でサービスアカウントを管理下へ移す
Terraform では明示的に渡さないと GCE インスタンスのデフォルトサービスアカウント設定されない
のであった
サービスエージェントを参照する
プロダクトごとにあるサービスアカウント
Service agents  |  Cloud IAM のドキュメント  |  Google Cloud
google_project_service_identity | Resources | hashicorp/google | Terraform Registry
2021/6/21 ではまだ Beta
普通に email から data resource として参照しようとしてもダメぽい?
Error: "account_id" ("...") doesn't match regexp "^[a-z](?:[-a-z0-9]{4,28}[a-z0-9])$"
google_app_engine_default_service_account | Data Sources | hashicorp/google | Terraform Registry
2021/11/5 PROJECT_ID@appspot.gserviceaccount.com は参照できるようになった
BigQuery Scheduled Query
google_bigquery_data_transfer_config | Resources | hashicorp/google | Terraform Registry
手で作って import する場合 name はどうやって調べるねん
$ bq ls --transfer_config --transfer_location=asia-northeast1
PubSub Message
data は base64 エンコード文字列を期待している
json を送るなら base64encode(jsonencode({...})) などする
Scheduler
API 有効のほかに AppEngine の region が必要なのでアプリケーションが必要
google_app_engine_application | Resources | hashicorp/google | Terraform Registry
これで AppEngine の初期化だけできる
depends_on
うーん GCP API の有効かどうかを全部 depends_on にいれるのがダルい
依存先のモジュールを消したら消えるのも望んではない場合どうするのがいいのか?
locals で set
code:locals-loop.tf
locals {
cloudbuild_user_roles = toset([
"roles/cloudkms.cryptoKeyEncrypterDecrypter",
"roles/container.developer"
])
}
resource "google_project_iam_member" "roles_cloudbuild_agent" {
for_each = local.cloudbuild_user_roles
member = "serviceAccount:${data.google_project.this.number}@cloudbuild.gserviceaccount.com"
role = each.value
}
setproduct の例
code:setproduct.tf
variable "bq_additional_users_email" {
type = set(string)
default = [
"user1@example.com",
"user2@example.com",
"user3@example.com",
]
}
variable "bq_additional_user_roles" {
type = set(string)
default = [
"roles/bigquery.dataViewer",
"roles/bigquery.jobUser",
]
}
resource "google_project_iam_member" "bq_additional_users" {
for_each = {
for v in setproduct(var.bq_additional_users_email, var.bq_additional_user_roles) :
"${v0}:${v1}" => {
user = v0
role = v1
}
}
member = "user:${each.value.user}"
role = each.value.role
}
Warning: Version constraints inside provider configuration blocks are deprecated
code:before.tf
provider "google" {
project = var.project
region = var.region
version = "..."
}
これが
code:after.tf
provider "google" {
project = var.project
region = var.region
version = "..."
}
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "..."
}
}
...
}
こうかな?