[Hands On] CI/CD – Jenkins pipeline을 이용한 ECS 배포

이번 포스팅은 CI/CD의 기본적인 구조인, Jenkins pipeline을 이용해 ECS에 배포하는 Hands On을 진행 하겠습니다.

0. 개요

<CI/CD 란?>

CI/CD란, 지속적인 통합, 지속적인 배포라는 개념의 애플리케이션 개발 단계를 자동화하여 애플리케이션을 보다 더 짧은 주기로 고객에게 제공하는 방법입니다.
디지털 트랜스포메이션이 각광 받기 시작하면서, 자연스럽게 CI/CD가 떠오르기 시작했습니다.

Jenkins 등의 CI 툴과 컨테이너 기반 애플리케이션을 관리하는 Amazon ECS를 함께 사용한다면 효과적인 CI/CD 구현이 가능합니다.

Jenkins는 소프트웨어 개발 시 지속적인 통합 서비스를 제공하는 툴 입니다.
다수의 개발자들이 하나의 프로그램을 개발할 때 버전 충돌을 방지하기 위해 각자 작업한 내용을 Git, CodeCommit 등의 소스 저장소에 업로드함으로써 지속적 통합이 가능하도록 해줍니다.

이 Hands On을 따라오시면,
CI/CD 구성의 기본적인 구조인 Jenkins pipeline을 이용해
Node.JS 프로젝트를 ECS에 배포할 수 있습니다.

[Hands On 실습 환경 구성]
Hands On은 아래와 같은 실습 환경을 바탕으로 진행됩니다.

[동작 과정]

Hands On의 동작 과정은 아래와 같습니다.

1. 개발자가 Git Push를 통해 CodeCommit 레포지토리에 소스 코드를 올립니다.
2. Jenkins는 CodeCommit Git Credentials를 이용해 소스 코드를 Clone 합니다.
3. Jenkins는 소스 코드를 Docker Build 후, 빌드한 이미지를 ECR에 Upload 합니다.
4. Jenkins는 AWS CLI인 update-service 명령어를 이용해 새 배포를 수행합니다.
5. ECS는 서비스 업데이트를 통해 Fargate로 배포합니다.

[Hands On 순서]
Hands On의 Pipeline 배포 과정은 다음과 같은 순서로 이루어 집니다.

  1. Dockerfile 생성
  2. CodeCommit에 소스 코드 Push
  3. Jenkins 환경변수 등록
  4. Jenkins용 IAM Role 생성
  5. Jenkins Pipeline 설정

참고 : ALB, ECR, ECS 구축은 이 글에서 다루지 않습니다.
문의 사항이나 기술 지원이 필요하시면,
NDS Sales팀으로 연락 주시길 바랍니다. 
cloud.sales@nongshim.co.kr

Hands On에서는 원활한 테스트를 위해 Express 프레임워크를 사용해 Node.JS 프로젝트를 생성하였습니다.
해당 프로젝트를 Jenkins를 통해 ECS에 배포하고자 합니다.


* 참고 : Express 프로젝트 생성하는 법

  1. Git Bash나 IDE의 터미널에서 수행합니다.
  2. 프로젝트를 만들 위치로 이동
  3. npm install -g express-generator
  4. express {프로젝트 명}

1. Dockerfile 생성

<Dockerfile이란?>

Dockerfile은 컨테이너에 설치해야 하는 패키지, 소스 코드, 명령어, 환경 변수 설정 등을 기록한 하나의 파일이며, Docker를 build하기 위한 파일입니다. 그리고 이를 빌드 하면 자동으로 이미지가 생성됩니다.

Node.JS 프로젝트의 루트 경로에 아래의 Dockerfile을 생성해주세요.


Hands On 샘플 프로젝트의 빌드에 사용될 Dockerfile 내용은 아래와 같습니다.

  • node:10.13 베이스 이미지를 사용
  • /home 디렉토리에 Node.JS 프로젝트를 복사
  • Asia/Seoul 타임존을 사용
  • package.json에 있는 패키지를 설치
  • 컨테이너가 실행될 때마다, npm run start 명령어를 사용해 배포
