Terraform(テラフォーム)について

2020年12月14日

最近、Terraformを利用する機会があったので、調査した内容を記載します。

Terraformとは

Terraformは、IaCを実現するツールです。Terraformはオープンソースであり、HashiCorpによってGo言語で開発されました。具体的にはTerraformではインフラの構成をコードで宣言します。構造化された構成ファイルでは、手動で操作することなくインフラ構成を自動で管理できます。インフラの初期プロビジョニング、更新、破棄、いずれもTerraformではコードにより宣言し、実行します。

公式サイト

https://www.terraform.io/

インフラのコード化のデメリット

  • 小規模なものであれば、CUIやクラウドの提供するマネージメントコンソールでのGUI操作で構築してしまった方が早く、IaCで構築する方が時間がかかってしまう
  • きちんと保守性を考えた設計し、運用手法やルールをチームで共有していかなければ、ただ管理する範囲が増えるだけで終わってしまう

 インフラのコード化のメリット

  • ソースコードでインフラを管理することで、VCSなりCI/CDといったソフトウェア開発のシステムをインフラにも適用でき、保守性やスケーラビリティを高めることができる
  • デメリットの裏返しで、多リージョン、多環境への対応が必要なシステムであれば、複製や再利用しやすいためコード化してしまった方が素早く正確に環境を用意できる

インストール

以下に従って OS 毎のビルド済みバイナリをダウンロードしてきてパスを通すだけ
https://www.terraform.io/intro/getting-started/install.html

Mac の場合
Homebrew でインストールできます

Homebrewでインストール

$ brew update
$ brew install terraform

terraformのバージョン確認

$ terraform -v
Terraform v0.14.2

初期設定

作業ディレクトリ作成
$ mkdir <作業ディレクトリ名>
$ vim main.tf
provider "aws" {
    access_key = "ACCESS_KEY_HERE"
    secret_key = "SECRET_KEY_HERE"
    region = "ap-northeast-1"
}
※以下の環境変数を設定しておくと、terraformコマンド実行時にクレデンシャル情報とリージョンが環境変数から自動的に読み込まれます。
AWS_ACCESS_KEY_ID
AWS_SECRET_ACCESS_KEY
AWS_DEFAULT_REGION
この場合、providerブロックには設定しなくとも動作します。

$ terraform init 

Terraformの仕組み&記述方法

tfファイルというファイルをHCLで記述し、terraformコマンドを実行すると、Providerというmoduleが 指定したサービス上のリソースをAPI経由で作成/計画/実行し、その結果の状態をtfstateというファイルで保持する

Terraformでの変数の利用

変数の定義

Terraformでは、変数はvariableブロックで定義します。variable"<変数名>"{}のように、空のブロックを与えて変数を宣言します。

変数の値の参照

変数の値はvar.<変数名>で参照することができます。${}を使うと、文字列に変数の値を埋め込むことができます。

例)

variable "test" {}

provider "aws" {
    access_key = "${var.access_key}"
    secret_key = "${var.secret_key}"
    region = "ap-northeast-1"
}

値の渡し方

変数の値の参照 コメント

変数の値はvar.<変数名>で参照することができます。${}を使うと、文字列に変数の値を埋め込むことができます。

例)

variable "test" {}

provider "aws" {
    access_key = "${var.access_key}"
    secret_key = "${var.secret_key}"
    region = "ap-northeast-1"
}

パラメータの渡し方

コマンドオプションで渡す

terraform apply -var 'container_name=test4’

環境変数で渡す

TF_VAR_ のプレフィックスを付けて環境変数を設定すると、その値が変数にロードされる。
TF_VAR_foo='env-test' terraform plan を実行すると同様の結果が得られる。

ファイルで渡す

変数の値を指定する設定ファイルを作成し、-var-fileで指定すると同様のことが出来る。

vars.tfvars

foo = "tfvars-file"
terraform plan -var-file=vars.tfvars を実行すると同様の結果が得られる。

ディレクトリ構成検討

Terraformはコードをどう配置するかというルールがありません。 だからといって自由にコードを書いてしまうと、コードを管理しづらくなったり、メンバーがコードの変更をキャッチアップしづらくなってしまいます。

Terraformのコードを扱うときは、どう管理するかというルールを定めることが大切です。 ここでは、Terraformにおけるディレクトリ構成の例を示します

├── examples
│   └── basic
│       └── main.tf  [可能な限り] このモジュールのミニマムな使用例を書きます
│                                何も変更せずともこのbasicディレクトリで
│                                terraform init && terraform apply が通るようにします
├── modules          [必要に応じて] ネストしたモジュール(このモジュール内でのみ使用するモジュール)はここで宣言します
│   ├── master-nodes [必要に応じて]
│   └── worker-nodes [必要に応じて]
├── LICENSE          [必要に応じて] 公開モジュールの場合は必ず置きましょう
├── README.md        [必須] モジュールの概要と用途を書きます。場合によっては図を含めましょう
│                           使用例は examples/basic 以下に実際に動くコードとして書き、
│                           ここからはリンクするにとどめたほうがよいです
├── variables.tf     [必須] 何もvariableがない場合でも空のファイルを作ります。
│                           またvariableのdescriptionは必ず書きます。
│                           単にリソースのargumentへ引き回しており自明に思えるときは
│                           https://www.terraform.io/docs/providers/aws/r/instance.html#ami
│                           などそのargument項目へのリンクを書くと良いです。
├── main.tf          [必須] 基本的にはここへリソース宣言を置きます。
├── another.tf       [必要に応じて] main.tfが長くなりすぎた場合はRoot Moduleと同じく
│                                 種別ごとにtfファイルを分けて書きます。
└── outputs.tf       [必須] 何もoutputがない場合でも空のファイルを作ります。
                            またoutputのdescriptionは必ず書きます。
                            単にリソースのoutputを引き回しており自明に思えるときは
                            https://www.terraform.io/docs/providers/aws/r/instance.html#id
                            などそのoutput項目へのリンクを書くと良いです。

