k8s Jenkins 구성 및 GitHub 연동을 통한 자동 이미지 빌드 구성

현대 소프트웨어 개발에서는 코드 변경 사항을 신속하게 반영하고 배포할 수 있는 CI/CD 환경의 구축이 필수적입니다. 본 문서에서는 Kubernetes 환경 위에 Jenkins를 배포하고, GitHub와의 연동을 통해 코드 변경 시 자동으로 Docker 이미지를 빌드하여 Docker Hub에 푸시하는 파이프라인 구성을 설명합니다. 이를 통해 반복적인 수작업을 줄이고, 일관된 빌드 및 배포 프로세스를 확보할 수 있습니다.

목차

1. 환경 구성
2. Github 구성
3. Jenkins 구성
3-1. Pipeline 작성 및 Jenkinsfile 업로드
3-2. Github 계정 연동
3-3. Jenkins Item 구성
3-4. Private Registry 사용
4. 결론

1. 환경 구성

  • kubernetes : v1.32.2
  • Jenkins : 2.504.2

2. Github 구성

먼저 Github 리포지토리를 생성하고 프로젝트를 Push합니다.

jenkins-test repository

Project의 Web Hook을 이용하여 Repository의 이벤트를 받습니다.

(Project -> Settings -> Webhooks)

Jenkins의 Payload URL은 Github에서 접근 가능한 Jenkins url 혹은 ip이여야합니다.

위와 같이 Webhook을 지정합니다.

  • GitHub Repository가 Private일 경우

Jenkins에서 Github Private Repository는 Github의 Personal Access Token을 사용합니다.

(Personal Access Token은 유출 시 전체 조직과 저장소가 위험해질 수 있습니다)

(repo, admin:repo_hook) 권한을 설정한 후 토큰을 생성합니다.

repo: Private, public (저장소 전체 제어 권한)
admin:repo_hook (저장소 Webhook 설정 제어)

생성된 Token 값은 따로 저장하여 사용해야합니다. (1회 발급, 재발급 불가)

Jenkins를 설정하여 빌드를 자동화합니다.

3. Jenkins 구성

3-1. Pipeline 작성 및 Jenkinsfile 업로드

Jenkins pipeline을 사용하기 위해 Jenkinsfile을 생성하여 Pipeline을 작성합니다.

Pipeline을 추가합니다.

pipeline {
  agent { # Pipeilne Script가 작동 될 agent를 지정합니다.
    kubernetes {
      label 'kaniko-prod-agent'  // 파드 식별용 label
      defaultContainer 'kaniko'  // docker 데몬을 쉽고 안전하게 사용할 수 있도록 kaniko를 사용합니다.
      yaml """
apiVersion: v1
kind: Pod
metadata:
  labels:
    jenkins: kaniko-prod
spec:
  containers:
  - name: kaniko
    resources:
      requests:
        memory: "1Gi"
        cpu: "500m"
      limits:
        memory: "2Gi"
        cpu: "1"
    image: gcr.io/kaniko-project/executor:debug  // Docker 이미지 빌드용 kaniko 컨테이너
    imagePullPolicy: IfNotPresent
    command: ["sleep"]
    args: ["infinity"]  // Jenkins가 명령을 실행할 때까지 대기
    volumeMounts:
    - name: dockerconfig
      mountPath: /kaniko/.docker  // docker 인증정보가 mount될 경로
    env:
    - name: HOME
      value: "/home/jenkins/agent"
  restartPolicy: Never
  volumes:
  - name: dockerconfig
    projected:
      sources:
      - secret:
          name: dockerhubconfig  // DockerHub 인증 정보를 담은 Kubernetes Secret 이름
          items:
          - key: .dockerconfigjson
            path: config.json  // mount될 파일명
"""
    }
  }

  environment {
    REGISTRY   = "docker.io"  // Docker Hub registry 주소
    PROJECT    = "asdfkco/jenkinstest"  // 이미지 이름 (ex: docker.io/[사용자명]/[이미지명])
  }

  stages {
    stage('Git Checkout') {
      steps {
        checkout scm  // Git 리포지토리에서 Jenkinsfile과 소스코드 체크아웃
      }
    }

    stage('Build & Push') {
      steps {
        container(name: 'kaniko', shell: '/busybox/sh') {
          script {
            // 이미지 태그 정의
            def dest = "${env.REGISTRY}/${env.PROJECT}:latest"

            sh """
              echo "Building ${dest}"
              /kaniko/executor \
                --dockerfile=./Dockerfile \         // Dockerfile 경로
                --context=dir://./ \                // 현재 디렉토리를 빌드 컨텍스트로 사용
                --destination=${dest} \             // 푸시될 대상 이미지 경로
                --verbosity=debug \                 
                --cleanup                           // 빌드 후 임시 파일 정리
            """
          }
        }
      }
    }
  }

  post { # 빌드가 끝난 후 작업을 정의
    always { 
      script {
        def dest = "${env.REGISTRY}/${env.PROJECT}:latest"
        echo "Finished build of ${dest}"  // 빌드 완료 메시지
      }
    }
  }
}

