CodePipeline로 ECS 애플리케이션 배포하기

글쓴이 상배 윤 날짜

AWS의 CodePipeline은 CI(Continuous Intergration – 지속적인 통합)과 CD(Continuous Delivery – 지속적인 배포) 환경을 구축 할 수 있도록 도와주는 서비스다. CodePipeline의 장점을 살리면서 효과적으로 도입하기 위해서는 결국, CI/CD가 무엇인지, 왜 도입하는 지, 어떤 효과를 얻을 수 있는지에 대한 이해가 필요하다.

클라우드를 전통적인 관점에서 접근할 경우 위의 프로세스를 따르게 될 것이다. 개발자와 운영자가 클라우드 관리자에게 요청을 하고 클라우드 관리자가 요청서를 읽어서 자원을 전개한다. 이때 개발자와 운영자는 클라우드 관리자와 함께, 서비스의 목적, 규모, 배포시간/배포방법, 서비스규모(용량산정), 서버와 애플리케이션의 설정에 대해서 논의를 해야 한다. 많은 시간이 소모될 것이다. 속도 보다는 제어(관리)를 중요하게 생각하는 접근 방식이다.

이 방식은 근본적으로 클라우드와는 맞지 않다. 클라우드라는게 예측할 수 없는 시장과 기술의 변화에 기민하게 대응 함으로써, 시장에 대한 빠른 반응 그리고 적응성을 높여서 성공하는 것을 목표로 만들어진 속도를 강조하는 플랫폼이다. 클라우드에서는 아래와 같은 방식을 더 선호한다.

개발자와 운영자는 클라우드 관리자를 거치지 않고, 직접 애플리케이션을 배포한다. 클라우드 관리자는 프로세스를 포함한 정책을 정의하고, 이 정책을 수행 할 수 있는 시스템을 구축한다. 결국 개발자는 클라우드 관리자의 개입 없이 애플리케이션을 배포 할 수 있게 된다. 클라우드 관리자는 애플리케이션이 사전에 정의한 정책에 따라서 수행하고 있는지를 모니터링, 레포팅하고, 서비스의 품질을 혁신하기 위한 더 나은 방법을 제안한다.

  • 정책 : 약속한 정책에 따라서 애플리케이션을 배포 했는지를 검토한다. 이 정책에는 개발,테스트,품질측정,배포의 과정, 로깅과 모니터링을 위한 장치, 보안, 백업/복구, 장애가 발생했을 때의 대응 프로세스 등이 포함돼 있다. 클라우드 관리자는 개발자와 함께 정책을 만들고 정책을 수행하기 위한 시스템, 솔류션, 툴들을 도입하고 개발한다.
  • 빌링 : 적절한 자원을 사용하고 있는지 검토한다. 적절하지 않다면, 개발자/운영자와 논의해서 자원을 효율적으로 사용 할 수 있도록 가이드 한다.
  • 가시화 : 클라우드 관리자, 개발자, 운영자는 배포된 애플리케이션의 형상, 배포 및 운영 상태, 모니터링, 비용, 보안, 권한 등이 실시간으로 보고할 수 있어야 한다. 이들 정보로 참여자들은 논의한 정책들이 효과적으로 적용되고 있는지를 확인 할 수 있다.

CI/CD

CI/CD는 개발자,운영자,클라우드 관리자가 우선적으로 구성해야 하는 핵심영역이다. 이 과정에 클라우드 네이티브한 환경을 만들기 위한 주요 요소들이 담기기 때문이다.

  • 버전을 포함한 코드의 형상은 어떻게 관리 할 것인가.
  • 개발, 테스트, 스테이징, 프러덕트 전개 까지의 흐름을 어떻게 만들 것인가.
  • 테스트는 어떻게 수행하며, 그 결과를 측정할 것 인가
  • 전개된 애플리케이션의 품질은 어떻게 측정 할 것 인가
  • 전개된 애플리케이션의 품질은 어떻게 관리 할 것인가
  • 자원을 효율적으로 사용하기 위한 아키텍처가 적용된 플랫폼의 제공
  • 안전하고 빠른, 소프트웨어의 개발 방법

DevOps 문화(혹은 조직)을 사업에 적용하기를 원한다면, 우선 CI/CD 부터 시작하는게 좋다.

CodePipeline

CodePipeline는 애플리케이션의 개발, 빌드, 테스트, 스테이징, 프로덕션까지의 과정을 통합 및 자동화를 지원하는 AWS의 CI/CD 서비스다. 이 문서에서는 CodePipeline를 이용 GitHub에 저장된 Go언어 기반의 예제 코드를 프러덕션까지 배포해 볼 것이다. 애플리케이션 구성은 아래와 같다.

