febc技術メモ

Japanese version of http://febc-yamamoto.hatenablog.com

【モダンTerraform】意外と便利!? Miscプロバイダーたち(概要編&Nullプロバイダー編)

f:id:febc_yamamoto:20180130182943p:plain

モダンTerraformシリーズです。

今回はTerraformのプロバイダーのうち、最近充実してきているMiscプロバイダーについてご紹介します。

Miscプロバイダーってなに?

最近Terraformのプロダイバーが増えてきたため、ドキュメントプロダイバーの分類が行われました。
現在は以下のような分類がなされています。

分類 概要
Major Cloud いわゆる3大クラウド(AWS/Azure/GCP)+α
Cloud ↑以外のパブリック/プライベートクラウドやHerokuなどのPaaSなど
Infrastructure Software KubernetesやRancherなどの基盤系ソフトウェア(ConsulやRabbitMQなども)
Network CDNDNSなどネットワーク系
VCS バージョン管理システム系(Bitbucket/GitHub/GitLab)
Monitor & System Management モニタリングやシステム管理系(OpsGenieやPagerDutyなども)
Database データベース(InfluxDB/MySQL/PostgreSQL)
Misc. その他諸々
Community サードパーティ製プロバイダー

Misc.プロダイバーはこの中でも分類しきれなかったその他のものですね。

Miscプロバイダーにはどんなものが含まれてるの?

現在は以下のプロバイダーが含まれています。

分類 概要
Archive ファイルやディレクトリのアーカイブを行う(現在はzipのみ対応)
Cobbler Linuxのネットワークインストール環境を構築/管理してくれるツールCobbler用プロバイダー
External 任意のプログラムをData Sourceとして利用する
Ignition ContainerLinux(CoreOS)で利用されているプロビジョニングユーティリティIgnition用の構成ファイルの出力
Local ローカルファイルの作成/参照を行う
Null 特殊な空リソース
Random ランダム値の生成を行う
Template テンプレート機能の提供
TLS キーペアの生成やCSRの生成、自己署名証明書の発行など

この中でも特によく使われるのは以下2つだと思います。

今回はこの中からNullプロバイダーについて紹介し、Templateプロバイダーやそれ以外のプロバイダーについては次回以降に紹介します。

Nullプロバイダー

NullプロバイダーはTerraformのリソースライフサイクルにおいて何も処理を行わないという特殊なリソースを提供します。
と言われてもわからないですよね。

これは通常のTerraformのリソースだけでは実現が難しいいくつかのケースに対応するためのものです。

Terraformは待ち合わせ処理が苦手

Terraformではtfファイルで定義されているリソース間の依存関係を検出し、自動的に構築順序を制御してくれます。
例えば以下のtfファイルの場合、リソースの構築順序はどうなるでしょうか?

# ディスクの定義
resource sakuracloud_disk "disk" {
  name = "foobar"
}

# サーバの定義
resource sakuracloud_server "server" {
  name  = "foobar"
  # 先程定義したディスクのIDを参照
  disks = ["${sakuracloud_disk.disk.id}"]
}

この場合、sakuracloud_server.serverリソースはsakuracloud_disk.diskリソースのIDを参照しているため、

sakuracloud_server.serverリソースがsakuracloud_disk.diskに依存している

状態となります。

Terraformはサーバを作成するにはディスクが必要という状態を検出し、

  • 1) ディスク
  • 2) サーバ

の順に作成してくれます。
(なお、依存関係はterraform graphコマンドで以下例のように表示できるGraphViz形式でグラフを出力してくれます) f:id:febc_yamamoto:20180204163928p:plain

では以下の場合はどうなるでしょうか?

# ディスクの定義(1本目)
resource sakuracloud_disk "disk01" {
  name  = "disk01"
}
# ディスクの定義(2本目)
resource sakuracloud_disk "disk02" {
  name  = "disk02"
}

# サーバの定義(1台目)
resource sakuracloud_server "server01" {
  name = "server01"
  # 先程定義したディスクのIDを参照
  disks = ["${sakuracloud_disk.disk01.id}"]
}
# サーバの定義(2台目)
resource sakuracloud_server "server02" {
  name = "server02"
  # 先程定義したディスクのIDを参照
  disks = ["${sakuracloud_disk.disk02.id}"]
}

先程の例にディスク/サーバを追加しそれぞれ2つずつリソースを作成する例となっています。 依存関係は以下のようになります。 f:id:febc_yamamoto:20180204165407p:plain