FROM node:10.13-alpine

WORKDIR /home
COPY . .
ENV TZ Asia/Seoul

RUN npm install
CMD npm run start
Bash


* 참고 : Dockerfile 문법

  • FROM : 생성할 이미지의 베이스가 될 이미지를 뜻합니다. 반드시 한번 이상 입력해야 합니다.
  • COPY : 파일이나 디렉토리를 이미지로 복사합니다. COPY <src> … <dest>
  • RUN : 이미지를 만들기 위해 컨테이너 내부에서 명령어를 실행합니다.
  • ADD : 파일을 이미지에 추가합니다. COPY 명령어와 매우 유사하나, src에 파일 대신 URL을 입력할 수 있고, src에 압축 파일을 입력하는 경우 자동으로 압축을 해제하면서 복사됩니다. ADD <src> … <dest>
  • WORKDIR : RUN, CMD, ADD, COPY 등의 명령어를 실행할 디렉토리. 배시 셸에서의 cd 명령어와 동일한 기능을 합니다.
  • EXPOSE: 이미지에서 노출할 포트를 설정합니다. 여러 개 포트도 가능. EXPOSE <port> [<port>…]
  • CMD : 컨테이너가 시작될 때마다 실행할 명령어. Dockerfile에서 한번만 사용할 수 있습니다. 그래서, 여러 개의 CMD가 존재할 경우 가장 마지막 CMD만 실행됩니다.
  • ENV : 컨테이너에서 사용할 환경 변수 지정. ENV <key> <value>

2. CodeCommit에 소스 코드 Push

<CodeCommit이란?>
AWS에서 제공하는 프라이빗 리포지토리를 호스팅하는 안전하고 확장 가능한 소스 관리형 서비스 입니다.

1) CodeCommit에 리포지토리를 생성합니다.

CodeCommit – 리포지토리 생성하는 법

2) CodeCommit 권한을 가지는 IAM 사용자를 생성합니다.

3) IAM 사용자 – 보안 자격 증명 – AWS CodeCommit에 대한 HTTPS Git 자격 증명을 생성합니다.

4) CodeCommit 레포지토리에 인증 후, 로컬 PC의 소스 코드를 Push 합니다.
git clone {레포지토리 HTTPS URL}을 입력하면 Git Credential Manager 창이 나타납니다.
이때, 다운로드 받은 Git Credential의 User Name과 Password를 입력합니다.
CodeCommit 레포지토리에 대한 인증이 완료되면, git add / git commit / git push를 이용해 소스코드를 push 합니다.

5) git push 결과 확인

3. Jenkins 환경 변수 등록

Jenkins 또한 CodeCommit 레포지토리에 인증을 받아, Clone 받을 수 있도록 해야 합니다.
Jenkins에서 환경 변수로 CodeCommit Git Credential을 등록합니다.

4. Jenkins용 IAM Role 생성

<외부 시스템에서 AWS 리소스에 접근하려면 어떻게 해야 할까?>

기본적으로 외부 시스템에서 AWS 리소스에 접근하기 위해서는 필연적으로 AWS IAM(AWS Identity and Access Management) 으로 부터 권한을 받아야 합니다.
즉, Jenkins가 AWS 리소스에 접근할 수 있는 방법은 크게 두 가지로 분류됩니다.

  • 장기 보안 자격 증명(Access Key, secret access key)
  • 임시 보안 자격 증명(Assume Role 방식)

장기 보안 자격 증명(Access Key, Secret Key)은 비활성화나 삭제를 하지 않는 이상, 장기적으로 사용할 수 있는 자격 증명이며, 파이프라인 코드 내부에 자격 증명을 매개 변수로 직접 전달하여 사용합니다. 그렇기 때문에, 이 방식은 젠킨스의 자격 증명 Key값이 직접적으로 노출되므로 권장하지 않습니다.

