[Jenkins] NCP, Github, Docker, Spring Boot, Discord 로 CI/CD 구축하기

두의 개발 고민 블로그·2023년 11월 26일

CarryAWay 프로젝트

목록 보기
5/7

💡 안녕하세요 ! 이번에 젠킨스를 이용하여 Spring Boot를 CI/CD 파이프라인 구축하는 것에 대해 글을 작성해보려 합니다. 많은 오류와 시행착오 속에서 어떻게 해결했는지, 더 좋은 방법은 없는지, 추가할 만한 좋은 오픈소스가 있는지에 대해서 알아보겠습니다.

아래의 사진은 65트만에 오류를 해결하고 성공한 Jenkins의 모습입니다.

1️⃣ 빌드 순서

  1. Code push to Github
  2. Build by Jenkins
  3. Push to Docker hub
  4. Docker run

2️⃣ 기술 스택

  • Spring Boot - gradle
  • AWS RDS
  • Docker
  • Github
  • Jenkins
  • NCP - Server

3️⃣ 서버 준비

저는 서버를 두 개로 준비해서 Jenkins가 돌아갈 서버, 

Spring Boot가 돌아갈 서버 두개를 준비했습니다.

  • 쿠버네티스를 공부하던 서버를 계속 사용해서 Jenkins 서버로 사용했고, Spring 서버는 NCP 무료서버를 사용했습니다.
  • 공인 IP를 연결하고, 보안그룹을 설정해줍니다.
⚠️ 아래 kube-study로 사용 중인 서버 스펙은 과금이 발생 할 수 있습니다. 전 해커톤에 나가서 받은 NCP 크레딧으로 충당했으니 여러분도 만약 만들게 되면 과금이 발생하지 않도록 주의 해주세요 !

서버가 자꾸 꺼질 때

해당 코드는 서버 내의 스왑 파일을 생성하고, 가상 메모리 기법을 활성화 하는 코드입니다. 가상 메모리 공간을 마련하여 메모리 부족 상황에서 시스템이 더욱 안정적으로 동작할 수 있도록 도와줍니다.

$ sudo dd if=/dev/zero of=/swapfile bs=128M count=16
$ sudo chmod 600 /swapfile
$ sudo mkswap /swapfile
$ sudo swapon /swapfile
$ sudo swapon -s
$ sudo vi /etc/fstab
/swapfile swap swap defaults 0 0

4️⃣ Jenkins 서버 준비

Jenkins 서버에서 Docker와 Jenkins 이미지를 설치하여 서버를 켜보겠습니다.

1. Docker를 설치

Install Docker Engine on Ubuntu

# 설치 및 버전 확인
docker -v

2. Jenkins 설치 및 빌드

docker run -d -p 8080:8080 -p 50000:50000 -v /jenkins:/var/jenkins -v /root/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock --name jenkins -u root jenkins/jenkins:lts

# 조심) 계정이 root면 위 것으로 사용하고, ubuntu면 -v /home/ubuntu/.ssh:/root/.ssh

이미지를 pull 받고 run을 돌려도 되지만, docker는 이미지가 없는 경우 docker hub 에서 공식 이미지를 찾아서 다운받아서 run 시켜주기 때문에 편한 방법 사용하시면 됩니다.

3. Jenkins Container 조회

docker ps

4. Jenkins 접속

[공인 IP 주소]:8080

위 주소로 접속하면 Jenkins가 비밀번호를 입력하라는 내용이 나옵니다.

docker logs jenkins

당황하지 않고 Jenkins 서버로 와서 위 코드를 작성하여, Jenkins가 초기 실행될 때 발생한 코드를 복사해서 시작하면 됩니다.

5. Install suggested Plugins 설치

이후 계정 생성은 본인이 기억할 수 있는 아이디와 비밀번호를 사용해줍니다.

6️⃣ Dockerfile 작성

Spring Project의 파일에 Dockerfile을 생성하고 아래 내용을 넣어줍니다.

FROM openjdk:17-alpine

