본문 바로가기
IT/Cloud

[CI/CD] Kubernetes 환경 Jenkins: 1년 운영하며 겪은 성능 최적화와 안정화 사례

by 수누다 2026. 6. 19.

[CI/CD] Kubernetes 환경 Jenkins: 1년 운영하며 겪은 성능 최적화와 안정화 사례

안녕하세요, 13년차 서버실 지킴이입니다. 오늘은 Kubernetes(쿠버네티스) 환경에서 Jenkins(젠킨스)를 1년 넘게 운영하며 겪었던 삽질과 그 과정에서 얻은 성능 최적화, 안정화 노하우를 풀어볼까 합니다. 많은 분들이 CI/CD(지속적 통합/지속적 배포) 파이프라인 구축에 Jenkins를 사용하고 계실 텐데, 특히 Kubernetes 위에서 Jenkins를 돌리면서 “이게 맞나?” 싶었던 경험들 있으실 거예요. 제가 딱 그랬거든요. 😅

처음엔 마냥 좋다고 생각했던 Jenkins on Kubernetes가 생각보다 많은 운영 난이도를 요구했습니다. 툭하면 죽는 빌드 에이전트, 느려터진 빌드 시간, 예상치 못한 마스터 다운까지... 정말이지 밤낮없이 씨름했던 기억이 생생합니다. 이 글에서는 제가 직접 부딪히며 해결했던 문제들과 그 과정을 여러분들께 멘토처럼 상세히 알려드릴게요. 혹시 비슷한 고민을 하고 계셨다면, 제 경험이 조금이나마 도움이 되기를 바랍니다! 🙏

Kubernetes 환경 Jenkins 아키텍처 개요: 마스터와 동적 에이전트 파드들의 상호작용

Jenkins on Kubernetes, 왜 선택했을까요? (개념 설명)

Jenkins는 워낙 유명한 CI/CD 자동화 도구죠. 프로젝트 빌드, 테스트, 배포 등 개발 프로세스의 여러 단계를 자동화해주는 역할을 합니다. 그런데 이걸 왜 굳이 Kubernetes 위에서 돌리냐고요? 간단히 말해서, 유연성과 확장성 때문입니다.

  • 동적 에이전트 프로비저닝 (Dynamic Agent Provisioning): Kubernetes의 가장 큰 장점 중 하나인데요, 빌드가 필요할 때만 Jenkins Agent(젠킨스 에이전트) Pod(파드)를 생성하고, 빌드가 끝나면 자동으로 제거할 수 있습니다. 덕분에 리소스를 효율적으로 사용할 수 있고, 동시에 여러 빌드를 처리할 때도 필요한 만큼 에이전트를 늘릴 수 있죠.
  • 높은 가용성 (High Availability): Kubernetes는 컨테이너화된 애플리케이션의 고가용성을 보장합니다. Jenkins Master(젠킨스 마스터) Pod가 문제가 생겨도 Kubernetes가 자동으로 다른 노드에 재시작해주니, 서비스 중단 시간을 최소화할 수 있습니다.
  • 환경 일관성 (Environment Consistency): 모든 빌드가 컨테이너 안에서 이뤄지므로, 개발 환경과 동일한 환경에서 빌드 및 테스트를 진행할 수 있어서 “내 로컬에서는 되는데 서버에서는 안 돼요!” 하는 문제를 줄일 수 있습니다.

이런 장점들 덕분에 저도 Kubernetes 환경으로 Jenkins를 옮기기로 결정했었죠. 하지만 현실은 녹록지 않았습니다. 😅

실전 구현: Jenkins Kubernetes 플러그인과 Pod Template

Kubernetes에서 Jenkins를 운영하려면 Jenkins Kubernetes Plugin(젠킨스 쿠버네티스 플러그인)이 필수인데요. 이 플러그인이 Jenkins Master와 Kubernetes 클러스터 간의 통신을 담당하면서 동적으로 에이전트 Pod를 생성하고 관리하는 핵심 역할을 수행합니다. 플러그인 설치 후, 가장 중요한 설정은 Pod Template(파드 템플릿)입니다.

Pod Template은 Jenkins Agent Pod가 어떤 이미지로, 어떤 리소스를 가지고 생성될지 정의하는 YAML 설정이거든요. 처음에는 기본 설정으로 시작했지만, 곧바로 성능 병목에 부딪혔습니다. 다음은 제가 주로 사용했던 Pod Template의 핵심 부분입니다.

apiVersion: v1
kind: Pod
spec:
  containers:
  - name: jnlp
    image: jenkins/inbound-agent:4.11.2-1-jdk11
    resources:
      requests:
        cpu: "500m"
        memory: "1Gi"
      limits:
        cpu: "1"
        memory: "2Gi"
    # ... (생략) ...
  - name: build-tools
    image: my-private-registry/custom-build-image:latest
    command: ["cat"]
    tty: true
    resources:
      requests:
        cpu: "1"
        memory: "2Gi"
      limits:
        cpu: "2"
        memory: "4Gi"
    # ... (생략) ...
  volumes:
  - name: jenkins-workspace
    emptyDir: {}
  # ... (생략) ...

