Open22

Terraformの勉強メモ

sakataku1991sakataku1991

このスクラップについて

https://www.amazon.co.jp/dp/B07XT7LJLC/ref=cm_sw_r_tw_dp_GVDA969X57ADJM4FYXCC
Terraformを使えるようになるために書籍『実践Terraform AWSにおけるシステム設計とベストプラクティス』の学習を開始。

Terraformのセットアップ時など、実行するコマンドまわりの記述が少し不親切に感じたので自分用にメモ。

※このスクラップでのページ数の表記はKindle版のそれに準拠しています。

教材のサンプルコード

https://github.com/tmknom/example-pragmatic-terraform

この教材、その時々のファイル構成やコードの全体像に関する記載がなくって、シンプルに順序立ったハンズオンの形式を成していないのがちょっと辛いですねー。
どういう状態でterraform applyすればいいのかよくわからなくて...。自分みたいな初心者だとその行間を読むのがなかなか難しいです。

https://www.udemy.com/course/iac-with-terraform/
Terraformを勉強するならこの動画教材も良さそうです。
余裕があればこれもやってみようかな...。

sakataku1991sakataku1991

第1章

セットアップ

1.1 AWS

1.1.3 クレデンシャル

P.19
環境変数の設定

ターミナル
export AWS_ACCESS_KEY_ID=(※自分のアクセスキー ID)
ターミナル
export AWS_SECRET_ACCESS_KEY=(※シークレットアクセスキー)
ターミナル
export AWS_DEFAULT_REGION=ap-northeast-1

P.20
AWSアカウントIDの確認

ターミナル
aws sts get-caller-identity --query Account --output text

以下のように表示されればAWSアカウントIDの設定確認はOK。

ターミナル
123456789012

※AWSアカウントID(12桁の半角数字)が表示される。

1.1 Terraform

1.2.1 Homebrew

P.21
Terraformのインストール(Homebrew経由)

以下のコマンドでTerraformをインストールする。

ターミナル
brew install terraform

P.21
インストールしたTerraformのバージョンを確認

ターミナル
terraform --version

以下のように表示されればTerraformのインストールはOK。

ターミナル
Terraform v1.1.7
on darwin_amd64

1.2.2 tfenv

P.21
tfenvのインストール(Homebrew経由)

「tfenv」とは?
Terraformのバージョン管理ツールのこと。
Rubyの場合「rbenv」というバージョン管理ツールがあるが、それのTerraform版といったイメージ。

以下のコマンドでtfenvをインストールする。

ターミナル
brew install tfenv

エラーが出るので指示通りにコマンドを実行する。

ターミナル
brew unlink terraform

再度tfenvのインストールコマンドを実行。

ターミナル
brew install tfenv

P.21
インストールしたtfenvのバージョンを確認

ターミナル
tfenv --version

以下のように表示されればtfenvのインストールはOK。

ターミナル
tfenv 2.2.3

P.21
tfenvのインストール(補足)

一度Terraformをunlinkしてしまったので、現状だとTerraformのバージョンが確認できない上にTerraformを使うこともできない。
なので今度はtfenvを使って改めてTerraform(最新版)をインストールする。

ターミナル
tfenv install latest

tfenvでインストールしたTerraformのバージョンを確認。

ターミナル
tfenv list

以下のように表示される。

ターミナル
1.1.7

tfenvで、使いたいTerraformのバージョンを指定する。

ターミナル
tfenv use 1.1.7

tfenv listで表示されたバージョンの数字を選ぶこと!

改めてTerraformのバージョンを確認する。

ターミナル
terraform --version

以下のように表示されれば、今度こそTerraformのインストールはOK。

ターミナル
Terraform v1.1.7
on darwin_amd64

P.22
以下のコマンドでインストール可能なTerraformのバージョンを確認することができる。

ターミナル
tfenv list-remote

対して、こちらのtfenv listコマンドの場合はすでに自分のPCにインストール済みのTerraformのバージョンだけが表示される。

ターミナル
tfenv list

P.24
チーム開発を考慮した場合のTerraformのバージョン指定方法

.terraform-versionという名前のファイルを作成すればいいらしい。
https://github.com/tfutils/tfenv#terraform-version-file
これも.ruby-versionのTerraform版といったところか。

ファイルの内容はバージョンの数字だけでいいのかな?

.terraform-version
1.1.7

※これは後で使用方法等検証する必要アリ。

1.2.3 Dockernized Terraform

ここからDockerも絡んでくる。
既存のdocker-compose.ymlを編集する必要がありそう。
本来であれば自身のポートフォリオのdocker-compose.ymlにTerraformの設定を追加、DockerでTerraformを動かしたいところではあるが、ちょっと今の自分の知識だと実装する自信がないのでここは後回し。
いずれ、api、front、dbと一緒にterraformもDockerで動かすように設定したい。

1.3 git-secrets

P.25
git-secretsのインストール(Homebrew経由)

「git-secrets」とは?
Gitの管理下にあるファイルの中に、認証情報などの第三者に知られてはならないデータが含まれていた場合、そのことを警告してくれるツールのこと。

以下のコマンドでgit-secretsをインストールする。

ターミナル
brew install git-secrets

git-secretsのインストールが完了したら、続けて自身の開発環境(PC)のGitにgit-secretsの設定を追加する。以下3つのコマンドを実行。

ターミナル
git secrets --register-aws --global
ターミナル
git secrets --install ~/.gittemplates/git-secrets
ターミナル
git config --global init.templatedir '~/.git-templates/git-secrets'

※これも後で動作を検証する必要アリ。

sakataku1991sakataku1991

第2章

基本操作

2.1 リソースの作成

※ここから、自身のポートフォリオにTerraformを実装する体で作業を進めていく。

P.27
Terraformのリソースの作成ことはじめ

※ターミナルで自身のアプリのルートディレクトリに移動しておく。

Terraform用のディレクトリ(今回はterraformディレクトリ)を作成する。

ターミナル
mkdir terraform

作成したterraformディレクトリに移動。

ターミナル
cd terraform

main.tfというファイルを作成する。

ターミナル
touch main.tf

2.1.1 HCL(HashiCorp Configuration Language)

作成したmain.tfファイルをエディターで開き、以下のように編集する。

リスト2.1:EC2インスタンスの定義

/myapp/terraform/main.tf
resource "aws_instance" "example" {
  ami           = "ami-0c3fd0f5d33134a76"
  instance_type = "t3.micro"
}

ソース:example-pragmatic-terraform/1.tf at main · tmknom/example-pragmatic-terraform
※このサンプルコードは、Amazon Linux 2のAMIをベースにEC2インスタンスを作成する内容となっている。

2.1.2 terraform init

P.28
main.tfを編集したら以下のコマンドを実行し、リソース作成に必要なバイナリファイルをダウンロードする。

以下のコマンドでバイナリファイルをダウンロードする。

ターミナル
terraform init

2.1.3 terraform plan

P.28
terraform planコマンドの実行。

Terraformの実行計画を出力する。

ターミナル
terraform plan

2.1.4 terraform apply

P.29
terraform applyコマンドの実行。

Terraformの実行を確認する。

ターミナル
terraform apply

terraform applyコマンドの実行後、以下のように表示される。

ターミナル
Enter a value:

yesと入力してEnterキーを押す。

Chromeなどブラウザを立ち上げ自身のAWSアカウントでAWSマネジメントコンソールにログインし、EC2の管理画面へと移動。TerraformによってEC2インスタンスが作成されたかどうか確認する。

※このときAWSのリージョンを特に指定していないが、たとえば自身の作業地域が日本の関東圏であれば、Terraformがいい感じに判断して自動的に東京リージョン、「アジアパシフィック(東京)ap-northeast-1」にEC2インスタンスを作成してくれるっぽい。

2.2 リソースの更新

2.2.1 リソースの設定変更

P.30
main.tfを更新する。

main.tfファイルを以下のように編集する。

リスト2.2:タグを追加

/myapp/terraform/main.tf
resource "aws_instance" "example" {
  ami           = "ami-0c3fd0f5d33134a76"
  instance_type = "t3.micro"

  tags = {
    Name = "example"
  }
}

ソース:example-pragmatic-terraform/2.tf at main · tmknom/example-pragmatic-terraform
※ここではタグを追加している。

main.tfファイルを編集したらterraform applyコマンドを実行。リソースに更新内容を反映させる。

ターミナル
terraform apply

更新の確認コマンドが表示される。

ターミナル
Enter a value:

ここでもyesと入力しEnterキーを押して反映。

AWSコンソールでEC2を確認。インスタンスに「example」という名前が付いていればOK。

2.2.2 リソースの再作成

P.33
インスタンスにApacheをインストールする。

main.tfファイルを以下のように編集する。

リスト2.3:User DataでApacheをインストール

/myapp/terraform/main.tf
resource "aws_instance" "example" {
  ami           = "ami-0c3fd0f5d33134a76"
  instance_type = "t3.micro"

  user_data = <<EOF
    #!/bin/bash
    yum install -y httpd
    systemctl start httpd.service
EOF
}

ソース:example-pragmatic-terraform/3.tf at main · tmknom/example-pragmatic-terraform
※Apacheをインストールする設定を追加。

編集内容を反映させるためterraform applyコマンドを実行。

ターミナル
terraform apply

yesと入力しEnterキーを押して反映。

AWSコンソールでEC2を確認。最初に作成されたリソースが終了・削除され、新しいリソースが作り直されている。

2.3 tfstateファイル

2.3.1 リソースの設定変更

P.34
※tfstateファイルについての説明。

2.4 リソースの削除

P.36
作成したリソースを削除する方法。

terraform destroyコマンドを実行することでリソースを削除することができる。

ターミナル
terraform destroy

yesと入力しEnterキーを押して反映。

AWSコンソールでEC2を確認。作成したリソースがすべて終了・削除されている。

sakataku1991sakataku1991

第3章

基本構文

3.1 変数

P.39
「変数」の定義。
※Terraformにおける「変数」についての説明。

variableを使うと変数を定義することができる。

3.2 ローカル変数

P.40
「ローカル変数」の定義。
※Terraformにおける「ローカル変数」についての説明。

localsを使うと変数を定義することができる。

3.3 出力値

P.40
「出力値」の定義。
※Terraformにおける「出力値」についての説明。

outputを使うと変数を定義することができる。

3.4 データソース

P.42
※「データソース」についての説明。

データソースを使うと外部データを参照できる。

3.5 プロバイダ

P.42
※「プロバイダ」についての説明。

AWSやGCP、Azureなど、クラウドサービスは複数存在するが、それらAPIの違いを吸収するのがプロバイダの役割。

3.6 参照

P.44
作成したインスタンスにブラウザでアクセスする。

ここまでの設定ではインスタンスにアクセスできない。
アクセスするにはセキュリティグループが必要。
main.tfファイルを以下のように編集する。

リスト3.6:EC2向けセキュリティグループの定義
リスト3.7:EC2にセキュリティグループを追加

/myapp/terraform/main.tf
resource "aws_security_group" "example_ec2" {
  name = "example-ec2"

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_instance" "example" {
  ami                    = "ami-0c3fd0f5d33134a76"
  instance_type          = "t3.micro"
  vpc_security_group_ids = [aws_security_group.example_ec2.id]

  user_data = <<EOF
    #!/bin/bash
    yum install -y httpd
    systemctl start httpd.service
EOF
}

output "example_public_dns" {
  value = aws_instance.example.public_dns
}

ソース:example-pragmatic-terraform/6.tf at main · tmknom/example-pragmatic-terraform
ソース:example-pragmatic-terraform/7.tf at main · tmknom/example-pragmatic-terraform
※80番ポートへのアクセスを許可する設定(セキュリティグループ)を追加。
※ここは読んでいてすごくわかり辛かったが、どうやらmain.tfファイルにリスト3.6のコードとリスト3.7のコードを合わせて記述すればいいらしい。

main.tfファイルを編集したらterraform applyコマンドを実行。リソースを新たに作成する。

ターミナル
terraform apply

yesと入力しEnterキーを押してリソースの作成を実行。

リソースの作成が完了したら、以下のように表示される。

ターミナル
Apply complete! Resources: 2 added, 0 changed, 0 destroyed.

Outputs:

example_public_dns = "ec2-XX-XX-XXX-XXX.ap-northeast-1.compute.amazonaws.com"

example_public_dnsに出力されているアドレスec2-XX-XX-XXX-XXX.ap-northeast-1.compute.amazonaws.com(「X」の部分には実際には数字が入っている)をコピーし、ブラウザでアクセスしてみる。Apacheの「Test Page」というタイトルのページが表示されればOK。
Terraformで作成したリソースをブラウザで参照できるようになった。

3.7 組み込み関数

P.46
Terraformには、「文字列操作」や「コレクション操作」など、よくある処理が組み込み関数として用意されている。

具体例
外部ファイルを読み込むfile関数

「file関数」を試す。
試しにApacheのインストールをするためのuser_data.shを作成。

ターミナル
touch user_data.sh

これを外部ファイルとして読み込んで使ってみる。

作成したuser_data.shファイルを以下のように編集する。

リスト3.8:Apacheのインストールスクリプト

/myapp/terraform/user_data.sh
#!/bin/bash
yum install -y httpd
systemctl start httpd.service

ソース:example-pragmatic-terraform/user_data.sh at main · tmknom/example-pragmatic-terraform

続けてmain.tfファイルを以下のように編集する。

リスト3.9:Apacheのインストールスクリプトをファイル読み込み

myapp/terraform/main.tf
resource "aws_instance" "example" {
  ami           = "ami-0c3fd0f5d33134a76"
  instance_type = "t3.micro"
  user_data     = file("./user_data.sh")
}

ソース:example-pragmatic-terraform/9.tf at main · tmknom/example-pragmatic-terraform

user_data.shファイルとmain.tfファイルの編集が済んだらterraform applyコマンドを実行。リソースを新たに作成する。

ターミナル
terraform apply

yesと入力しEnterキーを押してリソースの作成を実行。

AWSコンソールでEC2を確認。作成したリソースに...。
※なぜかここの処理はうまくいかなかった。リソースの作成処理をキャンセルして次の項目へ...。うまく確認できなかったけどしょうがないので飛ばす。

3.8 モジュール

P.47
※「モジュール」についての説明。

モジュールは別ディレクトリで作成する必要がある。

「モジュール」を試す。
試しにHTTPサーバーのモジュールを実装する。まずはhttp_serverディレクトリを作成。

ターミナル
mkdir http_server

作成したhttp_serverディレクトリに移動する。

ターミナル
cd http_server

http_serverディレクトリ内にmain.tfファイルを作成する。

ターミナル
touch main.tf

3.8.1 モジュールの定義

main.tfファイルを以下のように編集する。

リスト3.10:HTTPサーバーモジュールの定義

/myapp/terraform/http_server/main.tf
variable "instance_type" {}

resource "aws_instance" "default" {
  ami                    = "ami-0c3fd0f5d33134a76"
  vpc_security_group_ids = [aws_security_group.default.id]
  instance_type          = var.instance_type

  user_data = <<EOF
    #!/bin/bash
    yum install -y httpd
    systemctl start httpd.service
EOF
}

resource "aws_security_group" "default" {
  name = "ec2"

  ingress {
    from_port = 80
    to_port = 80
    protocol = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }

  egress {
    from_port = 0
    to_port = 0
    protocol = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

output "public_dns" {
  value = aws_instance.default.public_dns
}

ソース:example-pragmatic-terraform/10.tf at main · tmknom/example-pragmatic-terraform

3.8.2 モジュールの利用

ターミナルのカレントディレクトリを一度terraformディレクトリに戻す。

ターミナル
cd ../

モジュールを利用する側のmain.tfファイルを編集する。
terraformディレクトリ直下のmain.tfファイルを以下のように編集する。

リスト3.11:HTTPサーバーモジュールの利用

/myapp/terraform/main.tf
module "web_server" {
  source        = "./http_server"
  instance_type = "t3.micro"
}

output "public_dns" {
  value = module.web_server.public_dns
}

ソース:example-pragmatic-terraform/11.tf at main · tmknom/example-pragmatic-terraform

main.tfファイルを編集したら、ターミナルのカレントディレクトリがterraformであること(モジュールを利用する側のmain.tfファイルが置いてあるディレクトリ)をよく確認し、それからterraform applyコマンドを実行。リソースを新たに作成する。

しかし今回はterraform applyコマンドの前にterraform getコマンドを実行する。このコマンドによってモジュールを事前に取得する必要がある。

ターミナル
terraform get

そしてterraform applyコマンドを実行。

ターミナル
terraform apply

yesと入力しEnterキーを押してリソースの作成を実行。

リソースの作成が完了したら、以下のように表示される。

ターミナル
Apply complete! Resources: 2 added, 0 changed, 2 destroyed.

Outputs:

public_dns = "ec2-XX-XX-XXX-XXX.ap-northeast-1.compute.amazonaws.com"

example_public_dnsに出力されているアドレスec2-XX-XX-XXX-XXX.ap-northeast-1.compute.amazonaws.com(「X」の部分には実際には数字が入っている)をコピーし、ブラウザでアクセスしてみる。Apacheの「Test Page」というタイトルのページが表示されればOK。
Terraformのモジュール機能をうまく使えていることが確認できた。

sakataku1991sakataku1991

第4章

全体設計

4.1 システム要件

P.53
本格的なTerraformの実装を体験する。
次の第5章から第16章にかけたハンズオンになる模様。

※以降のセクションは第5章から始まるハンズオンの説明となる。

4.2 アーキテクチャ設計

4.3 テクノロジースタック

4.4 ファイルレイアウト

sakataku1991sakataku1991

第5章

権限管理

5.1 ポリシー

5.1.1 ポリシードキュメント

P.57
AWSのサービス利用権限は「ポリシー」で定義・制御する。
そしてポリシーは「ポリシードキュメント」というJSON形式のファイルで作成する。

JSON形式のポリシードキュメントの例

リスト5.1:JSON形式のポリシードキュメント

ポリシードキュメント
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": ["ec2:DescribeRegions"],
      "Resource": ["*"]
    }
  ]
}

