기존에는 Jenkins와 kubectl을 활용하여 쿠버네티스 배포를 직접 관리했으며, Jenkins 파이프라인을 통해 CI/CD를 구성하고 kubectl 명령어로 Deployment, Service 등의 리소스를 생성하거나 수정하는 방식으로 실습했다.

그러나 이러한 방식은 변경 이력 관리가 어렵고, 실시간 운영 환경과 Git 저장소 간의 불일치가 발생할 수 있어 유지보수가 복잡해지는 단점이 있다. 이를 개선하기 위해 ArgoCD를 통해 배포하는 것을 실습할 것이다.

그전에 k8s 패키지 매니저인 Helm과 Kustomize를 다뤄보면서 패키지 단위로 리소스를 배포해볼 것이다.

Helm이란?

쿠버네티스 패키지 매니저(apt, yum, pip 같은 거)

  • 여러 리소스(YAML)를 하나의 Chart로 묶어서 관리
  • 변수화(template) 가능 → 환경별 분기도 편리
  • 설치, 업데이트, 롤백이 편함
  • 다양한 배포 편의기능

예시 구조

mychart/
├── Chart.yaml          # 메타 정보
├── values.yaml         # 변수 설정 (환경값 등)
└── templates/
    ├── deployment.yaml
    ├── service.yaml

장점

  • values.yaml로 변수를 한 곳에서 관리할 수 있어서 편리
  • values.yaml로 환경변수를 다르게 줘 같은 템플릿으로 다양하게 활용 가능
  • 차트 단위로 배포하니 휴먼에러 줄임

Kustomize란?

이것도 쿠버네티스 패키지 매니저임

기본 YAML을 ‘덮어씌우기’식으로 변형해주는 도구

Heml처럼 템플릿은 쓰지 않고 YAML 그대로 조합만 함

  • base 폴더를 정의하고, 환경별 overaly로 조합
  • patch(부분 덮어쓰기) 지원
  • 쿠버네티스 내장됨

예시 구조

kustomization.yaml
├── base/
│   ├── deployment.yaml
│   └── service.yaml
├── overlays/
    ├── dev/
    │   └── kustomization.yaml (replicas 1)
    └── prod/
        └── kustomization.yaml (replicas 3) 

장점

  • 템플릿 없이 YAML 그대로 사용 가능
  • 환경별 차이 쉽게 관리
  • ArgoCD와 같이쓰기 좋음

예시로 차이점 알아보기

Helm 방식 (템플릿 기반)

mychart/
├── Chart.yaml
├── values.yaml (기본값)
├── values-dev.yaml (dev 환경변수)
├── values-prod.yaml (prod 환경변수)
└── templates/
    ├── deployment.yaml
    └── service.yaml

template 문법이 추가된 yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: myapp
spec:
  replicas: {{ .Values.replicaCount }} **-> 넣어주는 value로 달라짐**

배포 방법

# dev 환경
helm install myapp-dev ./mychart -f values-dev.yaml

# prod 환경
helm install myapp-prod ./mychart -f values-prod.yaml

Kustomize 방식 (patch 기반)

덮어쓰기가 핵심!

kustomize/
├── base/ (덮어 쓰기할 원본)
│   ├── deployment.yaml
│   ├── service.yaml
│   └── kustomization.yaml
└── overlays/
    ├── dev/
    │   ├── kustomization.yaml
    │   └── deployment-patch.yaml
    └── prod/
        ├── kustomization.yaml
        └── deployment-patch.yaml

원본가 다른 부분 덮어쓰기

# 원본
spec:
  replicas: 1
  
# dev
spec:
  replicas: 3 -> 이 값만 덮어씌워짐

YAML을 그대로 쓰기 때문에 별도의 수정은 필요 없음 원본 파일에 오염이 없음

ArgoCD란?

