Helm 차트 구축기

차트 구조와 템플릿 문법

2026. 06. 19.
KubernetesHelmDevOps

이 글의 목표: templates/ 폴더에서 일어나는 일을 라인 단위로 이해한다. 템플릿 디버깅의 기초.


차트 폴더의 표준 구조

helm create mychart 로 만든 표준 차트는 이렇게 생겼다.

mychart/
├── Chart.yaml          # 메타데이터
├── values.yaml         # 기본 변수
├── values.schema.json  # values 스키마 (선택)
├── charts/             # 의존 차트의 .tgz 보관소
├── templates/
│   ├── deployment.yaml
│   ├── service.yaml
│   ├── ingress.yaml
│   ├── _helpers.tpl    # ← 재사용 함수 모음 (밑줄 시작 = 직접 안 만듦)
│   ├── NOTES.txt       # ← install 후 출력될 메시지
│   └── tests/          # helm test 용
└── .helmignore         # 차트 패키징 시 제외할 파일

프로젝트의 helm/backend/는 더 단순하다 — _helpers.tpl도 없다.

helm/backend/
├── Chart.yaml
├── values.yaml
└── templates/
    ├── deployment.yaml
    ├── service.yaml
    ├── ingress.yaml
    └── externalsecret.yaml

→ 작은 차트는 굳이 헬퍼 함수 안 만들어도 된다.


템플릿 렌더링 흐름 (한 명령어가 일어나는 일)

helm install backend ./helm/backend -f values-prod.yaml

내부 동작:

1. ./helm/backend/Chart.yaml 읽기
2. values.yaml 읽기 (기본값)
3. -f values-prod.yaml 로 덮어쓰기
4. --set 옵션 으로 또 덮어쓰기
5. templates/*.yaml 을 Go template으로 렌더링
   → 위에서 만든 values를 .Values 로 주입
6. 결과 YAML들을 K8s API에 전송
7. 매니페스트 + 메타데이터를 Secret에 저장 (롤백용)

helm template ./helm/backend -f values-prod.yaml 을 실행하면 5단계까지만 수행되어 렌더링된 YAML을 그대로 볼 수 있다. 디버깅의 핵심 명령어다.


템플릿 안에서 쓸 수 있는 객체들

객체어디서 옴예시
.Valuesvalues.yaml 등{{ .Values.replicaCount }}
.Release.Namehelm install 시 인자{{ .Release.Name }} → "backend"
.Release.Namespace-n 옵션{{ .Release.Namespace }}
.Release.IsInstallinstall인지 upgrade인지{{ if .Release.IsInstall }}
.Chart.NameChart.yaml의 name{{ .Chart.Name }}
.Chart.VersionChart.yaml의 version{{ .Chart.Version }}
.Files차트 안의 파일 읽기{{ .Files.Get "config.json" }}
.Capabilities.KubeVersion클러스터 버전{{ .Capabilities.KubeVersion.Major }}

⚠️ 점(.)으로 시작! 점은 현재 컨텍스트(scope) 를 가리킨다.


자주 보는 문법 패턴

{{ }}{{- -}} 의 차이

{{ if .Values.x }}
foo: bar
{{ end }}

↓ 렌더링 시 빈 줄이 남는다.

 
foo: bar
 
{{- if .Values.x }}
foo: bar
{{- end }}

↓ 빈 줄이 깔끔하게 사라진다.

foo: bar

YAML에서는 들여쓰기와 빈 줄이 의미 있으므로 {{- 를 거의 항상 쓴다.

② if/else

spec:
  type: {{- if .Values.service.type }} {{ .Values.service.type }} {{- else }} ClusterIP {{- end }}

또는 default 함수가 더 깔끔하다.

spec:
  type: {{ .Values.service.type | default "ClusterIP" }}

③ range (반복)

env:
  {{- range $k, $v := .Values.env }}
  - name: {{ $k }}
    value: {{ $v | quote }}
  {{- end }}

values.yaml이 다음과 같으면

env:
  DB_URL: "jdbc:..."
  LOG_LEVEL: "INFO"

→ 두 개의 - name: ... / value: ... 항목이 생성된다.

⚠️ range 안에서는 .현재 순회중인 값으로 바뀐다. 바깥 .Values를 쓰려면 $.Values 로 접근한다.

④ toYaml + nindent

resources:
  {{- toYaml .Values.resources | nindent 4 }}

values.yaml:

resources:
  requests:
    cpu: 250m
    memory: 512Mi

→ 결과:

resources:
    requests:
      cpu: 250m
      memory: 512Mi
함수역할
toYamlGo 객체를 YAML 문자열로 변환
nindent N줄바꿈 후 N칸 들여쓰기 (앞에 \n 추가)
indent NN칸 들여쓰기만 (\n 없음)

→ "맵을 통째로 YAML에 끼워 넣을 때" 거의 항상 toYaml | nindent 조합이다.

⑤ quote

value: {{ .Values.springProfilesActive | quote }}

→ 값이 "prod" 처럼 따옴표로 감싸진다. 숫자처럼 보이는 문자열("3")을 안전하게 처리할 때 필수.


프로젝트 코드로 보는 템플릿 — helm/backend/templates/deployment.yaml

spec:
  replicas: {{ .Values.replicaCount }}                    # ① 단순 변수
  selector:
    matchLabels:
      app: {{ .Release.Name }}                            # ② Release 이름
  template:
    metadata:
      labels:
        app: {{ .Release.Name }}
    spec:
      containers:
        - name: backend
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          env:
            {{- if .Values.springProfilesActive }}        # ③ 조건부
            - name: SPRING_PROFILES_ACTIVE
              value: {{ .Values.springProfilesActive | quote }}
            {{- end }}
            {{- range $k, $v := .Values.env }}            # ④ map 반복
            - name: {{ $k }}
              value: {{ $v | quote }}
            {{- end }}
          {{- if .Values.externalSecret.enabled }}        # ⑤ 조건부 블록
          envFrom:
            - secretRef:
                name: {{ .Release.Name }}-secret
          {{- end }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}  # ⑥ 통째로 끼워넣기

번호별로:

  1. 단순한 값 치환
  2. 릴리스 이름을 라벨/셀렉터로 일관되게 사용 → 이름이 곧 식별자
  3. springProfilesActive 값이 있을 때만 SPRING_PROFILES_ACTIVE 환경변수 추가
  4. env 맵을 순회하며 환경변수 추가 — DB_URL 등이 여기로
  5. ExternalSecret이 켜져 있으면 envFrom으로 K8s Secret 통째로 마운트
  6. resources 같은 복잡한 맵은 toYaml로 통째로 — 들여쓰기 칸수(12)를 정확히 맞춰야 함

_helpers.tpl — 재사용 가능한 템플릿 함수

표준 차트에는 보통 templates/_helpers.tpl 이 있다 (밑줄 시작 = 렌더링 안 됨, 함수 정의용).

{{/* Common labels */}}
{{- define "mychart.labels" -}}
app.kubernetes.io/name: {{ .Chart.Name }}
app.kubernetes.io/instance: {{ .Release.Name }}
app.kubernetes.io/version: {{ .Chart.AppVersion }}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version }}
{{- end }}

