Deploy bằng sed sửa vài dòng manifest rồi kubectl apply chạy được với một site. Nhưng một website Laravel trên Kubernetes cần tới gần chục object: Deployment, Service, Ingress, Certificate, ConfigMap, Secret, secret kéo image, HPA. Nhân với hàng chục site, copy manifest rồi sửa tay chắc chắn sẽ lệch nhau. Đây là lúc cần Helm chart. Tài liệu này giải thích Helm chart là gì, cấu trúc một chart cho website Laravel, cách tổ chức values cho từng site, cách khai NFS ngay trong Deployment, khái niệm release, revision, rollback, và so sánh với manifest rời.
1. Helm chart là gì
Helm là trình quản lý gói cho Kubernetes, giống apt với Debian hay composer với PHP. Đơn vị đóng gói của Helm gọi là chart. Một Helm chart gồm hai phần:
- templates: các file manifest Kubernetes có chỗ trống, viết bằng cú pháp template của Go (
{{ .Values.domain }}). - values: các giá trị điền vào chỗ trống, đặt trong
values.yamlmặc định và ghi đè bằng file riêng (-f) hoặc tham số--set.
Ba khái niệm còn lại:
- release: một lần cài Helm chart vào cluster, có tên riêng. Cùng một chart, mỗi website là một release.
- revision: mỗi lần
helm upgradetạo một revision mới của release, đánh số 1, 2, 3... - rollback: đưa release về manifest và values của một revision cũ.
Với Helm 3, thông tin từng revision được lưu thành Secret ngay trong namespace của release (tên dạng sh.helm.release.v1.<release>.v<số>), không cần thành phần nào chạy trong cluster. Tài liệu gốc về cấu trúc Helm chart ở trang Charts của Helm.
2. Khởi tạo Helm chart bằng helm create
Cách nhanh nhất là sinh khung Helm chart bằng helm create web rồi chỉnh lại. Chart sinh ra mang theo nhiều giá trị mặc định cần sửa ngay, dễ thấy nhất là Chart.yaml:
apiVersion: v2
name: web
description: A Helm chart for Kubernetes
type: application
version: 0.1.0 # phiên bản của chart, tăng mỗi lần đổi template
appVersion: "1.16.0" # giá trị mẫu, nên sửa hoặc đừng dùng làm tag imageQuy trình hợp lý khi chuyển sang Helm chart: viết template, chạy helm template với values của một site thử, sửa cho tới khi manifest sinh ra giống bộ manifest rời đang chạy, rồi mới cài thử lên cluster.
3. Cấu trúc Helm chart cho website Laravel
web/
├── Chart.yaml
├── values.yaml # giá trị mặc định
├── example/
│ └── site-values.yaml # values mẫu có placeholder cho pipeline
└── templates/
├── _helpers.tpl # các hàm đặt tên, label dùng chung
├── deployment.yaml # Laravel + initContainer migrate
├── service.yaml
├── ingress.yaml # ingress-nginx, TLS
├── certificate.yaml # cert-manager, ClusterIssuer Let's Encrypt
├── configmap.yaml # biến môi trường không bí mật
├── secret.yaml # APP_KEY, mật khẩu DB...
├── regcred.yaml # secret kéo image từ registry
├── hpa.yaml
├── serviceaccount.yaml
├── NOTES.txt
└── tests/test-connection.yamlMỗi file trong templates/ của Helm chart tương ứng một object từng viết tay. ConfigMap và Secret giữ cách tổ chức ở bài ConfigMap và Secret: biến môi trường Laravel trên Kubernetes, Ingress và Certificate giữ cách làm ở bài ingress-nginx và cert-manager: HTTPS tự động trên Kubernetes. Khác biệt là mọi tên, domain, mật khẩu giờ lấy từ values.
4. _helpers.tpl: đặt tên và label một chỗ
_helpers.tpl chứa các template có tên (named template), gọi bằng include. Hai hàm quan trọng nhất của Helm chart:
{{- define "web.fullname" -}}
{{- $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 }}
{{- define "web.selectorLabels" -}}
app.kubernetes.io/name: {{ include "web.name" . }}
app.kubernetes.io/instance: {{ .Release.Name }}
{{- end }}- fullname: tên gốc cho mọi object của release. Cắt ở 63 ký tự vì tên nhiều loại object phải là DNS label hợp lệ.
- selectorLabels: bộ label để Deployment chọn pod và Service tìm pod. Hai bên dùng chung một hàm nên không thể lệch nhau.
- Có thể thêm hàm imagePullSecret ghép username/password trong values thành chuỗi
.dockerconfigjsonmã hoá base64 cho Secret kéo image.
Cách viết named template được hướng dẫn ở mục named templates trong Chart Template Guide.
5. Values: một file mẫu, mỗi site một bản
values.yaml mặc định giữ giá trị chung: image, resources (theo cách chọn ở bài Requests và limits, QoS và nodeAffinity: xếp pod lên đúng node), annotation của Ingress. Mỗi site có một file values riêng, sinh từ file mẫu chứa placeholder để pipeline thay (lược bớt, bí mật thay bằng <...>):
domain: "[DOMAIN]"
appName: "[APP_NAME]"
dbHost: "[DB_HOST]"
dbDatabase: "[DB_DATABASE]"
image:
repository: [IMAGE_NAME]
tag: "[IMAGE_TAG]"
autoscaling:
enabled: true
minReplicas: 1
targetCPUUtilizationPercentage: 80
volumes:
nfsIp: [NFS_IP]
nfsSharedFolder: [NFS_SHARED_FOLDER]
ingress:
enabled: true
className: nginx
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: "128m"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"Nên lưu file values của từng site vào Git, theo thư mục cluster và tên site, và để pipeline tự commit sau mỗi lần deploy. Khi đó muốn biết một site đang chạy với cấu hình gì chỉ cần mở đúng một file.
Mặt trái: values của từng site chứa mật khẩu database, APP_KEY và thông tin đăng nhập registry. Repo chứa values phải là repo riêng tư với quyền truy cập hẹp, và nên mã hoá phần bí mật (ví dụ helm-secrets với SOPS) hoặc tách bí mật ra công cụ quản lý riêng ngay từ đầu.
6. NFS khai inline trong Deployment
Website Laravel thường cần lưu file người dùng tải lên và theme đã chỉnh ra ngoài container. Cách đơn giản nhất là khai volume NFS thẳng trong pod template, không qua PersistentVolume hay PersistentVolumeClaim:
volumes:
- name: {{ include "web.fullname" . }}-volume
nfs:
server: {{ .Values.volumes.nfsIp }}
path: {{ .Values.volumes.nfsSharedFolder }}/{{ include "web.fullname" . }}
readOnly: falseContainer mount các thư mục con bằng subPath, ví dụ public, themes, plugins và storage/framework/sessions. Mỗi site có một thư mục riêng trên máy NFS, tên theo fullname của release.
| Được | Mất |
|---|---|
| Đơn giản: không phải tạo PV, PVC cho từng site | Volume inline không có chỗ khai mountOptions (phiên bản NFS, timeout...), chỉ PersistentVolume mới có |
| Xoá release không đụng tới dữ liệu trên NFS | Thư mục trên NFS phải tồn tại trước, pipeline phải tạo nó |
| Đường dẫn dễ đoán, dễ backup theo site | Kubernetes không biết gì về dung lượng hay vòng đời của volume; phân quyền dựa vào cấu hình export của máy NFS |
So với các loại volume ở bài Volume, PersistentVolume, PVC và StorageClass trên Kubernetes, đây là lựa chọn đơn giản nhất, đủ cho giai đoạn đầu khi số site còn ít.
7. Release, revision và rollback trong thực tế
Pipeline cài Helm chart cho từng site (ở đây tên release trùng namespace, mỗi site một namespace):
# render ra manifest để kiểm tra, không đụng cluster
helm template $RELEASE ./web -f $VALUES_FILE --debug
# chạy thử phía server
helm upgrade --install $RELEASE ./web -f $VALUES_FILE \
-n $RELEASE --create-namespace --dry-run
# cài mới hoặc nâng cấp
helm upgrade --install $RELEASE ./web -f $VALUES_FILE \
-n $RELEASE --create-namespaceCác lệnh hằng ngày với release:
helm list -A # mọi release trên cluster
helm history <release> -n <ns> # các revision, thời gian, trạng thái
helm get values <release> -n <ns> # values đang dùng
helm rollback <release> 3 -n <ns> # quay về revision 3
helm uninstall <release> -n <ns>Bẫy cần biết khi dùng Helm chart: nếu image dùng tag cố định với pullPolicy: Always và code mới được kéo về nhờ đổi một label trong pod template, thì helm rollback chỉ đưa label về giá trị cũ; pod được tạo lại nhưng vẫn kéo bản mới nhất của cùng tag. Rollback của Helm khi đó chỉ khôi phục cấu hình, không khôi phục code. Dữ liệu trên NFS và database cũng không quay lui theo. Muốn rollback được code, tag phải gắn với phiên bản code. Chi tiết lệnh ở trang helm rollback.
8. Manifest rời hay Helm chart
| Tiêu chí | Manifest rời + sed | Helm chart |
|---|---|---|
| Thêm một site | Copy cả thư mục, sửa tên ở nhiều file | Một file values, một lệnh helm upgrade --install |
| Sửa cấu hình chung | Sửa từng bản copy | Sửa template một lần, upgrade lần lượt các release |
| Biết đã deploy gì, khi nào | Chỉ có git log của file | helm history theo từng release |
| Gỡ site | Nhớ xoá đủ từng object | helm uninstall xoá mọi object của release |
| Độ khó khi đọc | YAML thuần, ai cũng đọc được | Phải đọc template Go, khoảng trắng và nindent dễ sai |
| Debug | Nhìn thẳng file | Phải helm template ra mới thấy manifest thật |
9. Lỗi thường gặp khi viết Helm chart
- Namespace viết cứng trong template. Đặt
namespace: {{ fullname }}trong từng file dễ lệch với namespace truyền bằng-n. Bỏ hẳn trường namespace khỏi template, để Helm gán theo-n. - HPA không tìm thấy Deployment.
scaleTargetRef.namesinh từhelm createlà{{ fullname }}; nếu bạn đổi tên Deployment thành{{ fullname }}-deployment, HPA trỏ vào khoảng trống. Đổi tên một object thì dò mọi chỗ tham chiếu tới nó, kể cả file test. - Đổi selector khi đã có release. Selector của Deployment không sửa được sau khi tạo; upgrade sẽ báo
field is immutable. Chốt selector trước khi có site thật. - Lỗi nằm im trong nhánh điều kiện. Một khối
{{- with .Values.podSecurityContext }}gọitoYamlthiếu tham số sẽ không bao giờ lộ lỗi nếu giá trị đang rỗng. Chạyhelm templatevới vài bộ values khác nhau để phủ các nhánh. - Tag image dự phòng theo appVersion. Template mặc định viết
.Values.image.tag | default .Chart.AppVersion. Nếu values quên tag, Helm dùng1.16.0, một tag không tồn tại, và pod kẹt ởImagePullBackOff.
10. Khuyến nghị
- Helm chart đáng giá từ site thứ hai trở đi. Với một site, manifest rời vẫn dễ hiểu hơn.
- Helm chart sinh từ
helm createmang theo nhiều giá trị mặc định: đọc lại từng dòng, đặc biệt là tên object và các tham chiếu chéo. - Luôn
helm template --debugvà--dry-runtrước khi cài thật; hai bước này rẻ và bắt được phần lớn lỗi cú pháp. - Rollback của Helm không thay được chiến lược đánh tag image.
- Values của Helm chart là nơi tập trung bí mật: quyết định sớm cách bảo vệ nó.
Bài tiếp theo
Cầu nối Bitbucket và GitLab CI bằng API và polling. Code nằm trên Bitbucket, pipeline build và deploy chạy trên GitLab: cách hai bên nói chuyện với nhau bằng một lệnh curl và một vòng lặp chờ kết quả.
