본문 바로가기
Git-DevOps

GitLab-Jenkins 연동 및 Pipeline을 이용한 CI 구축

by 간식주인 2022. 9. 20.

사설 GitLab을 구축하고 k8s cluster에 jenkins 설치까지 되었다면, 이제 본격적으로 CI를 위한 GitLab-Jenkins간 연동을 진행해보도록 하겠습니다.

 

요건

 - GitLab에서 Application code push 시 자동으로 빌드되어 docker hub에 이미지 저장하기 

 

환경

  • 1 GitLab Server
  • Jenkins(k8s 내부에 배포한 pod로 진행합니다.)
  • docker hub(계정 가입 후 Repository 생성)
  • Intellij (샘플 웹 애플리케이션 수정을 위한 툴)

Process

  1. GitLab 및 jenkins 구축 확인
  2. GitLab Repository 생성 및 SSH key 등록
  3. jenkins 플러그인 설치 및 pipeline 설정
  4. Spring boot를 이용한 샘플 웹 애플리케이션 생성 및 Jenkinsfile, Dockerfile 생성 후 GitLab 업로드
  5. k8s 매니페스트 파일 생성 후 GitLab 업로드
  6. pipeline 동작 확인과 웹 애플리케이션 구동

주의 사항

- 세부적인 설정에 유의하며 단계를 차근차근 진행합니다.

 

==================================================================================

1. GitLab 및 jenkins 구축 확인

이전글을 참고하여 GitLab 및 Jenkins를 구축합니다.

 

NCP Server를 이용한 GitLab 서버 구축

개발자분들이 소스코드를 관리하기 위해 Git Hub를 많이 사용하지만, 간혹 금융존 혹은 공공존 보안 요건으로 애플리케이션 코드를 외부 Repository에 보관하면 안되는 경우가 있었습니다. 이러한

enginnersnack.tistory.com

 

 

kubernetes cluster를 이용한 jenkins 구축

최근 개발자분들과 많은 커뮤니케이션을 진행하던 도중 CI/CD에 대한 수요가 늘어나고 있는 것을 알게 되었습니다. 아무래도 자동으로 빌드/배포가 되면 개발자분들 입장에선 코드에만 전념하면

enginnersnack.tistory.com

 

2. GitLab Repository 생성 및 SSH key 등록

GitLab 설치 시 만들어두었던 유저로 로그인 한 뒤, 애플리케이션 코드를 담을 레포지토리를 한 개와 매니페스트 파일을 담을 레포지토리 한 개를 생성합니다.

애플리케이션 코드를 담을 레포지토리 : spring

매니페스트 파일을 담을 레포지토리 : manifest

Repo 생성 과정
생성된 2개의 Repository

 

 

Repo 생성 이후 로컬 pc에서 GitLab에 별도의 계정 입력 없이 ssh 접속을 하기위한 key를 생성합니다.(리눅스/Mac)

ssh-keygen -t rsa -C "test@naver.com" 
(엔터 3번 입력)

chmod 700 ~/.ssh/id_rsa*
cat ~/.ssh/id_rsa.pub

윈도우의 경우 Git CMD를 사용하여 ssh 키를 생성할 수 있습니다.

[Git CMD]
ssh-keygen -t rsa -b 4096 -C 'test@naver.com' -f D:/id_rsa

 

ssh key를 생성했다면 public key 값을 조회하여 GitLab에 등록합니다.

오른쪽 상단에 프로필 클릭 > Edit profile > 왼쪽 메뉴바에서 SSH Keys 클릭 후 조회한 public key 값을 입력하고 Add key를 클릭합니다.

 

이제 로컬 PC에서 GitLab을 연결할 때 별도의 로그인 없이 바로 사용할 수 있습니다.

 

 

3. jenkins 플러그인 설치 및 pipeline 설정

먼저  웹훅과 원격 저장소 연결을 위해 서버간 네트워크 설정을 진행해야합니다.

서버의 ACG로 이동하여 GitLab 공인아이피 <-> k8s 내부 jenkins가 위치한 워커 노드의 공인아이피를 상호 오픈을 시켜주어야 합니다.

*내부 아이피를 통해 연결한다면 내부 아이피를 등록하면 됩니다.

서버 간 공인 아이피를 ACG에 등록

 해당 작업을 마치면 다음 작업을 진행합니다.

 

