AWS: Setting up a CodeDeploy pipeline for ECS blue-green deployment from ECR

37 Views Asked by At

I'm attempting to set up a CodeDeploy pipeline for ECS blue-green deployment from ECR, but I'm encountering some challenges. I need assistance and explanation on the missing steps to successfully configure this pipeline.

I'm working on configuring a continuous deployment pipeline from an Amazon Elastic Container Registry (ECR) repository to an Amazon Elastic Container Service (ECS) cluster using CodeDeploy for blue-green deployment. I've set up the ECS service, load balancer, and CodeDeploy deployment group. However, I'm struggling with integrating the pipeline with CodeDeploy and properly configuring the pipeline stages and actions.

Specifically, I need help with the following:

Integrating the ECR source with the CodeDeploy pipeline. Configuring the pipeline stages to build and deploy the application images. Setting up blue-green deployment strategies in CodeDeploy. Ensuring proper artifact management and passing the correct artifacts between pipeline stages. Handling any required IAM permissions for CodeDeploy actions.

import * as cloudwatch from 'aws-cdk-lib/aws-cloudwatch';
import * as codedeploy from 'aws-cdk-lib/aws-codedeploy';
import * as cdk from 'aws-cdk-lib';
import * as elbv2 from 'aws-cdk-lib/aws-elasticloadbalancingv2';
import { CodeDeployProps, CodeDeployResponse } from '../app-interface';
import * as acm from 'aws-cdk-lib/aws-certificatemanager';
import { Duration, Stack } from 'aws-cdk-lib';
import * as iam from 'aws-cdk-lib/aws-iam';
import { Utils } from '../util';

export function codeDeploy({
    stack,
    ecsService,
    vpc,
    alb,
}: CodeDeployProps): CodeDeployResponse {
    const [tgECSId, tgECSName] = Utils.generateResourceName(
        `ecs`,
        'target-group',
    );
    const blueTargetGroup = new elbv2.ApplicationTargetGroup(stack, tgECSId, {
        targets: [ecsService],
        targetGroupName: tgECSName,
        protocol: elbv2.ApplicationProtocol.HTTP,
        vpc: vpc,
        port: 3003,
        deregistrationDelay: cdk.Duration.seconds(30),
        healthCheck: {
            path: '/hello',
            healthyThresholdCount: 2,
            unhealthyThresholdCount: 3,
            interval: cdk.Duration.seconds(30),
            timeout: cdk.Duration.seconds(10),
            healthyHttpCodes: '200',
        },
    });

    const [ecsCodeDeployRoleID] = Utils.generateResourceName(
        `ecs-code-deploy`,
        `role`,
    );
    const ecsCodeDeployRole = new iam.Role(stack, ecsCodeDeployRoleID, {
        assumedBy: new iam.ServicePrincipal('ecs-tasks.amazonaws.com'),
        managedPolicies: [
            iam.ManagedPolicy.fromAwsManagedPolicyName(
                'AWSCodeDeployRoleForECS',
            ),
        ],
    });

    ecsCodeDeployRole.assumeRolePolicy?.addStatements(
        new iam.PolicyStatement({
            effect: iam.Effect.ALLOW,
            actions: ['sts:AssumeRole'],
            principals: [new iam.ServicePrincipal('codedeploy.amazonaws.com')],
        }),
    );

    const [tgGreenECSId, tgGreenECSName] = Utils.generateResourceName(
        `green`,
        'target-group',
    );
    const greenTargetGroup = new elbv2.ApplicationTargetGroup(
        stack,
        tgGreenECSId,
        {
            targets: [ecsService],
            targetGroupName: tgGreenECSName,
            targetType: elbv2.TargetType.IP,
            protocol: elbv2.ApplicationProtocol.HTTP,
            vpc: vpc,
            port: 3003,
            deregistrationDelay: cdk.Duration.seconds(30),
            healthCheck: {
                path: '/hello',
                healthyThresholdCount: 2,
                unhealthyThresholdCount: 3,
                interval: cdk.Duration.seconds(30),
                timeout: cdk.Duration.seconds(10),
                healthyHttpCodes: '200',
            },
        },
    );

    const anCert = acm.Certificate.fromCertificateArn(
        stack,
        'AnCert',
        process.env.AWS_ALB_CERT!,
    );

    const [httpsListenerId] = Utils.generateResourceName(`listener`, 'alb');
    const httpslistener = alb.addListener(httpsListenerId, {
        port: 443,
        open: true,
        certificates: [anCert],
    });

    const [httpsListenerActionId] = Utils.generateResourceName(
        `listener-action`,
        'alb',
    );
    httpslistener.addAction(httpsListenerActionId, {
        action: elbv2.ListenerAction.forward([
            blueTargetGroup,
            greenTargetGroup,
        ]),
    });

    const blueUnhealthyHosts = new cloudwatch.Alarm(
        stack,
        'BlueUnhealthyHosts',
        {
            alarmName: Stack.of(stack).stackName + '-Unhealthy-Hosts-Blue',
            metric: blueTargetGroup.metricUnhealthyHostCount(),
            threshold: 1,
            evaluationPeriods: 2,
        },
    );

    const greenUnhealthyHosts = new cloudwatch.Alarm(
        stack,
        'GreenUnhealthyHosts',
        {
            alarmName: Stack.of(stack).stackName + '-Unhealthy-Hosts-Green',
            metric: greenTargetGroup.metricUnhealthyHostCount(),
            threshold: 1,
            evaluationPeriods: 2,
        },
    );

    const blueApiFailure = new cloudwatch.Alarm(stack, 'Blue5xx', {
        alarmName: Stack.of(stack).stackName + '-Http-5xx-Blue',
        metric: blueTargetGroup.metricHttpCodeTarget(
            elbv2.HttpCodeTarget.TARGET_5XX_COUNT,
            { period: Duration.minutes(1) },
        ),
        threshold: 1,
        evaluationPeriods: 1,
    });

    const greenApiFailure = new cloudwatch.Alarm(stack, 'Green5xx', {
        alarmName: Stack.of(stack).stackName + '-Http-5xx-Green',
        metric: greenTargetGroup.metricHttpCodeTarget(
            elbv2.HttpCodeTarget.TARGET_5XX_COUNT,
            { period: Duration.minutes(1) },
        ),
        threshold: 1,
        evaluationPeriods: 1,
    });

    const deploy = new codedeploy.EcsDeploymentGroup(stack, 'BlueGreenDG', {
        alarms: [
            blueUnhealthyHosts,
            greenUnhealthyHosts,
            blueApiFailure,
            greenApiFailure,
        ],
        autoRollback: {
            stoppedDeployment: true,
        },
        service: ecsService,
        blueGreenDeploymentConfig: {
            blueTargetGroup,
            greenTargetGroup,
            listener: httpslistener,
        },
        role: ecsCodeDeployRole,
        deploymentConfig:
            codedeploy.EcsDeploymentConfig.CANARY_10PERCENT_5MINUTES,
    });
    return { deploy: deploy };
}

