❤️ AZDIGI chính thức cập nhật hệ thống blog mới hoàn chỉnh. Tuy nhiên có thể một số bài viết bị sai lệch hình ảnh, hãy ấn nút Báo cáo bài viết ở cuối bài để AZDIGI cập nhật trong thời gian nhanh nhất. Chân thành cám ơn.

Ở bài 1, mình đã nói về chuyện Helm giải quyết pain point nào của Kubernetes. Sang bài 2, mình dựng xong lab local với kind/OrbStack để có chỗ test lệnh thật. Đến bài này, mình sẽ tháo chart ra xem nó được lắp như thế nào, từ Chart.yaml, values.yaml cho tới templates/ và helper template.

ℹ️ Lab trong bài được verify bằng Helm 4.1.3, chart demo tạo trực tiếp bằng helm create anatomy-demo.

Khi mới học Helm, nhiều người thường nhảy thẳng vào viết template rồi vài phút sau bắt đầu thấy mọi thứ rối như canh hẹ: biến lấy ở đâu, label sinh từ đâu, file nào ảnh hưởng tới file nào, vì sao sửa values.yaml mà manifest render ra không như mong đợi, và tại sao có chart cài được còn có chart chỉ dùng để làm dependency.

Đó là lý do trước khi viết những template phức tạp hơn, bạn nên hiểu rõ anatomy của một Helm chart. Khi nắm được cấu trúc chart, bạn sẽ đọc chart người khác nhanh hơn, sửa scaffold do helm create sinh ra đỡ vỡ hơn, và quan trọng nhất là biết nên đặt logic ở đâu cho gọn.

Trong bài này, mình sẽ đi theo hướng thực hành: tạo chart mới bằng helm create, render thử bằng helm template, chỉnh một vài file để thấy rõ vai trò của từng phần, thêm values.schema.json để kiểm tra input, và cuối bài sẽ mở một chart public để xem cách người khác tổ chức chart thật ngoài đời.

Lab trong bài được verify bằng Helm 4.1.3, chart demo tạo trực tiếp bằng helm create anatomy-demo.

Vì sao nên hiểu cấu trúc chart trước khi viết template phức tạp?

Helm chart không chỉ là một đống YAML đặt cạnh nhau. Nó là một package có quy ước rất rõ:

  • Chart.yaml mô tả chart là gì, version bao nhiêu, có dependency nào.
  • values.yaml là nơi khai báo input mặc định.
  • templates/ là nơi sinh manifest Kubernetes.
  • _helpers.tpl là chỗ gom reusable template snippets.
  • NOTES.txt là thông báo sau khi install.
  • charts/ chứa chart dependency đã kéo về.
  • crds/ chứa CRD cần cài cùng chart.
  • values.schema.json giúp validate input trước khi render/install.

Nếu chưa hiểu các vai trò này mà đã cố viết template phức tạp, bạn rất dễ gặp những lỗi kiểu:

  • nhét quá nhiều logic vào một file trong templates/
  • hard-code giá trị thay vì lấy từ values.yaml
  • lặp lại labels/names nhiều chỗ thay vì dùng helper
  • tăng appVersion nhưng quên version, hoặc ngược lại
  • thêm dependency vào Chart.yaml nhưng quên update để sinh ra charts/

Nói ngắn gọn, hiểu anatomy của chart giúp bạn bớt sửa bằng cảm tính.

Bắt đầu lab: tạo chart mới bằng helm create

Sơ đồ cấu trúc thư mục Helm chart
Sơ đồ tổng quan cấu trúc thư mục chart sau khi khởi tạo bằng helm create.

Tạo chart demo:

helm create anatomy-demo

Cấu trúc scaffold ban đầu Helm sinh ra sẽ gần như sau:

anatomy-demo/
├── Chart.yaml
├── values.yaml
├── charts/
└── templates/
    ├── NOTES.txt
    ├── _helpers.tpl
    ├── deployment.yaml
    ├── hpa.yaml
    ├── httproute.yaml
    ├── ingress.yaml
    ├── service.yaml
    ├── serviceaccount.yaml
    └── tests/

Ngay sau khi tạo, mình render thử chart mặc định:

helm template anatomy-demo ./anatomy-demo

