본문 바로가기

Kuberentes

Kubernetes 환경에서 Jenkins 구성 및 docker image 기반 batch job 수행

개요

  • kubernetes 환경에서 Jenkins를 구성하고 backup/restore 가능하도록 구성
  • jenkins kubernetes plugin을 이용해서 각 배치 job마다 agent pod이 생성되어 docker image 기반으로 배치 job을 수행하도록 구성

Jenkins 구성

  • helm chart : https://github.com/jenkinsci/helm-charts
  • helm chart의 values.yaml을 가져와서 Jenkins 설정 및 리소스 등을 커스터마이징 한다. (각 옵션에 대한 설명)
  • installPlugins는 helm chart init 시점에 설치되는 플러그인 리스트로, 플러그인 버전이 업데이트 되는 경우 호환성에 문제가 생기므로 최소한을 유지하기 위해 chart에 기본적으로 선언되어있는 플러그인을 명시하고 thinBackup 플러그인을 추가했다.
  • jenkins를 백업/복구 하지 않는 환경이라면 installPlugins 대신 additionalPlugins에 추가해도 무방하다.
installPlugins:
  - thinBackup:1.10
  - kubernetes:1.29.2
  - workflow-aggregator:2.6
  - git:4.6.0
  - configuration-as-code:1.47

Backup

  • Jenkins ThinBackup 플러그인으로 매일 00:00에 Jenkins 디렉토리 전체를 백업하고, 매일 00:05분에 변경분 데이터를 커밋하고 있음
  • 백업 데이터의 사이즈가 크기때문에(현재 프로젝트 기준 120MB) 전체 데이터를 매번 커밋하는 방법보다 변경분 데이터만 최신 커밋으로 유지하고, 이전 백업 데이터가 필요한 경우 git으로 해당 시점 백업데이터 가져오는 방법을 추천

ThinBackup 설정

  • Jenkins 관리 > ThinBackup

  • Settings

  • Backup directory : 백업 데이터가 저장될 디렉토리
  • Backup schedule for full backups : 백업이 수행될 cron
  • max number of backup sets : 백업 셋의 개수 (-1로 정의하면 무제한)
    • 백업 데이터의 사이즈가 크기때문에 마스터 노드의 disk full을 방지하기 위해 1개로 제한함
  • Wait until Jenkins/hudson is idle to perform a backup : 안전한 상태에서 백업을 하기 위해 백업 시점에 잡이 수행중이라면 끝날때 까지 대기했다가 백업을 진행함
  • Force Jenkins to quiet mode after specified minutes : 입력한 (분)동안 잡이 계속해서 실행중인 상태라면 젠킨스를 quiet 모드(어떤 잡도 수행되지 않는 상태)로 변경하고, 백업이 끝나면 quiet 모드를 해제함
    • master에서 수행되는 배치 Job이 없고, build results를 백업하지 않을 것이므로 필요하지 않은 옵션이라 disable 함
  • Backup build results : 빌드결과를 백업. enable 시 백업 데이터 사이즈가 1.4GB, disable 시 백업 데이터 사이즈가 120MB로 크게 차이나서 disable 함
  • Backup 'userContent' folder : jenkins의 userContent 설정을 백업. 사용하지 않아 disable
  • Backup Plugin archives : 플러그인을 백업에 포함. 플러그인 버전 호환성을 위해 포함
  • cron 설정 이후에도 백업이 수행되지 않는다면 젠킨스 재시작 필요

Backup Job

선행작업

  • Jenkins 관리 > Nodes > master
  • 마스터 노드에서 백업이 수행될 수 있도록 executors를 1로 설정, 라벨이 일치할때만 잡이 수행되도록 Usage 설정

Backup Job 생성

  • master 노드에서만 수행되어야 하므로 레이블을 설정한다. (Node and Label Plugin 필요)

  • 백업 커밋이 수행될 cron 설정 (backup cron 이후의 시간으로 설정)
  • 백업 파일을 복사해서 git에 커밋하는 쉘 스크립트 작성
  • Git Publisher 플러그인으로 merge & push

Restore

  • 마스터 pod에 접속해서 git의 백업 파일을 복사하고 Restore 수행
  • 백업 파일 복사
> k exec pod/jenkins -it /bin/bash 
> git clone $BACKUP_REPOSITORY /var/jenkins_home/FULL-2021-04-07_00-02 # 날짜 포맷만 일치한다면 어떤 날짜라도 상관없음
  • Jenkins 관리 > ThinBackup > Restore
    • Restore with plugin 체크
  • restart API 호출 ex) http://jenkinsUrl/restart
  • 재시작 대기
    • 로그 확인 : k logs -f pod/jenkins -c jenkins

Job 구성

  • kubernetes-agent를 사용하기 위해서 pipeline script 기반으로 스크립트를 작성해야 한다.
// kubernetes에 새로 생성되는 jenkins-agent의 이름. 중복되지 않도록 UUID 기반 생성
def LABEL = "agent-${UUID.randomUUID().toString()}"
// 도커 이미지 저장소 및 태그
def IMAGE = ""

// agent pod 템플릿
podTemplate(
        // 잡이 수행될 레이블의 이름. 위에서 생성한 LABEL 변수의 값을 사용
        label: LABEL,
        // agent pod 내부에서 사용해야 하는 도커 컨테이너 명세. docker, java, maven 등 필요에 따라서 여러개의 컨테이너를 정의할 수 있음
        containers: [
                containerTemplate(name: 'docker', image: 'docker', command: 'cat', ttyEnabled: true)
        ],
        volumes: [
                // 파드가 도커를 이용할 수 있도록 host의 docker.sock 파일을 mount
                hostPathVolume(mountPath: '/var/run/docker.sock', hostPath: '/var/run/docker.sock')
        ]) {

    node(LABEL) {
        try {
            // 도커 이미지를 가져오는 stage
            stage('Pull Docker image') {
                container('docker') {
                    sh """
                    docker pull ${IMAGE}
                """
                }
            }
            // 도커 이미지가 실행되는 stage
            stage('Run Batch') {
                container('docker') {
                    sh """
                    docker run -e JAVA_TOOL_OPTIONS='
                        -Xms2g -Xmx2g -XX:+UseG1GC
                        // 컨테이너 위에서 java 실행 시 heap size를 컨테이너 내부에서 계산하도록 하는 옵션. jenkins-kubernetes 예제를 보고 추가했는데, 성능테스트 시 OOME가 발생하면 수정이 필요함
                        // https://dzone.com/articles/running-a-jvm-in-a-container-without-getting-kille
                        -XX:+UnlockExperimentalVMOptions 
                        -XX:+UseCGroupMemoryLimitForHeap
                        -Duser.timezone=Asia/Seoul
                        -Dspring.profiles.active=${PROFILE}
                        -Dspring.batch.job.names=${JOB_NAME}' \
                    ${IMAGE} \
                    // program argument(job parameter)는 java 어플리케이션 실행과 동일하게 이미지 argument 뒤에 위치해야한다.
                """
                }
            }
            currentBuild.result = 'SUCCESS'
        } catch (err) {
            currentBuild.result = 'FAILURE'
            // 실패 시 fail 메세지 전송
            sendMessage(buildFailMessage())
        } finally {
            if (currentBuild.getPreviousBuild().result == 'FAILURE' && currentBuild.result == 'SUCCESS') {
                // 이번 job은 성공인데, 이전 잡이 fail 이라면 fix 메세지 전송
            }
        }
    }
}