LoginSignup
0
0

RailsとNuxt3でtodoリストを作ろう[REST-API/Terraform/Fargate]〜その5、TerraformECS後編

Last updated at Posted at 2023-06-05

はじめに

RailsとNuxt3でtodoリストの作り方を
初めから丁寧に説明したいと思います。

使用pcはmacを想定しています。

完成した構成図は以下の通りです。

aws_structure.png

また、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デプロイ時、切り替える用に二つづつ作っています。

terraform/modules/elb/aws_lb.tf
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
  }
}
terraform/modules/elb/aws_lb_listener.tf
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"
  }
}
terraform/modules/elb/aws_lb_target_group.tf
# 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を保存しておく場所です。

terraform/modules/ecr/ecr.tf
# 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したことです。

terraform/modules/bash/null_resource.tf
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

terraform/modules/rds/aws_db_instance.tf
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"
  }
}
terraform/modules/rds/db_parameter_group.tf
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"
  }
}
terraform/modules/rds/aws_db_option_group.tf
resource "aws_db_option_group" "main_optiongroup" {
  name                 = "${var.app_name}-optiongroup"
  engine_name          = "mysql"
  major_engine_version = "8.0"
}

ecs

dockerコンテナをデブロイできます。

terraform/modules/ecs/ecs.tf
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"]
}
terraform/modules/ecs/aws_lb_listener_rule.tf
# 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 = ["*"]
    }
  }
}

terraform/modules/ecs/ecs_service.tf
# 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]
  }
}
terraform/modules/ecs/ecs_task_definition.tf
# 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

結果

Screenshot 2023-05-21 at 10.10.59.png

その後

その後のためにmigrationは自動化しておきます。

terraform/modules/ecs/null_resource.tf
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
  ]
}
0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0