jenkins 를 이용한 프로젝트 배포

프로젝트 배포에 jenkins 를 이용한다는 개념을 설명한 도식이야.

가장 중요한 점은 프로젝트 소스가 gitlab 에서 관리되고 있어야 한다는 점이지.

그래야만 jenkins 가 소스를 ① 내려받아서 ② 빌드하고 ③ 배포할 수가 있어.

이 개념에서 가장 중요한 행위자는 jenkins 야. jenkins 에서 gitlab 에 접속하고, webserver 에 접속할 수 있어야 해. 그러기 위해서는 jenkins 에 gitlab 계정과 webserver 계정이 관리되어야 한다는 점이지.

gitlab 계정 생성

gitlab 에 jenkins 가 접속할 수 있는 계정을 하나 만들어볼께. 계정을 만들기 위해서는 관리자 권한으로 로그인해야겠지?


Admin Area 의 Users 로 들어가면 “New user” 버튼으로 사용자계정을 생성할 수가 있어.


jenkins 에서 접속해서 clone 하는 역할을 수행하기 때문에 jenkins 라는 이름으로 등록해봤어.

Access level 은 Regular 로 설정했어.

이렇게 계정을 생성하면 Users 목록에 jenkins 라는 계정이 생성된 것을 확인할 수가 있어.


이제 이 계정으로 로그인이 가능하도록 패스워드를 설정해줄 차례야. 아직 관리자 권한의 계정으로 로그인된 상태이지. Users 목록에서 계정 오른쪽의 Edit 버튼을 클릭하면 해당 계정의 정보를 편집할 수 있는 화면이 표시가 돼. 여기에서 패스워드 입력란을 발견할 수가 있어.


처음에 계정을 생성할 때에는 없던 입력값이지. 임의의 패스워드값을 입력해서 해당 계정으로 로그인이 가능하도록 해볼께.

이제 관리자 권한의 계정에서 로그아웃하고, jenkins 계정으로 로그인을 해보자.


로그인하자마자 패스워드를 변경하는 화면이 표시가 돼. 여기서는 현재의 패스워드와 새로운 패스워드가 동일하게 입력되어도 새로운 패스워드로 설정이 가능하지.

패스워드 재설정이 끝나면 다시 로그인을 해줘야 해.

로그인에 성공했으면 이 jenkins 계정에서 대해서 Personal Access Token(PAT) 을 생성해 주어야 해. 외부에서 jenkins 의 패스워드를 이용하여 접근하는게 아니라, 새로 생성하는 PAT 를 이용하여 접근할 수 있도록 하기 위해서지.

계정 아이콘을 클릭해서 메뉴가 표시되면 Preferences 메뉴항목을 선택하면 돼.


가운데쯤 Access Tokens 메뉴가 보일거야.


오른쪽에 Add new token 버튼을 클릭해서 이 계정의 Personal Access Tokens 를 생성하는거야.


Token name 은 이 토큰의 사용 목적에 알맞게 다른 토큰들과 구분될 수 있는 값을 입력하면 돼. Select scopes 에서는 jenkins 에서 gitlab 의 소스를 clone 할 때에만 사용할 것이기 때문에 read_repository 항목만 체크하면 될 것 같아.


이제 토큰이 생성되었으면 이 값을 어딘가에 잘 메모해 두어야 해. 이 화면이 지나가고 나면 이 값을 어디에서도 구할 수가 없기 때문이야.

gitlab 프로젝트 구성원 등록

gitlab 에서 해야 할 일이 아직 한가지 남아있어. jenkins 에서 특정 프로젝트의 소스를 clone 하기 위해서는 이용할 계정이 해당 프로젝트의 구성원으로 등록이 되어 있어야 해.

다시 관리자 권한의 계정으로 gitlab 에 로그인을 해볼께.

Admin Area 로 이동해서 Projects 목록 화면으로 이동해보자.


