• NCP(Naver Cloud Platform) 에서 쿠버네티스 Jenkins 구축 - NCP Container Registry 연동

    2026. 5. 27.

    by. Daramu

    NCP에서 Jenkins를 구축할 것이다.

    Jenkins는 소프트웨 개발 과정에서 빌드/테스트/배포를 자동화하는 CI/CD 를 대표하는 오픈소스 서비스 중 하나이다.

     

    쿠버네티스 사용중이거나, gitlab을 사용중이라면 gitlab runner, argoCD등 다양한 선택지가 있지만 jenkins는 3tier및 쿠버네티스에서도 CI부분에서 여전히 높은 사용률을 보이고 있다.

     

    Jenkin로 CD(배포)도 가능하며, 권장하지는 않지만 가능하기에 이번 테스트에서는 CI/CD 모두 구축을 목표로 진행할 것이다.

     

    물론 CI와 CD에 정해진 것은 없으므로, Jenkins를 CI로만 사용하고 CD는 argoCD를,

    gitlab runner를 CI로 사용하고 Jenkins를 CD로 해도 상관 없다. 각기 장/단점이 있으며 자신들의 환경에 맞춘 선택을 하면 된다.

     

    본 포스팅 처럼 Jenkins를 CI/CD로 사용하겠다고 하면 몇가지 선택지가 있다.

     

    후에 나올 코드를보면 알겠지만 이미지를 생성하여 Push하는 과정이 있는데, 이때 Docker를 사용해 워커노드의 소켓을 mount해서 쓸 수 있고, 아니면 젠킨스 컨테이너 내부에서 도커 데몬을 띄우는 Docker-in-Docker 방식이 있다.

     

    하지만 둘 다 문제가 있는 방식이므로, 본 포스팅에서는 Jenkins에서 Docker를 사용하지 않고 "kaniko"를 사용하여 이미지를 빌드할 것이다.

     

    kaniko는 컨테이너 내부나 쿠버네티스 클러스터 환경에서 Docker 데몬 없이 dockerfile을 사용하여 컨테이너 이미지를 빌드하고 레지스트리에 push하는 오픈소스 도구로, Docker 데몬이 필요 없기에 리소스와 보안 면에서 좀 더 탁월하다.

     

    우선 NCP에서 Jenkins를 설치하기 전에, 환경은 NKS(Naver Kubernetes Service)이므로, NKS와 그 NKS에 연결된 Bastion 서버가 필요하다.

     

    각 설치와 연결은 NCP공식 가이드 참고하여 준비되었다는 가정하에 시작하겠다.

    ncp-iam-authenticator 설치

     

    ncp-iam-authenticator 설치

    ncp-iam-authenticator 설치 Prev Next VPC 환경에서 이용 가능합니다. Ncloud Kubernetes Service는 ncp-iam-authenticator를 통해 IAM 인증을 제공합니다. IAM 인증을 통해 kubectl 명령을 사용하려면 ncp-iam-authenticator를 설

    guide.ncloud-docs.com

     

     

    본격적으로 들어가기 전에, 우선 Bastion 서버에 Docker를 설치해야 한다. 

    Docker가 설치되어 있지 않다면 아래 명령어로 설치 가능하다.

    #Ubuntu 기준
    
    sudo apt remove $(dpkg --get-selections docker.io docker-compose docker-compose-v2 docker-doc podman-docker containerd runc | cut -f1)
    
    # Add Docker's official GPG key:
    sudo apt update
    sudo apt install ca-certificates curl
    sudo install -m 0755 -d /etc/apt/keyrings
    sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
    sudo chmod a+r /etc/apt/keyrings/docker.asc
    
    # Add the repository to Apt sources:
    sudo tee /etc/apt/sources.list.d/docker.sources <<EOF
    Types: deb
    URIs: https://download.docker.com/linux/ubuntu
    Suites: $(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}")
    Components: stable
    Architectures: $(dpkg --print-architecture)
    Signed-By: /etc/apt/keyrings/docker.asc
    EOF
    
    sudo apt update
    
    sudo apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

     

    설치가 완료 되었다면 NCP 콘솔에서 Container Registry를 생성한다.

     

    어렵지 않으니 동일하게 Container Registry 공식 가이드 문서로 대체 하겠다.

    Container Registry 시작

     

    Container Registry 시작

    Container Registry 시작 Prev Next Classic/VPC 환경에서 이용 가능합니다. Container Registry 시작에서는 Container Registry 사용 환경과 지원 사양을 확인하고, 전체 사용 시나리오를 숙지한 후 Container Registry를 정

    guide.ncloud-docs.com

     

     

    Registry가 준비되었다면 registry 클릭시에 endpoint url이 나올 것이다.

    붉은 네모칸 부근에 있다. 이 endpoint를 꼭 기억해 둬야한다. 바로 아래의 login과,

    이후 있을 build와 push, tag 에 사용된다.

     

    그리고 Docker 설치가 끝났다면 NCP Container Registry와 연결이 필요하다.

    연결(docker login)에는 accesskey와 secretkey가 필요하며, 아래 링크에서 생성할 수 있다.

    https://console.ncloud.com/member/accessManagement

     

     

    key가 준비되었다면 docker login을 통해 로그인을 진행한다.

    docker login {Container_registry_endpoint}
    ID: Access_Key
    PW: Private_Key

     

    로그인이 성공했다면 "Login Succeeded" 라는 안내 문구가 나온다.

     

    이제 Jenkins를 동작시키기 위한 이미지 파일을 생성해야한다.

    본 포스팅에서는 코드 저장소로 "GitLab"을 사용할 것이다.

     

    물론 github를 사용하는 경우가 많기에, 혹시나 하여 플러그 인으로 gitlab은 물론 github 플러그인 까지 집어 넣었다.

     

    이미지는 총 3개로, Jenkins의 대시보드와 UI를 제공해주는 본체가 되는 "Jenkins-controller", 

    실제 파이프라인(스테이지)를 실행하는 작업 공간인 "jenkins-agent",

    마지막으로 에이전트 내부에서 동작하며 도커 데몬 없이 이미지를 빌드 및 push하기 위한 "kaniko" 세개다.

     

    물론 2025을 마지막으로 kaniko는 구글이 업데이트를 하지 않아 후에 사용시 문제가 될 수 있지만, 본 포스팅은 과거 구현했던것을 그대로 구축해서 하는 것이기 때문에, 우선 같이 구축하고 동작을 확인하고 agent만 바꾸는 것을 추천한다.

     

    이중 jenkins-controller와 jenkins-agent는 Dockfile을 통해 별도의 커스터 마이징을 진행할 것이다.

     

    우선 Jenkins-controller이다.

    Bastion에서 Dockerfile.controller로 생성해둔다.

    FROM jenkins/jenkins:lts-jdk21
    
    # 플러그인 설치
    RUN jenkins-plugin-cli --verbose --latest true --plugins \
            blueocean \
            build-timeout \
            configuration-as-code \
            credentials-binding \
            extended-choice-parameter \
            git \
            github \
            github-branch-source \
            gitlab-api \
            gitlab-branch-source \
            gitlab-plugin \
            job-dsl \
            kubernetes \
            kubernetes-credentials-provider \
            pipeline-stage-view \
            pipeline-utility-steps \
            rebuild \
            role-strategy \
            timestamper \
            workflow-aggregator \
            ws-cleanup \
        && ls -la /usr/share/jenkins/ref/plugins/ | head -30
    
    # 초기 설정 마법사 스킵 (CasC로 관리)
    ENV JAVA_OPTS="-Djenkins.install.runSetupWizard=false"
    
    EXPOSE 8080 50000
    
    LABEL maintainer="devops" \
          role="controller" \
          description="Jenkins controller for Kubernetes"

     

    젠킨스에서 플러그인을 UI로 설치하는 것이 아닌, 이미지 단에서 모두 포함해서 애초에 이미지 단위로 가져올 것이다.

    jenkins-plugin-cli 를 통해 다운로드 하며, pipeline과 코드 저장소(gitlab,github)연동을 위한 플러그인을 준비했다.

     

    그리고 애초에 plugin 이 설치되어 있으니, 별도의 설치 설정 마법사는 필요 없으니 skip하고, 패스워드 지정등은 후에 다룰 CasC로 관리할 것이다.

     

    그리고 실제 파이프라인 동작을 담당할 agent다.

    Dockerfile.agent 로 생성해 두었다.

    FROM jenkins/inbound-agent:latest-jdk21
    
    USER root
    
    # 필수 패키지
    RUN apt-get update && apt-get install -y --no-install-recommends \
            curl \
            ca-certificates \
            bash \
            git \
        && rm -rf /var/lib/apt/lists/*
    
    # kubectl 설치 (stable 최신 버전)
    RUN curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" \
        && install -o root -g root -m 0755 kubectl /usr/local/bin/kubectl \
        && rm -f kubectl \
        && kubectl version --client=true
    
    # ncp-iam-authenticator 설치
    RUN curl -o /usr/local/bin/ncp-iam-authenticator -L \
            https://github.com/NaverCloudPlatform/ncp-iam-authenticator/releases/latest/download/ncp-iam-authenticator_linux_amd64 \
        && chmod +x /usr/local/bin/ncp-iam-authenticator \
        && ncp-iam-authenticator help > /dev/null
    
    # jenkins 유저용 디렉터리
    # inbound-agent 베이스는 jenkins 유저 UID 1000으로 동작
    RUN mkdir -p /home/jenkins/.ncloud /home/jenkins/.kube \
        && chown -R jenkins:jenkins /home/jenkins/.ncloud /home/jenkins/.kube
    
    USER jenkins
    
    LABEL maintainer="devops" \
          role="agent" \
          description="Jenkins agent with kubectl & ncp-iam-authenticator"

     

     

    이제 위 이미지를 docker build해서 이미지로 만들면 되는데,

    NCP Container Registry에 push하기 위해서는 일종의 규칙을 지켜야한다.

     

    가령 Container Registry의 endpodint가 jenkinstest.ncr.ntruss.com 라면,

    이미지 이름은 jenkinstest.ncr.ntruss.com/jenkins-controller:0.1 이 되어야 한다.

     

    //만약 자신의 container registry의 endpodint가 jenkinstest.ncr.ntruss.com이라면,
    docker build -t jenkinstest.ncr.ntruss.com/jenkins-controller:0.1 -f Dockerfile.controller .
    
    //registry에 push
    docker push jenkinstest.ncr.ntruss.com/jenkins-controller:0.1

     

    agent도 동일하게 생성 및 push 한다.

    //만약 자신의 container registry의 endpodint가 jenkinstest.ncr.ntruss.com이라면,
    docker build -t jenkinstest.ncr.ntruss.com/jenkins-agent:0.1 -f Dockerfile.agent .
    
    //registry에 push
    docker push jenkinstest.ncr.ntruss.com/jenkins-agent:0.1

     

    그리고 kaniko또한 push 한다.

    kaniko는 Dockerfile을 통해 별도의 편집이 필요 없기 때문에, 기존 이미지를 그대로 사용해서 push 한다.

     

     

    docker pull gcr.io/kaniko-project/executor:v1.23.2-debug
    
    #이미지 명 변경
    docker tag gcr.io/kaniko-project/executor:v1.23.2-debug jenkinstest.ncr.ntruss.com/jenkins-kaniko:0.1
    
    #push
    docker push jenkinstest.ncr.ntruss.com/jenkins-kaniko:0.1

     

    이렇게 되면 총 3개의 이미지가 container registry에 저장되어 있으 것이다.

     

     

    이제 이 이미지를 가지고 Jenkins를 생성할 건데,

    생성 전 마지막으로 CasC를 생성한다. CasC(Configuration as Code)는 사람이 직접 UI에서 설정하는 것 대신, 코드를 통해 설정을 진행하겠다는 것이다.

     

    우선 "jenkins-casc-config.yaml"을 생성한다.

    참고로 아무것도 수정할 필요 없이 그냥 복붙해서 apply 하면 된다.

    apiVersion: v1
    kind: ConfigMap
    metadata:
      name: jenkins-casc-config
      namespace: jenkins
    data:
      jenkins.yaml: |
        jenkins:
          numExecutors: 0
          mode: EXCLUSIVE
    
          securityRealm:
            local:
              allowsSignup: false
              users:
                - id: ${JENKINS_ADMIN_ID}
                  password: ${JENKINS_ADMIN_PASSWORD}
    
          authorizationStrategy:
            loggedInUsersCanDoAnything:
              allowAnonymousRead: false
    
          clouds:
            - kubernetes:
                name: "kubernetes"
                serverUrl: "https://kubernetes.default.svc"
                namespace: "jenkins"
                jenkinsUrl: "http://jenkins.jenkins.svc.cluster.local:8080"
                jenkinsTunnel: "jenkins.jenkins.svc.cluster.local:50000"
    
        # unclassified:
        #   location:
        #     url: "http://localhost:8080/"

     

    ID,Password는 Secret으로 관리할 것이다.

    잊기 전에 관리자의 계정을 secret으로 생성한다.

    kubectl create secret generic jenkins-admin \
      --from-literal=jenkins-admin-user='admin' \
      --from-literal=jenkins-admin-password='pasword입력' \
      -n jenkins

     

    추가로 쿠버네티스가 NCP Container Registry에서 이미지를 가져오기 위한 Secret 로 생성한다.

    #kubectl create secret docker-registry regcred --docker-server=Container_Registry_endpoint --docker-username=ACCESS_KEY --docker-password=SECRET_KEY -n jenkins
    kubectl create secret docker-registry regcred --docker-server=jenkinstest.ncr.ntruss.com --docker-username=aaaet3234t --docker-password=g3gewt342gewg -n jenkins

     

    설정이 완료 되었다면 jenkins를 설치한다.

    apiVersion: v1
    kind: Namespace
    metadata:
      name: jenkins
    
    ---
    apiVersion: v1
    kind: ServiceAccount
    metadata:
      name: jenkins
      namespace: jenkins
    
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRole
    metadata:
      name: jenkins-cluster-manager
    rules:
      - apiGroups: [""]
        resources: ["pods", "pods/exec", "pods/log", "services"]
        verbs: ["get", "list", "watch", "create", "delete", "update", "patch"]
       
      - apiGroups: ["apps"]
        resources: ["deployments", "statefulsets", "daemonsets", "replicasets"]
        verbs: ["get", "list", "watch", "create", "delete", "update", "patch"]
    
      - apiGroups: ["networking.k8s.io"]
        resources: ["ingresses"]
        verbs: ["get", "list", "watch", "create", "delete", "update", "patch"]
    
      - apiGroups: [""]
        resources: ["secrets", "configmaps"]
        verbs: ["get", "list", "watch", "create", "delete", "update", "patch"] # CD 과정에서 Secret/ConfigMap 생성·수정을 위해 권한 확대
    
      - apiGroups: [""]
        resources: ["events"]
        verbs: ["watch", "get", "list"]
    
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: ClusterRoleBinding
    metadata:
      name: jenkins-cluster-manager-binding
    subjects:
      - kind: ServiceAccount
        name: jenkins
        namespace: jenkins
    roleRef:
      kind: ClusterRole
      name: jenkins-cluster-manager
      apiGroup: rbac.authorization.k8s.io
    
    ---
    apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: jenkins-home
      namespace: jenkins
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 10Gi
      storageClassName: nks-block-storage
    
    ---
    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: jenkins
      namespace: jenkins
      labels:
        app: jenkins
    spec:
      replicas: 1
      strategy:
        type: Recreate
      selector:
        matchLabels:
          app: jenkins
      template:
        metadata:
          labels:
            app: jenkins
        spec:
          serviceAccountName: jenkins
          securityContext:
            fsGroup: 1000
            runAsUser: 1000
          imagePullSecrets:
          - name: regcred
          containers:
            - name: jenkins
              image: ${NCR_URL}/jenkins-controller:latest
              imagePullPolicy: Always
              ports:
                - name: http
                  containerPort: 8080
                - name: jnlp
                  containerPort: 50000
              env:
                - name: JAVA_OPTS
                  value: >-
                    -Djenkins.install.runSetupWizard=false
                    -Dhudson.model.UpdateCenter.never=true
                    -Dhudson.PluginManager.checkUpdate=false
                    -Xms1g -Xmx2g
                - name: JENKINS_OPTS
                  value: "--prefix=/"
                - name: CASC_JENKINS_CONFIG
                  value: /var/jenkins_config/jenkins.yaml
                - name: JENKINS_ADMIN_ID
                  valueFrom:
                    secretKeyRef:
                      name: jenkins-admin
                      key: jenkins-admin-user
                - name: JENKINS_ADMIN_PASSWORD
                  valueFrom:
                    secretKeyRef:
                      name: jenkins-admin
                      key: jenkins-admin-password
              volumeMounts:
                - name: jenkins-home
                  mountPath: /var/jenkins_home
                - name: jenkins-casc-config
                  mountPath: /var/jenkins_config
              resources:
                requests:
                  cpu: 500m
                  memory: 1Gi
                limits:
                  cpu: 2000m
                  memory: 4Gi
              livenessProbe:
                httpGet:
                  path: /login
                  port: 8080
                initialDelaySeconds: 90
                periodSeconds: 30
                failureThreshold: 5
              readinessProbe:
                httpGet:
                  path: /login
                  port: 8080
                initialDelaySeconds: 60
                periodSeconds: 10
          volumes:
            - name: jenkins-home
              persistentVolumeClaim:
                claimName: jenkins-home
            - name: jenkins-casc-config
              configMap:
                name: jenkins-casc-config
    
    ---
    apiVersion: v1
    kind: Service
    metadata:
      name: jenkins
      namespace: jenkins
      labels:
        app: jenkins
    spec:
      type: NodePort
      ports:
        - name: http
          port: 8080
          targetPort: 8080
        - name: jnlp
          port: 50000
          targetPort: 50000
      selector:
        app: jenkins
    
    ---
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      annotations:
        alb.ingress.kubernetes.io/healthcheck-path: "/login"
        alb.ingress.kubernetes.io/network-type: "private"
      name: path-ingress
      namespace: jenkins
    spec:
      ingressClassName: alb
      rules:
      - http:
          paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: jenkins
                port:
                  number: 8080

     

    양이 길지만 쫄 거 없다.

    그냥 namespaces, cluster role, deployment와 sevice일 뿐이다.

    참고로 ingress에서 alb를 사용하니, 만약 kubectl get pod -n kube-system에서 alb가 없다면 다운로드 해야한다.

    #NCP alb 다운로드
    kubectl --kubeconfig=$KUBE_CONFIG apply -f https://raw.githubusercontent.com/NaverCloudPlatform/nks-alb-ingress-controller/main/docs/install/pub/install.yaml

     

    controller yaml코드에서 주의깊게 볼것은 image부분이다.

    secret으로 이전 생성했던 container registry의 secret이 있고,

    그 밑에 image가 있다. 해당 이미지는 자신의 이미지에 맞게 수정하면 된다.

    (ex. jenkinstest.ncr.ntruss.com/jenkins-controller:0.1 )

          imagePullSecrets:
          - name: regcred
          containers:
            - name: jenkins
              image: ${NCR_URL}/jenkins-controller:latest

     

    그리고 apply 하면 된다.

    alb가 잘 있다면 NCP콘솔에서 로드밸런서 하나가 생성될 것이다.

    본 포스팅인 VPN환경이므로, 만약 VPN사용이 어려워 공인IP가 필요하다면 yaml파일 가장 마지막에 ingress부분에 "alb.ingress.kubernetes.io/network-type: "private"" 이 라인을 삭제하면 된다.

     

    정상작동 했다면 "kubectl get pod -n jenkins"로 확인시 running일 것이다.

    yaml파일에는 pvc도 들어가있기 때문에, 블록스토리지 생성시 시간이 어느정도 필요할 수 있으니 0/1 이어도 초조해 하지 말자. 블록 스토리지가 생성 중일 수 있으니 kubectl logs {pod_name} -n jenkins 로 확인시 특별한 error가 없다면 정상 작동중이다.

     

    이제 yaml파일에 ingress 파일도 있으니 자동으로 ALB가 생성되었을 것이다.

    NCP콘솔에서 확인 가능하다.

     

    ALB의 주소로 들어가면 설치 마법사가 모두 스킵된 jenkins 로그인 화면이 바로 보일 것이고,

    이전 secret으로 설정한 ID/PW로 접속해주면 된다.

     

    마지막으로 CasC 설정 당시 "Kubernetest"설정 또한 했었다.

    쿠버네티스 연동을 위한 설정으로, CasC가 정상적으로 적용 되었는지 확인해보자.

     

    젠킨스에 로그인 후 우측 상단 톱니바퀴 -> Clouds에서 CasC로 설정한 Kubernetes가 있다면 성공이다.

     

    댓글