Output thực tế cho thấy Helm đã sinh sẵn ServiceAccount, Service, Deployment và pod test trong templates/tests/test-connection.yaml. Đây là điểm khá hay của helm create: nó không tạo chart “trắng tinh”, mà cho bạn một bộ khung đủ để nhìn thấy mối liên hệ giữa values.yaml, helper template và manifest cuối cùng.

Chart.yaml: căn cước của chart

File Chart.yaml trong lab ban đầu có các trường quan trọng:

apiVersion: v2
name: anatomy-demo
description: A Helm chart for Kubernetes
type: application
version: 0.1.0
appVersion: "1.16.0"

Sau đó mình chỉnh thành:

apiVersion: v2
name: anatomy-demo
description: Demo chart để phân tích anatomy của một Helm chart
type: application
version: 0.2.0
appVersion: "1.27.0"
dependencies:
  - name: common
    version: 2.31.4
    repository: https://charts.bitnami.com/bitnami

version vs appVersion khác nhau thế nào?

Đây là chỗ người mới rất hay nhầm.

  • version: version của chart package. Bạn sửa template, đổi default value, thêm dependency, đổi schema… thì thường tăng cái này.
  • appVersion: version của ứng dụng được deploy. Ví dụ chart đang deploy NGINX 1.27.0 thì đặt appVersion: "1.27.0".

Trong output helm template, bạn sẽ thấy appVersion thường được đưa vào label app.kubernetes.io/version, còn version xuất hiện ở label kiểu helm.sh/chart: anatomy-demo-0.2.0.

Thực tế vận hành, nếu app không đổi nhưng bạn fix logic chart, thì chỉ tăng version là hợp lý. Nếu app đổi bản mới, đa phần bạn sẽ tăng cả hai, nhưng đó là hai khái niệm khác nhau.

type: application vs library

  • application: chart có thể render ra resource và cài được.
  • library: chart không nhằm deploy trực tiếp, mà chủ yếu chứa helper/template dùng chung cho chart khác.

Trong lab này, chart demo là application. Ngoài ra mình có tạo thêm ví dụ file library chart tối giản:

apiVersion: v2
name: demo-lib
description: Helper library chart example
type: library
version: 0.1.0
appVersion: ""

Nếu bạn làm một bộ helper chung cho nhiều chart nội bộ, library là kiểu phù hợp. Còn chart deploy web app, database, exporter… thì hầu hết là application.

values.yaml: nơi nhận input mặc định

Sơ đồ luồng values đến templates và manifest
Luồng dữ liệu quen thuộc trong Helm: values đi vào template rồi render thành manifest cuối cùng.

Nếu Chart.yaml là căn cước, thì values.yaml là bảng điều khiển đầu vào của chart.

Trong lab, mình chỉnh values.yaml để dễ minh hoạ hơn:

replicaCount: 2
image:
  repository: nginx
  pullPolicy: IfNotPresent
  tag: "1.27.0"
service:
  type: ClusterIP
  port: 80
uiMessage: "Demo anatomy chart"
config:
  logLevel: info
  featureFlags:
    enableMetrics: true

Ý chính của values.yaml là: đừng bắt người dùng sửa trực tiếp template nếu chỉ muốn đổi vài tham số như image tag, replica, port, ingress hay resource.

Khi bạn đọc một chart có sẵn từ repo, values.yaml gần như luôn là nơi nên mở đầu tiên. Chỉ cần lướt file này là bạn biết chart đó cho phép tuỳ biến những gì, độ phức tạp ra sao, và triết lý thiết kế của tác giả có “friendly” với người vận hành hay không.

templates/: nơi Helm render manifest Kubernetes

Thư mục templates/ mới là nơi Helm dùng Go template để sinh ra manifest cuối cùng.

Trong chart scaffold mặc định, bạn sẽ thấy các file như:

  • deployment.yaml
  • service.yaml
  • serviceaccount.yaml
  • ingress.yaml
  • hpa.yaml
  • httproute.yaml
  • tests/test-connection.yaml

Để nhìn rõ mối liên hệ giữa values.yaml và manifest, mình thêm một ConfigMap mới:

apiVersion: v1
kind: ConfigMap
metadata:
  name: {{ include "anatomy-demo.fullname" . }}
  labels:
    {{- include "anatomy-demo.labels" . | nindent 4 }}
