1. はじめに
これまでWebアプリケーション開発を中心に経験してきましたが、最近、実務でAWSのDR環境を構築する機会がありました。
構築手順書をもとにAWSコンソールから各リソースを作成しながら、AWSのサービスやネットワークについて理解を深めました。
今回は、コンソールで構築した環境をコードとして再現できるよう、Terraformで構築してみます。
この記事では、実務で経験した構成を簡略化したサンプル環境を使い、コンソールで行っていた構築をTerraformではどう表現するのかをまとめます。
(本記事で使用するTerraformのコードは、GitHubで公開しています。)
2. 今回構築する環境
今回は、実務で構築した環境を一部簡略化し、以下のAWSリソースをTerraformで構築します。
| AWSリソース | 構成 |
|---|---|
| VPC | 1 |
| Subnet | Public × 1 / Private × 2(2AZ) |
| Internet Gateway | 1 |
| Route Table | Public用 × 1 / Private用 × 1 |
| Security Group | Bastion / ALB / Web / DB |
| EC2 | Bastion × 1 / Web × 2 |
| ALB | Internal ALB × 1 |
| RDS | PostgreSQL × 1 |
実務で構築した環境には、このほかにも複数のAWSサービスが含まれていましたが、今回はネットワーク・EC2・ALB・RDSを中心とした基本構成に絞っています。
また、NAT Gatewayはコストを抑えるため、今回の検証環境では構築しません。
3. Terraformの基本と環境準備
Terraformは、インフラの構成をコードで定義・管理するためのIaC(Infrastructure as Code)ツールです。
今回はAWS Providerを利用してAWSリソースを構築します。
Terraformの基本
Terraformでは、主に以下を使って構成を記述します。
| 要素 | 用途 |
|---|---|
resource |
VPCやEC2など、Terraformで作成・管理するリソース |
data |
AMIなど、既存の情報を取得して利用するもの |
variable |
IPアドレスやDBのユーザー名など、外部から変更できる値 |
例えば、VPCの場合、以下のようにresourceとして定義します。
resource "aws_vpc" "main" {
cidr_block = "10.1.0.0/16"
tags = {
Name = "sample-vpc"
}
}
# "aws_vpc" が作成するリソースの種類。
# "main" がTerraform内で使用するリソースの識別子。
他のリソースからは、以下のように参照ができます。
vpc_id = aws_vpc.main.id
AWSコンソールでは作成済みのVPCを画面上で選択しますが、Terraformでは、リソース同士をコード上で参照して関連づけることになります。
環境準備
①Terraformをインストール
今回はHomebrewを使用してTerraformをインストールします。
# HashiCorp公式のHomebrewリポジトリを追加
$ brew tap hashicorp/tap
# Terraformをインストール
$ brew install hashicorp/tap/terraform
# Terraformのバージョン確認
$ terraform -version
②AWS CLIをインストール
# AWS CLIをインストール
$ brew install awscli
# AWS CLIのバージョン確認
$ aws --version
③AWS CLIの認証設定
# AWS CLIの認証情報を設定
$ aws configure
AWS Access Key ID [None]: <Access Key ID>
AWS Secret Access Key [None]: <Secret Access Key>
Default region name [None]: ap-northeast-1
Default output format [None]: json
設定後、AWSに接続できることを確認します。
# AWSアカウント情報を取得できることを確認
$ aws sts get-caller-identity
④Terraformプロジェクトを作成
# プロジェクト用のディレクトリを作成
$ mkdir dr-iac
# 作成したディレクトリに移動
$ cd dr-iac
まず、versions.tf を作成します。
versions.tf には、使用するTerraform本体とProviderのバージョンを定義します。環境によって異なるバージョンが使われることを防ぎ、同じ条件で実行しやすくするための設定です。
terraform {
required_version = ">= 1.13.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 6.0"
}
}
}
今回はTerraform 1.13.0 以上、AWS Provider 6.x を使用します。
⑤AWSProviderを設定
次に、provider.tf を作成します。
Providerは、TerraformがAWSなどの外部サービスを操作するためのプラグインです。
provider.tf では、そのProviderを利用する際のリージョンなどを設定します。
provider "aws" {
region = "ap-northeast-1"
}
今回は東京リージョン(ap-northeast-1)を使用します。
⑥Terraformを初期化
作成したTerraformプロジェクトで初期化を行います。
# Terraformを初期化
$ terraform init
terraform init を実行すると、設定したAWS Providerなど、Terraformの実行に必要なものがダウンロードされます。
以上で、環境準備は終了です。
4. ネットワークをTerraformで構築
ここから、実際にAWSリソースをTerraformで構築していきます。
Terraformコード全体はGitHubリポジトリに掲載しているため、本記事では各リソースを構築する際のポイントと、説明に必要なコードのみ抜粋して紹介します。
4-1. VPC
まず、main.tf にVPCを定義します。
resource "aws_vpc" "main" {
cidr_block = "10.1.0.0/16"
tags = {
Name = "sample-vpc"
}
}
ここで、Terraformでリソースを構築する際の基本的な流れを確認します。
# ①コードを整形する
$ terraform fmt
# ②構文をチェックする
$ terraform validate
# ③変更内容を確認する
$ terraform plan
# ④AWS環境へ反映する
$ terraform apply
上記のように、Terraformではコードを記述した後、整形 → 構文チェック → 変更内容の確認 → AWS環境への反映という流れでリソースを構築します。
以降のリソースについても、基本的に同じ流れで構築していきます。
4-2. サブネット・インターネットゲートウェイ・ルートテーブル
VPC作成後、サブネット・インターネットゲートウェイ・ルートテーブルを作成します。
■ サブネット
パブリックサブネットを1つ、プライベートサブネットを2つ作成します。
プライベートサブネットは、異なるAZ(ap-northeast-1a / ap-northeast-1c)に配置しました。
resource "aws_subnet" "private_1a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.1.11.0/24"
availability_zone = "ap-northeast-1a"
tags = {
Name = "sample-private-1a"
}
}
他のサブネットも、CIDRブロックやAZを変更して同様に定義します。
■ インターネットゲートウェイ
パブリックサブネットからインターネットへ通信できるように、インターネットゲートウェイを作成し、VPCに関連づけます。
resource "aws_internet_gateway" "main" {
vpc_id = aws_vpc.main.id
tags = {
Name = "sample-igw"
}
}
■ ルートテーブル
パブリック用・プライベート用のルートテーブルをそれぞれ作成します。
ルートテーブルの設定では、以下の3つを定義しました。
- ルートテーブル本体
- ルート
- ルートテーブルとサブネットの関連付け
パブリック用ルートテーブルには、インターネットゲートウェイへのルートを設定します。
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.main.id
}
また、作成したルートテーブルとサブネットを aws_route_table_association で関連づけます。
resource "aws_route_table_association" "public_1a" {
subnet_id = aws_subnet.public_1a.id
route_table_id = aws_route_table.public.id
}
Terraformではルートテーブル本体・ルート・サブネットとの関連付けをそれぞれ明示的に定義する必要があります。
なお、今回はNAT Gatewayを構築しないため、プライベート用ルートテーブルにはインターネット向けのルートを設定していません。
4-3. セキュリティグループ
EC2・ALB・RDS間の通信を制御するため、Bastion・ALB・Webサーバ・DB用のセキュリティグループを作成します。
aws_security_group でセキュリティグループ本体を作成し、通信ルールを設定します。
resource "aws_security_group" "web" {
name = "sample-web-sg"
vpc_id = aws_vpc.main.id
tags = {
Name = "sample-web-sg"
}
}
通信元にはCIDRだけでなく、他のセキュリティグループも指定できます。
例えばWebサーバでは、ALB用セキュリティグループからのHTTP通信を許可します。
resource "aws_vpc_security_group_ingress_rule" "web_http" {
security_group_id = aws_security_group.web.id
referenced_security_group_id = aws_security_group.alb.id
from_port = 80
to_port = 80
ip_protocol = "tcp"
}
同様に、DBではWebサーバからのPostgreSQL(5432)への通信を許可しています。
なお、BastionへのSSH接続元IPアドレスは variable とし、terraform.tfvars から設定しています。
まとめ
今回は、AWSコンソールで構築した環境をもとに、Terraformの基本的な使い方を確認しながら、VPC・サブネット・インターネットゲートウェイ・ルートテーブル・セキュリティグループまでを構築しました。
コンソールで行っていた設定をTerraformで書き直すことで、AWSの構成をコードとしてどのように表現するのかを理解することができました。
次の記事では、今回構築したネットワーク上にEC2・ALB・RDSを構築していきます。