ソース:example-pragmatic-terraform/2.tf at main · tmknom/example-pragmatic-terraform

各要素の説明

  • Effect:Allow(許可)、または、Deny(拒否)
  • Action:何のサービスで、どんな操作が実行できるか
  • Resource:操作可能なリソースは何か

5.1.2 IAMポリシー

P.58
※IAMポリシーの定義方法についての説明。

リスト5.2:ポリシードキュメントの定義

ポリシードキュメント|IAMポリシーの定義
resource "aws_iam_policy" "example" {
  name   = "example"
  policy = data.aws_iam_policy_document.allow_describe_regions.json
}

ソース:example-pragmatic-terraform/3.tf at main · tmknom/example-pragmatic-terraform

5.2 ロール

5.2.1 信頼ポリシー

P.59
※IAMロールによる権限管理の説明。「信頼ポリシー」の定義方法について。

5.2.2 IAMロール

P.60
※IAMロールによる権限管理の説明。「IAMロール」の定義方法について。

5.2.3 IAMポリシーのアタッチ

P.60
※IAMロールへのIAMポリシーの関連付けについての説明。

5.2.4 IAMロールのモジュール化

P.61
※IAMロールをモジュール化する方法の説明。

IAMロールモジュールで使われる3つの入力パラメータについて

  • name:IAMロールとIAMポリシーの名前
  • policy:ポリシードキュメント
  • identifier:IAMロールを関連付けるAWSのサービスの識別子

terraformディレクトリにいる状態で、IAMロールモジュールのファイルを置くためのiam_roleディレクトリを作成する。

ターミナル
mkdir iam_role

作成したiam_roleディレクトリに移動。

ターミナル
cd iam_role

IAMロールのモジュール化を定義するファイルを作成する。ファイル名はiam_role.tfとする。

ターミナル
touch iam_role.tf

作成したiam_role.tfファイルをエディターで開き、以下のように編集する。

リスト5.7:IAMロールモジュールの定義

/myapp/terraform/iam_role/iam_role.tf
# IAM Role Module
variable "name" {}
variable "policy" {}
variable "identifier" {}

resource "aws_iam_role" "default" {
  name               = var.name
  assume_role_policy = data.aws_iam_policy_document.assume_role.json
}

data "aws_iam_policy_document" "assume_role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = [var.identifier]
    }
  }
}

resource "aws_iam_policy" "default" {
  name   = var.name
  policy = var.policy
}

resource "aws_iam_role_policy_attachment" "default" {
  role       = aws_iam_role.default.name
  policy_arn = aws_iam_policy.default.arn
}

output "iam_role_arn" {
  value = aws_iam_role.default.arn
}

output "iam_role_name" {
  value = aws_iam_role.default.name
}

ソース:example-pragmatic-terraform/7.tf at main · tmknom/example-pragmatic-terraform

P.62
IAMロールモジュールの利用

iam.tfファイルで定義したIAMロールモジュールは、IAMロールを使いたいファイルで以下のように実装する。

リスト5.8:IAMロールモジュールの利用

/myapp/terraform/hogehoge.tf
module "describe_regions_for_ec2" {
  source     = "./iam_role/iam_role"
  name       = "describe-regions-for-ec2"
  identifier = "ec2.amazonaws.com"
  policy     = data.aws_iam_policy_document.allow_describe_regions.json
}

ソース:example-pragmatic-terraform/8.tf at main · tmknom/example-pragmatic-terraform

このhogehoge.tfファイルでは、

  • sourceiam_role.tfを呼び出して(ここは、このhogehoge.tfファイルから見た、iam_role.tfの相対パス)
  • nameでこの新しいIAMロールに名前を付け
  • identifierでIAMロールを関連付けるAWSサービスを選択し、
  • policyで具体的なポリシー内容を記述
    している。
sakataku1991sakataku1991

第6章

ストレージ

6.1 プライベートバケット

6.1.1 S3バケット

P.64
S3バケット(外部公開しないプライベートバケット)の作成方法。

まず、iam_roleディレクトリからterraformディレクトリに戻る。

ターミナル
cd ../

そしてterraformディレクトリにいる状態で、terraformディレクトリ直下にs3.tfファイルを作成する。

ターミナル
touch s3.tf

作成したs3.tfファイルをエディターで開き、以下のように編集する。

リスト6.1:プライベートバケットの定義

/myapp/terraform/s3.tf
resource "aws_s3_bucket" "private" {
  bucket = "private-pragmatic-terraform"

  versioning {
    enabled = true
  }

  server_side_encryption_configuration {
    rule {
      apply_server_side_encryption_by_default {
        sse_algorithm = "AES256"
      }
    }
  }
}

ソース:example-pragmatic-terraform/1.tf at main · tmknom/example-pragmatic-terraform
※S3バケットの命名規則に乗っ取り、このファイルのbucketに指定するバケット名は「全リージョンにおいてユニークな名前」にしなければならない。

6.1.2 ブロックパブリックアクセス

P.65
S3バケットへの「ブロックパブリックアクセス」の設定方法。

以下のコードを追記する。

リスト6.2:ブロックパブリックアクセスの定義

/myapp/terraform/s3.tf
resource "aws_s3_bucket_public_access_block" "private" {
  bucket                  = aws_s3_bucket.private.id
  block_public_acls       = true
  block_public_policy     = true
  ignore_public_acls      = true
  restrict_public_buckets = true
}

ソース:example-pragmatic-terraform/2.tf at main · tmknom/example-pragmatic-terraform

6.2 パブリックバケット

P.66
S3バケット(外部公開するパブリックバケット)の作成方法。

s3.tfファイルを以下のように編集する。

リスト6.3:パブリックバケットの定義

/myapp/terraform/s3.tf
resource "aws_s3_bucket" "public" {
  bucket = "public-pragmatic-terraform"
  acl    = "public-read"

  cors_rule {
    allowed_origins = ["https://example.com"]
    allowed_methods = ["GET"]
    allowed_headers = ["*"]
    max_age_seconds = 3000
  }
}

ソース:example-pragmatic-terraform/3.tf at main · tmknom/example-pragmatic-terraform

6.3 ログバケット

6.3.1 ログテーションバケット

P.67
※AWSの各種サービスのログを保存するための「ログバケット」についての説明。

6.3.2 バケットポリシー

P.68
※AWSのサービスからS3へのアクセス権を制御する「バケットポリシー」。それのtfファイルの作成方法についての説明。

P.69
※バケットの削除方法についての説明。

sakataku1991sakataku1991

第7章(その1)

ネットワーク

7.1 パブリックネットワーク

7.1.1 VPC(Virtual Private Cloud)

P.71
VPCの作成方法。

terraformディレクトリ直下にnetwork.tfファイルを作成する。

ターミナル
touch network.tf

作成したnetwork.tfファイルをエディターで開き、以下のように編集する。

リスト7.1:VPCの定義

/myapp/terraform/network.tf
# VPC
resource "aws_vpc" "example" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "example"
  }
}

ソース:example-pragmatic-terraform/1.tf at main · tmknom/example-pragmatic-terraform

7.1.2 パブリックサブネット

P.72
パブリックサブネットの作成方法。

引き続きnetwork.tfファイルを編集。パブリックサブネットを作成するために以下のコードを追加する。

リスト7.2:パブリックネットの定義

/myapp/terraform/network.tf
# Public Subnet
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.0.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "ap-northeast-1a"
}

ソース:example-pragmatic-terraform/2.tf at main · tmknom/example-pragmatic-terraform

7.1.3 インターネットゲートウェイ

P.74
インターネットゲートウェイの作成方法。

引き続きnetwork.tfファイルを編集。インターネットゲートウェイを作成するために以下のコードを追加する。

リスト7.3:インターネットゲートウェイの定義

/myapp/terraform/network.tf
# Internet Gateway
resource "aws_internet_gateway" "example" {
  vpc_id = aws_vpc.example.id
}

ソース:example-pragmatic-terraform/3.tf at main · tmknom/example-pragmatic-terraform

7.1.4 ルートテーブル

P.74
ルートテーブル(パブリックサブネット用)の作成方法。

引き続きnetwork.tfファイルを編集。パブリックサブネット用のルートテーブルを作成するために以下のコードを追加する。

リスト7.4:ルートテーブルの定義

/myapp/terraform/network.tf
# Route Table for Public Subnet
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.example.id
}

ソース:example-pragmatic-terraform/4.tf at main · tmknom/example-pragmatic-terraform

ルート

P.75
パブリックサブネットのルートの定義方法。

引き続きnetwork.tfファイルを編集。パブリックサブネットのルートを定義するために以下のコードを追加する。

リスト7.5:ルートの定義

/myapp/terraform/network.tf
# Route for Public Subnet
resource "aws_route" "public" {
  route_table_id         = aws_route_table.public.id
  gateway_id             = aws_internet_gateway.example.id
  destination_cidr_block = "0.0.0.0/0"
}

ソース:example-pragmatic-terraform/5.tf at main · tmknom/example-pragmatic-terraform

ルートテーブルの関連付け

P.75
ルートテーブルの関連付け方法(パブリックサブネット)。

引き続きnetwork.tfファイルを編集。ルートテーブルを関連付けるために以下のコードを追加する。

リスト7.6:ルートテーブルの関連付け

/myapp/terraform/network.tf
# Route Table Association for Public Subnet
resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

ソース:example-pragmatic-terraform/6.tf at main · tmknom/example-pragmatic-terraform

7.2 プライベートネットワーク

7.2.1 プライベートサブネット

P.77
プライベートサブネットの作成方法。

引き続きnetwork.tfファイルを編集。プライベートサブネットを作成するために以下のコードを追加する。

リスト7.7:プライベートサブネットの定義

/myapp/terraform/network.tf
# Private Subnet
resource "aws_subnet" "private" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.64.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = false
}

ソース:example-pragmatic-terraform/7.tf at main · tmknom/example-pragmatic-terraform

ルートテーブルの関連付け

P.78
ルートテーブル(プライベートサブネット用)の作成方法。

引き続きnetwork.tfファイルを編集。プライベートサブネット用のルートテーブルを作成するために以下のコードを追加する。

リスト7.8:プライベートルートテーブルと関連付けの定義

/myapp/terraform/network.tf
# Route Table for Private Subnet
resource "aws_route_table" "private" {
  vpc_id = aws_vpc.example.id
}

ソース:example-pragmatic-terraform/8.tf at main · tmknom/example-pragmatic-terraform

P.78
ルートテーブルの関連付け方法(プライベートサブネット)。

引き続きnetwork.tfファイルを編集。ルートテーブルを関連付けるために以下のコードを追加する。

リスト7.8:プライベートルートテーブルと関連付けの定義

/myapp/terraform/network.tf
# Route Table Association for Private Subnet
resource "aws_route_table_association" "private" {
  subnet_id      = aws_subnet.private.id
  route_table_id = aws_route_table.private.id
}

ソース:example-pragmatic-terraform/8.tf at main · tmknom/example-pragmatic-terraform

7.2.2 NATゲートウェイ

P.78
NATゲートウェイの作成方法。

引き続きnetwork.tfファイルを編集。NATゲートウェイを作成する下準備として、まずは「Elastic IP アドレス」を設定する必要がある。以下のコードを追加する。

リスト7.9:EIPの定義

/myapp/terraform/network.tf
# Elastic IP Address
resource "aws_eip" "nat_gateway" {
  vpc        = true
  depends_on = [aws_internet_gateway.example]
}

ソース:example-pragmatic-terraform/9.tf at main · tmknom/example-pragmatic-terraform

Elastic IP アドレスを設定できたら、今度こそNATゲートウェイを作成。以下のコードを追加する。

リスト7.10:NATゲートウェイの定義

/myapp/terraform/network.tf
# NAT Gateway
resource "aws_nat_gateway" "example" {
  allocation_id = aws_eip.nat_gateway.id
  subnet_id     = aws_subnet.public.id
  depends_on    = [aws_internet_gateway.example]
}

ソース:example-pragmatic-terraform/10.tf at main · tmknom/example-pragmatic-terraform
※NATゲートウェイは、プライベートサブネットに配置すること!

ルート

P.80
プライベートサブネットのルートの定義方法。

引き続きnetwork.tfファイルを編集。プライベートサブネットのルートを定義するために以下のコードを追加する。

リスト7.11:プライベートのルートの定義

/myapp/terraform/network.tf
# Route for Private Subnet
resource "aws_route" "private" {
  route_table_id         = aws_route_table.private.id
  nat_gateway_id         = aws_nat_gateway.example.id
  destination_cidr_block = "0.0.0.0/0"
}

ソース:example-pragmatic-terraform/11.tf at main · tmknom/example-pragmatic-terraform

7.2.3 暗黙的な依存関係

P.80

  • Elastic IP アドレス
  • NATゲートウェイ

以上の2つは、暗黙的にインターネットゲートウェイに依存している。
そのためどちらのコードでもdepends_on = [aws_internet_gateway.example]を定義する必要がある。

7.3 マルチAZ

7.3.1 パブリックネットワークのマルチAZ化

P.81
サブネット(パブリックサブネットのマルチAZ化)
ネットワークをマルチAZ化するために、複数のアベイラビリティゾーンにサブネットを作成する。
パブリックサブネットとプライベートサブネットとをそれぞれ追加で作成する。

パブリックサブネットの作成用に追加した以下のコードを

リスト7.2:パブリックネットの定義

/myapp/terraform/network.tf
# Public Subnet
resource "aws_subnet" "public" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.0.0/24"
  map_public_ip_on_launch = true
  availability_zone       = "ap-northeast-1a"
}

次のように変更する。

リスト7.12:プライベートサブネットのマルチAZ化

/myapp/terraform/network.tf
# Public Subnet 0
resource "aws_subnet" "public_0" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = true
}

# Public Subnet 1
resource "aws_subnet" "public_1" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.2.0/24"
  availability_zone       = "ap-northeast-1c"
  map_public_ip_on_launch = true
}

ソース:example-pragmatic-terraform/12.tf at main · tmknom/example-pragmatic-terraform
※「Public Subnet 0」はap-northeast-1aへ、「Public Subnet 1」はap-northeast-1cへと、それぞれ異なるAZにサブネットを作成するよう設定し直している。

P.82
ルートテーブルの関連付け(パブリックサブネットのマルチAZ化)
ネットワークをマルチAZ化するために、複数のアベイラビリティゾーンにサブネットを作成する。
パブリックサブネットとプライベートサブネットとをそれぞれ追加で作成する。

パブリックサブネットにルートテーブルを関連付けるために追加した以下のコードを

リスト7.6:ルートテーブルの関連付け

/myapp/terraform/network.tf
# Route Table Association for Public Subnet
resource "aws_route_table_association" "public" {
  subnet_id      = aws_subnet.public.id
  route_table_id = aws_route_table.public.id
}

次のように変更する。

リスト7.13:パブリックサブネットとルートテーブルの関連付けをマルチAZ化

/myapp/terraform/network.tf
# Route Table Association for Public Subnet 0
resource "aws_route_table_association" "public_0" {
  subnet_id      = aws_subnet.public_0.id
  route_table_id = aws_route_table.public.id
}

# Route Table Association for Public Subnet 1
resource "aws_route_table_association" "public_1" {
  subnet_id      = aws_subnet.public_1.id
  route_table_id = aws_route_table.public.id
}

ソース:example-pragmatic-terraform/13.tf at main · tmknom/example-pragmatic-terraform

7.3.2 プライベートネットワークのマルチAZ化

