Overview
Helm은 Kubernetes 애플리케이션을 정의하고 배포하는 데 사용되는 패키지 매니저로, Helm Chart를 통해 복잡한 Kubernetes 리소스를 템플릿화할 수 있다. 특히 Helm의 템플릿 문법은 Go 템플릿을 기반으로 하며, 유연한 구성과 재사용성을 제공한다.
이 글에서는 Helm Chart의 템플릿 문법을 중심으로 .Values, include, define, range, 조건문, toYaml, indent
등 실무에서 자주 사용하는 문법들을 예시와 함께 정리한다.
또한, 실제 배포 환경에서 사용할 수 있는 Deployment, imagePullSecret, _helpers.tpl
등도 함께 소개하여, Helm Chart의 효율적인 작성법을 실전과 연결해 학습할 수 있도록 구성했다.

📅 관련 글
2022.09.06 - [Container Orchestration/Kubernetes] - Helm 이란? (Kubernetes Package manager)
2023.05.16 - [Container Orchestration/Kubernetes] - Helm Chart 작성방법
2024.12.20 - [Container Orchestration/Kubernetes] - Helm Chart 생성 및 패키징 (gh-pages)
2025.01.06 - [Container Orchestration/Kubernetes] - Helm Base App Chart 생성(With ArgoCD)
Helm Chart Template 문법
Helm에서 YAML 파일은 차트 템플릿 및 값에 사용되어 Kubernetes 리소스에 대한 구성을 정의한다. Helm의 템플릿 구문은 Go 템플릿을 사용하여 동적 구성을 활성화하므로 Helm 차트 템플릿 내에서 표준 YAML과 Go 템플릿의 조합을 찾을 수 있다. 주요 구문 패턴은 다음과 같다.
Standard YAML Syntax
Helm 차트는 들여쓰기 및 key:value
쌍을 사용하여 구성 설정을 정의하는 YAML로 작성된다.
apiVersion: v1
kind: Service
metadata:
name: my-service
labels:
app: my-app
Go Templating Syntax ({{ ... }}
)
Helm은 {{ ... }}
로 구분된 YAML 파일 내 동적 값에 Go 템플릿을 사용한다.
metadata:
name: {{ .Release.Name }}-service
Values Reference (.Values
)
.Values
는 사용자 제공 구성을 위해 values.yaml
파일에 액세스 한다.
spec:
replicas: {{ .Values.replicaCount }}
Release and Chart Metadata (.Release, .Chart
)
.Release: .Release.Name
, .Release.Namespace
, .Release.Revision
등 릴리스에 대한 세부 정보를 제공한다..Chart: .Chart.Name
, .Chart.Version
과 같이 Chart.yaml
파일에 정의된 메타데이터에 액세스한다.
metadata:
labels:
chart: "{{ .Chart.Name }}-{{ .Chart.Version }}"
Conditional Logic (if/else Statements
)
조건문을 사용하면 지정된 조건에 따라 섹션을 렌더링할 수 있다.
{{ if .Values.service.enabled }}
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-service
{{ end }}
Loops (range
)
ragne
는 목록이나 맵을 반복하며 여러 리소스를 동적으로 생성하는 데 유용하다.
ports:
{{- range .Values.ports }}
- port: {{ . }}
{{- end }}
Default Values (default function)
default
는 값이 설정되지 않은 경우 대체 값을 제공한다.
replicas: {{ .Values.replicaCount | default 1 }}
Defining and Using Templates
Helm을 사용하면 define 및 template을 사용하여 차트 내에서 재사용 가능한 템플릿을 정의할 수 있다.
정의
{{- define "mychart.labels" -}}
app: {{ .Chart.Name }}
release: {{ .Release.Name }}
{{- end }}
사용
metadata:
labels:
{{ include "mychart.labels" . | nindent 4 }}
Pipeline Operators (|
)
파이프(|
)는 한 함수에서 다른 함수로 출력을 보낸다. 일반적으로 quote
, default
또는 toYaml
과 같은 함수에 사용
host: {{ .Values.hostname | quote }}
- 여기서는
.Values.hostname
값에quote
를 적용하여 문자열로 포맷한다.
사용 에시
host: "example.com"
toYaml
은 데이터 구조(예: 배열, 사전 또는 중첩 객체)를 YAML 형식의 문자열로 변환합니다. 이는 복잡한 데이터 구조를 YAML 구문으로 직접 변환하는 데 특히 유용하다.
env:
{{ .Values.envVars | toYaml | indent 4 }}
- 여기서
.Values.envVars
는 복잡한 데이터 구조이며 list or dictionary 일 수 있다.toYaml
은 이를 YAML 형식의 텍스트로 변환하고,indent 4
는 상위 구조 내에서 올바르게 정렬한다.
사용예시
envVars:
- name: ENV_VAR1
value: "value1"
- name: ENV_VAR2
value: "value2"
결과값
env:
- name: ENV_VAR1
value: "value1"
- name: ENV_VAR2
value: "value2"
Indentation and Nesting (indent, nindent
)
indent
및 nindent
'는 특히 중첩된 YAML 구조에서 간격을 관리하는 데 도움이 된다.
indent
: 시작 부분에 줄 바꿈을 추가하지 않고 텍스트의 각 줄 앞에 지정된 수의 공백을 추가한다.nindent
: indent 와 비슷하지만 시작 부분에 줄 바꿈이 포함되어 있어 YAML 내에서 새 섹션을 시작하는 데 유용하다.
metadata:
labels:
{{- include "mychart.labels" . | nindent 6 }}
nindent 6
은 출력의 각 줄 시작 부분에 6개의 공백을 추가하고 줄 바꿈으로 시작하여 labels 내에서 정렬한다.
env:
{{ .Values.envVars | toYaml | indent 4 }}
.Values.envVars
의 YAML 구조를 4칸 들여쓰기한다.
Use include
include
기능을 사용하면 차트 내에서 다른 템플릿을 호출하거나 재사용할 수 있다.
include
는 포함할 템플릿 이름과 템플릿의 컨텍스트(또는 범위)라는 두 가지 인수를 사용한다.
{{ include "template-name" . }}
일단 정의되면 include
를 사용하여 템플릿을 차트의 어느 곳에나 포함할 수 있다.
metadata:
labels:
{{ include "mychart.labels" . | nindent 4 }}
정의는 templates/_helpers.tpl
에 정의하면 된다. helm create
로 생성하면, 기본적으로 정의된다.
{{/*
Expand the name of the chart.
*/}}
{{- define "somaz.name" -}}
{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Create a default fully qualified app name.
We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec).
If release name contains chart name it will be used as a full name.
*/}}
{{- define "somaz.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := default .Chart.Name .Values.nameOverride }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}
{{/*
Create chart name and version as used by the chart label.
*/}}
{{- define "somaz.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}
{{/*
Common labels
*/}}
{{- define "somaz.labels" -}}
helm.sh/chart: {{ include "somaz.chart" . }}
{{ include "somaz.selectorLabels" . }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}
{{/*
Selector labels
*/}}
{{- define "somaz.selectorLabels" -}}
app.kubernetes.io/name: {{ include "somaz.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
{{/*
Create the name of the service account to use
*/}}
{{- define "somaz.serviceAccountName" -}}
{{- if .Values.serviceAccount.create }}
{{- default (include "somaz.fullname" .) .Values.serviceAccount.name }}
{{- else }}
{{- default "default" .Values.serviceAccount.name }}
{{- end }}
{{- end }}
Helm Chart Template 활용
이제 deployment template 을 예시로 활용해서 template 작성후해보자.
아래와 같이 작성이 가능하다.
templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: {{ include "somaz.fullname" . }}
labels:
{{- include "somaz.labels" . | nindent 4 }}
spec:
revisionHistoryLimit: {{ .Values.revisionHistoryLimit }}
{{- if not .Values.autoscaling.enabled }}
replicas: {{ .Values.replicaCount }}
{{- end }}
selector:
matchLabels:
{{- include "somaz.selectorLabels" . | nindent 6 }}
template:
metadata:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
labels:
{{- include "somaz.selectorLabels" . | nindent 8 }}
spec:
{{- with .Values.imagePullSecrets }}
imagePullSecrets:
{{- toYaml . | nindent 8 }}
{{- end }}
serviceAccountName: {{ include "somaz.serviceAccountName" . }}
securityContext:
{{- toYaml .Values.podSecurityContext | nindent 8 }}
volumes:
{{- range $persistentVolumeClaim := .Values.persistentVolumeClaims }}
- name: {{ $persistentVolumeClaim.type }}
persistentVolumeClaim:
claimName: {{ $persistentVolumeClaim.name }}
{{- end}}
containers:
- name: {{ .Chart.Name }}
securityContext:
{{- toYaml .Values.securityContext | nindent 12 }}
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
imagePullPolicy: {{ .Values.image.pullPolicy }}
env:
{{- range $key, $value := .Values.envConfig }}
- name: {{ $key }}
value: {{ $value | quote }}
{{- end }}
envFrom:
{{- range $config := .Values.configs }}
- configMapRef:
name: {{ $config.name }}
{{- end}}
ports:
- name: http
containerPort: {{ .Values.service.targetPort }}
protocol: TCP
volumeMounts:
{{- range $persistentVolumeClaim := .Values.persistentVolumeClaims }}
- name: {{ $persistentVolumeClaim.type }}
mountPath: {{ $persistentVolumeClaim.mountPath }}
{{- end}}
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
nodeSelector:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.affinity }}
affinity:
{{- toYaml . | nindent 8 }}
{{- end }}
{{- with .Values.tolerations }}
tolerations:
{{- toYaml . | nindent 8 }}
{{- end }}
somaz.values.yaml
# Default values for somaz.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 1
image:
repository: harbor.somaz.link/somaz/game
pullPolicy: IfNotPresent
# Overrides the image tag whose default is the chart appVersion.
tag: "362aa67"
imagePullSecretCreate:
enabled: true
name: harbor-robot-secret
dockerconfigjson: # dockerconfigjson
imagePullSecrets:
- name: harbor-robot-secret
nameOverride: "somaz"
fullnameOverride: "somaz"
serviceAccount:
# Specifies whether a service account should be created
create: true
# Annotations to add to the service account
annotations: {}
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name: ""
podAnnotations: {}
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 80
targetPort: 3000
ingress:
name: somaz-ingress
enabled: true
className: ""
annotations:
kubernetes.io/ingress.class: nginx
nginx.ingress.kubernetes.io/rewrite-target: /
cert-manager.io/cluster-issuer: clouddns-issuer # your clusterissuer
hosts:
- host: dev1-game.somaz.link
paths:
- path: /
pathType: Prefix
backend:
service:
name: somaz-svc
port: 80
tls:
- secretName: dev1-game-tls
hosts:
- dev1-game.somaz.link
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
autoscaling:
enabled: false
minReplicas: 1
maxReplicas: 100
targetCPUUtilizationPercentage: 80
# targetMemoryUtilizationPercentage: 80
rbac:
enabled: true
serviceAccountName: "" # Leave empty to use admin.fullname as default
role:
name: cert-job-manager-game
resources:
- apiGroups: ["cert-manager.io"]
resources: ["certificaterequests"]
verbs: ["get", "list", "watch", "delete"]
- apiGroups: ["batch"]
resources: ["jobs"]
verbs: ["get", "list", "watch", "delete"]
roleBinding:
name: cert-job-manager-binding-game
nodeSelector: {}
tolerations: []
affinity: {}
revisionHistoryLimit: 1
namespace: somaz-dev1
envConfig:
NODE_ENV: dev1
configs:
- name: somaz-dev1-config
namespace: somaz-dev1
datas:
SERVER_PORT: 3000
REDIS_DB_HOST: dev1-redis.somaz.link
REDIS_DB_PORT: 30480
ADMIN_DB_HOST: dev1-db.somaz.link
ADMIN_DB_PORT: 30737
ADMIN_DB_NAME: admin
ADMIN_DB_ID: somaz
ADMIN_DB_PW: somaz94
ADMIN_DB_SYNCHRONIZE: true
persistentVolumes:
- name: somaz-dev1-data-pv
type: data
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
reclaimPolicy: Retain
storageClassName: nfs-client
path: /data/somaz/gamedata/dev1/data
server: nfs-server.somaz.link
persistentVolumeClaims:
- name: somaz-dev1-data-pv-claim
accessModes:
- ReadWriteOnce
storageClassName: nfs-client
storage: 5Gi
type: data
mountPath: /app/data
# AWS
certificate:
enabled: true
secretName: dev1-game-tls
commonName: dev1-game.somaz.link
duration: 2160h0m0s # 90d
renewBefore: 720h0m0s # 30d
dnsNames:
- dev1-game.somaz.link
issuerName: route53-issuer
issuerKind: ClusterIssuer
# # GCP
# certificate:
# enabled: true
# secretName: dev1-game-tls
# commonName: dev1-game.somaz.link
# duration: 2160h0m0s # 90d
# renewBefore: 720h0m0s # 30d
# dnsNames:
# - dev1-game.somaz.link
# issuerName: clouddns-issuer
# issuerKind: ClusterIssuer
certCleanup:
enabled: false
CronJobName: cert-cleanup-cronjob-game
olderThanDays: 100
YesterDays: 1
Create ImagePullSecret
아래와 같이 helper template
과 조합하여 imagepullsecret
을 쉽게 만들 수 있다.
templates/_helpers.tpl
{{- define "imagePullSecret" }}
{{- with .Values.imageCredentials }}
{{- printf "{\"auths\":{\"%s\":{\"username\":\"%s\",\"password\":\"%s\",\"email\":\"%s\",\"auth\":\"%s\"}}}" .registry .username .password .email (printf "%s:%s" .username .password | b64enc) | b64enc }}
{{- end }}
{{- end }}
templates/imagePullSecret.yaml
{{- if .Values.imageCredentials.enabled -}}
apiVersion: v1
kind: Secret
metadata:
name: {{ .Values.imageCredentials.name }}
labels:
{{- include "somaz.labels" . | nindent 4 }}
type: kubernetes.io/dockerconfigjson
data:
.dockerconfigjson: {{ template "imagePullSecret" . }}
{{- end }}
values.yaml
imageCredentials:
enabled: true
registry: quay.io
username: someone
password: sillyness
email: someone@host.com
마무리
Helm 템플릿 문법은 단순한 YAML의 한계를 넘어, 복잡한 환경에서도 유지보수 가능한 Kubernetes 리소스를 정의할 수 있도록 도와준다. 특히 include, define, range, 조건문과 같은 Go 템플릿 기능을 활용하면 중복을 줄이고, 확장 가능하며 읽기 쉬운 차트를 작성할 수 있다.
템플릿화된 Deployment, Service, Ingress와 같은 리소스를 기반으로 Helm Chart를 설계하면, 다양한 환경(dev, staging, prod)에 손쉽게 대응할 수 있으며, ArgoCD 같은 GitOps 도구와 연동해 자동화된 배포 파이프라인을 구축할 수 있다.
지속적인 템플릿 구조 개선과 Helm 베스트 프랙티스 학습은 클라우드 네이티브 환경에서의 운영 안정성과 확장성을 크게 높여줄 수 있다. Helm Chart를 마스터하는 것은 Kubernetes 기반 DevOps 여정의 든든한 기초가 되어줄 것이다.
Reference
https://helm.sh/ko/docs/chart_best_practices/templates/
'Container Orchestration > Kubernetes' 카테고리의 다른 글
Kubernetes Headless Service란? (0) | 2024.12.03 |
---|---|
Kubernetes Deployment Strategy (0) | 2024.11.26 |
Kubernetes Garbage Collection (0) | 2024.08.30 |
Kubernetes 생태계 표준화와 Container Interface(CRI, CSI, CNI) (0) | 2024.07.15 |
Kubernetes Pod를 안전하게 종료하는 방법(cordon, uncordon, drain, scale) (0) | 2024.07.09 |