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

基于Terraform的AWS CI/CD管道:CodeBuild至ECS部署问题咨询

AWS CI/CD管道部署:CodeBuild构建镜像并推送至ECR的解决方案

问题描述

我正在部署一套AWS CI/CD管道,流程为:开发者提交代码至AWS CodeCommit → AWS CodeBuild构建Docker镜像并推送至ECR → 从ECR部署镜像到ECS Fargate。目前已完成部分Terraform配置,但卡在如何让CodeBuild正确获取Docker镜像URL并推送至ECR的环节,以下是我的Terraform代码:

resource "aws_codecommit_repository" "repo" {
  repository_name = var.repository_name
  description     = var.description
  default_branch  = var.default_branch

  # Tags
  tags = var.tags

}

# Triggers
resource "aws_codecommit_trigger" "triggers" {
  count           = length(var.triggers)
  repository_name = aws_codecommit_repository.repo.repository_name

  trigger {
    name            = lookup(element(var.triggers, count.index), "name")
    events          = lookup(element(var.triggers, count.index), "events")
    destination_arn = lookup(element(var.triggers, count.index), "destination_arn")
  }

}

resource "aws_ecr_repository" "my_sample_ecr_repo" {
  name = "my-sample-ecr-repo" 
}

resource "aws_codebuild_project" "codebuild_project" {
  name          = "sample-code"
  description   = "Codebuild demo with Terraform"
  build_timeout = "120"

  artifacts {
    type = "NO_ARTIFACTS"
  }

  source {
    type            = "CodeCommit"
    location        = lookup(var.repository_name)
  }

  environment {
    image                       = lookup(var.codebuild_params, "IMAGE")
    type                        = lookup(var.codebuild_params, "TYPE")
    compute_type                = lookup(var.codebuild_params, "COMPUTE_TYPE")
    image_pull_credentials_type = lookup(var.codebuild_params, "CRED_TYPE")
    privileged_mode             = true

    dynamic "environment_variable" {
      for_each = var.environment_variables
      content {
        name  = environment_variable.key
        value = environment_variable.value
      }
    }
  }

  logs_config {
    cloudwatch_logs {
      status = "DISABLED"
    }

    s3_logs {
      status = "DISABLED"
    }
  }
}

resource "aws_ecs_cluster" "my_cluster" {
  name = "my-cluster" # Naming the cluster
}

resource "aws_ecs_task_definition" "my_sample_task" {
  family                   = "my-sample-task" # Naming the task
  container_definitions    = <<DEFINITION
  [
    {
      "name": "my-sample-task",
      "image": "${aws_ecr_repository.my_sample_ecr_repo.repository_url}",
      "essential": true,
      "portMappings": [
        {
          "containerPort": 3000,
          "hostPort": 3000
        }
      ]
      "memory": 512,
      "cpu": 256
    }
  ]
  DEFINITION
  requires_compatibilities = ["FARGATE"] # Stating that we are using ECS Fargate
  network_mode             = "awsvpc"    # Using awsvpc as our network mode as this is required for Fargate
  memory                   = 512         # Specifying the memory our container requires
  cpu                      = 256         # Specifying the CPU our container requires
  execution_role_arn       = "${aws_iam_role.ecsTaskExecutionRole.arn}"
}

resource "aws_iam_role" "ecsTaskExecutionRole" {
  name               = "ecsTaskExecutionRole"
  assume_role_policy = "${data.aws_iam_policy_document.assume_role_policy.json}"
}