data:
  app-message: {{ .Values.uiMessage | quote }}
  log-level: {{ .Values.config.logLevel | quote }}
  metrics-enabled: {{ .Values.config.featureFlags.enableMetrics | quote }}

Sau đó render lại:

helm template demo-release ./anatomy-demo

Output thực tế cho thấy ConfigMap đã nhận đúng giá trị từ values.yaml:

data:
  app-message: "Demo anatomy chart"
  log-level: "info"
  metrics-enabled: "true"

Đây là cách Helm làm việc hằng ngày: template lấy dữ liệu từ .Values, .Chart, .Release, rồi render ra YAML thuần để Kubernetes hiểu.

_helpers.tpl: chỗ gom những mảnh template dùng lại nhiều lần

Minh hoạ helper template reuse trong Helm
Helper template giúp gom logic fullname, labels và selector dùng chung giữa nhiều file.

Nếu bạn thấy trong các file template có những đoạn kiểu:

{{ include "anatomy-demo.fullname" . }}
{{ include "anatomy-demo.labels" . }}

thì định nghĩa của chúng nằm trong templates/_helpers.tpl.

File scaffold mặc định đã có sẵn các helper để sinh:

  • tên chart
  • fullname của release
  • label chung
  • selector labels
  • tên service account

Trong lab, mình thêm một helper nhỏ để minh hoạ việc tái sử dụng label custom:

{{- define "anatomy-demo.tutorialLabels" -}}
azdigi.io/tutorial: helm-anatomy
app.kubernetes.io/part-of: helm-series
{{- end }}

rồi include nó vào deployment.yaml.

Kết quả render ra thực tế:

labels:
  helm.sh/chart: anatomy-demo-0.2.0
  app.kubernetes.io/name: anatomy-demo
  app.kubernetes.io/instance: demo-release
  app.kubernetes.io/version: "1.27.0"
  app.kubernetes.io/managed-by: Helm
  azdigi.io/tutorial: helm-anatomy
  app.kubernetes.io/part-of: helm-series

Khi chart bắt đầu lớn, _helpers.tpl gần như là nơi cứu bạn khỏi việc copy-paste tên, label, annotation và logic fullname khắp mọi file.

NOTES.txt: file nhỏ nhưng hữu ích sau khi install

Nhiều người bỏ qua templates/NOTES.txt, nhưng đây là chỗ rất hợp để in hướng dẫn sau khi helm install xong.

Mình đổi NOTES.txt thành nội dung đơn giản hơn:

1. Chart name: {{ .Chart.Name }}
2. Chart version: {{ .Chart.Version }}
3. App version: {{ .Chart.AppVersion }}
4. Release name: {{ .Release.Name }}
Kiểm tra nhanh manifest đã render:
  helm template {{ .Release.Name }} {{ .Chart.Name }}
Kiểm tra ConfigMap sau khi install:
  kubectl get configmap {{ include "anatomy-demo.fullname" . }} -o yaml

Với chart nội bộ trong team, NOTES.txt rất đáng dùng để nhắc lệnh kubectl port-forward, URL truy cập, hoặc bước verify sau deploy.

charts/: nơi dependency được kéo về

Sơ đồ dependency trong Helm chart
Dependency trong Helm chart thường được khai báo ở Chart.yaml rồi kéo về thư mục charts/.

Trong Chart.yaml, mình thêm dependency tới Bitnami Common:

dependencies:
  - name: common
    version: 2.31.4
    repository: https://charts.bitnami.com/bitnami

Sau đó chạy:

helm dependency update

Helm sẽ tải dependency về thư mục charts/. Ý nghĩa thực tế của charts/ là chart của bạn không phải lúc nào cũng đứng một mình. Nó có thể dùng thư viện helper, subchart, hoặc phụ thuộc một chart khác.

Người mới hay thêm dependency xong rồi quên update, dẫn tới chart không package/render đúng như mong muốn. Cứ nhớ một quy tắc đơn giản: sửa dependency trong Chart.yaml thì chạy lại helm dependency update.

crds/: nơi dành cho CustomResourceDefinition