この場合、ディスク -> サーバの組み合わせの作成順序はこれまで通りですが、2つのディスク/サーバの作成順序はどうなるのでしょうか?

この辺りはTerraformの賢いところで、互いに関連しない(依存しない)リソースについては並列で処理が行われます。
先程の図の①の2つのディスクについては互いに関連しないので並列で作成が行われます。

しかし、中には並列で作成されると困る場面もあります。
例えばクラスタの構築などでは、先にマスターとなるノードをプロビジョニングしてからスレーブ側のノードをプロビジョニングする必要がある場合があります。
並列でサーバ作成されるとこの順序を守ることができません。
また、全てのノードがクラスタに参加した後でプロビジョニングを別途実行したいというような場合もあるでしょう。

そこでTerraformではこの問題を解決するいくつかの方法を提供しており、そのうちの一つがNullリソースです。

以下の例では2台のサーバの作成が完了してからプロビジョニングを行う例となっています。
2台のサーバの作成後というタイミング指定のためにNullリソースを利用しています。

# ...ディスクやサーバの定義(省略)...

# 2台のサーバ作成後に実行するプロビジョニング
locals {
    server_ids = ["${sakuracloud_server.server01.id}", "${sakuracloud_server.server02.id}"]
}

resource null_resource "provisioning" {
  triggers = {
    depends = "${join(",", local.server_ids)}" # サーバ01と02のIDを指定
  }

  provisioner "local-exec" {
    command = "run_initialize_cluster_playbook.sh"
  }
}

この例の場合の依存関係は以下のようになります。 f:id:febc_yamamoto:20180204172137p:plain

Nullリソースのtriggersに(localsを通じて)サーバ01/02のIDを指定することで依存関係を示しています。
依存される側が先に作成されますので、この例では結果的にサーバ2台の作成後にNullリソースに設定されたプロビジョニング処理が実行されることになります。

おまけ: 別解としてdepends_on

今回みたいに単純な例ならNullリソースでなくてもMeta-Parametersの中のdepends_onを利用することでも順序の制御が可能です。

参考記事: 【モダンTerraform】v0.11以降でdynamicとfor_eachが実装されるかも

各リソースにdepends_onパラメータを指定することで依存関係が指定できますので、これを利用して順序の制御を行います。

# サーバの定義(2台目)
resource sakuracloud_server "server02" {
  # ...

  # 1台目のサーバに依存することを明示
  # (結果的に1台目のサーバ作成後に2台目の作成が行われる)
  depends_on = ["sakuracloud_server.server01"]

  provisioner "local-exec" {
    command = "run_initialize_cluster_playbook.sh"
  }
}

f:id:febc_yamamoto:20180204173231p:plain

この方法は単純な反面、関連するリソース数が増えてくるとdepends_onを各所に書かないといけないというデメリットもありますので適材適所で使い分けましょう。

Null データソース(今ではほぼ出番なし)

NullプロバイダーにはNullリソースのデータソース版であるNullデータソースというものも存在します。

これは以下のようにOutput用に値を参照する部分を一箇所にまとめたり、再利用するために利用します。

data "null_data_source" "values" {
  inputs = {
    all_server_ids = "${concat(aws_instance.green.*.id, aws_instance.blue.*.id)}"
    all_server_ips = "${concat(aws_instance.green.*.private_ip, aws_instance.blue.*.private_ip)}"
  }
}

resource "aws_elb" "main" {
  # ...

  instances = "${data.null_data_source.values.outputs["all_server_ids"]}"
}

output "all_server_ids" {
  value = "${data.null_data_source.values.outputs["all_server_ids"]}"
}

output "all_server_ips" {
  value = "${data.null_data_source.values.outputs["all_server_ips"]}"
}

が、現在ではLocal Valuesの仕組みがあるためほぼ使うことはないでしょう。

参考: 【モダンTerraform】VariableとLocal Valuesの使い分けについて

Local Valuesとの違いとしてはterraform graphへの表示有無があります(Local Valuesはgraph上表示されない)が、実用上これが問題になるケースはほとんどないと思います。

ここでもやっぱりLocal Valuesを積極的に使いましょう。

終わりに

ということで今回はNullプロバイダーを扱いました。
うまく使わないと見通しの悪いtfファイルになってしまいますが、順序や依存関係の細かな調整を行うのにはNullリソースが便利ですので状況に応じて適宜使っていきましょう。

以上です。