[DevOps] Jenkins, Docker로 Spring Boot CI/CD 구축하기

Hyein Kim·2021년 9월 12일
17
post-thumbnail

👩‍💻 CI/CD

구성 요소

  • Jenkins Server : AWS EC2 Ubuntu 18.04
  • Spring Boot Server : AWS EC2 Ubuntu 18.04
  • Github Repository
  • Docker Hub Repository

진행 순서

  1. Jenkins Server에 Docker 설치
  2. Jenkins Server에 Docker를 이용하여 Jenkins 실행
  3. Jenkins 접속
  4. Jenkins와 Github 연동
  5. Jenkins와 Docker Hub 연결
  6. Jenkins Server와 Spring Boot Server SSH 연결 설정
  7. Jenkins와 Slack 연동
  8. Jenkins Pipeline 구성
  • Spring Boot Project Github Repository Clone
  • Gradle Build
  • Docker Build
  • Docker Push
  • Spring Boot Server SSH 연결
    • Docker Pull
    • Docker Run
  • Slack Notification
    사전에 Jenkins Server, Spring Boot Server를 위한 AWS EC2 인스턴스 2개와 Spring Boot Project가 올라가있는 Github Repository와 Docker Hub Repository가 생성되어 있어야 한다. 대부분의 설정은 Jenkins Server에 접속하여 진행한다. Spring Boot Server는 SSH 연결 설정 시 public key를 등록할 때만 접속한다.

💻 Jenkins Server EC2

EC2 초기 설정

sudo apt update
sudo apt upgrade
sudo apt install build-essential

Docker 설치

1. 기본 설정, 사전 설치

$ sudo apt update
$ sudo apt install apt-transport-https ca-certificates curl software-properties-common

2. 자동 설치 스크립트 활용

  • 리눅스 배포판 종류를 자동으로 인식하여 Docker 패키지를 설치해주는 스크립트를 제공
$ sudo wget -qO- https://get.docker.com/ | sh

3. Docker 서비스 실행하기 및 부팅 시 자동 실행 설정

$ sudo systemctl start docker
$ sudo systemctl enable docker

4. Docker 그룹에 현재 계정 추가

$ sudo usermod -aG docker ${USER}
$ sudo systemctl restart docker
  • sudo를 사용하지 않고 docker를 사용할 수 있다.
  • docker 그룹은 root 권한과 동일하므로 꼭 필요한 계정만 포함
  • 현재 계정에서 로그아웃한 뒤 다시 로그인

5. Docker 설치 확인

$ docker -v

Docker로 Jenkins 설치하기

1. Jenkins 이미지 파일 내려받기(lts 버전)

$ docker pull jenkins/jenkins:lts

2. 내려받아진 이미지 확인

$ docker images

3. Jenkins 이미지를 Container로 실행

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

4. 돌아가고 있는 Container 확인

$ docker ps

✅ EC2 프리티어에서 Jenkins가 자꾸 죽어요..🤦‍♀️

프리티어 EC2는 너무 작고 소중합니다. Jenkins를 구성하고 빌드하면 멈춰버립니다. AWS CloudWatch로 사용률을 보면 아주 그래프를 뛰쳐 나갑니다. 그래서 사전에 swap 메모리를 할당해서 이러한 문제를 방지한다.

프리티어 EC2 기본 RAM이 1GB이니, 2GB(128MB * 16) swap 파일을 만들어 할당합니다.

$ 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

🤵‍♂️ Jenkins

Jenkins 접속

1. 브라우저에서 [EC2 인스턴스 URL]:8080으로 접속

2. 암호 입력

/var/lib/jenkins/secrets/initialAdminPassword를 확인해야 하는데 Jenkins Container에 접속하여 얻어오거나 "docker logs jenkins" 명령어를 사용하면 된다.

// Container에 접속하지 않고 확인
$ docker logs jenkins
// Container 접속
$ docker exec -it jenkins bash

// 암호 파일 확인
$ sudo cat /var/lib/jenkins/secrets/initialAdminPassword

3. Install suggested plugins로 플러그인 설치

4. Jenkins 계정 생성

Jenkins 메인 대시보드 페이지가 나오면 성공

Jenkins, Github 연동

1. ssh 키 생성

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

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

EC2에 접속하면 기본 유저가 ubuntu이기 때문에 /home/ubuntu/.ssh에 id_rsa와 id_rsa.pub이 생성된다.

2. Github Deploy Key 등록

만들어 놓은 Github Repository > Settings > Deploy Keys > Add deploy key로 접속한다.
Title은 Jenkins로 지어 주고(마음대로 해도 된다), Key 부분에 id_rsa.pub에 들어있는 public key 값을 넣어준다. 아래 명령어로 확인 가능하다.

