(オライリー本)「詳解 Terraform」要点だけピックアップしてみた
概要
この本について
さすがオライリーです。網羅的かつそれぞれの項目がしっかり突き詰められています。少なくとも日本では、Terraformに関してこれ以上詳しく書かれた書籍はないかと思われます。Terraformというものの性質上、設計(事前検討事項)がものをいうので、いかに事前に知識を詰め込んでおけるかが勝負です。効率的であり、運用も考慮されたTerraform環境を作り上げたいなら、是非読んでおきましょう。
この記事について
情報が膨大ですので、初級レベルの内容は切り捨てて、中級者以上に刺さるような内容をピックアップしてみました。なので、厳密にいうと中級者以上の方に向けた要約ということになります。初級者の方が読むには辛い部分があるかもしれないので、以下の書籍を選んだ方がいいかもしれません。
要点
1章 なぜTerraformを使うのか
IaCとは何か、IaCの利点、IaCの比較が非常に高度な内容で書かれています。実際のTerraform利用にあたっては直接関係ないかもしれませんが、しっかり読み込んでおきましょう。IaC選定の判断基準としても有用そうです。
1.3 Infrastructure as Codeの利点とは
- セルフサービス(システム管理者を経由せずにデプロイできる)
- スピードと安全性
- ドキュメント化(IaCはドキュメントとしての役割をもつ)
- バージョン管理
- バリデーション(妥当性の確認)
- 再利用性
- 幸福
1.5 Terraformと他のIacツールとはどう違うのか
考慮すべきトレードオフ
- 設定管理ツールかプロビジョニングツールか
- ミュータブルなインフラかイミュータブルなインフラか(変更を前提とされたものかどうか)
- 手続き型言語か、宣言型言語か
- 汎用言語かドメイン特化言語か(さまざまな分野をまたぐか、特定分野で使われるか)
- マスタか、マスタレスか
- エージェントか、エージェントレスか
- 有償か無償か
- 大きなコミュニティか、小さなコミュニティか
- 成熟か、最先端か
- 複数のツールの組み合わせ
2章 Terraformをはじめよう
AWSの基本リソースを使ったハンズオンが書かれています。このハンズオン、単にコードだけが書かれてるような安物ではないです。ひとつひとつのブロックに対して、詳細にコメントが書かれてます。そのため、かなり有用と考えてよさそうなので、経験者の方もあえて初心に帰ってハンズオンしても学びがありそうです。
2.6 Webサーバクラスタのデプロイ
create_before_destroy
:リソースを先に作成してから元のリソースを削除する
lifecycle {
create_before_destroy = true
}
3章 Terraformステートを管理する
Terraformにおいて重要なステートについて詳細に記載されています。バックエンドの利用、ディレクトリ構成の考え方、remote_state などです。実際の利用法までしっかり書かれているので確実に押さえておきましょう。Terraformにおいては後々のコーディングに大きく影響する重要な章です。
3.2 ステートファイルの共有ストレージ
-
prevent_destroy
:削除防止
lifecycle {
prevent_destroy = true
}
- backend:
encrypt = true
ディスク上の暗号化ができる(S3との二重暗号化)
3.3 Terraform バックエンドの制限
-
バックエンドのS3とDynamo
- TerraformでS3とDynamoをローカルバックエンドからデプロイ
- 作成したS3とDynamoをbackendに追加、
terraform init
を実行(ローカルステートをS3へ)
-
backendブロックでは変数や参照を使用できない
-
backend.hcl に共通パラメータを記載、
-backend-config=backend.hcl
で記載していないパラメータを保管できる。 -
backendの共通パラメータの設定は Terragrunt でも可能
3.4 ステートファイルの分離
3.4.2 ファイルレイアウトによる分離
- 一般的なファイルレイアウト:環境ごとにフォルダを分けて、その環境内のコンポーネントごとにさらにフォルダをわけている
- .tfの命名
- dependencies.tf:データソースを格納(外部依存の部分をわかりやすくする)
3.5 terraform_remote_state
- ステートファイルは分離しても、outputとデータソースを使うことでステートファイルを経由した値の取得が可能
-
templatefile
はBashスクリプトにも使える
4章 モジュールで再利用可能なインフラを作る
モジュールの使いかたから設計まで網羅的に書かれています。モジュールはTerraformを利用する上で重要な概念なので3章同様しっかり読み込んでおきましょう。
4.5 モジュールの注意点
4.5.1 ファイルパス
パス参照
-
path.module
:定義があるモジュールが存在するファイルシステムパスを返す -
path.root
:ルートモジュールのファイルシステムパスを返す -
path.cwd
:カレントディレクトリのファイルシステムパスを返す
4.5.2 インラインブロック
モジュールでインラインブロックを使うと、モジュール内でしか変更できなくなるため、別リソースで記載したほうがよい。
4.6 モジュールのバージョン管理
モジュールは更新をかけると全環境に影響してしまうので、GitHubのURLなどをソースにしてバージョン管理するとよい。
5章 Terraformを使うためのヒントとコツ:ループ、条件分岐、デプロイ、その他つまづきポイント
ループや条件分岐でも非常に細かい解説がされています。
5.1 ループ
5.1.1 countパラメータによるループ
countの制限事項
- インラインブロックに対しては使用できない
- countは配列として扱われるため、リストの途中から要素を削除すると以降の要素をすべて削除し、作り直しをしてしまう。
5.1.2 for_each式によるループ
awsプロバイダ内で default_tags
ブロックを設定すると、すべてのAWSリソースにタグ付できる。
default_tags {
tags = {
Owner = "team-foo"
ManagedBy = "Terraform"
}
}
5.2 条件分岐
- count パラメータ:条件付きリソースに使う
- for_eachとfor式:条件付きのリソースあるいはリソース内のインラインブロックに使う
- if 文字列ディレクティブ:文字列の分岐に使う
5.2.2 for_eachとforを使った条件分岐
リソースやモジュールを条件付きで作成したいときは count
、それ以外のループや条件分岐には for_each
がおすすめ。
5.3 ゼロダウンタイム
AutoScalingGroupではダウンタイムをなくすために、先に起動設定を作成する必要がある。そのため、2.6 Webサーバクラスタのデプロイにも記載されている create_before_destroy
を使う。
5.4 Terraformのつまづきポイント
- countとfor_eachの制限事項:どんなリソース出力も参照できない
- ゼロダウンタイムの制限事項:AWS側のインスタンスの更新で回避できる。問題の解決はTerraform側ではなくAWS側でできないかをまず考えること。
- 有効なプランも失敗することがある:既存のインフラがあるなら
import
しておく - リファクタリングは難しい:変数の変更はリスクが大きい。識別子の変更でリソースが再作成されるとダウンタイムが発生する。
5.4.4 リファクタリングは難しい
- ダウンタイムを起こさずにコードをリファクタリングするなら
terraform state mv
の実行かコードにmoved
ブロックを追加する - パラメータの変更はリソースが変更されないかをドキュメントやplanコマンドで確認する
関数のドキュメント
6章 シークレットを管理する
シークレットの管理方法が記載されています。使うべき状況とツールが網羅的にメリデメまで記載されているので、この章を読み込めば、このパターンはシークレットをこう使うというのが想定できそうです。
6.3 シークレット管理ツールとTerraform
6.3.2 リソースとデータベース
データベースなどの認証情報は以下の3つの手段で管理する方法がある。それぞれメリデメがあるので状況によって使い分ける。
- 環境変数
- 暗号化されたファイル(KMS)
- シークレットストア(Secrets Manager、HashiCorp Vault)
6.3.3 ステートファイルとプランファイル
- Terraformリソースやデータソースに渡したどんなシークレットも、Terraformステートファイルにプレーンテキストとして保存されてしまう。
-
terraform plan -out=example.plan
でシークレットがプレーンテキストで保存されてしまう。
7章 複数のプロバイダを使う
リージョン、アカウント、マルチクラウドの手法が書かれています。
- リージョン:複数のproviderブロックを使い、それぞれのregionとaliasパラメータで設定する。
provider "aws" {
region = "us-east-2"
alias = "region_1"
}
provider "aws" {
region = "us-west-1"
alias = "region_2"
}
data "aws_region" "region_1" {
provider = aws.region_1
}
data "aws_region" "region_2" {
provider = aws.region_2
}
- アカウント:複数のproviderブロックを使い、それぞれを別のassume_roleブロックとaliasパラメータで設定する。
provider "aws" {
region = "us-east-2"
alias = "parent"
}
provider "aws" {
region = "us-east-2"
alias = "child"
assume_role {
role_arn = var.child_iam_role_arn
}
}
- マルチクラウド:複数のproviderブロックを使い、それぞれに違うクラウドを設定する。
※ただし、1つのモジュールで複数のプロバイダを使うのは影響度的にアンチパターンである。リージョン、アカウント、クラウドは分離し、影響範囲を限定するために別々のモジュールを使うのが良い。
7.2 同じプロバイダのコピーを複数使う
7.2.1 複数のリージョンを使う
- 注意1:複数リージョンは難しい(そもそものインフラ的な概念として)
- 注意2:エイリアスは慎重に使う(エイリアスを使って複数リージョンを1つのモジュールで管理するのではなく、各リージョンを別々のモジュールで管理すべき。これはマルチアカウントも同じ。)
エイリアスは複数のプロバイダにまたがってデプロイするインフラが完全に結合していて、すべてをまとめてデプロイしたい場合にむいている。
7.2.3 複数のプロバイダを使えるモジュールを作る
providerモジュールを再利用可能モジュール(単体では動作しないモジュール)内に定義するのは、アンチパターン。複数プロバイダを扱えるモジュールを作るには設定エイリアス(congifuration_aliases)を使う。
8章 本番レベルのTerraformコード
Terraformを実装するにあたってのtipsが書かれています。この章は読まなくてもTerraformは実装できますが、意識するかしないかでコードの質がかわりそうです。中級者〜あたりは押さえておきましょう。
8.3 本番レベルのインフラモジュール
- 小さなモジュール(数百行以上のコードを含む、あるいは密接に関係してするインフラのパーツを数個以上デプロイしないモジュールである)
- 組み合わせ可能なモジュール
- テスト可能なモジュール(テスト駆動開発、つまりサンプルコードから書くと良い)
- バージョン管理されたモジュール
- Terraformモジュールの先へ(Terraform以外のコード利用など)
8.3.3 テスト可能なモジュール
8.3.3.1 バリデーション
基本的な型制約以上のチェックのため、入力変数にvalidationブロックを追加できる。
variable "instance_type" {
type = string
validation {
condition = contains(["t2.micro", "t3.micro"], "var.instance_type)
error_message = "Only free tier is allowed: t2.micro | t3.micro."
}
}
8.3.3.2 事前条件と事後条件
- precondition(事前条件)ブロック:applyを実行する前にエラーを捕捉する
- postcondition(事後条件)ブロック:applyを実行した後のエラーを捕捉するもの
resource "aws_instance" "example" {
instance_type = "t3.micro"
ami = data.aws_ami.example.id
lifecycle {
# The AMI ID must refer to an AMI that contains an operating system
# for the `x86_64` architecture.
precondition {
condition = data.aws_ami.example.architecture == "x86_64"
error_message = "The selected AMI must be for the x86_64 architecture."
}
# The EC2 instance must be allocated a public DNS hostname.
postcondition {
condition = self.public_dns != ""
error_message = "EC2 instance must be in a VPC that has public DNS hostnames enabled."
}
}
}
8.3.4 バージョン管理されたモジュール
-
Terraformコア
- Terraformにはメジャーバージョンの後方互換性はない
- マイナーバージョンとメジャーバージョンはロックファイル(.terraform.lock.hcl) によって固定される(最低限メジャーバージョンは固定する)
-
プロバイダ
- プロバイダを明示的にバージョンアップしたい場合は
required_providers
ブロックを更新した後にterraform init -upgrade
を実行する - Terraformではダウンロードした各プロバイダのチェックサムを記録する
- プロバイダを明示的にバージョンアップしたい場合は
8.3.5 Terraformモジュールの先へ
8.3.5.1 プロビジョナ
-
TerraformモジュールからTerraform以外のコードを実行する必要がある場合に使う
-
ローカルマシン(
local-exec
)やリモートマシン上(remote-exec
)でスクリプトを実行できる。
※理由がなければユーザーデータを使う方が有用ではある -
local-exec
resource "aws_instance" "example" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
provisioner "local-exec" {
command = "echo \"Hello, World from $(uname -smp)\""
}
}
- remote-exec
resource "aws_instance" "example" {
ami = data.aws_ami.ubuntu.id
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.instance.id]
key_name = aws_key_pair.generated_key.key_name
provisioner "remote-exec" {
inline = ["echo \"Hello, World from $(uname -smp)\""]
}
connection {
type = "ssh"
host = self.public_ip
user = "ubuntu"
private_key = tls_private_key.example.private_key_pem
}
}
9章 Terraformのコードをテストする
これもまたよく議題にあがるテストです。planやlintだけは実際にデプロイできないですし、デプロイしたあとのチェックまではできません。それを解決するのが Terratest であるのと同時に、言語の取得(ここではGo)も必要のようです。なので、このレベルのテストを求めるのであれば、十分な時間が必要になりそうです。
9.2 自動テスト
apply以降の自動テスト実装は、Terraformのコードだけでは簡潔できない。他の言語を扱うことになるため、実装には大きな労力がかかることを前提としておく。
9.2.1 ユニットテスト
Terratest:コードに対してterraform applyを実行し、期待通りに動くか確認し、最後にterraform destroyを実行してくれる。Terraformのコードは呼び出して使う。
ここはサンプルコードが長いので、下記サンプルコードを参照してください
9.2.4 その他テスト手法
9.2.4.1 静的解析
validateやtflint以外にも以下のようなツールが存在する
- tfsec:セキュリティ問題の検知
- Terrascan:規則違反やセキュリティ違反の検知
9.2.4.3 サーバテスト
- InSpec:監視とテストのフレームワーク
- Severspec:サーバに対するRSpecテスト(Rubyを使ったテスト)
- Goss:迅速で簡単なサーバテストと確認
10章 チームでTerraformを使う
チームということでこれまでの章よりも大きな視点で書かれている章です。運用面を考慮にするあたって重要なポイントが記載されています。
10.1 チームでInfrastructure as Codeを採用する
10.1.1 上司を説得する
導入において上司にはTerraformの機能や利点ではなく 「障害の発生を半分に減らす方法について考えがあるんです。」 と話始めるとよい。(デプロイが自動化、高い信頼性、繰り返し可能。手動での間違いが起こらない。)
10.1.2 少しずつ進める
重要なのはステップを区切るだけではなく、その後のステップが実現できなくても前のステップが価値を持つようにする。
10.3 インフラコードのデプロイワークフロー
- バージョンを管理する
- コードをローカルで実行する
- コードに変更を加える
- 変更に対するレビューを依頼する
- 自動テストを実行する
- 変更をマージしてリリースする
- デプロイする
10.3.1 バージョン管理する
リポジトリはmodule用と稼働中インフラ用でわける(Opsチームと各インフラチームで分担)
10.3.7.1 環境間の昇格
コードの重複量を減らすため、liveリポジトリ(稼働中インフラ)では Terragrunt を使う
(variable,outputs,mainをひとつのファイルに集約できる)
※サンプルコードはこちら
さいごに(ピックアップのピックアップ)
本記事のピックアップからさらにピックアップをして、実際にハンズオンしたら面白そうかもというところを以下に記述しておきます。後続の学習に役立ててみてください。
- create_before_destroy:リソースの依存関係をコントロール
- remote_state:ステートファイルを分離した上でのデータ取得
- backend.hcl(Terragrunt):バックエンドの共通パラメータの指定
- default_tags:全AWSリソースへのタグ付け
- terraform state mv/moved:ダウンタイムを起こさないリファクタリング
- Secrets Manager、HashiCorp Vault:シークレットストア
- validationブロック:基本的な型制約以上のチェック
- precondition/postcondition:apply前後のエラー補足
- Terratest:apply実行後の自動テスト
- tfsec:セキュリティ問題の検知
- Terrascan:規則違反やセキュリティ違反の検知
- Terragrunt:コードの重複回避
※ご購入はこちら
Discussion