import * as codepipeline_actions from 'aws-cdk-lib/aws-codepipeline-actions';
import * as codepipeline from 'aws-cdk-lib/aws-codepipeline';
import { AWSCodePipelineProps } from '../app-interface';
import * as iam from 'aws-cdk-lib/aws-iam';

export function codePipeline({
    stack,
    ecrRepository,
    vpc,
    deploy // codedeploy.EcsDeploymentGroup,
}: AWSCodePipelineProps) {
    const sourceOutput = new codepipeline.Artifact('SourceOutput');
    const pipeline = new codepipeline.Pipeline(stack, 'MyPipeline');
    const sourceAction = new codepipeline_actions.EcrSourceAction({
        actionName: 'ECR',
        repository: ecrRepository,
        imageTag: 'latest',
        output: sourceOutput,
    });
    pipeline.role.addManagedPolicy(
        iam.ManagedPolicy.fromAwsManagedPolicyName(
            'AWSCodePipeline_FullAccess',
        ),
    );
    const appSpecTemplateFile = codepipeline.ArtifactPath.artifactPath(
        'DestinationInput',
        'cd-ecs.json',
    );
    const taskDefinitionTemplateFile = codepipeline.ArtifactPath.artifactPath(
        'taskDefTeplate',
        'taskdef-dev.json',
    );
    const destinationOutput = new codepipeline.Artifact('DestinationOutput');
    const destinationAction =
        new codepipeline_actions.CodeDeployEcsDeployAction({
            actionName: 'CodeDeploy',
            deploymentGroup: deploy,
            appSpecTemplateFile: appSpecTemplateFile,
            taskDefinitionTemplateFile: taskDefinitionTemplateFile,
        });

    pipeline.addStage({
        stageName: 'Source',
        actions: [sourceAction],
    });

    pipeline.addStage({
        stageName: 'Destination',
        actions: [destinationAction],
    });
}

0

There are 0 best solutions below