Nếu chart của bạn liên quan operator hoặc custom resource, crds/ là thư mục cần biết.

Trong lab, mình tạo file crds/demodatabases.yaml là một CRD mẫu DemoDatabase.

Khi render thông thường bằng helm template demo-release ./anatomy-demo, bạn sẽ chưa thấy CRD. Nhưng khi thêm --include-crds:

helm template demo-release ./anatomy-demo --include-crds

output đầu tiên sẽ là:

# Source: anatomy-demo/crds/demodatabases.yaml
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
  name: demodatabases.demo.azdigi.local

Điểm cần nhớ là crds/ không hoạt động giống templates/. Đây là khu vực đặc biệt của Helm, nên khi đọc chart có sẵn, nếu liên quan operator mà bạn không thấy CRD trong templates/, hãy kiểm tra crds/ trước.

values.schema.json: lớp bảo vệ rất đáng dùng

Đây là phần nhiều chart nhỏ bỏ qua, nhưng thật ra rất hữu ích, nhất là khi chart có nhiều input.

Mình thêm values.schema.json để ép:

  • replicaCount phải là integer và >= 1
  • service.type chỉ được là ClusterIP, NodePort, LoadBalancer
  • service.port phải nằm trong khoảng hợp lệ
  • config.logLevel chỉ nhận debug, info, warn, error

Sau đó thử cố tình truyền sai:

helm template demo-release ./anatomy-demo --set service.port=70000

Helm trả lỗi ngay trước khi render/install:

values don't meet the specifications...
- at '/service/port': maximum: got 70,000, want 65,535

Đây là lợi ích rất thực tế của schema: chặn lỗi từ đầu, thay vì đợi đến lúc manifest apply fail hoặc ứng dụng chạy sai mới đi debug.

Cách đọc một chart có sẵn từ repo để hiểu nó

Để bài không chỉ xoay quanh chart tự tạo, mình kéo chart public bitnami/nginx về bằng:

helm pull bitnami/nginx --untar --untardir public-charts

Cấu trúc của chart này cho thấy một chart ngoài đời thật sẽ “đậm đặc” hơn scaffold rất nhiều:

  • Chart.yaml, values.yaml, templates/
  • charts/common là dependency đã được kéo về
  • có nhiều template hơn như networkpolicy, pdb, servicemonitor, prometheusrules

Khi đọc chart public, mình thường đi theo thứ tự này:

  1. **Mở Chart.yaml** để xem chart làm gì, version nào, có dependency gì.
  2. **Mở values.yaml** để xem các knob có thể chỉnh.
  3. **Mở _helpers.tpl** để hiểu naming convention, labels, helper logic.
  4. **Mở file template chính** như deployment.yaml, service.yaml, ingress.yaml.
  5. **Render thử bằng helm template** với vài --set phổ biến.

Trong Chart.yaml của Bitnami NGINX, mình thấy ngay những thông tin đáng chú ý:

  • appVersion: 1.29.7
  • version: 22.6.10
  • dependency common
  • source repo rõ ràng

Chỉ nhìn vậy thôi là bạn đã hiểu chart này tách rõ version chart và version app, đồng thời dùng chung thư viện helper của Bitnami.

Những lỗi người mới hay gặp khi sửa chart scaffold

Sau khi lab với chart anatomy-demo, có vài lỗi rất điển hình:

1. Sửa values.yaml nhưng template không đọc giá trị đó

Khai báo trong values.yaml không tự động có tác dụng. Template phải tham chiếu đúng .Values.xxx thì manifest mới đổi.

2. Hard-code tên resource

Nếu bạn viết thẳng tên trong deployment.yaml thay vì dùng helper như {{ include "anatomy-demo.fullname" . }}, chart sẽ khó tái sử dụng và dễ đụng tên khi đổi release.

3. Nhầm version với appVersion

Cập nhật image tag nhưng lại chỉ tăng version, hoặc sửa template mà lại chỉ tăng appVersion. Hai trường này liên quan nhưng không thay nhau được.

4. Thêm dependency nhưng quên helm dependency update

Kết quả là charts/ chưa có nội dung mới, chart package không đồng bộ với Chart.yaml.

5. Quên validate input

