Terraform 導入手引き

Terraformについておさらいする。書いてる時のバージョンは 0.12.24

Terraform Main Logo Terraform Main Logo

いわゆるIaCを実現するためのツールでterraformコマンドでクラウドサービスへリソースを要求したり削除したりする。これらのリソースの要求はマニフェストファイルに記載して宣言される。

マニフェストとセクションについて

マニフェストは下のようなセクションを並べて記述する。

1
2
3
locals {
    name = "my-name-is-tanaka"
}

トップレベルのセクションはterraform・リソース・変数(3種類)・データ・プロバイダ・モジュールの6つ。

セクション: terraform

terraform セクションは特殊で要求リソースに関係せず、状態管理をするバックエンドを指定したりバージョンを指定したりする。 バックエンドのタイプは色々ある。

1
2
3
4
5
6
7
terraform {
  required_version = "= 0.12.4"
  backend "gcs" {
    bucket = "tf-state-my-proj"
    prefix = "lgtm-proj/prod-core"
  }
}

セクション: 管理対象の3つ(resource, data, provider)

  • リソースはマニフェストで管理したい対象の定義や要求を記載する
  • データはマニフェストで管理したくはないけど関連データを取得するために記載する

マニフェストで記載するリソースはプロバイダと呼ばれるプラグインが提供している。 例えばgoogle_container_clusterリソースはgoogleプロバイダが提供する。 このプロバイダに渡すパラメータも下みたくマニフェストに記載する。

  • プロバイダはリソース・データの定義を実際に利用するためのパラメータを指定する(例えばGCPのプロジェクトとか)
1
2
3
4
5
provider "google" {
  credentials = file("account.json")
  project     = "my-project-id"
  region      = "us-central1"
}

セクション: module

マニフェストファイルはディレクトリ単位で名前空間を共有しモジュールと呼ばれる。 モジュールはリポジトリ(registory.terraform.io)に公開できる。 (マニフェストのコードを確認するよりは単純に自作した方が早そう)

ほぼ モジュールはリソース・データ・プロバイダをまとめたものだ。 実際にはモジュールに全てを含めることができる。そして実験的機能を有効化するために terraform セクションを含める必要があったりする。 ただ terraformbackend ブロックをモジュールに書くのは不自然だし有効化も疑わしいので、 terraform をモジュールに入れないとならない抽象化は良くないと思っている。

モジュールを使って複数リソースを定義することがほとんどだが、いまのところリソースと全く同じというわけではない。 呼び出し方の瑣末な違いのほか、リストから複数リソースを定義する count, for_each キーワードを使うことができない。 ただしこれは対応される気配がある

1
2
3
4
module "some-good-name" {
    source = "../modules/usefull_module"
    name   = "kusomiso"
}

変数3つ: variables, locals, output

マニフェストで利用する変数は variables, locals, output の3つ。

  • variables はモジュールの引数で呼び出し時に設定できる
  • locals はモジュール内で共通化のために使う
  • output はモジュールの呼び出し元でモジュールの適用結果などを取得するのに使う

variableドキュメンテーションや型そしてバリデーションが設定でき基本的に与えられるために存在している。 terraform init ./path/to/manifest/ って指定したディレクトリもモジュール。 -var key=value オプションで値をコマンドラインから渡すことができる。

バリデーションは実験的機能なのでモジュール内で terraform セクションを使い有効にしないと動かない。 さらに WARN も出る。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
terraform {
  experiments = [variable_validation]
}

variable "apple" {
  validation {
    condition = length(setsubtract([ for spec in var.specs : length(spec.some_attrs) ], [0])) == length(toset([ for spec in var.specs : length(spec.some_attrs) ]))
    error_message = "All some_attrs' length can't be 0."
  }
}

構文・キーワード

便利なキーワード count について書いておく。リストを元に複数のリソースを定義できる。 リソースセクションの中で count 値を設定するとその数だけリソースは定義される。 それぞれのリソースで count.index は別の値( /[0, count) )として参照される。

1
2
3
4
5
6
resouce hogehoge "example" {
    count = length(var.names)

    name = var.names[count.index]
    zone = element(var.zones, count.index)
}

他に for_each キーワードを使って count.index の利用を減らすことができる。 for_eachdynamic キーワードと組み合わせてセクションを動的に生成することに使える。

前述したが現在のところ count, for_each をモジュール内で使うことはできない。 count and for_each for modules · Issue #17519から対応が追える。

for_eachresourcedynamic ブロックと組み合わせられる。 ブロックを使うとブロック名がリソースに使うと each がイテレータの名前になる。 イテレータの key はマップの場合はフィールド名でリストの場合はインデックスが入る。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
locals {
    apples = {
       test = "test_value"
       foo = {
           attr_1 = "ar_1"
           attr_2 = "ar_2"
       }
    }
}

resource "foo" {
  dynamic "orange" {
    for_each = local.apples
    content {
      key = orange.key
      value = orange.value
    }
  }
}

resource "bar" {
  for_each = local.apples
  name = each.key
  value = each.value
}

ref. https://www.terraform.io/docs/configuration/expressions.html#dynamic-blocks

運用な話

環境の差分を workspace で切り替える方法があるけど綺麗に設計しなかったり少し要件が複雑になると煩雑になる。 なので最初はモジュール化して prod, stg から呼ぶようにして整理するのがいいと思っている。 十分に汎用的なモジュールに整理できてからトップレベルモジュールを workspace で与えればいいと思っている。

また、コマンドラインから切り替えるとか怖いのでトップレベルモジュールは検証目的のフェーズ以外で variables を使わず locals を使っている。

リファクタ時のステートの移動

リファクタで一部をモジュール化したことで、ステートファイルとマニフェストがずれてしまい管理外になったりする。 そんな時は state mv サブコマンドで移動する。

1
terraform state mv google_container_cluster.main[0] module.cluster.main[0]

他 IaCツールと連携

デプロイパイプラインのために output を準備して jq などで加工して使いたい時もあった。

1
2
3
terraform output -json ouptput_name /
  | jq -r '.[] | [.name, .location] | join(" ")'
  | tee output_name.txt

開発中にビルトイン関数を確認したい

tfの関数などの実験をREPLでやりたい時がある。 たとえば element() はリストから要素を取り出すが、インデックスがリスト長より大きくても剰余が使われるので通過してしまう。とか知れる。(使わない)

1
2
3
terraform console
> element([10, 20, 30], 4)
20

Reduce関数がみつからない

リストの各要素に対して all(), any() といった関数を適用したくなることがあるが見つからない。 なので setsubtract() 関数をつかって論理式を集合操作に置き換える必要がある。

リソースの更新は信用しない

同質のリソースを再作成するのにはとても便利。だけど作成済みリソースを更新するのには慎重になった方がいい。 update と思って destroy/create が走るなんてことがないように terraform plan で確認が必須。

参考資料

comments powered by Disqus