catsridingCATSRIDING|OCEANWAVES
Ops

Jenkins CI/CD Pipeline 구축하기 03. Jenkins에서 Docker 이미지 빌드하여 배포하기

jynn@catsriding.com
Mar 30, 2024
Published byJynn
999
Jenkins CI/CD Pipeline 구축하기 03. Jenkins에서 Docker 이미지 빌드하여 배포하기

Building Docker Images with Jenkins

현대적인 개발과 운영 환경에서는, Docker 이미지의 빌드와 배포가 CI/CD 파이프라인의 핵심 요소입니다. Jenkins를 활용하면 이런 과정들을 자동화할 수 있어 개발 싸이클을 효율적으로 운영할 수 있습니다. 소스 코드에 변화가 생기면, Jenkins는 이를 인지하고 GitHub의 지정된 브랜치에서 Docker 이미지를 자동으로 빌드하며, 변화를 반영하여 배포합니다. 이러한 자동화는 개발의 효율성를 크게 끌어올립니다.

이전 포스트에서는 Jenkins와 GitHub의 Webhooks을 연동하는 작업을 진행하였습니다. 이는 GitHub에서 이벤트가 발생할 때마다 Jenkins에서 해당 이벤트를 감지하고 지정된 작업을 자동으로 트리거하는 내용이었습니다. 이번 글에서는 이러한 Jenkins와 GitHub의 연동을 바탕으로, GitHub에 푸시되는 코드 변경사항들을 Jenkins에서 Docker 이미지로 빌드하고, 이를 AWS EC2에 배포하는 전체 CI/CD 파이프라인을 구축해보도록 하겠습니다.

Pipeline Overview

현재 구현하려는 Jenkins CI/CD 파이프라인은 다음과 같은 프로세스를 포함하고 있습니다:

building-docker-images-with-jenkins_00.png

  1. 개발자는 로컬에 코드 변경사항을 만든 후, 이를 GitHub에 푸시합니다.
  2. GitHub에서는 이러한 푸시 이벤트를 감지하고, 설정된 Webhooks을 통해 Jenkins에 이를 알립니다.
  3. Jenkins는 웹훅 이벤트를 받아서 설정된 프로젝트를 자동으로 트리거합니다.
  4. Jenkins에서 Docker 이미지를 빌드하는 작업을 시작하고, 결과적으로 GitHub 리포지토리의 현재 코드를 반영하는 새로운 Docker 이미지가 생성됩니다.
  5. Docker 이미지의 빌드가 성공적으로 완료되면, 이 이미지는 Docker Hub에 업로드됩니다.
  6. 이어서 Jenkins는 AWS EC2 인스턴스에 SSH 연결을 시도합니다. 이 연결이 완료되면, Jenkins는 EC2 내부에 미리 작성해 두었던 배포 스크립트를 실행합니다.
  7. 이 스크립트는 Docker Hub에서 이미지를 다운로드 받고 이 이미지를 이용해 새로운 컨테이너를 시작하고, 기존 컨테이너를 종료시킵니다.

코드 변경이 발생하면 자동으로 도커 이미지를 빌드하고 AWS에 배포하는 과정을 통해, 효율적인 개발 환경을 구축할 수 있습니다. 이는 개발자가 직접 배포 과정을 수행하는 수고를 덜어줄 뿐만 아니라, 배포 속도 또한 향상시킵니다.

Installing Docker Engine

Jenkins에서 Docker 이미지를 빌드하고 추가 명령을 실행하려면 Jenkins가 구동 중인 서버에 Docker Engine이 설치되어 있어야 합니다.

아래는 Linux Ubuntu 환경에서 Docker를 설치하는 방법입니다. 운영 체제마다 약간의 차이가 있으므로, 사용 중인 특정 리눅스 배포판에 맞는 Docker 공식 설치 가이드를 참조하는 것이 가장 좋습니다.

Preparations

Docker를 설치하려면 64-bit 버전의 다음 Ubuntu 버전 중 하나가 필요합니다:

  • Ubuntu Mantic 23.10
  • Ubuntu Jammy 22.04 (LTS)
  • Ubuntu Focal 20.04 (LTS)

또한 Docker Engine을 설치하기 전에 기존 버전의 Docker나 충돌할 수 있는 패키지를 제거해야 합니다. 아래 명령어를 통해 이러한 패키지를 제거할 수 있습니다.

$ for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done 

Setting up Docker's apt repository

첫번째로 Docker의 공식 GPG 키를 추가해야 합니다.