애플리케이션은 AWS Fargate 기반으로 배포한다. 앞단에는 ELB(Application Load Balancer)가 놓인다. 내가 원하는 구성은 아래와 같다. 소스코드는 GitHub에서 관리한다. 컨테이너 이미지는 Amazon ECR에서 관리를 한다. 내가 생각하는 최소한의 CI/CD 프로세스는 아래와 같다.

  1. 소스코드를 개발해서 GitHub에 Push 한다.
  2. (거의) 실시간으로 빌드 과정이 실행된다. 빌드 툴(Jenkins 혹은 AWS CodeBuild)은 GhubHub에서 최신의 소스코드를 읽어서, 빌드한다.
  3. 빌드과정에는 도커라이징이 포함된다. 빌드가 성공하면 도커 이미지를 만들어서 ECR에 등록 한다.
  4. 성공적으로 빌드가 끝나면, 즉 성공적으로 빌드된 애플리케이션이 ECR에 올라가면, 배포과정이 수행된다. 배포툴(Jenkins 혹은 AWS CodeDeploy)는 소프트웨어 전개 정의서를 읽어서(AWS CloudFormation) ELB, 오토스케일링 그룹, 타겟그룹(Trget Group)을 설정 애플리케이션이 작동하게 한다. ECS Fargate의 경우 Fargate 서비스 구성에 타겟그룹, 오토스케일링 그룹, 블루그린 배포 등에 대한 정보가 포함되므로 클라우드포메이션 구성은 필요 없을 것이다.

예제 코드

테스트에 사용할 Hello world 코드, 형상관리를 위한 Git 저장소, 도커라이징을 위한 Dockerfile, 도커 이미지를 저장하기 위한 ECR(Elastic Container Registry)를 준비해야 한다.

테스트에 사용할 Hello world 코드는 아래와 같다. GET /user/{username} 을 호출하면 “Hello username!”을 출력하는 간단한 프로그램이다.

package main

import (
    "fmt"
    "github.com/gorilla/mux"
    "log"
    "net/http"
)

func h_hello(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    name := vars["name"]
    fmt.Fprintf(w, "Hello %s!", name)
}

func main() {
    r := mux.NewRouter()
    r.HandleFunc("/user/{name}", h_hello)
    log.Fatal(http.ListenAndServe(":8080", r))
}

코드 빌드를 위한 Makefile 이다.

build:
    CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o user .
docker:
    docker build -t joinc/user:0.1 -t joinc/user:latest . 

아래는 Dockerfile 이다.

From scratch
ADD  user /
CMD  ["/user"]

Git 저장소로는 BitBucket을 사용했다.

개발 & 배포 프로세스

개발에서 배포까지의 프로세스를 정의해보자. 정의한 프로세스를 AWS에서 제공하는 CodePipeline으로 구축한다.

  1. Local PC : 개발 PC에서, 코드를 개발하고 테스트한다. 개발이 끝나면 GitHub에 Push 한다.
  2. GitHub에 새로운 코드가 Push 되면, Build Server는 GitHub로 부터 코드를 Pull 해서 빌드한다. 테스트까지 성공적으로 끝나면, Dockerizing을 해서 Docker Registry에 PUSH 한다. Jenkins와 같은 CI툴을 사용한다.
  3. Docker Registry에 새로운 버전의 이미지가 등록되면, 이 이미지를 읽어서 새로운 프로세스를 실행한다.

실제는 로컬 개발 -> 테스트 환경 배포 -> QA 혹은 스테이징 환경 배포 -> 프러덕트 환경 배포 로 이어지는 과정을 거쳐야 한다. 또한 테스트와 빌드 결과의 리포팅, API 문서의 생성, Blue/Green 배포/롤링 업데이트 까지 이어지는 복잡한 프로세스를 만들어야 할 것이다.

AWS CodePipeline

Fargate

AWS ECS Fargate을 기반으로 한다. AWS ECS Fargate를 이용한 애플리케이션 개발은 AWS Fargate를 이용한 API Service 개발 문서를 참고하자. 이미 Fargate와 ELB(Application Load Balancer)를 이용해서 Fargate 서비스를 배포 했다고 가정하고 진행 한다. 아래의 joinc-user-srv 를 CodePipeline을 이용해서 자동화 한다.

Pipeline 구축

AWS 웹 콘솔에서 CodePipelne 대시보드에 접근한다.

