You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Terraform配置ECS Fargate忽略ALB健康检查 GitHub Actions部署未失败

ECS Fargate+Terraform部署忽略ALB健康检查错误标记为成功问题排查求助

您好,我是ECS Fargate和Terraform的新手,下述配置大多参考教程与技术博客编写,目前遇到部署异常问题,具体情况如下:

观察到的基础现象

  • 应用因无法连接RDS无法启动(可通过CloudWatch日志验证),该情况属于预期内,因为我尚未配置RDS。
  • ECS/Fargate会销毁失败的任务并创建新任务,该行为也符合预期。

预期行为(部署应当失败)

我特意将配置设置为必然失败,没有任何ECS容器能成功启动,ALB健康检查永远不会返回成功状态,部署应当判定为失败,原因如下:

  • ALB health_check配置为匹配499响应码,该状态码在我的应用中不存在,且应用甚至没有/health检查端点。
  • 应用完全无法启动,启动后10秒内就会退出,甚至不会启动任何HTTP服务监听器。

实际异常行为

即便没有任何容器正常存活,部署始终会标记为成功。
我观察到的部署流程(假设期望实例数为3):

  • 部署启动后ECS任务显示为「3个Pending Tasks」
  • 随后变为「1个Running Task + 2个Pending Tasks」,任务失败后回到「3个Pending Tasks」状态
  • 频繁会出现「2个Running Tasks」的状态,任务失败后回到Pending状态
  • 一段时间后会短暂出现「3个Running Tasks」的状态
  • 一旦显示「3个Running Tasks」,部署就会被标记为成功。

当ECS显示「3个Running Tasks」时,没有任何ALB健康检查成功过,Running仅代表容器启动,不代表健康检查通过。
看起来ECS仅将「Running」状态作为部署成功的判定依据,完全忽略ALB健康检查结果,这和我了解的官方运行逻辑完全不符。
除此之外,ECS不会等待前一个任务完全健康就启动新任务,同样忽略了ALB健康检查,我原本预期它会基于ALB健康检查结果逐个启动容器。
网上有大量ECS部署因ELB健康检查失败而失败的相关内容,但我遇到了完全相反的情况,找不到合理的解释。
我是相关技术栈的新手,所以默认是我配置有误或者对运行逻辑有误解,但我排查了超过12小时仍找不到问题,希望能获得帮助。

Terraform配置

locals {
  name         = "${lower(var.project)}-${var.env}"
  service_name = "${local.name}-api"

  port = 3000
}

resource "aws_lb" "api" {
  name               = "${local.service_name}-lb"
  internal           = false
  load_balancer_type = "application"
  tags               = var.tags

  subnets = var.public_subnets

  security_groups = [
    aws_security_group.http.id,
    aws_security_group.https.id,
    aws_security_group.egress-all.id,
  ]
}

resource "aws_lb_target_group" "api" {
  name        = local.service_name
  port        = 3000
  protocol    = "HTTP"
  target_type = "ip"
  vpc_id      = var.vpc_id
  tags        = var.tags

  health_check {
    enabled             = true
    healthy_threshold   = 3
    interval            = 30
    path                = "/"
    port                = "traffic-port"
    protocol            = "HTTP"
    matcher             = "499" # This is a silly reponse code, it never succeeds
    unhealthy_threshold = 3
  }

  # NOTE: TF is unable to destroy a target group while a listener is attached,
  # therefore create a new one before destroying the old. This also means
  # we have to let it have a random name, and then tag it with the desired name.
  lifecycle {
    create_before_destroy = true
  }

  depends_on = [aws_lb.api]
}

resource "aws_lb_listener" "api-http" {
  load_balancer_arn = aws_lb.api.arn
  port              = "80"
  protocol          = "HTTP"

  default_action {
    type             = "forward"
    target_group_arn = aws_lb_target_group.api.arn
  }
}

resource "aws_iam_role" "ecs-alb-role" {
  name               = "${local.name}-api-alb-role"
  assume_role_policy = data.aws_iam_policy_document.ecs-task-assume-role.json
  tags               = var.tags
}

data "aws_iam_policy_document" "ecs-task-assume-role" {
  statement {
    actions = ["sts:AssumeRole"]

    principals {
      type        = "Service"
      identifiers = ["ecs-tasks.amazonaws.com"]
    }
  }
}

data "aws_iam_policy" "ecs-alb-role" {
  arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}

resource "aws_iam_role_policy_attachment" "ecs-alb-role" {
  role       = aws_iam_role.ecs-alb-role.name
  policy_arn = data.aws_iam_policy.ecs-alb-role.arn
}

resource "aws_ecs_cluster" "cluster" {
  name = "${local.name}-cluster"
  tags = var.tags
}

resource "aws_ecs_service" "ecs-api" {
  name            = local.service_name
  task_definition = aws_ecs_task_definition.ecs-api.arn
  cluster         = aws_ecs_cluster.cluster.id
  launch_type     = "FARGATE"
  desired_count   = var.desired_count
  tags            = var.tags

  network_configuration {
    assign_public_ip = false
    security_groups = [
      aws_security_group.api-ingress.id,
      aws_security_group.egress-all.id
    ]
    subnets = var.private_subnets
  }

  load_balancer {
    target_group_arn = aws_lb_target_group.api.arn
    container_name   = var.container_name
    container_port   = local.port
  }

  deployment_circuit_breaker {
    enable   = true
    rollback = true
  }
}