위의 Jenkinsfile을 Github Repository에 Push 합니다.

(Github Repository 안에 Jenkinsfile이 존재해야합니다.)

Docker hub에 이미지를 업로드하기 위해 Docker Hub 계정정보가 담긴 test.json을 Secret을 등록합니다.

(type은 kubernetes.io/dockerconfigjson으로 지정되어있어야합니다.)

3-2. Github 계정 연동 (Private Repository)

Jenkins 관리 페이지에 접속하여 Credentials를 선택합니다.

Domains의 Global를 클릭합니다.

Add Credentials를 클릭하여 Github 계정을 추가합니다.

(password는 위에서 생성했던 token을 사용하여야합니다.)

저장한 후 Jenkins 구성을 진행합니다.

3-3. Jenkins Item 구성

Jenkins의 Web UI는 아래와 같습니다. 새로운 Item을 추가하여 배포를 자동화합니다.

Pipeline을 선택합니다.

General 설정에서는 Github project를 선택합니다.

Trigger에서 GitHub Hook Trigger를 지정합니다.

Credentials에 3-2. Github 계정 연동에서 추가했었던 Credentials를 기입합니다.

Pipeline이 작성되어있는 Jenkinsfile을 찾을 수 있도록 Script Path를 지정합니다.

Github 중 main.py의 내용을 변경하여 빌드합니다.

성공적으로 완료된 것을 볼 수 있습니다.

Github push로 인해 빌드가 시작된 것을 확인할 수 있습니다.

빌드 중 Pipeline에 작성된 Agent가 배포되고 안에서 빌드가 실행되면서 Pod 한개가 생성이 되어 Pipeline이 작동하게 됩니다.

> kubectl get pods -n cicd | grep kaniko

kaniko-prod-agent-4bktm-5k848                      2/2     Running   0               12s

Docker Hub에 정상적으로 Push 된 것을 확인할 수 있습니다.

Dockerfile 대로 이미지가 빌드된 것을 확인할 수 있습니다.

$ docker ps

CONTAINER ID   IMAGE                 COMMAND                  CREATED         STATUS         PORTS                                       NAMES
2d3ef1addf43   asdfkco/jenkinstest   "uvicorn main:app --…"   4 seconds ago   Up 2 seconds   0.0.0.0:8000->8000/tcp, :::8000->8000/tcp   priceless_diffie

3-4. Private Registry 사용

Harbor의 계정을 Kubernetes Secret 리소스로 추가합니다.

# harbor.json

{
  "auths": {
    "[Harbor Domain]": {
      "username": "[Harbor ID]", 
      "password": "[Harbor Password]",
      "auth": "[ID:PW base 64 encoding]" # [ID]:[PW]를 Base64로 인코딩합니다.
    }   
  }
}

