ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 쿠버네티스 서버 CI ,DI 구조까지 완벽 가이드 (with 젠킨스편) 2탄
    Devops/kubernetes 2025. 8. 27. 14:19
    728x90
    반응형

     

     

    helm을 이용한 jenkins 설치

    • Jenkins Controller: 클러스터 내부 jenkins 네임스페이스에 Helm으로 설치
    • Jenkins Agent: Kubernetes Plugin으로 필요 시마다 파드로 생성(오토스케일)
    • 이미지 빌드: Kaniko(도커 데몬 없이 빌드) → 보안/간편
    • 배포: Helm(권장) 또는 kubectl apply
    • 접근권한: jenkins ServiceAccount + 최소권한(RBAC)으로 대상 네임스페이스만 조작
    • 환경 분리: dev / stage / prod 각각 네임스페이스 + 서로 다른 RoleBinding/크리덴셜

     

    1) Helm 값에 JCasC로 멀티 JDK 등록

    values-multi-jdk.yaml (새 파일)

    controller:
      # 리소스/Java 옵션
      resources:
        requests: { cpu: "500m", memory: "2Gi" }
        limits:   { cpu: "1",    memory: "4Gi" }
      javaOpts: "-Xms512m -Xmx3g"
    
      # 컨트롤러가 사용할 ServiceAccount 고정 (podTemplate도 이 SA를 씀)
      serviceAccount:
        create: true
        name: jenkins
    
      # known_hosts 마운트 (github 호스트키)
      extraVolumes:
        - name: known-hosts
          configMap:
            name: jenkins-known-hosts
      extraVolumeMounts:
        - name: known-hosts
          mountPath: /etc/ssh/ssh_known_hosts
          subPath: ssh_known_hosts
          readOnly: true
    
      # 프로브
      startupProbe:
        enabled: true
        failureThreshold: 60
        periodSeconds: 10
      livenessProbe:
        failureThreshold: 30
        periodSeconds: 10
    
      # 필요한 플러그인
      installPlugins:
        - configuration-as-code
        - jdk-tool
        - adoptopenjdk
        - workflow-aggregator
        - credentials-binding
        - kubernetes:latest
        - kubernetes-credentials-provider:latest
        - git:latest
        - git-client:latest
        - ssh-agent:latest
        - ssh-credentials:latest
    
      # 관리자 계정
      admin:
        username: crisp
        password: "crisp12#$"
    
      # JCasC
      JCasC:
        enabled: true
        defaultConfig: false
        configScripts:
          # JDK 도구
          tools: |
            tool:
              jdk:
                installations:
                  - name: "jdk 17"
                    properties:
                      - installSource:
                          installers:
                            - adoptOpenJdkInstaller:
                                id: "jdk-17.0.7+7"
                  - name: "jdk 21"
                    properties:
                      - installSource:
                          installers:
                            - adoptOpenJdkInstaller:
                                id: "jdk-21.0.3+9"
    
          # 로그인 강제
          security: |
            jenkins:
              authorizationStrategy:
                loggedInUsersCanDoAnything:
                  allowAnonymousRead: false
              securityRealm:
                local:
                  allowsSignup: false
                  users:
                    - id: "${chart-admin-username}"
                      password: "${chart-admin-password}"
    
          # ✅ Kubernetes Cloud 등록 (podTemplate가 쓸 Cloud)
          k8s-cloud: |
            jenkins:
              clouds:
                - kubernetes:
                    name: "kubernetes"                       # 파이프라인에서 cloud: 'kubernetes'
                    serverUrl: "https://kubernetes.default.svc"
                    namespace: "jenkins"                     # Jenkins가 띄울 에이전트 파드 네임스페이스
                    jenkinsUrl: "http://jenkins:8080"        # 클러스터 내부 서비스 DNS
                    containerCapStr: "10"
                    maxRequestsPerHostStr: "32"
                    podRetention: "never"
                    # in-cluster SA 사용 (credentialsId 생략)
    
      # 사이드카 자동 리로드 비활성화
      sidecars:
        configAutoReload:
          enabled: false
    
    # 퍼시스턴스
    persistence:
      enabled: true
      size: 20Gi
    
    # 서비스 타입
    controllerServiceType: NodePort
    
    # Jenkins 차트 RBAC 리소스 생성(기본 RBAC). 세부 권한은 별도 RBAC 매니페스트로 부여 완료.
    rbac:
      create: true

     

    적용:

    # 네임스페이스
    kubectl create ns jenkins
    
    # Helm 리포 추가
    helm repo add jenkins https://charts.jenkins.io
    helm repo update
    
    # 설치 (NodePort 예시 — Ingress 있으면 Ingress로 노출 권장)
    helm install jenkins jenkins/jenkins \
      -n jenkins \
      -f values-jdk.yaml

     

    ndoeip 설정:

     

    # jenkins 웹 서비스(NodePort 자동 할당)
    kubectl -n jenkins patch svc jenkins -p '{"spec":{"type":"NodePort"}}'
    
    # 할당된 NodePort 확인
    kubectl -n jenkins get svc jenkins -o jsonpath='{.spec.ports[?(@.port==8080)].nodePort}{"\n"}'
    # 접속: http://<클러스터 노드 IP>:<위에서 출력된 NodePort>
    
    kubectl -n jenkins patch svc jenkins \
      --type='json' \
      -p='[{"op":"replace","path":"/spec/ports/0/nodePort","value":32080}]'

    2. 젠킨스의 private git 접근하도록 credentials 적용시키기

    1.키 생성 (macOS / Linux)

    -  패스프레이즈 없는 키(보통 CI는 이 방식)

    ssh-keygen -t ed25519 -C "jenkins-deploy-key" -N "" -f ./id_jenkins_ed25519

     

     

    2. github에 생성한 id_jenkins_ed25519.pub 를 복사하여 등록한다.

     

    3.git과 연결됬는지 테스트

    GIT_SSH_COMMAND='ssh -i ./id_jenkins_ed25519' git ls-remote git@github.com:CRISP-REPO/crisp-lending-backend.git

     

    4.쿠버네티스 Secret 만들기 (배포키 넣기)

     

    git-ssh-cred.yaml:

    apiVersion: v1
    kind: Secret
    metadata:
      name: git-ssh-cred
      annotations:
        jenkins.io/credentials-type: "sshUserPrivateKey"    # Jenkins Credentials 타입
        jenkins.io/credentials-id: "git-ssh"                # 파이프라인에서 쓸 ID
        jenkins.io/credentials-description: "Deploy key for CRISP repo"
    type: Opaque
    stringData:
      username: git                                         # SSH는 보통 'git'
      privateKey: |                                         # 방금 테스트에 쓴 개인키 본문을 그대로 붙여넣기
        -----BEGIN OPENSSH PRIVATE KEY-----
        ...여기에 id_jenkins_ed25519 내용 전체...
        -----END OPENSSH PRIVATE KEY-----
      passphrase: ""                                        # 패스프레이즈 없으면 빈 문자열

     

    적용:

    # (jenkins 네임스페이스 예시, 환경에 맞게 -n 바꾸세요)
    kubectl -n jenkins apply -f git-ssh-cred.yaml

     

    5. known_hosts 파일 만들고 ConfigMap 생성

    # github.com, gitlab.com 등 필요한 호스트 전부 모읍니다.
    ssh-keyscan github.com > known_hosts
    # 추가 예: ssh-keyscan gitlab.com >> known_hosts
    # 사내 Git 호스트가 있으면 그 호스트도 추가
    
    # ConfigMap 생성
    kubectl -n jenkins create configmap jenkins-known-hosts \
      --from-file=ssh_known_hosts=./known_hosts

     

     

    6. RBAC 권한(필요 시)

     

    Jenkins ServiceAccount가 Secret을 읽을 수 있어야 합니다. 문제가 보이면 아래처럼 Role/RoleBinding을 추가하세요.

    cat <<'YAML' | kubectl apply -f -
    # 1) Jenkins가 K8s Secret/ConfigMap을 읽어 Credentials로 노출
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: jenkins-read-secrets
      namespace: jenkins
    rules:
    - apiGroups: [""]
      resources: ["secrets","configmaps"]
      verbs: ["get","list","watch"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: jenkins-read-secrets-binding
      namespace: jenkins
    subjects:
    - kind: ServiceAccount
      name: jenkins
      namespace: jenkins
    roleRef:
      kind: Role
      name: jenkins-read-secrets
      apiGroup: rbac.authorization.k8s.io
    
    # 2) Jenkins Kubernetes 플러그인이 에이전트 파드를 만들고 로그/exec 할 수 있도록
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: jenkins-agent-launcher
      namespace: jenkins
    rules:
    - apiGroups: [""]
      resources: ["pods","pods/exec","pods/log","pods/attach","events","configmaps","secrets"]
      verbs: ["create","delete","get","list","watch","patch","update"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: jenkins-agent-launcher-binding
      namespace: jenkins
    subjects:
    - kind: ServiceAccount
      name: jenkins
      namespace: jenkins
    roleRef:
      kind: Role
      name: jenkins-agent-launcher
      apiGroup: rbac.authorization.k8s.io
    
    # 3) 배포 시 set image/rollout status 에 필요한 권한
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: Role
    metadata:
      name: jenkins-deployer
      namespace: jenkins
    rules:
    - apiGroups: ["apps"]
      resources: ["deployments","replicasets"]
      verbs: ["get","list","watch","patch","update"]
    - apiGroups: [""]
      resources: ["pods"]
      verbs: ["get","list","watch"]
    ---
    apiVersion: rbac.authorization.k8s.io/v1
    kind: RoleBinding
    metadata:
      name: jenkins-deployer-binding
      namespace: jenkins
    subjects:
    - kind: ServiceAccount
      name: jenkins
      namespace: jenkins
    roleRef:
      kind: Role
      name: jenkins-deployer
      apiGroup: rbac.authorization.k8s.io
    YAML

     

     

    Secret 포맷을 표준으로 재생성

     

    # 기존 삭제(데이터 유지 안 해도 되면)
    kubectl -n jenkins delete secret git-ssh-cred --ignore-not-found
    
    # 표준 타입/키로 생성: kubernetes.io/ssh-auth + ssh-privatekey
    kubectl -n jenkins create secret generic git-ssh-cred \
      --type=kubernetes.io/ssh-auth \
      --from-file=ssh-privatekey=./id_jenkins_ed25519 \
      --from-literal=username=git \
      --from-literal=passphrase=""
    
    # 어노테이션 다시 부여
    kubectl -n jenkins annotate secret git-ssh-cred \
      jenkins.io/credentials-type=sshUserPrivateKey \
      jenkins.io/credentials-id=git-ssh \
      jenkins.io/credentials-description="Deploy key for CRISP repo" \
      --overwrite
      
      kubectl -n jenkins get secret git-ssh-cred -o jsonpath='{.type}{"\n"}{.data.ssh-privatekey}{"\n"}'
    # 첫 줄이 kubernetes.io/ssh-auth, 두 번째 줄은 base64 값이 나와야 정상
    
    kubectl auth can-i get secrets  --as=system:serviceaccount:jenkins:jenkins -n jenkins
    kubectl auth can-i list secrets --as=system:serviceaccount:jenkins:jenkins -n jenkins
    # 둘 다 yes 여야 합니다.

     

    7.Secret에 라벨 추가 (존재 라벨 셀렉터 충족)

     

    # 타입: sshUserPrivateKey -> basicSSHUserPrivateKey (라벨/어노테이션 모두 교정)
    kubectl -n jenkins label secret git-ssh-cred \
      jenkins.io/credentials-type=basicSSHUserPrivateKey --overwrite
    
    kubectl -n jenkins annotate secret git-ssh-cred \
      jenkins.io/credentials-type=basicSSHUserPrivateKey \
      jenkins.io/credentials-id=git-ssh \
      jenkins.io/credentials-user=git \
      jenkins.io/credentials-keybinding-privateKey=ssh-privatekey \
      --overwrite
    
    
    # 라벨 붙었는지 확인
    kubectl -n jenkins get secret git-ssh-cred --show-labels

     

    8.컨트롤러 재시작(재스캔 트리거)

    # 컨트롤러 포드만 한 개 지우면 자동 재생성
    kubectl -n jenkins get pods -o name | grep -E 'jenkins|controller' | head -n1 | xargs kubectl -n jenkins delete

     

    9. jenkins WebSocket 모드로 전환

     

     

    쿠버 디플로이 생성 및 도커 레지스트리 공유 설치 

     

    새 Deployment(crisp-app-api)를 만들기

     

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: crisp-app-api
      namespace: prod
    spec:
      replicas: 1
      selector:
        matchLabels: { app: crisp-app-api }
      template:
        metadata:
          labels: { app: crisp-app-api }
        spec:
          imagePullSecrets:
            - name: regcred
          containers:
            - name: crisp-app-api
              image: 115.68.219.196:5000/crisp-app-api:latest
              env:
                - name: SPRING_PROFILES_ACTIVE
                  value: prod
              ports:
                - containerPort: 8090    # 서버가 실제로 리슨하는 포트로 교체
              # (선택) 헬스체크도 함께 권장
              readinessProbe:
                httpGet:
                  path: /actuator/health/readiness
                  port: 8090
                initialDelaySeconds: 10
                periodSeconds: 5
              livenessProbe:
                httpGet:
                  path: /actuator/health/liveness
                  port: 8090
                initialDelaySeconds: 20
                periodSeconds: 10

     

    적용:

    kubectl apply -f deployment-crisp.yaml
    kubectl -n prod get deploy crisp-app-api

     

     

    1. prod 네임스페이스 생성

    kubectl create namespace prod

     

    2. 레지스트리 서버에 설치 (Docker Compose 예시) (다른 클라우드 서)

    방화벽: 5000/tcp 열기 (또는 443 매핑 원하면 443도)

     

    sudo mkdir -p /srv/registry/{data,auth,certs}
    cd /srv/registry
    
    # 1) BasicAuth 계정 (예: myuser / MyPass!)
    docker run --rm httpd:2.4-alpine htpasswd -Bbn myuser 'MyPass!' | sudo tee auth/htpasswd
    
    # 2) IP SAN 인증서 생성 (YOUR_IP에 레지스트리 서버 공인 IP)
    cat > openssl.cnf <<'EOF'
    [req]
    distinguished_name = req_distinguished_name
    x509_extensions = v3_req
    prompt = no
    [req_distinguished_name]
    CN = **YOUR_IP**
    [v3_req]
    subjectAltName = @alt_names
    [alt_names]
    IP.1 = **YOUR_IP**
    EOF
    
    openssl req -x509 -nodes -days 3650 -newkey rsa:2048 \
      -keyout certs/tls.key -out certs/tls.crt -config openssl.cnf

     

     

    경로 /srv/registry/docker-compose.yml 생성

    version: "3.8"
    services:
      registry:
        image: registry:2
        ports:
          - "5000:5000"         # 원하면 "443:5000"로 바꿔도 됨
        environment:
          REGISTRY_HTTP_ADDR: ":5000"
          REGISTRY_HTTP_TLS_CERTIFICATE: /certs/tls.crt
          REGISTRY_HTTP_TLS_KEY: /certs/tls.key
          REGISTRY_AUTH: htpasswd
          REGISTRY_AUTH_HTPASSWD_REALM: "Registry Realm"
          REGISTRY_AUTH_HTPASSWD_PATH: /auth/htpasswd
          # REGISTRY_STORAGE_DELETE_ENABLED: "true"   # 필요 시
        volumes:
          - ./data:/var/lib/registry
          - ./auth:/auth:ro
          - ./certs:/certs:ro

    실행:

    docker compose up -d

     

    2) 모든 쿠버네티스 노드에서: 레지스트리 인증서 신뢰 설정

    컨테이너 런타임이 containerd라고 가정:

    # 1) 디렉토리 생성
    sudo mkdir -p /etc/containerd/certs.d/115.68.219.196:5000
    
    # 2) 레지스트리 서버의 인증서(tls.crt)를 각 노드로 복사해서 배치 
    # 이전 레지스트리에 있는 값 복사
    #   (이미 노드에 파일이 있으면 cp 사용)
    sudo cp /path/to/tls.crt /etc/containerd/certs.d/115.68.219.196:5000/ca.crt
    
    # 3) hosts.toml 작성
    sudo tee /etc/containerd/certs.d/115.68.219.196:5000/hosts.toml >/dev/null <<'EOF'
    server = "https://115.68.219.196:5000"
    [host."https://115.68.219.196:5000"]
      capabilities = ["pull", "resolve", "push"]
      ca = "/etc/containerd/certs.d/115.68.219.196:5000/ca.crt"
    EOF
    
    # 4) containerd 재시작
    sudo systemctl restart containerd

     

    빠른 자체 테스트(선택):

    # containerd 직접 풀 테스트(필요 시)
    sudo ctr -n k8s.io images pull --user myuser:MyPass! 115.68.219.196:5000/alpine:test

     

     
     

    레지스트리 설정

    kubectl -n prod create secret docker-registry regcred \
      --docker-server=115.68.219.196:5000 \
      --docker-username=myuser \
      --docker-password='MyPass!' \
      --docker-email=zkdlwu94@gmail.com
      
      
      kubectl -n jenkins create secret docker-registry regcred \
      --docker-server=115.68.219.196:5000 \
      --docker-username=myuser \
      --docker-password='MyPass!' \
      --docker-email=devnull@example.com

     

     

    Deployment 생성:

    배포 매니페스트(요약) – k8s/prod/deployment.yaml (미리 저장)

    apiVersion: apps/v1
    kind: Deployment
    metadata:
      name: crisp-app-api
      namespace: prod
    spec:
      replicas: 3
      selector:
        matchLabels: { app: crisp-app-api }
      template:
        metadata:
          labels: { app: crisp-app-api }
        spec:
          imagePullSecrets:
            - name: regcred        # 이미 생성해둔 pull 시크릿
          containers:
            - name: myapp
              image: 115.68.219.196:5000/crisp-app-api:latest   # ← Jenkins에서 sed로 치환
              ports:
                - containerPort: 8080
              readinessProbe:
                httpGet: { path: /actuator/health/readiness, port: 8080 }
                initialDelaySeconds: 10
              livenessProbe:
                httpGet: { path: /actuator/health/liveness,  port: 8080 }
                initialDelaySeconds: 20

     

    apply 처리

    kubectl apply -n prod -f k8s/prod/deployment.yaml

     

    4) Jenkins(빌드/푸시)

    Kaniko 예시:

     

     

    stage('Build & Push (Kaniko, HTTPS by IP)') {
      steps {
        sh """
          mkdir -p /tmp/kaniko
          cat > /tmp/kaniko/config.json <<EOF
          {"auths":{"<IP>:5000":{"username":"myuser","password":"MyPass!"}}}
          EOF
    
          docker run --rm \
            -v `pwd`:/workspace \
            -v /tmp/kaniko:/kaniko/.docker \
            gcr.io/kaniko-project/executor:latest \
            --dockerfile /workspace/Dockerfile \
            --destination <IP>:5000/myapp:${BUILD_NUMBER} \
            --destination <IP>:5000/myapp:latest \
            --context dir:///workspace
        """
      }
    }

     

    4. kube-prometheus-stack(Prometheus Operator+Prometheus+Alertmanager+Grafana+kube-state-metrics 통합 설치

     

    • kube-prometheus-stack(Helm 차트): Prometheus Operator, Prometheus, Alertmanager, Grafana, node-exporter, kube-state-metrics, 기본 대시보드/알람 룰을 한 번에 설치. GitHubdevopsdaily.eu
    • ServiceMonitor / PodMonitor(CRD): Prometheus가 어떤 Service/Pod의 /metrics를 긁을지 선언적으로 지정. (ServiceMonitor는 Service 필요, PodMonitor는 Pod 라벨 직접 선택) Prometheus OperatorGitHub
    • kube-state-metrics: 쿠버네티스 오브젝트 상태를 메트릭으로 노출(디플로이먼트, 파드 수 등).

     

     

     

    빠른 설치(Helm)

     

    # 1) 레포 추가
    helm repo add prometheus-community https://prometheus-community.github.io/helm-charts
    helm repo update
    
    # 2) 네임스페이스 만들고 설치(릴리스명: monitoring)
    helm install monitoring prometheus-community/kube-prometheus-stack \
      -n monitoring --create-namespace

     

    Grafana 접속

     

    #(예) 포트포워드
    kubectl -n monitoring port-forward svc/monitoring-grafana 3000:80
    
    #(예) 초기 비밀번호 확인(릴리스/리소스명은 환경에 따라 다를 수 있음)
    kubectl -n monitoring get secret monitoring-grafana \
      -o jsonpath='{.data.admin-password}' | base64 -d; echo

     

    1.외부접속 Service 타입을 NodePort로 (내부망/테스트에 무난)

    kubectl -n monitoring patch svc monitoring-grafana \
      -p '{"spec":{"type":"NodePort","ports":[{"port":3000,"targetPort":3000,"protocol":"TCP","name":"http","nodePort":32000}]}}'
    kubectl -n monitoring get svc monitoring-grafana -o wide
    # 접속: http://<노드IP>:32000

     

     

    내 애플리케이션(Spring Boot) 메트릭 붙이기

    1. 앱에 Actuator + Micrometer Prometheus 적용 후 /actuator/prometheus 노출.
    2. Service로 노출하고 ServiceMonitor를 작성합니다. ServiceMonitor/PodMonitor 개념·스펙은 Prometheus Operator 문서 참고. Prometheus OperatorGitHub

    예: Spring 앱 Service + ServiceMonitor

     

     

    # app 서비스 (metrics 포트/엔드포인트 가정)
    apiVersion: v1
    kind: Service
    metadata:
      name: app
      namespace: app
      labels: { app: myapp }
    spec:
      selector: { app: myapp }
      ports:
        - name: http
          port: 8080
          targetPort: 8080
    
    ---
    # ServiceMonitor: app 네임스페이스의 app 서비스의 /actuator/prometheus를 스크레이프
    apiVersion: monitoring.coreos.com/v1
    kind: ServiceMonitor
    metadata:
      name: app-sm
      namespace: monitoring       # kube-prometheus-stack의 release 네임스페이스에 두는 것이 보통
      labels:
        release: monitoring       # (중요) 스택이 감지할 선택자에 맞춰주세요 (values에 따라 달라짐)
    spec:
      namespaceSelector:
        matchNames: [app]
      selector:
        matchLabels:
          app: myapp
      endpoints:
        - port: http
          path: /actuator/prometheus
          interval: 15s

     

     

    알람(경보)과 Alertmanager

    • kube-prometheus-stack에는 기본 룰/대시보드가 포함됩니다. 사용자 알람은 PrometheusRule로 추가해 Alertmanager로 전송하세요. GitHub

     

    apiVersion: monitoring.coreos.com/v1
    kind: PrometheusRule
    metadata:
      name: app-rules
      namespace: monitoring
      labels: { release: monitoring }
    spec:
      groups:
        - name: app-latency
          rules:
            - alert: AppHighLatency
              expr: histogram_quantile(0.95,
                     sum(rate(http_server_requests_seconds_bucket{job="app"}[5m])) by (le)) > 1
              for: 5m
              labels: { severity: warning }
              annotations:
                summary: "p95 latency > 1s for 5m"

     

     

     

     

     

     

     

     

    728x90
    반응형
Designed by Tistory.