Create pipeline으로 새로운 파이프라인을 만든다.

  • pipeline name : 파이프라인 이름
  • Service role : Role name을 가지는 서비스 롤을 만든다. 여기에는 Cloudformation, elasticbeanstalk, codecommit, codedeploy 등, 배포까지의 과정을 자동화 하기 위해서 필요한 정책들이 들어가 있다. 알아서 다 넣어주니, 그냥 새로 만들거나 이전에 만들었던 롤(Existing service role)을 그대로 사용하면 된다.
  • Artifact store : Artifact 파일을 저장하기 위한 저장공간. Artifact 파일에는 CodePipeline으로 처리할 작업에 대한 정보들이 담겨있다. 기본위치는 S3다. Default location을 선택하면, CodePipeline에서 알아서 S3 버킷을 만들어서 Artifact 파일을 관리해준다.

기본설정을 하고 나면, 빌드 단계로 넘어간다.

Source provider : 소스를 가져올 장소다. CodeCommit, ECR, S3, GitHub 중에서 선택 할 수 있다. 테스트 코드가 GitHub에 있기 때문에 GitHub를 선택했다.

AWS CodePipeline는 GitHub와 완전히 통합된다. Connect to GitHub로 GitHub 인증을 거치면, 접근 할 수 있는 Repository 목록이 나온다. 앞서 만든 go-hello 저장소를 선택했다.

Change detection options로 GitHub 변경사항이 반영되도록 할 수 있다. GitHub webhooks를 이용하면, GitHub 변경사항을 자동으로 받아서 codepipeline을 시작하게 할 수 있다. AWS CodePipeline를 이용해서 주기적으로 GitHub 내용을 읽어서 빌드하도록 할 수도 있다

다음 Add build stage로 넘어간다. 앞에서 설정한 Source provider로 부터 코드를 읽어서 빌드하는 일을 한다.

  • Build provider : 빌드에 사용 할 툴을 선택한다. AWS CodeBuild와 Jenkins 중 하나를 선택 할 수 있다. 나는 AWS CodeBuild를 선택했다.
  • Project name : AWS Cobuild를 위한 프로젝트 빌드 설정을 할 수 있다. Create project를 클릭하면 아래와 같은 프로젝트 설정 페이지가 뜬다.
  • Project name : 프로젝트 이름
  • Environment : AWS CodeBuild를 실행하기 위한 이미지 환경을 설정한다. Managed image 를 선택했다.
  • Operation system : 빌드할 운영체제를 선택한다. Ubuntu를 선택했다.
  • Runtime : 애플리케이션 런타임이다. .NET, Android, Java, Node.js, PHP, Python, Go 등 다양한 런타임이 준비돼 있다. 나는 Go 를 선택했다.
  • Runtime version : 애플리케이션 런타임의 버전을 선택한다. aws/codebuid/golang:1.11 을 선택했다.
  • Privileged : CodeBuild는 도커 컨테이너 형태로 실행이 된다. 여기에서 빌드한 go 애플리케이션은 도커 이미지로 도커라이징을 할 계획이다. 도커에서 도커 이미지를 만들기 위해서는 특수 권한(Privileged)를 가질 필요가 있으므로 check를 했다.
  • Service role : 코드빌드를 위한 서비스이름을 만든다.
  • Buildspec : codebuild는 buildspec.yml 파일에 있는 내용을 읽어서 빌드를 수행한다. 나는 아래와 같은 buildspec.yml 파일을 만들었다. 이 파일을 소스코드와 함께 배포하면 된다.

version: 0.2

phases:
  pre_build:
    commands:
      - echo Logging in to Amazon ECR...
      - go get github.com/gorilla/mux 
      - aws --version
      - $(aws ecr get-login --no-include-email --region ap-northeast-2)
      - REPOSITORY_URI=522373083963.dkr.ecr.ap-northeast-2.amazonaws.com/joinc/user
      - COMMIT_HASH=$(echo $CODEBUILD_RESOLVED_SOURCE_VERSION | cut -c 1-7)
      - IMAGE_TAG=${COMMIT_HASH:=latest}
  build:
    commands:
      - echo Source code compile... 
      - make build
      - echo Build started on `date`
      - echo Building the Docker image...          
      - docker build -t $REPOSITORY_URI:latest .
      - docker tag $REPOSITORY_URI:latest $REPOSITORY_URI:$IMAGE_TAG
  post_build:
    commands:
      - echo Build completed on `date`
      - echo Pushing the Docker images...
      - docker push $REPOSITORY_URI:latest
      - docker push $REPOSITORY_URI:$IMAGE_TAG
      - echo Writing image definitions file...
      - printf '[{"name":"joinc-user-continer","imageUri":"%s"}]' $REPOSITORY_URI:$IMAGE_TAG > imagedefinitions.json
