5. 빌드 및 배포 자동화

지금까지 우리가 직접 ECS 인스턴스에 서비스를 만들고 작업들을 배포했습니다. 수정 사항이 있을 때 같은 과정을 반복하게 됩니다. 이번에는 AWS의 CodeBuild와 CodePipeline을 이용해서 빌드 및 배포 자동화를 구축해보겠습니다.


5.0 메이븐Maven 캐싱용 s3 버킷 만들기

준비해야 할 것이 있습니다. 현재 우리가 배포해야 할 스프링 부트 프로젝트는 메이븐 이라는 도구를 이용해서 의존된 라이브러리를 관리하고 빌드합니다. 관련된 라이브러리 등의 도구들이 매우 사이즈가 큰 편이며 매 빌드 시마다 새로 다운로드하게 되면 시간이 매우 오래 걸립니다.

그래서 캐싱용 저장소를 s3에 만들어 둡니다. S3 웹 콘솔로 이동해서 싱가포르 리전에 새로운 버킷을 하나 만듭니다. ‘{username}-codebuild-cache-store’라는 이름으로 버킷을 만듭니다. 버킷 이름은 AWS 리전 전체에서 중복이 되지 않습니다. 중복에 유의하여 이름을 짓습니다.


그림 5-1



5.1 CodeBuild 만들기

"AWS CodeBuild는 소스 코드를 컴파일하는 단계부터 테스트 실행 후 소프트웨어 패키지를 개발하여 배포하는 단계까지 마칠 수 있는 완전관리형의 지속적 통합 CI 서비스입니다. CodeBuild를 사용하면 자체 빌드 서버를 프로비저닝, 관리 및 확장할 필요가 없습니다. CodeBuild는 지속적으로 확장되며 여러 빌드를 동시에 처리하기 때문에 빌드가 대기열에서 대기하지 않고 바로 처리됩니다."

우리는 CodeBuild를 통해서 깃헙 마스터 브랜치에 푸시가 되면 자동으로 스프링 프로젝트를 빌드하고 도커 이미지로 만든 다음 이미지를 ECR에 푸시하도록 할 것입니다.