여기서 `jnlp` 컨테이너는 Jenkins와 통신하는 기본 에이전트고, `build-tools`는 실제 빌드 도구들이 들어간 커스텀 이미지더라고요. 여기서 중요한 부분은 바로 resources 섹션입니다. 처음에는 이 부분을 대충 설정했다가 빌드 에이전트들이 자꾸 죽는 현상을 겪었어요. ⚠️

Jenkins UI Kubernetes 플러그인 설정 화면: Pod Template 구성 예시

Jenkins UI 내 Kubernetes 플러그인 설정 화면: Pod Template 구성 예시

⚠️ 1년 운영하며 겪은 삽질과 성능 최적화/안정화 사례

자, 이제 본론입니다. 1년 동안 겪었던 문제들과 그 해결책들을 공유해 드릴게요.

1. 빌드 에이전트 OOMKilled (메모리 부족) 현상

가장 흔하게 겪었던 문제입니다. 빌드 도중에 에이전트 Pod가 갑자기 OOMKilled(Out Of Memory Killed, 메모리 부족으로 종료됨) 상태가 되면서 빌드가 실패하는 거죠. 처음엔 원인을 몰라 헤맸는데, 알고 보니 Pod Template의 resources.limits.memory 설정이 너무 낮았던 것이었습니다.

  • 문제점: 빌드 프로세스가 예상보다 많은 메모리를 사용하면서 Kubernetes가 Pod를 강제로 종료시킴.
  • 해결책: resources.requestsresources.limits를 현실적으로 설정하는 것이 중요합니다. requests는 Pod가 스케줄링될 때 필요한 최소한의 리소스, limits는 Pod가 최대로 사용할 수 있는 리소스예요. 처음에 빌드를 여러 번 돌려보면서 실제로 필요한 메모리 양을 측정했습니다. 빌드 과정에서 peak 메모리 사용량을 모니터링해서 넉넉하게 잡아주는 게 중요하더라고요.
    resources:
      requests:
        cpu: "1"
        memory: "2Gi" # 최소 2GB 요청
      limits:
        cpu: "2"
        memory: "4Gi" # 최대 4GB까지 사용 허용

💡 팁: requests는 Kubernetes 스케줄러가 노드를 선택하는 기준이 되고, limits는 CGroup(컨트롤 그룹)에 의해 Pod의 최대 리소스 사용량을 제한합니다. 너무 타이트하면 OOMKilled 되고, 너무 넉넉하면 리소스 낭비가 될 수 있으니 적절한 튜닝이 필요합니다.

2. 느린 이미지 풀링 (Image Pull) 시간

동적 에이전트의 가장 큰 장점 중 하나지만, 매번 빌드 에이전트 Pod가 생성될 때마다 필요한 Docker Image(도커 이미지)를 다운로드(Pull)해야 한다는 단점도 있습니다. 특히 빌드 에이전트 이미지가 크거나, 여러 개의 이미지를 사용하는 경우 빌드 시작 시간이 너무 길어지는 문제가 발생했습니다.

  • 문제점: 빌드 시작 시 Docker Image Pull 시간 때문에 전체 빌드 시간이 길어짐.
  • 해결책:
    1. 로컬 Docker Registry(도커 레지스트리) 사용: 사설 Docker Registry를 클러스터 내부에 구축하고, 이미지를 미리 이곳에 캐싱해두면 Pull 속도가 훨씬 빨라집니다.
    2. Node에 Image Pre-pulling: Kubernetes Worker Node(워커 노드)에 DaemonSet(데몬셋)을 이용해서 자주 사용하는 에이전트 이미지를 미리 Pull 해두는 방법도 효과적이었습니다. 이렇게 하면 Pod가 스케줄링될 때 이미지가 이미 로컬에 있어 바로 실행될 수 있죠.
    3. Jenkins Agent 이미지 최적화: 빌드에 필요한 최소한의 도구만 포함된 경량 이미지를 만들었습니다. 불필요한 레이어를 줄이고, 베이스 이미지를 최신으로 유지하는 것도 중요하더라고요.

3. Jenkins Master의 안정성 확보