위의 Json 파일을 Secret 리소스로 추가합니다.

kubectl create secret generic harborconfig \  
  --type=kubernetes.io/dockerconfigjson \  
  --from-file=.dockerconfigjson=harbor.json \  
  -n [namespace]

Github Repository에 있는 Jenkinsfile을 수정합니다.

pipeline {
  agent {
    kubernetes {
      label 'kaniko-prod-agent'
      defaultContainer 'kaniko'
      yaml """
apiVersion: v1
kind: Pod
metadata:
  labels:
    jenkins: kaniko-prod
spec:
  containers:
  - name: kaniko
    resources:
      requests:
        memory: "1Gi"
        cpu: "500m"
      limits:
        memory: "2Gi"
        cpu: "1"
    image: gcr.io/kaniko-project/executor:debug
    imagePullPolicy: IfNotPresent
    command: ["sleep"]
    args: ["infinity"]
    volumeMounts:
    - name: dockerconfig
      mountPath: /kaniko/.docker
    env:
    - name: HOME
      value: "/home/jenkins/agent"
  restartPolicy: Never
  volumes:
  - name: dockerconfig
    projected:
      sources:
      - secret:
          name: harbor-dockerconfig # harbor의 계정 정보 Secret을 Jenkins Agent의 /kaniko/.docker/config.json으로 마운트합니다.
          items:
          - key: .dockerconfigjson
            path: config.json
"""
    }
  }

  environment {
    REGISTRY   = "[Harbor Registry URL]" # Private Registry의 URL을 작성합니다.
    PROJECT    = "jenkins-test" # Harbor Project의 이름을 작성합니다
    IMAGE_NAME = "python-custom" # IMAGE의 이름을 작성합니다.
  }

  stages {
    stage('Git Checkout') {
      steps {
        checkout scm
      }
    }
    stage('Build & Push') {
      steps {
        container(name: 'kaniko', shell: '/busybox/sh') {
          script {
            def dest = "${env.REGISTRY}/${env.PROJECT}/${env.IMAGE_NAME}:latest" # Project 이름을 추가로 넣습니다.
            sh """
              echo "Building ${dest}"
              /kaniko/executor \
                --dockerfile=./Dockerfile \
                --context=dir://./ \
                --destination=${dest} \
                --verbosity=debug \
                --skip-tls-verify \
                --insecure \
                --cleanup
            """
          }
        }
      }
    }
  }

  post {
    always {
      script {
        def dest = "${env.REGISTRY}/${env.PROJECT}/${env.IMAGE_NAME}:latest"
        echo "Finished build of ${dest}"
      }
    }
  }
}

정상적으로 빌드 및 Private Registry에 Image가 올라가는 것을 확인할 수 있습니다.

4. 결론

이번 구성에서는 GitHub의 Webhook과 Jenkins의 Pipeline을 활용하여 코드 푸시 이벤트 기반의 자동 빌드·배포 환경을 성공적으로 구현하였습니다. Jenkins는 Kubernetes 상에서 안정적으로 동작하며, Kaniko를 활용하여 도커 데몬 없이도 이미지 빌드를 안전하게 수행하였습니다. 또한, Docker Hub와의 연동을 통해 최종 빌드 산출물을 외부 저장소로 손쉽게 배포할 수 있었습니다.

해당 구조는 DevOps 환경의 표준적인 자동화 흐름을 기반으로 하며, 향후에는 테스트 자동화, 이미지 스캐닝, 배포 자동화(ArgoCD 등)와의 연계를 통해 더 견고하고 확장성 있는 파이프라인으로 발전시킬 수 있습니다.

Kubernetes에 관련된 더 많은 내용을 알고싶으시다면 NGINX STORE Kubernetes 카테고리를 방문해주세요.

NGINX STORE를 통한 솔루션 도입 및 기술지원 무료 상담 신청

* indicates required