Terraform
eeyan
今一度文法とかのドキュメント読む
code:terraform.tf
<BLOCK TYPE> "<BLOCK LABEL>" "BLOCK LABEL" {
# block body
<IDENTIFIER> = <EXPRESSION> # Argument
}
Attribute ≒ Argument
参照できるが値を割り当てられない Attribute があるので、Argument と区別して呼ぶ
terraform fmt
メタ引数
depends_on = [ RESOURCE... ]
繰り返しを指定できる、Name = "Server ${count.index}" のように参照して名前にしたり
Map や Set を取る、each.key, each.value で参照
使う provider を block 内で切り替えられる、provider = <PROVIDER>.<ALIAS>
create_before_destroy(bool), prevent_destroy(bool), ignore_changes(list of attribute names)
作成 → 破棄 → 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,
多くのリソースは参照していたら依存しているとみなされる
chef や puppet を使って構成できる
data リソース
ローカルのみの代表的なデータリソースはテンプレートとファイル
Provider
リソースタイプを追加する
Official / Verified / Community
provider "google" { ... } ← Configuration
version はもう obsolete、requirements のほうに書く
terraform { required_providers { ... } } ← こっちがRequirements
別名付けられる & configuration はその名前に対して設定する
変数
variable block
validation, sensitive, nullable なども設定できる
output でモジュールの出力値を親に公開できる
module.<MODULE_NAME>.<OUTPUT_NAME>
ルートモジュールは CLI に特定の値を出せる
terraform_remote_state データソースで他の state の output にアクセスできる
sensitive で出力を抑制
locals { ... }
? 使い分けどうする?
設定値として公開したいもの → variables
特定プロジェクトの設定(=ルートモジュール)なら公開したいものはない
編集するポイントですよという表明にはなる
複数の .tf にまたがる値 → variables / 特定の .tf でしか使わないもの → locals
env や region や project やらは variables
跨がらないものはまさに locals
値を組み立てた値 → locals
"${var.project_name}-${var.environment}" みたいなのは local に束縛するかなあ
モジュール
ルートモジュール = メインのやつ
module <MODULE_NAME> { source = "...", version = "..." }
普段使うのは Terraform レジストリ, GitHub ぐらいか、GCS や S3, HTTP などもある
providers メタ変数、親のどの provider が子で使えるか
string, number, bool, list, map
jsonencode
ストリップマーカー ~
三項演算子が書ける
python のリスト内包表記みたいな感じ
var.list[*].id と書くと [for o in var.list : o.id] と同じ
dynamic "NAME" { for_each = var.settings ... } のようにしてブロック自体の繰り替えしを定義できる
関数
そんなに使わない
入力に JSON や YAML を要求するやつに対してたまに使う
エスケープしながら JSON 書いたり heredoc でやる必要はない
formatformat("Hello, %s!", "Ander") lookup lookup(map, key, default) 何か設定をまとめて map に入れておいたのを読み取る
テンプレートエンジン
defaults defaults(input, default) で input がない場合 null の場合に default を使う locals { targets = toset(["foo", "bar", "baz"])} などして for_each にわたす
terraform { ... } のブロック
required_provider など
State
.tf と state から plan を作る
実態 → state へ反映する
terraform refresh = terraform apply -refresh-only -auto-approve
* terraform apply -refresh-only 差分を出力されるし確認も挟めるしこっちがいい 別の state (≒ backend) の output を読む data リソース
ほとんどの backend はロックをサポートしている
1つの backend で複数の環境をさわったりする
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 自体を機密情報として扱う
? Secret Manager > tfvars に書き出してから実行する? なにか良い仕組みはある?
リソースを書いて名前を決める
設定ファイル
カレントディレクトリの *.tf を読んでくれる
ディレクトリ構成
_ or -
Use _ (underscore) instead of - (dash) in all: resource names, data source names, variable names, outputs.
backend
tfstate を保管する先
これ自体を terraform で作ると destroy のときに巻き込まれる
まあ destroy しないなら作ってもいいかな
null_resource
data
参照用のリソース
function
json を設定値に埋めたい時に jsonencode({"foo": "bar"}) などする
heredoc
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
テンプレート
今は組み込みの function がある
setproduct
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 を変数にしたい
けどできない
サービスアカウントキーの発行を 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 おかしくなってしまったとき
中身を空(resource と name だけ)にして import していく
衝突させないように
import コマンドは各リソースのドキュメントの下部に import セクションがある
import でミスったら terraform rm ... で消す?
リソース名の rename
これを適当にやって tfstate がおかしくなってしまった
.tf を編集して name 部分を変更
terraform state mv {resouce}.{old_name} {resouce}.{new_name}
terraform plan して差分を見る
のであった
サービスエージェントを参照する
プロダクトごとにあるサービスアカウント
2021/6/21 ではまだ Beta
普通に email から data resource として参照しようとしてもダメぽい?
Error: "account_id" ("...") doesn't match regexp "^[a-z](?:[-a-z0-9]{4,28}[a-z0-9])$"
2021/11/5 PROJECT_ID@appspot.gserviceaccount.com は参照できるようになった
BigQuery Scheduled Query
手で作って import する場合 name はどうやって調べるねん
$ bq ls --transfer_config --transfer_location=asia-northeast1
PubSub Message
data は base64 エンコード文字列を期待している
json を送るなら base64encode(jsonencode({...})) などする
Scheduler
API 有効のほかに AppEngine の region が必要なのでアプリケーションが必要
これで 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) :
}
}
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 = "..."
}
}
...
}
こうかな?