P.82
サブネット(プライベートサブネットのマルチAZ化)

プライベートサブネットの作成用に追加した以下のコードを

リスト7.7:プライベートサブネットの定義

/myapp/terraform/network.tf
# Private Subnet
resource "aws_subnet" "private" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.64.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = false
}

次のように変更する。

リスト7.14:プライベートサブネットのマルチAZ化

/myapp/terraform/network.tf
# Private Subnet 0
resource "aws_subnet" "private_0" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.65.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = false
}

# Private Subnet 1
resource "aws_subnet" "private_1" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.66.0/24"
  availability_zone       = "ap-northeast-1c"
  map_public_ip_on_launch = false
}

ソース:example-pragmatic-terraform/14.tf at main · tmknom/example-pragmatic-terraform

P.83
NATゲートウェイ(プライベートサブネットのマルチAZ化)
マルチAZ化をする場合には、アベイラビリティゾーンごとにNATゲートウェイを作成する。

NATゲートウェイの作成用に追加した以下のコードを

リスト7.9:EIPの定義
リスト7.10:NATゲートウェイの定義

/myapp/terraform/network.tf
# Elastic IP Address
resource "aws_eip" "nat_gateway" {
  vpc        = true
  depends_on = [aws_internet_gateway.example]
}

# NAT Gateway
resource "aws_nat_gateway" "example" {
  allocation_id = aws_eip.nat_gateway.id
  subnet_id     = aws_subnet.public.id
  depends_on    = [aws_internet_gateway.example]
}

次のように変更する。

リスト7.15:NATゲートウェイのマルチAZ化

/myapp/terraform/network.tf
# Elastic IP Address 0
resource "aws_eip" "nat_gateway_0" {
  vpc        = true
  depends_on = [aws_internet_gateway.example]
}

# Elastic IP Address 1
resource "aws_eip" "nat_gateway_1" {
  vpc        = true
  depends_on = [aws_internet_gateway.example]
}

# NAT Gateway 0
resource "aws_nat_gateway" "nat_gateway_0" {
  allocation_id = aws_eip.nat_gateway_0.id
  subnet_id     = aws_subnet.public_0.id
  depends_on    = [aws_internet_gateway.example]
}

# NAT Gateway 1
resource "aws_nat_gateway" "nat_gateway_1" {
  allocation_id = aws_eip.nat_gateway_1.id
  subnet_id     = aws_subnet.public_1.id
  depends_on    = [aws_internet_gateway.example]
}

ソース:example-pragmatic-terraform/15.tf at main · tmknom/example-pragmatic-terraform

P.84
ルートテーブル(プライベートサブネットのマルチAZ化)
NATゲートウェイと同様に、マルチAZ化する場合にはルートテーブルもアベイラビリティゾーンごとに作成する。

プライベートサブネット用のルートテーブル作成用に追加した以下のコードを

リスト7.8:プライベートルートテーブルと関連付けの定義
リスト7.11:プライベートのルートの定義

/myapp/terraform/network.tf
# Route Table for Private Subnet
resource "aws_route_table" "private" {
  vpc_id = aws_vpc.example.id
}

# Route for Private Subnet
resource "aws_route" "private" {
  route_table_id         = aws_route_table.private.id
  nat_gateway_id         = aws_nat_gateway.example.id
  destination_cidr_block = "0.0.0.0/0"
}

# Route Table Association for Private Subnet
resource "aws_route_table_association" "private" {
  subnet_id      = aws_subnet.private.id
  route_table_id = aws_route_table.private.id
}

次のように変更する。

リスト7.16:プライベートサブネットのルートテーブルのマルチAZ化

/myapp/terraform/network.tf
# Route Table for Private Subnet 0
resource "aws_route_table" "private_0" {
  vpc_id = aws_vpc.example.id
}

# Route Table for Private Subnet 1
resource "aws_route_table" "private_1" {
  vpc_id = aws_vpc.example.id
}

# Route for Private Subnet 0
resource "aws_route" "private_0" {
  route_table_id         = aws_route_table.private_0.id
  nat_gateway_id         = aws_nat_gateway.nat_gateway_0.id
  destination_cidr_block = "0.0.0.0/0"
}

# Route for Private Subnet 1
resource "aws_route" "private_1" {
  route_table_id         = aws_route_table.private_1.id
  nat_gateway_id         = aws_nat_gateway.nat_gateway_1.id
  destination_cidr_block = "0.0.0.0/0"
}

# Route Table Association for Private Subnet 0
resource "aws_route_table_association" "private_0" {
  subnet_id      = aws_subnet.private_0.id
  route_table_id = aws_route_table.private_0.id
}

# Route Table Association for Private Subnet 1
resource "aws_route_table_association" "private_1" {
  subnet_id      = aws_subnet.private_1.id
  route_table_id = aws_route_table.private_1.id
}

ソース:example-pragmatic-terraform/16.tf at main · tmknom/example-pragmatic-terraform

ここまで編集してみて、network.tfファイルの中身は最終的に以下のようになった。

/myapp/terraform/network.tf
# VPC
resource "aws_vpc" "example" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_support   = true
  enable_dns_hostnames = true

  tags = {
    Name = "example"
  }
}

# Internet Gateway
resource "aws_internet_gateway" "example" {
  vpc_id = aws_vpc.example.id
}


# Public Subnet 0
resource "aws_subnet" "public_0" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.1.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = true
}

# Public Subnet 1
resource "aws_subnet" "public_1" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.2.0/24"
  availability_zone       = "ap-northeast-1c"
  map_public_ip_on_launch = true
}


# Route Table for Public Subnet
resource "aws_route_table" "public" {
  vpc_id = aws_vpc.example.id
}

# Route for Public Subnet
resource "aws_route" "public" {
  route_table_id         = aws_route_table.public.id
  gateway_id             = aws_internet_gateway.example.id
  destination_cidr_block = "0.0.0.0/0"
}

# Route Table Association for Public Subnet 0
resource "aws_route_table_association" "public_0" {
  subnet_id      = aws_subnet.public_0.id
  route_table_id = aws_route_table.public.id
}

# Route Table Association for Public Subnet 1
resource "aws_route_table_association" "public_1" {
  subnet_id      = aws_subnet.public_1.id
  route_table_id = aws_route_table.public.id
}


# Elastic IP Address 0
resource "aws_eip" "nat_gateway_0" {
  vpc        = true
  depends_on = [aws_internet_gateway.example]
}

# Elastic IP Address 1
resource "aws_eip" "nat_gateway_1" {
  vpc        = true
  depends_on = [aws_internet_gateway.example]
}

# NAT Gateway 0
resource "aws_nat_gateway" "nat_gateway_0" {
  allocation_id = aws_eip.nat_gateway_0.id
  subnet_id     = aws_subnet.public_0.id
  depends_on    = [aws_internet_gateway.example]
}

# NAT Gateway 1
resource "aws_nat_gateway" "nat_gateway_1" {
  allocation_id = aws_eip.nat_gateway_1.id
  subnet_id     = aws_subnet.public_1.id
  depends_on    = [aws_internet_gateway.example]
}


# Private Subnet 0
resource "aws_subnet" "private_0" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.65.0/24"
  availability_zone       = "ap-northeast-1a"
  map_public_ip_on_launch = false
}

# Private Subnet 1
resource "aws_subnet" "private_1" {
  vpc_id                  = aws_vpc.example.id
  cidr_block              = "10.0.66.0/24"
  availability_zone       = "ap-northeast-1c"
  map_public_ip_on_launch = false
}


# Route Table for Private Subnet 0
resource "aws_route_table" "private_0" {
  vpc_id = aws_vpc.example.id
}

# Route Table for Private Subnet 1
resource "aws_route_table" "private_1" {
  vpc_id = aws_vpc.example.id
}

# Route for Private Subnet 0
resource "aws_route" "private_0" {
  route_table_id         = aws_route_table.private_0.id
  nat_gateway_id         = aws_nat_gateway.nat_gateway_0.id
  destination_cidr_block = "0.0.0.0/0"
}

# Route for Private Subnet 1
resource "aws_route" "private_1" {
  route_table_id         = aws_route_table.private_1.id
  nat_gateway_id         = aws_nat_gateway.nat_gateway_1.id
  destination_cidr_block = "0.0.0.0/0"
}

# Route Table Association for Private Subnet 0
resource "aws_route_table_association" "private_0" {
  subnet_id      = aws_subnet.private_0.id
  route_table_id = aws_route_table.private_0.id
}

# Route Table Association for Private Subnet 1
resource "aws_route_table_association" "private_1" {
  subnet_id      = aws_subnet.private_1.id
  route_table_id = aws_route_table.private_1.id
}
sakataku1991sakataku1991

第7章(その2)

ネットワーク

7.4 ファイアウォール

7.4.1 セキュリティグループ

P.86
AWSのファイアウォールの種類(2種類)

  1. ネットワークACL(サブネットレベルで動作する)
  2. セキュリティグループ(インスタンスレベルで動作する)

セキュリティグループ

「セキュリティグループ」と「セキュリティグループルール」は分けて書くこともできる。

※ここからは「セキュリティグループ」の話になる。
※これまで編集してきたnetwork.tfファイルとは別に、security_group_sample.tfファイルを新たに作成。このsecurity_group_sample.tfファイルを編集していく。
※セキュリティグループはインスタンスに設定するものなので、本来はec2.tfファイルやecs.tfファイルなど、インスタンスを作成するtfファイルに記述すべき内容である(が、今回は学習用としてsecurity_group_sample.tfファイルに色々書いていく)。

terraformディレクトリ直下にsecurity_group_sample.tfファイルを作成する。

ターミナル
touch security_group_sample.tf

まずは「セキュリティグループ」を実装する。
作成したsecurity_group_sample.tfファイルをエディターで開き、以下のように編集する。

リスト7.17:セキュリティグループの定義

/myapp/terraform/security_group_sample.tf
# Security Group
resource "aws_security_group" "example" {
  name   = "example"
  vpc_id = aws_vpc.example.id
}

ソース:example-pragmatic-terraform/17.tf at main · tmknom/example-pragmatic-terraform

セキュリティグループルール(インバウンド)

続いて「セキュリティグループルール(インバウンド)」を実装。security_group_sample.tfファイルに以下のコードを追記する。

リスト7.18:セキュリティグループルール(インバウンド)の定義

/myapp/terraform/security_group_sample.tf
# Security Group Rule (Inbound)
resource "aws_security_group_rule" "ingress_example" {
  type              = "ingress"
  from_port         = "80"
  to_port           = "80"
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.example.id
}

ソース:example-pragmatic-terraform/18.tf at main · tmknom/example-pragmatic-terraform

セキュリティグループルール(アウトバウンド)

次は「セキュリティグループルール(アウトバウンド)」を実装。security_group_sample.tfファイルに以下のコードを追記する。

リスト7.19:セキュリティグループルール(アウトバウンド)の定義

/myapp/terraform/security_group_sample.tf
# Security Group Rule (Outbound)
resource "aws_security_group_rule" "egress_example" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.example.id
}

ソース:example-pragmatic-terraform/19.tf at main · tmknom/example-pragmatic-terraform

7.4.2 セキュリティグループのモジュール化

P.88
※セキュリティグループをモジュール化する方法の説明。

セキュリティグループモジュールで使われる4つの入力パラメータについて

  • name:セキュリティグループの名前
  • vpc_id:VPCのID
  • port:通信を許可するポート番号
  • cidr_blocks:通信を許可するCIDRブロック

terraformディレクトリにいる状態で、セキュリティグループモジュールのファイルを置くためのsecurity_groupディレクトリを作成する。

ターミナル
mkdir security_group

作成したsecurity_groupディレクトリに移動。

ターミナル
cd security_group

セキュリティグループのモジュール化を定義するファイルを作成する。ファイル名はsecurity_group.tfとする。

ターミナル
touch security_group.tf

作成したsecurity_group.tfファイルをエディターで開き、以下のように編集する。

リスト7.20:セキュリティグループモジュールの定義

/myapp/terraform/security_group/security_group.tf
# Security Group Module
variable "name" {}
variable "vpc_id" {}
variable "port" {}
variable "cidr_blocks" {
  type = list(string)
}

resource "aws_security_group" "default" {
  name   = var.name
  vpc_id = var.vpc_id
}

resource "aws_security_group_rule" "ingress" {
  type              = "ingress"
  from_port         = var.port
  to_port           = var.port
  protocol          = "tcp"
  cidr_blocks       = var.cidr_blocks
  security_group_id = aws_security_group.default.id
}

resource "aws_security_group_rule" "egress" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.default.id
}

output "security_group_id" {
  value = aws_security_group.default.id
}

ソース:example-pragmatic-terraform/20.tf at main · tmknom/example-pragmatic-terraform

P.89
セキュリティグループモジュールの利用

security_group.tfファイルで定義したセキュリティグループモジュールは、セキュリティグループを使いたいファイルで以下のように実装する。

リスト7.21:セキュリティグループモジュールの利用

/myapp/terraform/hogehoge.tf
module "example_sg" {
  source      = "./security_group/security_group"
  name        = "module-sg"
  vpc_id      = aws_vpc.example.id
  port        = 80
  cidr_blocks = ["0.0.0.0/0"]
}

ソース:example-pragmatic-terraform/21.tf at main · tmknom/example-pragmatic-terraform

このhogehoge.tfファイルでは、

  • sourcesecurity_group.tfを呼び出して(ここは、このhogehoge.tfファイルから見た、security_group.tfの相対パス)
  • nameでこの新しいセキュリティグループに名前を付け
  • vpc_idでVPCのIDを設定
  • portで通信を許可するポート番号を設定し
  • cidr_blocksで通信を許可するCIDRブロックを設定
    している。
sakataku1991sakataku1991

第8章

ロードバランサーとDNS

8.1 ALBの構成要素

P.91
※ALBについての説明。

8.2 HTTP用ロードバランサー

8.2.1 アプリケーションロードバランサー

P.92
※アプリケーションロードバランサーを作成する方法の説明。

terraformディレクトリ直下にalb.tfファイルを作成する。

ターミナル
touch alb.tf

作成したalb.tfファイルをエディターで開き、以下のように編集する。

リスト8.1:アプリケーションロードバランサーの定義

/myapp/terraform/alb.tf
# ALB
resource "aws_lb" "example" {
  name                       = "example"
  load_balancer_type         = "application"
  internal                   = false
  idle_timeout               = 60
  enable_deletion_protection = true

  subnets = [
    aws_subnet.public_0.id,
    aws_subnet.public_1.id,
  ]

  access_logs {
    bucket  = aws_s3_bucket.alb_log.id
    enabled = true
  }

  security_groups = [
    module.http_sg.security_group_id,
    module.https_sg.security_group_id,
    module.http_redirect_sg.security_group_id,
  ]
}

output "alb_dns_name" {
  value = aws_lb.example.dns_name
}

ソース:example-pragmatic-terraform/1.tf at main · tmknom/example-pragmatic-terraform

アプリケーションロードバランサーの作成時に使われる入力パラメータについて

  • name:アプリケーションロードバランサーの名前
  • load_balancer_type:ロードバランサーの種類(今回はALBなのでapplicationを指定している)
  • internal:ALBが「インターネット向け(false)」なのか「VPC内部向け(true)」なのかを指定
  • idle_timeout:タイムアウトするまでの時間の指定。秒単位で指定する。デフォルト値は60秒。
  • enable_deletion_protection:削除保護の有効化。trueを指定すると削除保護機能が有効になる。
  • subnets:作成するロードバランサーが所属するサブネットの指定。ここで異なるAZが複数指定されることで、クロスゾーン負荷分散が実現される。
  • access_logs:アクセスログ。S3などへのアクセスログの保存が有効になる。
  • security_groups:セキュリティーグループの定義

alb.tfファイルに以下のコードを追記する。

リスト8.2:アプリケーションロードバランサーのセキュリティグループの定義

/myapp/terraform/alb.tf
# Security Group
module "http_sg" {
  source      = "./security_group"
  name        = "http-sg"
  vpc_id      = aws_vpc.example.id
  port        = 80
  cidr_blocks = ["0.0.0.0/0"]
}

module "https_sg" {
  source      = "./security_group"
  name        = "https-sg"
  vpc_id      = aws_vpc.example.id
  port        = 443
  cidr_blocks = ["0.0.0.0/0"]
}

module "http_redirect_sg" {
  source      = "./security_group"
  name        = "http-redirect-sg"
  vpc_id      = aws_vpc.example.id
  port        = 8080
  cidr_blocks = ["0.0.0.0/0"]
}

ソース:example-pragmatic-terraform/2.tf at main · tmknom/example-pragmatic-terraform

8.2.2 リスナー

P.95
※リスナーの設定方法の説明。

alb.tfファイルに以下のコードを追記する。

リスト8.3:HTTPリスナーの定義