$ cd /home/ubuntu/.ssh
$ cat id_rsa.pub

3. Jenkins Credentials 등록

Jenkins 대시보드 > Jenkins 관리 > Manage Credentials > Credentials에 접속한다.
Store Jenkins에 Domain이 (global)인 화살표를 눌러 Global credentials (unrestricted)로 이동한다.
왼쪽 메뉴의 Add credentials를 눌러 credentials를 추가한다.

  • Kind
    SSH Username with private key
  • ID
    github -> 마음대로 지어도 된다. 다만 Pipeline Script 작성 시 credentialsId로 사용되니 식별할 수 있도록 하자.
  • Username
    root (default)
  • Private Key
    Enter directly 체크 -> private key 입력
    여기서 private key는 Jenkins Server에서 생성한 id_rsa이다. 아래 명령어로 확인 가능하다.
    $ cd /home/ubuntu/.ssh
    $ cat id_rsa
    -----BEGIN OPENSSH PRIVATE KEY-----
    ...
    ... 이와 같은 형태의 key가 private key 입니다 ...
    ...
    -----END OPENSSH PRIVATE KEY-----
  • OK를 눌러 키를 생성

Jenkins, Docker Hub 연결

1. Docker Plugin 설치

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

2. Docker Hub Credentials 등록

Jenkins 대시보드 > Jenkins 관리 > Manage Credentials > Credentials에 접속한다.
Store Jenkins에 Domain이 (global)인 화살표를 눌러 Global credentials (unrestricted)로 이동한다.
왼쪽 메뉴의 Add credentials를 눌러 credentials를 추가한다.

  • Kind
    Username with password
  • Username
    본인의 Docker Hub ID
  • Password
    본인의 Docker Hub Password
  • ID
    docker-hub -> 마음대로 지어도 된다. 다만 Pipeline Script 작성 시 credentialsId로 사용되니 식별할 수 있도록 하자.
  • OK를 눌러 키를 생성

3. Jenkins Container 내부에 Docker 설치

Jenkins Pipeline에서 Docker 명령어를 사용할 수 있도록 Jenkins Container 내부에 Docker를 설치해야 한다.

  • Jenkins Container에 접속
$ docker exec -it jenkins bash
  • Docker 설치
$ docker run -d -p 8080:8080 -p 50000:50000 -v /jenkins:/var/jenkins -v /home/ubuntu/.ssh:/root/.ssh -v /var/run/docker.sock:/var/run/docker.sock --name jenkins -u root jenkins/jenkins:lts

Jenkins Container를 가동할 때 위에 처럼 외부 Docker volume을 연결해놓아서 아래처럼 docker.sock 권한 변경만 하면 Container 내부에서 docker 명령어가 사용 가능하다고 하는데 Pipeline Script를 작성하고 Build했더니 docker 명령어 사용 부분에서 command not found 에러가 발생하여 그냥 Container 내부에도 Docker를 설치하였다.
Docker 설치 방법은 위에서 EC2 내에 Docker를 설치한 방법과 동일하다. 다만 Container 내부에 sudo, vi, wget이 설치되어 있지 않아 차례대로 설치 후 Docker를 설치해야 한다.

  • docker.sock 권한 변경
    $ sudo chmod 666 /var/run/docker.sock

✅ Jenkins Container 내부에 Docker를 설치하는 것에 대하여..

  1. Docker가 설치되어 있는 Jenkins 이미지 파일을 사용한다.
  2. docker exec 명령어로 Jenkins Container 내부로 접속한 다음 내부에 Docker를 설치한다.
  3. docker 경로와 docker.sock 파일 경로를 도커 볼륨에 추가해 사용한다.
  • 이 방법을 사용하더라도 Container 내부에 docker-cil는 설치해야 한다는 글을 보았다.
  • 실험 필요.

Jenkins, Spring Boot Server SSH 연결

Jenkins로 Gradle 빌드하고 Dockerfile로 도커 이미지를 빌드해서 Docker Hub에 Push하고 Spring Boot Server에서 도커 이미지를 Pull해서 실행하게 하기 위해 필요하다. Jenkins Pipeline Script에서 SSH를 사용하여 Spring Boot Server의 명령어 실행을 할 수 있도로 하는 것이다.

1. SSH Agent Plugin 설치

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

2. Spring Boot Server 접속

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

// Jenkins Server에서 public key 확인
$ cat /home/ubuntu/.ssh/id_rsa.pub

// Spring Boot Server에 Jenkins Server public key 추가
$ vi /home/ubuntu/.ssh/authorized_keys