우선 CodeBuild를 만들어보겠습니다. 입력할 내용이 많기 때문에 천천히 따라해보시기 바랍니다. 싱가포르 리전의 [CodeBuild 페이지](https://ap-southeast-1.console.aws.amazon.com/codebuild/home?region=ap-southeast-1)로 이동합니다. 프로젝트 만들기 버튼을 클릭하여 프로젝트 구성 화면으로 들어옵니다.

프로젝트 이름은 ‘petclinic-rest-build’라고 입력합니다. 소스 공급자는 GitHub입니다. [내 계정의 리포지토리 사용]을 선택하고 GitHub과 연결합니다. 그 이후 3.1 준비 단계에서 포크fork했던 petclinic-rest를 선택합니다. Webhook 체크 박스를 체크하여 코드 변경이 빌드로 이어질 수 있게끔 연결해줍니다. 모든 코드 변경이 아닌 master 브랜치의 변경만이 빌드로 이어지게끔 필터를 설정합니다. (추후에 CodePipeline을 연결할 때 CodePipeline에서 Webhook을 받기 때문에 그때는 이 설정을 끌 예정입니다.)


그림 5-2



빌드 방법을 설정합니다. 빌드하는 환경을 설정하는데 운영체제는 우분투, 런타임은 자바로 설정합니다. 런타임 버전은 ‘openjdk8’입니다. 이번 빌드가 단순히 자바 패키징이 아닌 도커 이미지를 만드는 것이므로 ‘권한이 있음’에 꼭 체크해야 합니다. CodeBuild도 도커 이미지 내에서 빌드를 하게 되는데 도커 이미지 내에서 도커 이미지를 만들려면 해당 설정이 꼭 필요합니다. 빌드 사양Build Spec은 어떻게 빌드할지 정의하는 방법입니다. 우리는 기본값이 buildspec.yml을 사용하여 정의하였습니다.




그림 5-3



간단하게 현재 프로젝트의 buildspec 코드를 간단히 살펴보겠습니다. 크게 네 가지 부분이 있습니다. 환경변수를 설정하는 env, 빌드 스크립트를 기술하는 phases, 빌드 산출물을 정의하는 artifacts, 그리고 캐시에 대해 설정하는 cache 부분이 있습니다.

그중에서도 중요한 phases 부분은 또 세 가지 부분으로 나누어져 있습니다. 첫 번째는 빌드하기 전에 필요한 것을 준비하는 pre_build 단계입니다. json을 파싱하기 위해서 필요한 도구인 jq를 설치하고 ECR에 푸시하기 위해서 로그인을 합니다. 두 번째는 build 단계입니다. 메이븐을 통해서 프로젝트를 패키징하고 패키징한 내용을 바탕으로 도커 이미지를 만들어 냅니다. 세 번째는 빌드 후처리를 하는 post_build 단계입니다. aws cli를 통해서 ‘petclinic-rest’라는 이름을 가진 리포지토리 정보를 가져와서 jq를 통해서 URI를 추출합니다. 만들어진 도커 이미지를 추출한 URI로 태깅하고 푸시합니다. 그리고 작업 정의에서 ECS container 이름과 이미지 주소를 json 형태의 파일로 만들어서 저장합니다.

재미있는 부분은 스프링 부트에서 정말 편하게 사용하고 있는 maven wrapper를 사용하면 에러가 난다는 것입니다. 그래서 그냥 mvn 명령어로 빌드하였습니다.

version: 0.2
# build image : aws/codebuild/java:openjdk-8
env:
variables:
IMAGE_NAME: "petclinic-rest"
phases:
pre_build:
commands:
- echo "Installing jq..."
- curl -qL -o jq https://stedolan.github.io/jq/download/linux64/jq && chmod +x ./jq
- echo "Logging in to Amazon ECR..."
- DOCKER_LOGIN=`aws ecr get-login --no-include-email`
- ${DOCKER_LOGIN}
build:
commands:
- echo "Build started on `date`"
- echo "Building the Docker image..."
- mvn clean package
- docker build -f src/main/docker/Dockerfile -t petclinic-rest .
post_build:
commands:
- echo "Build completed on `date`"
- echo "Pushing the Docker image..."
- REPOSITORY_URI=`aws ecr describe-repositories --repository-name ${IMAGE_NAME} | ./jq -r ".repositories[0].repositoryUri"`
- docker tag petclinic-rest:latest ${REPOSITORY_URI}:latest
- docker push ${REPOSITORY_URI}:latest
- printf '[{"name":"petclinic-rest-app","imageUri":"%s"}]' ${REPOSITORY_URI}:latest > imagedefinitions.json
artifacts:
files: imagedefinitions.json
cache:
paths:
- '/root/.m2/**/*'


앞서 아티펙트는 정의했지만 현재는 사용하지 않기 때문에 아티팩트 없음을 선택합니다. 그리고 캐시는 직전에 만들어둔 S3 버킷 이름을 선택하고 접두사를 ‘petclinic-rest’라고 입력합니다. 이 버킷은 나중에 다른 프로젝트를 빌드할 때도 접두사만 바꿔주면 재사용할 수 있습니다.


그림 5-4



로그를 CloudWatch에서 보고 싶다면 다음처럼 체크하고 입력해줍니다.


그림 5-5



서비스의 역할은 새로 생성하도록 하고 이름을 기억해둡니다. 만들어진 바로 직후에 역할Role에게 ECR에 대한 권한을 부여해야 하기 때문입니다.


그림 5-6



그러면 이제 CodeBuild를 만들 준비가 완료되었습니다. 계속을 눌러서 검수하고 생성합니다. 이제는 CodeBuild가 가진 역할에 ECR에 대한 접근 권한을 추가해야 합니다. 그렇게 해야만 만들어진 도커 이미지를 ECR에 푸시할 수 있습니다. IAM 콘솔 화면에 들어가서 우측 메뉴에서 역할을 클릭합니다. 검색을 통해서 petclinic-rest-build의 역할을 찾습니다. 역할 이름을 클릭합니다.


그림 5-7



역할에 대한 정보가 나옵니다. 우측 상단에 있는 인라인 정책 추가 버튼을 클릭합니다.


그림 5-8



정책에 대한 JSON 에디터에서 다음의 JSON 코드를 입력합니다. ECR에 대한 권한을 부여하는 코드입니다.


{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": [
                "ecr:CompleteLayerUpload",
                "ecr:DescribeImages",
                "ecr:GetAuthorizationToken",
                "ecr:DescribeRepositories",
                "ecr:UploadLayerPart",
                "ecr:InitiateLayerUpload",
                "ecr:BatchCheckLayerAvailability",
                "ecr:PutImage"
            ],
            "Resource": "*"
        }
    ]
}


정책 검토 화면에서 이름을 ‘codebuild-ecr-policy’라고 입력합니다. 등록을 하게 되면 권한이 부여됩니다.


