Jenkins Batch Agent 쿠버네티스로 이관하기

hbjs97·2024년 9월 9일
post-thumbnail

기존 Jenkins를 사용하여 관리하던 배치 작업을 Kubernetes 클러스터로 이전하는 과정에서 발생한 문제점과 이를 해결한 과정을 정리한다.

배경 및 문제 상황

  • 보안상의 이유로 클러스터 내부에 있는 DB에 접근하려면, 같은 클러스터 내부에서 작업이 수행되어야 한다. 외부로 노출하지 않기 위해서이다.

  • Kubernetes에서 Jenkins를 배포하면, Job 실행 시마다 새로운 Agent Pod이 프로비저닝되고, 작업이 끝나면 해당 Pod이 종료된다. 이로 인해 다음과 같은 문제가 발생했다

    • Agent Pod 프로비저닝에 시간이 오래 걸림.
    • Agent Pod 내에서 Batch Job을 실행시키는데 추가적인 시간이 소요됨.
    • Batch Job을 실행하는 템플릿을 Job 안에 선언하면, 이중으로 프로비저닝되는 문제가 발생함.

시도했던 방법과 한계

1. Agent Pod을 Deployment로 구성하여 재사용 시도

  • Agent Pod을 Deployment로 구성하고, Jenkins Console에서 Add Node로 수동 등록하여 재사용을 시도했다.
  • Scale-out이 필요한 경우, 자동화된 방법을 찾지 못해 수동으로 노드를 추가해야 하는 불편함이 있었다.
  • 결론적으로, 이 방법은 효율적이지 않아 포기했다.

2. PersistentVolumeClaim(PVC)을 사용하여 jar 파일 공유 시도

  • 여러 Agent Pod이 하나의 PVC를 공유하여 jar 파일을 공유하려 했다.
  • RWX(ReadWriteMany) 모드로 PVC를 구성해야 했지만, 사용 중인 CSI 드라이버인 Longhorn이 NFS 관련 기능이 적용되지 않아 RWX 모드를 사용할 수 없었다.
  • RWX 모드로 PVC를 선언하면 NFS 마운트 실패 에러가 발생했다.
    RWX 모드 선언한 PVC  사용 시 NFS 마운트 실패 에러
  • RWO(ReadWriteOnce) 방식으로는 문제를 해결할 수 없어, 이 방법도 포기했다.

3. Jenkins의 archiveArtifacts 기능 활용

Jenkins 마스터 노드의 파일 시스템을 이용해 jar 파일을 관리하려 했으나 문제가 많았다.

  • Jenkins의 archiveArtifacts 기능을 사용하여 마스터 노드의 파일 시스템에 jar 파일을 저장하고, 이를 공유하려 했다.
  • 그러나 archiveArtifacts 기능은 경로를 지정할 수 없고, build 산출물에 포함되어 경로가 계속 바뀌는 문제가 있었다.
  • 또한, 덮어쓰기가 아닌 매번 새로운 산출물이 생성되어 디스크 용량을 많이 차지하는 문제가 있었다.
  • 이로 인해, archiveArtifacts 기능을 사용하는 방법도 포기했다.

최종 해결 방안

Jenkins Agent Pod의 재사용

  • Jenkins의 Kubernetes Cloud 설정에서 제공하는 Pod Template의 기능을 활용했다.
  • Helm Chart의 agent.enabled: true 설정 시 기본적으로 동적 Agent가 생성 및 제거되나, 추가로 아래 옵션을 설정하여 Agent Pod을 재사용 가능하게 했다.

Time in minutes to retain agent when idle
기본값(작업 종료 즉시 제거) 대신, 재사용을 위한 유지 시간을 설정했다.
Concurrency Limit
최대 Agent Pod 수를 제한하여 자원을 효율적으로 관리했다.

이를 통해 추가적인 Deployment나 PVC 없이 기본 기능만으로 환경을 구성할 수 있었다.

Batch jar 파일 관리 방식 개선

1. TeamCity의 Artifact Storage 기능 활용 (실패)

Build Configuration → General Settings

지정된 버킷 아래 app.jar 가 바로 저장되는게 아니라 프로젝트에 따라 prefix 가 자동으로 부여된다.
<BUCKET>/<PROJECT_PATH>/<TEAMCITY_BUILD_ID>/app.jar

저장 위치를 커스터마이징할 수 없어 경로 관리가 어려워 포기했다.

2. TeamCity에서 직접 Minio 저장소에 업로드 (채택)

  • TeamCity에서 직접 Minio 저장소에 업로드하는 방식을 채택했다.
  • 빌드 과정에서 Minio Client(mc)를 설치하여, 명시적이고 단순한 경로에 업로드하도록 구성했다.

필요한 환경변수를 설정해주고

curl https://dl.min.io/client/mc/release/linux-amd64/mc \
  --create-dirs \
  -o $HOME/minio-binaries/mc

chmod +x $HOME/minio-binaries/mc

$HOME/minio-binaries/mc alias set minio %env.MINIO_URL% %env.MINIO_ACCESS_KEY% %env.MINIO_SECRET_KEY%

# 빌드된 jar 파일을 버전과 공통 경로로 업로드
$HOME/minio-binaries/mc cp batch/build/libs/batch-0.0.1-SNAPSHOT.jar minio/artifacts/<PROJECT>/<PROFILE>/<SERVICE>/%build.vcs.number%/app.jar
$HOME/minio-binaries/mc cp minio/artifacts/<PROJECT>/<PROFILE>/<SERVICE>/%build.vcs.number%/app.jar minio/artifacts/<PROJECT>/<PROFILE>/<SERVICE>/app.jar

이로써 Jenkins pipeline에서는 항상 일정한 경로에서 최신 jar 파일을 다운로드할 수 있게 됐다.

Jenkins Pipeline 구성

최종적으로 적용된 Jenkins pipeline은 다음과 같다.

pipeline {
    agent any

    stages {
        stage('Download from Minio') {
            steps {
                script {
                    withAWS(endpointUrl: '<YOUR_MINIO_API>', credentials: '<YOUR_MINIO>') {
                        s3Download(
                            pathStyleAccessEnabled: true, 
                            file: "app.jar",
                            bucket: 'artifacts',
                            path: '<PROJECT>/<PROFILE>/<SERVICE>/app.jar',
                            force: true
                        )
                    }
                }
            }
        }
        
        stage('Run app.jar') {
            steps {
                script {
                    try {
                        def tz = TimeZone.getTimeZone('Asia/Seoul')
                        def requestDate = new Date().format('yyyyMMdd', tz)
                        def jobName = '<JOB_NAME>'

                        sh """
                        java -Dspring.profiles.active=prod \
                             -Dcom.amazonaws.sdk.disableEc2Metadata=true \
                             -jar app.jar \
                             --job.name=${jobName} \
                             requestDate=${requestDate}
                        """
                    } catch (Exception e) {
                        echo 'Error occurred while executing app.jar.'
                        error 'Failed to execute app.jar'
                    }
                }
            }
        }
    }

    post {
        success {
            echo 'Build succeeded and app.jar ran successfully.'
        }
        failure {
            echo 'Build failed or app.jar execution failed.'
        }
    }
}

이 구성으로 jar 파일을 효율적으로 관리하며 Kubernetes 환경에서 Jenkins의 배치잡 관리가 효과적으로 개선됐다.

0개의 댓글