본문 바로가기
infra

aws code pipeline blue / green 배포

by marble25 2024. 1. 12.

AWS 내에서 CD를 구성하기 위해 Code Pipeline을 CDK로 구성할 필요가 있다. 이를 공부하면서 겪은 내용을 정리해 둔다.

참고 문서:

Code Pipeline

배포를 위해 주로 사용하는 AWS 스택으로, 여러 개의 Stage로 구성할 수 있다.

이 Stage는 기능에 따라 크게 Source, Build, Deploy Stage로 나눌 수 있다.

Source Stage는 코드 등 아티팩트를 가져오는 역할을 하고, Build는 임시 인스턴스를 띄워서 빌드 또는 테스트하는 역할을 수행한다. Deploy는 실제 배포를 진행한다.

Code Pipeline은 다양한 워크플로우를 조합하여 Continuous Deployment를 구현할 수 있다.

NOTE: Code Pipeline은 v1과 v2가 있다. Default 옵션은 v1으로 되어 있다. v2를 이용하면 git tag 기반 배포를 진행할 수 있고, pipeline variable이나 source revision을 선택해서 배포할 수 있다.

  • V1 유형 파이프라인: 활성 파이프라인 (30일 이상 존재하고 해당 월에 코드 변경이 한 번 이상 실행되는 파이프라인)당 월 1.00 USD가 부과된다. 1개월간 사용하면서 새로운 코드 변경이 없는 파이프라인에 대해서는 요금이 청구되지 않는다. 활성 파이프라인은 한 달을 모두 채우지 못한 경우 요금이 비례 할당으로 계산되지 않는다. 파이프라인 생성 후 처음 30일 동안은 무료이다.
  • V2 유형 파이프라인: 작업 실행 분당 0.002 USD가 부과된다. 작업 실행 기간은 파이프라인에서 작업을 실행하기 시작한 시간부터 해당 작업이 완료 상태에 도달할 때까지 분 단위로 계산되며 가장 가까운 분 단위로 반올림된다. 수동 승인 및 사용자 지정 작업 유형을 제외한 모든 작업 유형에 대해 요금이 부과된다.

v1 파이프라인이 예상 금액이 적을 뿐더러 cdk에서는 현재 기준으로 v1 파이프라인만 지원한다. 어쩔 수 없이 v1 파이프라인을 선택했다.

Source Stage

workshop에서는 Code Commit을 이용한 배포를 설명하고 있다. Code Commit을 사용하면 전적으로 AWS Infra의 git 서버를 사용하기 때문에 private하게 사용할 수 있지만, github 환경을 사용할 수 없어서 많은 변화가 필요하다.

다행히, code commit 대신에 사용할 수 있는 옵션이 여러개 있다.

  • S3
  • ECR
  • CodeCommit
  • CodeStarConnection(Bitbucket, Github, Gitlab)
const sourceOutput = new Artifact();
const sourceAction = new CodeStarConnectionsSourceAction({
  actionName: "CodeStarConnection",
  owner: "xxx",
  repo: "repo",
  branch: "main",
  connectionArn: "<arn>",
  output: sourceOutput,
});
sourceStage.addAction(sourceAction);

NOTE: AWS CodeStar 지원 중단

Amazon Web Services(AWS)는 2024년 7월 31일부로 AWS CodeStar 프로젝트 생성 및 보기에 대한 지원을 중단합니다. 2024년 7월 31일 이후에는 더 이상 AWS CodeStar 콘솔에 액세스하거나 새 프로젝트를 생성할 수 없습니다. 하지만 소스 리포지토리, 파이프라인, 빌드를 포함하여 AWS CodeStar에서 생성한 AWS 리소스는 이 변경의 영향을 받지 않고 계속 작동합니다. AWS CodeStar 연결은 이번 중단의 영향을 받지 않습니다.

2024년 7월 31일부로 코드스타는 deprecate되는 것 같다. 하지만 Github에서 코드를 가져오려면 Code Star를 사용할 수밖에 없다.

생각해볼 거리