data "aws_iam_policy_document" "assume_role_policy" {
  statement {
    actions = ["sts:AssumeRole"]

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

resource "aws_iam_role_policy_attachment" "ecsTaskExecutionRole_policy" {
  role       = "${aws_iam_role.ecsTaskExecutionRole.name}"
  policy_arn = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
}


resource "aws_ecs_service" "my_sample_service" {
  name            = "my-sample-service"                             # Naming our first service
  cluster         = "${aws_ecs_cluster.my_cluster.id}"             # Referencing our created Cluster
  task_definition = "${aws_ecs_task_definition.my_sample_task.arn}" # Referencing the task our service will spin up
  launch_type     = "FARGATE"
  desired_count   = 1 # Setting the number of containers to 3

  load_balancer {
    target_group_arn = "${aws_lb_target_group.target_group.arn}" # Referencing our target group
    container_name   = "${aws_ecs_task_definition.my_sample_task.family}"
    container_port   = 3000 # Specifying the container port
  }

  network_configuration {
    subnets          = ["${aws_default_subnet.default_subnet_a.id}", "${aws_default_subnet.default_subnet_b.id}", "${aws_default_subnet.default_subnet_c.id}"]
    assign_public_ip = true                                                # Providing our containers with public IPs
    security_groups  = ["${aws_security_group.service_security_group.id}"] # Setting the security group
  }
}

resource "aws_default_vpc" "default_vpc" {
}

# Providing a reference to our default subnets
resource "aws_default_subnet" "default_subnet_a" {
  availability_zone = "ap-south-1c"
}

resource "aws_default_subnet" "default_subnet_b" {
  availability_zone = "ap-south-1b"
}

resource "aws_default_subnet" "default_subnet_c" {
  availability_zone = "ap-south-1a"
}
resource "aws_security_group" "service_security_group" {
  ingress {
    from_port = 0
    to_port   = 0
    protocol  = "-1"
    # Only allowing traffic in from the load balancer security group
    security_groups = ["${aws_security_group.load_balancer_security_group.id}"]
  }

  egress {
    from_port   = 0 # Allowing any incoming port
    to_port     = 0 # Allowing any outgoing port
    protocol    = "-1" # Allowing any outgoing protocol 
    cidr_blocks = ["0.0.0.0/0"] # Allowing traffic out to all IP addresses
  }
}

resource "aws_alb" "application_load_balancer" {
  name               = "test-lb-tf" # Naming our load balancer
  load_balancer_type = "application"
  subnets = [ # Referencing the default subnets
    "${aws_default_subnet.default_subnet_a.id}",
    "${aws_default_subnet.default_subnet_b.id}",
    "${aws_default_subnet.default_subnet_c.id}"
  ]
  # Referencing the security group
  security_groups = ["${aws_security_group.load_balancer_security_group.id}"]
}

resource "aws_security_group" "load_balancer_security_group" {
  ingress {
    from_port   = 80 # Allowing traffic in from port 80
    to_port     = 80
    protocol    = "tcp"
    cidr_blocks = ["0.0.0.0/0"] # Allowing traffic in from all sources
  }

  egress {
    from_port   = 0 # Allowing any incoming port
    to_port     = 0 # Allowing any outgoing port
    protocol    = "-1" # Allowing any outgoing protocol 
    cidr_blocks = ["0.0.0.0/0"] # Allowing traffic out to all IP addresses
  }
}

resource "aws_lb_target_group" "target_group" {
  name        = "target-group"
  port        = 80
  protocol    = "HTTP"
  target_type = "ip"
  vpc_id      = "${aws_default_vpc.default_vpc.id}" # Referencing the default VPC
  health_check {
    matcher = "200,301,302"
    path = "/"
  }
}

resource "aws_lb_listener" "listener" {
  load_balancer_arn = "${aws_alb.application_load_balancer.arn}" # Referencing our load balancer
  port              = "80"
  protocol          = "HTTP"
  default_action {
    type             = "forward"
    target_group_arn = "${aws_lb_target_group.target_group.arn}" # Referencing our tagrte group
  }
}

解决步骤

1. 为CodeBuild配置ECR访问权限

当前Terraform未配置CodeBuild的服务角色,需要创建具备ECR访问权限的IAM角色:

# CodeBuild服务角色
resource "aws_iam_role" "codebuild_role" {
  name = "codebuild-ci-cd-role"
  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "codebuild.amazonaws.com"
        }
      }
    ]
  })
}