임시 보안 자격 증명(Assume Role 방식)은 그 이름이 암시하듯 단기적입니다. 이 자격 증명은 몇 분에서 몇 시간까지 지속되도록 구성할 수 있습니다. 자격 증명이 만료된 후 AWS는 더는 그 자격 증명을 인식하지 못하거나 그 자격 증명을 사용한 API 요청으로부터 이루어지는 어떤 종류의 액세스도 허용하지 않기 때문에 보안상 권장하는 방법입니다.

이에, Hands On에서는 임시 보안 자격 증명(Assume Role) 방식으로 Jenkins에 IAM Role을 부여할 것입니다.

IAM Role을 생성하겠습니다.
아래의 사진과 같이 3개의 정책을 생성해 연결합니다.

[least-ecs-policy]
ecs:UpdateService : ECS의 클러스터의 서비스 업데이트 시 사용합니다.
ecs:Describe* : 클러스터, 서비스, 작업에 대한 상세 조회에 사용합니다.
ecs:List* : 모든 클러스터, 서비스, 작업 등을 조회할 때 사용합니다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "application-autoscaling:Describe*",
                "application-autoscaling:PutScalingPolicy",
                "application-autoscaling:DeleteScalingPolicy",
                "application-autoscaling:RegisterScalableTarget",
                "cloudwatch:DescribeAlarms",
                "cloudwatch:PutMetricAlarm",
                "ecs:List*",
                "ecs:ExecuteCommand",
                "ecs:Describe*",
                "ecs:UpdateService",							 
                "iam:PassRole",
                "iam:AttachRolePolicy",
                "iam:CreateRole",
                "iam:GetPolicy",
                "iam:GetPolicyVersion",
                "iam:GetRole",
                "iam:ListAttachedRolePolicies",
                "iam:ListRoles",
                "iam:ListGroups",
                "iam:ListUsers"
            ],
            "Resource": [
                "*"
            ]
        }
    ]
}
JavaScript

[STS-assume-policy]
AWS Security Token Service(AWS STS)를 사용하면 AWS 리소스에 대한 액세스를 제어할 수 있는 임시 보안 자격 증명을 생성하여 신뢰받는 사용자에게 제공할 수 있습니다. 이에, STS 정책도 추가합니다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "VisualEditor0",
            "Effect": "Allow",
            "Action": "sts:AssumeRole",
            "Resource": "*"
        }
    ]
}
JavaScript

[ecr-upload-policy]
ecr:GetAuthorizationToken : 레지스트리 인증에 사용합니다.
ecr:BatchCheckLayerAvailability : 저장소에서 하나 이상의 이미지 레이어의 가용성을 확인하는데 사용합니다.
ecr:PutImage: 이미지를 업로드 하는데 사용합니다.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ecr:GetAuthorizationToken",
                "ecr:BatchCheckLayerAvailability",
                "ecr:PutImage",
                "logs:CreateLogStream",
                "logs:PutLogEvents"
            ],
            "Resource": "*"
        }
    ]
JavaScript

5. Jenkins Pipeline 설정

1) Jenkins 플러그인 설치(Pipeline: AWS Steps)

Assume Role을 이용해 Jenkins 파이프라인 구성을 하기 위해서는,
아래의 플러그인이 설치되어야 합니다.

2) Jenkins에서 새로운 Item을 생성합니다.

3) CodeCommit Git Credential이 등록되어 있는 환경 변수를 미리 등록합니다.

4) Pipeline script 칸에, 파이프라인 코드를 넣어줍니다.

아래 코드는 간단한 예시이므로, 참고 부탁 드립니다.