jenkins latest 이미지에서 제공하는 기본 플러그인 이외에도 추가로 플러그인을 설치해야 합니다.

Jenkins 관리 > 플러그인 관리로 이동하여 플러그인 설치를 진행합니다.

jenkins lts 설치시 설치되어 있음(자동설치) : git plugin, Gradle Plugin

설치 필요 : GitLab, Generic Webhook Trigger, Post build task, Docker, Docker Commons, Docker Pipeline, Docker API, SSH Agent

*만약 설치할 플러그인이 보이지 않는다면 "설치 가능" 항목으로 이동하여 설치를 진행합니다.

 

플러그인 설치 후 Jenkins 관리 > Global Tool Configuration 으로 이동하여 몇가지 설정을 진행합니다.

먼저 jdk를 설정합니다.

 

이후 Gradle을 설정합니다.

 

마지막으로 Docker 경로를 설정합니다.

모든 Global Configuration 설정을 마치고 Save를 클릭하여 저장합니다.

 

추가로 한개의 credentials을 등록합니다.

해당 credentials의 경우 비밀번호를 통한 인증 방식을 사용하고 이후 manifest repo를 clone 및 push할 때 사용하게 됩니다.(jenkinsfile에서 사용 예정)

 

Jenkins 메인 페이지로 돌아와서 아이템 > Pipeline 생성을 클릭합니다.

pipeline 이름은 "pipeline"으로 설정하고 진행하겠습니다.

 

pipeline을 클릭한 뒤 왼쪽 메뉴에서 "구성"을 클릭합니다.

 

Build Triggers로 이동한 뒤 GitLab 설정을 진행합니다.

이 때, webhook URL을 따로 기록해둡니다.

하단으로 스크롤을 내리고 "고급"을 클릭하여 Secret Token을 생성하기 위해 Generate를 클릭합니다.

 

이후 기록해둔 webhook url과 secret token을 이용하여 GitLab의 Spring 레포지토리에서 웹훅 설정을 진행합니다.

GitLab에서 Spring Repo 선택 후 Settings > Webhooks 클릭

 

URL과 Secret Token을 입력하고 "Add webhook"을 클릭합니다.

추가로 Trigger > Push events에서 사용하는 branch를 입력합니다.

 

다시 Jenkins 설정으로 돌아와서 상단 메뉴바중 Pipeline으로 이동합니다.

아래와 같이 설정을 진행합니다.

Definition : Pipeline script from SCM

SCM : Git

Repository URL : git@git.kbotest.shop:kbo/spring.git

(자신이 만든 Git Repository의 Git url)

 

Credentials에서 Add를 클릭하고 아래와 같이 설정을 진행합니다.

Kind : SSH Username with private key

ID : GitLab_SSH_Key (임의 지정 가능)

Username : test@naver.com (임의 지정 가능)

Private Key 항목에서 Enter directly를 클릭하고 로컬 PC에서 발급 받았던 SSH Private key를 조회하여 입력합니다.

cat ~/.ssh/id_rsa

 

만약 다음과 같은 에러가 뜬다면 K8s 마스터 노드로 접속하여 jenkins 컨테이너에서 원격 저장소에 대한 인증을 진행해야 합니다.

 

Control Plane(마스터 노드)에서 jenkins pod 정보를 조회하고 명령어를 통해 컨테이너 내부로 진입합니다.

kubectl get all -n jenkins
kubectl exec -it -n jenkins pod/jenkins-deployment-56fb4b665-nrb9r /bin/bash
(컨테이너 아이디는 환경마다 다르니 수정이 필요합니다.)

 

Jenkins에서 확인한 에러 문구 중  "git ls-remote -h -- git@git.kbotest.shop:kbo/spring.git HEAD"를 복사하여 입력합니다.

명령어 입력 후 "yes"를 입력하고 컨트롤+C 명령어로 빠져나옵니다.

 

jenkins Repository URL 설정 부분에서 URL을 삭제한 뒤 다시 입력하면 에러가 사라진 것을 확인할 수 있습니다.

 

모든 설정을 마치면 "저장"을 클릭합니다.

 

4. Spring boot를 이용한 샘플 웹 애플리케이션 생성 및 Jenkinsfile, Dockerfile 생성

GitLab-Jenkins 연동 이후 실제로 테스트를 진행하기 위해 샘플 웹 애플리케이션을 로컬에 생성합니다.

spring initialzr를 통해 웹 애플리케이션을 생성합니다.

