AWS 내에서 CD를 구성하기 위해 Code Pipeline을 CDK로 구성할 필요가 있다. 이를 공부하면서 겪은 내용을 정리해 둔다.
참고 문서:
- https://ecsworkshop.com/blue_green_deployments/
- https://github.com/aws-containers/ecs-workshop-blue-green-deployments
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 |