대안으로는, 프로세스를 완전히 바꾸는 방법도 있다. Github에서 브랜치에 머지되는 시점에 ECR로 push할 수 있다. 그리고 source stage에서는 ECR에 새 이미지가 들어올 때 trigger되도록 할 수 있다. 그러면 build stage도 필요하지 않게 된다.

Build Stage

build stage에서는 다음 옵션을 선택할 수 있다.

  • CodeBuild
  • Custom Jenkins
  • Custom TeamCity
  • Custom CloudBees

이 중에서 간단하게 사용할 수 있는 Code Build를 사용한다.

const buildStage = pipeline.addStage({
  stageName: "Build",
});

const buildArtifact = new Artifact('buildArtifact');
const ecr = new Repository(this, `my-repository`);
const buildProject = new PipelineProject(
  this,
  `my-build-project`,
  {
    environment: {
      buildImage: LinuxBuildImage.AMAZON_LINUX_2_5,
      // 도커 이미지 생성하려면 priviliged가 true여야 합니다.
      privileged: true,
    },
    // buildspec.yml에서 사용할 환경 변수를 정의합니다.
    environmentVariables: {
      REPOSITORY_URI: { value: "<uri>" },
      CONTAINER_NAME: {
        value: "<container_name>",
      },
      EXECUTION_ROLE_ARN: {
        value: "<rolearn>",
      },
      TASK_ROLE_ARN: {
        value: "<rolearn>",
      },
      FAMILY: {
        value: "<family>",
      },
    },
    buildSpec: BuildSpec.fromSourceFilename("./cdk/buildspec.yml"),
    encryptionKey: key,
  }
);
// ecr repo로 pull, push할 수 있는 권한을 줍니다.
ecr.grantPullPush(buildProject.grantPrincipal);
const buildAction = new CodeBuildAction({
  actionName: `my-build-action`,
  project: buildProject,
  input: sourceOutput,
  outputs: [buildArtifact],
});
buildStage.addAction(buildAction);

buildspec.yml은 build 단계에서 사용할 명령어를 정의해 둔 파일이다. 다음을 참고해서 작성한다.

version: 0.2

phases:
  pre_build:
	  commands:
      ...
  build:
    commands:
      ...
  post_build:
    commands:
      ...
      - IMAGE_NAME=$REPOSITORY_URI:$COMMIT_HASH
      - sed -i "s|SED_REPLACE_EXECUTION_ROLE_ARN|$EXECUTION_ROLE_ARN|g" ./cdk/taskdef.json
      - sed -i "s|SED_REPLACE_TASK_ROLE_ARN|$TASK_ROLE_ARN|g" ./cdk/taskdef.json
      - sed -i "s|SED_REPLACE_FAMILY|$FAMILY|g" ./cdk/taskdef.json
      - sed -i "s|SED_REPLACE_IMAGE_NAME|$IMAGE_NAME|g" ./cdk/taskdef.json
      - cat ./cdk/taskdef.json > taskdef.json
      - cat ./cdk/appspec.yaml > appspec.yaml

artifacts:
  files:
    - appspec.yaml
    - taskdef.json

phases는 자유롭게 작성하되, ECS blue green 배포를 위해서는 artifacts를 잘 작성해야 한다. 기본적으로 appspec.yaml, 2개의 파일이 필요하다.

  • appspec.yaml
version: 0.0
Resources:
  - TargetService:
      Type: AWS::ECS::Service
      Properties:
        TaskDefinition: <TASK_DEFINITION>
        LoadBalancerInfo:
          ContainerName: "Controller"
          ContainerPort: 8080

