ETC

Jenkins를 이용한 Docker 컨테이너 자동 배포하기(Blue Ocean, Bitbucket, NCP)

Beekei 2022. 5. 6. 16:03
반응형

이전 글에서는 Jenkins을 이용해 jar파일 자동화 배포를 해보았다.

이번에는 소스코드를 도커 이미지 화 후 서버에서 해당 이미지를 받아 구동시키는 파이프라인을 구축하려고 한다.

Blue Ocean 플러그인을 사용해 빌드가 되는 상태를 시각화하고 파이프라인을 쉽게 만들어보겠다.

 

이전 글과 마찬가지로 Jenkins 서버는 Docker로 로컬에서 구동했고, Jenkins 서버 구축 방법은 이전 글을 참고 바란다.

배포할 서버는 Naver Cloud Platform Server를 사용했고, 컨테이너 저장소는 Naver Container Registry를 사용했으므로 AWS EC2나 ECR을 사용하는 경우 설정법이 조금 다를 수 있다.

 

대략 진행되는 단계를 그림으로 나타내면 아래와 같다.


구축 예제

1. Jenkins 설치 및 도커 설치

먼저 Jenkins 서버를 설치할 것인데 Jenkins 컨테이너 안에서 Docker 명령어를 사용해 이미지화시켜야 한다.

이는 Jenkins 서버 안에 또 다른 도커 데몬을 설치해야 하는데 이는 권장하는 방법이 아니라고 한다.

Jenkins가 설치되어있는 서버의 docker 볼륨과 Jenkins 컨테이너 안 docker의 볼륨을 연결해 사용해야 한다.

지금 작성하는 예제는 로컬에서 Jenkins 컨테이너를 띄어 구축했으므로 로컬이 Jenkins가 설치되어있는 서버라고 생각하면 되겠다.

로컬(Jenkins가 설치되어있는 서버)

$ docker run -d -it -v /var/run/docker.sock:/var/run/docker.sock -p 8090:8080 --name jenkins jenkins/jenkins:lts

다른 블로그 글에서는 볼륨만 연결해주면 도커를 따로 설치해주지 않아도 도커 명령어를 사용할 수 있다고 하는데 나는 왜인지 되지 않았다.

그래서 Jenkins 컨테이너에 접속해 도커를 따로 설치해주었더니 명령어가 정상적으로 작동했다.

Jenkins 컨테이너에 접속해 도커를 설치하려고 OS를 확인해보니 debian이었다... 처음 맞추져 보는 OS라 명령어를 잘 몰랐는데 정리가 잘되어있는 블로그 글을 찾아 설치했다.

