infra
Platform

모듈 맵

[Kubernetes] 나만의 커스텀 Helm Chart 작성법과 환경별 Value 튜닝

0 / 29 완료

펼치기
0 / 29 완료0%

Kubernetes · 22 / 29

[Kubernetes] 나만의 커스텀 Helm Chart 작성법과 환경별 Value 튜닝

templates/, Chart.yaml, values.yaml 구조부터 Go 템플릿 함수와 서브차트까지 프로덕션 수준의 Helm Chart를 직접 만들어봅니다

🚨INCIDENT ALERT
HIGH

팀마다 비슷한 Deployment YAML을 복사해 쓰다가 포트, 라벨, probe 설정이 제각각이 되었습니다. 공통 패턴을 Chart로 만들면 환경별 값만 바꾸고 검증된 템플릿을 재사용할 수 있습니다. Helm Chart 작성은 플랫폼팀이 배포 표준을 코드로 제공하는 방식입니다.

Helm Chart 직접 작성하기

팀에 새 마이크로서비스가 추가될 때마다 누군가 밤새 YAML을 복붙하며 수정하고 있습니다. 환경마다 이미지 태그가 달라야 하고, 스테이징과 프로덕션의 레플리카 수가 다르고, 시크릿 이름도 제각각입니다. 처음 한 번만 고생하면 그 이후에는 helm install my-service ./charts/my-service -f values-prod.yaml 한 줄로 끝나는 구조를 만들 수 있습니다. Helm Chart를 직접 작성하면 인프라 변경이 코드 리뷰를 거치고, 히스토리가 남고, 롤백이 가능해집니다. 이 모듈에서는 빈 디렉토리에서 시작해 프로덕션에서 실제로 사용하는 수준의 Chart를 직접 만들어봅니다.


이번 챕터에서 배울 것

직접 작성한 Helm Chart로 Kubernetes 배포를 코드로 관리하는 방법을 익힙니다. Go 템플릿 함수부터 서브차트 의존성 관리까지, 팀 전체가 사용하는 공용 Chart를 만들 수 있게 됩니다.

  • 1Helm Chart 디렉토리 구조 — Chart.yaml, values.yaml, templates/ 역할
  • 2Go 템플릿 기본 문법 — 변수 참조, 조건문, 반복문
  • 3핵심 함수 — include, toYaml, required, default, nindent
  • 4_helpers.tpl로 named template 재사용 패턴 구현
  • 5서브차트(dependency)로 외부 Chart 의존성 관리
  • 6helm template / helm lint으로 로컬 디버깅
실습 환경 준비

Helm v3와 kubectl이 설치되어 있고 클러스터에 연결되어 있어야 합니다. minikube나 kind 등 로컬 클러스터로도 충분합니다.

Helm 버전 확인 (v3 이상 필요)
helm version --short
로컬 Kubernetes 클러스터 연결 확인
kubectl cluster-info
실습 네임스페이스 생성
kubectl create namespace helm-dev
실습 디렉토리 생성
mkdir -p ~/helm-workshop && cd ~/helm-workshop
실습 완료 후 정리

helm uninstall my-webapp -n helm-dev && kubectl delete namespace helm-dev

💡개념

Helm Chart 디렉토리 구조

팀마다 비슷한 Deployment YAML을 각자 관리하다 보면 probe 설정이 누락되거나 리소스 제한이 제각각이 되는 문제가 생깁니다. 직접 Chart를 작성하면 이 공통 패턴을 한 곳에서 관리하고 환경별 값만 values.yaml로 분리할 수 있습니다. helm create가 자동 생성하는 구조를 이해하지 않고 쓰면 불필요한 파일이 많아 혼란스럽습니다. Chart.yaml, values.yaml, templates/ 세 요소의 역할 분담을 먼저 파악하면 어떤 로직이 어디에 있어야 하는지 판단이 생겨 유지보수 가능한 Chart를 만들 수 있습니다.

Helm Chart 디렉토리 구조

Chart 기본 구조