아래와 같이 설정을 진행하고 디펜던시로 Spring web을 추가한 뒤 Generate를 클릭합니다.

 

웹 애플리케이션을 수정하기 위해 Intellij를 설치합니다.

https://www.jetbrains.com/idea/

 

IntelliJ IDEA: The Capable & Ergonomic Java IDE by JetBrains

A Capable and Ergonomic Java IDE for Enterprise Java, Scala, Kotlin and much more...

www.jetbrains.com

 

Intellij로 다운로드 받은 spring boot web project를 연 뒤, src > main > static 폴더 밑에 index.html 파일을 생성합니다.

(만약 툴 설치가 어렵다면 해당 경로에 메모장 같은 프로그램을 이용하여 파일을 생성해도 됩니다.)

<!doctype html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>
<body>
<h1>my name is world</h1>
</body>
</html>

 

springboot 폴더 내부에 Dockerfile과 Jenkinsfile을 생성할 차례입니다.

Jenkinsfile을 통해 파이프라인이 수행되며 Dockerfile을 통해 웹 애플리케이션을 이미지로 만들 수 있습니다.

Dockerfile

FROM adoptopenjdk/openjdk8
#ARG HOST_JAR_FILE_PATH=./springtest-0.0.1-SNAPSHOT.jar # Jar 경로 환경변수 설정.
COPY ROOT.jar /ROOT.jar
# 해당 Docker image로 Container를 생성/실행하는 시점에 아래의 커맨드가 수행되도록한다.
CMD ["java",  "-jar", "/ROOT.jar"]

 

Jenkinsfile

*사용자의 Git URL에 따라 변경이 필요하며 Dockerhub에 이미지 저장을 위한 repository를 생성해야 합니다.

pipeline {
    agent any
    tools {
      gradle 'gradle_7.5.1'
    }
 environment {
      dockerHubRegistry = 'dogsnack/spring' /* URL변경에 따른 수정 필요 */
      /* dockerHubRegistryCredential = '{Credential ID}'*/
  }
  stages {

    stage('Checkout Application Git Branch') {
        steps {
            git credentialsId: 'credential_kbo',
                url: 'http://git.kbotest.shop/kbo/spring.git', /* URL변경에 따른 수정 필요 */
                branch: 'main'
        }
        post {
                failure {
                  echo 'Repository clone failure !'
                }
                success {
                  echo 'Repository clone success !'
                }
        }
    }


    stage('gardle Jar Build') {
            steps {
                sh 'chmod +x gradlew'
                sh './gradlew bootjar'
            }
            post {
                    failure {
                      echo 'Gradle jar build failure !'
                    }
                    success {
                      echo 'Gradle jar build success !'
                    }
            }

    }

    stage('Docker Image Build') {
            steps {
                sh "cp build/libs/springtest-0.0.1-SNAPSHOT.jar ./ROOT.jar"
                sh "docker build . -t ${dockerHubRegistry}:${currentBuild.number}"
                sh "docker build . -t ${dockerHubRegistry}:latest"
            }
            post {
                    failure {
                      echo 'Docker image build failure !'
                    }
                    success {
                      echo 'Docker image build success !'
                    }
            }
    }


    stage('Docker Image Push') {
            steps {
                      sh "echo 도커허브비밀번호 | docker login -u dogsnack --password-stdin"
                      sh "docker push ${dockerHubRegistry}:${currentBuild.number}"
                      sh "docker push ${dockerHubRegistry}:latest"
                      sleep 10 /* Wait uploading */
                  
            }
            post {
                    failure {
                      echo 'Docker Image Push failure !'
                      sh "docker rmi ${dockerHubRegistry}:${currentBuild.number}"
                      sh "docker rmi ${dockerHubRegistry}:latest"
                    }
                    success {
                      echo 'Docker image push success !'
                      sh "docker rmi ${dockerHubRegistry}:${currentBuild.number}"
                      sh "docker rmi ${dockerHubRegistry}:latest"
                    }
            }
    }
    
    stage('K8S Manifest Update') {
        steps {
            git credentialsId: 'credential_kbo',
                url: 'http://git.kbotest.shop/kbo/manifest.git', /* URL변경에 따른 수정 필요 */
                branch: 'main'
            sh "git config --global user.email 'test@naver.com'"
            sh "git config --global user.name 'kbo'"
            sh "sed -i 's/spring:.*\$/spring:${currentBuild.number}/g' springapp_deployment.yaml"
            sh "git add springapp_deployment.yaml"
            sh "git commit -m '[UPDATE] springapp ${currentBuild.number} image versioning'"
            sshagent (credentials: ['GitLab_SSH_Key']) {
                sh "git remote set-url origin git@git.kbotest.shop:kbo/manifest.git" /* URL변경에 따른 수정 필요 */
                sh "git push -u origin main"
            }  
        }
        post {
                failure {
                  echo 'K8S Manifest Update failure !@'
                }
                success {
                  echo 'K8S Manifest Update success !!'
                }
        }
    }

  }
}

 