(참고 : https://www.hostwinds.kr/tutorials/install-docker-debian-based-operating-system)

 

 

설치 후 Docker 명령어를 사용해보면 권한이 없다고 나타날 수 있는데 로컬(Jenkins가 설치되어있는 서버) Docker에는 jenkins 계정에 권한이 없기 때문이다.

root 계정으로 Jenkins 컨테이너에 접속해 jenkins계정에 Docker 볼륨을 사용할 수 있는 권한을 주어야 한다.

$ docker exec -it -u root jenkins bash
$ chown jenkins:jenkins /var/run/docker.sock

 

이제 다시 실행중인 컨테이너를 확인해보면 로컬(Jenkins가 설치되어있는 서버)에 컨테이너 정보가 보이는 것을 확인할 수 있다. 로컬 Docker 볼륨과 Jenkins 컨테이너의 Docker 볼륨이 공유되고 있는 것이다.

Jenkins 컨테이너

$ docker ps

참고!
예제처럼 로컬 도커에 Jenkins 컨테이너를 띄어 구축하시는 분들은 코드 빌드 시 Jenkins 컨테이너가 자꾸 종료되는 현상이 있을 수 있는데 아마 CPU나 메모리 문제인 것 같다.
docker stats으로 확인해보면 메모리 Limit가 1.9GB정도 되는데 빌드할때 메모리를 2GB 넘게 잡아먹는다.

그래서 Jenkins 컨테이너에 CPU 최대 사용량을 80% 정도, 메모리를 4G 설정하고 빌드하게되면 조금 느려지지만 컨테이너는 종료되지 않았다. (근데 메모리 설정은 먹히지 않는듯 하다.. docker stats으로 확인해봣지만 여전히 1.9GB다...ㅠ 왜지?)
$ docker update --cpus=0.8 jenkins
$ docker update --memory 4G --memory-swap -1 jenkins

2. Jenkins 필요 라이브러리 설치

Jenkins과 Docker를 정상적으로 설치했다면 Jenkins 페이지에 접속해 필요한 라이브러리들을 설치해야 한다.

Plugin Manager에 접속해 Blue Ocean, SSH Agent, Docker Pipeline, Bitbucket Plugin을 설치해준다.

Blue Ocean은 파이프라인 구축할 때 시각화하여  쉽게 구축할 수 있도록 도와준다.

SSH Agent Plugin은 배포할 서버 안에서 명령어를 실행할 때 사용한다.

파이프라인 안에서 Docker Pipeline의 문법을 사용해 이미지를 Build 하고 Push 한다.

Bitbuket Plugin은 Bitbuket 푸시 시 웹 훅을 받을 수 있도록 설정할 수 있다.


3. 권한 설정

모두 설치가 끝났으면 Container Registry에 접근할 수 있는 권한SSH로 배포할 서버에 접근할 수 있는 권한을 등록한다.

Jenkins 관리 > Manage Credentials에서 권한을 추가할 수 있다.

Container Registry 접근 Credential
SSH 서버 접근 Credential


4. 파이프라인 생성

Blue Ocean 플러그인을 설치했으면 왼쪽 사이드 메뉴에 블루 오션 열기라는 메뉴가 보일 텐데, 요기에 들어가 파이프라인을 등록해보자.

4-1. 파이프 라인 생성

먼저 Git Repository와 연결하여 소스코드와 파이프라인을 연결해야 한다.

4-2. Bitbucket에 공개 키,  Webhook URL 등록

ssh로 연결하기 때문에 파이프라인에서 발급받은 공개 키를 복사해 Bitbucket SSH Key에 등록해야 한다.

https로 연결 시 계정을 이용해 인증 후 파이프라인을 생성할 수도 있다.

그리고 Bitbucket에 접속한 김에 Webhook도 등록해준다.

이전 글에서 설명했지만 localhost로는 웹 훅을 발송할 수 없어 ngrok으로 포워딩해서 웹 훅 URL을 설정하였다.

 

4-3. 파이프 라인 설정

소스코드가 정상적으로 연결됐다면 Jenkinsfile이 저장되어있는 브랜치는 자동으로 잡히게 되는데, 저장되어있지 않다면 파이프라인 설정 페이지로 이동할 것이다.

파이프라인 설정 페이지

+ 아이콘을 클릭해 Jenkins 페이지에서 여러 가지 방법의 스텝을 설정해 Jenkinsfile을 Push 할 수 있다.

나는 귀찮아서 그냥 Jenkinsfile을 작성해 바로 브랜치에 올려버렸다.(그냥 브랜치에 올려도 Blue Ocean에서 import 된다.)

Jenkinsfile 파일은 Dockerfile과 마찬가지로 프로젝트 최 상단 디렉터리에 존재해야 한다. Jenkinsfile의 내용은 아래와 같다.

pipeline {
    agent any

    stages {
        stage("Set Variable") {
            steps {
                script {
                    IMAGE_NAME = "이미지명"
                    IMAGE_STORAGE = "Container Registry 경로"
                    IMAGE_STORAGE_CREDENTIAL = "Container Registry 접근 Credential id"
                    SSH_CONNECTION = "접속할 계정@배포할 서버 IP"
                    SSH_CONNECTION_CREDENTIAL = "SSH 서버 접근 Credential id"
                }
            }
        }

        stage("Clean Build Test") {
            steps {
                sh "./gradlew clean build test"
            }
        }

        stage("Build Container Image") {
            steps {
                script {
                    image = docker.build("${IMAGE_STORAGE}/${IMAGE_NAME}")
                }
            }
        }

        stage("Push Container Image") {
            steps {
                script {
                    docker.withRegistry("https://${IMAGE_STORAGE}", IMAGE_STORAGE_CREDENTIAL) {
                        image.push("${env.BUILD_NUMBER}")
                        image.push("latest")
                        image
                    }
                }
            }
        }

        stage("Server Run") {
            steps {
                sshagent([SSH_CONNECTION_CREDENTIAL]) {
                    // 최신 컨테이너 삭제
                    sh "ssh -o StrictHostKeyChecking=no ${SSH_CONNECTION} 'docker rm -f ${IMAGE_NAME}'"
                    // 최신 이미지 삭제
                    sh "ssh -o StrictHostKeyChecking=no ${SSH_CONNECTION} 'docker rmi -f ${IMAGE_STORAGE}/${IMAGE_NAME}:latest'"
                    // 최신 이미지 PULL
                    sh "ssh -o StrictHostKeyChecking=no ${SSH_CONNECTION} 'docker pull ${IMAGE_STORAGE}/${IMAGE_NAME}:latest'"
                    // 이미지 확인
                    sh "ssh -o StrictHostKeyChecking=no ${SSH_CONNECTION} 'docker images'"
                    // 최신 이미지 RUN
                    sh "ssh -o StrictHostKeyChecking=no ${SSH_CONNECTION} 'docker run -d --name ${IMAGE_NAME} -p 8080:8080 ${IMAGE_STORAGE}/${IMAGE_NAME}:latest'"
                    // 컨테이너 확인
                    sh "ssh -o StrictHostKeyChecking=no ${SSH_CONNECTION} 'docker ps'"
                }
            }
        }
    }

}

Dockerfile의 내용은 아래와 같다. 

FROM amazoncorretto:11-alpine-jdk # JDK 11 이미지를 불러옴
MAINTAINER DevBeekei <devbeekei.shin@gmail.com>

EXPOSE 8080

ARG JAR_FILE=build/libs/*.jar # 빌드된 Jar 파일
COPY ${JAR_FILE} deploy-app.jar # deploy-app.jar 파일명으로 복사
CMD java -jar ./deploy-app.jar # jar 파일 실행

Jenkins 파일에서 등록한 스텝에서 요 Dockerfile를 빌드해 이미지화시켜 Container Registry에 등록하는 것이다.

 

파이프라인이 정상적으로 생성되었으면 설정에 들어가 보자.

git Behaviours에 4가지 항목이 설정되어있는데,

민감한 정보가 들어있는 yml 파일을 gitignore에 설정해 따로 볼륨에 올려준다면 Clean after checkout, Clean before checkout을 삭제해야 한다. 삭제하지 않으면 따로 올려준 yml 파일이 계속해서 삭제된다.

 

그리고 하단에 Build when a change is pushed to BitBucket을 체크해준다.

요것을 체크해야 발송할 webhook을 받을 수 있다.


5. 테스트!

이제 소스코드를 Push 해 파이프라인이 잘 작동하는지 테스트해보자.

여기서 하나! 더 물론 배포할 서버에도 Docker가 설치되어 있어야 Docker 명령어를 사용할 수 있다.

이미지 Push까지는 정상적으로 작동하는데 배포하려는 서버에서 스크립트 명령어가 먹히질 않는다...ㅜㅜ

심지어 아무런 로그까지 출력되지 않아 심히 고민이 많았다.


6. 배포할 서버에 Jenkins 공개 키 등록 (NCP Server)

생각해보니 NCP Server는 AWS EC2와는 다르게 Private Key로 접속을 해도 관리자 비밀번호를 한번 더 물어본다.

아마 그것 때문에 아무런 로그도 반응도 하지 않는 것 같았다.

 

그래서 Jenkins 컨테이너 안에서 공개키를 만들고 그 공개키를 NCP Server에 등록해주었다.

(아래 설정은 NCP Server를 사용하시는 분들만 해당될 듯하다.)

Jenkins 컨테이너

$ ssh-keygen -t rsa -b 4086 # 공개키 생성
$ ssh-copy-id -i /var/jenkins_home/.ssh/id_rsa.pub ncloud@배포할 서버 IP # 배포할 서버에 공개키 등록

공개키 등록 후 jenkins 컨테이너에서 ssh로 명령어를 사용해보니 비밀번호를 묻지 않고 바로 실행이 되는 것을 확인할 수 있었다.

그럼 다시 파이프라인 테스트를 해보자.

정상적으로 배포가 되었고, 서버에 접속해보니 배포한 컨테이너가 잘 돌아가고 있는 것을 확인할 수 있다.!!


설정할 것들이 많아 너무 주저리주저리 정리했다...

OS나 쉘 스크립트 부분을 잘 안다면 참 쉬운 설정인데.. 나의 무지로 인해 찾아보고 테스트해보는 시간이 3일 정도 걸렸다.

 

이번 글이 너무 길어 Jenkins 파이프라인 각 스텝마다 Slack 알람을 설정해 정상적으로 빌드되고, 배포되고 있는지 실시간으로 알림을 받을 수 설정은 따로 정리해 올리려고 한다.

 

* 구축이 안되거나 이해가 잘 가지 않는 부분을 댓글로 말씀해주시면 제가 아는 선에서는 빠르게 대답해드리겠습니다.

반응형