my-webapp/
├── Chart.yaml          # Chart 메타데이터 (필수)
├── values.yaml         # 기본값 정의 (필수)
├── charts/             # 서브차트(dependency) 저장 디렉토리
└── templates/          # Kubernetes manifest 템플릿
    ├── _helpers.tpl    # 재사용 named template (렌더링 대상 아님)
    ├── deployment.yaml
    ├── service.yaml
    ├── ingress.yaml
    ├── configmap.yaml
    └── NOTES.txt       # helm install 후 출력될 안내 메시지

Chart.yaml — Chart 메타데이터

YAML
# Chart.yaml
apiVersion: v2          # Helm v3는 반드시 v2
name: my-webapp
description: A production-ready web application chart
type: application       # application | library
version: 0.1.0          # Chart 버전 (SemVer)
appVersion: "1.0.0"     # 실제 앱 버전 (참조용, 이미지 태그와는 별도)

# 외부 Chart 의존성 (서브차트)
dependencies:
  - name: redis
    version: "18.x.x"
    repository: "https://charts.bitnami.com/bitnami"
    condition: redis.enabled   # values.yaml의 값으로 선택적 설치

values.yaml — 기본값 정의

YAML
# values.yaml
replicaCount: 1

image:
  repository: nginx
  tag: "1.25"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80
  targetPort: 8080

ingress:
  enabled: false
  host: ""
  annotations: {}

resources:
  requests:
    cpu: 100m
    memory: 128Mi
  limits:
    cpu: 500m
    memory: 512Mi

env: []
#  - name: DATABASE_URL
#    valueFrom:
#      secretKeyRef:
#        name: db-secret
#        key: url

redis:
  enabled: false      # 서브차트 활성화 여부
  auth:
    enabled: false

templates/deployment.yaml — 핵심 템플릿