TASK_DEFINITION은 code deploy가 작업 정의의 arn으로 동적으로 치환해 준다.

  • taskdef.json
{
    "containerDefinitions": [
        {
            "name": "Controller",
            "image": "SED_REPLACE_IMAGE_NAME",
            "cpu": 4096,
            "memory": 8192,
            "links": [],
            "portMappings": [
                {
                    "containerPort": 8080,
                    "hostPort": 8080,
                    "protocol": "tcp"
                }
            ],
            "essential": true,
            "entryPoint": [],
            "command": [
                "command"
            ],
            "environment": [],
            "environmentFiles": [],
            "mountPoints": [],
            "volumesFrom": [],
            "secrets": [],
            "dnsServers": [],
            "dnsSearchDomains": [],
            "extraHosts": [],
            "dockerSecurityOptions": [],
            "dockerLabels": {},
            "ulimits": [],
            "logConfiguration": {
                "logDriver": "awslogs",
                "options": {
                    "awslogs-group": "/logs/~~~",
                    "awslogs-region": "ap-northeast-2",
                    "awslogs-stream-prefix": "ecs"
                },
                "secretOptions": []
            },
            "systemControls": []
        }
    ],
    "family": "SED_REPLACE_FAMILY",
    "taskRoleArn": "SED_REPLACE_TASK_ROLE_ARN",
    "executionRoleArn": "SED_REPLACE_EXECUTION_ROLE_ARN",
    "networkMode": "awsvpc",
    "requiresAttributes": [
        ...
    ],
    "requiresCompatibilities": [
        "FARGATE"
    ],
    "cpu": "4096",
    "memory": "8192",
    "runtimePlatform": {
        "cpuArchitecture": "X86_64"
    }
  }

IMAGE_NAME1도 code deploy에서 동적으로 치환되기 때문에 내버려 둔다.

SED_REPLACE~~ 필드는 code build 명령어에서 동적으로 치환되기 때문에 내버려 둔다.

TIP: ECS fargate에 먼저 배포해 보고 해당 task definition을 가져와서 필요한 필드만 남겨놓는 것이 현명하다.

Build stage에서는 build만 진행할 수 있는 것이 아닌, 테스트도 진행할 수 있다. command를 어떻게 구성하느냐에 따라 원하는 대로 동작 가능하다.

Deploy Stage

Deploy에서는 S3, CodeDeploy, ECS 등등 다양한 배포 옵션을 선택할 수 있다. 그 중에서 Blue Green 배포를 사용할 수 있는 ECS를 선택했다.

const deployStage = pipeline.addStage({
  stageName: "Deploy",
});

const deployAction = new CodeDeployEcsDeployAction({
  actionName: `my-deploy-action`,
  taskDefinitionTemplateInput: buildArtifact,
  appSpecTemplateInput: buildArtifact,
  deploymentGroup: new EcsDeploymentGroup(this, `my-deployment-group`, {
    application: new EcsApplication(this, `my-application`, {
      applicationName: `my-application`,
    }),
    deploymentGroupName: `my-deployment-group`,
    deploymentConfig: EcsDeploymentConfig.ALL_AT_ONCE,
    service: props.fargate,
    blueGreenDeploymentConfig: {
      blueTargetGroup: props.blueTargetGroup,
      greenTargetGroup: props.greenTargetGroup,
      listener: props.prodListener,
    }
  }),
});
deployStage.addAction(deployAction);

Blue target group과 Green target group, Prod listener와 fargate는 ECS에서 이미 정의되어 있음을 가정한다. 구성은 다음 문서를 참고할 수 있다.

Deploy 속성은 10분동안 트래픽을 점진적으로 blue → green으로 옮기는 옵션도 존재하는 등 여러 개가 존재하지만, 굳이 필요하지 않아 ALL_AT_ONCE로 설정했다.

Blue Green 배포시 배포 실패하면 이전 버전으로 자동으로 배포되게 된다. 배포한 버전에 오류가 있다면 이전 버전으로 롤백도 간단하게 할 수 있다. 배포 이전 버전의 배포를 클릭 후 배포 재시도를 누르면 이전 버전으로 롤백 가능하다.

'infra' 카테고리의 다른 글

AWS lambda layer  (0) 2024.03.23
[TIL] aws codebuild에서 docker image pull rate limit 해결하기  (0) 2023.12.15
[TIL] mig 해제하기  (0) 2023.12.14
[TIL] js pm2 패키지  (0) 2023.11.05
무중단 배포 프로세스  (0) 2023.09.17