그림 5-9



이제 빌드에 대한 모든 준비를 마쳤습니다. 빌드를 해보겠습니다. CodeBuild 웹 콘솔로 들어가서 우리가 만든 빌드 프로젝트를 클릭합니다. 두 가지 방식으로 빌드를 할 수 있습니다. 첫 번째는 GitHub에 코드 푸시를 통해서 하는 것입니다. 두 번째는 빌드 프로젝트 화면에서 [빌드 시작] 버튼을 누르는 것입니다. 저는 두 번째 방식으로 빌드를 해보겠습니다.


그림 5-10


빌드 시작 버튼을 누르게 되면 설정 화면이 나오는데 기본값에 대한 수정없이 그냥 빌드를 시작합니다. 빌드 단계 세부 정보의 상태와 빌드 로그를 확인할 수 있습니다. 처음하게 되면 5분 정도 시간이 소요됩니다. 그러나 두 번째부터는 1분 내외로 시간이 소요됩니다.


그림 5-11



ECR 콘솔에서도 잘 반영이 되었는지 확인해봅니다. 다음 단계는 빌드가 되고 나서 수정사항이 자동으로 반영되도록 하는 것입니다.


5.3 CodePipeline 만들기

AWS CodeBuild는 말 그대로 빌드하고 빌드한 결과물을 어딘가에 저장하는 역할을 하고 있습니다. 그 이후 흐름은 CodePipeline이 진행합니다.

"AWS CodePipeline은 빠르고 안정적인 애플리케이션 및 인프라 업데이트를 위해 릴리스 파이프라인을 자동화하는 데 도움이 되는 완전관리형 지속적 전달 서비스입니다. CodePipeline은 코드 변경이 발생할 때마다 사용자가 정의한 릴리스 모델을 기반으로 릴리스 프로세스의 빌드, 테스트 및 배포 단계를 자동화합니다."

우리는 CodePipeline을 통해서 소스가 변경되면 CodeBuild를 통해서 빌드하고 그 이후 ECS 작업을 업데이트하도록 설정할 것입니다. AWS CodePipeline 웹 콘솔 화면으로 들어가서 [시작하기] 버튼을 누릅니다. 만약 만들어진 파이프라인이 있다면 파이프라인 생성 버튼을 누릅니다.


그림 5-12



파이프라인 생성을 시작합니다. 파이프 라인 이름은 ‘petclinic-rest-codepipeline’이라고 입력합니다.


그림 5-13


그다음 소스 공급자를 GitHub으로 지정하고 GitHub에 연결합니다. 만약 미리 연결이 되어있지 않다면 GitHub에 로그인하고 등록하는 과정을 진행합니다.


그림 5-14


GitHub가 연결되었다면 리포지토리와 브랜치 정보를 입력합니다. 이제는 CodePipeline이 Webhook을 사용하여 변경을 감지하게 됩니다.


그림 5-15


빌드 공급자는 AWS CodeBuild를 선택하고 이전 단계에서 만들었던 ‘petclinic-rest-build’ 프로젝트를 입력합니다.


그림 5-16



배포에 대한 설정을 진행합니다. 배포 공급자는 [Amazon ECS]를 선택합니다. ECS의 클러스터 이름은 ‘petclinic-rest-cluster’, 서비스 이름은 ‘petclinic-rest-service’, 이미지 파일 이름은 buildspec.yml에서 정의했던 파일 명인 ‘imagedefinations.json’을 입력합니다. 간략하게 저 파일의 규칙과 역할을 설명하겠습니다. 

printf '[{"name":"petclinic-rest-app","imageUri":"%s"}]' ${REPOSITORY_URI}:latest > imagedefinitions.json


파일은 JSON 배열 내에 ECS 작업 정의에 기술된 컨테이너 이름(name)과 도커 이미지의 주소(imageUri) 정보를 기술합니다. artifact 파일에 컨테이너 명이나 이미지 주소가 잘못 기술되어 있다면 CodePipeline의 배포 단계에서 에러가 발생합니다.


그림 5-17



그다음 현재 만들고 있는 CodePipeline의 역할을 만들어서 입력합니다. [역할 만들기]를 클릭하게 되면 역할을 만드는 화면이 나옵니다. 역할 이름은 자동으로 입력된 값으로 했습니다.


그림 5-18



이 작업을 모두 마치면 코드 파이프라인이 만들어지고 동작을 시작합니다. 전체 모두를 진행하는데 약 10분 정도 소요됩니다.


그림 5-19


