はじめに
第1章 セットアップ
第2章 基本操作
第3章 基本構文
第4章 全体設計
第5章 権限管理
第6章 ストレージ
第7章 ネットワーク
第8章 ロードバランサーとDNS
第9章 コンテナオーケストレーション
第10章 バッチ
第11章 鍵管理
第12章 設定管理
第13章 データストア
第14章 デプロイメントパイプライン
第15章 SSHレスオペレーション
第16章 ロギング
第17章 Terraformベストプラクティス
第18章 AWSベストプラクティス
第19章 高度な構文
第20章 tfstateファイルの管理
第21章 構造化
第22章 モジュール設計
第23章 リソース参照パターン
第24章 リファクタリング
第25章 既存リソースのインポート
第26章 チーム開発
第27章 継続的Apply
第28章 落ち穂拾い
付録A 巨人の肩の上に乗る
おわりに
『実践Terraform』では、AWSを題材にTerraformの設計と実装を学びます。ECS Fargateなどのマネージドサービスを中心にアーキテクチャ設計を行い、Terraformで実装していきます。
変更しやすいコードを維持し、本番運用に強いシステムを構築するために必要となるプラクティスもあわせて紹介します。200以上のサンプルコードを用意したので、ぜひ手を動かしながら一緒に学びましょう。
Terraformはインフラストラクチャを安全かつ効率的に管理するためのツールです。Terraformを使うと、宣言的なコードでインフラストラクチャを記述できます。コードをバージョン管理することで、システムの変更履歴を簡単に追跡できるようになります。
Terraformはインフラストラクチャを変更する前に、これからなにが起きるのかを教えてくれます。変更を反映する前に、本当に実行してよいか判断する機会が与えられるため、安心してシステムを変更できます。またTerraformは自動的に依存関係を解決するため、手作業にありがちなヒューマンエラーを大幅に削減します。
本書は「入門編」「実践編」「運用・設計編」の3部で構成されています。
第1章から第3章までが「入門編」です。第1章でAWSとTerraformのセットアップを行います。そして第2章と第3章で、Terraformの基礎知識を一気に学びます。
第4章から第16章までが「実践編」です。第4章でシステムの全体設計を行います。そして第5章から第16章では、AWSの各種リソースをTerraformで実装します。
第17章から第28章が「運用・設計編」です。コードの構造化・リファクタリング・モジュール設計・チーム開発など、本番運用に欠かせない考え方を学びます。
対象読者は「Terraformに関心のある人すべて」です。
Terraformが未経験であれば、最初から読みはじめましょう。AWSの経験が多少あれば、すんなり入門できます。
Terraformの基礎知識がすでにあるなら、第5章から第16章の気になる章をチェックしましょう。本番運用を想定した、各種AWSリソースの構築方法を習得できます。
Terraformを本番環境で運用している人には、第17章以降が役立つでしょう。ソフトウェアの設計原則をベースにしたTerraformの設計技法や、運用ノウハウを凝縮してお届けします。
動作確認はすべて、macOS上で行っています。Terraformのバージョンは0.12.5、AWSプロバイダのバージョンは2.20.0です。また本書では、次のツールはインストール済みという前提で進めます。インストールされていない場合は、事前にインストールを済ませておきましょう。
・Homebrew1
・Git2
・Docker3
なお本書では、たくさんのAWSのサービスを扱います。無料枠ではないリソースも多いので、不要になったリソースは適宜削除しましょう。
本書に記載された内容は、情報の提供のみを目的としています。したがって、本書を用いた開発、製作、運用は、必ずご自身の責任と判断によって行ってください。これらの情報による開発、製作、運用の結果について、著者はいかなる責任も負いません。
本書に記載されている会社名、製品名などは、一般に各社の登録商標または商標、商品名です。会社名、製品名については、本文中では©、®、™マークなどは表示していません。
本書籍は、技術系同人誌即売会「技術書典6」で頒布されたものを底本としています。
本章ではmacOSを前提に、AWSとTerraformのセットアップを行います。Terraform専用のIAMユーザーを作成してアクセスキーを払い出し、AWS CLIとTerraformをインストールします。
AWSでIAMユーザーを作成し、アクセスキーを払い出します。あわせてAWS CLIをインストールし、コマンドラインからAWSのAPIを操作できるようにします。
Terraform用のIAMユーザーを作成します。ブラウザーからAWSマネジメントコンソールのIAM1に移動し、「(1)ユーザー」「(2)ユーザーを追加」の順にクリックします(図1.1)。
「(1)ユーザー名」を入力します。「(2)プログラムによるアクセス」をチェックして、「(3)次のステップ: アクセス権限」をクリックします(図1.2)。
なお、「AWSマネジメントコンソールへのアクセス」のチェックは外します。このIAMユーザーはAWSマネジメントコンソールへのサインインには使用しないため、パスワードの生成は行いません。
「(1)既存のポリシーを直接アタッチ」をクリックします。「(2)AdministratorAccess」をチェックして、「(3)次のステップ: タグ」をクリックします(図1.3)。
タグは追加せず「次のステップ: 確認」をクリックします(図1.4)。
内容を確認し、「ユーザーの作成」をクリックします(図1.5)。
するとIAMユーザーが作成され、アクセスキーが表示されます(図1.6)。それではブラウザーを開いたまま、次に進みます。
AWS CLIは、Pythonパッケージマネージャーのpip3からインストールできます。
$ pip3 install awscli --upgrade
インストールできたら、AWS CLIのバージョンを確認します。
$ aws --version
aws-cli/1.16.198 Python/3.7.4 Darwin/18.6.0 botocore/1.12.188
AWSマネジメントコンソール(図1.6)からアクセスキーIDとシークレットアクセスキーをコピーし、環境変数に設定します。
$ export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
$ export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
$ export AWS_DEFAULT_REGION=ap-northeast-1
確認しましょう。AWSアカウントIDが出力されれば、正しく設定されています。
$ aws sts get-caller-identity --query Account --output text
123456789012
なお、「AdministratorAccess」ポリシーがアタッチされたアクセスキーの権限は強力です。間違っても流出しないよう、扱いには細心の注意を払いましょう。
とりあえずTerraformを試すならHomebrewが手軽です。しかし、実運用では頻繁にTerraformのバージョンアップが発生します。そこで「tfenv」や「Dockernized Terraform」で、複数のバージョンを切り替えられるようにしましょう。
なお、TerraformでAWSを操作するには「1.1.3 クレデンシャル」の設定が必要です。
Homebrewを使う場合、次のようにインストールできます。
$ brew install terraform
インストールできたら、Terraformのバージョンを確認します。
$ terraform --version
Terraform v0.12.5
tfenv2はTerraformのバージョンマネージャーです。tfenvを使うと、異なるバージョンのTerraformを簡単に扱えます。
tfenvはHomebrewでインストールできます。
$ brew install tfenv
インストールできたら、tfenvのバージョンを確認します。
$ tfenv --version
tfenv 1.0.1
「list-remote」コマンドで、インストールできるバージョンの一覧を取得できます。
$ tfenv list-remote
0.12.5
0.12.4
0.12.3
「install」コマンドを使って、0.12.5をインストールしましょう。
$ tfenv install 0.12.5
インストールできたら0.12.4など、他のバージョンもインストールします。いくつかインストールしたら「list」コマンドを使って、インストール済みのバージョンを確認します。すると、最後にインストールしたバージョンが自動的に選択されています。
$ tfenv list
0.12.5
* 0.12.4 (set by /usr/local/Cellar/tfenv/1.0.1/version)
そこで「use」コマンドを使い、バージョンを切り替えます。
$ tfenv use 0.12.5
たったこれだけでバージョンが切り替わります。確認しましょう。
$ tfenv list
* 0.12.5 (set by /usr/local/Cellar/tfenv/1.0.1/version)
0.12.4
チーム開発の場合は、「.terraform-version」ファイルをリポジトリに含めましょう。
$ echo 0.12.5 > .terraform-version
$ tfenv install
このファイルにバージョンを記述すると、チームメンバーが「tfenv install」コマンドを実行するだけで、バージョンを統一できます。
TerraformはDocker Hub3で公式イメージが配布されています。Dockerさえ入っていればどこでも実行できるシンプルさが魅力です。まずdocker pullします。
$ docker pull hashicorp/terraform:0.12.5
バージョンを確認します。
$ docker run --rm hashicorp/terraform:0.12.5 --version
Terraform v0.12.5
実行時にはソースコードがあるディレクトリをボリュームマウントして、作業ディレクトリを指定します。またAWSのクレデンシャルは、明示的に環境変数として渡します。
$ docker run --rm -i -v $PWD:/work -w /work \
-e AWS_ACCESS_KEY_ID=$AWS_ACCESS_KEY_ID \
-e AWS_SECRET_ACCESS_KEY=$AWS_SECRET_ACCESS_KEY \
-e AWS_DEFAULT_REGION=$AWS_DEFAULT_REGION \
hashicorp/terraform:0.12.5 <command>
Dockernized Terraformの欠点はコマンドが長いことです。この方法を採用する場合、エイリアスの設定やラッパーシェルの実装など、工夫が必要です。
クレデンシャル流出防止のために、「git-secrets4」を導入しましょう。git-secretsはアクセスキーやパスワードのような秘匿情報をGitでコミットしようとすると、警告してくれます。git-secretsはHomebrewでインストールできます。
$ brew install git-secrets
インストールできたらGitに設定します。
$ git secrets --register-aws --global
$ git secrets --install ~/.git-templates/git-secrets
$ git config --global init.templatedir '~/.git-templates/git-secrets'
以後は誤って秘匿情報をコミットしようとすると、エラーになります。
$ git commit -m "sample commit"
main.tf:2: access_key = "AKIAIOSFODNN7EXAMPLE"
main.tf:3: secret_key = "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY"
[ERROR] Matched one or more prohibited patterns
本章ではTerraformの基本操作を学びます。TerraformでEC2インスタンスを作成し、設定変更・再作成を行います。最後にEC2インスタンスを削除し、Terraformによるリソース管理のライフサイクルを一巡します。
事前準備として、まずは適当なディレクトリに「main.tf」というファイルを作ります。
$ mkdir example
$ cd example
$ touch main.tf
作成したmain.tfをエディターで開き、リスト2.1のように実装します。このコードではAmazon Linux 2のAMIをベースに、EC2インスタンスを作成します。
1: resource "aws_instance" "example" {
2: ami = "ami-0c3fd0f5d33134a76"
3: instance_type = "t3.micro"
4: }
TerraformのコードはHCL (HashiCorp Configuration Language)という言語で実装します。HCLはTerraformを開発している、HashiCorp社が設計した言語です。EC2インスタンスのようなリソースは「resource」ブロックで定義します。
コードを書いたら「terraform init」コマンドを実行し、リソース作成に必要なバイナリファイルをダウンロードします。「Terraform has been successfully initialized!」と表示されていれば成功です。
$ terraform init
Initializing the backend...
Terraform has been successfully initialized!
次は「terraform plan」コマンドです。このコマンドを実行すると『実行計画』が出力され、これからなにが起きるのか、Terraformが教えてくれます。
$ terraform plan
An execution plan has been generated and is shown below.
Resource actions are indicated with the following symbols:
+ create
Terraform will perform the following actions:
# aws_instance.example will be created
+ resource "aws_instance" "example" {
+ ami = "ami-0c3fd0f5d33134a76"
+ id = (known after apply)
+ instance_type = "t3.micro"
......
}
Plan: 1 to add, 0 to change, 0 to destroy.
『+』マークとともに「aws_instance.example will be created」というメッセージが出力されています。これは「新規にリソースを作成する」という意味です。
今度は「terraform apply」コマンドを実行します。このコマンドでは、あらためてplan結果が表示され、本当に実行していいか確認が行われます。
$ terraform apply
......
Do you want to perform these actions?
Terraform will perform the actions described above.
Only 'yes' will be accepted to approve.
Enter a value:
「Enter a value:」と表示され『yes』と入力すると、リソース作成を実行します1。
aws_instance.example: Still creating... [10s elapsed]
aws_instance.example: Creation complete after 13s [id=i-0ac33b62500ad8067]
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
AWSマネジメントコンソールでも、インスタンスの作成を確認できます(図2.1)。
リソースの作成に成功したら、変更してみましょう。
リスト2.1をリスト2.2のように変更し、タグを追加します。
1: resource "aws_instance" "example" {
2: ami = "ami-0c3fd0f5d33134a76"
3: instance_type = "t3.micro"
4:
5: tags = {
6: Name = "example"
7: }
8: }
コードを修正したら、terraform applyを実行します。
$ terraform apply
# aws_instance.example will be updated in-place
~ resource "aws_instance" "example" {
~ tags = {
+ "Name" = "example"
}
}
Plan: 0 to add, 1 to change, 0 to destroy.
『+』マークから『~』マークに変化して「aws_instance.example will be updated in-place」というメッセージが出力されています。これは「既存のリソースの設定を変更する」という意味です。では、変更を反映します。
aws_instance.example: Modifying... [id=i-0ac33b62500ad8067]
aws_instance.example: Modifications complete after 2s [id=i-0ac33b62500ad8067]
Apply complete! Resources: 0 added, 1 changed, 0 destroyed.
AWSマネジメントコンソールでも、Nameタグの追加が確認できます(図2.2)。
Apacheをインストールするため、リスト2.3のように変更してapplyします。
1: resource "aws_instance" "example" {
2: ami = "ami-0c3fd0f5d33134a76"
3: instance_type = "t3.micro"
4:
5: user_data = <<EOF
6: #!/bin/bash
7: yum install -y httpd
8: systemctl start httpd.service
9: EOF
10: }
$ terraform apply
# aws_instance.example must be replaced
-/+ resource "aws_instance" "example" {
ami = "ami-0c3fd0f5d33134a76"
~ id = "i-0ac33b62500ad8067" -> (known after apply)
+ user_data = "655c303ddd9e02635f849fe2993693f147" # forces replacement
}
Plan: 1 to add, 0 to change, 1 to destroy.
今度は『-/+』マークがつき「aws_instance.example must be replaced」というメッセージが出力されています。これは「既存のリソースを削除して新しいリソースを作成する」という意味です。
このメッセージは要注意です。これはリソース削除を伴うため、場合によってはサービスダウンを引き起こします。そのため、リソースが再作成される場合は念入りに確認すべきです。では、実行しましょう。
aws_instance.example: Destroying... [id=i-0ac33b62500ad8067]
aws_instance.example: Still destroying... [id=i-0ac33b62500ad8067, 10s elapsed]
aws_instance.example: Destruction complete after 20s
aws_instance.example: Creating...
aws_instance.example: Still creating... [10s elapsed]
aws_instance.example: Creation complete after 13s [id=i-0afd75edebabb1319]
Apply complete! Resources: 1 added, 0 changed, 1 destroyed.
AWSマネジメントコンソールで確認すると、最初のインスタンスがterminatedになり、新しいインスタンスが立ち上がっています(図2.3)。
このように、Terraformによるリソースの更新は、「既存リソースをそのまま変更する」ケースと「リソースが作り直しになる」ケースがあります。本番運用では、意図した挙動になるか、plan結果をきちんと確認することが大切です。
ここまでTerraformは、必要な部分だけを適切に変更してくれました。しかし、どうやってその判断をしているのでしょう。その答えが「tfstateファイル」です。
applyを一度でも実行していれば、terraform.tfstateファイルが作成されます。ファイルの中身を確認してみましょう。
$ cat terraform.tfstate
{
"version": 4,
"terraform_version": "0.12.5",
"serial": 6,
"lineage": "4c3075a8-4c5c-73d0-5bce-f88ae39a4e3b",
"outputs": {},
"resources": [
{
"mode": "managed",
"type": "aws_instance",
"name": "example",
"provider": "provider.aws",
"instances": [
{
"schema_version": 1,
"attributes": {
"ami": "ami-0c3fd0f5d33134a76",
"id": "i-0afd75edebabb1319",
"user_data": "655c303ddd9e02635f849fe2993693f147f4baf1",
......
tfstateファイルはTerraformが生成するファイルで、現在の状態を記録します。Terraformはtfstateファイルの状態とHCLのコードに差分があれば、その差分のみを変更するよう振る舞います。
デフォルトではローカルでtfstateファイルを管理しますが、リモートのストレージでも管理できます。詳細は第20章「tfstateファイルの管理」で学びます。
せっかく作ったリソースですが、「terraform destroy」コマンドで削除しましょう。
$ terraform destroy
# aws_instance.example will be destroyed
- resource "aws_instance" "example" {
- ami = "ami-0c3fd0f5d33134a76" -> null
- id = "i-0afd75edebabb1319" -> null
Plan: 0 to add, 0 to change, 1 to destroy.
ここでは『-』マークがつき「aws_instance.example will be destroyed」というメッセージが出力されました。これは「リソースを削除する」という意味です。applyコマンド同様に、実行していいか確認が行われるのでyesと入力して、削除を実行します。
aws_instance.example: Destroying... [id=i-0afd75edebabb1319]
aws_instance.example: Still destroying... [id=i-0afd75edebabb1319, 10s elapsed]
aws_instance.example: Destruction complete after 20s
Destroy complete! Resources: 1 destroyed.
AWSマネジメントコンソールを確認すると、インスタンスがterminatedになり、きちんと削除されています(図2.4)。
2019年5月にリリースされたTerraform 0.12では、言語仕様が拡張されました。そのため、0.11以前のバージョンと互換性がありません。
そこで調べ物をするときは、どのバージョンについての情報なのか、きちんと把握することが大切です。バージョンが明示されていないときは記事の日付を確認し、古い情報に惑わされないよう注意しましょう。