/myapp/terraform/alb.tf
# Listener
resource "aws_lb_listener" "http" {
  load_balancer_arn = aws_lb.example.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type = "fixed-response"

    fixed_response {
      content_type = "text/plain"
      message_body = "これは『HTTP』です"
      status_code  = "200"
    }
  }
}

ソース:example-pragmatic-terraform/3.tf at main · tmknom/example-pragmatic-terraform

リスナーの設定に使われる入力パラメータについて

  • port:ポート番号。上記のコードではHTTPなので「80」を指定している。
  • protocol:プロトコル。「HTTP」と「HTTPS」のみサポートしている。
  • default_action:デフォルトアクション。リスナーは複数のルールを設定して、異なるアクションを実行できる。

8.2.3 HTTPアクセス

P.97
※リスナーの設定方法の説明。

8.3 Route 53

8.3.1 ドメインの登録

P.98
※Route 53によるドメインの登録方法の説明。
※Terraformではドメインの登録までは実行できない(まあドメイン登録まで自動でできる必要はなさそうだけど)。

terraformディレクトリ直下にdns.tfファイルを作成しておく。

ターミナル
touch dns.tf

このファイルにRoute 53やACMの設定を記述していく。

8.3.2 ホストゾーン

P.98
※ホストゾーンについての説明。

8.3.3 DNSレコード

P.99
※DNSレコードの設定方法の説明。

ここから本格的にDNSレコードの設定をしていく。
dns.tfファイルに以下のコードを追記する。

リスト8.6:ALBのDNSレコードの定義

/myapp/terraform/dns.tf
# Route 53
resource "aws_route53_record" "example" {
  zone_id = data.aws_route53_zone.example.zone_id
  name    = data.aws_route53_zone.example.name
  type    = "A"

  alias {
    name                   = aws_lb.example.dns_name
    zone_id                = aws_lb.example.zone_id
    evaluate_target_health = true
  }
}

output "domain_name" {
  value = aws_route53_record.example.name
}

ソース:example-pragmatic-terraform/6.tf at main · tmknom/example-pragmatic-terraform
以上の設定により、自前のドメインでALBにアクセスできるようになる。

※恐らくexample部分に自分が持っているドメインを記載すればいいということなんだろうけど、教材にはその辺の説明が一切ない。自分で実際に入れて試して確認しないとダメそう。

8.3.4 独自ドメインへのアクセス

※設定した独自ドメインへのアクセスをテストする。

8.4 ACM(AWS Certificate Manager)

ACMとは?
→ ドメインのSSL証明書を管理することができるAWSのマネージドサービスのこと。SSL証明書の自動更新に対応している。

8.4.1 SSL証明書の作成

P.102
※アプリケーションロードバランサーを作成する方法の説明。

SSL証明書の設定をするために、dns.tfファイルに以下のコードを追記する。

リスト8.7:SSL証明書の定義

/myapp/terraform/dns.tf
# ACM
resource "aws_acm_certificate" "example" {
  domain_name               = aws_route53_record.example.name
  subject_alternative_names = []
  validation_method         = "DNS"

  lifecycle {
    create_before_destroy = true
  }
}

ソース:example-pragmatic-terraform/7.tf at main · tmknom/example-pragmatic-terraform

SSL証明書の設定に使われる入力パラメータについて

  • domain_name:ドメイン名。ここに自前のドメイン名を記入する。
  • subject_alternative_names:ドメイン名を追加するための項目。サブドメインを設定できるみたい。特に追加したいドメインがない場合は、[]と書いて空のリストを渡すようにしておけばいいらしい。
  • validation_method:検証方法。ドメインの所有権の検証方法を設定するための項目。「DNS検証」と「Eメール検証」のどちらかを選べるらしい。SSL証明書を自動更新したい場合はDNS検証を選択すること!
  • lifecycle:ライフサイクル。create_before_destroy = trueというコードで、「新しいSSL証明書を作ってから、古いSSL証明書と差し替える」という処理を設定している。

8.4.2 SSL証明書の検証

P.103
※SSL証明書の検証についての説明。
「DNS検証」とは?
→ SSL証明書を自動で更新する検証方法のこと。DNS検証を使えばSSL証明書の更新漏れがなくなるらしい。DNS検証の仕組みがよくわからないけど、とりあえず「SSL証明書の更新を楽にするために必要な設定」とだけ理解した。別途ググって知識を補完しよう。

検証用DNSレコード
DNS検証の設定方法。dns.tfファイルに以下のコードを追記し、DNSレコードを追加する。

リスト8.8:SSL証明書の検証用レコードの定義

/myapp/terraform/dns.tf
# ACM (DNS verification)
resource "aws_route53_record" "example_certificate" {
  for_each = {
    for dvo in aws_acm_certificate.example.domain_validation_options : dvo.domain_name => {
      name   = dvo.resource_record_name
      record = dvo.resource_record_value
      type   = dvo.resource_record_type
    }
  }

  name    = each.value.name
  type    = each.value.type
  records = [each.value.record]
  zone_id = data.aws_route53_zone.example.id
  ttl     = 60
}

ソース:example-pragmatic-terraform/8.tf at main · tmknom/example-pragmatic-terraform

検証の待機
aws_acm_certificate_validationリソースを使うことで、terraform applyの実行時、SSL証明書の検証が完了するまで処理の続行を待機するように設定できるらしい。教材のリスト8.9がそのサンプルコードになる。

8.5 HTTPS用ロードバランサー

HTTPSでALBにアクセスできるようHTTPSリスナーを作成する。

8.5.1 HTTPSリスナー

P.105
HTTPSリスナーの設定方法。

dns.tfファイルに以下のコードを追記する。

リスト8.10:HTTPSリスナーの定義

/myapp/terraform/dns.tf
# HTTPS Listener
resource "aws_lb_listener" "https" {
  load_balancer_arn = aws_lb.example.arn
  port              = "443"
  protocol          = "HTTPS"
  certificate_arn   = aws_acm_certificate.example.arn
  ssl_policy        = "ELBSecurityPolicy-2016-08"

  default_action {
    type = "fixed-response"

    fixed_response {
      content_type = "text/plain"
      message_body = "これは『HTTPS』です"
      status_code  = "200"
    }
  }
}

ソース:example-pragmatic-terraform/10.tf at main · tmknom/example-pragmatic-terraform

8.5.2 HTTPSへのリダイレクト

P.106
HTTPをHTTPSへリダイレクトさせるための設定。「リダイレクトリスナー」を作成する。

dns.tfファイルに以下のコードを追記する。

リスト8.11:HTTPからHTTPSにリダイレクトするリスナーの定義

/myapp/terraform/dns.tf
# Redirect Listener
resource "aws_lb_listener" "redirect_http_to_https" {
  load_balancer_arn = aws_lb.example.arn
  port              = "8080"
  protocol          = "HTTP"

  default_action {
    type = "redirect"

    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}

ソース:example-pragmatic-terraform/11.tf at main · tmknom/example-pragmatic-terraform

8.5.3 HTTPSアクセス

P.107
※HTTPSへアクセスできるかどうかの確認作業。

8.6 リクエストフォワーディング

任意のターゲットへリクエストをフォワードできるようにする設定。

8.6.1 ターゲットグループ

P.108
「リクエストをフォワードする」の「フォワード」とは?
→ 同一サーバー内での転送処理のこと。「リダイレクト」との違いもあわせて理解しよう。

ALBではリクエストをフォワードする対象のことを「ターゲットグループ」と呼ぶ。
dns.tfファイルに以下のコードを追記し、ターゲットグループを定義する。

リスト8.12:ターゲットグループの定義

/myapp/terraform/dns.tf
# Target Group
resource "aws_lb_target_group" "example" {
  name                 = "example"
  target_type          = "ip"
  vpc_id               = aws_vpc.example.id
  port                 = 80
  protocol             = "HTTP"
  deregistration_delay = 300

  health_check {
    path                = "/"
    healthy_threshold   = 5
    unhealthy_threshold = 2
    timeout             = 5
    interval            = 30
    matcher             = 200
    port                = "traffic-port"
    protocol            = "HTTP"
  }

  depends_on = [aws_lb.example]
}

ソース:example-pragmatic-terraform/12.tf at main · tmknom/example-pragmatic-terraform

暗黙的な依存関係
ALBとターゲットグループをECSサービスと同時に作成するとエラーになってしまうらしい(ECS(Fargate)によるコンテナがまだ作成されていないのにALBの設定ができるか!ってなるからだろうか?)。そのためdepends_on = [aws_lb.example]というコードを設定することでtfファイルの依存関係を制御する必要がある。

8.6.2 リスナールール

P.111
ターゲットグループにリクエストをフォワードするリスナールールの作成。

dns.tfファイルに以下のコードを追記する。

リスト8.13:リスナールールの定義

/myapp/terraform/dns.tf
# Listener Rule
resource "aws_lb_listener_rule" "example" {
  listener_arn = aws_lb_listener.https.arn
  priority     = 100

  action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.example.arn
  }

  condition {
    path_pattern {
      values = ["/*"]
    }
  }
}

ソース:example-pragmatic-terraform/13.tf at main · tmknom/example-pragmatic-terraform

sakataku1991sakataku1991

第9章

コンテナオーケストレーション

9.1 ECSの構成要素

P.113
※ECSについての説明。

  • タスク
  • ECSサービス
  • ECSクラスタ

9.2 ECSの起動タイプ

ECSには以下2種類の起動タイプがある。

  1. EC2起動タイプ
  2. Fargate起動タイプ

9.2.1 EC2起動タイプ

P.113
※EC2起動タイプの説明。

9.2.2 Fargate起動タイプ

P.114
※Fargate起動タイプの説明。

※ここから先、「Fargate起動タイプ」で実装を進めていく!

9.3 Webサーバーの構築

  • ECSをプライベートネットワークに配置し
  • nginxコンテナを起動
  • ALB経由でリクエストを受け取り
  • それをECS上のnginxコンテナが処理する

9.3.1 ECSクラスタ

P.115
まずはECSの設定用にecs.tfを作成する

terraformディレクトリ直下にecs.tfファイルを作成。

ターミナル
touch ecs.tf

ecs.tfファイルに以下のコードを追記する。

リスト9.1:ECSクラスタの定義

/myapp/terraform/ecs.tf
# ECS Cluster
resource "aws_ecs_cluster" "example" {
  name = "example"
}

ソース:example-pragmatic-terraform/1.tf at main · tmknom/example-pragmatic-terraform

9.3.2 タスク定義

P.115
ECSでは、コンテナの実行単位のことを「タスク」と呼ぶ。
そしてタスクは「タスク定義」から生成される。

実際にタスク定義を実装してみる。
ecs.tfファイルに以下のコードを追記。

リスト9.2:タスク定義

/myapp/terraform/ecs.tf
# ECS Task
resource "aws_ecs_task_definition" "example" {
  family                   = "example"
  cpu                      = "256"
  memory                   = "512"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  container_definitions    = file("./tasks/container_definitions.json")
}

ソース:example-pragmatic-terraform/2.tf at main · tmknom/example-pragmatic-terraform

※この部分は教材のコードに変更を加えています。container_definitions = file("./container_definitions.json")の箇所をcontainer_definitions = file("./tasks/container_definitions.json")とし、tasksディレクトリの中のcontainer_definitions.jsonファイルを指定するように設定しています。

タスク定義に使われる入力パラメータについて

  • family:ファミリー。タスク定義名のプレフィックスのこと。
  • cpumemory:タスクサイズ。タスクが使用するリソースのサイズを設定する項目。
  • network_mode:ネットワークモード。今回のように、「Fargate起動タイプ」を選択している場合はこの項目に「awsvpc」を指定する。
  • requires_compatibilities:起動タイプ。「FARGATE起動タイプ」にするので「FARGATE」を指定する。
  • container_definitions:コンテナ定義。container_definitions.jsonファイルなど、タスクで実行するコンテナの定義を別途JSONファイルで用意する。このコンテナ定義用のファイルは、今後、タスクの数に応じて複数作成することになるのが予想されるため、terraformディレクトリ直下にtasksなどの名前のディレクトリを作成し、その中にコンテナ定義のJSONファイルを格納して管理するようにするとよさそう。

ということで、実際にコンテナ定義のJSONファイルを作成してみる。

terraformディレクトリにいる状態で、コンテナ定義のJSONファイルを置くためのtasksディレクトリを作成する。

ターミナル
mkdir tasks

そして作成したtasksディレクトリに移動。

ターミナル
cd tasks

container_definitions.jsonというファイルを作成する。

ターミナル
touch container_definitions.json

作成したcontainer_definitions.jsonファイルをエディターで開き、以下のように編集する。

リスト9.3:コンテナ定義

/myapp/terraform/tasks/container_definitions.json
[
  {
    "name": "example",
    "image": "nginx:latest",
    "essential": true,
    "portMappings": [
      {
        "protocol": "tcp",
        "containerPort": 80
      }
    ]
  }
]

ソース:example-pragmatic-terraform/container_definitions.json at main · tmknom/example-pragmatic-terraform

コンテナ定義のJSONファイルに使われる入力パラメータについて

  • name:コンテナの名前
  • image:使用するコンテナイメージ
  • essential:タスク実行に必須かどうかのフラグ。この項目ではtruefalseのどちらかを指定する。
  • portMappings:マッピングするコンテナのポート番号

9.3.3 ECSサービス

P.118
コンテナ(タスク)の起動を維持するために「ECSサービス」を使う。

ecs.tfファイルに以下のコードを追記し、ECSサービスを実装する。

リスト9.4:ECSサービスの定義

/myapp/terraform/ecs.tf
# ECS Service
resource "aws_ecs_service" "example" {
  name                              = "example"
  cluster                           = aws_ecs_cluster.example.arn
  task_definition                   = aws_ecs_task_definition.example.arn
  desired_count                     = 2
  launch_type                       = "FARGATE"
  platform_version                  = "1.3.0"
  health_check_grace_period_seconds = 60

  network_configuration {
    assign_public_ip = false
    security_groups  = [module.nginx_sg.security_group_id]

    subnets = [
      aws_subnet.private_0.id,
      aws_subnet.private_1.id,
    ]
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.example.arn
    container_name   = "example"
    container_port   = 80
  }

  lifecycle {
    ignore_changes = [task_definition]
  }
}

module "nginx_sg" {
  source      = "./security_group"
  name        = "nginx-sg"
  vpc_id      = aws_vpc.example.id
  port        = 80
  cidr_blocks = [aws_vpc.example.cidr_block]
}

ソース:example-pragmatic-terraform/4.tf at main · tmknom/example-pragmatic-terraform

ECSサービスに使われる入力パラメータについて

  • clustertask_definition:ECSクラスタとタスク定義。clusterにはresource "aws_ecs_cluster"で作成したクラスタ名を設定する。task_definitionにはresource "aws_ecs_task_definition"で作成したタスク定義を設定する。
  • desired_count:維持するタスク数。ECSサービスが維持するタスク数をこの項目で指定する。2以上のタスク数を指定することで、何らかの問題である1つのコンテナが異常終了したとしてもサービスが落ちないようにカバーすることができる。
  • launch_type:起動タイプ。「FARGATE起動タイプ」にするので「FARGATE」を指定する。
  • platform_version:プラットフォームバージョン。ここは「LATEST」は指定せず、バージョン数を明示的に指定すること!
  • health_check_grace_period_seconds:ヘルスチェック猶予期間。秒単位で指定する。
  • network_configuration:ネットワーク構成。「サブネット」と「セキュリティグループ」、「パブリックIPアドレスを割り当てるかどうか」を設定する。
  • load_balancer:ロードバランサー。「ターゲットグループ」と「コンテナの名前」、「ポート番号」を指定し、ロードバランサーと関連付ける。
  • lifecycle:ライフサイクル。今回はタスク定義の変更を無視するように設定している。

9.3.4 コンテナの動作確認

P.122
※ここまで実装してきた内容を、terraform applyコマンドを実行して確認してみる。

9.4 Fargateにおけるロギング

9.4.1 CloudWatch Logs

P.123

  • Fargateではホストサーバーにログインできず、コンテナのログを見ることができない
  • そこで「CloudWatch Logs」と連携し、ログを記録・確認できるようにする

CloudWatch Logsとは?
→ 現在利用しているAWSサービスのログを収集・記録することができるAWSのマネージドサービスのこと。

CloudWatch Logsを利用するために、Terraformでcloud_watch.tfファイルを作成する。

terraformディレクトリ直下にcloud_watch.tfファイルを作成。

ターミナル
touch cloud_watch.tf

作成したcloud_watch.tfファイルをエディターで開き、以下のように編集する。

リスト9.5:CloudWatch Logsの定義

/myapp/terraform/cloud_watch.tf
# CloudWatch for ECS
resource "aws_cloudwatch_log_group" "for_ecs" {
  name              = "/ecs/example"
  retention_in_days = 180
}