4. Jenkins Credentials 등록

Jenkins 대시보드 > Jenkins 관리 > Manage Credentials > Credentials에 접속한다.
Store Jenkins에 Domain이 (global)인 화살표를 눌러 Global credentials (unrestricted)로 이동한다.
왼쪽 메뉴의 Add credentials를 눌러 credentials를 추가한다.

  • Kind
    SSH Username with private key
  • ID
    ssh -> 마음대로 지어도 된다. 다만 Pipeline Script 작성 시 credentialsId로 사용되니 식별할 수 있도록 하자.
  • Username
    root (default)
  • Private Key
    Enter directly 체크 -> private key 입력
    여기서 private key는 Jenkins Server에서 생성한 id_rsa이다. 아래 명령어로 확인 가능하다.
    $ cd /home/ubuntu/.ssh
    $ cat id_rsa
    -----BEGIN OPENSSH PRIVATE KEY-----
    ...
    ... 이와 같은 형태의 key가 private key 입니다 ...
    ...
    -----END OPENSSH PRIVATE KEY-----
  • OK를 눌러 키를 생성
    사실상 위의 Jenkins와 Github 연동 시 생성한 Jenkins Credentials과 동일하다. 하지만 ID를 구분하여 사용하고자 SSH Credentials을 별도로 생성하였다.

✅ SSH Agent Plugin vs Publish Over SSH Plugin

전에 Jenkins Pipeline이 아닌 Freestyle project로 동일한 CI/CD를 구성했을 때는 SSH Agent Plugin이 아닌 Publish Over SSH Plugin을 사용했다. 하지만 Pipeline으로 변경하면서 Script에서 SSH를 사용하는 방법으로 SSH Agent Plugin이 더 많이 검색되고 간단해보여 SSH Agent Plugin을 사용하였다. 하지만 두 개의 플러그인의 차이를 나중에 보다 자세하게 알아보아야겠다.
Freestyle project로 구성했을 때 Publish Over SSH를 설치 및 설정했던 방법은 아래와 같다.

1. Publish Over SSH 플러그인 설치

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

2. Publish Over SSH 플러그인 설정

Jenkins 대시보드 > Jenkins 관리 > 시스템 설정에서 Publish Over SSH 영역의 고급 버튼을 눌러 설정

  • Path to key는 private key의 경로를 입력한다.
    지금까지는 Jenkins Server에서 jenkins 컨테이너에 접속하지 않고 key들을 접근했지만 jenkins는 컨테이너로 실행중이니까 Jenkins Server의 key 경로를 모른다. 즉 /home/ubuntu/.ssh/id_rsa를 입력하면 안된다. 우리는 다행이 Jenkins Server에서 컨테이너를 실행할 때 /home/ubuntu/.ssh를 /root/.ssh와 연결해 놓았기 때문에 /root/.ssh/id_rsa를 입력하면 된다.
  • Key는 private key 값을 넣어주면 된다.
    id_rsa 파일 내용을 복사해서 넣어준다.
  • Name은 접속할 ssh 서버의 이름을 입력한다.(마음대로 지어주세요)
  • Hostname은 접속할 서버의 주소를 넣어주세요.
    Spring Boot Server EC2 인스턴스 URL을 넣어준다.
  • Username은 접속할 유저명을 넣어준다.
    기본 유저인 ubuntu를 입력한다.
  • Test Configuration 버튼을 눌러 정상적으로 연결 되는지 확인한다.

Jenkins, Slack 연동

1. Slack Notification Plugin 설치

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

2. Slack에 Jenkins 앱 추가

Slack 앱에서 하단의 앱 추가 > jenkins 검색 > jenkins 선택 후 연동을 하면 연동 가이드 웹페이지에 하위 도메인과 토큰이 출력된다. 이를 이용하여 Slack Credentials을 등록한다.

3. Slack Credentials 등록

Jenkins 대시보드 > Jenkins 관리 > Manage Credentials > Credentials에 접속한다.
Store Jenkins에 Domain이 (global)인 화살표를 눌러 Global credentials (unrestricted)로 이동한다.
왼쪽 메뉴의 Add credentials를 눌러 credentials를 추가한다.

  • Kind
    Secret text
  • Scope
    Global
  • Secret
    Slack에서 jenkins 앱 연동 시 출력된 토큰
  • ID
    slack -> 마음대로 지어도 된다. 다만 Pipeline Script 작성 시 credentialsId로 사용되니 식별할 수 있도록 하자.
  • OK를 눌러 키를 생성

4. Slack 설정