GitOps 방식의 쿠버네티스 CD 툴

  • Git이 Source of Truth가 됨 Git의 YAML과 클러스터 동기화
  • Git에 Push만 하면 자동 배포
  • UI로 리소스 상태 실시간 확인
  • Heml/Kustomize 둘 다 지원

보통 실무에선 아래와 같은 흐름으로 구성

개발자가 PR → main merge
       ↓
[1] Jenkins
    - 테스트/빌드
    - Docker 이미지 빌드 & Push
       ↓
[2] GitOps Repo에 values.yaml 수정 (image tag 등)
       ↓
[3] ArgoCD
    - Git 상태를 감지
    - 자동 배포 실행 (Helm/Kustomize로 정의된 리소스)

CI → Jenkins

CD → ArgoCD

실습

Helm 설치

▶ CI/CD Server에서 helm 설치 (root 유저) - M시리즈용

[root@cicd-server ~]#
yum install -y tar
curl -O <https://get.helm.sh/helm-v3.13.2-linux-arm64.tar.gz>
tar -zxvf helm-v3.13.2-linux-arm64.tar.gz
mv linux-arm64/helm /usr/bin/helm

▶ 확인하기

# jenkins 유저로 전환해서 확인
[root@cicd-server ~]# su - jenkins -s /bin/bash
[jenkins@cicd-server ~]$ helm

▶ 템플릿 생성하기

[jenkins@cicd-server ~]$ helm create api-tester

Kustomize 설치

이미 내장 되어 있음 kubectl에

▶ Kustomize Version 확인

kubectl version --client

Helm 배포 시작하기

Definition : Pipeline script from SCM
Definition > SCM : Git
Definition > SCM > Repositories > Repository URL : <https://github.com/>/kubernetes-anotherclass-sprint2.git
Definition > SCM > Branches to build > Branch Specifier : */main
Definition > SCM > Branches to build > Additional Behaviours > Sparse Checkout paths > Path : 2221
Definition > Script Path : 2221/Jenkinsfile

기존처럼 만들고 배포시작

배포 중 템플릿을 확인하게 만드는 과정 추가

 

helm template api-tester-2221 ./2221/deploy/helm/4.addition/api-tester -n anotherclass-222 --create-namespace

이 명령어로 헬름 차드가 어떤 리소스를 생성하게 될 지 미리 확인할 수 있다.

즉 내가 만들 때 환경 변수를 잘 넣어줬나 혹은 values.yaml을 잘 적었고, 리소스에 적용까지 잘 된 것인가를 미리 확인할 수 있는 것

실제 적용 전에 확인하므로써 시스템에 적용되기전 한 번의 체크를 더 할 수 있어서 안정적인 운영을 할 수 있다.

1. 초기상태(init)

처음 만들게 되면 기본적으로 아래와 같이 만들어짐

helm create api-tester -> 내가 수정할 템플릿 초안을 만드는 것

실습에서는 github에 올려진 템플릿으로 배포할 것임

[jenkins@cicd-server ~]$ helm create api-tester -> 내가 수정할 템플릿 초안을 만드는 것
[jenkins@cicd-server ~]$ cd api-tester
[jenkins@cicd-server api-tester]$ ls -al
total 16
drwxr-xr-x.  4 jenkins jenkins   93 Jun  1 23:31 .
drwxr-xr-x. 20 jenkins jenkins 4096 Jun  1 23:55 ..
-rw-r--r--.  1 jenkins jenkins  349 Jun  1 23:57 .helmignore
-rw-r--r--.  1 jenkins jenkins 1146 Jun  1 23:57 Chart.yaml
drwxr-xr-x.  2 jenkins jenkins    6 Jun  1 23:31 charts
drwxr-xr-x.  3 jenkins jenkins  162 Jun  1 23:31 templates
-rw-r--r--.  1 jenkins jenkins 2255 Jun  1 23:57 values.yaml

이렇게 기본적인 값들로만 이뤄져있음