아무리 에이전트가 잘 돌아가도 Master가 불안정하면 모든 CI/CD 파이프라인이 멈춥니다. Master Pod의 잦은 재시작이나 데이터 손실은 정말 끔찍하죠.

  • 문제점: Master Pod의 리소스 부족, 데이터 손실 위험.
  • 해결책:
    1. Persistent Volume Claim (PVC) 사용: Jenkins Master의 /var/jenkins_home 디렉터리는 빌드 설정, 플러그인, 빌드 이력 등 중요한 데이터가 저장되는 곳이거든요. 반드시 Persistent Volume Claim(PVC, 영구 볼륨 클레임)을 사용하여 데이터를 영구적으로 저장해야 합니다. 저는 NFS(네트워크 파일 시스템) 기반의 PV(영구 볼륨)를 사용했는데, 클라우드 환경에서는 EBS, Azure Disk, GCE Persistent Disk 등을 활용할 수 있습니다.
    2. Master Pod 리소스 충분히 할당: Master Pod도 적절한 CPU와 Memory를 할당해야 합니다. 너무 적으면 UI가 느려지거나, 플러그인 로딩 중 OOMKilled 될 수 있습니다.
    3. Configuration as Code (CasC) 도입: Jenkins 설정을 YAML 파일로 관리하는 CasC(Configuration as Code)는 Master의 안정성을 높이는 데 크게 기여했습니다. 설정을 Git에 버전 관리하고, Jenkins 재시작 시 자동으로 적용되도록 하여 휴먼 에러를 줄이고 재현 가능한 환경을 구축할 수 있었습니다.
최적화 전후 Jenkins 빌드 시간 및 Kubernetes 리소스 사용량 비교 Grafana 대시보드

최적화 후 빌드 시간 단축 및 리소스 사용량 안정화 결과

4. 네트워크 성능 문제 (대용량 아티팩트 전송)

빌드 결과물(Artifacts, 아티팩트)이 크거나, 빌드 과정에서 외부 리소스(Maven Repository 등)를 자주 다운로드받는 경우 네트워크 병목이 발생할 수 있습니다.

  • 문제점: 대용량 아티팩트 전송 및 외부 리소스 다운로드로 인한 빌드 지연.
  • 해결책:
    1. 클러스터 내부 캐싱 프록시: Maven, npm 등의 패키지 매니저를 위한 캐싱 프록시(예: Sonatype Nexus, Artifactory)를 클러스터 내부에 구축하여 외부 네트워크 트래픽을 줄였습니다.
    2. 빠른 스토리지 사용: PVC에 사용하는 스토리지가 느리면 빌드 과정에서 파일 I/O(입출력) 성능 저하가 발생합니다. 가능한 한 SSD 기반의 고성능 스토리지를 사용하는 것이 좋습니다.
    3. 불필요한 아티팩트 최소화: 빌드 후 Jenkins에 저장하거나 전송하는 아티팩트의 크기를 최소화하도록 파이프라인을 최적화했습니다.

검증 및 결과: 드디어 안정화! 🎉

위에 언급된 최적화들을 적용하고 나니, 거짓말처럼 Jenkins 환경이 안정화되기 시작했습니다. 빌드 실패율은 현저히 줄었고, 빌드 시간도 평균 30% 이상 단축되는 성과를 얻을 수 있었습니다. 특히 동적 에이전트의 효율적인 리소스 사용 덕분에 클러스터 비용도 절감할 수 있었죠.

  • 빌드 성공률: 60% → 95% 이상으로 향상
  • 평균 빌드 시간: 5분 → 3분 내외로 단축 (프로젝트별 상이)
  • 리소스 사용 효율: 유휴 에이전트 감소로 클러스터 리소스 활용률 증가

이러한 개선 사항들은 Prometheus(프로메테우스)와 Grafana(그라파나)를 통해 모니터링하면서 실시간으로 확인했습니다. 특히 빌드 성공/실패율, 큐(Queue)에 대기 중인 빌드 수, 에이전트 Pod의 리소스 사용량 등을 꾸준히 트래킹하면서 추가적인 개선점을 찾아나갔습니다. 📈

Kubernetes Jenkins 운영 최적화 주요 포인트 요약 인포그래픽

Kubernetes Jenkins 운영 최적화 주요 포인트 요약 인포그래픽

마무리: 멘토로서의 조언과 다음 단계

Kubernetes 환경에서 Jenkins를 운영하는 것은 분명 도전적인 일입니다. 처음에는 “이거 왜 이렇게 어렵지?” 싶다가도, 하나하나 문제를 해결해나가면서 시스템이 안정화되는 모습을 보면 정말 뿌듯하더라고요. 제가 13년 동안 인프라 엔지니어로 일하면서 느낀 건, 삽질은 결국 성장의 밑거름이 된다는 겁니다.

이 글에서 다룬 내용들이 여러분의 Kubernetes Jenkins 운영에 조금이나마 도움이 되었으면 좋겠습니다. 혹시 더 깊은 질문이나 다른 경험담이 있다면 언제든지 댓글로 남겨주세요! 저도 처음엔 헷갈렸던 부분이 많았거든요.

다음 단계로는 GitOps(깃옵스) 철학을 기반으로 Argo CD(아르고 CD) 같은 도구를 Jenkins와 연동하여 배포 파이프라인을 더욱 자동화하고 고도화하는 방안을 고민하고 있습니다. CI/CD 여정은 끝이 없는 것 같아요. 계속해서 배우고 실험하며 더 나은 방법을 찾아나가야겠죠? 😊