Jenkins 대시보드 > Jenkins 관리 > 시스템 설정에서 Slack 영역을 설정

  • Workspace
    Slack에서 jenkins 앱 연동 시 출력된 하위 도메인
  • Credential
    위에서 등록한 Slack Credential
  • Default channel / member id
    Jenkins 알림이 전송되길 원하는 채널명

🦸‍♂️ Jenkins Pipeline

이제 드디어 Pipeline을 구성할 준비가 됐다! Jenkins 대시보드 > 새로운 Item에서 item name을 입력하고 Pipeline을 선택, OK 버튼을 누른다.

Jenkins, Github Webhook 연동

Github Repository에 push event가 발생했을 때 자동으로 Build가 실행되게 하기 위해 Pipeline과 Github Webhook을 연동해야 한다.

1. Github Integration Plugin 설치

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

2. Jenkins Pipeline 설정

  • Github project 설정
    Pipeline 구성 화면 > General 영역에서 Github project를 선택한다. Project url에 본인의 Github Repository Url을 입력한다. 이 때 Repository Url은 Clone 시 사용하는 HTTPS Url(.git으로 끝남)을 입력한다.
  • Build Triggers 설정
    Pipeline 구성 화면 > Build Triggers 영역에서 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 type
    application/x-www-form-urlencoded
  • 나머지는 모두 default 설정 유지
    -Add webhook 버튼을 눌러 Webhook을 추가 -> 목록에서 녹색 체크 아이콘이 생성되면 성공

Pipeline Script 작성

pipeline {
    agent any

    environment {
        imagename = "docker build로 만들 이미지 이름"
        registryCredential = 'Docker Hub Credential ID'
        dockerImage = ''
    }

    stages {
        stage('Prepare') {
          steps {
            echo 'Clonning Repository'
            git url: 'Github Repository SSH Url(git@github.com로 시작)',
              branch: 'Clone 받아올 Branch 이름',
              credentialsId: 'Github Credential ID -> github'
            }
            post {
             success { 
               echo 'Successfully Cloned Repository'
             }
           	 failure {
               error 'This pipeline stops here...'
             }
          }
        }

        stage('Bulid Gradle') {
          steps {
            echo 'Bulid Gradle'
            dir('.'){
                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 Credential ID -> ssh']) {
                    sh "ssh -o StrictHostKeyChecking=no [Spring Boot Server username]@[Spring Boot Server IP 주소] 'docker pull [도커이미지 이름]'" 
                    sh "ssh -o StrictHostKeyChecking=no [Spring Boot Server username]@[Spring Boot Server IP 주소] 'docker ps -q --filter name=[컨테이너 이름] | grep -q . && docker rm -f \$(docker ps -aq --filter name=[컨테이너 이름])'"
                    sh "ssh -o StrictHostKeyChecking=no [Spring Boot Server username]@[Spring Boot Server IP 주소] 'docker run -d --name [컨테이너 이름] -p 8080:8080 [도커이미지 이름]'"
                }
            }
        }
    }
    post {
        success {
            slackSend (channel: '#채널명', color: '#00FF00', message: "SUCCESSFUL: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
        failure {
            slackSend (channel: '#채널명', color: '#FF0000', message: "FAILED: Job '${env.JOB_NAME} [${env.BUILD_NUMBER}]' (${env.BUILD_URL})")
        }
    }
}

✅ 특정 branch push event에만 동작하도록 설정..

위와 같은 Github Webhook 설정으로는 모든 branch의 push event에 Pipeline이 동작하게 된다.
특정 branch push event에만 동작하도록 하기 위해서는 추가 플러그인과 설정이 필요한 것 같은데 더 공부가 필요하다.

✅ application.yml과 같은 비밀정보가 들어있는 파일을 어떻게 넣어주지..

여러 비밀정보가 들어있는 application.yml을 Github에 올라가지 않도록 해놓았는데 해당 application.yml이 resources 폴더에 있지않아 Gradle Build 오류가 계속 발생했다.
따로 application.yml을 서버에 넣어주고 이를 copy하여 루트 디렉토리에 위치시키게 하고 java -jar 실행시 -Dspring.config.location=/application.yml과 같이 동작하도록 Dockerfile을 구성해보기도 했으나 test 시 오류가 발생했다.
따라서 현재는 그냥 workspace 내부 resources 폴더에 직접 넣어놓았다. 하지만 더 좋은 방법을 찾아봐야겠다.

💬 회고


이번이 Jenkins로 CI/CD를 구축하는 게 세 번째인데 이제야 좀 알겠다.. Jenkins랑 점점 정드는 중.. 더 공부해야지~

📗참고

profile
I'm free

0개의 댓글