作成した全てのコード
githubリポジトリ:"https://github.com/shu130/terraform-study27"
ディレクトリ
ルートの main.tf
で各モジュール(nw
、ecs
、alb
、cloudwatch
)を呼び出す
.
├── provider.tf
├── main.tf #=> モジュール呼び出し
├── variables.tf #=> モジュール呼び出しで使用する変数の定義
├── outputs.tf #=> モジュール側アウトプット(ID、ARN、DNS名など)を参照する役割
├── terraform.tfvars #=> 変数の具体値(git管理外)
├── modules
│ ├── nw
│ │ └── vpc.tf
│ │ └── igw.tf
│ │ └── natgw.tf
│ │ └── private_subnets.tf
│ │ └── public_subnets.tf
│ │ └── variables.tf
│ │ └── outputs.tf #=> モジュールを呼び出すルート側に参照させる役割
│ ├── ecs
│ │ └── main.tf
│ │ └── iam.tf
│ │ └── sg.tf
│ │ └── variables.tf
│ │ └── outputs.tf #=> モジュールを呼び出すルート側に参照させる役割
│ ├── lb
│ │ └── main.tf
│ │ └── sg.tf
│ │ └── variables.tf
│ │ └── outputs.tf #=> モジュールを呼び出すルート側に参照させる役割
│ └── cloudwatch
│ └── main.tf
│ └── variables.tf
│ └── outputs.tf
構成図
プロバイダなど
- AWSプロバイダ/Terraformバージョン
- プロファイル
- リージョン
- デフォルトタグ
# ./provider.tf
terraform {
required_version = ">= 1.6.2, < 2.0"
required_providers {
aws = {
source = "hashicorp/aws"
version = ">= 5.4.0"
}
}
}
provider "aws" {
profile = var.profile
region = var.region
default_tags {
tags = {
"ManagedBy" = "Terraform"
"Project" = var.app_name
}
}
}
変数定義:
# variables.tf
#-------------------------------
# provider, etc
variable "profile" {
type = string
default = "default"
}
variable "region" {
type = string
}
variable "app_name" {
type = string
}
変数の具体値:
# ./terraform.tfvars
#-------------------------------
# provider, etc
profile = "example-user"
region = "us-west-2"
app_name = "nginx-app"
1. VPC
NWモジュール側で、VPCのみ作成:
# ./modules/nw/vpc.tf
resource "aws_vpc" "this" {
cidr_block = var.vpc.cidr
tags = {
Name = "${var.app_name}-vpc"
}
}
NWモジュール側で変数定義:
# ./modules/nw/variables.tf
variable "vpc" {
type = object({
cidr = string
})
}
variable "app_name" {
type = string
}
NWモジュール側でアウトプット定義:
# ./modules/nw/outputs.tf
output "vpc_id" {
value = aws_vpc.this.id
}
ルート側で呼び出し:
# main.tf
module "network" {
source = "./modules/nw"
app_name = var.app_name
vpc = var.vpc
}
ルート側で変数定義:
#-------------------------------
# nw
variable "vpc" {
type = object({
cidr = string
})
}
ルート側で変数の具体値:
# ./terraform.tfvars
#-------------------------------
# provider, etc
(省略)
#-------------------------------
# nw
vpc = {
cidr = "10.0.0.0/16"
}
2. private/publicサブネット
NWモジュール側でcount関数
を使い、まずはサブネットのみ作成
NATGW, IGWを作成したあとにルートテーブルを追加する
# ./modules/nw/private_subnets.tf
resource "aws_subnet" "privates" {
count = length(var.private_subnets)
vpc_id = aws_vpc.this.id
cidr_block = var.private_subnets[count.index].private_subnet_cidr
availability_zone = var.private_subnets[count.index].availability_zone
tags = {
Name = "${var.app_name}-private-${count.index}"
}
}
# ./modules/nw/public_subnets.tf
※プライベートサブネットと同様
NATゲートウェイ
NWモジュール側でNATGWと固定IP作成:
# ./modules/nw/natgw.tf
resource "aws_nat_gateway" "natgws" {
count = length(var.public_subnets)
allocation_id = aws_eip.nat_eip[count.index].id #=>EIPを割り当て
subnet_id = aws_subnet.publics[count.index].id #=>パブリックサブネットに配置
tags = {
Name = "${var.app_name}-ngw-${count.index}"
}
}
resource "aws_eip" "nat_eip" {
count = length(var.public_subnets)
domain = "vpc" #=> EIPをVPC内に割り当てる指定
tags = {
Name = "${var.app_name}-nat_eip-${count.index}"
}
}
インターネットGW
NWモジュール側でIGW作成:
# ./modules/nw/igw.tf
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.this.id
tags = {
Name = "${var.app_name}-igw"
}
}
ルートテーブル
NWモジュール側のプライベートサブネットにNATGW宛ルート追加(#=>
):
# ./modules/nw/private_subnets.tf
resource "aws_subnet" "privates" {
(上記)
}
# サブネット毎にルートテーブル作成
resource "aws_route_table" "private_rt" {
count = length(var.private_subnets)
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
nat_gateway_id = aws_nat_gateway.natgws[count.index].id #=>NATGW宛ルート
}
tags = {
Name = "${var.app_name}-private_rt-${count.index}"
}
}
# ルートテーブルとサブネットの関連付け
resource "aws_route_table_association" "private_asso" {
count = length(var.private_subnets)
subnet_id = aws_subnet.privates[count.index].id
route_table_id = aws_route_table.private_rt[count.index].id
}
NWモジュール側のパブリックサブネットにIGW宛ルート追加(#=>
):
# ./modules/nw/public_subnet.tf
resource "aws_subnet" "publics" {
(上記)
}
# サブネット毎にルートテーブル作成
resource "aws_route_table" "public_rt" {
count = length(var.public_subnets)
vpc_id = aws_vpc.this.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id #=>IGW宛
}
tags = {
Name = "${var.app_name}-public_rt-${count.index}"
}
}
# ルートテーブルとサブネットの関連付け
resource "aws_route_table_association" "public_asso" {
count = length(var.public_subnets)
subnet_id = aws_subnet.publics[count.index].id
route_table_id = aws_route_table.public_rt[count.index].id
}
NWモジュール側の変数定義:
NWモジュール側で変数定義を追記:
# ./modules/nw/variables.tf
(省略)
variable "private_subnets" {
type = list(object({
private_subnet_cidr = string
availability_zone = string
}))
}
variable "public_subnets" {
type = list(object({
public_subnet_cidr = string
availability_zone = string
}))
}
アウトプット
NWモジュール側でアウトプットし、メイン側のアウトプット定義で使う
メイン側のアウトプットでは以下のように定義する:
module.nw.vpc_id
module.nw.private_subnets_ids
module.nw.public_subnets_ids
# ./modules/nw/outputs.tf
output "vpc_id" {
value = aws_vpc.this.id
}
output "private_subnets_ids" {
value = aws_subnet.privates[*].id
}
output "private_route_table_ids" {
value = aws_route_table.private_rt[*].id
}
output "public_subnets_ids" {
value = aws_subnet.publics[*].id
}
output "public_route_table_ids" {
value = aws_route_table.public_rt[*].id
}
output "internet_gateway_id" {
value = aws_internet_gateway.igw.id
}
output "nat_gateway_id" {
value = aws_nat_gateway.natgws[*].id
}
ルート側のmain(main.tf
)での呼び出し、変数定義、変数の具体値、アウトプット
呼び出し:
# ./main.tf
module "nw" {
source = "./modules/nw"
app_name = var.app_name
vpc = var.vpc
private_subnets = var.private_subnets
public_subnets = var.public_subnets
}
変数定義:
# ./variables.tf
#-------------------------------
# provider, etc
(上記)
#-------------------------------
# nw
variable "vpc" {
type = object({
cidr = string
})
}
variable "private_subnets" {
type = list(object({
private_subnet_cidr = string
availability_zone = string
}))
}
variable "public_subnets" {
type = list(object({
public_subnet_cidr = string
availability_zone = string
}))
}
変数の具体値:
# ./terraform.tfvars
#-------------------------------
# provider, etc
(上記)
#-------------------------------
# nw
vpc = {
cidr = "10.0.0.0/16"
}
private_subnets = [
{
private_subnet_cidr = "10.0.1.0/24"
availability_zone = "us-west-2a"
},
{
private_subnet_cidr = "10.0.2.0/24"
availability_zone = "us-west-2b"
}
]
public_subnets = [
{
public_subnet_cidr = "10.0.3.0/24"
availability_zone = "us-west-2a"
},
{
public_subnet_cidr = "10.0.4.0/24"
availability_zone = "us-west-2b"
}
]
アウトプット:
# ./outputs.tf
#------------------------
# nw
output "vpc_id" {
value = module.nw.vpc_id
}
output "public_subnets_ids" {
value = module.nw.public_subnets_ids
}
output "private_subnets_ids" {
value = module.nw.private_subnets_ids
}
3. LB/ECSモジュールのルート側での呼び出し
ルート側のmain(main.tf
)での呼び出し、変数定義、変数の具体値、アウトプット:
・呼び出し内容から、モジュール側で必要となるアウトプットを判断
・LBモジュール側outputでは以下の#=>
の2つが必要
・ECSモジュール側outputでは必要なものは無し(ECS以外のモジュールブロックで呼び出す箇所が無い)
# ./main.tf
module "nw" {
source = "./modules/nw"
app_name = var.app_name
vpc = var.vpc
private_subnets = var.private_subnets
public_subnets = var.public_subnets
}
module "lb" {
source = "./modules/lb"
app_name = var.app_name
vpc_id = module.nw.vpc_id
public_subnets = module.nw.public_subnets_ids
}
module "ecs" {
source = "./modules/ecs"
app_name = var.app_name
region = var.region
vpc_id = module.nw.vpc_id
subnet_ids = module.nw.private_subnets_ids
lb_security_group_id = module.lb.security_group_id #=>LBモジュール側outputを参照
lb_target_group_arn = module.lb.alb_target_group_arn #=>LBモジュール側outputを参照
}
module "cloudwatch" {
source = "./modules/cloudwatch"
app_name = var.app_name
}
4. LB
LBモジュール側のSecurityGroup:
LBセキュリティグループ作成
# ./modules/lb/sg.tf
resource "aws_security_group" "alb_sg" {
name = "${var.app_name}-alb-sg"
description = "ALB security group"
vpc_id = var.vpc_id
ingress {
protocol = "tcp"
from_port = 80
to_port = 80
cidr_blocks = ["0.0.0.0/0"]
description = "Allow HTTP from anywhere"
}
egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
description = "Allow all outbound traffic"
}
tags = {
Name = "${var.app_name}-alb-sg"
}
}
LBモジュール側のmain.tf
(alb):
・LB本体を作成、セキュリティグループ適用、パブリックサブネットに配置
・ターゲットグループ作成
・リスナー作成
# ./modules/lb/main.tf
resource "aws_lb" "this" {
name = "${var.app_name}-alb"
load_balancer_type = "application"
security_groups = [aws_security_group.alb_sg.id]
subnets = var.public_subnets
}
resource "aws_lb_target_group" "this" {
name = "${var.app_name}-target-group"
port = 80
protocol = "HTTP"
vpc_id = var.vpc_id
target_type = "ip"
health_check {
path = "/"
timeout = 5
healthy_threshold = 2
unhealthy_threshold = 2
interval = 30
}
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.this.arn
port = 80
protocol = "HTTP"
default_action {
type = "forward"
target_group_arn = aws_lb_target_group.this.arn
}
}
LBモジュール側の変数定義:
# ./modules/lb/variables.tf
variable "app_name" {
type = string
}
variable "vpc_id" {
type = string
}
variable "public_subnets" {
type = list(string)
}
LBモジュール側のアウトプット:
LBモジュール側output定義して、ルート側mainで以下のように呼び出す:
module.lb.security_group_id
module.lb.alb_target_group_arn
# ./modules/lb/outputs.tf
output "security_group_id" {
value = aws_security_group.alb_sg.id
}
output "alb_target_group_arn" {
value = aws_lb_target_group.this.arn
}
5. ECS
ECSモジュール側のIAM:
・ECSタスクにアタッチするタスク実行ロール
を作成し、ポリシーをアタッチ
・ポリシーは、cloudwath_logsにログを送信するための最小限のカスタムポリシー
# ./modules/ecs/iam.tf
resource "aws_iam_role" "ecs_task_execution_role" {
name = "${var.app_name}-ecs-execution-role"
assume_role_policy = jsonencode({
Version = "2012-10-17",
Statement = [{
Action = "sts:AssumeRole",
Effect = "Allow",
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
}]
})
}
resource "aws_iam_policy" "ecs_cloudwatch_logs_policy" {
name = "${var.app_name}-ecs-cloudwatch-logs-policy"
policy = jsonencode({
Version = "2012-10-17",
Statement = [
{
Effect = "Allow",
Action = [
"logs:CreateLogGroup",
"logs:CreateLogStream",
"logs:PutLogEvents"
],
Resource = "*"
}
]
})
}
resource "aws_iam_role_policy_attachment" "ecs_cloudwatch_logs_policy_attachment" {
role = aws_iam_role.ecs_task_execution_role.name
policy_arn = aws_iam_policy.ecs_cloudwatch_logs_policy.arn
}
ECSモジュール側のSecurityGroup:
# ./modules/ecs/sg.tf
resource "aws_security_group" "ecs_sg" {
name = "${var.app_name}-ecs-sg"
description = "ECS security group"
vpc_id = var.vpc_id
ingress {
protocol = "tcp"
from_port = 80
to_port = 80
security_groups = [var.lb_security_group_id] #=>LBモジュール側で作成したものを適用
description = "Allow HTTP from LB"
}
egress {
protocol = "-1"
from_port = 0
to_port = 0
cidr_blocks = ["0.0.0.0/0"]
description = "Allow all outbound traffic"
}
tags = {
Name = "${var.app_name}-ecs-sg"
}
}
ECSモジュール側のmain.tf
(ECS):
ecsクラスター、タスク定義、サービスを作成
# ./modules/ecs/main.tf
resource "aws_ecs_cluster" "this" {
name = "${var.app_name}-ecs-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
}
resource "aws_ecs_task_definition" "this" {
family = var.app_name
requires_compatibilities = ["FARGATE"]
network_mode = "awsvpc"
cpu = "256"
memory = "512"
execution_role_arn = aws_iam_role.ecs_task_execution_role.arn
container_definitions = jsonencode([
{
name = "nginx"
image = "nginx:latest"
essential = true
portMappings = [{
containerPort = 80
protocol = "tcp"
}]
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-group = "/ecs/${var.app_name}"
awslogs-region = var.region
awslogs-stream-prefix = "nginx"
}
}
}
])
}
resource "aws_ecs_service" "this" {
name = "${var.app_name}-service"
cluster = aws_ecs_cluster.this.id
task_definition = aws_ecs_task_definition.this.arn
desired_count = 2
launch_type = "FARGATE"
network_configuration {
subnets = var.subnet_ids
security_groups = [aws_security_group.ecs_sg.id]
}
load_balancer {
target_group_arn = var.lb_target_group_arn
container_name = "nginx"
container_port = 80
}
}
ECSモジュール側の変数定義:
# ./modules/ecs/variables.tf
variable "region" {
type = string
}
variable "app_name" {
type = string
}
variable "vpc_id" {
type = string
}
variable "subnet_ids" {
type = list(string)
}
variable "lb_security_group_id" {
type = string
}
variable "lb_target_group_arn" {
type = string
}
ECSモジュール側のアウトプット:
# ./modules/ecs/outputs.tf
※アウトプット必須のものは無し
6. CloudWatch(CW)
CWモジュール側のmain.tf
:
# ./modules/cloudwatch/main.tf
resource "aws_cloudwatch_log_group" "ecs_log_group" {
name = "/ecs/${var.app_name}"
retention_in_days = 7
}
CWモジュール側の変数定義:
# ./modules/cloudwatch/variables.tf
variable "app_name" {
type = string
}
CWモジュール側のアウトプット:
# ./modules/cloudwatch/outputs.tf
※アウトプット必須のものは無し
7. plan
, apply
が正常に完了したあとの確認
terraformコマンド:
terraforom state list
, terraform state show
NGINXのウェルカムページが表示されるか確認:
# LBのリストを出して、
$ terraform state list | grep lb
module.lb.aws_lb.this
module.lb.aws_lb_listener.http
module.lb.aws_lb_target_group.this
module.lb.aws_security_group.alb_sg
# DNS名を探して、
$ terraform state show module.lb.aws_lb.this | grep dns
dns_name = "nginx-app-alb-287260778.us-west-2.elb.amazonaws.com"
# アクセスすると、正常にウェルカムページが表示された。
$ curl http://nginx-app-alb-287260778.us-west-2.elb.amazonaws.com
その他、ECSやCloudWatchLogs側の確認はコンソール画面にて正常を確認できた。
今回はここまでにしたいと思います。