Chart ban đầu chạy ổn với default value, nhưng khi người khác truyền --set linh tinh thì phát sinh lỗi khó hiểu. values.schema.json giúp giảm mạnh kiểu lỗi này.

6. Nhét quá nhiều logic vào một template file

Khi logic name/label/annotation lặp đi lặp lại, hãy đưa sang _helpers.tpl. Đừng biến deployment.yaml thành bãi chiến trường.

7. Không render thử trước khi cài

Thói quen tốt là luôn chạy ít nhất một trong các lệnh sau trước khi install/upgrade:

helm lint ./chart-name
helm template release-name ./chart-name

Lab này mình cũng đã chạy helm lint và chart pass bình thường.

Kết luận

Một Helm chart nhìn qua có vẻ đơn giản, nhưng mỗi file trong đó đều có vai trò riêng. Khi hiểu Chart.yaml, values.yaml, templates/, _helpers.tpl, NOTES.txt, charts/, crds/ và cả values.schema.json, bạn sẽ thấy Helm dễ kiểm soát hơn nhiều.

Điểm quan trọng nhất không phải là nhớ định nghĩa từng file như học thuộc lòng, mà là hiểu luồng làm việc:

  • chart metadata nằm ở đâu
  • input đi vào ở đâu
  • manifest được render ở đâu
  • logic dùng chung nên để ở đâu
  • dependency và CRD nằm chỗ nào
  • validate đầu vào bằng cách nào

Khi nắm anatomy của chart, bạn sẽ đọc chart người khác nhanh hơn, sửa chart scaffold tự tin hơn và viết template phức tạp theo cách gọn gàng hơn.

Ở bài 4, mình sẽ đi tiếp vào phần thực chiến hơn: cách viết template Helm “đúng bài” với các hàm thường dùng, pipeline xử lý giá trị, điều kiện if/else, vòng lặp range, và những mẹo debug khi template render ra không như mong đợi.

Assets từ lab thực tế

Các asset/output đã lưu trong workspace để tiện trích dẫn hoặc chụp màn hình:

  • temp/helm-anatomy-lab/assets/01-include-crds.txt
  • temp/helm-anatomy-lab/assets/02-schema-validation.txt
  • temp/helm-anatomy-lab/assets/03-helm-lint.txt
  • temp/helm-anatomy-lab/assets/04-bitnami-nginx-structure.txt
  • temp/helm-anatomy-lab/assets/05-bitnami-nginx-chart.yaml.txt
  • temp/helm-anatomy-lab/assets/06-bitnami-nginx-values-head.txt
  • temp/helm-anatomy-lab/assets/07-library-chart-example.yaml

Mã nguồn lab:

  • temp/helm-anatomy-lab/anatomy-demo/
  • Bài viết nháp hoàn chỉnh: temp/helm-anatomy-lab/article.md

FAQ về cấu trúc Helm Chart

Chart.yaml dùng để làm gì trong Helm chart?

Chart.yaml chứa metadata của chart như tên chart, version, appVersion, type và dependency. Đây là file đầu tiên nên mở khi bạn muốn hiểu một chart đang làm gì.

values.yaml khác gì với templates/?

values.yaml là nơi khai báo giá trị đầu vào mặc định, còn templates/ là nơi dùng các giá trị đó để render ra manifest Kubernetes. Nôm na là một bên giữ input, một bên sinh output.

_helpers.tpl có bắt buộc không?

Không bắt buộc, nhưng gần như chart nào nghiêm túc cũng nên có. File này giúp gom logic lặp lại như fullname, labels, selectorLabels hoặc annotation để tránh copy-paste khắp nơi.

Khi nào nên dùng values.schema.json?

Nên dùng ngay khi chart có nhiều input hoặc được nhiều người cùng dùng. values.schema.json giúp validate giá trị trước khi render hoặc cài đặt, từ đó chặn lỗi sớm hơn nhiều.

Chia sẻ:
Bài viết đã được kiểm duyệt bởi AZDIGI Team

Về tác giả

Trần Thắng

Trần Thắng

Chuyên gia tại AZDIGI với nhiều năm kinh nghiệm trong lĩnh vực web hosting và quản trị hệ thống.

Hơn 10 năm phục vụ 80.000+ khách hàng

Bắt đầu dự án web của bạn với AZDIGI