Kubernetes – ArgoCD + Helm 으로 Native Helm + GitOps Values 패턴 구성하기

By | 2026년 9월 26일
Table of Contents

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 가 필요 없습니다.
하지만 다음 두 가지 용도로 설치합니다.

  1. ArgoCD 를 처음 설치하는 부트스트랩
  2. 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 에 있는 그대로 복원됩니다.

답글 남기기