概要
これまでは1インスタンス上に複数のコンテナを使うときは動的ポートマッピングのような方法で対応する必要がありました。
しかし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のコード
今回の成果物はこちらです
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のコメントで解決しました。
1.21ではドキュメントに明記されるようです。
Task Definition
resource "aws_ecs_task_definition" "api" { family = "api" container_definitions = "${file("task-definitions/api.json")}" network_mode = "awsvpc" }
network_mode
にawsvpc
を付けます。
[ { "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など詳細な表示に変わっています。
LB
LBには指定したコンテナがPrivateIPで紐付いていることが分かります。
複数コンテナ
インスタンスタイプを上げるとENI上限も増えるので、複数コンテナを扱えるようになります。
t2.microでは1コンテナしか起動できませんが、t2.smallにすると3コンテナ使えます。
以下のように各コンテナで別のPrivateIPがついていることが確認できます。
1つめのタスク
2つめのタスク
まとめ
TerraformでawsvpcモードのECSサービスを作りました。
各コンテナがPrivateIPを持てば、ServiceDiscoveryを用意すればgRPCの負荷分散が可能になります。
まだ東京リージョンには公開されていないので待ち遠しいですね。