ソース:example-pragmatic-terraform/5.tf at main · tmknom/example-pragmatic-terraform

9.4.2 ECSタスク実行IAMロール

P.123
ECSに権限を付与する。そのためにECSタスク実行用のIAMロールを作成する。

IAMポリシーデータソース

ecs.tfファイルに以下のコードを追記する。

リスト9.6:AmazonECSTaskExecutionRolePolicyの参照

/myapp/terraform/ecs.tf
# IAM Role (IAM Policy Data Source)
data "aws_iam_policy" "ecs_task_execution_role_policy" {
  arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

ソース:example-pragmatic-terraform/6.tf at main · tmknom/example-pragmatic-terraform

ポリシードキュメント

P.124
ポリシードキュメントを定義する。

ecs.tfファイルに以下のコードを追記する。

リスト9.7:ECSタスク実行IAMロールのポリシードキュメントの定義

/myapp/terraform/ecs.tf
# IAM Role (Policy Document)
data "aws_iam_policy_document" "ecs_task_execution" {
  source_json = data.aws_iam_policy.ecs_task_execution_role_policy.policy

  statement {
    effect    = "Allow"
    actions   = ["ssm:GetParameters", "kms:Decrypt"]
    resources = ["*"]
  }
}

ソース:example-pragmatic-terraform/7.tf at main · tmknom/example-pragmatic-terraform

IAMロール

P.124
ECS用のIAMロールを作成する。

ecs.tfファイルに以下のコードを追記する。

リスト9.8:ECSタスク実行IAMロールの定義

/myapp/terraform/ecs.tf
# IAM Role (for ECS)
module "ecs_task_execution_role" {
  source     = "./iam_role"
  name       = "ecs-task-execution"
  identifier = "ecs-tasks.amazonaws.com"
  policy     = data.aws_iam_policy_document.ecs_task_execution.json
}

ソース:example-pragmatic-terraform/8.tf at main · tmknom/example-pragmatic-terraform

9.4.3 Dockerコンテナのロギング

P.125
DockerコンテナがCloudWatch Logsにログを投げられるように設定する。

タスクの定義用に追加した以下のコードを

リスト9.2:タスク定義

/myapp/terraform/ecs.tf
# ECS Task
resource "aws_ecs_task_definition" "example" {
  family                   = "example"
  cpu                      = "256"
  memory                   = "512"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  container_definitions    = file("./tasks/container_definitions.json")
}

次のように変更する。

リスト9.9:タスク定義にECSタスク実行IAMロールを追加

/myapp/terraform/ecs.tf
# ECS Task
resource "aws_ecs_task_definition" "example" {
  family                   = "example"
  cpu                      = "256"
  memory                   = "512"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  container_definitions    = file("./container_definitions.json")
  execution_role_arn       = module.ecs_task_execution_role.iam_role_arn
}

ソース:example-pragmatic-terraform/9.tf at main · tmknom/example-pragmatic-terraform

※パラメータexecution_role_arnを追加している。

続いてコンテナ定義のコードも変更。

コンテナ定義のJSONファイル、container_definitions.jsonファイルの以下のコードを

リスト9.3:コンテナ定義

/myapp/terraform/tasks/container_definitions.json
[
  {
    "name": "example",
    "image": "nginx:latest",
    "essential": true,
    "portMappings": [
      {
        "protocol": "tcp",
        "containerPort": 80
      }
    ]
  }
]

次のように変更する。

リスト9.10:コンテナ定義にCloudWatch Logsのグループ名を追加

/myapp/terraform/tasks/container_definitions.json
[
  {
    "name": "example",
    "image": "nginx:latest",
    "essential": true,
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-region": "ap-northeast-1",
        "awslogs-stream-prefix": "nginx",
        "awslogs-group": "/ecs/example"
      }
    },
    "portMappings": [
      {
        "protocol": "tcp",
        "containerPort": 80
      }
    ]
  }
]

ソース:example-pragmatic-terraform/container_definitions.json at main · tmknom/example-pragmatic-terraform

※パラメータlogConfigurationを追加している。

sakataku1991sakataku1991

第10章

バッチ

10.1 バッチ設計

10.1.1 バッチ設計の基本原則

P.129
バッチ設計で重要な4つの観点について

  1. ジョブ管理
  2. エラーハンドリング
  3. リトライ
  4. 依存関係制御

10.1.2 ジョブ管理

P.130
※ジョブ管理の手法についての説明。

10.2 ECS Scheduled Tasks

10.2.1 バッチ用タスク定義

バッチ用CloudWatch Logs

P.131
バッチ用のタスク定義から始める。

第9章で作成したcloud_watch.tfファイルをエディターで開き、以下のコードを追記する。

リスト10.1:バッチ用CloudWatch Logsの定義

/myapp/terraform/cloud_watch.tf
# CloudWatch for ECS Scheduled Tasks
resource "aws_cloudwatch_log_group" "for_ecs_scheduled_tasks" {
  name              = "/ecs-scheduled-tasks/example"
  retention_in_days = 180
}

ソース:example-pragmatic-terraform/1.tf at main · tmknom/example-pragmatic-terraform

バッチ用タスク定義

P.132
続いて、バッチ用のタスク定義を実装する。

ecs.tfファイルに以下のコードを追記する。
※このコードはecs.tfファイルに追記でいいのかどうか、ちょっと自信ない...(何てファイルでこのコードを管理すればいいのかちゃんと書いておいてよ...)。検証の必要アリ!!

リスト10.2:バッチ用タスク定義

/myapp/terraform/ecs.tf
# Batch
resource "aws_ecs_task_definition" "example_batch" {
  family                   = "example-batch"
  cpu                      = "256"
  memory                   = "512"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  container_definitions    = file("./batch_container_definitions.json")
  execution_role_arn       = module.ecs_task_execution_role.iam_role_arn
}

ソース:example-pragmatic-terraform/2.tf at main · tmknom/example-pragmatic-terraform

バッチ用コンテナ定義

P.133
コンテナ定義をbatch_container_definitions.jsonというファイルに実装していく。

というわけで、まずはそれ用のディレクトリを作成する。

terraformディレクトリにいる状態で、バッチ用のコンテナ定義を記述するファイル群を置くためのbatchディレクトリを作成する。

ターミナル
mkdir batch

そして作成したbatchディレクトリに移動。

ターミナル
cd batch

batch_container_definitions.jsonというファイルを作成する。

ターミナル
touch batch_container_definitions.json

作成したbatch_container_definitions.jsonファイルをエディターで開き、以下のように編集する。

リスト10.3:バッチ用コンテナ定義

/myapp/terraform/batch/batch_container_definitions.json
[
  {
    "name": "alpine",
    "image": "alpine:latest",
    "essential": true,
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-region": "ap-northeast-1",
        "awslogs-stream-prefix": "batch",
        "awslogs-group": "/ecs-scheduled-tasks/example"
      }
    },
    "command" : ["/bin/date"]
  }
]

ソース:example-pragmatic-terraform/batch_container_definitions.json at main · tmknom/example-pragmatic-terraform

10.2.2 CloudWatchイベントIAMロール

P.134
CloudWatchイベントからECSを起動するためのIAMロールを作成する。

「AmazonEC2ContainerServiceEventsRole」というポリシーの役割

  • 「タスクを実行する」権限の付与
  • 「タスクにIAMロールを渡す」権限の付与

...どうしよう。どのファイルに書けばいいのか全然わからない!
iam_role.tfcloud_watch.tf? それともecs.tf
頼むからファイルとコードの関係性をちゃんと明記してくれ〜!!

わかんないのでとりあえずecs.tfファイルに以下のコードを追記する。

リスト10.4:CloudWatchイベントIAMロールの定義

/myapp/terraform/ecs.tf
# CloudWatch Event IAM Role
module "ecs_events_role" {
  source     = "./iam_role"
  name       = "ecs-events"
  identifier = "events.amazonaws.com"
  policy     = data.aws_iam_policy.ecs_events_role_policy.policy
}

data "aws_iam_policy" "ecs_events_role_policy" {
  arn = "arn:aws:iam::aws:policy/service-role/AmazonEC2ContainerServiceEventsRole"
}

ソース:example-pragmatic-terraform/4.tf at main · tmknom/example-pragmatic-terraform

10.2.3 CloudWatchイベントルール

P.134
ジョブの実行スケジュールを定義するため、CloudWatchのイベントルールを作成する。

cloud_watch.tfファイルに以下のコードを追記する。

リスト10.5:CloudWatchイベントルールの定義

/myapp/terraform/cloud_watch.tf
# CloudWatch Event Rule
resource "aws_cloudwatch_event_rule" "example_batch" {
  name                = "example-batch"
  description         = "とても重要なバッチ処理です"
  schedule_expression = "cron(*/2 * * * ? *)"
}

ソース:example-pragmatic-terraform/5.tf at main · tmknom/example-pragmatic-terraform

10.2.4 CloudWatchイベントターゲット

P.135
CloudWatchイベントターゲットで、実行対象のジョブを定義する。

cloud_watch.tfファイルに以下のコードを追記する。

リスト10.6:CloudWatchイベントターゲットの定義

/myapp/terraform/cloud_watch.tf
# CloudWatch Event Target
resource "aws_cloudwatch_event_target" "example_batch" {
  target_id = "example-batch"
  rule      = aws_cloudwatch_event_rule.example_batch.name
  role_arn  = module.ecs_events_role.iam_role_arn
  arn       = aws_ecs_cluster.example.arn

  ecs_target {
    launch_type         = "FARGATE"
    task_count          = 1
    platform_version    = "1.3.0"
    task_definition_arn = aws_ecs_task_definition.example_batch.arn

    network_configuration {
      assign_public_ip = "false"
      subnets          = [aws_subnet.private_0.id]
    }
  }
}

ソース:example-pragmatic-terraform/6.tf at main · tmknom/example-pragmatic-terraform

10.2.5 バッチの動作確認

P.137
※ここまで実装してきた内容を、filter-log-eventsコマンドを実行して動作確認してみる。

sakataku1991sakataku1991

第11章

鍵管理

11.1 KMS(Key Management Service)

KMSとは?
→ AWSで暗号鍵を管理するためのマネージドサービスのこと。

11.1.1 カスタマーマスターキー

P.139
カスタマーマスターキーを定義する。
そのためにまずはkms.tfを作成。

terraformディレクトリ直下にkms.tfファイルを作成。

ターミナル
touch kms.tf

そしてkms.tfファイルを以下のように編集する。

リスト11.1:カスタマーマスターキーの定義

/myapp/terraform/kms.tf
# Customer Master Key
resource "aws_kms_key" "example" {
  description             = "Example Customer Master Key"
  enable_key_rotation     = true
  is_enabled              = true
  deletion_window_in_days = 30
}

ソース:example-pragmatic-terraform/1.tf at main · tmknom/example-pragmatic-terraform

カスタマーマスターキーの定義に使われる入力パラメータについて

  • description:概要。どういった用途のためのカスタマーマスターキーなのかわかるように名前を付ける。
  • enable_key_rotation:自動ローテーション
  • is_enabled:有効化と無効化。カスタマーマスターキーを有効にするか/無効にするかを設定する。
  • deletion_window_in_days:削除待機期間。デフォルトは30日。待機期間中であればカスタマーマスターキーの削除を取り消すことができる。

11.1.2 エイリアス

P.140

  • カスタマーマスターキーにはUUIDが割り当てられている
  • しかし、そのUUIDは人間からするとわかりづらい
  • なので「エイリアス」を設定できる機能がある

ということで、カスタマーマスターキーのエイリアスを設定するため、kms.tfファイルに以下のコードを追記する。

リスト11.2:エイリアスの定義

/myapp/terraform/kms.tf
# Customer Master Key (Alias)
resource "aws_kms_alias" "example" {
  name          = "alias/example"
  target_key_id = aws_kms_key.example.key_id
}

ソース:example-pragmatic-terraform/2.tf at main · tmknom/example-pragmatic-terraform

※このエイリアスで設定するnameには、alias/というプレフィックスを付ける必要がある!(なので今回のコードではexampleという名前を付けるためにalias/exampleと設定している)

sakataku1991sakataku1991

第12章

設定管理

12.1 コンテナの設定管理

ECSでは、設定をコンテナ起動時に注入する。

12.2 SSMパラメータストア

SSMパラメータストアとは?
→ 設定管理に特化したマネージドサービスのこと。

12.2.1 AWS CLIによる操作

P.143
AWS CLIによるSSMパラメータストアの操作に慣れよう。

平文
SSMパラメータストアに値を保存するput-parameterコマンド

ターミナル
aws ssm put-parameter --name 'plain_name' --value 'plain value' --type String

値を参照するget-parameterコマンド

ターミナル
aws ssm get-parameter --output text --name 'plain_name' --query Parameter.Value

暗号化
暗号化した値を保存するput-parameterコマンド

ターミナル
aws ssm put-parameter --name 'encryption_name' --value 'encryption value' --type SecureString

暗号化された値を複合した状態で参照する--with-decryptionオプション

ターミナル
aws ssm get-parameter --output text --query Parameter.Value --name 'encryption_name' --with-decryption

12.2.2 Terraformによるコード化

P.145
SSMパラメータストアをTerraformで管理する。

平文
暗号化
※Gitなどによるバージョン管理を考慮しない場合の管理方法についての説明。
※リスト12.1とリスト12.2はその意味で実用性がないため、覚えなくてよさそう。割愛。

Terraformによる初期値の設定とAWS CLIによる暗号化

P.147
SSMパラメータストアをTerraformで管理する上で、「Terraformではダミー値を設定して、あとでAWS CLIから更新する」という方法を採用。

今回はmain.tfファイルに実装する。
main.tfファイルに以下のコードを追記する。

リスト12.3:データベースのパスワードのダミー定義

/myapp/terraform/main.tf
# SSM Parameter for DB
resource "aws_ssm_parameter" "db_password" {
  name        = "/db/password"
  value       = "uninitialized"
  type        = "SecureString"
  description = "データベースのパスワード"

  lifecycle {
    ignore_changes = [value]
  }
}

ソース:example-pragmatic-terraform/3.tf at main · tmknom/example-pragmatic-terraform

main.tfファイルを編集後、以下のコマンドを実行し、AWS CLIで更新をかける。

ターミナル
aws ssm put-parameter --name '/db/password' --type SecureString --value 'ModifiedStrongPassword!' --overwrite

12.2.3 SSMパラメータストアとECSの統合

P.148
SSMパラメータストアの値は、ECSのDockerコンテナ内で環境変数として参照できる。
※ECSからSSMパラメータストアの値を参照する権限は、リスト9.7で事前に設定済み。

コンテナ定義用に作成したbatch_container_definitions.jsonファイル(リスト10.3)のコードを

リスト10.3:バッチ用コンテナ定義

/myapp/terraform/batch/batch_container_definitions.json
[
  {
    "name": "alpine",
    "image": "alpine:latest",
    "essential": true,
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-region": "ap-northeast-1",
        "awslogs-stream-prefix": "batch",
        "awslogs-group": "/ecs-scheduled-tasks/example"
      }
    },
    "command" : ["/bin/date"]
  }
]

次のように変更する。

リスト12.4:コンテナ定義からSSMパラメータストアの値を参照

/myapp/terraform/batch/batch_container_definitions.json
[
  {
    "name": "alpine",
    "image": "alpine:latest",
    "essential": true,
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-region": "ap-northeast-1",
        "awslogs-stream-prefix": "batch",
        "awslogs-group": "/ecs-scheduled-tasks/example"
      }
    },
    "secrets": [
      {
        "name": "DB_USERNAME",
        "valueFrom": "/db/username"
      },
      {
        "name": "DB_PASSWORD",
        "valueFrom": "/db/password"
      }
    ],
    "command" : ["/usr/bin/env"]
  }
]

ソース:example-pragmatic-terraform/batch_container_definitions.json at main · tmknom/example-pragmatic-terraform

secretsの値を追加している。

  • name:コンテナ内での環境変数の名前
  • valueFrom:SSMパラメータストアのキー名
sakataku1991sakataku1991

第13章

データストア

13.1 RDS(Relational Database Service)

RDSとは?
→ AWSが提供するリレーショナルデータベースのこと。

  • MySQL
  • PostgresSQL
  • Oracle

などをサポートしている。

13.1.1 DBパラメータグループ

P.150
データベースの設定ファイルを作成する。
※今回はMySQLを採用。

まずはRDSの設定用にrds.tfファイルを作成する。

terraformディレクトリ直下にrds.tfファイルを作成。

ターミナル
touch rds.tf

作成したrds.tfファイルをエディターで開き、以下のように編集する。

リスト13.1:DBパラメータグループの定義

