
목차
Helm Chart란
Helm Chart는 Kubernetes 리소스를 정의하는 파일들의 모음입니다. Chart는 애플리케이션을 배포하는 데 필요한 모든 정보를 포함하며, 재사용 가능하고 버전 관리가 가능합니다.
Chart 구조
기본적인 Chart 디렉토리 구조는 다음과 같습니다:
mychart/ ├── Chart.yaml # Chart에 대한 메타데이터 ├── values.yaml # Chart의 기본 설정값 ├── charts/ # 이 Chart가 의존하는 다른 Chart들 ├── templates/ # Kubernetes 리소스 템플릿 파일들 │ ├── deployment.yaml │ ├── service.yaml │ ├── ingress.yaml │ ├── _helpers.tpl # 템플릿 헬퍼 함수들 │ └── NOTES.txt # 설치 후 출력될 사용 안내문 ├── .helmignore # 패키징 시 제외할 파일 패턴 └── README.md # Chart 설명 문서
|
Chart 생성하기
1. 새 Chart 생성
Helm CLI를 사용하여 기본 Chart 구조를 생성합니다:
이 명령은 mychart 디렉토리를 생성하고 기본 템플릿 파일들을 자동으로 생성합니다.
2. Chart.yaml 작성
Chart.yaml은 Chart의 메타데이터를 정의하는 파일입니다:
apiVersion: v2 name: mychart description: A Helm chart for my application type: application
version: 0.1.0
appVersion: "1.0.0"
maintainers: - name: Your Name email: your.email@example.com
keywords: - web - application
home: https://example.com
sources: - https://github.com/yourusername/mychart
dependencies: - name: mysql version: "9.3.4" repository: https://charts.bitnami.com/bitnami condition: mysql.enabled
|
3. values.yaml 작성
values.yaml은 Chart의 기본 설정값을 정의합니다:
replicaCount: 1
image: repository: nginx pullPolicy: IfNotPresent tag: "1.21.0"
imagePullSecrets: []
serviceAccount: create: true name: "" annotations: {}
podAnnotations: {}
podSecurityContext: {}
securityContext: {}
service: type: ClusterIP port: 80
ingress: enabled: false className: "" annotations: {} hosts: - host: chart-example.local paths: - path: / pathType: Prefix tls: []
resources: {}
autoscaling: enabled: false minReplicas: 1 maxReplicas: 100 targetCPUUtilizationPercentage: 80
nodeSelector: {}
tolerations: []
affinity: {}
env: []
configMap: enabled: false data: {}
secret: enabled: false data: {}
|
4. Deployment 템플릿 작성
templates/deployment.yaml:
apiVersion: apps/v1 kind: Deployment metadata: name: {{ include "mychart.fullname" . }} labels: {{- include "mychart.labels" . | nindent 4 }} spec: {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} {{- end }} selector: matchLabels: {{- include "mychart.selectorLabels" . | nindent 6 }} template: metadata: {{- with .Values.podAnnotations }} annotations: {{- toYaml . | nindent 8 }} {{- end }} labels: {{- include "mychart.selectorLabels" . | nindent 8 }} spec: {{- with .Values.imagePullSecrets }} imagePullSecrets: {{- toYaml . | nindent 8 }} {{- end }} serviceAccountName: {{ include "mychart.serviceAccountName" . }} securityContext: {{- toYaml .Values.podSecurityContext | nindent 8 }} containers: - name: {{ .Chart.Name }} securityContext: {{- toYaml .Values.securityContext | nindent 12 }} image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" imagePullPolicy: {{ .Values.image.pullPolicy }} ports: - name: http containerPort: 80 protocol: TCP livenessProbe: httpGet: path: / port: http readinessProbe: httpGet: path: / port: http resources: {{- toYaml .Values.resources | nindent 12 }} {{- with .Values.env }} env: {{- toYaml . | nindent 12 }} {{- end }} {{- with .Values.nodeSelector }} nodeSelector: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.affinity }} affinity: {{- toYaml . | nindent 8 }} {{- end }} {{- with .Values.tolerations }} tolerations: {{- toYaml . | nindent 8 }} {{- end }}
|
5. Service 템플릿 작성
templates/service.yaml:
apiVersion: v1 kind: Service metadata: name: {{ include "mychart.fullname" . }} labels: {{- include "mychart.labels" . | nindent 4 }} spec: type: {{ .Values.service.type }} ports: - port: {{ .Values.service.port }} targetPort: http protocol: TCP name: http selector: {{- include "mychart.selectorLabels" . | nindent 4 }}
|
6. Ingress 템플릿 작성
templates/ingress.yaml:
{{- if .Values.ingress.enabled -}} apiVersion: networking.k8s.io/v1 kind: Ingress metadata: name: {{ include "mychart.fullname" . }} labels: {{- include "mychart.labels" . | nindent 4 }} {{- with .Values.ingress.annotations }} annotations: {{- toYaml . | nindent 4 }} {{- end }} spec: {{- if .Values.ingress.className }} ingressClassName: {{ .Values.ingress.className }} {{- end }} {{- if .Values.ingress.tls }} tls: {{- range .Values.ingress.tls }} - hosts: {{- range .hosts }} - {{ . | quote }} {{- end }} secretName: {{ .secretName }} {{- end }} {{- end }} rules: {{- range .Values.ingress.hosts }} - host: {{ .host | quote }} http: paths: {{- range .paths }} - path: {{ .path }} pathType: {{ .pathType }} backend: service: name: {{ include "mychart.fullname" $ }} port: number: {{ $.Values.service.port }} {{- end }} {{- end }} {{- end }}
|
7. Helper 템플릿 작성
templates/_helpers.tpl:
{{/* Chart의 전체 이름 생성 */}} {{- define "mychart.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 }}
{{/* Chart 이름과 버전으로 chart 레이블 생성 */}} {{- define "mychart.chart" -}} {{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} {{- end }}
{{/* 공통 레이블 */}} {{- define "mychart.labels" -}} helm.sh/chart: {{ include "mychart.chart" . }} {{ include "mychart.selectorLabels" . }} {{- if .Chart.AppVersion }} app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} {{- end }} app.kubernetes.io/managed-by: {{ .Release.Service }} {{- end }}
{{/* Selector 레이블 */}} {{- define "mychart.selectorLabels" -}} app.kubernetes.io/name: {{ include "mychart.name" . }} app.kubernetes.io/instance: {{ .Release.Name }} {{- end }}
{{/* Chart 이름 */}} {{- define "mychart.name" -}} {{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} {{- end }}
{{/* ServiceAccount 이름 생성 */}} {{- define "mychart.serviceAccountName" -}} {{- if .Values.serviceAccount.create }} {{- default (include "mychart.fullname" .) .Values.serviceAccount.name }} {{- else }} {{- default "default" .Values.serviceAccount.name }} {{- end }} {{- end }}
|
8. NOTES.txt 작성
templates/NOTES.txt:
1. 다음 명령으로 애플리케이션 상태를 확인하세요:
kubectl get pods -n {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "mychart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}"
2. 다음 명령으로 애플리케이션에 접근하세요:
{{- if .Values.ingress.enabled }} {{- range $host := .Values.ingress.hosts }} {{- range .paths }} http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} {{- end }} {{- end }} {{- else if contains "NodePort" .Values.service.type }} export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "mychart.fullname" . }}) export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") echo http://$NODE_IP:$NODE_PORT {{- else if contains "LoadBalancer" .Values.service.type }} export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "mychart.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") echo http://$SERVICE_IP:{{ .Values.service.port }} {{- else if contains "ClusterIP" .Values.service.type }} export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "mychart.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") echo "Visit http://127.0.0.1:8080 to use your application" kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT {{- end }}
|
Chart 검증 및 테스트
1. Chart 문법 검증
2. Chart 템플릿 렌더링 확인
helm template mychart
helm template my-release mychart
helm template my-release mychart -f custom-values.yaml
helm template my-release mychart --show-only templates/deployment.yaml
|
3. Dry-run으로 설치 테스트
helm install my-release mychart --dry-run --debug
|
4. 실제 설치
helm install my-release mychart
helm install my-release mychart -f custom-values.yaml
helm install my-release mychart --set replicaCount=3
|
Chart 패키징 및 배포
1. Chart 패키징
helm package mychart
helm package mychart -d ./packages
|
2. Chart 의존성 관리
Chart가 다른 Chart에 의존하는 경우:
helm dependency update mychart
helm dependency build mychart
helm dependency list mychart
|
3. Chart Repository에 업로드
curl --data-binary "@mychart-0.1.0.tgz" http://chartmuseum.example.com/api/charts
helm repo index . --url http://charts.example.com
|
고급 기능
1. 조건부 리소스
{{- if .Values.configMap.enabled }} apiVersion: v1 kind: ConfigMap metadata: name: {{ include "mychart.fullname" . }} data: {{- toYaml .Values.configMap.data | nindent 2 }} {{- end }}
|
2. 반복문 사용
{{- range .Values.env }} - name: {{ .name }} value: {{ .value | quote }} {{- end }}
|
3. 기본값 설정
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
|
4. 파일 include
data: config.yaml: | {{- .Files.Get "configs/config.yaml" | nindent 4 }}
|
모범 사례
- 명확한 네이밍: Chart 이름과 리소스 이름을 명확하게 작성
- values.yaml 문서화: 각 설정값에 주석으로 설명 추가
- 기본값 제공: 모든 필수 값에 합리적인 기본값 설정
- 버전 관리: Chart 버전과 AppVersion을 명확히 구분
- 템플릿 검증: 배포 전 항상
helm lint와 helm template 실행
- 리소스 제한: 프로덕션 환경을 위한 리소스 제한값 설정
- 보안 고려: 민감한 정보는 Secret으로 관리
- 의존성 고정: 의존하는 Chart의 버전을 명시적으로 지정
- NOTES.txt 활용: 사용자를 위한 명확한 설치 후 안내 제공
- README 작성: Chart 사용법과 설정 옵션을 문서화
ConfigMap과 Secret 추가하기
ConfigMap 추가
1. templates/configmap.yaml 생성
apiVersion: v1 kind: ConfigMap metadata: name: {{ include "mychart.fullname" . }}-config labels: {{- include "mychart.labels" . | nindent 4 }} data: app.properties: | database.url={{ .Values.config.databaseUrl }} log.level={{ .Values.config.logLevel }}
API_URL: {{ .Values.config.apiUrl }} ENVIRONMENT: {{ .Values.config.environment }}
|
2. values.yaml에 ConfigMap 설정값 추가
config: databaseUrl: "mongodb://localhost:27017/mydb" logLevel: "info" apiUrl: "https://api.example.com" environment: "production"
|
3. Deployment에서 ConfigMap 사용
apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: {{ .Chart.Name }} envFrom: - configMapRef: name: {{ include "mychart.fullname" . }}-config
env: - name: DATABASE_URL valueFrom: configMapKeyRef: name: {{ include "mychart.fullname" . }}-config key: database.url
volumeMounts: - name: config-volume mountPath: /etc/config
volumes: - name: config-volume configMap: name: {{ include "mychart.fullname" . }}-config
|
Secret 추가
1. templates/secret.yaml 생성
apiVersion: v1 kind: Secret metadata: name: {{ include "mychart.fullname" . }}-secret labels: {{- include "mychart.labels" . | nindent 4 }} type: Opaque data: {{- range $key, $value := .Values.secrets }} {{ $key }}: {{ $value | b64enc | quote }} {{- end }}
|
또는 stringData 사용 (자동 인코딩):
apiVersion: v1 kind: Secret metadata: name: {{ include "mychart.fullname" . }}-secret labels: {{- include "mychart.labels" . | nindent 4 }} type: Opaque stringData: database-password: {{ .Values.secrets.databasePassword }} api-key: {{ .Values.secrets.apiKey }}
|
2. values.yaml에 Secret 값 추가
secrets: databasePassword: "mypassword" apiKey: "my-secret-api-key"
|
중요: 실제 운영 환경에서는 values.yaml에 시크릿을 직접 저장하지 말고, 다음 방법 사용:
방법 1: 설치 시 값 오버라이드
helm install myapp ./mychart \ --set secrets.databasePassword="actual-password" \ --set secrets.apiKey="actual-api-key"
|
방법 2: 별도 values 파일 사용 (gitignore에 추가)
helm install myapp ./mychart -f secrets-values.yaml
|
방법 3: 외부 Secrets 참조
apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: myapp env: - name: DB_PASSWORD valueFrom: secretKeyRef: name: {{ .Values.existingSecret | default (include "mychart.fullname" .) }} key: database-password
|
3. Deployment에서 Secret 사용
apiVersion: apps/v1 kind: Deployment spec: template: spec: containers: - name: {{ .Chart.Name }} envFrom: - secretRef: name: {{ include "mychart.fullname" . }}-secret
env: - name: DATABASE_PASSWORD valueFrom: secretKeyRef: name: {{ include "mychart.fullname" . }}-secret key: database-password
volumeMounts: - name: secret-volume mountPath: /etc/secrets readOnly: true
volumes: - name: secret-volume secret: secretName: {{ include "mychart.fullname" . }}-secret
|
조건부 생성
ConfigMap이나 Secret을 선택적으로 생성하려면:
{{- if .Values.config.enabled }} apiVersion: v1 kind: ConfigMap metadata: name: {{ include "mychart.fullname" . }}-config data: {{- end }}
|
values.yaml:
config: enabled: true databaseUrl: "..."
|
보안 모범 사례
External Secrets Operator 사용
- AWS Secrets Manager, HashiCorp Vault, GCP Secret Manager 등과 연동
- Secret을 외부 시스템에서 동적으로 가져오기
Sealed Secrets 사용
- 암호화된 Secret을 Git에 안전하게 저장
- 클러스터에서만 복호화 가능
Helm Secrets Plugin 사용
- SOPS(Secrets OPerationS)로 values 파일 암호화
- Git에 암호화된 파일 안전하게 커밋
최소 권한 원칙
- Secret에 접근할 수 있는 ServiceAccount 제한
- RBAC를 통한 접근 제어
유용한 명령어 정리
helm create mychart
helm lint mychart
helm template my-release mychart
helm install my-release mychart --dry-run --debug
helm install my-release mychart
helm upgrade my-release mychart
helm package mychart
helm show values mychart
helm show chart mychart
helm show all mychart
|