-
쿠버네티스 서버 CI ,DI 구조까지 완벽 가이드 (with 젠킨스편) 2탄Devops/kubernetes 2025. 8. 27. 14:19728x90반응형
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.yamlndoeip 설정:
# 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_ed255192. 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.git4.쿠버네티스 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.yaml5. 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_hosts6. 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 YAMLSecret 포맷을 표준으로 재생성
# 기존 삭제(데이터 유지 안 해도 되면) 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-labels8.컨트롤러 재시작(재스캔 트리거)
# 컨트롤러 포드만 한 개 지우면 자동 재생성 kubectl -n jenkins get pods -o name | grep -E 'jenkins|controller' | head -n1 | xargs kubectl -n jenkins delete9. 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-api1. prod 네임스페이스 생성
kubectl create namespace prod2. 레지스트리 서버에 설치 (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 -d2) 모든 쿠버네티스 노드에서: 레지스트리 인증서 신뢰 설정
컨테이너 런타임이 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.comDeployment 생성:
배포 매니페스트(요약) – 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: 20apply 처리
kubectl apply -n prod -f k8s/prod/deployment.yaml4) 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-namespaceGrafana 접속
#(예) 포트포워드 kubectl -n monitoring port-forward svc/monitoring-grafana 3000:80 #(예) 초기 비밀번호 확인(릴리스/리소스명은 환경에 따라 다를 수 있음) kubectl -n monitoring get secret monitoring-grafana \ -o jsonpath='{.data.admin-password}' | base64 -d; echo1.외부접속 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) 메트릭 붙이기
- 앱에 Actuator + Micrometer Prometheus 적용 후 /actuator/prometheus 노출.
- 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반응형'Devops > kubernetes' 카테고리의 다른 글
쿠버네티스 pod란 ?파드란? pod생성 및 사용법 -수정중 (0) 2024.05.03 Kubernetes 필요이유 , Kubernetes란 무엇인가? 쿠버네티스란? k8s?,Docker? -1 (0) 2024.04.29