/myapp/terraform/rds.tf
# DB Parameter Group
resource "aws_db_parameter_group" "example" {
  name   = "example"
  family = "mysql5.7"

  parameter {
    name  = "character_set_database"
    value = "utf8mb4"
  }

  parameter {
    name  = "character_set_server"
    value = "utf8mb4"
  }
}

ソース:example-pragmatic-terraform/1.tf at main · tmknom/example-pragmatic-terraform

13.1.2 DBオプショングループ

P.151
DBオプショングループは、データベースエンジンにオプション機能を追加する。

リスト13.2:DBパラメータグループの定義

DBパラメータグループの定義の一例(「MariaDB監査プラグイン」の追加)
resource "aws_db_option_group" "example" {
  name                 = "example"
  engine_name          = "mysql"
  major_engine_version = "5.7"

  option {
    option_name = "MARIADB_AUDIT_PLUGIN"
  }
}

ソース:example-pragmatic-terraform/2.tf at main · tmknom/example-pragmatic-terraform

13.1.3 DBサブネットグループ

P.152
データベースを稼働させるサブネットを、DBサブネットグループで定義する。

リスト13.3:DBサブネットグループの定義

/myapp/terraform/rds.tf
# DB Subnet Group
resource "aws_db_subnet_group" "example" {
  name       = "example"
  subnet_ids = [aws_subnet.private_0.id, aws_subnet.private_1.id]
}

ソース:example-pragmatic-terraform/3.tf at main · tmknom/example-pragmatic-terraform

13.1.4 DBインスタンス

P.153
DBインスタンスを実装。データベースサーバーを作成する。

リスト13.4:DBインスタンスの定義

/myapp/terraform/rds.tf
# DB Instance
resource "aws_db_instance" "example" {
  identifier                 = "example"
  engine                     = "mysql"
  engine_version             = "5.7.25"
  instance_class             = "db.t3.small"
  allocated_storage          = 20
  max_allocated_storage      = 100
  storage_type               = "gp2"
  storage_encrypted          = true
  kms_key_id                 = aws_kms_key.example.arn
  username                   = "admin"
  password                   = "VeryStrongPassword!"
  multi_az                   = true
  publicly_accessible        = false
  backup_window              = "09:10-09:40"
  backup_retention_period    = 30
  maintenance_window         = "mon:10:10-mon:10:40"
  auto_minor_version_upgrade = false
  deletion_protection        = true
  skip_final_snapshot        = false
  port                       = 3306
  apply_immediately          = false
  vpc_security_group_ids     = [module.mysql_sg.security_group_id]
  parameter_group_name       = aws_db_parameter_group.example.name
  option_group_name          = aws_db_option_group.example.name
  db_subnet_group_name       = aws_db_subnet_group.example.name

  lifecycle {
    ignore_changes = [password]
  }
}

ソース:example-pragmatic-terraform/4.tf at main · tmknom/example-pragmatic-terraform

セキュリティグループ

P.156
DBインスタンスのセキュリティグループを設定する。

リスト13.5:DBインスタンスのセキュリティグループの定義

/myapp/terraform/rds.tf
# Security Group
module "mysql_sg" {
  source      = "./security_group/security_group"
  name        = "mysql-sg"
  vpc_id      = aws_vpc.example.id
  port        = 3306
  cidr_blocks = [aws_vpc.example.cidr_block]
}

ソース:example-pragmatic-terraform/5.tf at main · tmknom/example-pragmatic-terraform

13.1.5 マスターパスワードの変更

P.156
リスト13.4で仮設定したpassword(=マスターパスワード)を変更する。
そのためにterraform applyコマンドの実行後に、さらに次のコマンドを実行し、マスターパスワードを変更する。

ターミナル
aws rds modify-db-instance --db-instance-identifier 'example' --master-user-password 'NewMasterPassword!'

※実際には、NewMasterPassword!に独自に用意した複雑なパスワードを入力する!

ここまで編集してみて、rds.tfファイルの中身は最終的に以下のようになった。

/myapp/terraform/rds.tf
# DB Parameter Group
resource "aws_db_parameter_group" "example" {
  name   = "example"
  family = "mysql5.7"

  parameter {
    name  = "character_set_database"
    value = "utf8mb4"
  }

  parameter {
    name  = "character_set_server"
    value = "utf8mb4"
  }
}


# DB Subnet Group
resource "aws_db_subnet_group" "example" {
  name       = "example"
  subnet_ids = [aws_subnet.private_0.id, aws_subnet.private_1.id]
}


# Security Group
module "mysql_sg" {
  source      = "./security_group/security_group"
  name        = "mysql-sg"
  vpc_id      = aws_vpc.example.id
  port        = 3306
  cidr_blocks = [aws_vpc.example.cidr_block]
}


# DB Instance
resource "aws_db_instance" "example" {
  identifier                 = "example"
  engine                     = "mysql"
  engine_version             = "5.7.25"
  instance_class             = "db.t3.small"
  allocated_storage          = 20
  max_allocated_storage      = 100
  storage_type               = "gp2"
  storage_encrypted          = true
  kms_key_id                 = aws_kms_key.example.arn
  username                   = "admin"
  password                   = "VeryStrongPassword!"
  multi_az                   = true
  publicly_accessible        = false
  backup_window              = "09:10-09:40"
  backup_retention_period    = 30
  maintenance_window         = "mon:10:10-mon:10:40"
  auto_minor_version_upgrade = false
  deletion_protection        = true
  skip_final_snapshot        = false
  port                       = 3306
  apply_immediately          = false
  vpc_security_group_ids     = [module.mysql_sg.security_group_id]
  parameter_group_name       = aws_db_parameter_group.example.name
  option_group_name          = aws_db_option_group.example.name
  db_subnet_group_name       = aws_db_subnet_group.example.name

  lifecycle {
    ignore_changes = [password]
  }
}

RDSの削除

P.157
一度AWSに作成したRDSを削除するには、rds.tfファイルの以下の部分を変更する必要がある。

  • deletion_protectionfalseにする(削除保護の無効化)
  • skip_final_snapshottrueにする(スナップショットの作成をスキップするように設定)

rds.tfファイルのaws_db_instanceの以上パラメータを変更したのち、一度terraform applyコマンドを実行する。

ターミナル
terraform apply

それからterraform destroyコマンドを実行することで、ようやくDBインスタンスを削除することができる。

ターミナル
terraform destroy

13.2 ElastiCache

P.158
ElastiCacheはMemcachedとRedisをサポートしている。
今回はRedisを作成する。

13.2.1 ElastiCacheパラメータグループ

P.158
まずはElastiCacheの設定用にelasti_cache.tfファイルを作成する。

terraformディレクトリ直下にelasti_cache.tfファイルを作成。

ターミナル
touch elasti_cache.tf

そしてクラスタモードを無効にする設定を定義。
作成したelasti_cache.tfファイルをエディターで開き、以下のように編集する。

リスト13.6:ElastiCacheパラメータグループの定義

/myapp/terraform/elasti_cache.tf
# ElastiCache Parameter Group
resource "aws_elasticache_parameter_group" "example" {
  name   = "example"
  family = "redis5.0"

  parameter {
    name  = "cluster-enabled"
    value = "no"
  }
}

ソース:example-pragmatic-terraform/6.tf at main · tmknom/example-pragmatic-terraform

13.2.2 ElastiCacheサブネットグループ

P.159
ElastiCacheサブネットグループを定義する。

リスト13.7:ElastiCacheサブネットグループの定義

/myapp/terraform/elasti_cache.tf
# ElastiCache Subnet Group
resource "aws_elasticache_subnet_group" "example" {
  name       = "example"
  subnet_ids = [aws_subnet.private_0.id, aws_subnet.private_1.id]
}

ソース:example-pragmatic-terraform/7.tf at main · tmknom/example-pragmatic-terraform

13.2.3 ElastiCacheレプリケーショングループ

P.159
elasti_cache.tfファイルに以下のコードを追記してElastiCacheレプリケーショングループを実装し、Redisサーバーを作成する。

リスト13.8:ElastiCacheレプリケーショングループの定義

/myapp/terraform/elasti_cache.tf
# ElastiCache Replication Group
resource "aws_elasticache_replication_group" "example" {
  replication_group_id          = "example"
  replication_group_description = "Cluster Disabled"
  engine                        = "redis"
  engine_version                = "5.0.4"
  number_cache_clusters         = 3
  node_type                     = "cache.m3.medium"
  snapshot_window               = "09:10-10:10"
  snapshot_retention_limit      = 7
  maintenance_window            = "mon:10:40-mon:11:40"
  automatic_failover_enabled    = true
  port                          = 6379
  apply_immediately             = false
  security_group_ids            = [module.redis_sg.security_group_id]
  parameter_group_name          = aws_elasticache_parameter_group.example.name
  subnet_group_name             = aws_elasticache_subnet_group.example.name
}

ソース:example-pragmatic-terraform/8.tf at main · tmknom/example-pragmatic-terraform

セキュリティグループ

P.162
ElastiCacheがVPC内からの通信のみを許可するように設定。
作成したセキュリティグループをリスト13.8のsecurity_group_idsに設定する。

リスト13.9:ElastiCacheレプリケーショングループのセキュリティグループの定義

/myapp/terraform/elasti_cache.tf
# Redis Security Group
module "redis_sg" {
  source      = "./security_group/security_group"
  name        = "redis-sg"
  vpc_id      = aws_vpc.example.id
  port        = 6379
  cidr_blocks = [aws_vpc.example.cidr_block]
}

ソース:example-pragmatic-terraform/9.tf at main · tmknom/example-pragmatic-terraform

ここまで編集してみて、elasti_cache.tfファイルの中身は最終的に以下のようになった。

/myapp/terraform/elasti_cache.tf
# ElastiCache Parameter Group
resource "aws_elasticache_parameter_group" "example" {
  name   = "example"
  family = "redis5.0"

  parameter {
    name  = "cluster-enabled"
    value = "no"
  }
}


# ElastiCache Subnet Group
resource "aws_elasticache_subnet_group" "example" {
  name       = "example"
  subnet_ids = [aws_subnet.private_0.id, aws_subnet.private_1.id]
}


# Redis Security Group
module "redis_sg" {
  source      = "./security_group/security_group"
  name        = "redis-sg"
  vpc_id      = aws_vpc.example.id
  port        = 6379
  cidr_blocks = [aws_vpc.example.cidr_block]
}


# ElastiCache Replication Group
resource "aws_elasticache_replication_group" "example" {
  replication_group_id          = "example"
  replication_group_description = "Cluster Disabled"
  engine                        = "redis"
  engine_version                = "5.0.4"
  number_cache_clusters         = 3
  node_type                     = "cache.m3.medium"
  snapshot_window               = "09:10-10:10"
  snapshot_retention_limit      = 7
  maintenance_window            = "mon:10:40-mon:11:40"
  automatic_failover_enabled    = true
  port                          = 6379
  apply_immediately             = false
  security_group_ids            = [module.redis_sg.security_group_id]
  parameter_group_name          = aws_elasticache_parameter_group.example.name
  subnet_group_name             = aws_elasticache_subnet_group.example.name
}
sakataku1991sakataku1991

第14章

デプロイメントパイプライン

14.1 デプロイメントパイプラインの設計

P.164
※GitHubにコードをプッシュ→ECSへコンテナをデプロイ、の流れの説明

14.2 コンテナレジストリ

P.164
まず、Dockerイメージを補完するコンテナレジストリを作成。
そのためにECRを利用する。

14.2.1 ECRリポジトリ

P.164
Dockerイメージを保管するECRリポジトリを実装していく。

まずはECRの設定用にecr.tfファイルを作成する。

terraformディレクトリ直下にecr.tfファイルを作成。

ターミナル
touch ecr.tf

作成したecr.tfファイルをエディターで開き、以下のように編集する。

リスト14.1:ECRリポジトリの定義

/myapp/terraform/ecr.tf
# ECR Repository
resource "aws_ecr_repository" "example" {
  name = "example"
}

ソース:example-pragmatic-terraform/1.tf at main · tmknom/example-pragmatic-terraform

14.2.2 ECRライフサイクルポリシー

P.165
ECRリポジトリに保存できるイメージの数を設定する。
イメージが増えすぎないように制限をかける。

リスト14.2:ECRライフサイクルポリシーの定義

/myapp/terraform/ecr.tf
# ECR Lifecycle Policy
resource "aws_ecr_lifecycle_policy" "example" {
  repository = aws_ecr_repository.example.name

  policy = <<EOF
  {
    "rules": [
      {
        "rulePriority": 1,
        "description": "Keep last 30 release tagged images",
        "selection": {
          "tagStatus": "tagged",
          "tagPrefixList": ["release"],
          "countType": "imageCountMoreThan",
          "countNumber": 30
        },
        "action": {
          "type": "expire"
        }
      }
    ]
  }
EOF
}

ソース:example-pragmatic-terraform/2.tf at main · tmknom/example-pragmatic-terraform

14.2.3 Dockerイメージのプッシュ

P.166

Dockerクライアントを認証する。

ターミナル
$(aws ecr get-login --region $AWS_DEFAULT_REGION --no-include-email)

イメージ名のレジストリをECRにして、適当なDockerfileをビルドする。

ターミナル
docker build -t XXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/example:latest .

※「XXXXXX」は自身の環境の文字列に書き換える!

Dockerイメージをプッシュする。

ターミナル
docker push XXXXXX.dkr.ecr.ap-northeast-1.amazonaws.com/example:latest

※「XXXXXX」は自身の環境の文字列に書き換える!

14.3 継続的インテグレーション

P.167
AWSの継続的インテグレーション(CI)サービスといえば...
→ CodeBuild

14.3.1 CodeBuildサービスロール

P.167
CodeBuildが使用するIAMロールを作成する。

まずはCodeBuildの設定用にcode_build.tfファイルを作成する。

terraformディレクトリ直下にcode_build.tfファイルを作成。

ターミナル
touch code_build.tf

作成したcode_build.tfファイルをエディターで開き、以下のように編集する。

リスト14.3:CodeBuildサービスロールのポリシードキュメントの定義

/myapp/terraform/code_build.tf
# IAM Role (Policy Document)
data "aws_iam_policy_document" "codebuild" {
  statement {
    effect    = "Allow"
    resources = ["*"]

    actions = [
      "s3:PutObject",
      "s3:GetObject",
      "s3:GetObjectVersion",
      "logs:CreateLogGroup",
      "logs:CreateLogStream",
      "logs:PutLogEvents",
      "ecr:GetAuthorizationToken",
      "ecr:BatchCheckLayerAvailability",
      "ecr:GetDownloadUrlForLayer",
      "ecr:GetRepositoryPolicy",
      "ecr:DescribeRepositories",
      "ecr:ListImages",
      "ecr:DescribeImages",
      "ecr:BatchGetImage",
      "ecr:InitiateLayerUpload",
      "ecr:UploadLayerPart",
      "ecr:CompleteLayerUpload",
      "ecr:PutImage",
    ]
  }
}

ソース:example-pragmatic-terraform/3.tf at main · tmknom/example-pragmatic-terraform

このポリシードキュメントで定義する権限

  • ビルド出力アーティファクトを保存するためのS3操作権限
  • ビルドログを出力するためのCloudWatch Logs操作権限
  • DockerイメージをプッシュするためのECR操作権限

IAMロール

P.169
CodeBuild用のIAMロールを実装する。

リスト14.4:CodeBuildサービスロールの定義

/myapp/terraform/code_build.tf
# IAM Role (for CodeBuild)
module "codebuild_role" {
  source     = "./iam_role"
  name       = "codebuild"
  identifier = "codebuild.amazonaws.com"
  policy     = data.aws_iam_policy_document.codebuild.json
}

ソース:example-pragmatic-terraform/4.tf at main · tmknom/example-pragmatic-terraform

14.3.2 CodeBuildプロジェクト

P.169
CodeBuildプロジェクトを作成する。

リスト14.5:CodeBuildプロジェクトの定義

/myapp/terraform/code_build.tf
# CodeBuild Project
resource "aws_codebuild_project" "example" {
  name         = "example"
  service_role = module.codebuild_role.iam_role_arn

  source {
    type = "CODEPIPELINE"
  }

  artifacts {
    type = "CODEPIPELINE"
  }

  environment {
    type            = "LINUX_CONTAINER"
    compute_type    = "BUILD_GENERAL1_SMALL"
    image           = "aws/codebuild/standard:2.0"
    privileged_mode = true
  }
}

ソース:example-pragmatic-terraform/5.tf at main · tmknom/example-pragmatic-terraform

14.3.3 ビルド仕様

P.171
CodeBuildのビルド処理を規定するのがbuildspec.ymlファイルとなる。
※今回自分はCIツールに「Capistrano」を使用するので、この工程は割愛...。

14.4 継続的デリバリー

