2
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Terraformまとめ

Last updated at Posted at 2023-11-07

クラウドインフラの構成管理をするデファクトスタンダードとしての地位を確立しているTerraform。
この記事は、以下についてまとめたものである。

  • Terraformの概要
    • 動作イメージ
    • 利用フロー
  • Terraformの利用
    • ローカル環境の構築方法
    • Terraformのディレクトリ・ファイル構成
    • .tfファイルの記述方法
    • terraformコマンド(主要なもの)
    • 公式ドキュメント(参照することが多そうなもの)

(TODOになっているところも今後更新していく予定です)

1. Terraformの概要

  • Terraformは、HashiCorp社(Vagrant, Vault)によってGo言語で開発されたOSSで、Infratructure as Codeを実現するツール。
  • AWS,GCP,Azureの他、DatadogやHerokuなど様々なクラウドインフラの構成管理ができる
    • 類似ツールにCloudFormationがあるが、こちらはAWSのインフラリソースのみが対象
    • ちなみに、クラウドインフラ以外にローカルのDocker環境の構成管理もできる
  • インフラ構成の定義は、.tfという拡張子のファイルに、各インフラのリソースの定義をコードで記述することによって行う。
    • 具体的には、各インフラの初期化、更新、削除といった内容を記述する。
    • .tfファイルに記述されたコードに基づいてTerraformが自動でインフラを構築してくれる。
      • 利用者は、インフラリソースの構築・設定順を気にする必要はあまりなく、インフラリソース間の依存関係は基本的にTerraform側でいい感じに考慮して制御してくれる。
      • 冪等性の担保もしてくれて、コードとして記述している内容が対象のインフラリソースですでに実現されている場合、その構築処理をスキップしてくれる。
  • Terraformの主な構成要素は、TERRAFORM CORETERRAFORM PROVIDERである。

1.1. 動作イメージ

[.tfファイル]
↓
↓
↓
---------------- Terraform ------------------
[TERRAFORM CORE]
↓
↓(gRPC)
↓
[TERRAFORM PROVIDER]
↓
↓(Golang)
↓
[各社クラウドインフラが提供するSDK(API)]
---------------------------------------------
↓
↓(HTTP(s))
↓
[各社クラウドインフラのリソース]

TERRAFORM PROVIDER

  • HassiCorp社とTerraformコミュニティによってGolangで開発されているプラグインである。以下単にプロバイダと呼ぶ。
  • .tfファイルの設定に基づき、各社クラウドから提供されているAPI, SDKを利用していい感じに各社クラウドインフラリソースを操作してくれるものである。
  • TERRAFORM PROVIDERは、ローカルでgRPCサーバとして動作する。

1.2. 利用フロー

Terraformを利用して、インフラリソースの構築・設定反映をする工程は、以下の通り。

  • ①インフラリソースの設定ファイル(.tf)を記述
  • ②設定ファイルの検証
    • terraform validateコマンドを実行する
  • ③作業用ディレクトリの初期化
    • terraform initコマンドを実行する
      →設定ファイルのインフラリソース定義の反映に必要な分のプロバイダがDLされる
  • ④インフラリソースへ設定を適用するための具体的な操作を確認
    • terraform planコマンドを実行する
      →「クラウドリソースに対して、Terraformがこれからどんな操作をするのか?」の内容が表示されるので、問題ないかを確認する
  • ⑤インフラリソースへ設定を適用
    • teffaform applyコマンドを実行する

2. Terraformの利用

2.1. ローカル環境構築

公式ドキュメントを参考に、以下をインストール。

  • Terraform(※)
  • terraformコマンド補完機能

※terraformのバージョンを管理しながら使いたいならば、tfenv経由でインストールして利用する。

# tfenvをインストール
brew install tfenv

# インストール可能なterraformのバージョンを確認
tfenv list-remote

# terraformをバージョンを指定してインストール
tfenv install <バージョン>

# インストール済みのterraformのバージョンを一覧表示(*付きが現在使用しているバージョン)
tfenv list

# 使用するterraformのバージョンを切り替え
tfenv use <バージョン>

2.2. Terraformのディレクトリ・ファイル構成

設定ファイル(.tf or .tf.json)群を同じディレクトリにまとめたものを、モジュールという。

  • モジュールは、基本的にディレクトリ直下の設定ファイル群のみで構成される。
  • サブディレクトリは完全に別のモジュールとして扱われる
    • モジュール呼び出しという仕組みを使わない限り、自動的に読み込まれたりすることはない。

