젠킨스(Jenkins) 파이프라인 서비스 배포 on EC2 인스턴스

5 분 소요


👉 해당 포스트를 읽는데 도움을 줍니다.

👉 이어서 읽기를 추천합니다.

0. 들어가면서

GitHub 웹훅(webhook)으로 코드 변경 사항을 감지했다면, 이를 파이프라인을 통해 빌드, 테스트하고 서비스로 배포해야합니다. 개발 이터레이션(iteration)마다 생기는 변경 사항들을 자동화 된 파이프라인을 통해 수시로 배포하여 모든 팀원들이 프로젝트의 변화되는 모습을 쉽게 확인할 수 있어야 합니다.

이번 포스트에선 프로젝트의 변경된 코드를 파이프라인을 통해 빌드하고 서비스로 배포하는 과정을 정리하였습니다. 아마존 웹 서비스(AWS, Amazon Web Service)의 여러 제품을 사용하면 쉽게 배포 환경을 만들 수 있지만, 무료 티어(tier)를 사용하기 때문에 과금을 피하고자 컨테이너 종류와 개수를 최소화하였습니다. 회사 제품인 PCF(Pivotal Cloud Foundry) 지원을 받았다면 더 쉽고, 부담없이 파이프라인을 구축할 수 있었을텐데 아쉽습니다. 현재 구조라면 프로젝트 중간에 회사 지원을 받게 되어 클라우드 플랫폼을 변경하더라도 파이프라인엔 크게 영향이 없을 것으로 보입니다.

터미널 명령어들이 섞여서 나오기 때문에 헷갈릴 수 있어서 별도로 표시하였습니다.

  • on EC2 인스턴스 접미사가 붙은 것은 AWS EC2 인스턴스에서 작업한 내용입니다.
  • on Macbook 접미사가 붙은 것은 맥북에서 작업한 내용입니다.
AWS EC2 서비스 배포 작업 영역

세부적인 작업 내용
  • EC2 인스턴스에 이미 비공개 도커 레지스트리가 구축되어 있다고 가정합니다.
  • 간단한 예시를 위해 빌드와 배포 과정은 최대한 단순화시켰습니다.
  • 파이프라인은 checkout > build > deploy로 구성하였습니다.
  • checkout 스테이지
    • GitHub 개인 레포지토리에 변경된 코드 사항들을 최신화합니다.
  • build 스테이지
    • 프론트엔드 서비스를 빌드합니다.
    • Dockerfile을 이용해 이미지를 생성합니다.
  • deploy 스테이지
    • ssh 명령어로 EC2 인스턴스에게 이전에 실행 중인 front-end 컨테이너 정지와 삭제를 지시합니다.
    • 비공개 레지스트리에 이미지를 push / pull 하기 위해 도커 로그인을 수행합니다.
    • 비공개 레지스트리에 이미지를 push 합니다.
    • ssh 명령어로 EC2 인스턴스에게 새로운 이미지 pull을 지시합니다.
    • ssh 명령어로 EC2 인스턴스에게 새로 받은 이미지 실행을 지시합니다.

1. 프론트엔드 서비스 만들기

파이프라인에 배포할 프론트엔드 서비스를 하나 만들어보겠습니다. 젠킨스(Jenkins) GitHub Webhooks 연동 포스트에서 사용했던 레포지토리에 리액트 프로젝트를 하나 만들겠습니다.

CRA, Create React App on Macbook
jenkins-github-webhook % npx create-react-app@5.0.0 front-end

...

We suggest that you begin by typing:

  cd front-end
  npm start

Happy hacking!
Dockerfile 파일 생성 on Macbook
  • 도커 이미지를 만들때 필요한 Dockerfilefront-end 프로젝트에 생성합니다.
FROM nginx

RUN mkdir /app

WORKDIR /app

RUN mkdir ./build

ADD ./build ./build

RUN rm /etc/nginx/conf.d/default.conf

COPY ./nginx.conf /etc/nginx/conf.d

EXPOSE 80

CMD ["nginx", "-g", "daemon off;"]
nginx.conf 파일 생성 on Macbook
  • nginx를 실행할 때 필요한 설정 파일을 front-end 프로젝트에 생성합니다.
server {
	listen 80;
	location / {
		root	/app/build;
		index	index.html;
		try_files $uri $uri/ /index.html;
	}
}
프로젝트 디렉토리 구조
  • front-end 폴더에 Dockerfilenginx.conf 파일이 존재합니다.
  • 개인 레포지토리 루트 경로에 AWS EC2 인스턴스에 접속할 때 사용하는 private-key.pem 파일을 위치시켰습니다.
./
├── README.md
├── front-end
│   ├── Dockerfile
│   ├── README.md
│   ├── nginx.conf
│   ├── package-lock.json
│   ├── package.json
│   ├── public
│   │   ├── favicon.ico
│   │   ├── index.html
│   │   ├── logo192.png
│   │   ├── logo512.png
│   │   ├── manifest.json
│   │   └── robots.txt
│   └── src
│       ├── App.css
│       ├── App.js
│       ├── App.test.js
│       ├── index.css
│       ├── index.js
│       ├── logo.svg
│       ├── reportWebVitals.js
│       └── setupTests.js
└── private-key.pem

2. EC2 인스턴스 접근 허용 포트 추가

파이프라인이 정상적으로 동작하여 서비스가 배포되었다면 이를 사용자가 확인할 수 있어야 합니다. 테스트를 위해 EC2 인스턴스의 포트 3000번에 대한 외부 접근을 허용하도록 하겠습니다. 도커 레지스트리(Docker registry) 설치 on EC2 인스턴스 포스트에서 비공개 레지스트리 접근을 위해 포트 5000번을 허용한 것과 같은 방법입니다.

