😺

AWS Fargateタスクは各AZにどのように分散されるのか?

2024/09/23に公開

はじめに

皆さんはAWS Fargateがお好きでしょうか?私は好きです。
突然ですが、以下AWS公式ドキュメントにFargateタスクは利用可能なAZに可能な限り分散してくれるとの記載があります。

AWS公式ドキュメント: How Amazon ECS places tasks on container instances Fargate launch type

Task placement strategies and constraints aren't supported for tasks using the Fargate launch type. Fargate will try its best to spread tasks across accessible Availability Zones. If the capacity provider includes both Fargate and Fargate Spot, the spread behavior is independent for each capacity provider.

利用可能なAZに可能な限り分散してくれるとはどのような意味なのでしょうか?
実際に東京リージョン, バージニア北部リージョンで300タスクを起動させ、どのように分散されるのかを試してみました。

TL;DR

東京リージョンにおいてはAZ毎に100タスクずつ綺麗に分散されました。しかし、バージニア北部リージョン(通称:バー北)においては綺麗に分散されませんでした。バー北においてはFargateのvCPU Quotaの関係で、0.25vCPU, 0.5GB MEMで300タスクを起動しました。

実行条件によっては均等に分散されない可能性もあるので、それぞれの環境で実際に試してみることを強くお勧めします(多くのリソースを割り当てたり、FARGATE_SPOTを利用したりした場合、均等に分散しないかもしれません)
以下コードを利用すればお好みのリージョンで検証することが可能です。

検証に利用したコード

Terraform

variable "desired_count" {
  type    = number
  default = 3
}

variable "region" {
  type    = string
  default = "ap-northeast-1"
}

provider "aws" {
  region = var.region

  default_tags {
    tags = {
      Terraform = "true"
    }
  }
}

terraform {
  required_providers {
    aws = {
      source  = "hashicorp/aws"
      version = "5.67"
    }
  }

  required_version = ">= 1.0.0"
}

data "aws_availability_zones" "available" {
  state = "available"
}

resource "aws_vpc" "main" {
  cidr_block           = "10.0.0.0/16"
  enable_dns_hostnames = true
  enable_dns_support   = true

  tags = {
    Name = "fargate-vpc"
  }
}

resource "aws_subnet" "public" {
  count                   = length(data.aws_availability_zones.available.names)
  vpc_id                  = aws_vpc.main.id
  cidr_block              = "10.0.${count.index * 8}.0/21"
  availability_zone       = data.aws_availability_zones.available.names[count.index]
  map_public_ip_on_launch = true

  tags = {
    Name = "Public Subnet AZ ${count.index + 1}"
  }
}

resource "aws_internet_gateway" "main" {
  vpc_id = aws_vpc.main.id

  tags = {
    Name = "fargate-igw"
  }
}

resource "aws_route_table" "public" {
  vpc_id = aws_vpc.main.id

  route {
    cidr_block = "0.0.0.0/0"
    gateway_id = aws_internet_gateway.main.id
  }

  tags = {
    Name = "Public Route Table"
  }
}

resource "aws_route_table_association" "public" {
  count          = length(data.aws_availability_zones.available.names)
  subnet_id      = aws_subnet.public[count.index].id
  route_table_id = aws_route_table.public.id
}

resource "aws_security_group" "ecs_tasks" {
  name        = "ecs-tasks-sg"
  description = "Allow inbound/outbound traffic for ECS tasks"
  vpc_id      = aws_vpc.main.id

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["10.0.0.0/16"]
  }

  # Allow Pull container from ECR.
  egress {
    from_port   = 443
    to_port     = 443
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_ecs_cluster" "fargate_cluster" {
  name = "fargate-cluster"
}

resource "aws_ecs_task_definition" "sample_task" {
  family                   = "sample-task"
  network_mode             = "awsvpc"
  requires_compatibilities = ["FARGATE"]
  # cpu                      = "256"
  # memory                   = "512"
  cpu                      = "4096"
  memory                   = "16384"

  runtime_platform {
    cpu_architecture = "ARM64"
  }

  container_definitions = jsonencode([
    {
      name  = "sample-nginx-container"
      image = "public.ecr.aws/nginx/nginx:latest"
      portMappings = [
        {
          containerPort = 80
          hostPort      = 80
        }
      ]
    }
  ])
}

resource "aws_ecs_service" "sample_service" {
  name            = "sample-service"
  cluster         = aws_ecs_cluster.fargate_cluster.id
  task_definition = aws_ecs_task_definition.sample_task.arn
  desired_count   = var.desired_count
  launch_type     = "FARGATE"

  network_configuration {
    subnets          = aws_subnet.public[*].id
    assign_public_ip = true
    security_groups  = [aws_security_group.ecs_tasks.id]
  }
}

実行シェル

以下シェルをcheck-evenly-distributed-among-azs.shという名前で保存したのち、bash check-evenly-distributed-among-azs.sh ap-northeast-1 300のように実行することで本記事と同一条件でテストが可能です。
※ いい感じのAWS権限, jq/aws cli/Terraformインストール, 同一ディレクトリにTerraform(.tf)ファイルの配置が必要