resource "aws_cloudwatch_log_group" "ecs-api" {
  name = "/ecs/${local.service_name}"
  tags = var.tags
}

resource "aws_ecs_task_definition" "ecs-api" {
  family             = local.service_name
  execution_role_arn = aws_iam_role.ecs-alb-role.arn
  tags               = var.tags

  cpu                      = 256
  memory                   = 512
  requires_compatibilities = ["FARGATE"]
  network_mode             = "awsvpc"

  container_definitions = <<EOF
  [
    {
      "name": "${var.container_name}",
      "image": "${var.ecr_url}/${var.container_name}:latest",
      "portMappings": [
        {
          "containerPort": ${local.port}
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-region": "${var.aws_region}",
          "awslogs-group": "/ecs/${local.service_name}",
          "awslogs-stream-prefix": "ecs"
        }
      }
    }
  ]
  EOF
}


resource "aws_security_group" "http" {
  name        = "http"
  description = "HTTP traffic"
  vpc_id      = var.vpc_id
  tags        = var.tags

  ingress {
    from_port   = 80
    to_port     = 80
    protocol    = "TCP"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_security_group" "https" {
  name        = "https"
  description = "HTTPS traffic"
  vpc_id      = var.vpc_id
  tags        = var.tags

  ingress {
    from_port   = 443
    to_port     = 443
    protocol    = "TCP"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_security_group" "egress-all" {
  name        = "egress_all"
  description = "Allow all outbound traffic"
  vpc_id      = var.vpc_id
  tags        = var.tags

  egress {
    from_port   = 0
    to_port     = 0
    protocol    = "-1"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

resource "aws_security_group" "api-ingress" {
  name        = "api_ingress"
  description = "Allow ingress to API"
  vpc_id      = var.vpc_id
  tags        = var.tags

  ingress {
    from_port   = 3000
    to_port     = 3000
    protocol    = "TCP"
    cidr_blocks = ["0.0.0.0/0"]
  }
}

GitHub Action部署配置

env:
  AWS_REGION: eu-west-1
  ECR_REPOSITORY: my-service-api
  ECS_SERVICE: my-service-dev-api
  ECS_CLUSTER: my-service-dev-cluster
  TASK_DEFINITION: arn:aws:ecs:eu-west-1:123456789:task-definition/my-service-dev-api

name: Deploy
on:
  push:
    branches:
      - main
jobs:
  build:
    name: Deploy
    runs-on: ubuntu-latest
    timeout-minutes: 10
    permissions:
      packages: write
      contents: read
    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Configure AWS credentials
        uses: aws-actions/configure-aws-credentials@13d241b293754004c80624b5567555c4a39ffbe3
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ env.AWS_REGION }}

      - name: Login to Amazon ECR
        id: login-ecr
        uses: aws-actions/amazon-ecr-login@aaf69d68aa3fb14c1d5a6be9ac61fe15b48453a2

      - name: Build, tag, and push image to Amazon ECR
        id: build-image
        env:
          ECR_REGISTRY: ${{ steps.login-ecr.outputs.registry }}
          IMAGE_TAG: ${{ github.sha }}
        run: |
          docker build -t $ECR_REGISTRY/$ECR_REPOSITORY:$GITHUB_RUN_NUMBER .
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$GITHUB_RUN_NUMBER

          docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$GITHUB_RUN_NUMBER $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG

          docker tag $ECR_REGISTRY/$ECR_REPOSITORY:$GITHUB_RUN_NUMBER $ECR_REGISTRY/$ECR_REPOSITORY:latest
          docker push $ECR_REGISTRY/$ECR_REPOSITORY:latest

          echo "::set-output name=image::$ECR_REGISTRY/$ECR_REPOSITORY:$GITHUB_RUN_NUMBER"

      - name: Download task definition
        id: download-task
        run: |
          aws ecs describe-task-definition \
            --task-definition ${{ env.TASK_DEFINITION }} \
            --query taskDefinition > task-definition.json
          echo ${{ env.TASK_DEFINITION }}
          echo "::set-output name=revision::$(cat task-definition.json | jq .revision)"

      - name: Fill in the new image ID in the Amazon ECS task definition
        id: task-def
        uses: aws-actions/amazon-ecs-render-task-definition@v1
        with:
          task-definition: task-definition.json
          container-name: ${{ env.ECR_REPOSITORY }}
          image: ${{ steps.build-image.outputs.image }}

      - name: Deploy Amazon ECS task definition
        uses: aws-actions/amazon-ecs-deploy-task-definition@v1
        with:
          task-definition: ${{ steps.task-def.outputs.task-definition }}
          service: ${{ env.ECS_SERVICE }}
          cluster: ${{ env.ECS_CLUSTER }}
          wait-for-service-stability: true
          wait-for-minutes: 5

      - name: De-register previous revision
        run: |
          aws ecs deregister-task-definition \
            --task-definition ${{ env.TASK_DEFINITION }}:${{ steps.download-task.outputs.revision }}

(已匿名化部分标识)上述配置可正常完成部署,唯一问题是ECS容器从未通过ALB健康检查的情况下,GitHub CI仍不会判定部署失败。

内容的提问来源于stack exchange,提问作者Niels Krijger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.10.05 01:09:03