14.4.1 CodePipelineサービスロール

P.173
CodePipelineが使用するIAMロールを作成する。
※こちらの工程も割愛...。

14.4.2 アーティファクトストア

P.175
※CodePipelineの各ステージでデータの受け渡しに使用するアーティファクトストアの作成方法についての説明。

14.4.3 GitHubトークン

P.176
※割愛。

14.4.4 CodePipeline

P.177
※CodePipelineについての説明。

Builldステージ

P.180
リスト14.11

14.4.5 CodePipeline Webhook

P.181
リスト14.12

14.4.6 GitHubプロバイダ

14.4.7 GitHub Webhook

sakataku1991sakataku1991

第15章

SSHレスオペレーション

15.1 オペレーションサーバーの設計

P.186
EC2とSession Manager(以降、「SSM」と表記する)を組み合わせた、オペレーションサーバーの構築方法を学ぶ。

オペレーションサーバーの設計で考慮すべきこと

  • 運用
  • セキュリティ
  • トレーサビリティ

15.1.1 運用

  • EC2にはDockerだけインストールする
  • ECS Fargateにデプロイするイメージを流用する
  • 設定情報はSSMパラメータストアから取得し、EC2では管理しない

15.1.2 セキュリティ

  • SSMを導入し、SSHログインを不要にする
  • インターネットからのアクセスも遮断する

15.1.3 トレーサビリティ

  • SSMで操作ログを保存する
  • コマンドの実行結果も自動的に残す

15.2 Session Manager(SSM)

P.187
Session Manager(SSM)とは?
→ AWSにて、SSHログインなしにシェルアクセスを実現するサービスのこと。

15.2.1 インスタンスプロファイル

EC2にサービス権限を付与する場合は、IAMロールを関連付けるのではなく、IAMロールをラップしたインスタンスプロファイルを関連付ける。

ポリシードキュメント

P.188
ポリシードキュメントを定義する。

まずはSSMの設定用にssm.tfファイルを作成する。

terraformディレクトリ直下にssm.tfファイルを作成。

ターミナル
touch ssm.tf

作成したssm.tfファイルをエディターで開き、以下のように編集する。

リスト15.1:オペレーションサーバー用ポリシードキュメントの定義

/myapp/terraform/ssm.tf
# IAM Policy Document (EC2 for SSM)
data "aws_iam_policy_document" "ec2_for_ssm" {
  source_json = data.aws_iam_policy.ec2_for_ssm.policy

  statement {
    effect    = "Allow"
    resources = ["*"]

    actions = [
      "s3:PutObject",
      "logs:PutLogEvents",
      "logs:CreateLogStream",
      "ecr:GetAuthorizationToken",
      "ecr:BatchCheckLayerAvailability",
      "ecr:GetDownloadUrlForLayer",
      "ecr:BatchGetImage",
      "ssm:GetParameter",
      "ssm:GetParameters",
      "ssm:GetParametersByPath",
      "kms:Decrypt",
    ]
  }
}


# IAM Policy (EC2 for SSM)
data "aws_iam_policy" "ec2_for_ssm" {
  arn = "arn:aws:iam::aws:policy/AmazonSSMManagedInstanceCore"
}

ソース:example-pragmatic-terraform/1.tf at main · tmknom/example-pragmatic-terraform

IAMロール

P.189
オペレーションサーバー用のIAMロールを定義する。

ssm.tfファイルに以下のコードを追記する。

リスト15.2:オペレーションサーバー用IAMロールの定義

/myapp/terraform/ssm.tf
# EC2 for SSM Role
module "ec2_for_ssm_role" {
  source     = "./iam_role/iam_role"
  name       = "ec2-for-ssm"
  identifier = "ec2.amazonaws.com"
  policy     = data.aws_iam_policy_document.ec2_for_ssm.json
}

ソース:example-pragmatic-terraform/2.tf at main · tmknom/example-pragmatic-terraform

インスタンスプロファイル

P.190
インスタンスプロファイルをEC2インスタンスに関連付ける。

ssm.tfファイルに以下のコードを追記する。

リスト15.3:インスタンスプロファイルの定義

/myapp/terraform/ssm.tf
# IAM Instance Profile (EC2 for SSM)
resource "aws_iam_instance_profile" "ec2_for_ssm" {
  name = "ec2-for-ssm"
  role = module.ec2_for_ssm_role.iam_role_name
}

ソース:example-pragmatic-terraform/3.tf at main · tmknom/example-pragmatic-terraform

15.2.2 EC2インスタンス

P.190
EC2インスタンスを作成し、オペレーションサーバーを構築する。

ssm.tfファイルに以下のコードを追記する。

リスト15.4:オペレーションサーバー用EC2インスタンスの定義

/myapp/terraform/ssm.tf
# Instance (Example for Operation)
resource "aws_instance" "example_for_operation" {
  ami                  = "ami-0c3fd0f5d33134a76"
  instance_type        = "t3.micro"
  iam_instance_profile = aws_iam_instance_profile.ec2_for_ssm.name
  subnet_id            = aws_subnet.private_0.id
  user_data            = file("./user_data.sh")
}

output "operation_instance_id" {
  value = aws_instance.example_for_operation.id
}

ソース:example-pragmatic-terraform/4.tf at main · tmknom/example-pragmatic-terraform

上記コードによるオペレーションサーバーの構築のため、EC2インスタンス作成時に実行するプロビジョニングスクリプトをuser_data.shファイルに定義する。

user_data.shに以下のコードを追記する。

リスト15.5:オペレーションサーバー用User Dataの定義

/myapp/terraform/user_data.sh
#!/bin/sh
amazon-linux-extras install -y docker
systemctl start docker
systemctl enable docker

ソース:example-pragmatic-terraform/user_data.sh at main · tmknom/example-pragmatic-terraform

15.2.3 オペレーションログ

P.192
SSMの操作ログを自動保存するために、SSM Documentを作成する。
ログの保存先にはS3バケットを指定する。

ssm.tfに以下のコードを追記。

リスト15.6:オペレーションログを保存するS3バケットの定義

/myapp/terraform/ssm.tf
# Operation Log (for S3)
resource "aws_s3_bucket" "operation" {
  bucket = "operation-pragmatic-terraform"

  lifecycle_rule {
    enabled = true

    expiration {
      days = "180"
    }
  }
}

ソース:example-pragmatic-terraform/6.tf at main · tmknom/example-pragmatic-terraform

CloudWatch Logs

P.192
オペレーションログ保存先のCloudWatch Logsを定義する。

ssm.tfに以下のコードを追記。

リスト15.7:オペレーションログを保存するCloudWatch Logsの定義

/myapp/terraform/ssm.tf
# Operation Log (for CloudWatch Logs)
resource "aws_cloudwatch_log_group" "operation" {
  name              = "/operation"
  retention_in_days = 180
}

ソース:example-pragmatic-terraform/7.tf at main · tmknom/example-pragmatic-terraform

SSM Document

P.193
SSM Documentを定義する。

ssm.tfに以下のコードを追記。

リスト15.8:Session Manager用SSM Documentの定義

/myapp/terraform/ssm.tf
# SSM Document
resource "aws_ssm_document" "session_manager_run_shell" {
  name            = "SSM-SessionManagerRunShell"
  document_type   = "Session"
  document_format = "JSON"

  content = <<EOF
  {
    "schemaVersion": "1.0",
    "description": "Document to hold regional settings for Session Manager",
    "sessionType": "Standard_Stream",
    "inputs": {
      "s3BucketName": "${aws_s3_bucket.operation.id}",
      "cloudWatchLogGroupName": "${aws_cloudwatch_log_group.operation.name}"
    }
  }
EOF
}

ソース:example-pragmatic-terraform/8.tf at main · tmknom/example-pragmatic-terraform

15.3 ローカル環境

P.194
ローカル環境をセットアップする。

15.3.1 Session Manager Plugin

P.195
Session Manager Pluginをインストールする。

ターミナルで以下のコマンド(5つ)を順番に実行していく。

ターミナル
cd /tmp
ターミナル
curl "https://s3.amazonaws.com/session-manager-downloads/plugin/latest/mac/sessionmanager-bundle.zip" -o "sessionmanager-bundle.zip"
ターミナル
unzip sessionmanager-bundle.zip
ターミナル
sudo ./sessionmanager-bundle/install -i /usr/local/sessionmanagerplugin -b /usr/local/bin/session-manager-plugin

上記のsudo~コマンドの実行後、以下のように表示される。

ターミナル
Password:

自身のMacのログインパスワードを入力してEnterキーを押す。

ターミナル
rm -rf sessionmanager-bundle sessionmanager-bundle.zip

最後に、次のコマンドを実行してSession Manager Pluginがインストールされたことを確認する。

ターミナル
session-manager-plugin

上記のsession-manager-pluginコマンドの実行後、以下のように表示されればSession Manager PluginのインストールはOK。

ターミナル
The Session Manager plugin was installed successfully. Use the AWS CLI to start a session.

15.3.2 シェルアクセス

P.196
AWS CLI経由でシェルアクセスを試してみる。

※ここでは以降のコマンドについては割愛。本番時にはコマンドを実行してちゃんと確認すること!

sakataku1991sakataku1991

第16章

ロギング

16.1 ロギングの種類

P.198
どのサービスがどこにログを保存するのか把握・整理する。

16.1.1 S3へのロギング

P.198
S3へロギングを行なうサービスは以下の通り。

  • ALB
  • Session Manager(SSM)
  • CloudTrail
  • VPCフローログ
  • S3アクセスログ

16.1.2 CloudWatch Logsへのロギング

P.198
CloudWatch Logsへロギングを行なうサービスは以下の通り。

  • ECS
  • RDS
  • Route 53
  • Session Manager(SSM)
  • CloudTrail
  • VPCフローログ
  • Lambda

S3にしろ、CloudWatch Logsにしろ、

  • ログを定期的に削除する設定
  • ログの保持期間を指定する設定

以上の設定を必ず行なうこと!

16.2 ログ検索

P.199
ELK(Elasticsearch, Logstash, Kibana)スタックなどを使って、自前でログ検索の仕組みを構築することも可能。しかし、今回はAthenaとCloudWatch Logs Insightsを試してみる。

16.2.1 Athena

P.199
Athena(アテナ)とは?
→ S3のデータを直接SQLで検索するサービスのこと。

※AthenaはTerraformで管理するメリットが薄いため、AWSマネジメントコンソールで直接操作する。

...というわけでブラウザでAWSマネジメントコンソールを開き、Athenaのページへと移動。
Athenaのページのメニューにて「クエリエディタ」をクリック。クエリエディタの画面を表示させる。

クエリの入力欄に以下のコードを入力する。

クエリエディタ
CREATE DATABASE mylog;

[保存]ボタンを押し、クエリを保存する。
今回は、
クエリ名test-mylog
クエリの説明My log creation test.
以上を入力して...

クエリエディタ
CREATE EXTERNAL TABLE IF NOT EXISTS alb_logs (
            type string,
            time string,
            elb string,
            client_ip string,
            client_port int,
            target_ip string,
            target_port int,
            request_processing_time double,
            target_processing_time double,
            response_processing_time double,
            elb_status_code int,
            target_status_code string,
            received_bytes bigint,
            sent_bytes bigint,
            request_verb string,
            request_url string,
            request_proto string,
            user_agent string,
            ssl_cipher string,
            ssl_protocol string,
            target_group_arn string,
            trace_id string,
            domain_name string,
            chosen_cert_arn string,
            matched_rule_priority string,
            request_creation_time string,
            actions_executed string,
            redirect_url string,
            lambda_error_reason string,
            target_port_list string,
            target_status_code_list string,
            classification string,
            classification_reason string
            )
            ROW FORMAT SERDE 'org.apache.hadoop.hive.serde2.RegexSerDe'
            WITH SERDEPROPERTIES (
            'serialization.format' = '1',
            'input.regex' = 
        '([^ ]*) ([^ ]*) ([^ ]*) ([^ ]*):([0-9]*) ([^ ]*)[:-]([0-9]*) ([-.0-9]*) ([-.0-9]*) ([-.0-9]*) (|[-0-9]*) (-|[-0-9]*) ([-0-9]*) ([-0-9]*) \"([^ ]*) (.*) (- |[^ ]*)\" \"([^\"]*)\" ([A-Z0-9-_]+) ([A-Za-z0-9.-]*) ([^ ]*) \"([^\"]*)\" \"([^\"]*)\" \"([^\"]*)\" ([-.0-9]*) ([^ ]*) \"([^\"]*)\" \"([^\"]*)\" \"([^ ]*)\" \"([^\s]+?)\" \"([^\s]+)\" \"([^ ]*)\" \"([^ ]*)\"')
            LOCATION 's3://your-alb-logs-directory/AWSLogs/<ACCOUNT-ID>/elasticloadbalancing/<REGION>/';

ソース:Application Load Balancer ログのクエリ - Amazon Athena

sakataku1991sakataku1991

第17章

Terraformベストプラクティス

この章ではTerraformの運用・設計について学習する。

17.1 Terraformバージョンを固定する

P.212
チーム開発を見越してTerraformのバージョンを固定させる。

現時点で自身の環境にインストールしているTerraformのバージョンを確認。

ターミナル
terraform --version

terraformディレクトリ直下にversion.tfファイルを作成する。

ターミナル
touch version.tf

version.tfファイルに以下のコードを追記。

リスト17.1:Terraformバージョンの定義

/myapp/terraform/version.tf
terraform {
  required_version = "1.1.7"
}

ソース:example-pragmatic-terraform/1.tf at main · tmknom/example-pragmatic-terraform

17.2 プロバイダバージョンを固定する

P.212
Terraformのバージョン同様、プロバイダのバージョンも固定する。

現時点で自身の環境にインストールしているTerraform AWS Providerのバージョンを確認。

ターミナル
terraform providers --version

version.tfファイルに以下のコードを追記する。

リスト17.2:プロバイダバージョンの定義

/myapp/terraform/version.tf
provider "aws" {
  version = "4.8.0"
}

ソース:example-pragmatic-terraform/2.tf at main · tmknom/example-pragmatic-terraform

そして、ここまでのバージョン固定の設定を行なったら、それを反映させるためにターミナルで以下のコマンドを実行する。

ターミナル
terraform init

17.3 削除操作を抑止する

P.213
削除されると困る重要なリソースの削除操作を抑止する設定を追加する。

s3.tfファイルに以下のコードを追記する。

リスト17.3:ライフサイクルによる削除抑止定義

/myapp/terraform/s3.tf
# Suppressing delete operations
resource "aws_s3_bucket" "prevent_destroy_bucket" {
  bucket = "prevent-destroy-pragmatic-terraform"

  lifecycle {
    prevent_destroy = true
  }
}

ソース:example-pragmatic-terraform/3.tf at main · tmknom/example-pragmatic-terraform
prevent_destroy = true←このprevent_destroyパラメータをtrueにすることでリソースの削除を抑止する機能が有効になる。

17.4 コードフォーマットをかける

P.214
Terraformのコードフォーマット機能を有効化する。

ターミナルで以下のコマンドを実行すると、Terraformファイルのコードフォーマット機能が有効になる。

ターミナル
terraform fmt -recursive

-recursiveオプション:サブディレクトリ配下のファイルも含め、すべてのファイルをフォーマットするためのオプション

-checkオプションを付けてterraform fmtコマンドを実行すると、コマンドの実行時点で存在している.tfファイルがフォーマット済みかどうかをチェックすることができる。

ターミナル
terraform fmt -recursive -check

17.5 バリデーションをかける

P.214
作成したTerraformのファイルにバリデーションをかける。

バリデーションの基本コマンドは以下の通り。

ターミナル
terraform varidate

しかし、terraform varidateコマンドだと、このコマンドを実行したディレクトリ直下のファイルにしかバリデーション機能が実行されない。そこで色々とオプションを付けることですべてのディレクトリ・すべてのファイルに対してバリデーションチェックがされるようにする。
その場合のコマンドは以下の通り。

ターミナル
find . -type f -name '*.tf' -exec dirname {} \; | sort -u | xargs -I {} terraform validate {}

17.6 オートコンプリートを有効にする

P.215
Terraformのオートコンプリート機能を有効化する。

ターミナルで以下のコマンドを実行すると、bashやzshでTerraformファイルのオートコンプリート機能が追加される。

ターミナル
terraform -install-autocomplete

17.7 プラグインキャッシュを有効にする

P.216
terraform initコマンド実行時にダウンロードされるプロバイダのバイナリファイルのキャッシュを有効化する。

まずはキャッシュの設定用に.terraformrcファイルを作成する。

terraformディレクトリ直下に.terraformrcファイルを作成。

ターミナル
touch .terraformrc

作成した.terraformrcファイルをエディターで開き、以下のように編集する。

リスト17.4:プラグインキャッシュの有効化