$ sudo apt-get update 
$ sudo apt-get install ca-certificates curl 
$ sudo install -m 0755 -d /etc/apt/keyrings 
$ sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc 
$ sudo chmod a+r /etc/apt/keyrings/docker.asc 

그리고 Docker의 apt 소스 리스트를 추가해야 합니다.

$ echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
  $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | \
  sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
$ sudo apt-get update

Ubuntu 파생 배포판을 사용하는 경우 VERSION_CODENAME 대신 UBUNTU_CODENAME를 사용해야 할 수 있습니다.

Installing Docker packages

다음 명령어를 통해 Docker를 설치합니다.

$ sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

Verifying Docker installation

hello-world 이미지를 동작시켜 Docker 설치가 성공적으로 이루어졌는지 확인합니다.

$ sudo docker run hello-world 

이 명령어는 이미지를 다운로드 받아 컨테이너를 실행시키며, 컨테이너가 실행되면 확인 메시지를 출력하고 종료됩니다.

Non-root Docker Execution

일반적으로 Docker 명령을 실행하려면 sudo 명령이 필요합니다. 이는 Docker 그룹에 가입한 사용자가 없기 때문인데, non-root 사용자가 Docker 명령을 실행할 수 있도록 하려면 Docker 그룹에 해당 사용자를 추가해야 합니다.

$ sudo usermod -aG docker $USER 

해당 명령어로 지정된 사용자를 Docker 그룹에 추가합니다. 이렇게 하면 해당 사용자도 Docker 명령을 실행 할 수 있습니다. 변경 사항을 적용하려면 세션을 재시작해야 할 수 있습니다.

이렇게 해서 Docker를 설치하고 실행하였습니다.

Installing Publish Over SSH

Jenkins는 사용자가 필요한 기능을 쉽게 확장하고 설정할 수 있도록 다양한 플러그인을 제공합니다. 이들 중 Publish Over SSH 플러그인은 원격 서버에 파일을 전송하거나 스크립트를 실행하는 작업을 가능하게 합니다. 이제 Publish Over SSH 플러그인 설치 및 필요한 환경 설정 과정에 대해 알아보겠습니다.

먼저, Jenkins의 플러그인 관리 페이지로 이동합니다. 이 페이지에서 Publish Over SSH를 검색하여 플러그인을 설치합니다.

building-docker-images-with-jenkins_00.png

다음은 SSH 접속을 위한 설정이 필요합니다. 이를 위해 Jenkins에서는 처음 EC2 인스턴스를 생성할 때 사용한 키 페어를 등록해야 합니다. 이 과정은 Jenkins의 시스템 설정 페이지 내 Publish Over SSH 섹션에서 이루어집니다.

SSH Servers 추가 버튼을 클릭하면 새로운 서버를 추가하는 입력 폼이 생성됩니다. 이 폼에는 AWS EC2 인스턴스에 연결하기 위해 필요한 정보를 기입해야 합니다. EC2 키페어 설정은 Advanced 버튼을 클릭하고, Use password authentication, or use a different key 옵션을 선택하여 진행 합니다. EC2 인스턴스 생성시 발급받은 key-pair.pem 파일을 텍스트 에디터로 열고, 그 안에 있는 RSA 프라이빗 키 값을 확인합니다. 이 값을 키페어 입력 창에 붙여넣어 설정을 완료합니다.

building-docker-images-with-jenkins_02.png

이러한 설정을 마친 후, 연결 정보가 모두 정확하게 기입되었는지 확인하기 위해 입력폼 우측 하단의 Test Configuration 버튼을 클릭하여 연결 상태를 검사해봅니다.

이렇게 SSH Server를 등록해두면, Jenkins 프로젝트 내에서 해당 서버 설정을 불러와 AWS EC2 인스턴스에 접속하는 데 활용할 수 있습니다.

Configuring Build Steps

이전 포스트에서 GitHub Webhooks을 연동하였습니다. 그 결과, Jenkins는 GitHub 이벤트를 감지하고 이에 따라 소스 코드가 자동으로 동기화되게 됩니다. 이제 동기화된 소스 코드로 Docker 이미지를 자동으로 빌드하고 Docker Hub로 업로드하는 작업을 Jenkins 빌드 프로세스에 추가해보겠습니다.

Jenkins 프로젝트 설정으로 이동하여 Build Steps 섹션에서 Add build step 버튼을 클릭하고 Execute shell을 선택합니다. 그런 다음, Docker 이미지를 빌드하고 Docker Hub에 업로드하는 셸 명령을 다음과 같이 구성합니다:

# Docker 이미지 빌드
docker build --platform linux/amd64 -t ${DOCKER_USERNAME}/${DOCKER_IMAGE_NAME} .