artifacts:
files: imagedefinitions.json

pre_build 영역에 빌드하기전 필요한 명령들을 기술 하면 된다. Go 코드 빌드를 위해서 필요한 패키지를 다운로드하고, 빌드한 코드를 ECR에 올리기 위해서 ECR Login 명령을 입력했다.

build 영역에 빌드 명령을 수행하면 된다. make 를 이용해서 빌드를 한후 도커 이미지를 만들도록 했다.

post_build : build 에서 만들어진 도커이미지를 ECR에 올리도록 했다

디플로이 설정을 한다. ECS Fargate로 전개하기 위한 설정을 한다.

  • Deploy provider : 어떤 방법으로 애플리케이션을 전개 할건지 선택한다. ECS를 선택했다.
  • Cluster name : 애플리케이션을 전개할 ECS Cluster 를 선택한다.
  • Service name : ECS Cluster에 올릴 서비스 이름을 선택한다.

설정을 다 끝내고 Next 버튼을 누르면 Review 단계로 넘어간다. Review 페이지에서 Create pipeline 버턴을 클릭하면, 빌드가 시작된다

Source는 잘 가져왔는데, 빌드 단계에서 실패했다. Details를 클릭해서 원인을 찾기로 했다.

// 앞 부분 생략
[Container] 2019/02/16 16:53:53 Running command $(aws ecr get-login --no-include-email --region ap-northeast-2) 
 
An error occurred (AccessDeniedException) when calling the GetAuthorizationToken operation: User: arn:aws:sts::522373083963:assumed-role/codebuild-joinc-user-app-service-role/AWSCodeBuild-75eb1f58-b7d3-4de8-8d7e-0a91c512cdea is not authorized to perform: ecr:GetAuthorizationToken on resource: * 
 
[Container] 2019/02/16 16:53:54 Command did not exit successfully $(aws ecr get-login --no-include-email --region ap-northeast-2) exit status 255 
[Container] 2019/02/16 16:53:54 Phase complete: PRE_BUILD Success: false 
[Container] 2019/02/16 16:53:54 Phase context status code: COMMAND_EXECUTION_ERROR Message: Error while executing command: $(aws ecr get-login --no-include-email --region ap-northeast-2). Reason: exit status 255 

Details에서 상세 빌드 과정을 확인 할 수 있다. ECR 로그인 과정에서 GetAuthorizationToken 을 수행하지 못해서 실패했다. codebuild-joinc-user-app-service-role 에 ECR 관련 권한을 넣어주지 않았기 때문이다. IAM 으로 이동해서 codebuild-joinc-user-app-service-role에 AmazonEC2ContainerRegistryFullAccess 정책을 추가하고 다시 빌드를 수행했다.

Deploy까지 성공했다. Application Load Balancer로의 접근을 테스트해봤다.

# curl joinc-api-108413602.ap-northeast-2.elb.amazonaws.com/user/yundream
Hello yundream!

ECS 대시보드에서 방금 실행한 서비스가 성공적으로 실행된 걸 확인 할 수 있다.

소스코드를 아래와 같이 변경하고 github에 push 했다.

func h_hello(w http.ResponseWriter, r *http.Request) {
    vars := mux.Vars(r)
    name := vars["name"]
    fmt.Fprintf(w, "Hello %s!. How are you", name)
}

Push를 하면 github webhook에 의해서 codepipeline 빌드 프로세스가 즉시 시작되고, 대략 5분 후에 새로운 버전의 애플리케이션 배포가 끝났다.

정리

CodePipeline을 이용해서 CICD를 구성해봤다. 앞으로 아래와 같은 일을 해야 한다.

  1. 테스트 코드를 포함해서, 빌드 단위에서 애플리케이션의 테스트를 수행해야 한다.
  2. 위 예제는 롤링 업데이트 방식으로 애플리케이션을 배포한다. ECS Fargate는 블루/그린 배포를 지원하며, CodePipeline에 적용 할 수 있다. 블루/그린 배포방식을 테스트해보자. 이 문서에 있는 내용을 수행했다면 간단하게 블루/그린 배포를 적용 할 수 있을 것이다.
  3. 실 서비스는 개발 -> 테스트 -> QA 혹은 스테이징 -> 배포의 단계를 거칠 것이다. 서비스에 맞는 배포 프로세스를 설계 해야 한다.

댓글 남기기

이메일은 공개되지 않습니다. 필수 입력창은 * 로 표시되어 있습니다

Bitnami