Security Groups 설정 화면 이동
  • EC2 인스턴스 정보를 확인할 수 있는 대시보드에서 해당 인스턴스를 선택합니다.
    • EC2 > Instances > {container id} 화면
  • 선택한 EC2 인스턴스 상세 정보 화면 하단에 Security 탭에서 security groups를 선택합니다.

Inbound rule 설정 화면
  • Edit inbound rules 버튼을 눌러 인바운드(inbound) 규칙 설정 화면으로 이동합니다.

Inbound rule 추가
  • Custom TCP를 선택하여 포트 3000번을 허용합니다.
  • 소스(source)는 0.0.0.0/0으로 지정하여 모든 IP에서 접근을 허용합니다.

3. 젠킨스 파이프라인 변경

젠킨스(Jenkins) GitHub Webhooks 연동 포스트에서 진행했던 파이프라인 잡(job)을 사용하였습니다.

3.1. 젠킨스 PATH 환경변수 변경

처음 스크립트를 작성하고 빌드를 수행했을 때 많은 에러들을 만났습니다. 계속 만나는 에러들을 해결해나가면서 시간을 허비했는데, 젠킨스에서 사용하는 환경 변수가 젠킨스를 호스트하는 OS(맥북)의 환경 변수와 다르다는 것을 나중에 알았습니다. 결국 문제의 원인은 "젠킨스 환경 변수 $PATH가 맥북에서 사용하는 $PATH와 달랐다." 였습니다. 맥북에서 직접 명령어를 수행했을 때 성공하고, 젠킨스 파이프라인에선 실패했다면 젠킨스의 환경 변수 PATH를 의심해보시길 바랍니다.

젠킨스 환경 변수 추가
  • Manage Jenkins > Configure System 화면으로 이동합니다.
  • Global properties 항목을 찾습니다.
  • Environment varaibles 체크 박스를 선택합니다.
  • 맥북에서 사용하는 $PATH 변수와 동일한 값을 Value 항목에 넣습니다.
  • Add 버튼을 눌러 환경 변수를 추가합니다.

3.2. 젠킨스 Credential 추가

젠킨스 파이프라인에서 EC2 인스턴스 비공개 레지스트리에 접근할 아이디와 비밀번호를 젠킨스 credential로 추가합니다.

EC2 인스턴스 비공개 레지스트리 로그인 정보 등록
  • 젠킨스(Jenkins) GitHub Webhooks 연동 포스트와 같은 방법으로 credential 정보를 추가합니다.
  • Manage Jenkins > Manage Credentials 화면으로 이동합니다.
  • Stores scoped to Jenkins 표에 보이는 (global) 링크를 클릭합니다.
  • Add Credentials 버튼을 선택합니다.
  • 비공개 도커 레지스트리(Private docker registry) 만들기 포스트에서 설정했던 비공개 레지스트리 로그인 아이디와 비밀번호를 입력합니다.
    • 글쓴이는 아이디 cicduser, 비밀번호 0000을 사용하였습니다.

3.2. 젠킨스 Declarative 파이프라인 스크립트

  • jenkins-github-webhook 프로젝트 > configure 화면으로 이동합니다.
  • Pipelin 항목의 Script 영역에 입력합니다.
  • {ec2-instance-public-ip}는 EC2 인스턴스 공개 IP입니다.
  • private_registry_credential은 이전 단계에서 등록한 비공개 레지스트리 credential 아이디를 사용합니다.
pipeline {
    agent any
    environment {
        AWS_PUBLIC_IP = '{ec2-instance-public-ip}'
        SSH_CMD = 'ssh -i private-key.pem ec2-user@{ec2-instance-public-ip}'
        DOCKER = 'sudo docker'
    }
    stages {
        stage('checkout') {
            steps {
                git branch: 'main',
                    credentialsId: 'github_access_token',
                    url: 'https://github.com/Junhyunny/jenkins-github-webhook.git'
                sh 'chmod 400 private-key.pem'
            }
        }
        stage('build') {
            steps {
                dir ('front-end') {
                    sh 'rm -rf build'
                    sh 'npm install'
                    sh 'npm run build'
                    sh 'docker build -t $AWS_PUBLIC_IP:5000/front-end:latest .'
                }
            }
        }
        stage('deploy') {
            steps {
                script {
                    try {
                        sh '$SSH_CMD $DOCKER stop front-end'
                        sh '$SSH_CMD $DOCKER rm front-end'
                    } catch (e) {
                        sh 'echo "fail to stop and remove container"'
                    }
                    withCredentials([usernamePassword(credentialsId: 'private_registry_credential', usernameVariable: 'USERNAME', passwordVariable: 'PASSWORD')]) {
                        sh 'docker login $AWS_PUBLIC_IP:5000 -u $USERNAME -p $PASSWORD'
                        sh '$SSH_CMD $DOCKER login localhost:5000 -u $USERNAME -p $PASSWORD'
                }
                sh 'docker push $AWS_PUBLIC_IP:5000/front-end:latest'
                sh '$SSH_CMD $DOCKER pull localhost:5000/front-end:latest'
                sh '$SSH_CMD $DOCKER run -d --name front-end -p 3000:80 localhost:5000/front-end:latest'
                }
            }
        }
    }
}

4. CI/CD 파이프라인 동작 테스트

Github에서 커밋(commit)을 만들고, 젠킨스 파이프라인이 정상적으로 동작하는지 확인합니다. 파이프라인이 배포까지 모두 성공하였을 때 변경된 서비스가 반영되었는지 살펴보겠습니다. 테스트를 위해 화면에 Learn React라는 문구를 Hello React World라는 문구로 변경하였습니다.

REFERENCE

댓글남기기