카테고리 없음
AWS Code build , Code deploy를 사용하여 CI / CD 적용
woong29
2022. 9. 16. 12:20
문제
프로젝트 계획을 서비스를 나눠서 개발 해야겠다고 계획을 세우고 설계를 하는 과정속에서 배포 자동화도 같이 진행하기로 하였습니다. 배포해야될 서비스 4개 + cdk 1개 총 5개의 업데이트를 진행하기 위해서는 로컬에서 테스트 하고 git서버에 올린후 build하고 docker build 진행후에 ecr에 업로드하고 task 재실행... 의 길고 긴 과정을 개선하기 위해서 CI / CD를 고려하게되었습니다.
해결
- service의 CI/CD와 CDK의 CI/CD를 다르게 구성
- aws Code Build 와 Code Deploy를 사용
- 환경변수는 Aws Secrets Manger와 CDK설정을 통해서 외부에서 주입
CI (Continuous Integration) 및 CD (Continuous Delivery)를 위하여 AWS의 Code build 및 pipeline을 이용하였습니다. 인프라 관련 CDK 소스도 동일하게 github을 통하여 관리하게 되고 업로드시 build되어 Infra에 자동으로 적용되도록 구성하였습니다.
github에 Push를 진행하면 webhook을 통하여 AWS Code build에 Triger를 주게 되고 code build는 build설정에 따라 build 및 테스트를 진행하게 되고 설정에 따라 아티팩트를 저장하게 됩니다. code deploy는 저장된 아티팩트의 경로를 받아 배포를 진행하게 됩니다.
CDK Source는 아래와 같이 작업하였습니다.
const githubConnection = CodePipelineSource.gitHub(`${INFRA_GIT_REPO.owner}/${INFRA_GIT_REPO.repoName}`, INFRA_GIT_REPO.branch);
const pipeline = new CodePipeline(this, 'Pipeline', {
pipelineName: `InfraPipeline`,
synth: new ShellStep('Synth', {
input: githubConnection,
installCommands: [
'npm install typescript -g'
],
commands: [
'npm ci',
'npm run build',
'npx cdk synth'
],
}),
});
//Code Pipeline
const pipeline = new Pipeline(this, 'Pipeline', {
pipelineName: config.serviceName,
artifactBucket: new s3.Bucket(this, `bucket`, {
bucketName: `${config.serviceName}-pipeline`,
removalPolicy: RemovalPolicy.DESTROY,
autoDeleteObjects: true,
})});
//Source Stage
const sourceOutput = new Artifact();
const sourceAction = this.getGitHubSourceAction(config.gitRepo, sourceOutput);
pipeline.addStage({stageName: 'Source', actions: [sourceAction],})
//Build Stage
const buildOutput = new Artifact();
const buildAction = this.getCodeBuildAction(sourceOutput, buildOutput);
pipeline.addStage({stageName: 'Build', actions: [buildAction],})
//Deploy Beta Stage
const deployBetaAction = this.getEcsBetaDeployActioin(buildOutput);
pipeline.addStage({stageName: 'Deploy-Beta', actions: [deployBetaAction],})
private getGitHubSourceAction = (repo:GitRepo, output:Artifact) : GitHubSourceAction => {
return new GitHubSourceAction({
actionName: 'GitHubSourceAction',
owner: repo.owner,
output: output,
repo: repo.repoName,
branch: repo.branch,
oauthToken: SecretValue.secretsManager(repo.tokenName),
});
}
private getCodeBuildAction = (input: Artifact, output: Artifact): CodeBuildAction => {
return new CodeBuildAction({
actionName: "BuildAction",
input: input,
project: this.createCodeBuildProject(),
outputs: [output]
});
}
private getEcsBetaDeployActioin = (buildArtifact: Artifact):EcsDeployAction => {
return new EcsDeployAction({
actionName: `DeployAction`,
service: this.config.serviceBeta.service,
input: buildArtifact,
});
}
private createCodeBuildProject = (): PipelineProject => {
const buildspec = buildSpecContent;
buildspec.phases.post_build.commands.push(
`printf \'[{"name":"${this.config.serviceName}-container","imageUri":"%s"}]\' $ECR_REPO:latest > imagedefinitions.json`
)
const codeBuildProject = new PipelineProject(this, `${this.config.serviceName}-Codebuild`, {
projectName: `${this.config.serviceName}-Codebuild`,
environment: {
buildImage: LinuxBuildImage.STANDARD_5_0,
privileged: true,
},
environmentVariables: this.getEnvironmentVariables(),
buildSpec: BuildSpec.fromObject(buildspec),
cache: Cache.local(LocalCacheMode.DOCKER_LAYER, LocalCacheMode.CUSTOM),
});
codeBuildProject.role?.addManagedPolicy(
ManagedPolicy.fromAwsManagedPolicyName('AmazonEC2ContainerRegistryPowerUser')
);
return codeBuildProject;
}
private getEnvironmentVariables = () => {
return {
ACCOUNT_ID: {
value: this.account
},
ACCOUNT_REGION: {
value: this.region
},
ECR_REPO: {
value: this.config.ecrRepo.repositoryUri
},
IMAGE_NAME: {
value: this.config.ecrRepo.repositoryName
},
};
}
export default {
version: '0.2',
phases: {
pre_build: {
commands: [
'echo Login to Amazon ECR...',
'aws --version',
'echo $ACCOUNT_ID.dkr.ecr.$ACCOUNT_REGION.amazonaws.com',
'(aws ecr get-login-password --region $ACCOUNT_REGION | docker login --username AWS --password-stdin $ACCOUNT_ID.dkr.ecr.$ACCOUNT_REGION.amazonaws.com)',
'COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)',
'IMAGE_TAG=${COMMIT_HASH:=latest}'
]
},
build: {
commands: [
'echo Build started on `date`',
'./gradlew build',
'echo Building the Docker image : $ECR_REPO, $IMAGE_NAME',
'docker build -t $IMAGE_NAME:latest .',
'docker tag $IMAGE_NAME:latest $ACCOUNT_ID.dkr.ecr.$ACCOUNT_REGION.amazonaws.com/$IMAGE_NAME:latest',
'echo Build completed on `date`'
]
},
post_build: {
commands: [
'echo Build completed on `date`',
'echo Pushing the Docker image...',
'docker push $ACCOUNT_ID.dkr.ecr.$ACCOUNT_REGION.amazonaws.com/$IMAGE_NAME:latest',
'printf \'{"ImageURI":"%s"}\' $ECR_REPO:latest > imageDetail.json',
]
}
},
artifacts: {
files: [
'imageDetail.json',
'imagedefinitions.json',
]
}
};