# Docker Hub 로그인
echo "${DOCKER_HUB_ACCESS_TOKEN}" | docker login -u "${DOCKER_HUB_USERNAME}" --password-stdin

# 이미지 푸시
docker push ${DOCKER_USERNAME}/${DOCKER_IMAGE_NAME}

위의 명령어에서 ${VARIABLES} 형식으로 적혀있는 부분은 실제 값으로 변경해야 합니다. 보안성을 위해 이런 값들은 Jenkins Credentials 또는 Secret Text를 이용하여 숨기는 것을 권장합니다.

building-docker-images-with-jenkins_03.png

Docker 이미지의 빌드와 푸시가 성공적으로 완료되면, 이제 Jenkins에서 AWS EC2로 이미지를 다운로드하고 실행 중인 컨테이너를 새로운 것으로 교체하는 스크립트를 실행하도록 설정해야 합니다. 이를 위해서는 Build Steps 설정에서 Send files or execute commands over SSH를 추가하는 빌드 스텝이 필요합니다.

building-docker-images-with-jenkins_04.png

이 설정에서는 이전 단계에서 생성한 SSH 서버 정보를 사용하며, Exec command 부분에는 EC2에서 실행할 스크립트 명령어를 입력하게 됩니다.

building-docker-images-with-jenkins_05.png

EC2에서 Docker 컨테이너를 실행하기 위해 작성된 스크립트는 다음과 같습니다:

docker-startup.sh
cd /home/ec2-user/applications

# Docker Hub 로그인
echo ${DOCKER_HUB_ACCESS_TOKEN} | docker login --username ${DOCKER_HUB_USERNAME} --password-stdin

# 기존 Docker 컨테이너 정리
docker rm -f hello-docker

# 기존 Docker 이미지 정리
docker rmi -f ${DOCKER_USERNAME}/${DOCKER_IMAGE_NAME}

# Docker 이미지 다운로드
docker pull ${DOCKER_USERNAME}/${DOCKER_IMAGE_NAME}

# Docker 컨테이너 실행
docker run -d --name hello-docker -v hello-volume:/app/logs --network=host ${DOCKER_USERNAME}/${DOCKER_IMAGE_NAME} --spring.profiles.active=prod

여기까지 Docker 이미지를 활용한 CI/CD 파이프라인의 설정을 완료하였습니다. 이제 GitHub에서 Webhooks 이벤트가 발생하면 Jenkins가 그것을 자동으로 감지하여 자동으로 빌드를 수행하고 배포를 진행하게 됩니다.

그렇지만, Jenkins를 사용한 CI/CD 파이프라인 구축에서 EC2 서버가 도커 이미지를 직접 다운로드하고 배포하는 프로세스는 여전히 몇 가지 한계점이 존재합니다.

  • 서버가 스케일 아웃(Scale-Out) 상태로 새로운 서버 인스턴스가 추가될 경우, 각각의 새 서버에 대해 젠킨스를 통해 별도의 배포 작업이 필요합니다. 이 경우 SSH 접속 정보가 각 서버마다 다르므로, 이를 수동으로 관리하고 업데이트해야 하는 부담이 생깁니다. 이는 특히 트래픽이 급증하며 실시간 대응이 필요한 상황에서 문제가 됩니다. 각각의 새로운 서버에 별도로 배포 작업을 수행해야 하기 때문에, 배포에 소요되는 시간이 늘어나고, 이로 인해 전체 시스템의 레이턴시가 증가하게 됩니다. 또한, 이런 상황에서는 자동 확장(Auto Scaling-Out) 기능을 적용하는 것도 어렵습니다.
  • Docker 컨테이너를 교체하는 과정에서 애플리케이션은 일시적으로 중지될 수 있으며, 이는 서비스 연속성을 저해하게 됩니다.

따라서 이런 한계점들을 극복하고 무중단 배포와 효율적인 스케일링 대응이 가능한 환경을 위해서는 AWS에서 제공하는 관리형 Kubernetes 서비스인 Amazon EKS(Elastic Kubernetes Service)를 활용할 수 있습니다. EKS는 Kubernetes를 클라우드 환경에서 쉽게 실행할 수 있게 해주며, AWS의 자동 스케일링 그룹과 함께 작동하여 EC2 인스턴스를 자동으로 스케일 아웃할 수 있게 해줍니다. 이런 방식을 통해 중단 없는 서비스 운영과 자원의 효율적인 관리가 가능해집니다. 이에 대한 내용은 다음 글에서 살펴보도록 하겠습니다. ☸️

  • Docker
  • CI / CD