jenkins 를 이용해서 배포하고자 하는 프로젝트는 study 그룹 하위의 diary 프로젝트야. 이 프로젝트 항목의 링크(빨간색 사각형 영역)를 클릭해볼께.


오른쪽에 study 그룹의 멤버로 1개의 계정만 등록되어 있고, diary 프로젝트의 멤버는 0명인데, diary 프로젝트가 study 그룹에 포함되어 있기 때문에 상위 그룹에 할당된 멤버가 그대로 프로젝트의 멤버로 적용이 되기 때문에 woohaha 라는 계정은 이미 이 프로젝트의 멤버인거야.

jenkins 라는 계정을 이 프로젝트의 멤버로 등록을 하려면 프로젝트 멤버 오른쪽의 Manage access 버튼을 클릭하면 돼.


앞에서 설명했던 것처럼 Project members 목록에는 woohaha 계정이 나타나지? Source 컬럼에는 study 라고 그룹이름이 표시가 되고 있지.

오른쪽 위의 Invite members 버튼을 클릭해서 jenkins 계정을 이 프로젝트 멤버에 포함시켜줄거야.


Username 항목에 jenkins 라고 입력하면 gitlab 에 등록되어 있는 사용자계정에서 검색한 후에 목록으로 표시해줘. 목록의 항목을 클릭해주면 돼.

그 아래쪽에 Developer 라는 항목은 이 계정의 프로젝트에 대한 권한을 의미하는데, 적어도 Developer 는 되어야 소스를 clone 할 수가 있어.


이제 이 프로젝트의 멤버로서 2개의 계정이 등록된 것을 확인할 수가 있지.

webserver 계정

이제 webserver (10.10.1.2) 에 웹 프로그램을 실행시킬 계정을 하나 추가해볼께. 계정 추가 작업을 하기 위해서는 root 권한이 필요해.

$ sudo useradd -m webapp
$ sudo passwd webapp

모든 웹 애플리케이션을 실행시키기 위한 범용 계정으로 webapp 을 추가해봤어.

그리고 /var 디렉토리 아래에 웹 앱을 설치하기 위한 webapp 디렉토리를 생성해서 webapp 계정의 소유로 설정을 해봤어.

$ sudo mkdir -p /var/webapp
$ sudo chown webapp:webapp /var/webapp

jenkins 에 계정 등록

이제 jenkins 에 gitlab 계정을 등록하는 과정을 살펴볼께. 당연히 관리자 권한의 계정으로 로그인해야겠지.

로그인해서 대시보드의 “Jenkins 관리” 메뉴로 진입해볼께.


Security 그룹의 Credentials 항목을 클릭해서 들어가보자.


위쪽의 Credentials 목록에는 아무것도 없을 수가 있어. 지금부터 하는 작업이 Credentials 목록에 항목을 추가하는 과정이야.

(global) 링크를 클릭해서 credentials 항목을 추가하는 방법을 알아볼께.


Kind 항목으로 기본값인 Username with password 가 선택된 상태에서 Username 에는 gitlab 에서 등록한 계정(jenkins)을 입력해주고, Password 에는 메모해 둔 토큰값을 입력해주면 돼.

ID 에는 jenkins 에서 이 인증을 구별할 수 있는 값을 입력해주도록 해. 나는 jenkins_on_gitlab 이라는 이름을 입력했어. 이 ID 값은 이 인증을 대표하는 값으로 다른데서 사용하게 될거야.


이제 jenkins 에서 jenkins 라는 gitlab 계정을 통해서 gitlab 프로젝트에 접근하기 위한 설정이 준비가 되었어.

jenkins 에서 gitlab 프로젝트에 접근해서 소스를 내려받는 방법에 대해서 알아볼께.

Source Clone

Source Clone 할 대상 프로젝트로 diary 를 선택했어. 앞에서 이 프로젝트의 구성원으로 jenkins 계정을 등록했던걸 기억할거야.

jenkins 에 diary 프로젝트 배포를 위한 폴더를 생성해볼께.

