Kubernetes 환경에서 배포 자동화를 고민해본 엔지니어라면 GitOps라는 용어에 익숙할 것입니다. Git을 중심으로 한 선언적 구성과 자동화를 핵심으로 하는 이 방식은, 일관된 인프라 운영을 가능하게 합니다. 하지만 실제로 GitOps를 적용하려고 하면, 적절한 도구를 선택하거나 배포 구조를 설계하는 데 어려움을 겪는 경우가 많습니다.
GitOps 환경의 기본적인 개념과 원리에 대한 이해가 있다는 전제를 바탕으로, 선언적 리소스 구성과 배포·테스트를 위해 Helm과 Kustomize를 사용하는 방법을 다음과 같은 순서로 자세히 살펴보겠습니다.
- 빌드/배포 자동화
- Kustomize 기반 리소스 디렉토리 구조
- Base 리소스 구성
- Overlay 리소스 구성
- GitOps 배포 CLI 자동화
- 배포 및 테스트
- 마무리
이 글은 GitOps 데모를 위해 Helm과 Kustomize로 미리 구성한 HttpBin 어플리케이션을 사용하여 작성되었습니다. 이와 관련된 코드는 GitHub의 gitops-demo 저장소에서 확인할 수 있습니다.
왜 Helm과 Kustomize를 함께 써야 할까?
Kubernetes 환경에서 애플리케이션을 안정적으로 배포하고 운영하기 위해서는 템플릿화된 배포 관리와 환경별 커스터마이징이라는 두 가지 요소를 모두 충족해야 합니다. 이때 각각의 강점을 가진 Helm과 Kustomize를 함께 사용하면 더 유연하고 강력한 배포 구조를 구성할 수 있습니다.
Helm은 Kubernetes 애플리케이션 배포를 위한 패키지 매니저로, 복잡한 리소스들을 템플릿 형태로 정의하고 재사용할 수 있게 해줍니다. 이를 통해 반복 가능한 배포가 가능하며, 유지보수가 용이한 구조를 만들 수 있습니다. 하지만 Helm만으로는 개발, 스테이징, 운영 등 환경별 설정을 세분화하거나, 복잡한 조건에 따라 리소스를 유연하게 조정하는 데에는 한계가 있습니다.
반면 Kustomize는 리소스를 선언적으로 수정하고, 환경에 따라 패치하거나 커스터마이징하기에 적합한 도구입니다. 오버레이 구조를 통해 각 환경에 필요한 설정만 분리해 적용할 수 있으며, 리소스를 직접 수정하지 않고도 유연하게 변형할 수 있습니다.
이 두 도구를 조합하면 Helm을 사용해 애플리케이션의 기본 차트(베이스)를 구성하고, Kustomize를 통해 환경별로 필요한 설정을 오버레이 형태로 패치하거나 리소스를 추가할 수 있습니다. 결과적으로 Helm이 제공하는 템플릿 기반의 일관된 배포와, Kustomize가 제공하는 환경별 유연한 커스터마이징을 동시에 활용할 수 있게 됩니다. GitOps 스타일의 배포를 고려한다면, 이 조합은 반복 가능하면서도 환경에 따라 세밀하게 조정 가능한 이상적인 선택이 될 수 있습니다.
1. 빌드/배포 자동화
Kubernetes 환경에서 애플리케이션 빌드와 배포 자동화는 CI/CD와 GitOps를 함께 활용하는 방식이 업계에서 사실상 표준으로 자리잡고 있습니다.
CI/CD 단계
- 개발자가 자신의 Git 브랜치에서 main 브랜치로 PR을 요청합니다.
- PR이 승인되어 머지되면 CI/CD 도구가 변경을 감지하여 코드를 빌드하고 테스트합니다.
- 이미지를 빌드하고 이미지 레지스트리에 푸시합니다.
- Kubernetes 리소스를 Git 저장소에서 가져와 이미지 태그를 업데이트하고, Git에 커밋하고 푸시합니다.
GitOps 단계
- Kubernetes 클러스터에 배포할 리소스를 YAML, Helm, Kustomize 형태로 선언적으로 구성하여 Git 저장소에서 관리합니다.
- GitOps 도구(ArgoCD, FluxCD 등)가 변경 사항을 감지하여 클러스터에 자동으로 적용합니다.
- 클러스터 상태가 Git 저장소의 선언 상태와 불일치할 경우 자동으로 복구(Self-healing)를 수행합니다.
여기서는 GitOps에서 가장 핵심이 되는 배포 리소스를 Helm과 Kustomize를 활용해 구성하는 방법에 대해 소개합니다.
2. Kustomize 기반 리소스 디렉토리 구조
GitOps를 위해 Kustomize와 Helm을 사용하여 선언적 Kubernetes 리소스를 구성하고 관리하는 Git 저장소의 디렉터리 구조를 다음과 같이 구성합니다.
.
|
├── kustomize/ # Kustomize 기반 리소스 관리
| ├── base/ # 모든 환경에서 공통으로 사용하는 리소스 구성
| │ ├── helm/ # Helm 차트를 로컬에 저장
| │ │ └── httpbin/ # 예제 앱 (httpbin)의 Helm 차트 디렉토리
| │ │ └── templates/ # Helm 템플릿 내부 디렉토리
| │ │ └── tests/ # Helm 테스트 리소스
| │ └── resources/ # namespace, ingress 등 공통 리소스 구성
| └── overlays/ # 환경별 오버레이 구성
| ├── dev/ # 개발 환경 전용 설정
| │ ├── helm/ # dev용 Helm 차트와 values 설정
| │ ├── patches/ # dev 환경 전용 패치 (예: 컨테이너 포트, env 등)
| │ ├── resources/ # dev에만 필요한 리소스 (예: 테스트용 ConfigMap)
| │ └── tls/ # dev 환경용 TLS 인증서 및 키
| └── stg/ # 스테이징 환경 전용 설정 (dev와 유사한 구조)
└── Makefile
- Base에는 모든 환경에서 공통으로 사용하는 Helm 차트와 공통 리소스가 포함합니다.
- Overlay에는 dev, stg, prd 등 환경별로 필요한 리소스, 패치, 인증서 등을 추가 구성합니다.
- Makefile은 Helm 차트를 원격 저장소에서 내려받아 base/helm/에 저장하고, kustomization.yaml을 통해
배포하거나 삭제하는 작업을 자동화합니다.
이러한 구조는 GitOps에서 환경 분리, 버전 관리, 자동화 배포를 효율적으로 실현할 수 있게 해주며, Argo CD, Flux 등과의 통합에도 매우 적합합니다.
3. Base 리소스 구성
Base는 dev, stg, prd 등 다양한 환경에서 공통으로 사용되는 Kubernetes 리소스를 구성하는 영역입니다. 각 Overlay 환경에서는 Base를 기반으로 환경별 설정을 추가하거나 덮어쓰는 방식으로 리소스를 관리합니다.
kustomize/
├── base/
│ ├── helm/
│ │ └── httpbin/
│ │ └── templates/
│ │ ├── ...
│ │ └── ...
│ └── resources/
| ├── namespace.yaml
│ └── ingress.yaml
└── kustomization.yaml
- base/helm/
Helm 저장소에서 Helm 차트를 내려받아 저장합니다.
Kustomize 패치 기능으로 수정하기 어려운 리소스는 이 디렉토리 내에서 직접 수정할 수 있습니다.
Overlay 환경에서는 이 차트와 values.yaml을 활용해 환경별 리소스를 생성하도록 구성합니다.
여기서는 HttpBin Helm 차트를 내려받아 저장하였습니다. - base/resources/
모든 Overlays 환경에서 공통적으로 사용되어야 하는 Deployment, ConfigMap, RBAC 등의 리소스를 추가합니다.
Base의 kustomization.yaml에는 모든 Overlay에서 공통으로 사용되어야 하는 리소스를 정의합니다.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
resources:
- resources/namespace.yaml
- resources/ingress.yaml
4. Overlay 리소스 구성
Overlay는 Base를 기반으로 dev, stg, prd 등 환경별 설정을 추가하거나 덮어쓰는 방식으로 동작합니다. 이 방식을 통해 공통 구성은 재사용하면서도, 환경에 따라 필요한 설정만 유연하게 적용할 수 있습니다.
이번에는 Dev Overlay를 기준으로 Helm 차트 설정, 패치, TLS 및 추가 리소스를 구성하는 방법을 살펴보겠습니다.
kustomize/overlays/dev/
├── helm/
│ ├── values.yaml
│ └── helm-chart.yaml
├── patches/
│ ├── add-timezone.yaml
│ └── update-containerport.yaml
├── tls/
│ ├── tls.crt
│ └── tls.key
├── resources/
└── kustomization.yaml
- dev/helm/
HelmChartInflationGenerator를 위한 Helm 차트 설정 파일, values.yaml을 저장합니다. - dev/patches/
base 리소스를 환경에 맞게 수정하는 패치 파일을 저장합니다. - dev/tls/
SecretGenerator를 위한 TLS 인증서와 키를 저장합니다. - dev/resources/
dev 환경에만 필요한 추가 리소스를 저장합니다. - dev/kustomization.yaml
해당 Overlay에서 배포되어야 하는 모든 리소스에 대한 정보를 통합하여 정의합니다.
Kustomize는 Dev Overlay의 kustomization.yaml 파일을 정의된 리소스를 바탕으로, 최종적으로 Kubernetes 클러스터에 배포할 리소스를 생성합니다.
apiVersion: kustomize.config.k8s.io/v1beta1
kind: Kustomization
namespace: httpbin
generators:
- ./helm/helm-chart.yaml
resources:
- ../../base
images:
- name: mccutchen/go-httpbin
newName: kong/httpbin
newTag: latest
patches:
- target:
kind: Deployment
name: httpbin
path: patches/add-timezone.yaml
- target:
kind: Deployment
name: httpbin
path: patches/update-containerport.yaml
secretGenerator:
- name: httpbin-tls
files:
- tls.crt=./tls/tls.crt
- tls.key=./tls/tls.key
type: kubernetes.io/tls
네임스페이스 정의
모든 리소스를 httpbin 네임스페이스로 배포합니다.
namespace: httpbin
Helm 차트로부터 리소스 생성
Overlay의 각 환경에서는 Base에 구성된 Helm 차트를 활용하여 리소스를 생성합니다. 이를 위해 Overlay의 helm/ 디렉토리에 helm-chart.yaml와 values.yaml 파일을 설정합니다.
“helm-chart.yaml”
apiVersion: builtin
kind: HelmChartInflationGenerator
metadata:
name: httpbin
name: httpbin
chartHome: ../../base/helm
chart: httpbin
releaseName: httpbin
valuesFile: ./helm/values.yaml
“values.yaml”
fullnameOverride: httpbin
image:
repository: mccutchen/go-httpbin
pullPolicy: IfNotPresent
helm-chart.yaml은 아래와 같이 kustomization.yaml 파일의 generators 항목에 등록하여, 해당 Helm 차트로부터 Kubernetes 리소스를 생성할 수 있도록 합니다.
generators:
- ./helm/helm-chart.yaml
Base 리소스 참조
Base에 공통 리소스가 구성된 경우, Overlay에서 참조하도록 구성합니다.
resources:
- ../../base
이렇게 하면 Base의 kustomization.yaml에 정의된 모든 리소스가 Overlay에 포함되어 환경별 추가 설정과 병합됩니다.
이미지 오버라이드
Base의 httpbin Helm 차트에 정의된 이미지는 amd64 아키텍처만 지원하므로, 멀티 플랫폼을 지원하는 이미지로 교체하여 arm64에서도 정상적으로 동작하도록 구성하였습니다. 이 변경은 values.yaml에서도 가능하지만, Kustomize를 사용하여 이미지 오버라이드하였습니다.
images:
- name: mccutchen/go-httpbin
newName: kong/httpbin
newTag: latest
참고로 대부분의 프로젝트에서는 애플리케이션 변경 시 CI/CD 파이프라인을 통해 newTag 값을 자동으로 갱신하고, GitOps 도구를 통해 Git 저장소와 동기화하여 Kubernetes 클러스터에 변경 사항을 자동으로 반영합니다.
Deployment 패치
Deployment 리소스에 다음과 같은 변경을 패치로 적용합니다.
- 기존 이미지의 기본 포트 8080을, 오버라이드한 이미지가 사용하는 80번 포트로 변경합니다.
- 컨테이너 환경 변수로 TZ=Asia/Seoul을 추가해 시간대를 설정합니다.
patches:
- target:
kind: Deployment
name: httpbin
path: patches/add-timezone.yaml
- target:
kind: Deployment
name: httpbin
path: patches/update-containerport.yaml
“add-timezone.yaml”
- op: add
path: /spec/template/spec/containers/0/env
value:
- name: TZ
value: Asia/Seoul
“update-containerport.yaml”
- op: replace
path: /spec/template/spec/containers/0/ports/0/containerPort
value: 8080
TLS Secret 생성
secretGenerator를 사용하여 httpbin-tls과 같은 Secret을 생성할 수 있습니다.
secretGenerator:
- name: httpbin-tls
files:
- tls.crt=./tls/tls.crt
- tls.key=./tls/tls.key
type: kubernetes.io/tls
이 Secret은 Base의 ingress.yaml에서 tls.secretName으로 참조되며, Ingress에 설정된 host가 이 Secret에 있는 도메인과 일치할 경우 HTTPS 연결이 정상적으로 작동합니다.
httpbin-tls에 정의된 도메인은 다음과 같습니다.
- cnap.dev
- *.cnap.dev
- local
- *.local
이 도메인 변경을 위해 새로운 인증서를 발급하고자 하는 경우는, gitops-demo의 cert/Makefile 파일을 참조하세요.
5. GitOps 배포 CLI 자동화
GitOps 스타일 배포에서는 반복 가능하고 일관된 배포 흐름이 매우 중요합니다. 특히 환경별로 Helm 차트를 커스터마이징하고 Kustomize 오버레이를 적용할 때, 복잡한 CLI 명령어를 직접 관리하는 것은 번거롭고 실수할 위험도 큽니다.
이러한 문제를 해결하기 위해 Makefile을 활용하면, 복잡한 CLI 명령어를 간결한 타겟(target)으로 추상화할 수 있습니다. 이를 통해 GitOps 구성 관리, 리소스 배포, 배포 검증 과정을 효율적이고 일관되게 수행할 수 있습니다.
.PHONY: pull preview apply delete help
export DEPLOY_ENV ?= dev
KUBECTL := kubectl
KUSTOMIZE ?= kustomize
GITOPS_OVERLAYS := kustomize/overlays/$(DEPLOY_ENV)
namespace: ## Create Kubernetes namespace 'gateway'
kubectl create namespace httpbin || true
pull: ## Pull helm chart
rm -rf kustomize/base/helm
helm repo add httpbin https://matheusfm.dev/charts
helm repo update
helm pull httpbin/httpbin \
--version 0.1.1 \
--untar \
--destination kustomize/base/helm
delete: ## Undeploy workload
-$(KUSTOMIZE) build --enable-alpha-plugins $(GITOPS_OVERLAYS) | $(KUBECTL) delete -f -
apply: ## Deploy workload
$(KUSTOMIZE) build --enable-alpha-plugins $(GITOPS_OVERLAYS) | $(KUBECTL) apply -f -
preview:
$(KUSTOMIZE) build --enable-alpha-plugins $(GITOPS_OVERLAYS)
help: ## Show available targets and descriptions
@echo "Usage: make <target>"
@echo ""
@echo "Targets:"
@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
| sort \
| awk -F ':.*?## ' '{printf " %-20s %s\n", $$1, $$2}'
- namespace는 httpbin 네임스페이스를 생성합니다.
- pull은 Helm 차트를 지정된 버전으로 다운로드하여 base/helm에 저장합니다.
- preview는 오버레이 설정을 적용한 Kustomize 결과(YAML)를 출력합니다. (배포 없음)
- apply는 실제 Kubernetes 클러스터에 리소스 배포합니다.
- delete는 Kubernetes 배포된 모든 리소스를 삭제합니다.
- help는 Makefile에 정의된 모든 타겟 설명을 출력합니다.
6. 배포 및 테스트
이 작업을 위해서는 Kubernetes 클러스터가 준비되어 있어야 합니다. 테스트 환경은 linux-arm64 아키텍처를 지원하는 운영체제를 기준으로 구성됩니다. Kubernetes 클러스터 설치가 필요한 경우는 이전에 포스트한 Kind로 Kubernetes 클러스터 구축하기를 참조하시기 바랍니다.
먼저 helm, kustomize를 다운로드 받아 /usr/local/bin에 설치합니다.
# Helm v3.17.1 다운로드 및 설치 (Linux arm64 기준)
HELM_VERSION="v3.17.1"
curl -sLo helm.tar.gz https://get.helm.sh/helm-${HELM_VERSION}-linux-arm64.tar.gz
tar -xzf helm.tar.gz
sudo mv linux-arm64/helm /usr/local/bin/helm
sudo chmod +x /usr/local/bin/helm
helm version
# kustomize
KUSTOMIZE_VERSION="v5.6.0"
curl -sLo kustomize.tar.gz https://github.com/kubernetes-sigs/kustomize/releases/download/kustomize%2F${KUSTOMIZE_VERSION}/kustomize_${KUSTOMIZE_VERSION}_linux_arm64.tar.gz
tar -xzf kustomize.tar.gz
sudo mv kustomize /usr/local/bin/
sudo chmod +x /usr/local/bin/kustomize
kustomize version
이 글에서는 HttpBin Helm Chart를 Kustomize로 배포할 수 있도록 준비한 GitOps 리소스를 gitops-demo 저장소에서 가져옵니다.
git clone https://github.com/cnapcloud/gitops-demo.git
cd gitops-demo/httpbin
httpbin 폴더로 이동한 후, 다음 같은 순서로 httpbin 어플리케이션을 배포합니다.
# 네임스페이스 생성
make namespace
# Helm 차트 다운로드 (base/helm에 이미 존재한다면 생략 가능)
make pull
# 배포 전 미리보기
make preview
# 실제 배포 수행
make apply
배포가 정상적으로 완료되면 다음 명령어로 리소스를 확인할 수 있습니다.
$ kubectl -n httpbin get all,ing,secrets
NAME READY STATUS RESTARTS AGE
pod/httpbin-5c9946c75-lvrtr 1/1 Running 0 9h
pod/httpbin-test-connection 0/1 Completed 0 9h
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/httpbin ClusterIP 10.96.31.173 <none> 80/TCP 9h
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/httpbin 1/1 1 1 9h
NAME DESIRED CURRENT READY AGE
replicaset.apps/httpbin-5c9946c75 1 1 1 9h
NAME CLASS HOSTS ADDRESS PORTS AGE
ingress.networking.k8s.io/httpbin nginx httpbin.cnap.dev localhost 80,443 9h
NAME TYPE DATA AGE
secret/httpbin-tls-cmhf7fg45b kubernetes.io/tls 2 9h
secret/httpbin-tls-tdfmdfb99f kubernetes.io/tls 2 9h
Httpbin 어플리케이션에 HTTP 요청을 보내 정상 동작하는지 확인합니다. curl 컨테이너를 배포한 후, 내부에서 httpbin 서비스에 요청합니다.
kubectl exec -it deploy/curl -- curl http://httpbin.httpbin/status/200 -I
응답 결과:
HTTP/1.1 200 OK
Server: gunicorn
Date: Tue, 22 Jul 2025 12:46:05 GMT
Connection: keep-alive
Content-Type: text/html; charset=utf-8
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Content-Length: 0
로컬 환경에서 도메인 이름으로 접속하기 위해 /etc/hosts 파일에 다음 라인을 추가합니다.
127.0.0.1 httpbin.cnap.dev
이후 웹 브라우저에서 https://httpbin.cnap.dev로 접속하여 HTTPS 통신이 정상 동작하는지 확인합니다.
인증서 문제로 접속이 불가능한 경우, 이 글의 하단에 참고를 확인하여 루트 인증서를 등록합니다.
마지막으로 다음과 같이 Kubernetes 클러스터에 배포된 모든 자원을 삭제할 수 있습니다.
make delete
7. 마무리
Kustomize와 Helm을 결합하면 환경별로 유연한 리소스 관리가 가능해집니다. Overlay를 통해 Development → Staging → Production으로 확장도 손쉽게 이루어질 수 있고, 실무에서 배포 전략을 체계적으로 구성하는 데 매우 유용한 방식입니다.
이러한 구성은 다음과 같은 장점을 가집니다:
- Helm의 유연성과 Kustomize의 선언형 관리 장점 결합
- 확장성 있는 GitOps 파이프라인 설계 가능
- 구조화된 환경 분리: dev, stg, prod를 명확히 분리 가능
- Git 기반 버전관리: 모든 설정을 Git으로 관리 가능
다음 단계로는 Argo CD나 Flux와 같은 GitOps 툴을 이용하여 자동화된 배포를 구성해볼 수 있습니다.
참고
HTTPBin을 설치하고 Chrome이나 Safari에서 URL로 접근 시 발생하는 “신뢰할 수 없는 인증서” 경고를 피하기 위해서 Root CA이 등록이 필요하다. 여기서 사용된 Root CA는 gitops-demo 저장소의 cert/root-ca/rootCA.pem 경로에서 확인할 수 있습니다.
Windows에 Root CA 인증서 등록 방법
- .crt 또는 .pem 형식의 인증서 파일을 더블 클릭
- “인증서 설치” 클릭
- “로컬 컴퓨터(Local Machine)” 선택 후 다음
- 관리자 권한 필요
- “모든 인증서를 다음 저장소에 저장” 선택
- “신뢰할 수 있는 루트 인증 기관” 선택
- 설치 완료 후 브라우저 재시작 또는 재부팅
macOS에 Root CA 인증서 등록
- Spotlight에서 “키체인 접근” 입력하고 “키체인 열기” 실행
- 왼쪽 상단에서 시스템 키체인 선택
- 인증서 파일(.crt 또는 .pem)을 드래그하거나 파일 > 항목 가져오기로 가져오기
- 가져온 인증서를 더블 클릭
- “신뢰” 항목 펼치기
- “이 인증서를 사용할 때: 항상 신뢰"로 변경
- 설정 닫기 → 관리자 비밀번호 입력
Linux에 Root CA 인증서 등록
- gitop-demo/cert로 이동
- make root_ca