ARG JAR_FILE=build/libs/*.jar

COPY ${JAR_FILE} app.jar

ENTRYPOINT ["java","-jar","/app.jar"]

깃허브에 올려줍니다.

git add Dockerfile
git commit -m 'Docs : add Dockerfile'

# 전 commit을 vi 편집기로 해서 아래 내용 작성했습니다.
git commit
Docs : add Dockerfile
This is a Dockerfile that uses Java 17 version and creates a .jar file.

git push

7️⃣ Jenkins, Github 연동

1. 공개키, 비밀키 생성 (Jenkins Server)

Jenkins Container를 생성할 때 "/root/.ssh:/root/.ssh"로 .ssh 디텍도리를 마운트 해놓았기 때문에 Container 밖에서 ssh 키를 생성하면 Jenkins Container와 연결된다.

# 그냥 전부 enter를 입력해default로 만든다.
ssh-keygen

# /root/.ssh에 id_rsa와 id_rsa.pub이 생성된다.
ls /root/.ssh
id_rsa  id_rsa.pub  known_hosts
  • id_rsa : private key
  • id_rsa.pub : public key → 깃허브에 주면 됌
♻️ `/root/.ssh:/root/.ssh` 바운드 마운트 하는 이유 Jenkins 컨테이너 내부로 들어가서 ssh-keygen을 통해 github에 key를 넘겨줘도 되지만, 컨테이너가 삭제되게 되면 Jenkins 컨테이너 내부의 데이터가 사라지고, 설정값들이 사라지게 된다. 그럼 추후에도 많은 설정들을 할 건데, 그런 값들도 다시 변경해줘야 하는 번거로움이 생기게 된다. key값 뿐만 아니라 다양한 설정값들을 저장하고 유지하는 방법이 Docker Volume에 있으니 궁금하면 찾아보면 좋을 듯 하다.

2. Github Deploy Key 등록

Github Repository > Settings > Deploy Keys > Add deploy key 선택

title은 아무거나 입력해도 상관없다. 본인은 jenkins 라고 입력했음

# 공개키 복붙하고 key에 아래 내용을 넣어주면 된다.
cat /root/.ssh/id_rsa.pub

3. Jenkins Credentials 등록

  • Jenkins 대시보드 > Jenkins 관리 > Security > Credentials

**Stores scoped to Jenkins**에 Domain이 (global)인 text 클릭 Global credentials (unrestricted)로 이동한다. 왼쪽 메뉴의 Add credentials를 눌러 credentials를 추가

  • KindSSH
    • Username with private key
  • ID
    • github
  • Username
    • root (default)
  • Private Key
    • Enter directly 체크 -> private key 입력

    • 여기서 private key는 Jenkins Server에서 생성한 id_rsa이다. 아래 명령어로 확인 가능하다.

      cat /root/.ssh/id_rsa

8️⃣ Jenkins, Docker hub 연결

1. Docker Plugin 설치

Jenkins 대시보드 > Jenkins 관리 > 플러그인 관리 > 설치 가능 > Docker 검색 > Docker, Docker Pipeline 플러그인 설치 및 재실행

⚠️ 아마 곧 재시작 된다는 말이 나오게 되는데, 난 30분을 기다려도 재시작 되지 않았다. 그래서 수동으로 다시 시작하는 방법을 알려드리겠습니다.

주의) 절대 컨테이너를 삭제하면 안됩니다.

Jenkins 컨테이너 재시작

docker ps -a

# 현재 잠시 Stop 상태의 Jenkins가 보인다면
docker start [Container ID]

이후 다시 시작하면 됩니다.

2. Docker Hub Credencials 등록

  • Jenkins 대시보드 > Jenkins 관리 > Security > Credentials
  • Kind
    • Username with password
  • Username
    • 본인의 Docker Hub ID
  • Password
    • 본인의 Docker Hub Password
  • ID
    • docker-hub

3. Jenkins 내부에 Docker 설치

젠킨스가 빌드 되면서 docker와 관련된 명령어를 실행할 수 있도록 제작할 것인데, 중요한 것은 Jenkins 내부 컨테이너에 Docker가 설치되있지 않아서, 나중에 Docker 명령어 오류가 뜬다. 그것을 미연에 방지하기 위해 여러분들은 저처럼 삽질하지 마시고, 미리 설치하여 번거러움을 더시길 바랍니다 😊

# jenkins container 터미널 접속
docker exec -it jenkins /bin/bash
 
# linux 버전 확인
cat /etc/issue
# --------------- OS --------------------------------
# root@DESKTOP-R4P59B3:/home/opendocs# cat /etc/issue
# Ubuntu 20.04.4 LTS \n \l
# --------------- jenkins Container OS --------------------------------
# root@DESKTOP-R4P59B3:/home/opendocs# docker exec -it jenkins /bin/bash
# root@8fc963af71bb:/# cat /etc/issue
# Debian GNU/Linux 11 \n \l
 
# Docker 설치
## - Old Version Remove
apt-get remove docker docker-engine docker.io containerd runc
## - Setup Repo
apt-get update -y
apt-get install -y\
    ca-certificates \
    curl \
    gnupg \
    lsb-release
mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian \
  $(lsb_release -cs) stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
## - Install Docker Engine
apt-get update -y
apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y

9️⃣ Jenkins, Spring Boot Server SSH 연결

⚠️ 미리 알아 두어야 할 점

Jenkins 서버에서 Spring 서버로 SSH 연결을 할 때, 공개키, 비밀키를 사용하게 된다. SSH 접속 원리는 유저가 SSH 접속을 할 때, 유저한테 비밀키, 서버에 공개키를 저장해두고, ssh 키 인증을 하게된다.

그렇다면, Jenkins에서 Spring Server로 SSH 접속을 하게되면, Jenkins가 유저가 되고, Spring Server가 서버가 된다. 즉, 아까 ssh-keygen을 통해 생성된 공개키와 비공개 키 중 공개키를 Srping Server에 전달해야 한다.

Jenkins로 Spring Boot 코드를 빌드하고, 생성된 이미지를 Spring Boot 서버에서 구동시키기 위해서 ssh를 사용한다. 아래는 이후에 생성할 pipeline에서 사용하는 ssh 연결 후 명령어를 전달하는 코드이다.

stage('Docker Run') {
    steps {
        echo 'Pull Docker Image & Docker Image Run'
        sshagent (credentials: ['SSH Credential ID -> ssh']) {
            sh "ssh -o StrictHostKeyChecking=no root@[서버IP] 'docker pull [docker image]'" 
            sh "ssh -o StrictHostKeyChecking=no root@[서버IP] 'docker ps -q --filter name=carry | grep -q . && docker rm -f \$(docker ps -aq --filter name=[컨테이너 이름])'"
            sh "ssh -o StrictHostKeyChecking=no root@[서버IP] 'docker run -d --name carry -p 8080:8080 [docker image]'"
        }
    }
}

1. SSH Agent Plugin 설치

Jenkins 대시보드 > Jenkins 관리 > 플러그인 관리 > 설치 가능 > SSH Agent 플러그인을 검색하고 설치 및 재실행

2. Spring Server에 공개키 설정

.ssh/authorized_keys 파일에 Jenkins Server의 public key를 추가

# jenkins server
cat /root/.ssh/id_rsa.pub

# Spring server에 pub키 추가
vi /root/.ssh/authorized_keys

3. Jenkins Credentials 등록

Jenkins 대시보드 > Jenkins 관리 > Security > Credentials

**Stores scoped to Jenkins**에 Domain이 (global)인 text 클릭 Global credentials (unrestricted)로 이동한다. 왼쪽 메뉴의 Add credentials를 눌러 credentials를 추가

  • Kind
    • SSH Username with private key
  • ID
    • ssh
  • Username
    • root (default)
  • Private Key
    • Enter directly 체크 -> jenkins server의 private key 입력

      cat /root/.ssh/id_rsa

🔟 Jenkins, Discord 연동

Discord > 서버 설정 > 연동 > 웹후크 > 새 웹후크

스크린샷 2023-11-26 오후 12.21.58.png

이름과 채널명은 자유롭게 작성하시면 됩니다. 웹후크 URL은 추후에 Pipeline에 넣을겁니다.

1️⃣1️⃣ Jenkins, Github Webhook 설정

github에 push가 발생했을 때 자동으로 jenkins의 빌드가 실행될 수 있게 해줍니다. 하지만, 이것은 선택

1. Github Integration Plugin 설치

Jenkins 대시보드 > Jenkins 관리 > 플러그인 관리 > 설치 가능 > Github Integration 플러그인을 검색하고 설치 및 재실행

2. Jenkins Pipeline 설정

Jenkins Pipeline 구성 > General > Github project에 repo URL을 입력해준다. 그리고 아래 GitHub hook trigger for GITScm polling 체크

3. Github Webhook 추가

Github Repository에서 Settings > Webhooks > Add Webhook 을 눌러 Webhook을 추가한다.

  • Payload URL
    • [Jenkins Server URL]:[Jenkins Server 포트]/github-webhook/
  • Content
    • typeapplication/x-www-form-urlencoded
  • 나머지는 모두 default 설정 유지
    • Add webhook 버튼을 눌러 Webhook을 추가 -> 목록에서 녹색 체크 아이콘이 생성되면 성공

1️⃣2️⃣ Pipeline Script

  • [] 안에 내용은 []를 지우고 주소나 변수 명을 작성하시면 됩니다.
pipeline {
    agent any

    environment {
        imagename = "[이미지 명]"
        registryCredential = 'docker-hub'
        dockerImage = ''
    }

    stages {
        stage('Prepare') {
          steps {
            echo 'Clonning Repository'
            git url: 'Github Repository SSH Url([git@github.com 으로 시작하는 깃허브 주소])',
              branch: 'main',
              credentialsId: 'github'
            }
            post {
             success { 
               echo 'Successfully Cloned Repository'
             }
           	 failure {
               error 'This pipeline stops here...'
             }
          }
        }

        stage('Bulid Gradle') {
          steps {
            echo 'Bulid Gradle'
            dir('.'){
                sh './gradlew build -x test'
                sh './gradlew clean build'           
					  }
          }
          post {
            failure {
              error 'This pipeline stops here...'
            }
          }
        }
        
        stage('Bulid Docker') {
          steps {
            echo 'Bulid Docker'
            script {
                dockerImage = docker.build imagename
            }
          }
          post {
            failure {
              error 'This pipeline stops here...'
            }
          }
        }

        stage('Push Docker') {
          steps {
            echo 'Push Docker'
            script {
                docker.withRegistry( '', registryCredential) {
                    dockerImage.push() 
                }
            }
          }
          post {
            failure {
              error 'This pipeline stops here...'
            }
          }
        }
        
				stage('Docker Run') {
            steps {
                echo 'Pull Docker Image & Docker Image Run'
                sshagent (credentials: ['ssh']) {
                    sh "ssh -o StrictHostKeyChecking=no root@[공인 IP] 'docker pull jongdo737/carraway'" 
                    sh "ssh -o StrictHostKeyChecking=no root@[공인 IP] 'docker ps -q --filter name=carry | grep -q . && docker rm -f \$(docker ps -aq --filter name=carry); docker run -d --name carry -p 8080:8080 jongdo737/carraway'"
                    
                }
            }
        }
    }
		post {
          success {
              discordSend description: "알림테스트", 
                footer: "테스트 빌드가 성공했습니다.", 
                link: env.BUILD_URL, result: currentBuild.currentResult, 
                title: "테스트 젠킨스 job", 
                webhookURL: "[DisCord Webhook 주소]"
          }
          failure {
              discordSend description: "알림테스트", 
                footer: "테스트 빌드가 실패했습니다.", 
                link: env.BUILD_URL, result: currentBuild.currentResult, 
                title: "테스트 젠킨스 job", 
                webhookURL: "[DisCord Webhook 주소]"
          }
      }
}

아래 웹후크 주소를 추가해 줍니다. 그리고 Build를 누르게 되면 아까 설정한 Discord의 채널로 메시지가 전송되게 됩니다.

Jenkins Build 에러 해결한 내용 보러가기

[Jenkins] NCP, Github, Docker, Spring Boot, Discord 로 CI/CD 구축하기 - 에러 해결 방법

참고 블로그

https://hyeinisfree.tistory.com/23
https://malwareanalysis.tistory.com/353

profile
고민이 많은 개발자

0개의 댓글