Dashboard 의 새로운 Item 항목을 클릭해보자.


Item name 에 diary 라고 프로젝트 이름을 입력했고, Item type 으로 Folder 를 선택했어.


“OK” 버튼을 클릭하면 이 폴더 항목에 대한 추가 속성을 설정하는 화면이 표시되는데, 아무것도 설정하지 않은 상태에서 “Save” 버튼을 클릭해도 돼.


간단하게 설명하자면 Display Name 은 폴더 이름이 아닌 다른 값을 표시하기 위해서 입력하는 값이고, Description 은 이 폴더에 대한 간략한 설명을 입력하는 값이야.


자, 이제 방금 만든 diary 폴더로 진입이 되어 있는 상태가 되었지. 이 곳에 New Item 을 선택해서 Jenkins 작업(Job)을 만들어나가 볼거야.


Item name 으로 clonesource 라고 입력하고 Item type 으로는 Pipeline 을 선택한 다음에 “OK” 버튼을 클릭해볼께.


이 화면이 diary 프로젝트에 대한 jenkins 작업(Job)을 구성하는 화면이야. 이 구성요소 중에서 Pipeline 항목에 내용을 채워넣어볼께.


오른쪽 콤보박스에서 “Hello World” 항목을 선택하면 기본적인 Pipeline script 가 입력되거든. 그 뼈대 코드를 조금씩 수정해볼께.

pipeline {
    agent any

    stages {
        stage('CloneSource') {
            steps {
                echo 'Cloning...'
            }
        }
    }
}

각 stage 는 작업 단계를 의미해. stage 를 그룹핑하는 노드가 stages 가 되는거야.

stage 는 하위에 steps 로 구성되는데, 이 steps 안에 작업 내용을 기록해주면 되지. 지금은 gitlab 에 접속해서 diary 프로젝트 소스를 clone 하는 작업을 작성해보려고 하는데, 아래처럼 기록해주면 돼.

pipeline {
    agent any

    stages {
        stage('CloneSource') {
            steps {
                echo 'Cloning...'
                git branch: 'main', credentialsId: 'jenkins_on_gitlab', url: 'https://gitlab.woohahaapps.com:7528/study/diary.git'
            }
        }
    }
}

git 이라는 명령어를 사용해서 프로젝트 소스를 clone 하는건데,

branch: 는 clone 할 소스의 브랜치명을,
credentialsId: 로는 해당 프로젝트에 접근할 때의 계정정보(jenkins 에 등록한)를
url: 은 해당 프로젝트의 git 주소를 입력하는 방법이야.

url 에 입력한 https://gitlab.woohahaapps.com:7528/study/diary.git 프로젝트 주소는 gitlab 에서 얻을 수가 있지.


이렇게 작성한 상태에서 이 작업을 실행시켜볼께.


해당 아이템에서 “지금 빌드” 를 선택하면 이 작업이 실행되지.

만약 실행중에 에러가 발생하면 Console Output 메뉴를 선택해서 그 내용을 확인해볼 수가 있어.



에러가 발생한 원인은 jenkins 서버에 git 이 설치되어 있지 않기 때문이야.

jenkins 서버에 ssh 로 접속해서 git 을 설치해볼께.

$ which git

위 명령어를 실행했을 때 git 이 설치되어 있다면 git 이 설치되어 있는 경로를 표시해주거든.

위와 같은 에러가 발생했다면 아마도 아무것도 표시되지 않을거야.

$ sudo apt install git

위 명령어를 이용해서 jenkins 서버에 git 을 설치해줄 수가 있어.

git 설치가 완료된 후에 git –version 명령어로 설치된 git 의 버전을 확인하고, which git 으로 git 이 설치된 경로를 확인해보자.


이제 jenkins 에서 git 경로를 설정해볼께.


Jenkins 관리 > Tools 화면으로 이동해보자.


중간쯤에 Git installations 항목이 보이지. Git 실행파일의 경로값이 git 으로 기록되어 있을텐데, 앞에서 which git 으로 구해진 경로를 입력하고 저장해볼께.