pipeline {
    agent any
    stages {
        stage('Git Clone') {
            steps {
                script {
                    try {
                        git url: "{CodeCommit 리포지토리 HTTPS URL}", branch: "master", credentialsId: "$GIT_CREDENTIALS_ID"
                        sh "sudo rm -rf ./.git"
                        env.cloneResult=true
                    } catch (error) {
                        print(error)
                        env.cloneResult=false
                        currentBuild.result = 'FAILURE'
                    }
                }
            }
        }
        stage('ECR Upload') {
            steps{
                script{
                    try {                       
                        withAWS(role: '{Jenkins용 IAM Role 이름}', roleAccount: '{AWS 계정 번호}', externalId:'externalId') {
                            sh 'aws ecr get-login-password --region ap-northeast-2 | docker login --username AWS --password-stdin {ECR 리포지토리 URI}'
                            sh 'docker build -t nodejs .'
                            sh 'docker tag nodejs:latest {ECR 리포지토리 URI}/nodejs:latest'
                            sh 'docker push {ECR 리포지토리 URI}/nodejs:latest'
                        }
                    } catch (error) {
                        print(error)
                        echo 'Remove Deploy Files'
                        sh "sudo rm -rf /var/lib/jenkins/workspace/${env.JOB_NAME}/*"
                        currentBuild.result = 'FAILURE'
                    }
                }
            }
            post {
                success {
                    echo "The ECR Upload stage successfully."
                }
                failure {
                    echo "The ECR Upload stage failed."
                }
            }
        }
        stage('Deploy'){
            steps {
                script{
                    try {
                        withAWS(role: '{IAM Role 이름}', roleAccount: '{AWS 계정 번호}', externalId:'externalId') {
                            sh"""
                                aws ecs update-service --region ap-northeast-2 --cluster {ECS 클러스터 이름} --service {ECS 서비스 이름} --force-new-deployment
                            """
                        }
                        
                    } catch (error) {
                        print(error)
                        echo 'Remove Deploy Files'
                        sh "sudo rm -rf /var/lib/jenkins/workspace/${env.JOB_NAME}/*"
                        currentBuild.result = 'FAILURE'
                    }
                }
            }
            post {
                success {
                    echo "The deploy stage successfully."
                }
                failure {
                    echo "The deploy stage failed."
                }
            }
        }
    }
}
JavaScript

6. Final Result

1) Build with Parameters를 클릭해, 파이프라인 코드를 작동 시킵니다.
아래 사진과 같이, 모든 단계가 오류 없이 작동해야 합니다.
오류가 발생한다면, Console Output에서 로그를 확인해 확인합니다.

2) ECS에 배포되는 작업을 확인합니다.
기존에 1개로 유지되었던 작업이, 2개로 늘어난 것을 확인할 수 있습니다.

3) ELB 타켓 그룹에 기존 Fargate “draining”을 확인합니다.
기존에 배포되었던 컨테이너가 draining 상태가 되는 것을 확인할 수 있습니다.
시간이 지나면 새로 배포된 컨테이너 1대만 남아있게 됩니다.

4) ALB DNS Name을 호출해 배포가 잘 되었는지 확인합니다.


참고 자료
https://docs.aws.amazon.com/ko_kr/AmazonECS/latest/developerguide/Welcome.html
https://docs.aws.amazon.com/STS/latest/APIReference/API_AssumeRole.html
https://docs.aws.amazon.com/ko_kr/IAM/latest/UserGuide/id_roles_create_for-user.html
https://ko.wikipedia.org/wiki/%EC%A0%A0%ED%82%A8%EC%8A%A4_(%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4)


결론

이번 Hands On을 통해 Jenkins pipeline, ECS를 이용한 CI/CD를 구현을 할 수 있었습니다.

Amazon ECS는 클러스터에서 컨테이너를 쉽게 실행, 중지 및 관리할 수 있게 해주는 컨테이너 관리 서비스 입니다.

Jenkins 등의 CI 툴과 컨테이너 기반 애플리케이션을 관리하는 Amazon ECS를 함께 사용한다면 더욱 효과적인 CI/CD 구현이 가능합니다.

이상으로 Hands On을 마치겠습니다.

AWS 서비스에 관해 문의 사항이나 기술 지원이 필요하시면,
NDS Sales팀으로 연락 주시길 바랍니다. 

cloud.sales@nongshim.co.kr

SA 김민지