はじめに
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デプロイ後編
elb
aws_lb_target_groupが複数あるのは、blue/greenデプロイ時、切り替える用に二つづつ作っています。
resource "aws_lb" "alb" {
name = "${var.app_name}-alb"
internal = false
enable_deletion_protection = false
load_balancer_type = "application"
security_groups = ["${var.alb_sg_id}"]
subnets = [
"${var.subnet_p1a_id}",
"${var.subnet_p1c_id}"
]
access_logs {
bucket = var.s3_bucket_id
prefix = "access-log"
enabled = true
}
}
resource "aws_lb_listener" "http" {
load_balancer_arn = aws_lb.alb.arn
port = "80"
protocol = "HTTP"
default_action {
target_group_arn = aws_lb_target_group.web_alb_target_group.arn
type = "forward"
}
}
# LbTargetGroup for api
resource "aws_lb_target_group" "api_blue" {
name = "${var.api_app_name}-blue-tg"
port = var.api_ports[0].internal
protocol = "HTTP"
target_type = "ip"
vpc_id = var.main_vpc_id
health_check {
interval = 300
path = "/"
port = var.api_ports[0].internal
protocol = "HTTP"
timeout = 120
unhealthy_threshold = 10
matcher = "200-299"
}
tags = {
Name = "${var.app_name}-${var.api_app_name}-blue-tg"
}
}
resource "aws_lb_target_group" "api_green" {
name = "${var.api_app_name}-green-tg"
port = var.api_ports[0].internal
protocol = "HTTP"
target_type = "ip"
vpc_id = var.main_vpc_id
health_check {
interval = 300
path = "/"
port = var.api_ports[0].internal
protocol = "HTTP"
timeout = 120
unhealthy_threshold = 10
matcher = "200-299"
}
tags = {
Name = "${var.app_name}-${var.api_app_name}-green-tg"
}
}
# LbTargetGroup for web
resource "aws_lb_target_group" "web_blue" {
name = "${var.web_app_name}-blue-tg"
port = var.web_ports[0].internal
protocol = "HTTP"
target_type = "ip"
vpc_id = var.main_vpc_id
health_check {
interval = 300
path = "/index.html"
port = var.web_ports[0].internal
protocol = "HTTP"
timeout = 120
unhealthy_threshold = 10
matcher = "200-299"
}
tags = {
Name = "${var.app_name}-${var.web_app_name}-blue-tg"
}
}
resource "aws_lb_target_group" "web_green" {
name = "${var.web_app_name}-green-tg"
port = var.web_ports[0].internal
protocol = "HTTP"
target_type = "ip"
vpc_id = var.main_vpc_id
health_check {
interval = 300
path = "/index.html"
port = var.web_ports[0].internal
protocol = "HTTP"
timeout = 120
unhealthy_threshold = 10
matcher = "200-299"
}
tags = {
Name = "${var.app_name}-${var.web_app_name}-green-tg"
}
}
ecr
ecsに入れるimageを保存しておく場所です。
# EcrRepository for api
resource "aws_ecr_repository" "api_repository" {
name = var.api_app_name
force_delete = true
image_scanning_configuration {
scan_on_push = true
}
tags = {
Name = "${var.app_name}-${var.api_app_name}-repository"
}
}
# EcrRepository for web
resource "aws_ecr_repository" "web_repository" {
name = var.web_app_name
force_delete = true
image_scanning_configuration {
scan_on_push = true
}
tags = {
Name = "${var.app_name}-${var.web_app_name}-repository"
}
}
Null Resource
terraform apllyした時に一緒にbashscriptを実行します。
内容は、ecrにimageをpushしたことです。
resource "null_resource" "default" {
provisioner "local-exec" {
command = "aws ecr get-login-password --region ${var.region} | docker login --username AWS --password-stdin ${data.aws_caller_identity.self.account_id}.dkr.ecr.ap-northeast-1.amazonaws.com"
}
provisioner "local-exec" {
command = "docker build -t ${var.api_repository_url}:latest --file ../${var.api_app_dir_name}/Dockerfile ../${var.api_app_dir_name}/"
}
provisioner "local-exec" {
command = "docker build -t ${var.web_repository_url}:latest --file ../${var.web_app_dir_name}/Dockerfile ../${var.web_app_dir_name}/"
}
provisioner "local-exec" {
command = "docker push ${var.api_repository_url}:latest"
}
provisioner "local-exec" {
command = "docker push ${var.web_repository_url}:latest"
}
}
rds
resource "aws_db_instance" "db-setting" {
engine = "mysql"
engine_version = "8.0.31"
identifier = "db-mysql"
db_name = var.db_name
username = var.db_username
password = var.db_password
instance_class = "db.t2.micro"
allocated_storage = 20
max_allocated_storage = 50
storage_type = "gp2"
storage_encrypted = false
multi_az = false
availability_zone = "ap-northeast-1a"
db_subnet_group_name = var.db_sbg_name
vpc_security_group_ids = ["${var.sg_rds_sg_id}"]
publicly_accessible = false
port = var.db_ports[0].external
parameter_group_name = aws_db_parameter_group.db-pg.name
option_group_name = aws_db_option_group.main_optiongroup.name
backup_window = "04:00-05:00"
backup_retention_period = 7
maintenance_window = "Mon:05:00-Mon:08:00"
auto_minor_version_upgrade = false
deletion_protection = false
skip_final_snapshot = true
apply_immediately = true
tags = {
Name = "${var.app_name}-db-standalone"
}
}
resource "aws_db_parameter_group" "db-pg" {
name = "${var.app_name}-db-pg"
family = "mysql8.0"
parameter {
name = "character_set_database"
value = "utf8mb4"
}
parameter {
name = "character_set_server"
value = "utf8mb4"
}
}
resource "aws_db_option_group" "main_optiongroup" {
name = "${var.app_name}-optiongroup"
engine_name = "mysql"
major_engine_version = "8.0"
}
ecs
dockerコンテナをデブロイできます。
resource "aws_ecs_cluster" "cluster" {
name = "${var.app_name}-cluster"
setting {
name = "containerInsights"
value = "enabled"
}
tags = {
Name = "${var.app_name}-cluster"
}
}
resource "aws_ecs_cluster_capacity_providers" "capacity_provider" {
cluster_name = "${var.app_name}-cluster"
capacity_providers = ["FARGATE"]
}
# LbListenerRule for api
resource "aws_lb_listener_rule" "api-blue" {
listener_arn = var.http_arn
priority = 1
action {
type = "forward"
target_group_arn = var.api_blue_arn
}
condition {
path_pattern {
values = ["/api/*"]
}
}
}
# LbListenerRule for web
resource "aws_lb_listener_rule" "web-blue" {
listener_arn = var.http_arn
priority = 3
action {
type = "forward"
target_group_arn = var.web_blue_arn
}
condition {
path_pattern {
values = ["*"]
}
}
}
# EcsService for Fargate api
resource "aws_ecs_service" "api-service" {
name = "${var.api_app_name}-service"
cluster = aws_ecs_cluster.cluster.id
depends_on = [aws_lb_listener_rule.api-blue]
task_definition = aws_ecs_task_definition.api-definition.arn
desired_count = 1
# ecs exec
enable_execute_command = true
network_configuration {
subnets = ["${var.subnet_p1a_id}"]
security_groups = ["${var.apserver_sg_id}"]
assign_public_ip = true
#assign_public_ip = false
}
load_balancer {
target_group_arn = var.api_alb_target_group_arn
container_name = var.api_app_name
container_port = var.api_ports[0].internal
}
deployment_controller {
type = "CODE_DEPLOY"
}
capacity_provider_strategy {
base = 1
weight = 100
capacity_provider = "FARGATE"
}
lifecycle {
ignore_changes = [desired_count, task_definition, load_balancer]
}
}
# EcsService for Fargate web
resource "aws_ecs_service" "web-service" {
name = "${var.web_app_name}-service"
cluster = aws_ecs_cluster.cluster.id
depends_on = [aws_lb_listener_rule.web-blue, aws_lb_listener_rule.api-blue]
task_definition = aws_ecs_task_definition.web-definition.arn
desired_count = 1
load_balancer {
target_group_arn = var.web_alb_target_group_arn
container_name = var.web_app_name
container_port = var.web_ports[0].internal
}
network_configuration {
subnets = ["${var.subnet_p1c_id}"]
security_groups = ["${var.webserver_sg_id}"]
assign_public_ip = true
#assign_public_ip = false
}
deployment_controller {
type = "CODE_DEPLOY"
}
capacity_provider_strategy {
base = 1
weight = 100
capacity_provider = "FARGATE"
}
lifecycle {
ignore_changes = [desired_count, task_definition, load_balancer]
}
}
# TaskDefinition for Fargate api
resource "aws_ecs_task_definition" "api-definition" {
family = "${var.api_app_name}-definition"
requires_compatibilities = ["FARGATE"]
cpu = 256
memory = 512
execution_role_arn = var.ecs_main_role
task_role_arn = var.ecs_main_role
network_mode = "awsvpc"
container_definitions = jsonencode([
{
name = "${var.api_app_name}"
image = "${var.api_repository_url}:latest"
cpu = 10
memory = 256
essential = true
portMappings = [
{
hostPort = var.api_ports[0].internal
containerPort = var.api_ports[0].external
}
]
environment = [
{
name = "HOST"
value = split(":", "${var.db_endpoint}")[0]
},
{
name = "DBNAME"
value = "${var.db_name}"
},
{
name = "USERNAME"
value = "${var.db_username}"
},
{
name = "PASSWORD"
value = "${var.db_password}"
},
]
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-region = "ap-northeast-1"
awslogs-stream-prefix = "${var.api_app_name}"
awslogs-create-group = "true"
awslogs-group = "/fargate/${var.app_name}/dev/${var.api_app_name}"
}
}
}
])
runtime_platform {
operating_system_family = "LINUX"
cpu_architecture = "X86_64"
}
tags = {
Name = "${var.app_name}-${var.api_app_name}-template"
}
}
# TaskDefinition for Fargate web
resource "aws_ecs_task_definition" "web-definition" {
family = "${var.web_app_name}-definition"
requires_compatibilities = ["FARGATE"]
cpu = 256
memory = 512
execution_role_arn = var.ecs_main_role
task_role_arn = var.ecs_main_role
network_mode = "awsvpc"
container_definitions = jsonencode([
{
name = "${var.web_app_name}"
image = "${var.web_repository_url}:latest"
cpu = 10
memory = 256
essential = true
portMappings = [
{
containerPort = var.web_ports[0].internal
hostPort = var.web_ports[0].external
}
]
logConfiguration = {
logDriver = "awslogs"
options = {
awslogs-region = "ap-northeast-1"
awslogs-stream-prefix = "${var.web_app_name}"
awslogs-create-group = "true"
awslogs-group = "/fargate/${var.app_name}/dev/${var.web_app_name}"
}
}
}
])
runtime_platform {
operating_system_family = "LINUX"
cpu_architecture = "X86_64"
}
tags = {
Name = "${var.app_name}-${var.web_app_name}-template"
}
}
ecs execをする
今回は
クラスター名=todoproject
コンテナ名=apiserver
です。
タスクIDはaws consoleから、ecs→サービス→タスクで確認できます。
aws ecs execute-command --task=【タスクID】 --interactive \
--cluster=【クラスター名】 --container=【コンテナ名】 --command /bin/bash
マイグレーションを実行します。
rails db:migrate
結果
その後
その後のためにmigrationは自動化しておきます。
resource "null_resource" "init_db" {
triggers = {
cluster_arn = aws_ecs_cluster.cluster.arn
task_definition_arn = aws_ecs_task_definition.api-definition.arn
}
provisioner "local-exec" {
command = <<EOT
aws ecs run-task \
--region $AWS_DEFAULT_REGION \
--launch-type FARGATE \
--network-configuration "awsvpcConfiguration={subnets=[$SUBNET_ID],securityGroups=[$SECURITY_GROUP_ID],assignPublicIp=ENABLED}" \
--cluster $CLUSTER_NAME \
--task-definition $TASK_NAME \
--overrides "{\"containerOverrides\": [{\"name\": \"apiserver\",\"command\": [\"rails\", \"db:migrate\"]}]}"
EOT
interpreter = ["/bin/bash", "-c"]
environment = {
AWS_DEFAULT_REGION = "ap-northeast-1"
SUBNET_ID = var.subnet_p1a_id
SECURITY_GROUP_ID = var.apserver_sg_id
CLUSTER_NAME = aws_ecs_cluster.cluster.name
TASK_NAME = aws_ecs_task_definition.api-definition.family
}
}
depends_on = [
aws_ecs_cluster.cluster,
aws_ecs_task_definition.api-definition
]
}