Carpe Diem

備忘録

TerraformでECSのawsvpcモードを使って各コンテナにPrivate IPを振る

概要

これまでは1インスタンス上に複数のコンテナを使うときは動的ポートマッピングのような方法で対応する必要がありました。

christina04.hatenablog.com

しかしawsvpcモードが使えるようになったことでコンテナ毎にPrivateIPを振れるようになり、ポートでなくIPベースで複数のコンテナを扱えるようになりました。
今回はその方法をterrraformで実現します。

環境

  • Terraform v0.11.7
  • terraform-provider-aws v1.20.0

前提

まず以下の点に注意してください。

  • awsvpsネットワークモードでは動的ポートマッピングは使えない
  • awsvpcの場合、Amazon ECS サービスにリンクされたロールが必要
  • 1タスクー1ENI使うので、インスタンスタイプのENI上限に注意
  • LBはALB or NLBのみサポート。ClassicLBは使えない

2つ目は既存の書き方だと実行時にコケます。
また3つ目は気づきにくいので必ず確認しましょう。
例えばt2.microだとPrivateIPは2個しか持てないので、インスタンス自身のIPとコンテナのIP1個で埋まります。なのでt2.micro上には複数のコンテナを起動できません。以下のようなエラーが出てしまいます。

service api-service was unable to place a task because no container instance met all of its requirements. The closest matching container-instance 5fd60464-7fc6-4c85-ba14-2eb9b2783950 encountered error "RESOURCE:ENI". For more information, see the Troubleshooting section.

Terraformのコード

今回の成果物はこちらです

github.com

ECSのパターンに近いので、以下追加で修正が必要な箇所を中心に説明します。

IAM

resource "aws_iam_service_linked_role" "ecs" {
  aws_service_name = "ecs.amazonaws.com"
}

awsvpcモードではAmazon ECS サービスにリンクされたロールが必要なので作成します。

Service

resource "aws_ecs_service" "api_service" {
  name                               = "api-service"
  cluster                            = "${aws_ecs_cluster.api_cluster.id}"
  task_definition                    = "${aws_ecs_task_definition.api.arn}"
  desired_count                      = 1
  deployment_minimum_healthy_percent = 0
  deployment_maximum_percent         = 100
  # Don't set iam_role if you use service linked role.
  # For more detail, See this issue. https://github.com/terraform-providers/terraform-provider-aws/issues/4657
  #iam_role                           = "${aws_iam_service_linked_role.ecs.arn}"

  network_configuration {
    security_groups = [
      "${aws_security_group.internal.id}",
    ]

    subnets = [
      "${aws_subnet.private_1a.id}",
    ]
  }

  load_balancer {
    target_group_arn = "${aws_alb_target_group.test_api.arn}"
    container_name   = "nginx"
    container_port   = 80
  }
}

以下の対応をします

  • iam_roleを付けない
  • network_configurationを追加する

特に前者はドキュメントにも書いてなくてハマったのですが、以下のissueのコメントで解決しました。

github.com

1.21ではドキュメントに明記されるようです。

Task Definition

resource "aws_ecs_task_definition" "api" {
  family                = "api"
  container_definitions = "${file("task-definitions/api.json")}"
  network_mode          = "awsvpc"
}

network_modeawsvpcを付けます。

[
  {
    "name": "nginx",
    "image": "nginx",
    "cpu": 64,
    "memory": 100,
    "essential": true,
    "network_mode": "awsvpc",
    "portMappings": [
      {
        "containerPort": 80,
        "hostPort": 80
      }
    ]
  }
]

またJSONの方にも"network_mode": "awsvpc"を付けます。
portMappings公式ドキュメントではcontainerPortのみになっていますが、terraformではhostPortが省略されると毎回変更点に出てしまうので注意してください。

またawsvpcモードではtask definitionのlinks機能は使えないようです。以下のようなエラーになります。

ClientException: Links are not supported when networkMode=awsvpc

ALB

resource "aws_alb_target_group" "test_api" {
  name                 = "test-api"
  port                 = 80
  protocol             = "HTTP"
  vpc_id               = "${aws_vpc.vpc.id}"
  target_type          = "ip"
  deregistration_delay = 30

  health_check {
    interval            = 30
    path                = "/"
    protocol            = "HTTP"
    timeout             = 5
    healthy_threshold   = 2
    unhealthy_threshold = 4
    matcher             = 200
  }

  tags {
    Name        = "test-alb"
    Environment = "Test"
    Type        = "ALB"
  }
}

target groupにtarget_type = "ip"を付けます。

動作検証

1コンテナ

まずは1つのコンテナだけで確認していきます。

タスクのネットワークコンフィグ

以前はbridgeとしか書かれていなかった箇所が、IPやsubnetなど詳細な表示に変わっています。 f:id:quoll00:20180528182549p:plain

LB

LBには指定したコンテナがPrivateIPで紐付いていることが分かります。 f:id:quoll00:20180528182540p:plain

複数コンテナ

インスタンスタイプを上げるとENI上限も増えるので、複数コンテナを扱えるようになります。
t2.microでは1コンテナしか起動できませんが、t2.smallにすると3コンテナ使えます。
以下のように各コンテナで別のPrivateIPがついていることが確認できます。

1つめのタスク

f:id:quoll00:20180528133003p:plain

2つめのタスク

f:id:quoll00:20180528133000p:plain

まとめ

TerraformでawsvpcモードのECSサービスを作りました。
各コンテナがPrivateIPを持てば、ServiceDiscoveryを用意すればgRPCの負荷分散が可能になります。
まだ東京リージョンには公開されていないので待ち遠しいですね。

ソース