[jenkins@cicd-server templates]$ cat service.yaml
apiVersion: v1
kind: Service
metadata:
  name: {{ include "api-tester.fullname" . }}
  labels:
    {{- include "api-tester.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: http
      protocol: TCP
      name: http
  selector:
    {{- include "api-tester.selectorLabels" . | nindent 4 }}

이 상태에서 필요없는거 지우고 기존 거 수정하고, 추가하면서 나의 Helm Chart를 만드는 것이다.

2. 당장 필요없는 내용 삭제

  • charts 제거: 하위 차트 없음
  • testes 제거: 실습이라 test 할 필요 없음

 

 

 

3. 내 YAML 에 맞게 수정하기

기존의 있는 리소스를 수정하기

 

 

4. 내 리소스 더 추가하기

기존의 자동으로 만들어주지 않는 리소스 추가하기

최종적으로 아래와 같은 파일구조가 만들어진다.

 

 

실습에서는 jenkinsfile 에는 아래와 같이 github의 모든 수정이 끝난 addition/api-tester로 배포하게 되어있음

stage('헬름 배포') {
            steps {
                input message: '배포 시작', ok: "Yes"
                sh "helm upgrade api-tester-${CLASS_NUM} ./${CLASS_NUM}/deploy/helm/4.addition/api-tester -n anotherclass-222 --create-namespace --install"
            }
        }

헬름 차트를 통해서 정상적으로 배포가 됐음

 

 

5. 실습 후 정리

  1. helm 삭제 → 추후에 다시 만들 헬름 릴리스 이름으로 중복 오류 발생 방지를 위해
  2. namespace 삭제 → 이건 실제 쿠버네티스 리소스들을 지우기 위해
# helm 조회
helm list -n anotherclass-222
# helm 삭제
helm uninstall -n anotherclass-222 api-tester-2221
# namespace 삭제
kubectl delete ns anotherclass-222

[jenkins@cicd-server ~]$ helm list -n anotherclass-222
NAME            NAMESPACE               REVISION        UPDATED                               STATUS   CHART                   APP VERSION
api-tester-2221 anotherclass-222        1               2025-06-02 00:36:22.151814401 +0900 KSdeployed api-tester-0.1.0        v1.0.0
[jenkins@cicd-server ~]$ helm uninstall -n anotherclass-222 api-tester-2221
release "api-tester-2221" uninstalled
[jenkins@cicd-server ~]$ kubectl delete ns anotherclass-222
namespace "anotherclass-222" deleted

실습 결과

Helm 차트로 미리 내가 사용할 YAML 파일들을 구조화해두면, 한 줄의 명령어로 관련된 모든 Kubernetes 리소스(Deployment, Service, ConfigMap, Secret 등)를 패키지처럼 일괄 배포할 수 있어 매우 효율적이다.

또한, 환경별 설정을 values.yaml로 분리할 수 있어 재사용성과 유지보수성이 크게 향상된다. 기존에 kubectl apply -f로 일일이 파일을 관리하던 방식보다 버전 관리와 팀 협업에도 유리하다.

Helm은 단순한 배포 도구를 넘어서, Kubernetes 리소스의 선언적 패키징 및 버전 관리를 가능하게 하며, upgrade, rollback, uninstall 등 배포 라이프사이클 전반을 체계적으로 관리할 수 있도록 도와준다.

이렇게 values.yaml을 여러 개 만들어 다양한 환경(prod, qa, dev)을 바로 install할 수 있다.

  • helm install: kubectl create > 생성 (있으면 에러)
  • helm upgrade: kubectl patch → 부분적 변경
  • helm upgrade —install ⇒ kubectl apply → 이게 가장 간편함 없으면 설치 있으면 변경된 부분 적용

Helm VS Kustomize 비교

  • 명령어
    • 헬름은 별도의 명령어
    • kubectl에 내장됨
  • 패키지
    • 헬름은 Artifact HUB에 다양한 패키지들이 올라와 있음(설치가이드도 보통 있음)
    • 공식 패키지 저장소 없이 Git 저장소를 이용
  • 사용방식
    • 헬름은 함수 방식
    • kustomize는 base YAML에 overlay를 덮어쓰기

 

 

파라미터로 제공 개발환경을 입력할 수 있음

 

Kustomize을 통한 배포

        stage('커스터마이즈 배포') {
            steps {
                // K8S 배포
                input message: '배포 시작', ok: "Yes"
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/kubectl/namespace-${params.PROFILE}.yaml"
                sh "kubectl apply -k ./${CLASS_NUM}/deploy/kustomize/api-tester/overlays/${params.PROFILE}"
            }
        }

kubectl 그대로 사용하고 있음

 

 

두개의 네임스페이스가 만들어지고 파드까지 있음

# namespace 삭제
kubectl delete ns anotherclass-222-dev
kubectl delete ns anotherclass-222-qa
kubectl delete ns anotherclass-222-prod

헬름을 통한 배포

        stage('헬름 배포') {
            steps {
                input message: '배포 시작', ok: "Yes"
                sh "kubectl apply -f ./${CLASS_NUM}/deploy/kubectl/namespace-${params.PROFILE}.yaml"
                sh "helm upgrade api-tester-${CLASS_NUM} ./${CLASS_NUM}/deploy/helm/api-tester" +
                   " -f ./${CLASS_NUM}/deploy/helm/api-tester/values-${params.PROFILE}.yaml" +
                   " -n anotherclass-222-${params.PROFILE} --install"  //  --create-namespace
            }

네임스페이스 만드는 건 helm 명령어로 바로 사용할 수 있음 옵션추가해서

하지만 별도로 kubectl로 네임스페이스를 만듦

헬름배포 시 네임스페이스 관여시키는 건 안좋음

별도의 명령어인 helm을 사용하여 헬름 릴리스 생성

 

헬름 릴리스 확인

 

 

# helm 조회
helm list --all-namespaces
# helm 삭제
helm uninstall -n anotherclass-222-dev api-tester-2223
helm uninstall -n anotherclass-222-qa api-tester-2223
helm uninstall -n anotherclass-222-prod api-tester-2223

# namespace 삭제
kubectl delete ns anotherclass-222-dev
kubectl delete ns anotherclass-222-qa
kubectl delete ns anotherclass-222-prod

배포 파이프라인 구축 후 마주하게 되는 고민들

1. Docker config.json 유지 고민

문제 상황

  • 이건 docker login 명령어 실행 시 자동으로 만들어지는 파일 → 이후 로그인 없이 바로 접속가능하도록
  • 여기는 암호화 안된 채로 중요 정보 담김
  • 노드 별로 따로 만들어야하고
  • 계정마다 파일 따로 필요

해결 방안

  1. docker login 명령어를 파이프라인 내에 넣기 → 보안 상 문제
  2. Jenkins Credentials + withCredentials 사용 → 안전
  3. docker-credential-helpers 사용 → 암호화된 채로 보관
  4. 노드 간 공유 디렉토리 마운트

 

 

2. 사용하지 않는 이미지가 계속 남음

문제 상황

컨테이너가 사용하는 이미지가 Node에 저장됨

하지만 해당 이미지가 더 이상 어떤 Pod에서도 사용되지 않아도 자동 삭제 안됨 → 디스크용량의 압박

해결 방안

일정 조건이 되면 자동으로 이미지를 삭제하게 설정

3. Helm 배포 완료와 실제 준비 완료 불일치

문제 상황

helm install 후 파드가 실제로 준비가 됐는지 별도로 확인이 필요함

트래픽을 넘겨주기전에 ready 체크를 따로 로직을 넣어줘야함

해결 방안

--wait 사용하고 Probe 를 필수로 설정해준다.

4. 이미지 태그 latest 사용의 위험성

문제 상황

잦은 배포를 하며 개발하는 환경에서 무의미한 버전닝이 될 수 있어서 latest를 사용한다면 + imagePullPolicy:IfNotPresent

소스코드가 수정되어 새롭게 빌드된 이미지가 있지만 항상 최신을 쓰기 위해 latest를 사용한다면

템플릿은 변경되지 않을 것이다.

그렇기에 helm upgrade는 변화를 감지하지 못하고 소스코드의 변화를 적용하지 못할 것이다.

imagePullPolicy

  • Always: 항상 이미지 pull
  • IfNotPresent: 이미지가 로컬에 없을 때만 pull → 이미지 이름이 다르면 가져옴
  • Never: 이미지 절대 Pull 안함

해결 방안

  1. latest 태그 사용을 자제
    • latest는 불명확한 버전표시
    • 소스코드가 변경되어 새 이미지를 푸시해도 쿠버네티스는 이를 감지하지 못할 수 있다.
    • 따라서 v1.0.1, build-2311, 20250603 같이 고유한 버전 태그를 사용하는 것이 좋다.
  2. imagePullPolicy: Always 설정 (주의 필요)
    • Always는 무조건 이미지를 다시 다운로드한다.
    • 이는 CI/CD 테스트에는 유용하지만,
      • 네트워크 낭비
      • 속도 저하
      • 프라이빗 레지스트리 인증 문제
      • 등의 문제가 발생할 수 있어 실운영 환경에서는 안좋을 수 있다.
  3. metadata.annotations 사용
    • 템플릿 내용은 동일하고, 이미지 태그도 latest로 고정되어 있다면 Helm은 변경사항이 없다고 판단
    • 하지만 다음과 같이 annotations에 빌드시간, Git SHA, UUID 등 무작위 값을 넣어주면 템플릿 변경이 발생한 것으로 인식되어 새롭게 배포를 강제할 수 있음
    • 이 방식은 Helm이 변경을 감지하게 만들어 강제로 롤링 업데이트를 유도

실습

1. Credentials로 config파일 만들기

  1. 인증서(마스터노드에 접근할 수 있는)를 호스트로 복사 후 Credentials에 등록
  2. 기존 cicd에 있는 인증서 삭제
  3. cicd서버에서 kubectl 사용 못함
[jenkins@cicd-server ~]$mv ~/.kube/config ~/.kube/config_bakk
[jenkins@cicd-server ~]$ kubectl get pods -A
E0602 14:54:02.662744    5232 memcache.go:265] couldn't get current server API group list: <html><head><meta http-equiv='refresh' content='1;url=/login?from=%2Fapi%3Ftimeout%3D32s'/><script id='redirect' data-redirect-url='/login?from=%2Fapi%3Ftimeout%3D32s' src='/static/d6b8cd4e/scripts/redirect.js'></script></head><body style='background-color:white; color:white;'>
Authentication required
<!--
-->
</body></html>
E0602 14:54:02.664773    5232 memcache.go:265] couldn't get current server API group list: <html><head><meta http-equiv='refresh' content='1;url=/login?from=%2Fapi%3Ftimeout%3D32s'/><script id='redirect' data-redirect-url='/login?from=%2Fapi%3Ftimeout%3D32s' src='/static/d6b8cd4e/scripts/redirect.js'></script></head><body style='background-color:white; color:white;'>
Authentication required
<!--
-->

이렇게 서버에는 인증정보 관련한 걸 가지면 안됨

이제 이걸 Credentials 로 사용할 수 있게 하면 됨

2. 잦은 배포

잦은 배포로 버전을 적는게 무의미함

자동으로 태그를 만들게 해서 매번 변경을 감지할 수 있게

environment {
  APP_VERSION = '1.0.1'
  BUILD_DATE = sh(script: "echo `date +%y%m%d.%d%H%M`", returnStdout: true).trim()
  // 위에 date 포맷 오류가 있어요. %y%m%d.%H%M%S가 맞습니다)
  TAG = "${APP_VERSION}-" + "${BUILD_DATA}"

stage('컨테이너 빌드 및 업로드') {
  steps {
	script{
	  // 도커 빌드
      sh "docker build ./2224/build/docker -t 1pro/api-tester:${TAG}"
      sh "docker push 1pro/api-tester:${TAG}"

stage('헬름 배포') {
  steps {
    withCredentials([file(credentialsId: 'k8s_master_config', variable: 'KUBECONFIG')]) {
      sh "helm upgrade api-tester-2224 ./2224/deploy/helm/api-tester -f ./2224/deploy/helm/api-tester/values-dev.yaml" +
         ...
         " --set image.tag=${TAG}"   // Deployment의 Image에 태그 값 주입

3. 업로두 후 이미지 삭제

stage('컨테이너 빌드 및 업로드') {
  steps {
	script{
	  // 도커 빌드
      sh "docker build ./${CLASS_NUM}/build/docker -t ${DOCKERHUB_USERNAME}/api-tester:${TAG}"
      sh "docker push ${DOCKERHUB_USERNAME}/api-tester:${TAG}"
      sh "docker rmi ${DOCKERHUB_USERNAME}/api-tester:${TAG}"   // 이미지 삭제

4. 네임스페이스는 배포와 별도로 관리

stage('네임스페이스 생성') {  // 배포시 apply로 Namespace 생성 or 배포와 별개로 미리 생성 (추후 삭제시 별도 삭제)
  steps {
    withCredentials([file(credentialsId: 'k8s_master_config', variable: 'KUBECONFIG')]) {
      sh "kubectl apply -f ./2224/deploy/kubectl/namespace-dev.yaml --kubeconfig " + '"${KUBECONFIG}"'
...
stage('헬름 배포') {
  steps {

5. Helm 부가 기능이용

  • 배포 후 Pod 업그레이드 안될 수 있으니 랜덤값 적용시킴
apiVersion: apps/v1
kind: Deployment
spec:
  template:
    metadata:      
      annotations:  
        rollme: {{ randAlphaNum 5 | quote }} // 항상 새 배포를 위해 랜덤값 적용
  • Pod가 완전 기동 될때까지 기다리게 설정
stage('헬름 배포') {
  steps {
    withCredentials([file(credentialsId: 'k8s_master_config', variable: 'KUBECONFIG')]) {
      sh "helm upgrade api-tester-2224 ./2224/deploy/helm/api-tester -f ./2224/deploy/helm/api-tester/values-dev.yaml" +
         ...
         " --wait --timeout=10m" +  // 최대 10분으로 설정

6. 사용 안하는 이미지 자동 삭제

  • 이미지가 너무 많을 때 삭제해주는 Garbage Collection을 통해 관리
// GC 속성 추가하기
[root@k8s-master ~]# vi /var/lib/kubelet/config.yaml
-----------------------------------
imageMinimumGCAge : 3m // 이미지 생성 후 해당 시간이 지나야 GC 대상이 됨 (Default : 2m)
imageGCHighThresholdPercent : 80 // Disk가 80% 위로 올라가면 GC 수행 (Default : 85)
imageGCLowThresholdPercent : 70 // Disk가 70% 밑으로 떨어지면 GC 수행 안함(Default : 80)
-----------------------------------

// kubelet 재시작
[root@k8s-master ~]# systemctl restart kubelet

 

 

 

 

마무리

다양한 환경을 준비하려면 기존의 방식으로는 환경마다의 리소스를 위한 yaml 파일이 필요했다.

이번에 배운 Helm, Kustomize를 이용한다면 yaml 파일들을 패키지화해서 관리할 수 있어 편리하다.

 

이제 마지막 단계인 ArgoCD를 통해 배포를 해보며 지금보다 어떤 장점이 있는지 알아보면 될 것 같다.

 

+ Recent posts