サブディレクトリの扱い.drawio.png

  • Terraformはモジュールのディレクトリ直下の設定ファイル内の記述すべてを評価し、それが1つの設定ファイルであるかのように動作する。
    • ブロックを別ファイルにわけるのは、純粋に読む人・メンテナンスする人にとってわかりやすくするためだけで、動作には全く影響しない。
  • Terraformは単一のルートモジュールを起点として動作する。
    • Terraform CLIでは、Terraformを起動する作業用ディレクトリ = ルートモジュール。
      • コマンドライン引数で、別ディレクトリをルートモジュールにすることもできるが、実際にはほぼやらないらしい。

2.3. .tfファイルの記述方法

  • 文字コード: UTF-8
  • 改行コード: 慣習的にLF(CRLFでも可)
  • コメント: #で一行コメントを記述

基本的な構造、構文と用語

example.tf
# --- ブロック ----
<ブロックタイプ> "<ラベル>" "<ラベル>" {
  # --- ボディ ---
  ...
  # 引数
  <引数名> = <値> or <式>
  # --- ボディここまで ---
}
# --- ブロックここまで ---

  • ブロック: 設定内容のコンテナであり、ブロックタイプ, 任意の数のラベル, ボディ({}で囲まれる部分。引数、他のブロックを含む)から構成される。
  • 引数: 引数名に「値」もしくは「式」を紐付ける形で記述する。
  • 式: 他の値を参照したり、組み合わせたりするもの。

例えば、以下のような形になる。

example.tf
resource "aws_vpc" "main" {
  cidr_block = var.base_cidr_block
}

2.3.1. ブロックタイプ

terraformブロック

Terraform自体の基本的な設定をするブロック。
モジュールに必要なプロバイダ(required_providersブロック)や、tfstateの置き場(backendブロック)をボディに記述する。

例.

example.tf
terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "~> 1.0.4"
    }
  }
  backend "gcs" {
    bucket  = "tf-state-prod"
    prefix  = "terraform/state"
  }
}
required_providersブロック

モジュールで利用する全てのプロバイダを列挙するブロック。

sample.tf
required_providers {
  <プロバイダのローカル名> = {
    source = "<プロバイダのソースアドレス>",
    version = "<プロバイダのバージョン>"
  }
}

プロバイダのローカル名はproviderブロックで使われる。

backendブロック

ステートファイル(.tfstate)の保存方法(バックエンド)を設定するブロック。

backend "<バックエンドタイプ>" {
  <バックエンドタイプ固有の設定>
}

ステートファイルは、その名の通りTerraformの管理対象であるインフラ構成の状態を保存するもの。
backendブロックで指定できるバックエンドタイプや、その固有の設定については、使用するTerraformバージョンのリファレンスを参照する必要がある。例えばgcsバックエンドタイプでは、以下のような設定をする。

terraform {
  backend "gcs" {
    bucket  = "tfstate" # 保存先のGCSバケット名
    prefix  = "terraform/production" # <保存先のバケット名以下のディレクトリ名>/<tfstateファイル名>
  }
}

valiable

TODO

providerブロック

プロバイダを使って、各種インフラリソースの詳細設定をするにあたっての、前提条件を設定するブロック。

sample.tf
provider <プロバイダのローカル名> {
  <プロバイダ固有の設定>
}

ボディにはプロバイダ固有の設定を記述する。
例えばgoogleプロバイダだとproject, regionという固有の設定がある。

sample.tf
provider "google" {
  # resourceブロックで設定するインフラリソースが所属するGoogle Cloudプロジェクトのデフォルト値
  project = "acme-app" 
  # resourceブロックで設定するインフラリソースが所属するGoogle Cloudリージョンのデフォルト値
  region  = "us-central1" 
}

ボディで設定できる項目は、各プロバイダのリファレンスを参照する必要がある。

resourceブロック

仮想ネットワークなどのインフラリソースの設定をするブロック。

sample.tf
resource "<リソースタイプ>" "<リソースのローカル名>" { # リソースタイプとローカル名の組み合わせはモジュール内で一意
    <リソースタイプ固有の設定>
}

対象のリソース(1番目のラベル)に対する詳細設定を記述(ボディ)し、任意のローカル名(2番目のラベル)を与える。
指定できるリソースタイプは、required_providersブロックで設定したプロバイダから提供されるもの。
ローカル名はモジュール内で一意である必要があり、そのモジュール内だけで有効である。
ボディで設定できる項目は、providerブロック同様、各プロバイダのリファレンスを参照する必要がある。

