当新映像推送到 ECR 存储库时如何自动部署到 ECS Fargate

And*_*nko 5 amazon-ecs amazon-ecr aws-fargate aws-cdk

首先,这是特定于 CDK 的 - 我知道围绕这个主题有很多问题/答案,但没有一个是特定于 CDK 的。

鉴于最佳实践规定 Fargate 部署不应在 ECR 存储库中查找“最新”标签,那么在使用 ECR 作为源时如何设置 CDK 管道呢?

在多存储库应用程序中,每个服务都位于其自己的存储库中(这些存储库将有自己的 CDK CodeBuild 部署来设置构建并推送到 ECR),基础设施 CDK 管道如何知道新镜像被推送到ECR 存储库并能够将该新映像部署到 ECS Fargate 服务吗?

由于任务定义必须指定图像标签(否则它将查找可能不存在的“最新”),这似乎是不可能的。

作为一个具体的例子,假设我有以下 2 个存储库:

  • Cdk基础设施
    • 将为每个客户创建这些存储库之一,以为其应用程序创建完整的环境
  • 一些服务
    • 实际应用代码
    • 此存储库中只能存在一个并由多个 CdkInfra 项目重复使用
    • cdk定义 CodeBuild 项目的目录,因此当检测到推送到 master 时,将构建服务并将映像推送到 ECR

预期的工作流程如下:

  1. SomeService 存储库已更新,因此新映像已推送到 ECR
  2. CdkInfra 管道应检测到跟踪的 ECR 存储库具有新映像
  3. CdkInfra 管道更新 Fargate 任务定义以引用新图像的标签
  4. Fargate 服务拉取新映像并部署它

我知道目前 CodeDeploy 不支持 ECS 部署存在限制,因为 CFN 不支持 ECS 部署,但 CodePipelineActions 似乎能够设置一个 EcrSourceAction ,它可能能够实现此目的,但我一直无法得到这个工作至今。

这是否可能,或者我是否一直在等待 CFN 支持 ECS CodeDeploy 功能?

And*_*nko 2

好吧,经过一番黑客攻击后,我成功做到了这一点。

首先,服务本身(在本例中是一个 Spring Boot 项目)cdk在其根目录中获取一个目录。这基本上只是设置 CI/CD 管道的 CI 部分:

const appName: string = this.node.tryGetContext('app-name');

const ecrRepo = new ecr.Repository(this, `${appName}Repository`, {
    repositoryName: appName,
    imageScanOnPush: true,
    removalPolicy: cdk.RemovalPolicy.DESTROY,
});

const bbSource = codebuild.Source.bitBucket({
    // BitBucket account
    owner: 'mycompany',
    // Name of the repository this project belongs to
    repo: 'reponame',
    // Enable webhook
    webhook: true,
    // Configure so webhook only fires when the master branch has an update to any code other than this CDK project (e.g. Spring source only)
    webhookFilters: [codebuild.FilterGroup.inEventOf(codebuild.EventAction.PUSH).andBranchIs('master').andFilePathIsNot('./cdk/*')],
});

const buildSpec = {
    version: '0.2',
    phases: {
        pre_build: {
            // Get the git commit hash that triggered this build
            commands: ['env', 'export TAG=${CODEBUILD_RESOLVED_SOURCE_VERSION}'],
        },
        build: {
            commands: [
                // Build Java project
                './mvnw clean install -Dskiptests',
                // Log in to ECR repository that contains the Corretto image
                'aws ecr get-login-password --region us-west-2 | docker login --username AWS --password-stdin 489478819445.dkr.ecr.us-west-2.amazonaws.com',
                // Build docker images and tag them with the commit hash as well as 'latest'
                'docker build -t $ECR_REPO_URI:$TAG -t $ECR_REPO_URI:latest .',
                // Log in to our own ECR repository to push
                '$(aws ecr get-login --no-include-email)',
                // Push docker images to ECR repository defined above
                'docker push $ECR_REPO_URI:$TAG',
                'docker push $ECR_REPO_URI:latest',
            ],
        },
        post_build: {
            commands: [
                // Prepare the image definitions artifact file
                'printf \'[{"name":"servicename","imageUri":"%s"}]\' $ECR_REPO_URI:$TAG > imagedefinitions.json',
                'pwd; ls -al; cat imagedefinitions.json',
            ],
        },
    },
    // Define the image definitions artifact - is required for deployments by other CDK projects
    artifacts: {
        files: ['imagedefinitions.json'],
    },
};