/myapp/terraform/.terraformrc
plugin_cache_dir = "$HOME/.terraform.d/plugin-cache"

ソース:example-pragmatic-terraform/.terraformrc at main · tmknom/example-pragmatic-terraform

.terraformrcファイルの編集ができたら、キャッシュを保存するディレクトリを作成する。
ターミナルで以下のコマンドを実行。

ターミナル
mkdir -p "$HOME/.terraform.d/plugincache"

.terraformrcファイルや上記ディレクトリ作成コマンドにある$HOME(ホームディレクトリ)は、自身のPCの、現在使用しているアカウントのホームディレクトリに相当する。

17.8 TFLintで不正なコードを検出する

17.8.1 TFLintのインストール

P.217
TFLintとは?
→ Terraformのリンターのこと。TFLintをインストールすることで、terraform planコマンドではエラーとして検出されないような不正コードも徹底的に検出されるようになる。

以下のコマンドでTFLintをインストールする。

ターミナル
brew install tflint

TFLintのインストールが完了したら、次のコマンドでインストールしたTFLintのバージョンを確認する。

ターミナル
tflint --version

以下のように表示されればTFLintのインストールはOK。

ターミナル
TFLint version 0.35.0

17.8.2 TFLintの使い方

P.218
TFLintの実行コマンドは以下の通り。

ターミナル
tflint

※上記コマンドでは、サブディレクトリ以下のファイルまではリントしてくれないので注意!!

17.8.3 Deep Checking

P.218
tflintコマンドに--deepオプションを付けると、AWS APIを実行してより詳細なチェックを行なうことができる。

ターミナル
tflint --deep --aws-region=ap-northeast-1

--deepオプションはTFLintのv0.23.0で廃止されてしまった模様。なので、新しいバージョンのTFLintで--deepオプションを使うには、.tflint.hclファイルを作成してプラグインを追加する必要があるとのこと(.tflint.hclファイルの作成方法やプラグインの追加方法等に関しては別途要調査!)。

sakataku1991sakataku1991

第18章

AWSベストプラクティス

AWS固有のベストプラクティスを学ぶ。

P.220

18.1 ネットワーク系デフォルトリソースの使用を避ける

  • VPC

など

18.2 データストア系デフォルトリソースの使用を避ける

  • RDS
  • ElastiCache

など

18.3 APIの削除保護機能を活用する

  • aws_db_instanceリソースのdeletion_protection
  • aws_lbリソースのenable_deletion_protection
  • aws_s3_bucketリソースのforce_destroy

など

18.4 暗黙的な依存関係を把握する

  • EIP →「インターネットゲートウェイ」に依存
  • NATゲートウェイ →「インターネットゲートウェイ」に依存

※暗黙的な依存関係がある場合は、depends_onを定義することで依存関係を明示するべし!

18.5 暗黙的に作られるリソースに注意する

  • サービスにリンクされたロール(Service-Linked Role)← IAMロールの特殊版
    • aws_ecs_clusterリソースとaws_ecs_serviceリソースの作成時
sakataku1991sakataku1991

第19章

高度な構文

この章では第3章の発展として、さらに高度な構文について学習する。
※この章の内容はリファレンス的に活用するとよさそう。

19.1 三項演算子

P.224
Terraformでは、三項演算子を使うことができる。

本番環境とステージング環境でインスタンスタイプを切り替える。

リスト19.1:三項演算子によるインスタンスタイプの切り替え

/myapp/terraform/main.tf
# Switch instance type
variable "env" {}

resource "aws_instance" "example" {
  ami           = "ami-0c3fd0f5d33134a76"
  instance_type = var.env == "prod" ? "m5.large" : "t3.micro"
}

ソース:example-pragmatic-terraform/1.tf at main · tmknom/example-pragmatic-terraform

上記の設定後、terraform planコマンドの実行時にenv変数を切り替えれば、それに応じたplan結果が返ってくる。

本番環境

ターミナル
terraform plan -var 'env=prod'

ステージング環境

ターミナル
terraform plan -var 'env=stage'

19.2 複数リソース作成

P.224
Terraformにはcountというメタ引数が存在する。
countを使うことで、複数のリソースを簡単に作成することができる。

リスト19.2:coutによる複数リソースの定義

/myapp/terraform/main.tf
# Create 3 VPC
resource "aws_vpc" "examples" {
  count      = 3
  cidr_block = "10.${count.index}.0.0/16"
}

ソース:example-pragmatic-terraform/2.tf at main · tmknom/example-pragmatic-terraform

19.3 リソース作成制御

P.225
三項演算子とcountを組み合わせると、リソース作成を制御できる。

試しにsecurity_groupモジュールを実装してみる。

リスト19.3:リソース作成を制御するモジュールの定義

/myapp/terraform/security_group.tf
# Security Group Module
variable "allow_ssh" {
  type = bool
}

resource "aws_security_group" "example" {
  name = "example"
}

resource "aws_security_group_rule" "egress" {
  type              = "egress"
  from_port         = 0
  to_port           = 0
  protocol          = "-1"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.example.id
}

resource "aws_security_group_rule" "ingress" {
  count = var.allow_ssh ? 1 : 0

  type              = "ingress"
  from_port         = 22
  to_port           = 22
  protocol          = "tcp"
  cidr_blocks       = ["0.0.0.0/0"]
  security_group_id = aws_security_group.example.id
}

output "allow_ssh_rule_id" {
  value = join("", aws_security_group_rule.ingress[*].id)
}

ソース:example-pragmatic-terraform/3.tf at main · tmknom/example-pragmatic-terraform

リスト19.4ではSSHを許可するセキュリティグループルールが作成される。

リスト19.4:「SSHを許可するセキュリティグループルール」を作成する

/myapp/terraform/main.tf
# Security Group (allow SSH)
module "allow_ssh" {
  source    = "./security_group/security_group"
  allow_ssh = true
}

output "allow_ssh_rule_id" {
  value = module.allow_ssh.allow_ssh_rule_id
}

ソース:example-pragmatic-terraform/4.tf at main · tmknom/example-pragmatic-terraform

SSHを許可するセキュリティグループルールを作成しない場合は、次のリスト19.5のように実装する。

リスト19.5:「SSHを許可するセキュリティグループルール」を作成しない

/myapp/terraform/main.tf
# Security Group (not allow SSH)
module "disallow_ssh" {
  source    = "./security_group/security_group"
  allow_ssh = false
}

output "disallow_ssh_rule_id" {
  value = module.disallow_ssh.allow_ssh_rule_id
}

ソース:example-pragmatic-terraform/5.tf at main · tmknom/example-pragmatic-terraform

19.4 主要なデータソース

19.4.1 AWSアカウントID

P.228
aws_caller_identifyデータソースを使うことで、自身のAWSアカウントIDを取得することができる。

リスト19.6:AWSアカウントIDの取得

/myapp/terraform/main.tf
# Get My AWS Account ID
data "aws_caller_identity" "current" {}

output "account_id" {
  value = data.aws_caller_identity.current.account_id
}

ソース:example-pragmatic-terraform/6.tf at main · tmknom/example-pragmatic-terraform

19.4.2 リージョン

P.228
aws_resionデータソースを使うことで、リージョンを取得することができる。

リスト19.7:リージョンの取得

/myapp/terraform/main.tf
# Get Region
data "aws_region" "current" {}

output "region_name" {
  value = data.aws_region.current.name
}

ソース:example-pragmatic-terraform/7.tf at main · tmknom/example-pragmatic-terraform

19.4.3 アベイラビリティゾーン

P.229
aws_availability_zonesデータソースを使うことで、アベイラビリティゾーンをリストで取得することができる。

リスト19.8:アベイラビリティゾーンのリストの取得

/myapp/terraform/main.tf
# Get Availability Zones List
data "aws_availability_zones" "available" {
  state = "available"
}

output "availability_zones" {
  value = data.aws_availability_zones.available.names
}

ソース:example-pragmatic-terraform/8.tf at main · tmknom/example-pragmatic-terraform

19.4.4 サービスアカウント

P.229
第6章のリスト6.5では、「582318560864」という特殊なアカウントが登場するが、これはALBの「サービスアカウント」と呼ばれるものである。aws_elb_service_accountデータソースをリスト19.9のように定義すると、ALBのサービスアカウントを取得することができる。

リスト19.9:ALBのサービスアカウントの取得

/myapp/terraform/main.tf
# Get ELB Service Account
data "aws_elb_service_account" "current" {}

output "alb_service_account_id" {
  value = data.aws_elb_service_account.current.id
}

ソース:example-pragmatic-terraform/9.tf at main · tmknom/example-pragmatic-terraform
aws_cloudtrail_service_accountデータソースやaws_redshift_service_accountデータソースなど、他のサービスアカウントのデータソースも提供されている。

19.5 主要な組込関数

P.230
Terraformの組み込み関数を試すには、ターミナルでterraform consoleコマンドを実行してみるといい。

ターミナル
terraform console

コンソールを終了するにはexitと入力し、Enterキーを押す。

ターミナル
exit

19.5.1 Numeric Functions

P.231
Terraformでは、数値演算のために以下の関数が提供されている。

  • max関数
  • floor関数
  • pow関数

max関数
実行コマンド

ターミナル
max(1, 100, 10)

実行結果

ターミナル
100

19.5.2 String Functions

P.231
Terraformでは、文字列操作のために以下の関数が提供されている。

  • substr関数
  • format関数
  • split関数

substr関数
実行コマンド

ターミナル
substr("Pragmatic Terraform on AWS", 10, 9)

※部分文字列を取得する

実行結果

ターミナル
Terraform

19.5.3 Collection Functions

P.231
Terraformでは、コレクション操作のために以下の関数が提供されている。

  • flatten関数
  • concat関数
  • length関数

flatten関数
実行コマンド

ターミナル
flatten([["Pragmatic"], ["Terraform", ["on", "AWS"]]])

※多次元配列を1次元配列に変換する

実行結果

ターミナル
[
  "Pragmatic",
  "Terraform",
  "on",
  "AWS"
]

19.5.4 Filesystem Functions

P.232
Terraformでは、ファイル操作のために以下の関数が提供されている。

  • templatefile関数
  • fileexists関数
  • file関数

templatefile関数
まずはテンプレートファイルinstall.shを作成し

ターミナル
touch install.sh

作成したinstall.shファイルを以下のように編集する。

リスト19.10:テンプレートファイルの定義

/myapp/terraform/install.sh
#!/bin/bash
yum install -y ${package}

ソース:example-pragmatic-terraform/install.sh at main · tmknom/example-pragmatic-terraform

以上のファイルを用意した後は、templatefile関数の実行時にpackageに変数を埋め込むことができる。

実行コマンド

ターミナル
templatefile("${path.module}/install.sh", { package = "httpd" })

実行結果

ターミナル
#!/bin/bash
yum install -y httpd

19.5.5 その他の組み込み関数

P.233
Terraformでは、他にも以下のような関数が提供されている。

  • Encoding Functions:エンコード・デコードの関数
  • Date and Time Functions:日時関数
  • Hash and Crypto Functions:ハッシュ化・暗号化の関数
  • IP Network Functions:IPネットワークの関数
  • Type Conversion Functions:型変換の関数

19.6 ランダム文字列

  • リスト13.2のマスターパスワード
  • リスト14.12の秘密鍵

以上のようなランダム性が求められる値では、Randomプロバイダのrandom_stringリソースを使うとよい。

実際にrandom_stringリソースを使って、パスワードを自動生成してみる。

リスト19.11:パスワードのランダム生成

/myapp/terraform/main.tf
# Random String (Password)
provider "random" {}

resource "random_string" "password" {
  length  = 32
  special = false
}

ソース:example-pragmatic-terraform/11.tf at main · tmknom/example-pragmatic-terraform
※DBインスタンスのマスターパスワードには一部の特殊文字が使えないため、special = falseのコードで特殊文字を抑制している。

続けて以下のコードを実装し、先ほどランダム生成したパスワードをDBインスタンスのマスターパスワードに設定してみる。

リスト19.12:DBインスタンスにRandomプロバイダが生成したパスワードを設定

/myapp/terraform/main.tf
resource "aws_db_instance" "example" {
  engine              = "mysql"
  instance_class      = "db.t3.small"
  allocated_storage   = 20
  skip_final_snapshot = true
  username            = "admin"
  password            = random_string.password.result
}

ソース:example-pragmatic-terraform/12.tf at main · tmknom/example-pragmatic-terraform

19.7 Multipleプロバイダ

P.235
複数のリージョンにリソースを作成する方法。

リスト19.13のように実装し、実際に複数のリージョンにリソースを作成してみる。

リスト19.13:Multipleプロバイダの定義

/myapp/terraform/main.tf
# Multiple Provider
provider "aws" {
  alias  = "virginia"
  region = "us-east-1"
}

provider "aws" {
  region = "ap-northeast-1"
}

ソース:example-pragmatic-terraform/13.tf at main · tmknom/example-pragmatic-terraform
aliasが未定義のプロバイダはデフォルトプロバイダとして扱われる。というわけで、上記実装の場合はap-northeast-1(アジアパシフィック(東京))がデフォルトとなる。

19.7.1 リソースのマルチリージョン定義

P.235
リソースをマルチリージョンで定義するには、リスト19.14のように実装する。

リスト19.14:リソースのマルチリージョン定義

/myapp/terraform/main.tf
# Multiple Provider (Resource)
resource "aws_vpc" "virginia" {
  provider   = aws.virginia
  cidr_block = "192.168.0.0/16"
}

resource "aws_vpc" "tokyo" {
  cidr_block = "192.168.0.0/16"
}

output "virginia_vpc" {
  value = aws_vpc.virginia.arn
}

output "tokyo_vpc" {
  value = aws_vpc.tokyo.arn
}

ソース:example-pragmatic-terraform/14.tf at main · tmknom/example-pragmatic-terraform
resource "aws_vpc" "tokyo"の方にはproviderの定義がないが、こちらはデフォルトとして設定しているap-northeast-1(アジアパシフィック(東京))に自動的に作成されるため不要なのだろう。たぶん...。

19.7.2 モジュールのマルチリージョン定義

P.237
今度はvpcモジュールをリスト19.15のように実装する。

まず、terraformディレクトリにいる状態で、vpcモジュールのファイルを置くためのvpcディレクトリを作成する。

ターミナル
mkdir vpc

作成したvpcディレクトリに移動。

ターミナル
cd vpc

vpcのモジュール化を定義するファイルを作成する。ファイル名はvpc.tfとする。

ターミナル
touch vpc.tf

作成したvpc.tfファイルをエディターで開き、以下のように編集する。

リスト19.15:マルチリージョン用のvpcモジュールの定義

/myapp/terraform/vpc/vpc.tf
resource "aws_vpc" "default" {
  cidr_block = "192.168.0.0/16"
}

output "vpc_arn" {
  value = aws_vpc.default.arn
}

ソース:example-pragmatic-terraform/15.tf at main · tmknom/example-pragmatic-terraform

vpc.tfファイルの編集が済んだらターミナルでterraformディレクトリに戻り、

ターミナル
cd ../

以下のコードをmain.tfファイルに実装し、モジュールをマルチリージョンで定義する。

リスト19.16:モジュールのマルチリージョン定義

/myapp/terraform/main.tf
# Multiple Provider (VPC)
module "virginia" {
  source = "./vpc/vpc"

  providers = {
    aws = aws.virginia
  }
}

module "tokyo" {
  source = "./vpc"
}

output "module_virginia_vpc" {
  value = module.virginia.vpc_arn
}

output "module_tokyo_vpc" {
  value = module.tokyo.vpc_arn
}

ソース:example-pragmatic-terraform/16.tf at main · tmknom/example-pragmatic-terraform
module "tokyo"の方にprovidersの定義がないが、これもまたap-northeast-1(アジアパシフィック(東京))がデフォルトとして設定されているおかげで、東京リージョンの方には自動的にモジュールが作成される。

19.8 Dynamic blocks

P.238
Dynamic blocksを使うと動的にブロック要素を生成することができる。

sakataku1991sakataku1991

Terraformのコマンドまとめ

【一番最初に使うコマンド】

作業用ディレクトリを初期化する|terraform initコマンド

ターミナル
terraform init

【よく使うコマンド】

Terraformのリントを実行する|tflintコマンド

ターミナル
tflint

Terraformの実行計画を出力する|terraform planコマンド

ターミナル
terraform plan

terraform planコマンドによって、「今の設定でterraform applyを実行したらこうなりますよ」というのが確認できる

TerraformでAWSにリソースを作成する|terraform applyコマンド

ターミナル
terraform apply

Terraformで作成したAWSのリソースを削除する|terraform destroyコマンド

ターミナル
terraform destroy

Terraformコンソールを実行する|terraform consoleコマンド

ターミナル
terraform console