はじめに
RailsとNuxt3でtodoリストの作り方を
初めから丁寧に説明したいと思います。
使用pcはmacを想定しています。
完成した構成図は以下の通りです。
また、githubレポジトリはこちらです。
各シリーズは以下の通りです。
RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その1、Rails基本設定編
RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その2、Rails API編
RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その3、Nuxt.js編
RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その4、TerraformECS前編
RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その5、TerraformECS後編
RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その6、Blue/Greenデプロイ前編
RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その7、Blue/Greenデプロイ後編
bashscriptを作る
terraformでいつも実行するコマンドはbashscriptを作りまとめておきます。
#!/usr/bin/env bash
set -euo pipefail
function terraform_apply() {
local cwd="$1"
cd "${cwd}"
cd terraform
terraform init
terraform plan
terraform apply --auto-approve
}
function main() {
local cwd
cwd="$(cd "$(dirname "$0")/.." && pwd)"
terraform_apply "${cwd}"
}
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
main "$@"
fi
terraformを書く
tagはつけた方がいいです。
aws consoleで何のリソースかわからないから。
以下のサイトを参考に作ります。
main.tf
main.tfを読み込んで、terraformを実行します。
読み出すmoduleを書きます。
source以下は、moduleに渡す変数です。
terraform {
required_version = "=1.0.6"
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.0"
}
}
}
provider "aws" {
region = var.region
}
# IAM
module "iam" {
source = "./modules/iam"
app_name = var.app_name
}
# cloudwatch
module "cloudwatch" {
source = "./modules/cloudwatch"
app_name = var.app_name
web_app_name = var.web_app_name
api_app_name = var.api_app_name
}
# network
module "network" {
source = "./modules/network"
app_name = var.app_name
web_app_name = var.web_app_name
api_app_name = var.api_app_name
http_ports = var.http_ports
https_ports = var.https_ports
web_ports = var.web_ports
api_ports = var.api_ports
db_ports = var.db_ports
}
# s3
module "s3" {
source = "./modules/s3"
app_name = var.app_name
}
# elb
module "elb" {
source = "./modules/elb"
app_name = var.app_name
web_app_name = var.web_app_name
api_app_name = var.api_app_name
main_vpc_id = module.network.main_vpc_id
subnet_p1a_id = module.network.subnet_public_1a_id
subnet_p1c_id = module.network.subnet_public_1c_id
alb_sg_id = module.network.alb_sg_id
web_ports = var.web_ports
api_ports = var.api_ports
s3_bucket_id = module.s3.s3_bucket_id
}
# ECR
module "ecr" {
source = "./modules/ecr"
app_name = var.app_name
web_app_name = var.web_app_name
api_app_name = var.api_app_name
}
# Null Resource
module "after_ecr" {
source = "./modules/bash"
region = var.region
web_app_name = var.web_app_name
api_app_name = var.api_app_name
web_app_dir_name = var.web_app_dir_name
api_app_dir_name = var.api_app_dir_name
api_repository_url = module.ecr.api_repository_url
web_repository_url = module.ecr.web_repository_url
}
# rds
module "rds" {
source = "./modules/rds"
db_sbg_name = module.network.db_sbg_name
sg_rds_sg_id = module.network.sg_rds_sg_id
db_ports = var.db_ports
app_name = var.app_name
db_name = var.db_name
db_username = var.db_username
db_password = var.db_password
}
# ECS
module "ecs" {
source = "./modules/ecs"
app_name = var.app_name
web_app_name = var.web_app_name
api_app_name = var.api_app_name
apserver_sg_id = module.network.apserver_sg_id
subnet_p1a_id = module.network.subnet_public_1a_id
webserver_sg_id = module.network.webserver_sg_id
subnet_p1c_id = module.network.subnet_public_1c_id
ecs_main_role = module.iam.ecs_main_role
db_endpoint = module.rds.db_endpoint
db_name = var.db_name
db_username = var.db_username
db_password = var.db_password
api_alb_target_group_arn = module.elb.api_alb_target_group_arn
web_alb_target_group_arn = module.elb.web_alb_target_group_arn
web_ports = var.web_ports
api_ports = var.api_ports
http_arn = module.elb.http_arn
api_repository_url = module.ecr.api_repository_url
web_repository_url = module.ecr.web_repository_url
}
variables.tf
親元の変数です。間違えやすいport番号も登録します。
main.tfを通して、moduleに渡します。
variable "app_name" {
description = "application name"
type = string
default = "todolist"
}
variable "web_app_name" {
description = "webserver name"
type = string
default = "webserver"
}
variable "api_app_name" {
description = "apiserver name"
type = string
default = "apiserver"
}
variable "web_app_dir_name" {
description = "webserver directory name"
type = string
default = "webserver"
}
variable "api_app_dir_name" {
description = "apiserver directory name"
type = string
default = "apserver"
}
variable "region" {
description = "AWS region to create resources in"
type = string
default = "ap-northeast-1"
}
variable "http_ports" {
type = list(object({
internal = number
external = number
protocol = string
}))
default = [
{
internal = 80
external = 80
protocol = "tcp"
}
]
}
variable "https_ports" {
type = list(object({
internal = number
external = number
protocol = string
}))
default = [
{
internal = 443
external = 443
protocol = "tcp"
}
]
}
variable "web_ports" {
type = list(object({
internal = number
external = number
protocol = string
}))
default = [
{
internal = 3000
external = 3000
protocol = "tcp"
}
]
}
variable "api_ports" {
type = list(object({
internal = number
external = number
protocol = string
}))
default = [
{
internal = 8080
external = 8080
protocol = "tcp"
}
]
}
variable "db_ports" {
type = list(object({
internal = number
external = number
protocol = string
}))
default = [
{
internal = 3306
external = 3306
protocol = "tcp"
}
]
}
variable "db_name" {
type = string
description = "database name"
default = "todoproject"
}
variable "db_username" {
type = string
description = "database user name"
default = "rubyruby"
}
variable "db_password" {
type = string
description = "database password"
default = "rubyruby"
}
moduleを理解する
以下はecrのmodlueです。
.
├── ecr.tf
├── outputs.tf
└── variables.tf
outputs.tfは他のmodlueに変数を渡すためにあります。
output "api_repository_url" {
description = "The URL of the api image repository."
value = aws_ecr_repository.api_repository.repository_url
}
variables.tfは他のmodlueから変数を受けるためにあります。
variable "app_name" {
type = string
}
iam
ecsに他のawsサービスと連携するためにiamを許可します。
resource "aws_iam_role" "main_role" {
name = "${var.app_name}_role"
assume_role_policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Sid = ""
Action = "sts:AssumeRole"
Effect = "Allow"
Principal = {
Service = "ecs-tasks.amazonaws.com"
}
},
]
})
tags = {
Name = "${var.app_name}-app-iam-role"
}
}
以下のようにpolicyを自作する必要はないです。
すでにあるpolicyのarnをコピーしとけばokです。
今回は自作します。
resource "aws_iam_policy" "ecr_policy" {
name = "${var.app_name}_ecr_policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"ecr:*"
]
Effect = "Allow"
Resource = "*"
},
]
})
}
resource "aws_iam_policy" "cloudwatch_policy" {
name = "${var.app_name}_cloudwatch_policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"logs:*"
]
Effect = "Allow"
Resource = "arn:aws:logs:*:*:*"
},
]
})
}
resource "aws_iam_policy" "ecs_task_role_policy" {
name = "${var.app_name}_ecs_task_role_policy"
policy = jsonencode({
Version = "2012-10-17"
Statement = [
{
Action = [
"ssmmessages:CreateControlChannel",
"ssmmessages:CreateDataChannel",
"ssmmessages:OpenControlChannel",
"ssmmessages:OpenDataChannel"
]
Effect = "Allow"
Resource = "*"
},
]
})
}
policyをアタッチします。
resource "aws_iam_policy_attachment" "ecr_attach" {
name = "${var.app_name}-ecr_attach"
roles = ["${aws_iam_role.main_role.name}"]
policy_arn = aws_iam_policy.ecr_policy.arn
}
resource "aws_iam_policy_attachment" "cloudwatch_attach" {
name = "${var.app_name}_cloudwatch_attach"
roles = ["${aws_iam_role.main_role.name}"]
policy_arn = aws_iam_policy.cloudwatch_policy.arn
}
resource "aws_iam_policy_attachment" "ecs_task_role_attach" {
name = "${var.app_name}_ecs_task_role_attach"
roles = ["${aws_iam_role.main_role.name}"]
policy_arn = aws_iam_policy.ecs_task_role_policy.arn
}
CloudWatch
CloudWatchはecsのエラーログを吐き出すために必要です。
# CloudWatchLog for Fargate api
resource "aws_cloudwatch_log_group" "app" {
name = "/fargate/${var.app_name}/dev/${var.api_app_name}"
retention_in_days = 1
tags = {
Name = "${var.app_name}-${var.api_app_name}-cloudwatch-log"
}
}
# CloudWatchLog for Fargate web
resource "aws_cloudwatch_log_group" "web" {
name = "/fargate/${var.app_name}/dev/${var.web_app_name}"
retention_in_days = 1
tags = {
Name = "${var.app_name}-${var.api_app_name}-cloudwatch-log"
}
}
network
resource "aws_vpc" "main" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
instance_tenancy = "default"
tags = {
Name = "${var.app_name}-main-vpc"
}
}
resource "aws_internet_gateway" "igw" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.app_name}-igw"
}
}
resource "aws_subnet" "public_subnet_1a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.11.0/24"
availability_zone = "ap-northeast-1a"
map_public_ip_on_launch = true
tags = {
Name = "${var.app_name}-public-1a"
}
}
resource "aws_subnet" "public_subnet_1c" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.12.0/24"
availability_zone = "ap-northeast-1c"
map_public_ip_on_launch = true
tags = {
Name = "${var.app_name}-public-1c"
}
}
resource "aws_subnet" "private_subnet_1a" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.21.0/24"
availability_zone = "ap-northeast-1a"
map_public_ip_on_launch = true
tags = {
Name = "${var.app_name}-private-1a"
}
}
resource "aws_subnet" "private_subnet_1c" {
vpc_id = aws_vpc.main.id
cidr_block = "10.0.22.0/24"
availability_zone = "ap-northeast-1c"
map_public_ip_on_launch = true
tags = {
Name = "${var.app_name}-private-1c"
}
}
resource "aws_db_subnet_group" "db-sg" {
name = "db-sg"
subnet_ids = [aws_subnet.private_subnet_1a.id, aws_subnet.private_subnet_1c.id]
}
resource "aws_route_table" "public_rt" {
vpc_id = aws_vpc.main.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.igw.id
}
tags = {
Name = "${var.app_name}-public-rt"
}
}
resource "aws_route_table_association" "public_rt_1a" {
route_table_id = aws_route_table.public_rt.id
subnet_id = aws_subnet.public_subnet_1a.id
}
resource "aws_route_table_association" "public_rt_1c" {
route_table_id = aws_route_table.public_rt.id
subnet_id = aws_subnet.public_subnet_1c.id
}
resource "aws_route_table" "private_rt" {
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.app_name}-private-rt"
}
}
resource "aws_route_table_association" "private_rt_1a" {
route_table_id = aws_route_table.private_rt.id
subnet_id = aws_subnet.private_subnet_1a.id
}
resource "aws_route_table_association" "private_rt_1c" {
route_table_id = aws_route_table.private_rt.id
subnet_id = aws_subnet.private_subnet_1c.id
}
# SecurityGroup for Fargate alb
resource "aws_security_group" "alb_sg" {
name = "${var.app_name}-alb-sg"
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.app_name}-alb-sg"
}
}
# SecurityGroup for Fargate api
resource "aws_security_group" "apserver_sg" {
name = "${var.api_app_name}-sg"
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.app_name}-${var.api_app_name}-sg"
}
}
# SecurityGroup for Fargate web
resource "aws_security_group" "webserver_sg" {
name = "${var.web_app_name}-sg"
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.app_name}-${var.web_app_name}-sg"
}
}
# SecurityGroup for RDS
resource "aws_security_group" "rds_sg" {
name = "rds-sg"
vpc_id = aws_vpc.main.id
tags = {
Name = "${var.app_name}-rds-sg"
}
}
# SecurityGroupRules for ALB
resource "aws_security_group_rule" "alb_in_http" {
type = "ingress"
from_port = var.http_ports[0].internal
to_port = var.http_ports[0].external
protocol = var.http_ports[0].protocol
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.alb_sg.id
}
resource "aws_security_group_rule" "alb_out_full" {
type = "egress"
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.alb_sg.id
}
# SecurityGroupRules for Fargate api
resource "aws_security_group_rule" "apserver_in_alb" {
type = "ingress"
from_port = var.api_ports[0].internal
to_port = var.api_ports[0].external
protocol = var.api_ports[0].protocol
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.apserver_sg.id
}
resource "aws_security_group_rule" "apserver_out_db" {
type = "egress"
from_port = var.db_ports[0].internal
to_port = var.db_ports[0].external
protocol = var.db_ports[0].protocol
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.apserver_sg.id
}
resource "aws_security_group_rule" "apserver_out_http" {
type = "egress"
from_port = var.http_ports[0].internal
to_port = var.http_ports[0].external
protocol = var.http_ports[0].protocol
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.apserver_sg.id
}
resource "aws_security_group_rule" "apserver_out_https" {
type = "egress"
from_port = var.https_ports[0].internal
to_port = var.https_ports[0].external
protocol = var.https_ports[0].protocol
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.apserver_sg.id
}
# SecurityGroupRules for Fargate web
resource "aws_security_group_rule" "webserver_in_alb" {
type = "ingress"
from_port = var.web_ports[0].internal
to_port = var.web_ports[0].external
protocol = var.web_ports[0].protocol
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.webserver_sg.id
}
resource "aws_security_group_rule" "webserver_out_http" {
type = "egress"
from_port = var.http_ports[0].internal
to_port = var.http_ports[0].external
protocol = var.http_ports[0].protocol
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.webserver_sg.id
}
resource "aws_security_group_rule" "webserver_out_https" {
type = "egress"
from_port = var.https_ports[0].internal
to_port = var.https_ports[0].external
protocol = var.https_ports[0].protocol
cidr_blocks = ["0.0.0.0/0"]
security_group_id = aws_security_group.webserver_sg.id
}
# SecurityGroupRules for db
resource "aws_security_group_rule" "rds_in_api" {
type = "ingress"
from_port = var.db_ports[0].internal
to_port = var.db_ports[0].external
protocol = var.db_ports[0].protocol
source_security_group_id = aws_security_group.apserver_sg.id
security_group_id = aws_security_group.rds_sg.id
}
s3
elbのacesslogをs3に貯めます。
resource "aws_s3_bucket" "alb_access_log" {
bucket = "access-log-${random_string.s3_unique_key.result}"
force_destroy = true
tags = {
Name = "${var.app_name}-s3-access-log"
Environment = "Dev"
}
}
resource "random_string" "s3_unique_key" {
length = 6
upper = false
lower = true
numeric = true
special = false
}
resource "aws_s3_bucket_versioning" "versioning" {
bucket = aws_s3_bucket.alb_access_log.id
versioning_configuration {
status = "Enabled"
}
}
locals {
alb_root_account_id = "582318560864"
# ロードバランサーのリージョンに対応するAWSアカウントID
# https://docs.aws.amazon.com/elasticloadbalancing/latest/classic/enable-access-logs.html#attach-bucket-policy
}
resource "aws_s3_bucket_policy" "allow_access" {
bucket = aws_s3_bucket.alb_access_log.id
policy = data.aws_iam_policy_document.allow_access.json
}
data "aws_iam_policy_document" "allow_access" {
statement {
effect = "Allow"
principals {
type = "AWS"
identifiers = ["${local.alb_root_account_id}"]
}
actions = ["*"]
resources = [
aws_s3_bucket.alb_access_log.arn,
"${aws_s3_bucket.alb_access_log.arn}/*",
]
}
}