YAML
# templates/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-webapp.fullname" . }}
  labels:
    {{- include "my-webapp.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "my-webapp.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "my-webapp.selectorLabels" . | nindent 8 }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - containerPort: {{ .Values.service.targetPort }}
          {{- if .Values.env }}
          env:
            {{- toYaml .Values.env | nindent 12 }}
          {{- end }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}

💡개념

Go 템플릿 핵심 함수 — include, toYaml, required, nindent

Helm 템플릿을 처음 작성할 때 가장 많이 만나는 오류가 들여쓰기 불일치나 "wrong type for value" 입니다. YAML은 들여쓰기가 문법의 일부이기 때문에, 함수 호출 결과를 올바르게 삽입하지 않으면 유효하지 않은 YAML이 만들어집니다. nindent로 들여쓰기를 맞추고, toYaml로 구조체를 직렬화하며, required로 필수 값 누락을 배포 전에 잡는 패턴은 실무 Chart에서 반복적으로 나타납니다. 이 4가지 함수의 동작을 이해하면 다른 사람의 Chart를 읽고 디버깅하는 속도도 빨라집니다.

Go 템플릿 핵심 함수 — include, toYaml, required, nindent

include — named template 호출 (pipeline 지원)

YAML
# template 함수는 pipeline에 연결할 수 없어서 indent가 안 됩니다.
# include를 사용하면 결과를 pipeline으로 넘길 수 있습니다.

# 잘못된 방법 (indent 불가)
{{template "my-webapp.labels" .}}

# 올바른 방법 (nindent로 들여쓰기 적용)
{{- include "my-webapp.labels" . | nindent 4 }}

# 예: 여러 manifest에서 동일한 label 블록 재사용
metadata:
  labels:
    {{- include "my-webapp.labels" . | nindent 4 }}
spec:
  selector:
    matchLabels:
      {{- include "my-webapp.selectorLabels" . | nindent 6 }}

toYaml — 값 객체를 YAML로 직렬화

YAML
# values.yaml에 정의한 구조체를 그대로 YAML로 출력
resources:
  {{- toYaml .Values.resources | nindent 2 }}

# 결과:
# resources:
#   requests:
#     cpu: 100m
#     memory: 128Mi
#   limits:
#     cpu: 500m
#     memory: 512Mi

# env 배열 처리 (아이템이 있을 때만 출력)
{{- if .Values.env }}
env:
  {{- toYaml .Values.env | nindent 2 }}
{{- end }}

# annotations 맵 처리
{{- with .Values.ingress.annotations }}
annotations:
  {{- toYaml . | nindent 4 }}
{{- end }}

required — 필수 값 검증

YAML
# 값이 비어 있으면 오류 메시지와 함께 렌더링 중단
image:
  repository: {{ required "image.repository는 필수 값입니다" .Values.image.repository }}
  tag: {{ required "image.tag는 필수 값입니다" .Values.image.tag | quote }}

# Ingress host 검증
{{- if .Values.ingress.enabled }}
  {{- $host := required "ingress.enabled가 true이면 ingress.host를 지정해야 합니다" .Values.ingress.host }}
  rules:
    - host: {{ $host }}
{{- end }}

default — 기본값 설정

YAML
# .Values에 값이 없거나 비어 있을 때 기본값 사용
replicas: {{ .Values.replicaCount | default 1 }}
pullPolicy: {{ .Values.image.pullPolicy | default "IfNotPresent" }}

# 조건부 기본값 (string 빈 값 처리)
{{- $name := .Values.nameOverride | default .Chart.Name }}
name: {{ $name | trunc 63 | trimSuffix "-" }}

nindent vs indent

YAML
# indent: 지정한 수만큼 공백 추가 (앞에 줄바꿈 없음)
# nindent: newline + indent (앞에 줄바꿈 포함) ← 주로 이것을 사용

# indent 사용 시 (줄바꿈을 직접 관리해야 함)
labels:
{{ include "my-webapp.labels" . | indent 4 }}

# nindent 사용 시 (더 깔끔함, - 로 앞 공백 제거)
labels:
  {{- include "my-webapp.labels" . | nindent 2 }}

💡개념

_helpers.tpl — named template 재사용 패턴

여러 template 파일에서 동일한 label 블록을 반복 작성하면 유지보수가 어렵습니다. _helpers.tpl에 named template을 정의하고 include로 불러 쓰면 한 곳만 수정해도 모든 manifest에 반영됩니다.

_helpers.tpl 전체 예시

YAML
{{/*
  Expand the name of the chart.
*/}}
{{- define "my-webapp.name" -}}
{{- .Values.nameOverride | default .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}

{{/*
  Create a default fully qualified app name.
  Release 이름이 Chart 이름을 포함하면 중복 제거
*/}}
{{- define "my-webapp.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := .Values.nameOverride | default .Chart.Name }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{/*
  공통 label — 모든 리소스에 붙는 표준 label
*/}}
{{- define "my-webapp.labels" -}}
helm.sh/chart: {{ include "my-webapp.chart" . }}
{{ include "my-webapp.selectorLabels" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- if .Chart.AppVersion }}
app.kubernetes.io/version: {{ .Chart.AppVersion | quote }}
{{- end }}
{{- end }}

{{/*
  Selector label — Deployment의 selector.matchLabels에 사용
  (한 번 배포 후 변경하면 안 됨!)
*/}}
{{- define "my-webapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-webapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}

{{/*
  Chart 이름+버전 문자열
*/}}
{{- define "my-webapp.chart" -}}
{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }}
{{- end }}

named template에서 컨텍스트 전달

YAML
# . (현재 컨텍스트 전체)를 전달하는 것이 일반적
{{- include "my-webapp.labels" . | nindent 4 }}

# 특정 값만 전달할 때 (dict 사용)
{{- include "my-webapp.serviceAccountName" (dict "Values" .Values "Release" .Release) }}

# range 블록 내부에서 루트 컨텍스트 전달 ($ 사용)
{{- range .Values.ingress.hosts }}
- host: {{ .host }}
  paths:
  {{- range .paths }}
    - path: {{ .path }}
      # range 블록 내부에서 .Values는 현재 스코프(path 객체)를 가리킴
      # $.Values로 루트 컨텍스트의 Values에 접근
      pathType: {{ $.Values.ingress.pathType | default "Prefix" }}
  {{- end }}
{{- end }}

💡개념

서브차트(Dependency)로 외부 Chart 관리

my-webapp이 Redis를 필요로 한다면, Redis Chart를 직접 관리하는 대신 Helm의 dependency 메커니즘으로 가져올 수 있습니다. 서브차트는 선택적으로 활성화/비활성화할 수 있어 로컬 개발(Redis 불필요)과 프로덕션(Redis 필요) 환경을 동일한 Chart로 처리할 수 있습니다.

서브차트 설정 흐름

로컬 터미널
# 1단계: Chart.yaml에 dependency 추가 (이미 앞에서 작성함)
# dependencies:
#   - name: redis
#     version: "18.x.x"
#     repository: "https://charts.bitnami.com/bitnami"
#     condition: redis.enabled

# 2단계: repository 추가 및 dependency 다운로드
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update
helm dependency update ./my-webapp

# 결과: charts/ 디렉토리에 redis-18.x.x.tgz 생성
ls ./my-webapp/charts/
# redis-18.6.1.tgz

# 3단계: 서브차트 values 오버라이드 (values.yaml에서)
# 서브차트 이름(redis)을 키로 사용하여 values 전달

서브차트 values 오버라이드

YAML
# values.yaml — 서브차트 설정은 서브차트 이름 아래에 작성
redis:
  enabled: true               # condition 값 (이 키로 서브차트 활성화 제어)
  auth:
    enabled: true
    password: "my-secret-pw"  # 프로덕션에서는 --set으로 전달
  master:
    persistence:
      size: 5Gi
  replica:
    replicaCount: 1

서브차트 내부 Service 이름 참조

YAML
# templates/deployment.yaml — Redis 서비스에 연결하는 환경변수
env:
  {{- if .Values.redis.enabled }}
  - name: REDIS_HOST
    # 서브차트 Service 이름은 "<release-name>-redis-master" 형식
    value: "{{ .Release.Name }}-redis-master"
  - name: REDIS_PORT
    value: "6379"
  {{- end }}

Chart 설치 및 확인

로컬 터미널
# Redis 포함하여 설치
helm install my-webapp ./my-webapp \
  --namespace helm-dev \
  --set redis.enabled=true \
  --set redis.auth.password=secret123

# 렌더링 결과 확인 (클러스터 불필요)
helm template my-webapp ./my-webapp \
  --set redis.enabled=true \
  --namespace helm-dev | less

# Redis 없이 설치 (로컬 개발)
helm install my-webapp-dev ./my-webapp \
  --namespace helm-dev \
  --set redis.enabled=false

실습 — Helm Chart 처음부터 만들기

1단계: Chart 골격 생성

로컬 터미널
mkdir -p ~/helm-workshop/my-webapp/{templates,charts}
cd ~/helm-workshop/my-webapp

# Chart.yaml 작성
cat > Chart.yaml << 'EOF'
apiVersion: v2
name: my-webapp
description: Production-ready web application Helm Chart
type: application
version: 0.1.0
appVersion: "1.0.0"
EOF

2단계: values.yaml 작성

로컬 터미널
cat > values.yaml << 'EOF'
replicaCount: 2

image:
  repository: nginx
  tag: "1.25-alpine"
  pullPolicy: IfNotPresent

service:
  type: ClusterIP
  port: 80
  targetPort: 80

ingress:
  enabled: false
  host: ""

resources:
  requests:
    cpu: 100m
    memory: 64Mi
  limits:
    cpu: 200m
    memory: 128Mi

env: []
EOF

3단계: _helpers.tpl 작성

로컬 터미널
cat > templates/_helpers.tpl << 'EOF'
{{- define "my-webapp.name" -}}
{{- .Values.nameOverride | default .Chart.Name | trunc 63 | trimSuffix "-" }}
{{- end }}

{{- define "my-webapp.fullname" -}}
{{- if .Values.fullnameOverride }}
{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- $name := .Values.nameOverride | default .Chart.Name }}
{{- if contains $name .Release.Name }}
{{- .Release.Name | trunc 63 | trimSuffix "-" }}
{{- else }}
{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }}
{{- end }}
{{- end }}
{{- end }}

{{- define "my-webapp.labels" -}}
helm.sh/chart: {{ printf "%s-%s" .Chart.Name .Chart.Version }}
{{ include "my-webapp.selectorLabels" . }}
app.kubernetes.io/managed-by: {{ .Release.Service }}
{{- end }}

{{- define "my-webapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-webapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
EOF

4단계: Deployment, Service 템플릿 작성

로컬 터미널
cat > templates/deployment.yaml << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ include "my-webapp.fullname" . }}
  labels:
    {{- include "my-webapp.labels" . | nindent 4 }}
spec:
  replicas: {{ .Values.replicaCount }}
  selector:
    matchLabels:
      {{- include "my-webapp.selectorLabels" . | nindent 6 }}
  template:
    metadata:
      labels:
        {{- include "my-webapp.selectorLabels" . | nindent 8 }}
    spec:
      containers:
        - name: {{ .Chart.Name }}
          image: "{{ .Values.image.repository }}:{{ required "image.tag는 필수입니다" .Values.image.tag }}"
          imagePullPolicy: {{ .Values.image.pullPolicy }}
          ports:
            - name: http
              containerPort: {{ .Values.service.targetPort }}
          {{- if .Values.env }}
          env:
            {{- toYaml .Values.env | nindent 12 }}
          {{- end }}
          resources:
            {{- toYaml .Values.resources | nindent 12 }}
EOF

cat > templates/service.yaml << 'EOF'
apiVersion: v1
kind: Service
metadata:
  name: {{ include "my-webapp.fullname" . }}
  labels:
    {{- include "my-webapp.labels" . | nindent 4 }}
spec:
  type: {{ .Values.service.type }}
  ports:
    - port: {{ .Values.service.port }}
      targetPort: {{ .Values.service.targetPort }}
      protocol: TCP
  selector:
    {{- include "my-webapp.selectorLabels" . | nindent 4 }}
EOF

5단계: lint와 template으로 검증

로컬 터미널
# Chart 문법 검증
helm lint ./my-webapp
# ==> Linting ./my-webapp
# [INFO] Chart.yaml: icon is recommended
# 1 chart(s) linted, 0 chart(s) failed

# 로컬 렌더링 (클러스터 불필요)
helm template test-release ./my-webapp --namespace helm-dev

# 예상 출력:
# ---
# # Source: my-webapp/templates/service.yaml
# apiVersion: v1
# kind: Service
# metadata:
#   name: test-release-my-webapp
#   labels:
#     helm.sh/chart: my-webapp-0.1.0
#     app.kubernetes.io/name: my-webapp
#     app.kubernetes.io/instance: test-release
#     app.kubernetes.io/managed-by: Helm
# ...

# values 오버라이드 확인
helm template test-release ./my-webapp \
  --set replicaCount=3 \
  --set image.repository=myapp \
  --set image.tag=v2.0.0 \
  --namespace helm-dev

6단계: 클러스터에 설치

로컬 터미널
# 설치 (--dry-run으로 먼저 확인)
helm install my-webapp ./my-webapp \
  --namespace helm-dev \
  --dry-run

# 실제 설치
helm install my-webapp ./my-webapp --namespace helm-dev

# 결과 확인
helm list -n helm-dev
# NAME       NAMESPACE  REVISION  STATUS    CHART
# my-webapp  helm-dev   1         deployed  my-webapp-0.1.0

kubectl get all -n helm-dev
# NAME                                        READY   STATUS    RESTARTS
# pod/my-webapp-my-webapp-7d9f6b8d4c-xk2p9   1/1     Running   0
# pod/my-webapp-my-webapp-7d9f6b8d4c-r5mn2   1/1     Running   0
#
# NAME                          TYPE        CLUSTER-IP      PORT(S)
# service/my-webapp-my-webapp   ClusterIP   10.96.173.42    80/TCP

# values 변경 후 업그레이드
helm upgrade my-webapp ./my-webapp \
  --namespace helm-dev \
  --set replicaCount=3

# 롤백
helm rollback my-webapp 1 --namespace helm-dev
🔍실행 후 확인할 것
  • NAME조회 대상 리소스 이름이 예상한 대상과 일치하는지 확인합니다.
  • STATUS/READYRunning, Ready, Available처럼 정상 상태를 나타내는 필드가 있는지 봅니다.
  • RESTARTS/EVENTS재시작 횟수나 Warning 이벤트가 증가하지 않는지 확인합니다.

문제 상황

로컬 터미널
$ helm template test ./my-webapp
Error: template: my-webapp/templates/deployment.yaml:12:7: \
executing "my-webapp/templates/deployment.yaml" at <include "my-webapp.labels" .>: \
error calling include: template: my-webapp/templates/_helpers.tpl:8:3: \
executing "my-webapp.labels" at <include "my-webapp.selectorLabels" .>: \
wrong type for value; expected string; got template

원인 분석

_helpers.tpl에서 named template을 정의할 때 define 블록 끝에 불필요한 공백이나 줄바꿈이 포함되면 이 오류가 납니다. Helm template은 공백에 매우 민감합니다.

YAML
# 잘못된 _helpers.tpl (줄바꿈이 반환값에 포함됨)
{{- define "my-webapp.selectorLabels" }}
app.kubernetes.io/name: {{ include "my-webapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{ end }}
# ^ end 앞 줄바꿈, define 뒤 줄바꿈이 문제

# 올바른 _helpers.tpl (- 로 공백 제거)
{{- define "my-webapp.selectorLabels" -}}
app.kubernetes.io/name: {{ include "my-webapp.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}
# ^ define과 end 양쪽에 - 붙여 앞뒤 공백 제거

디버깅 방법

로컬 터미널
# 1단계: 어느 파일의 몇 번째 줄인지 오류 메시지에서 확인
# "my-webapp/templates/deployment.yaml:12" → 12번째 줄
# "my-webapp/templates/_helpers.tpl:8" → _helpers.tpl 8번째 줄

# 2단계: 문제 template을 단독으로 렌더링 시도
helm template test ./my-webapp --show-only templates/deployment.yaml

# 3단계: --debug 플래그로 렌더링 전 전처리 결과 확인
helm template test ./my-webapp --debug 2>&1 | head -50

# 4단계: 공백 문제 확인 — define/end 양쪽에 반드시 - 붙이기
# {{- define "name" -}} ... {{- end }}
#      ^              ^         ^
#      앞 공백 제거    뒤 공백 제거  앞 공백 제거

자주 나오는 template 오류 패턴

로컬 터미널
# 오류 1: undefined template
# Error: template: ...at <include "my-webapp.fullname" .>: function "my-webapp.fullname" not defined
# → _helpers.tpl 파일이 없거나 define 이름이 일치하지 않음

# 오류 2: wrong number of args
# Error: ...wrong number of args for include: want 2 got 1
# → include에 두 번째 인수(컨텍스트) 누락
# 잘못됨: {{ include "my-webapp.labels" }}
# 올바름: {{ include "my-webapp.labels" . }}

# 오류 3: can't evaluate field X in type interface {}
# Error: ...can't evaluate field replicaCount in type interface {}
# → .Values 접근 경로 오타
# values.yaml: replicaCount → template: .Values.replicaCount (대소문자 확인)

💼
실무 맥락
현업 패턴

배경

스타트업 C사의 DevOps 엔지니어. 현재 10개 마이크로서비스가 각자 deployment.yaml, service.yaml을 별도로 관리하고 있습니다. 환경마다 수작업으로 이미지 태그를 바꾸고, 레플리카 수를 수정하다가 실수가 반복됩니다. Chart를 도입하여 표준화하는 작업을 맡았습니다.

전환 전략

1단계: 가장 단순한 서비스 하나를 Chart로 전환 (파일럿)
2단계: 공통 패턴 추출 → 팀 공용 _helpers.tpl 정립
3단계: 나머지 서비스를 순차적으로 전환
4단계: CI/CD 파이프라인에 helm upgrade 통합

기존 YAML에서 Chart로 변환하는 패턴

로컬 터미널
# 기존 deployment.yaml의 하드코딩 값을 values.yaml로 추출
# Before:
#   replicas: 2
#   image: gcr.io/myproject/user-service:abc123def
# After values.yaml:
#   replicaCount: 2
#   image:
#     repository: gcr.io/myproject/user-service
#     tag: abc123def

CI/CD 통합 예시 (GitHub Actions)

YAML
# .github/workflows/deploy.yml
- name: Deploy to Kubernetes
  run: |
    helm upgrade --install ${{ env.SERVICE_NAME }} ./charts/${{ env.SERVICE_NAME }} \
      --namespace ${{ env.NAMESPACE }} \
      --set image.tag=${{ github.sha }} \
      --set replicaCount=${{ env.REPLICA_COUNT }} \
      --atomic \
      --timeout 5m
    # --atomic: 실패 시 자동 롤백
    # --timeout: 배포 완료 대기 시간

현장에서 배운 교훈

1. selectorLabels는 한 번 배포 후 절대 변경하지 말 것
   → 변경 시 Deployment 삭제 후 재생성 필요 (다운타임 발생)

2. Chart 버전(Chart.yaml의 version)과 앱 버전(appVersion)을 분리할 것
   → Chart 구조 변경은 version 올리기, 앱 코드 변경은 appVersion 올리기

3. 시크릿은 values.yaml에 직접 쓰지 말 것
   → helm install ... --set secret.password=$(cat /tmp/secret)
   → 또는 외부 시크릿 관리 도구(External Secrets Operator) 사용

핵심 요약

개념설명
Chart.yamlChart 이름, 버전, dependency 정의
values.yaml기본값 정의 — 환경별 오버라이드 가능
templates/Kubernetes manifest Go 템플릿 파일
_helpers.tplnamed template 정의 — _로 시작하여 렌더링 대상 제외
includenamed template 호출 + pipeline 지원
toYaml값 구조체를 YAML 문자열로 직렬화
required필수 값 없을 때 명확한 오류로 렌더링 중단
nindent N앞에 줄바꿈 + N칸 들여쓰기
helm template클러스터 없이 로컬 렌더링 결과 확인
helm lintChart 문법·구조 검증
dependency서브차트 — helm dependency update로 다운로드

지식 확인

퀴즈 — 4문제

Q1

Helm Chart의 templates/ 디렉토리에서 `_helpers.tpl` 파일의 역할은?

Q2

values.yaml에서 정의된 값을 template에서 참조할 때 올바른 문법은?

Q3

Helm의 `required` 함수를 사용하는 이유는?

Q4

`helm template` 명령어의 주된 용도는?

0 / 4 답변

🧪 실습으로 확인하기

K8s 기초 — Pod/Deployment/Service 생성

초급

kubectl로 nginx Pod를 생성하고 Deployment와 Service를 차례로 만들어 클러스터 외부에서 접근 가능한 상태까지 구성한다. K8s 3대 리소스의 역할과 관계를 직접 손으로 익힌다.

40📋 5단계💻 직접 환경
실습 시작하기 →

이것도 배워보세요

kubernetes고급 · 60
[Kubernetes] Operator 패턴과 커스텀 컨트롤러 기반 자가 복구 인프라 구축
Kubernetes 트랙 계속
docker입문 · 30
[Docker] 백엔드 개발자에게 Docker와 컨테이너 가상화가 필수인 이유
Docker 트랙 시작점