dip Engineer Blog

Engineer Blog
ディップ株式会社のエンジニアによる技術ブログです。
弊社はバイトル・はたらこねっとなど様々なサービスを運営しています。

TerraformでFargateを構築する

はじめに

インフラエンジニアとしてTerraform運用を行っているのですが、 TerraformやFargateもだいぶ浸透してきて、導入している企業も増えてきているように感じます。 そのようなケースのサンプルとして公開したいと思います。

ファイル構成

ファイル構成は以下としています。

├── logs
│   ├── backend.tf
│   ├── main.tf
│   ├── outputs.tf
│   ├── provider.tf
│   └── variables.tf
├── buckets
│   ├── backend.tf
│   ├── main.tf
│   ├── outputs.tf
│   ├── provider.tf
│   └── variables.tf
├── ecr
│   ├── backend.tf
│   ├── main.tf
│   ├── outputs.tf
│   ├── provider.tf
│   └── variables.tf
└── fargate
    ├── backend.tf
    ├── files
    │   └── container_definition.json
    ├── main.tf
    ├── outputs.tf
    ├── provider.tf
    └── variables.tf

ポイントとしては、logとFargateのtfファイルの階層を分けている事です。 分ける理由としては、同階層にしてしまうとTerraform destoryした時にログまで消えてしまって後悔。。。というケースが起こるかなと思い階層を分けています。 同様の理由としてS3bucket、ECRも階層を分けています。

また、files以下にコンテナ定義のjsonファイル等を入れています。

Fargate

まずはクラスターから

resource "aws_ecs_cluster" "default" {
  name = local.service_name
}

タスク定義は以下のようになっています。 定義の内容をjsonファイルに記載しておいて、ECRのARNなどはdataソースを使って記入しています。

data "template_file" "default" {
  template = file("files/container_definition.json")
  vars = {
    ECR_ARN      = data.terraform_remote_state.ecr.outputs.default["repository_url"]
    SERVICE_NAME = local.service_name
  }
}

resource "aws_ecs_task_definition" "default" {
  family                   = local.service_name
  container_definitions    = data.template_file.default.rendered
  task_role_arn            = aws_iam_role.default.arn
  network_mode             = "awsvpc"
  execution_role_arn       = aws_iam_role.default.arn
  cpu                      = 512
  memory                   = 1024
  requires_compatibilities = ["FARGATE"]
}

タスク定義のjsonファイルは以下のようになっています。

[
  {
    "name": "${SERVICE_NAME}",
    "image": "${ECR_ARN}",
    "essential": true,
    "portMappings": [
      {
        "containerPort": 80,
        "hostPort": 80
      }
    ],
    "logConfiguration": {
      "logDriver": "awslogs",
      "options": {
        "awslogs-region": "ap-northeast-1",
        "awslogs-group": "/ecs/${SERVICE_NAME}",
        "awslogs-stream-prefix": "${SERVICE_NAME}"
      }
    }
  }
]

Ecsのサービスは以下のようになっています。

resource "aws_ecs_service" "default" {
  name            = local.service_name
  cluster         = aws_ecs_cluster.default.id
  task_definition = aws_ecs_task_definition.default.arn
  desired_count   = 2
  launch_type     = "FARGATE"

  load_balancer {
    target_group_arn = aws_lb_target_group.default.arn
    container_name   = local.service_name
    container_port   = 80
  }

  network_configuration {
    subnets = [
      data.terraform_remote_state.subnet.outputs.publib_a["id"],
      data.terraform_remote_state.subnet.outputs.publib_c["id"],
    ]
    security_groups = [
      aws_security_group.default.id
    ]
    assign_public_ip = true
  }
}

LB

resource "aws_lb" "default" {
  name = local.service_name
  internal           = false
  load_balancer_type = "application"
  subnets = [
    data.terraform_remote_state.subnet.outputs.publib_a["id"],
    data.terraform_remote_state.subnet.outputs.publib_c["id"],
  ]
  security_groups = [
    aws_security_group.default.id,
  ]

  enable_deletion_protection = true

  access_logs {
    bucket  = data.terraform_remote_state.buckets.outputs.default["id"]
    enabled = true
  }
}

resource "aws_lb_target_group" "default" {
  name        = local.service_name
  port        = 80
  protocol    = "HTTP"
  vpc_id      = data.terraform_remote_state.subnet.outputs.default["id"],
  target_type = "ip"
}

VPCとサブネットはバックエンドから参照しています。 また、security groupは別で作成しておいてください。 bucketについては、前述の通り階層を分けているためバックエンドから参照してください。 ターゲットグループのtarget_typeはFargateと連携するためにipに指定しておいてください。

LBへSSL証明書の適用 Certificate Managerに登録してあるSSL証明書を参照しています。

resource "aws_lb_listener" "default" {
  load_balancer_arn = aws_lb.default.arn
  protocol          = "HTTPS"
  port              = "443"
  ssl_policy        = "ELB_SecurityPolicy-TLS-1-2-2017-01"
  certificate_arn   = data.terraform_remote_state.certificate.outputs.default["arn"]
  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.default.arn
  }
}

80ポートへのアクセスはリダイレクトするようにします。

resource "aws_lb_listener" "redirect_https" {
  load_balancer_arn = aws_lb.default.arn
  port              = "80"
  protocol          = "HTTP"

  default_action  {
    type = "redirect"

    redirect {
      port        = "443"
      protocol    = "HTTPS"
      status_code = "HTTP_301"
    }
  }
}

variables

最後に、variablesは以下のようになっています。

variable "environment" {
  default = "development"
}

locals {
  service_name = "${var.environment}-service-name"
}

ここでlocalsを使っているのは、環境名などを変数に埋め込むためです。 特に必要ないかもしれないです。

最後に

これからTerraformを導入していきたい、Fargateを使ってみたいという方に少しでも参考になれば。。。

参考

筆者

https://dippeople.dip-net.jp/6818/dippeople.dip-net.jp (写真左)