그리고 우리는 CodeBuild의 Webhook 설정을 꺼줘야합니다. 코드가 수정되었을 때 빌드를 두 번씩하게 됩니다. 왜냐하면 CodeBuild의 Webhook이 한 번, CodePipeline이 한 번, 이렇게 두 번하기 때문입니다. CodeBuild 웹 콘솔에서 petclinic-rest-build를 선택하고 프로젝트 편집을 클릭합니다. 그리고 빌드 대상에서 Webhook 체크박스를 비워둡니다.


그림 5-20


CodeBuild의 빌드 기록을 확인해보면 제출자, 빌드를 요청하는 주체가 Github Webhook, root, CodePipeline 이렇게 세 가지임을 확인할 수 있습니다. 기존에는 GitHub의 Webhook에 바로 반응했다면 이제는 Codpipeline에서 빌드하게 됩니다.


그림 5-21



문제가 없다면 아래처럼 CodePipeline이 정상적으로 동작하는 것을 확인할 수 있습니다.


그림 5-22



5.4 비정상인 소스 수정

이번에는 비정상적으로 소스를 수정했을 경우에 CodePipeline이 어떻게 동작하는지 확인해보겠습니다. 비정상적인 소스 수정은 여러 가지가 있겠지만 이번에는 ‘작업 정의와 맞지 않은 아티펙트 작성’과 ‘테스트 실패'에 대해서 다뤄보겠습니다.

우선 작업 정의와 맞지 않은 아티펙트 작성입니다.

Cloud9에서 petclinic-rest > buildspec.yml 파일을 엽니다. 30번째 라인에서 ‘petclinic-rest-app’을 ‘petclinic-rest’로 바꿔서 푸시해봅시다.


그림 5-23

git add buildspec.yml 
git commit -m "Test wrong ecs container name"
git push -u origin master


동작 중에는 커밋 메시지가 보입니다. 코드 반영이 시작 되고 몇 분 뒤에 Staging 단계에서 실패하는 것을 확인할 수 있습니다. 세부 정보를 클릭하면 구성이 못되어 있다는 메시지를 확인할 수 있습니다. 코드를 원상 복구하고 다시 확인해봅니다.


그림 5-24


그림 5-25


그림 5-26



두 번째로 테스트가 실패하면 어떻게 동작하는지 확인해보겠습니다. Cloud9에서 petclinic-rest → src → test → java → vw.demo.petclinic → BlockingDeployTests.java 파일을 엽니다. 주석 처리된 8~11번째 라인을 주석 해제합니다. 강제로 테스트가 실패하게 하는 코드입니다.


그림 5-27

git add src/test/java/vw/demo/petclinic/BlockingDeployTests.java
git commit -m "Test fail test code"
git push -u origin master


푸시와 함께 CodePipeline이 동작합니다. 이번에는 빌드 중에 실패했습니다. 기본적으로 메이븐으로 빌드할 때 테스트 코드를 모두 실행해보고 빌드하는데 중간에 테스트 코드가 실패하면 메이븐 빌드도 실패하기 때문입니다. CodeBuild에서도 메이븐 빌드가 실패하였으므로 전체 빌드가 실패하는 것입니다.


그림 5-28



다시 실패하는 테스트 코드를 주석 처리 혹은 삭제하고 푸시합니다. 그리고 정상 동작하는 지 확인합니다.


그림 5-29



5.5 정상적인 소스 수정

이번에는 정상적으로 코드를 수정하고 반영해보도록 하겠습니다. cloud9에서 petclinic-rest → src → main → java → vw.demo.petclinic → interfaces → tests → UpdateTestController.java 파일을 열어봅니다.

이번에는 "동물병원 서비스 코드 파이프라인 업데이트 테스트."라고 문구를 수정해 보겠습니다. 수정을 마치고 코드를 푸시합니다.


그림 5-30

git add src/main/java/vw/demo/petclinic/interfaces/tests/UpdateTestController.java
git commit -m "Test Codepipeline update"
git push origin master


정상적으로 파이프라인이 동작했습니다.


그림 5-31


브라우저를 통해서 반영이 되었는지 확인해봅시다.


그림 5-32



지금까지의 작업으로 ECS 인프라를 클러스터를 구성하고 작업을 정의한 다음, 서비스를 생성하여 서비스를 통해서 작업을 배포하도록 설정했습니다. 또한 CodeBuild와 CodePipeline을 통해서 배포에 대해서도 자동화를 마쳤습니다. 다음에는 서비스 운영을 위한 모니터링과 오토스케일링에 대해서 다뤄보겠습니다.