#!/bin/bash

# Check Args.
if [ $# -ne 2 ]; then
    echo "Error : 2つの引数が必要です。" >&2
    echo "Usage : $0 <AWS_REGION> <ASSUMPTION_TASK_COUNT>" >&2
    echo "<AWS_REGION>はAWSのリージョン名を指定。"
    echo "<ASSUMPTION_TASK_COUNT>は起動するタスクの総数を指定。"
    echo "使用例: $0 ap-northeast-1 30"
    exit 1
fi

export AWS_DEFAULT_REGION=$1
export ASSUMPTION_TASK_COUNT=$2
export TF_VAR_region=$1
export TF_VAR_desired_count=$2
ATTEMPT_COUNT=5
OUTPUT_FILE_NAME="$AWS_DEFAULT_REGION-result-$ASSUMPTION_TASK_COUNT.txt"

for NUM in $(seq 1 $ATTEMPT_COUNT); do
    echo "-------------------------" | tee -a $OUTPUT_FILE_NAME
    echo "Attempt: $NUM" | tee -a $OUTPUT_FILE_NAME

    ## Terraform Apply
    terraform init && terraform apply -auto-approve

    ## Waiting Start ECS Tasks.
    sleep 30

    while true; do
        TASK_COUNT=$(aws ecs list-tasks --cluster fargate-cluster --service-name sample-service | jq '.taskArns | length')
        if [ "$TASK_COUNT" -eq "$ASSUMPTION_TASK_COUNT" ]; then
            echo "Task count reached $ASSUMPTION_TASK_COUNT. Displaying distribution:"
            aws ecs list-tasks --cluster fargate-cluster --service-name sample-service |
                jq -r '.taskArns[]' |
                xargs -I {} aws ecs describe-tasks --cluster fargate-cluster --tasks {} |
                jq -r '.tasks[].availabilityZone' |
                sort | uniq -c | tee -a $OUTPUT_FILE_NAME
            break
        else
            echo "Task count: $TASK_COUNT. Waiting 10 seconds before checking again..."
            sleep 10
        fi
    done

    ## Terraform destroy
    terraform destroy -auto-approve

    echo "Attempt $NUM completed."
    echo "-------------------------" | tee -a $OUTPUT_FILE_NAME

    ## Wait
    if [ "$NUM" -ne "$ATTEMPT_COUNT" ]; then
        sleep 30
    fi
done

実行条件(東京リージョン)

  • 実行タスク数: 300
  • 利用AZ:
    • ap-northeast-1a(apne1-az4)
    • ap-northeast-1c(apne1-az1)
    • ap-northeast-1d(apne1-az2)
  • CPUアーキテクチャ: ARM64
  • スペック: 4vCPU, 16GB MEM

結果(東京リージョン)

Attempt ap-northeast-1a ap-northeast-1c ap-northeast-1d
1 100 100 100
2 100 100 100
3 100 100 100
4 100 100 100
5 100 100 100

この結果から、東京リージョンにおいてはタスクが均等に分散されたことがわかりました。
(0.25vCPU, 0.5GB MEMの条件でも実施しましたが、綺麗に100タスクずつ分散しました)

実行条件(バージニア北部)

  • 実行タスク数: 300
  • 利用AZ:
    • us-east-1a(use1-az2)
    • us-east-1b(use1-az4)
    • us-east-1c(use1-az6)
    • us-east-1d(use1-az1)
    • us-east-1e(use1-az3)
    • us-east-1f(use1-az5)
  • CPUアーキテクチャ: ARM64
  • スペック: 0.25vCPU, 0.5GB MEM

結果(バージニア北部)

Attempt us-east-1a us-east-1b us-east-1c us-east-1d us-east-1f
1 62 78 66 46 48
2 62 67 57 56 58
3 61 58 57 61 63
4 64 62 58 60 56
5 68 65 64 53 50

この結果から、バージニア北部においてはタスクが均等に分散されなかったことがわかりました。

ちなみに、バージニア北部のus-east-1e(use1-az3)においてはARMがサポートされていないため、Fargateのサービスで該当AZを指定してもタスクは起動しません。

AWS公式ドキュメント: Amazon ECS task definitions for 64-bit ARM workloads

For the Fargate launch type, the following AWS Regions do not support 64-bit ARM workloads:

US East (N. Virginia), the use1-az3 Availability Zone

終わりに

今回は「AWS Fargateタスクは各AZにどのように分散されるのか?」と題して、東京リージョン, バージニア北部リージョンでFargateタスクがAZにどのように分散されるのかについて、実際に検証してみました。
概ね均等レベルにタスクを分散すれば良い場合、特に作り込まずとも良いことがわかりました。

この記事を読んでくださった方が、AWSに興味を少しでも持っていただけると嬉しいです。

ここまで読んでくださってありがとうございました。

Discussion