Kubernetes – ArgoCD + Helm 으로 Native Helm + GitOps Values 패턴 구성하기
이 글은 Kubernetes – Install on Debian & run nginx 의 후속편입니다.
1편에서 kubeadm 으로 구성한 단일 노드 클러스터(Debian 13 / Raspberry Pi, Kubernetes v1.34, Flannel) 위에
ArgoCD 와 Helm 을 올리고, 클러스터의 모든 애플리케이션을 Git 에 있는 values 파일만으로 관리하도록 바꿉니다.
0. 목표
1편까지의 상태는 다음과 같습니다.
- nginx 는
kubectl create -f로 직접 배포 - metrics-server, Dashboard 는 인터넷에서 받은 YAML 을
kubectl apply - 무엇이 어떤 설정으로 설치되어 있는지 기록이 클러스터 안에만 존재
2편에서는 이것을 아래처럼 바꿉니다.
- 클러스터에 설치되는 것은 모두 Helm 차트 (공식 차트를 수정 없이 사용 = Native Helm)
- 환경별 설정은 Git 저장소의 values.yaml 에만 존재 (GitOps Values)
- 배포는 사람이 아닌 ArgoCD 가 Git 을 보고 수행
- ArgoCD 자기 자신도 같은 방식으로 관리 (Self-managed)
1. Native Helm + GitOps Values 패턴이란?
ArgoCD 에서 Helm 차트를 배포하는 방법은 크게 세 가지가 있습니다.
| 방식 | 구조 | 단점 |
|---|---|---|
| Umbrella(Wrapper) 차트 | 내 Git 에 Chart.yaml 을 만들고 공식 차트를 dependencies 로 포함 |
차트 버전 업그레이드 시 Chart.lock/charts/*.tgz 관리 필요, 값이 한 단계 중첩됨 |
| Application 에 values 인라인 | spec.source.helm.values 에 YAML 을 직접 작성 |
Application 매니페스트가 비대해지고, helm template 로 로컬 검증하기 어려움 |
| Native Helm + GitOps Values ✅ | 차트는 Helm 저장소에서 그대로, values 파일은 내 Git 저장소에서 가져와 합침 | ArgoCD multi-source 기능 필요 (v2.6+, 현재 v3.x 에서는 문제 없음) |
세 번째 방식의 구조는 다음과 같습니다.
┌─────────────────────────────┐
Helm Repo │ metrics-server chart 3.13.x │──┐ (차트: 수정하지 않음)
└─────────────────────────────┘ │
├──▶ ArgoCD (helm template) ──▶ Cluster
┌─────────────────────────────┐ │
Git Repo │ values/metrics-server/ │──┘ (설정: Git 에서만 변경)
(내 저장소) │ values.yaml │
└─────────────────────────────┘
- 차트 버전 업그레이드 = Application 의
targetRevision한 줄 변경 - 설정 변경 =
values.yaml수정 후git push - 로컬에서도
helm template <chart> -f values/xxx/values.yaml로 ArgoCD 와 동일한 결과를 미리 볼 수 있음
2. 1편에서 추가/수정할 부분
2.1 전편 수동 설치 삭제
# 5장 nginx
kubectl delete svc nginx-service-nodeport
kubectl delete deployment nginx-server
# 6장 metrics-server (kube-system 의 동일 이름 리소스와 충돌하므로 반드시 삭제)
kubectl delete -f https://github.com/kubernetes-sigs/metrics-server/releases/latest/download/components.yaml
# 6장 대시보드
kubectl delete -f dashboard-user.yaml
kubectl delete -f recommended.yaml
2.2 Git 저장소 준비
GitHub 등에 저장소를 하나 만듭니다. 이 글에서는 https://github.com/<YOUR_ID>/k8s-gitops.git 로 가정합니다.
k8s-gitops/
├── bootstrap/
│ └── root-app.yaml # App of Apps 루트 (최초 1회만 kubectl apply)
├── apps/ # ArgoCD Application 정의 (차트 + 버전 + values 위치)
│ ├── argocd.yaml
│ ├── metrics-server.yaml
│ ├── headlamp.yaml
│ └── nginx.yaml
├── values/ # ★ 실제 설정은 전부 여기
│ ├── argocd/values.yaml
│ ├── metrics-server/values.yaml
│ ├── headlamp/values.yaml
│ └── nginx/values.yaml
└── charts/ # 공개 차트가 없는 자체 앱만
└── nginx/
├── Chart.yaml
├── values.yaml
└── templates/
├── deployment.yaml
└── service.yaml
규칙은 단순합니다.
apps/*.yaml: 무엇을, 어느 버전으로 설치할지values/*/values.yaml: 어떻게 설치할지- 차트 자체는 수정하지 않는다 (자체 앱 차트만
charts/에 둔다)
2.3 values/argocd/values.yaml
라즈베리 파이 단일 노드용 경량 설정입니다. 접속은 1편과 동일하게 NodePort 를 사용합니다.
# values/argocd/values.yaml
global:
domain: 192.168.20.206 # 노드 IP (1편 kubeadm init 에서 확인한 값)
configs:
params:
server.insecure: false
cm:
timeout.reconciliation: 180s # Git 폴링 주기 (기본 120s, 저사양이므로 약간 늘림)
server:
service:
type: NodePort
nodePortHttp: 30080
nodePortHttps: 30443
resources:
requests: { cpu: 50m, memory: 64Mi }
limits: { memory: 256Mi }
controller:
resources:
requests: { cpu: 100m, memory: 256Mi }
limits: { memory: 768Mi }
repoServer:
resources:
requests: { cpu: 50m, memory: 128Mi }
limits: { memory: 512Mi }
redis:
resources:
requests: { cpu: 20m, memory: 32Mi }
limits: { memory: 128Mi }
applicationSet:
resources:
requests: { cpu: 20m, memory: 32Mi }
limits: { memory: 128Mi }
# 단일 사용자 홈랩에서는 불필요 → 메모리 절약
dex:
enabled: false
notifications:
enabled: false
2.4 values/metrics-server/values.yaml
kubeadm 으로 만든 클러스터의 kubelet 은 자체 서명 인증서를 사용하기 때문에, metrics-server 가 kubelet 에 접속할 때 TLS 검증에 실패합니다.
1편 6장의 metrics-server 가 kubectl top 에서 오류를 냈다면 이것이 원인입니다.
# values/metrics-server/values.yaml
args:
- --kubelet-insecure-tls
resources:
requests: { cpu: 50m, memory: 64Mi }
limits: { memory: 128Mi }
운영 환경이라면
--kubelet-insecure-tls대신 kubelet 의serverTLSBootstrap: true설정과 CSR 승인으로 정식 인증서를 발급받는 것이 맞습니다. 홈랩 기준으로는 위 설정으로 충분합니다.
2.5 values/headlamp/values.yaml
Kubernetes Dashboard 는 2026년 1월 21일 아카이브되었고, 프로젝트 측에서 Headlamp 사용을 권장하고 있습니다.
1편의 대시보드(NodePort 30239)를 Headlamp 로 교체합니다.
# values/headlamp/values.yaml
service:
type: NodePort
port: 80
nodePort: 30239 # 1편 대시보드와 같은 포트 재사용
resources:
requests: { cpu: 20m, memory: 64Mi }
limits: { memory: 256Mi }
2.6 자체 앱: charts/nginx + values/nginx/values.yaml
1편의 nginx-deployment.yaml 을 최소한의 Helm 차트로 옮깁니다.
charts/nginx/Chart.yaml
apiVersion: v2
name: nginx
description: Hello World nginx (1편 예제)
type: application
version: 0.1.0
appVersion: "1.23.3"
charts/nginx/values.yaml (차트 기본값)
replicaCount: 1
image:
repository: nginx
tag: "" # 비우면 appVersion 사용
service:
type: ClusterIP
port: 80
nodePort: null
charts/nginx/templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ .Release.Name }}
labels:
app: {{ .Release.Name }}
spec:
replicas: {{ .Values.replicaCount }}
selector:
matchLabels:
app: {{ .Release.Name }}
template:
metadata:
labels:
app: {{ .Release.Name }}
spec:
containers:
- name: nginx
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
ports:
- containerPort: 80
charts/nginx/templates/service.yaml
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}
spec:
type: {{ .Values.service.type }}
selector:
app: {{ .Release.Name }}
ports:
- port: {{ .Values.service.port }}
targetPort: 80
{{- if and (eq .Values.service.type "NodePort") .Values.service.nodePort }}
nodePort: {{ .Values.service.nodePort }}
{{- end }}
values/nginx/values.yaml (이 클러스터의 실제 설정)
replicaCount: 2
service:
type: NodePort
nodePort: 31405 # 1편에서 자동 할당되던 포트를 고정
자체 차트라도 차트 기본값(
charts/nginx/values.yaml)과 환경 설정(values/nginx/values.yaml)을 분리해 두면, 나중에 차트를 OCI 레지스트리로 옮기거나 클러스터를 하나 더 만들 때 values 만 추가하면 됩니다.
2.7 [추가] Helm CLI 설치
ArgoCD 는 내부에 Helm 을 내장하고 있어서 배포 자체에는 Helm CLI 가 필요 없습니다.
하지만 다음 두 가지 용도로 설치합니다.
- ArgoCD 를 처음 설치하는 부트스트랩
- values 를 수정한 뒤 push 하기 전에
helm template로 결과 확인
현재 안정 버전은 Helm 4 입니다.
curl -fsSL -o get_helm.sh https://raw.githubusercontent.com/helm/helm/main/scripts/get-helm-4
chmod 700 get_helm.sh
./get_helm.sh
helm version
사용할 차트 저장소를 등록하고, 실제 사용 가능한 버전을 확인합니다.
helm repo add argo https://argoproj.github.io/argo-helm
helm repo add metrics-server https://kubernetes-sigs.github.io/metrics-server/
helm repo add headlamp https://kubernetes-sigs.github.io/headlamp/
helm repo update
helm search repo argo/argo-cd
helm search repo metrics-server/metrics-server
helm search repo headlamp/headlamp
이 글 작성 시점(2026년 10월) 기준 버전은 argo-cd 10.9.x (Argo CD v3.5), metrics-server 3.13.x, headlamp 0.45.x 입니다.
아래targetRevision은 반드시helm search repo결과로 바꿔서 사용하세요.
2.8 [추가] ArgoCD 설치 (Helm 으로 부트스트랩)
Git 저장소를 clone 한 디렉터리에서 실행합니다. Git 의 values 파일을 그대로 사용한다는 점이 중요합니다.
그래야 이후 ArgoCD 가 자기 자신을 넘겨받을 때 차이(diff)가 생기지 않습니다.
git clone https://github.com/<YOUR_ID>/k8s-gitops.git
cd k8s-gitops
helm install argocd argo/argo-cd \
--namespace argocd --create-namespace \
--version 10.9.5 \
-f values/argocd/values.yaml
kubectl -n argocd get pods
모든 파드가 Running 이 되면 초기 비밀번호를 확인합니다.
kubectl -n argocd get secret argocd-initial-admin-secret \
-o jsonpath="{.data.password}" | base64 -d; echo
브라우저에서 https://192.168.20.206:30443/ 으로 접속해 admin / 위 비밀번호로 로그인합니다.
(1편 대시보드와 마찬가지로 https 로 접속하고, 자체 서명 인증서 경고는 무시합니다.)
2.9 ArgoCD CLI 설치
ARCH=$(dpkg --print-architecture) # 라즈베리 파이는 arm64
curl -sSL -o argocd https://github.com/argoproj/argo-cd/releases/latest/download/argocd-linux-${ARCH}
sudo install -m 555 argocd /usr/local/bin/argocd
rm argocd
argocd login 192.168.20.206:30443 --username admin --insecure
argocd account update-password
kubectl -n argocd delete secret argocd-initial-admin-secret # 비밀번호 변경 후 삭제
2.10 Git 저장소 연결 (Private 저장소인 경우)
Public 저장소라면 이 단계는 건너뜁니다. Private 저장소라면 GitHub 에서 읽기 전용 Fine-grained Token 을 발급받아 등록합니다.
argocd repo add https://github.com/<YOUR_ID>/k8s-gitops.git \
--username <YOUR_ID> --password <GITHUB_TOKEN>
토큰이 들어간 Secret 은 Git 에 커밋하지 않습니다. 저장소 인증 정보는 부트스트랩 계층에서 수동으로 넣고,
그 밖의 비밀값은 이후 Sealed Secrets 나 External Secrets Operator 로 관리하는 것을 권장합니다.
3. Application 작성 (패턴의 핵심)
==============> 테스트 필요
3.1 공식 차트 + Git values : metrics-server
apps/metrics-server.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: metrics-server
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io # Application 삭제 시 리소스도 함께 삭제
spec:
project: default
sources:
# ① 차트: Helm 저장소에서 그대로
- repoURL: https://kubernetes-sigs.github.io/metrics-server/
chart: metrics-server
targetRevision: 3.13.1 # helm search repo 결과로 변경
helm:
releaseName: metrics-server
valueFiles:
- $values/values/metrics-server/values.yaml
# ② values: 내 Git 저장소 (ref 이름이 위의 $values 가 됨)
- repoURL: https://github.com/<YOUR_ID>/k8s-gitops.git
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: kube-system
syncPolicy:
automated:
prune: true # Git 에서 지운 리소스는 클러스터에서도 삭제
selfHeal: true # kubectl 로 수동 변경해도 Git 상태로 되돌림
syncOptions:
- CreateNamespace=true
핵심은 sources 가 복수형이라는 점입니다.
- 첫 번째 source 는 차트를 제공하고
- 두 번째 source 는
ref: values로 이름만 붙여 두고 아무것도 배포하지 않습니다 - 첫 번째 source 의
valueFiles에서$values/...로 두 번째 저장소의 파일을 참조합니다
3.2 공식 차트 + Git values : Headlamp
apps/headlamp.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: headlamp
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: https://kubernetes-sigs.github.io/headlamp/
chart: headlamp
targetRevision: 0.45.0
helm:
releaseName: headlamp
valueFiles:
- $values/values/headlamp/values.yaml
- repoURL: https://github.com/<YOUR_ID>/k8s-gitops.git
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: headlamp
syncPolicy:
automated: { prune: true, selfHeal: true }
syncOptions:
- CreateNamespace=true
3.3 자체 차트 + Git values : nginx
자체 차트도 같은 형태로 씁니다. 차트와 values 가 같은 저장소에 있지만, 형태를 통일해 두면 나중에 차트를 다른 곳으로 옮겨도 apps/nginx.yaml 의 첫 번째 source 만 바꾸면 됩니다.
apps/nginx.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: nginx
namespace: argocd
finalizers:
- resources-finalizer.argocd.argoproj.io
spec:
project: default
sources:
- repoURL: https://github.com/<YOUR_ID>/k8s-gitops.git
targetRevision: main
path: charts/nginx
helm:
releaseName: nginx-server
valueFiles:
- $values/values/nginx/values.yaml
- repoURL: https://github.com/<YOUR_ID>/k8s-gitops.git
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: default
syncPolicy:
automated: { prune: true, selfHeal: true }
3.4 ArgoCD 가 자기 자신을 관리 : argocd
5장에서 Helm 으로 설치한 ArgoCD 를 이제 ArgoCD 가 넘겨받습니다.
releaseName, namespace, 차트 버전, values 파일이 5장 설치와 완전히 같아야 리소스가 재생성되지 않고 그대로 인수됩니다.
apps/argocd.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: argocd
namespace: argocd
# finalizer 를 넣지 않음: 실수로 Application 을 지워도 ArgoCD 자체는 남도록
spec:
project: default
sources:
- repoURL: https://argoproj.github.io/argo-helm
chart: argo-cd
targetRevision: 10.9.5 # 5장 helm install --version 과 동일하게
helm:
releaseName: argocd
valueFiles:
- $values/values/argocd/values.yaml
- repoURL: https://github.com/<YOUR_ID>/k8s-gitops.git
targetRevision: main
ref: values
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated:
prune: false # 자기 자신은 자동 삭제하지 않음
selfHeal: true
syncOptions:
- ServerSideApply=true # ArgoCD CRD 는 커서 client-side apply 시 annotation 크기 제한에 걸림
3.5 App of Apps 루트
apps/ 디렉터리의 Application 들을 하나하나 kubectl apply 하지 않도록, 이 디렉터리 자체를 바라보는 루트 Application 을 하나 만듭니다.
bootstrap/root-app.yaml
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: root
namespace: argocd
spec:
project: default
source:
repoURL: https://github.com/<YOUR_ID>/k8s-gitops.git
targetRevision: main
path: apps
destination:
server: https://kubernetes.default.svc
namespace: argocd
syncPolicy:
automated: { prune: true, selfHeal: true }
여기까지 작성한 내용을 push 합니다.
git add .
git commit -m "Native Helm + GitOps values"
git push origin main
4. 적용
루트 Application 만 한 번 수동으로 적용하면, 그 이후로는 kubectl 로 아무것도 배포하지 않습니다.
kubectl apply -f bootstrap/root-app.yaml
kubectl -n argocd get applications
------------------------------------
NAME SYNC STATUS HEALTH STATUS
argocd Synced Healthy
headlamp Synced Healthy
metrics-server Synced Healthy
nginx Synced Healthy
root Synced Healthy
확인
# metrics-server
kubectl top nodes
kubectl top pods -A
# nginx (1편과 같은 주소)
curl http://192.168.20.206:31405/
# Headlamp 로그인 토큰 (1편 6장 '계정생성' 을 대체)
kubectl -n headlamp create token headlamp
- nginx :
http://192.168.20.206:31405/ - Headlamp :
http://192.168.20.206:30239/(위 토큰으로 로그인) - ArgoCD :
https://192.168.20.206:30443/
Headlamp 차트는 기본값으로
cluster-admin권한의 ServiceAccount 를 만듭니다(1편의admin-user와 같은 수준).
읽기 전용으로 쓰고 싶다면values/headlamp/values.yaml에clusterRoleBinding.clusterRoleName: view를 추가하면 됩니다.
5. 운영: 이제부터는 Git 만 수정합니다
5.1 설정 변경
nginx 레플리카를 3개로 늘리려면:
vi values/nginx/values.yaml # replicaCount: 3
git commit -am "nginx replicas 3"
git push
최대 timeout.reconciliation(3분) 안에 ArgoCD 가 반영합니다. 바로 반영하려면 UI 에서 Refresh 를 누르거나:
argocd app get nginx --refresh
5.2 차트 버전 업그레이드
helm repo update
helm search repo headlamp/headlamp --versions | head
vi apps/headlamp.yaml # targetRevision 만 변경
git commit -am "headlamp 0.46.0"
git push
5.3 push 전에 결과 미리 보기
ArgoCD 와 같은 차트 + 같은 values 이므로 로컬 결과가 그대로 배포됩니다.
helm template metrics-server metrics-server/metrics-server \
--version 3.13.1 -n kube-system \
-f values/metrics-server/values.yaml | less
# 클러스터 현재 상태와의 차이
argocd app diff metrics-server
5.4 하지 말아야 할 것
kubectl edit/kubectl scale로 직접 수정 →selfHeal이 몇 초 만에 되돌립니다helm upgrade argocd ...로 ArgoCD 업그레이드 → 6.4 이후로는apps/argocd.yaml의targetRevision을 바꿉니다
6. 알아두어야 할 차이점과 주의사항
6.1 helm list 에 나오지 않습니다
ArgoCD 는 helm install 이 아니라 helm template 으로 렌더링한 결과를 kubectl apply 하듯 적용합니다.
따라서 metrics-server, headlamp, nginx 는 helm list -A 에 보이지 않고, helm rollback 도 쓸 수 없습니다.
롤백은 Git revert 또는 ArgoCD UI 의 History and Rollback 으로 합니다.
argocd 릴리스는 5장에서 helm install 했기 때문에 helm list -n argocd 에 남아 있지만, 이후 버전 정보는 갱신되지 않으므로 무시합니다.
6.2 Helm hook / lookup
- Helm hook(
pre-install,post-upgrade등)은 ArgoCD 의 PreSync/PostSync hook 으로 대응되어 대부분 그대로 동작합니다. - 차트 템플릿의
lookup함수는 ArgoCD 렌더링 시 항상 빈 값을 반환합니다. 이 함수에 의존하는 차트(예: "기존 Secret 이 있으면 재사용")는 values 로 값을 명시해야 합니다.
6.3 values 에 비밀값을 넣지 않습니다
values/ 는 Git 에 평문으로 남습니다. DB 비밀번호 같은 값은 차트의 existingSecret 옵션을 쓰고, Secret 자체는 Sealed Secrets / External Secrets Operator 로 관리합니다. (다음 글의 주제로 남겨 둡니다.)
6.4 Multi-source Application 의 제약
- ArgoCD UI 의 Parameters 탭에서 값을 수정하는 기능은 multi-source 에서 제한적입니다. 이 패턴에서는 어차피 Git 에서만 수정하므로 문제가 되지 않습니다.
ref만 있는 source 에는path를 지정하지 않습니다.path를 넣으면 그 경로의 매니페스트까지 배포됩니다.
6.5 라즈베리 파이(4GB)에서 Sync 가 느리거나 OOM 이 날 때
kubectl -n argocd top pods
kubectl -n argocd get events --sort-by=.lastTimestamp | tail
OOMKilled 가 보이면 해당 컴포넌트의 limits.memory 를 values/argocd/values.yaml 에서 올리고 push 합니다(ArgoCD 가 스스로 반영). 주로 repoServer, controller 가 대상입니다.
7. 정리
1편 → 2편 변경 요약
| 항목 | 1편 | 2편 |
|---|---|---|
| 배포 방법 | kubectl create/apply |
Git push → ArgoCD 자동 Sync |
| 설정 위치 | 클러스터 안 / 수정한 YAML 파일 | k8s-gitops/values/ |
| nginx | 단일 YAML + kubectl expose |
자체 Helm 차트 + values (NodePort 31405 고정) |
| metrics-server | components.yaml | 공식 Helm 차트 + --kubelet-insecure-tls |
| 대시보드 | Kubernetes Dashboard v2.7.0 (아카이브됨) | Headlamp (공식 Helm 차트) |
| ArgoCD | 없음 | Helm 부트스트랩 후 Self-managed |
| 수동 작업 | 매번 | 최초 1회 (helm install argocd, kubectl apply root-app.yaml) |
클러스터가 망가져도 1편으로 노드를 다시 만든 뒤 5장(ArgoCD 설치)과 7장(root-app 적용) 두 단계만 하면, 나머지는 Git 에 있는 그대로 복원됩니다.