다시 clonesource 아이템 화면으로 돌아가서 “지금 빌드” 명령을 실행시켜보면 성공할거야.

이렇게까지 수행했는데도 지금 빌드에서 에러가 발생한다면, jenkins 서버에서 gitlab 서버로의 접근 설정에 문제가 있는거야. jenkins 서버에 ssh 로 접근해서 git clone 명령을 직접 실행해서 원인을 찾아보는게 좋아.

나의 경우 gitlab 저장소 주소중에 gitlab.woohahaapps.com:7528 이 jenkins 에서 연결할 수 없는 상태였거든. 그래서 /etc/hosts 파일에 gitlab 서버 IP 주소와 gitlab.woohahaapps.com 도메인에 대한 정보를 추가해서 해결했어.

빌드가 성공하면 초록색을 확인할 수가 있어.

성공한 로그를 보여줄께.

Started by user 관리자
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/lib/jenkins/workspace/diary/clonesource
[Pipeline] {
[Pipeline] stage
[Pipeline] { (CloneSource)
[Pipeline] echo
Cloning...
[Pipeline] git
The recommended git tool is: NONE
using credential jenkins_on_gitlab
Cloning the remote Git repository
Cloning repository https://gitlab.woohahaapps.com:7528/study/diary.git
 > /usr/bin/git init /var/lib/jenkins/workspace/diary/clonesource # timeout=10
Fetching upstream changes from https://gitlab.woohahaapps.com:7528/study/diary.git
 > /usr/bin/git --version # timeout=10
 > git --version # 'git version 2.34.1'
using GIT_ASKPASS to set credentials gitlab account jenkins for clone source
 > /usr/bin/git fetch --tags --force --progress -- https://gitlab.woohahaapps.com:7528/study/diary.git +refs/heads/*:refs/remotes/origin/* # timeout=10
 > /usr/bin/git config remote.origin.url https://gitlab.woohahaapps.com:7528/study/diary.git # timeout=10
 > /usr/bin/git config --add remote.origin.fetch +refs/heads/*:refs/remotes/origin/* # timeout=10
Avoid second fetch
 > /usr/bin/git rev-parse refs/remotes/origin/main^{commit} # timeout=10
Checking out Revision bb682a62d0e6d8b22bac4fe87005ac14f6d1276e (refs/remotes/origin/main)
 > /usr/bin/git config core.sparsecheckout # timeout=10
 > /usr/bin/git checkout -f bb682a62d0e6d8b22bac4fe87005ac14f6d1276e # timeout=10
 > /usr/bin/git branch -a -v --no-abbrev # timeout=10
 > /usr/bin/git checkout -b main bb682a62d0e6d8b22bac4fe87005ac14f6d1276e # timeout=10
Commit message: "Merge branch 'woohaha' into 'main'"
First time build. Skipping changelog.
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS


clonesource 라는 젠킨스 JOB 이 소스를 내려받은 디렉토리는 위 로그에서 굵은 글씨로 표시한 /var/lib/jenkins/workspace/diary/clonesource 야, 각각의 JOB 마다 이 디렉토리 경로가 JOB 의 이름이 적용되어 달라진다는걸 기억해둬. 뒤에서 이 프로젝트를 빌드하더라도 최초 소스를 내려받은 디렉토리 경로가 기본이 된다는게 중요해.

프로젝트 빌드

이제 젠킨스 JOB 에 프로젝트 빌드 명령어를 추가해볼께.

젠킨스 diary 폴더로 이동해서 새로운 Pipeline Item 을 추가해보자.


item name 은 build project 야. item type 은 Pipeline 으로 설정했어. 이 아이템의 Pipeline script 는 다음과 같이 기록했어.

pipeline {
    agent any

    stages {
        stage('build') {
            steps {
                echo 'building project...'
                sh '''
                    cd /var/lib/jenkins/workspace/diary/clonesource
                    chmod +x gradlew
                    ./gradlew clean bootJar
                '''
            }
        }
    }
}

가장 먼저 cd 명령어로 앞에서 clonesource JOB 의 작업 경로로 이동했어. 프로젝트의 소스가 해당 경로에 저장되어 있기 때문이지. 만약 하나의 JOB 에서 clone source 와 build project 를 한다면 cd 명령어는 불필요하지.

그리고나서 gradlew 명령 스크립트에 실행 권한을 설정(chmod +x)하고, 이 스크립트를 이용해서 프로젝트를 빌드하고 있어.

이제 diary 폴더에는 2개의 JOB 이 등록되었어.


이제 build project JOB 을 실행시켜볼께.


다행스럽게도 한방에 성공을 했네.

이 JOB 의 실행 로그를 살펴볼께.

Started by user 관리자
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/lib/jenkins/workspace/diary/build project
[Pipeline] {
[Pipeline] stage
[Pipeline] { (build)
[Pipeline] echo
building project...
[Pipeline] sh
+ cd /var/lib/jenkins/workspace/diary/clonesource
+ chmod +x gradlew
+ ./gradlew clean bootJar
Downloading https://services.gradle.org/distributions/gradle-8.5-bin.zip
............10%.............20%............30%.............40%.............50%............60%.............70%.............80%............90%.............100%

Welcome to Gradle 8.5!

Here are the highlights of this release:
 - Support for running on Java 21
 - Faster first use with Kotlin DSL
 - Improved error and warning messages

For more details see https://docs.gradle.org/8.5/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)
> Task :clean UP-TO-DATE
> Task :compileJava
> Task :processResources
> Task :classes
> Task :resolveMainClassName
> Task :bootJar

BUILD SUCCESSFUL in 1m 35s
5 actionable tasks: 4 executed, 1 up-to-date
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
Finished: SUCCESS

gradle 8.5 버전을 내려받아서 프로젝트를 빌드하는데 성공을 했네.

프로젝트 배포

gradle 을 이용해서 빌드하면 아웃풋 파일은 build/libs 디렉토리에 생성이 돼.

젠킨스 서버에 ssh 로 접속해서 디렉토리의 파일을 조회해볼께.

/var/lib/jenkins/workspace/diary/clonesource 경로가 처음 소스를 clone 한 JOB 의 작업 디렉토리 경로이고 이 경로 아래의 build/libs 디렉토리에서 프로젝트 빌드 아웃풋 파일인 study.diary-0.0.1-SNAPSHOT.jar 파일이 조회되고 있어.

이제 이 아웃풋 파일을 웹서버로 복사해서 실행시키는 JOB 을 만들어볼께.

diary 폴더에 deploy 라는 이름의 Pipeline 아이템을 만들었어.


이 아이템의 Pipeline script 는 아래와 같이 작성했지(일부만 작성한 상태).

pipeline {
    agent any

    stages {
        stage('deploy') {
            steps {
                echo 'deploying output'
                sh '''
                    ssh -o StrictHostKeyChecking=no webapp@10.10.1.2 mkdir -p /var/webapp/diary
                '''
            }
        }
    }
}

이 상태에서 deploy JOB 을 실행시키면 다음과 같이 Permission 오류가 발생하게 돼.

Started by user 관리자
[Pipeline] Start of Pipeline
[Pipeline] node
Running on Jenkins in /var/lib/jenkins/workspace/diary/deploy
[Pipeline] {
[Pipeline] stage
[Pipeline] { (deploy)
[Pipeline] echo
deploying output
[Pipeline] sh
+ ssh -o StrictHostKeyChecking=no webapp@10.10.1.2 mkdir -p /var/webapp/diary
Permission denied, please try again.
Permission denied, please try again.
webapp@10.10.1.2: Permission denied (publickey,password).
[Pipeline] }
[Pipeline] // stage
[Pipeline] }
[Pipeline] // node
[Pipeline] End of Pipeline
ERROR: script returned exit code 255
Finished: FAILURE

jenkins 서버에서 webserver(10.10.1.2) 에 ssh 로 접근할 때 webapp 계정의 패스워드 입력이 필요한데, 그 과정이 수행되지 않기 때문이야. 그래서 아래와 같은 과정으로 ssh 연결할 때 패스워드 입력없이 접근될 수 있게 설정했어.

jenkins 서버(10.10.1.21) 에서 jenkins 계정의 private, public key 를 생성했어.

$ sudo su -s /bin/bash jenkins
$ ssk-keygen -t rsa
$ cd ~/.ssh
$ ls -al


이 중에서 public key 파일인 id_rsa.pub 파일을 webserver(10.10.1.2) 로 전송한 다음, webapp 계정의 authorized_keys 파일에 등록을 해줬지.

jenkins 서버(10.10.1.21) 에서
$ sudo su -s /bin/bash jenkins
$ scp ~/.ssh/id_rsa.pub webapp@10.10.1.2:~/jenkins@jenkins-ubuntu.pub

webserver(10.10.1.2) 에서
$ sudo su -s /bin/bash webapp
$ ssh-keygen -t rsa
$ cat ~/jenkins@jenkins-ubuntu.pub >> ~/.ssh/authorized_keys

이제 jenkins 서버에서 ssh 로 webserver 에 접속할 때 webapp 계정에 대해서 비밀번호를 입력하지 않아도 접속이 가능해졌어.

deploy JOB 을 실행해보면 성공하는걸 확인할 수 있어.


이제 배포에 필요한 나머지 스크립트도 작성을 해볼까?

pipeline {
    agent any

    stages {
        stage('deploy') {
            steps {
                echo 'deploying output'
                sh '''
                    ssh -o StrictHostKeyChecking=no webapp@10.10.1.2 mkdir -p /var/webapp/diary
                    cd /var/lib/jenkins/workspace/diary/clonesource/build/libs
                    scp study.diary-0.0.1-SNAPSHOT.jar webapp@10.10.1.2:/var/webapp/diary/
                '''
            }
        }
        stage('execute') {
            steps {
                echo 'executing program'
                sh '''
                    ssh -o StrictHostKeyChecking=no webapp@10.10.1.2 /usr/bin/java -jar /var/webapp/diary/study.diary-0.0.1-SNAPSHOT.jar > /dev/null 2>&1 &
                '''
            }
        }
    }
}


deploy JOB 을 실행시키면 webserver 에서 실행중인 프로세스를 확인할 수 있게 돼.


deploy JOB 에서 기존에 실행중인 프로세스를 종료시키는 로직이 없다는 부족함이 있긴 하지만, 어쨌든, 소기의 목적을 달성했어.

참고로 특정 프로세스를 강제로 종료시키는 명령어는 sudo kill -9 <PID> 야. <PID> 자리에 225557 값을 입력하면 돼.

이제 clonesource, build project, deploy 등 3개의 Pipeline Item 을 한꺼번에 실행시키는 Pipeline Script 를 하나더 만들어볼께.

onestop 이라는 이름으로 Pipeline Item 을 하나 추가하고 아래와 같이 Pipeline script 를 작성했어.

pipeline {
    agent any

    stages {
        stage('clonesource') {
            steps {
                build job: 'clonesource'
            }
        }
        stage('build project') {
            steps {
                build job: 'build project', wait: true
            }
        }
        stage('deploy') {
            steps {
                build job: 'deploy', wait: true
            }
        }
    }
}

이 JOB 을 실행시키면 3개의 stage 에 대한 결과가 나뉘어 표시되지.


실제로 diary 프로젝트가 정상적으로 실행된 것도 확인할 수가 있어.

정리

앞으로 다른 포스트를 통해서 이 프로젝트를 서비스화시키는 작업과 환경설정 파일을 분리해서 적용시키는 방법등에 관해서 심화시켜볼께.

Leave a Comment