다른 템플릿에서:

metadata:
  labels:
    {{- include "mychart.labels" . | nindent 4 }}

여러 템플릿에서 같은 라벨/이름 규칙을 쓸 때 중복 제거 용도. 우리 차트는 작아서 안 쓰지만, 차트가 커지면 거의 필수다.


NOTES.txt

templates/NOTES.txt 가 있으면 helm install/upgrade 후 출력된다.

Thank you for installing {{ .Chart.Name }}.

Your release is named {{ .Release.Name }}.

Get the application URL by running:
  kubectl port-forward svc/{{ .Release.Name }} 8080:80

→ 사용자에게 이제 뭐 해야 하는지 알려주는 용도다.


.helmignore

.gitignore 처럼 차트 패키징 시 제외할 파일을 적는다.

.git/
.idea/
*.swp
.DS_Store

helm package 로 .tgz를 만들 때 위 파일들이 안 들어간다.


디버깅 명령어 모음 ⭐

명령어용도
helm template ./mychart렌더링된 YAML을 화면에 출력 (가장 자주 씀)
helm template ./mychart -f values-prod.yaml특정 values로 렌더링 결과 확인
helm install --debug --dry-run실제 설치 안 하고 검증
helm lint ./mychart차트 문법/구조 검사
helm get manifest <release>클러스터에 설치된 매니페스트 보기
helm get values <release>적용된 values 보기

→ 템플릿 에러가 나면 무조건 helm template 부터 실행해 본다.


자주 하는 실수

⚠️ 들여쓰기 한 칸 차이로 깨짐 — 특히 toYaml | nindent N 의 N

⚠️ .Values.foo가 없을 때 nil 에러default 함수나 if 로 가드

⚠️ range 안에서 .Release.Name 이 안 나옴range 안의 . 이 바뀜. $.Release.Name 사용

⚠️ 숫자처럼 보이는 문자열을 따옴표 없이tag: 0.0.1 이 0.001이 될 수 있음. tag: "0.0.1" 로 작성


다음 글에서는 helm/backend, helm/ingress, helm/external-secrets 를 하나씩 분해해서 읽어본다.