lifecycle.ignore_changes

resource ブロック内の特定の項目について、差分を無視する設定ができる。
ここを設定した状態で、terraform plan や terraform apply を実行すると、実際には対象項目について差分が発生する場合も no changes と表示されて実行が終了する。が、tfstateには変更後の状態がちゃんと記録される。
「差分は出ないでほしいが、実際には変わっていてほしい」というケースが稀にあるが、そんな場合は terraform show で確認。

不安になったらば

moduleブロック

「複数のインフラリソースを束ねるコンテナ(=module)」の設定をするブロック。
moduleは、

data

TODO

2.3.2. データ型

大区分 小区分 型名 概要
プリミティブ - string 文字列型
プリミティブ - number 数値型。整数も小数も扱える。
プリミティブ - bool ブーリアン型
複合 Collection list 配列の型。
配列の全ての要素が同じ型でなければならない。
明示的に宣言する場合は list(<値の型>)とする。
複合 Collection set ユニークな値の集合。
全ての要素が同じ型でなければならない。
インデックスやキーを持たず、並び順の概念がない。
複合型 Collection map キーバリューを伴う型。
全てのバリューが同じ型でなければならない。
{ "<キー>": <値>, ... }
or
{ <キー> = <値>, ... }
という形で生成できる。
ただし、数値から始まるキーの場合は、前者の引用符でくくる方でなければならない。
複合 Structural tuple 配列の型。
各要素の型は違っていてよい。
明示的に型を宣言する場合は、
tuple([<値の型>, <値の型>, ...])
という形で、要素数・要素の型の並び順ともにきっちり決まっていなければならない。
複合 Structural object キーとバリューを伴う型。
値の型はそれぞれ別でよい。
明示的に型を宣言する場合は、
object( { <キー>=<値の型>, <キー>=<値の型> } )
という形で行う。

Collection型とStructural型の違い
Collection型は全ての値が同じ型出ないとダメ、Structural型はそうでなくてよい。

複合型の選択(所感)

以下のようなフローで判断すればよさそう。

  • 計算量の観点から、利用する際に制約を満たすことができるのであれば、計算量の観点からsetを選択する
  • setの制約を満たさないのであれば配列型はlistとtupleを、 キーとバリューを伴う型はmapとobjectを「値の型が全て同じであるかないか」で選択する

参考
Types and Values(公式ドキュメント)
Type Constraints(公式ドキュメント)

2.3.3. 繰り返し処理

2.3.3.1. for_each

類似の内容のresource or data or moduleブロックを別々に記述したくないときに使う。
for_eachmap or set(string)型のデータを与えて繰り返し処理する。

例. mapの場合

resource "<リソースタイプ>" "<リソースのローカル名>" {
  for_each = tomap({
    <キー1>: <値1>
    <キー2>: <値2>
    ...
  })
  <引数1> = each.key
  <引数2> = each.value
}

例. set(string)の場合

resource "<リソースタイプ>" "<リソースのローカル名>" {
  for_each = toset(["<要素1>", "<要素2>",...})
  <引数1> = each.key
}

each.key, each.valueともに繰り返し時にsetの要素値が使われるため、<引数1> = each.valueでも可。
for_eachによって複製された各リソースへアクセスするためには、
<リソースタイプ>.<リソースのローカル名>["<mapのキー> or <set(string)の要素>"] 
とすればよい。

参考: The for_each Meta-Argument

2.3.3.2. count

for_eachと同様に類似の内容のresource or data or moduleブロックを別々に記述したくないときに使う。

こちらは、number型(かつ整数)のデータを与えて繰り返し処理させるもの。

例.

resource "<リソースタイプ>" "<リソースのローカル名>" {
  count = 3
  <引数1> = "xxx-${count.index}"
  <引数2> = count.index
}

複製された各リソースへアクセスするためには、
<リソースタイプ>.<リソースのローカル名>[<インデックス(0始まり)>]
とすればよい。

参考: The count Meta-Argument

2.3.3.3. for

resource or data or moduleブロック単位の繰り返し処理ではなく、単純に 「複合型(list, set, tuple, map, object)を、式を設定して別の複合型(tuple, object)に変換する」際に使う。

tupleへ変換する場合
# 複合型の値のみ使ってtupleへ変換する
[for <複合型の値を格納する変数> in <複合型> : <変換後tupleの値の式>]

# 複合型のキー, バリューともに使ってtupleへ変換する
[for <複合型のキー or インデックスを格納する変数>, <複合型の値を格納する変数> in <複合型> : <変換後tupleの値の式>]