const buildProject = new codebuild.Project(this, `${appName}BuildProject`, {
    projectName: appName,
    source: bbSource,
    environment: {
        buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_3,
        privileged: true,
        environmentVariables: {
            // Required for tagging/pushing image
            ECR_REPO_URI: { value: ecrRepo.repositoryUri },
        },
    },
    buildSpec: codebuild.BuildSpec.fromObject(buildSpec),
});

!!buildProject.role &&
    buildProject.role.addToPrincipalPolicy(
        new iam.PolicyStatement({
            effect: iam.Effect.ALLOW,
            actions: ['ecr:*'],
            resources: ['*'],
        }),
    );
Run Code Online (Sandbox Code Playgroud)

设置完成后,必须手动构建 CodeBuild 项目一次,以便 ECR 存储库具有有效的“最新”映像(否则将无法正确创建 ECS 服务)。

现在,在单独的基础设施代码库中,您可以照常创建 ECS 集群和服务,并从查找中获取 ECR 存储库:

const repo = ecr.Repository.fromRepositoryName(this, 'SomeRepository', 'reponame'); // reponame here has to match what you defined in the bbSource previously

const cluster = new ecs.Cluster(this, `Cluster`, { vpc });

const service = new ecs_patterns.ApplicationLoadBalancedFargateService(this, 'Service', {
    cluster,
    serviceName: 'servicename',
    taskImageOptions: {
        image: ecs.ContainerImage.fromEcrRepository(repo, 'latest'),
        containerName: repo.repositoryName,
        containerPort: 8080,
    },
});
Run Code Online (Sandbox Code Playgroud)

最后创建一个侦听 ECR 事件的部署构造,手动将生成的 imageDetail.json 文件转换为有效的 imagedefinitions.json 文件,然后部署到现有服务。

const sourceOutput = new cp.Artifact();
const ecrAction = new cpa.EcrSourceAction({
    actionName: 'ECR-action',
    output: sourceOutput,
    repository: repo, // this is the same repo from where the service was originally defined
});

const buildProject = new codebuild.Project(this, 'BuildProject', {
    environment: {
        buildImage: codebuild.LinuxBuildImage.AMAZON_LINUX_2_3,
        privileged: true,
    },
    buildSpec: codebuild.BuildSpec.fromObject({
        version: '0.2',
        phases: {
            build: {
                commands: [
                    'cat imageDetail.json | jq "[. | {name: .RepositoryName, imageUri: .ImageURI}]" > imagedefinitions.json',
                    'cat imagedefinitions.json',
                ],
            },
        },
        artifacts: {
            files: ['imagedefinitions.json'],
        },
    }),
});

const convertOutput = new cp.Artifact();
const convertAction = new cpa.CodeBuildAction({
    actionName: 'Convert-Action',
    input: sourceOutput,
    outputs: [convertOutput],
    project: buildProject,
});

const deployAction = new cpa.EcsDeployAction({
    actionName: 'Deploy-Action',
    service: service.service,
    input: convertOutput,
});

new cp.Pipeline(this, 'Pipeline', {
    stages: [
        { stageName: 'Source', actions: [ecrAction] },
        { stageName: 'Convert', actions: [convertAction] },
        { stageName: 'Deploy', actions: [deployAction] },
    ],
});
Run Code Online (Sandbox Code Playgroud)

显然,一旦 CloudFormation 完全支持这一点,这并不像原本那样干净,但它工作得很好。