필요한 파일을 모두 작성한 후 "spring" repository에 git push를 진행합니다.

*초기 계정 설정
git config --global user.name "name"
git config --global user.email "email"

git remote add origin git@git.kbotest.shop:kbo/spring.git
git branch -m main

git pull origin main

git add .

git commit -m "first commit -> spring repo"
git push origin main

 

정상적으로 파일이 올라간 것을 확인할 수 있습니다.

 

 

5. k8s 매니페스트 파일 생성 후 GitLab 업로드

Jenkinsfile에 작성한 내용중 k8s manifest file update하는 부분이 있으므로 쿠버네티스 클러스터에 웹 애플리케이션을 배포하기 위한 yaml 파일을 작성해주어야 합니다.

yaml 파일의 경우 pod 배포를 위한 springapp_deployment.yaml 과 워커노드를 통해서 외부에서 접속하기 위한 springapp_service.yaml을 작성합니다.

springapp_deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata: 
  name: springapp-deploy
spec:
  replicas: 2
  selector:
    matchLabels:
      type: spring
      version: v1
  template:
    metadata:
      labels:
        type: spring
        version: v1
    spec:
      containers:
      - name: springapp-01
        image: dogsnack/spring:latest
        ports:
        - containerPort: 8080

springapp_service.yaml

apiVersion: v1
kind: Service
metadata:
  name: springapp-service
spec:
  selector:
    type: spring
  ports:
  - port: 8080
    targetPort: 8080
    nodePort: 30050
  type: NodePort

 

작성한 manifest 파일을 control plane에 옮겨놓습니다.

*아직 웹 애플리케이션에 대한 이미지를 만들지 않았으므로 해당 파일들을 k8s cluster에 배포할 경우 에러가 발생하게 됩니다.

 

 

6. Pipeline 동작 확인과 웹 애플리케이션 구동

로컬pc에서 웹 애플리케이션의 소스코드를 변경한 후 git push를 진행합니다.

저는 index.html 파일의 내용을 수정하였습니다.

git add .
git commit -m "3rd"
git push origin main

 

로컬 환경에서 git push가 발생하면 설정해놓은 pipeline이 동작합니다.

정상적으로 pipeline이 동작되는지 확인하며, 만약 에러가 발생한다면 해당 스텝을 눌러서 로그를 확인하여 트러블 슈팅을 진행합니다.

 

마지막 단계인 k8s manifest update 작업까지 완료되었으므로 GitLab의 manifest Repository를 확인하면 springapp_deployment.yaml 파일이 변경된 것을 확인할 수 있습니다.(이미지 버전이 변경됨)

 

이전 단계에서 control plane에 옮겨둔 springapp_deployment.yaml 파일의 내용을 위와 같이 수정합니다.(git clone을 해당 파일들을 가져와도 됩니다.)

이후 kubectl 명령어를 통해 2개의 파일을 배포합니다.

kubectl apply -f springapp_deployment.yaml
kubectl apply -f springapp_service.yaml

kubectl get all

 

워커노드의 공인아이피와 노드포트(30050)로 웹 애플리케이션 접속을 진행합니다.

로컬에서 변경한 내용을 정상적으로 확인할 수 있습니다.

 

[여담]

처음 구성하시는 분들이라면 아마 많이 어렵지 않을까 싶습니다.(저는 아직도 어렵습니다 ㅎㅎ;;;;)

글 작성에 최대한 주의를 기울이긴 했는데 워낙 환경이 다양하다보니 에러가 많이 발생하실수도 있습니다.

발생하는 에러에 대해서는 댓글을 남겨주시면 제가 아는선까지는 최대한 도와드리도록 하겠습니다.

 

이제 CI를 구축하였으니 다음글에서는 CD 구축으로 찾아뵙겠습니다~!