具体例.

variable sample_set {
    type set(string)
    default = ["a", "b"]
}

resource "<リソースタイプ>" "<リソースのローカル名>" {
    <引数1> = [for sample in var.sample_set : upper(sample)]
}

variable sample_map {
    type map(string)
    default = {
        "a": "A",
        "b": "B"
    }
}

resource "<リソースタイプ>" "<リソースのローカル名>" {
    <引数1> = [for k, v in var.sample_map : "${k}, ${v}"]
}
objectへ変換する場合
# 複合型の値のみ使ってobjectへ変換する
{for <複合型の値を格納する変数> in <複合型> : <変換後objectのキーの式> => <変換後objectの値の式>}

# 複合型のキー, バリューともに使ってobjectへ変換する
{for <複合型のキー or インデックスを格納する変数>, <複合型の値を格納する変数> in <複合型> : <変換後objectのキーの式> => <変換後objectの値の式>}

つまり、括弧が []{}かの違いで変換後の型がtupleになるのかobjectになるのか決まる。

具体例.

variable sample_set {
    type set(string)
    default = ["a", "b"]
}

resource "<リソースタイプ>" "<リソースのローカル名>" {
    <引数1> = {for sample in var.sample_set : sample => upper(sample)}
}

variable sample_map {
    type map(string)
    default = {
        "a": "A",
        "b": "B"
    }
}

resource "<リソースタイプ>" "<リソースのローカル名>" {
    <引数1> = {for k, v in var.sample_map : ${v} => ${k}"}
}

参考: for Expressions

2.4. terraform コマンド

主要なコマンド

主要なものをTerraformを使用する際の工程順に並べると以下の通り。

terraform validate

.tfファイルを検証する

terraform init

.tfファイルに基づくクラウドインフラへの設定反映に必要なPROVIDERのダウンロード

terraform plan

クラウドリソースに対する現設定からの変化点の確認

「実際の環境の状態」、「過去にTerraformで適用した際の状態(tfstate)」、「これから適用しようとしている内容(.tf)」を加味し、結果どういう変更がなされるのか?の結果が出力される。

tfstate
Terraformで適用したインフラリソースの状態を保存しておくファイル。
最後に terraform apply した後で、Terraformを介さず手動でリソースの設定を変更してしまった場合、以下のようなメッセージで指摘される。

Terraform detected the following changes made outside of Terraform since the last "terraform apply":

terraform apply

クラウドリソースに.tfファイルの設定を反映

その他コマンド

terraform destory

.tfファイルで定義したクラウドリソースを削除

terraform fmt

.tfファイル内の標準の記述形式に則ってフォーマット

terraform show

tfstate ファイルに記録されている、「最後に terraform apply が実行された際のリソースの状態」を表示

plan,apply,destroy について

差分に現れる記号の意味

  • +: 追加
  • -: 削除
  • -/+: 置換 (削除からの追加)
  • ~: 更新 (削除からの追加じゃなくて、単純な更新)
  • <=: 読み込み? (データリソースへの適用時のみ発生しうるらしい)

特定のリソースに対してだけインフラ設定を反映させたい場合

以下のようにする。

terraform plan -target='<対象のリソース1>' -target='<対象のリソース2>' ...

リソースは、'module.<モジュール名>.<リソースタイプ>.<リソースのローカル名>'という形で指定するのが無難。
for_eachで暗黙的に生成されるresourceオブジェクトを指定する場合、
'module.<モジュール名>.<リソースタイプ>.<リソースのローカル名>["キー名"]'
みたいな形になるが、'でくくらずに実行して以下のエラーに遭遇したことがあった

Error: Invalid target "module.<モジュール名>.<リソースタイプ>.<リソースのローカル名>[<キー名>]"
│
│ Index brackets must contain either a literal number or a literal string.
╵

2.5. 公式ドキュメント

以下、特に有用と思われるドキュメント。

3. 参考

4. しばらく実務で使ってみての感想

GCP周りで使う機会があったが、以下のような問題があった。利用するうえではなかなかストレスがたまるところ・・・

  • 依存関係を明示的に記述すべき箇所があったりする。
  • 各リソースで、意味のない「etag」の差分が一生表示される問題が残っている模様。プロバイダ管理外の情報のため、ignoreすることもできないっぽい。こちらはGitHubプロバイダのものだが事象は同じっぽい(https://github.com/integrations/terraform-provider-github/issues/796)
2
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
8

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?