# 附加ECR全访问策略
resource "aws_iam_role_policy_attachment" "codebuild_ecr_policy" {
  role       = aws_iam_role.codebuild_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonEC2ContainerRegistryFullAccess"
}

# 附加CodeBuild基础管理权限
resource "aws_iam_role_policy_attachment" "codebuild_base_policy" {
  role       = aws_iam_role.codebuild_role.name
  policy_arn = "arn:aws:iam::aws:policy/AWSCodeBuildAdminAccess"
}

将该角色关联到CodeBuild项目,修改aws_codebuild_project.codebuild_project:

resource "aws_codebuild_project" "codebuild_project" {
  name          = "sample-code"
  description   = "Codebuild demo with Terraform"
  build_timeout = "120"
  service_role  = aws_iam_role.codebuild_role.arn # 添加此行关联角色

  # 其余原有配置保持不变
}

2. 在CodeCommit仓库中添加buildspec.yml

CodeBuild需要通过buildspec.yml定义构建流程,在仓库根目录创建该文件:

version: 0.2

phases:
  pre_build:
    commands:
      # 获取AWS账号ID和区域
      - ACCOUNT_ID=$(aws sts get-caller-identity --query Account --output text)
      - REGION=$(aws configure get region)
      # 拼接ECR仓库URI
      - ECR_REPO_URI="${ACCOUNT_ID}.dkr.ecr.${REGION}.amazonaws.com/my-sample-ecr-repo"
      # 登录ECR
      - aws ecr get-login-password --region $REGION | docker login --username AWS --password-stdin $ECR_REPO_URI
      # 用提交ID生成唯一镜像标签
      - IMAGE_TAG=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
  build:
    commands:
      # 构建Docker镜像
      - docker build -t $ECR_REPO_URI:$IMAGE_TAG .
  post_build:
    commands:
      # 推送镜像至ECR
      - docker push $ECR_REPO_URI:$IMAGE_TAG
      # 输出镜像地址(供后续部署使用)
      - echo "推送完成,镜像地址:$ECR_REPO_URI:$IMAGE_TAG"

3. 修复现有Terraform代码的错误

  • 修正CodeBuild源地址:source.location应使用CodeCommit的HTTP克隆地址,而非仓库名称:
source {
  type            = "CodeCommit"
  location        = aws_codecommit_repository.repo.clone_url_http
}
  • 修复任务定义语法错误:container_definitions中portMappings后缺少逗号:
[
  {
    "name": "my-sample-task",
    "image": "${aws_ecr_repository.my_sample_ecr_repo.repository_url}",
    "essential": true,
    "portMappings": [
      {
        "containerPort": 3000,
        "hostPort": 3000
      }
    ], // 添加此逗号
    "memory": 512,
    "cpu": 256
  }
]

4. 可选:自动触发ECS服务更新

若要在镜像推送后自动更新ECS服务,需给CodeBuild角色添加ECS权限:

resource "aws_iam_role_policy_attachment" "codebuild_ecs_policy" {
  role       = aws_iam_role.codebuild_role.name
  policy_arn = "arn:aws:iam::aws:policy/AmazonECS_FullAccess"
}

同时修改buildspec.yml的post_build阶段,添加更新命令(需在仓库中放置任务定义模板task-definition.json,将镜像地址替换为IMAGE_PLACEHOLDER):

post_build:
  commands:
    - docker push $ECR_REPO_URI:$IMAGE_TAG
    - echo "推送完成,镜像地址:$ECR_REPO_URI:$IMAGE_TAG"
    # 替换任务定义中的镜像地址并注册新任务定义
    - sed -i "s|IMAGE_PLACEHOLDER|$ECR_REPO_URI:$IMAGE_TAG|" task-definition.json
    - aws ecs register-task-definition --cli-input-json file://task-definition.json
    # 强制更新ECS服务
    - aws ecs update-service --cluster my-cluster --service my-sample-service --force-new-deployment

内容的提问来源于stack exchange,提问作者Karthik Raja

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 06:00:17