tfstateファイルの保存場所

運用的にtfstateをローカルの環境に保存して置くのは良く無いのでs3などの共通の場所に保存する

  • Terraformが認識しているリソースの状態をStateと言う。それを保持するファイルをtfstateファイルと言う
  • バージョニング設定ありで、名前はなんでも構わない
  • tfstateの置く場所を、Backendと呼ぶ

コマンド一覧

# 初期化
$ terraform init 
# 構成をフォーマットして検証
$ terraform fmt
# dry-run
$ terraform plan
# 特定のリソースだけ実行
$ terraform plan -target={type.local-name01,type.local-name02}
# 実行
$ terraform apply 
# 既存のインフラリソースをインポート
$ terraform import aws_vpc.vpc vpc-09a9f1827bfd851f4
※予め対応するリソースをtfファイルに記述しておく必要がある

デバッグ方法

$ TF_LOG=DEBUG terraform plan

文字列操作、演算子、組み込み関数

Terraform には、文字列操作やリスト操作、よくある処理が組み込み関数として提供されている。

文字列操作

文字列結合 
main_api_gw = "app.api.${var.env_name}.mydomain.com"    

演算子

四則演算
# integer
add = "${1 + 2 + 3}" # add = 6
sub = "${10 - 5}" # sub = 5
mul = "${2 * 5}" # mul = 10
div = "${10 / 5}" # div = 2
mod = "${11 % 3}" # mod = 2

# float
add_f= "${1.5 + 2.5 + 3.5}" # add_f = 7.5
sub_f = "${10 - 4.5}" # sub_f = 5.5
mul_f = "${2.5 * 5}" # mul_f = 12.5
div_f = "${12.5 / 5.0}" # div_f = 2.5

等価演算子
true_result_eq = "${1 == 1}" # true_result_eq = true
false_result_eq = "${1 == 2}" # false_result_eq = false
true_result_ne = "${1 != 2}" # true_result_ne = true
false_result_ne = "${1 != 1}" # false_result_ne = false

比較演算子
result_gt = "${10 > 10}" # result_gt = false
result_ge = "${10 >= 10}" # result_ge = true
result_lt = "${5 < 10}" # result_lt = true
result_le = "${10 <= 5}" # result_le = false

二値論理演算子
true_result_and = "${true && true}" # true_result_and = true
false_result_and = "${truel && false}" # false_result_and = false
true_result_or = "${true || false}" # true_result_or = true
fasle_result_or = "${false || false}" # false_result_or = false
true_result_unary = "${true && ! false}" # true_result_unary = true
false_result_unary = "${!(true || false)}" # false_result_unary = false

三項演算子
true_cond = "${true ? 10 : 0}" # true_cond = 10
false_cond = "${false ? 10 : 0}" # false_cond = 0

組み込み関数

cidrhost(iprange, hostnum)

iprangeで与えられたCIDRブロック内で有効なIPアドレスのうちhostnum番目のIPアドレスを返します。hostnumが負の値の場合は後ろから数えてIPアドレスを返します。

concat(list1, list2, …)

引数で与えられたlist型の値を全て結合したlistを返します。

contains(list, element)

listがelementを要素として含んでいる場合にtrueを返します。

element

listのindex番目の値を返します。list[index – 1]と同じ挙動です。ネストされたリストに対しては正常に振る舞いません。

format(format, args, …)

一般的なフォーマットストリングと同じ使い方です。Go言語のfmtパッケージにあるSprintf()を使って実装されているので、サポートされている記法もそれに準拠しています。

index(list, elem)

element(list, index)の逆で、listの中の指定された要素のindexを返します。ネストされたリストに対しては正常に振る舞いません。

join(delim, list)

listの要素をdelimを区切り文字にして結合した文字列を返します。ネストされたリストに対しては正常に振る舞いません。

length(list)

listの要素数を返します。ネストされているリストは1として数えられます。

list(items, …)

引数で受け取った値をlistにして返します。

lookup(map, key, [default])

mapの中でkeyに対応する値を返します。keyがない場合はエラーになりますが、defaultが定義されている場合はdefaultを返します。

map(key, value, …)

2N個の引数を受け取り、奇数番目の引数にそれぞれ次の引数の値を対応づけてmapを生成し返します。奇数番目のkeyになる引数は全てstring型でなければなりません。

merge(map1, map2, …)

2つ以上のmapを結合したmapを返します。keyが重複している場合、対応する値は順に上書きされていきます。

split(delim, string)

join(delim, list)とは逆にstringをdelimで区切った文字列のリストを返します。

timestamp()

関数がコールされたときのタイムスタンプを返します。実行の度に値が変わるので、リソースの作成日時をタグ付けしたい場合やリソースの更新日時を常に記録しておきたいような場合に有用です。

title(string)

引数のstringに含まれる全ての単語の頭文字を大文字にした文字列を返します。アルファベットと数字とアンダースコア(_)以外のアスキー文字は文章のセパレータとして認識され、セパレータの前後の文字列は全て別々の単語としてパースされます。

values(map)

引数のmapが持つkeyに対応する全ての値をリストにして返します。