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
相关产品推荐
相关产品推荐

