Giới thiệu Helm, viết Helm Chart đầu tiên và publish lên Github
Hello các bạn lại là mình đây 👋👋
Hôm nay ta tiếp tục quay trở lại với series Học Kubernetes từ cơ bản đến "gần" nâng cao nhé.
Trong bài hôm nay ta sẽ tìm hiểu về Helm khi làm việc với Kubernetes, cùng với đó là sẽ viết 1 Helm chart của riêng chúng ta, sau đó publish lên Github cho mọi người sử dụng nha.
Mặc áo phao 🛟 rồi lên thuyền với mình nàoooooo ⛴️⛴️
Bài này mình sẽ cố gắng viết tối giản nhất có thể, tập trung vào những gì mà ta thường dùng trong thực tế thôi
Giới thiệu Helm
Giới thiệu trong 1 câu: Helm là Package manager cho K8S! 😊😎
Giả sử ta cần deploy 1 ứng dụng thì nào là ta cần Deployment, Service, Ingress,...Viết tất cả các file manifest cho chúng. Ngày mai nếu có ai hỏi ta rằng họ cũng muốn deploy 1 app tương tự trên cơ sở hạ tầng của họ (infrastructure - infra), thì ta lại bảo họ là đây ông đọc 1 loạt các file manifest này, sau đó thích sử gì thì sửa 😅 Trong khi ta có tới vài chục file manifest cần đọc - tội nghiệp cho 1 tâm hồn non trẻ 🤣🤣
Với trường hợp như vậy thì ta có thể đóng gói app của chúng ta lại thành 1 Helm chart
, sau đó public cho mọi người (hoặc gửi nội bộ cho người khác), sau đó họ chỉ cần sửa xíu cấu hình cho phù hợp với infra của họ là apply
được luôn.
Hiểu đơn giản Helm + Helm chart ở đây là 1 cách để tái sử dụng công việc cấu hình manifest khi làm việc với Kubernetes
Liên hệ với những gì ta vẫn làm, bình thường khi ta code NodeJS, để tiết kiệm thời gian ta thường dùng framework ví dụ NestJS hay các thư viện mà người ta dựng sẵn để tạo webserver như Express.JS, các framework/thư viện kia được đẩy lên npm/yarn
- npm/yarn
chính là Package manager cho NodeJS
Hoặc như bên Python tạo webserver ta hay dùng Flask được cài từ pip
cũng là package manager cho Python, hoặc Maven repository
cho phía Java, NuGet
cho C#,...
Setup Helm
Giờ ta zô phần thực hành để xem mặt mũi nó như thế nào nhé 💪💪.
Nếu các bạn đã cài Helm rồi thì bỏ qua bước cài đặt này nha
Đầu tiên ta cần cài đặt Helm, các bạn vô trang chủ: https://helm.sh/
Sau đó ta vô phần Docs > Introduction > Installing Helm
(https://helm.sh/docs/intro/install/)
Ở đó ta thấy có rất nhiều cách để cài Helm, tuỳ vào hệ điều hành và các mà ta muốn thực hiện. Cá nhân mình thấy thì dùng Binary Releases
là dễ và tiện nhất. Phần này các bạn tự làm nhé.
Sau khi cài xong thì ta mở terminal và check xem mọi thứ đã oke chưa bằng cách chạy command sau:
helm version
>>>
version.BuildInfo{Version:"v3.15.1", GitCommit:"e211f2aa62992bd72586b395de50979e31231829", GitTreeState:"clean", GoVersion:"go1.22.3"}
Nếu thấy in ra như trên là oke bước đầu rồi 🥳
Chạy Helm chart đầu tiên
À vẫn như thường lệ, vì bài này thực hành trên K8S cluster của mình nên ta sẽ cần lấy session, các bạn làm như các bài trước, vô trang của mình để lấy session nha: https://learnk8s.jamesisme.com/. Các bạn nhớ tick vào Require domain
để tí nữa ta dùng với Ingress nhé:
Trước khi làm thì ta hiểu định nghĩa về Helm Chart là gì trước đã nhé: như mình nói ở đầu bài, với Helm ta sẽ đóng gói tất cả các file manifest thành 1 package, và với Helm họ gọi nó là Chart thay vì package. Hết, định nghĩa chỉ có vậy 🤣🤣
Helm chart sau đó thường sẽ được đẩy lên Repository public hoặc private, hoặc đơn giản là share giữa các anh em dev với nhau và chạy trực tiếp.
Ở bài này ta sẽ thử chạy chart từ các public repo nhé. Đầu tiên ta list
xem ở local ta đang lưu địa chỉ của repo nào rồi:
helm repo list
>>> Error: no repositories to show
Vì ta vừa install Helm xong nên mọi thứ còn sạch trơn và báo là không có repo nào cả
Giờ ta quay lại trang chủ Helm, tìm 1 public chart và chạy thử nhé, ở bài này ta thử với Nginx nha:
Sau khi click thì ta được đưa tới Artifact Hub, ở đó họ chuyên để tìm kiếm các package cloud native (build cho cloud)
Sau đó ta chọn nginx của Bitnami
Cho anh em nào chưa biết thì Bitnami cung cấp rất nhiều đồ opensource cực xịn, từ Docker image, helm chart các thứ, mà đồ của họ làm rất chỉn chu, dễ cấu hình
Khi đã vô trang Helm chart Nginx của Bitnami ở đó ta thấy có README khá dài và đầy đủ, các bạn có thể đọc thêm, sau đó ta bấm Install
:
Hướng dẫn install
chart hiện ra như sau:
Như ở trên ta thấy là đầu tiên ta cần phải add repo của Bitnami về local trước, sau đó mới install được
Ta làm từng bước nhé:
helm repo add bitnami https://charts.bitnami.com/bitnami
>>> "bitnami" has been added to your repositories
Sau đó ta thử list repo xem:
helm repo list
>>>
NAME URL
bitnami https://charts.bitnami.com/bitnami
Oke ngon rồi, giờ ta install nginx chart nhé, nhưng ta cần chú ý rằng, tương tự các bài trước, mỗi khi chạy helm install thì ta cần truyền vào file kubeconfig
(vì các bạn đang dùng cluster của mình mà, sau này các bạn có cluster riêng permission đầy đủ thì không cần 😁)
Ta mở terminal ở ngay thư mục chứa file session k8s kubernetes-config
và chạy:
helm install my-nginx bitnami/nginx --version 18.2.3 --kubeconfig=kubernetes-config
Chạy lên ta sẽ thấy log báo như sau là oke nè:
Ở trên log ghi ra các thông số về chart mà ta đang chạy như version, name, cách truy cập service, cách lẩy URL để truy cập từ trình duyệt...
chú ý rằng chart version không phải là nginx version đâu nhé
Giờ ta list
xem các chart mà ta đã install nhé:
helm list --kubeconfig=kubernetes-config
>>>
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
my-nginx lk8s-fce251 1 2024-10-20 10:23:33.950566 +0800 +08 deployed nginx-18.2.3 1.27.2
Ở trên ta có REVISION, dành cho nếu sau này ta upgrade my-nginx
thì sẽ có 1 revision mới, để nếu ta có muốn nhanh chóng rollback về 1 revision cũ sẽ tiện hơn
Giờ ta lại get Pods và service xem mọi thứ oke chưa:
kubectl get po --kubeconfig=./kubernetes-config
>>>
NAME READY STATUS RESTARTS AGE
my-nginx-8667dd878d-jxcv6 1/1 Running 0 14m
---
kubectl get svc --kubeconfig=./kubernetes-config
>>>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx LoadBalancer 10.245.101.1 144.126.243.188 80:32646/TCP,443:31565/TCP 14m
Ở trên ta thấy rằng Load balancer IP cho service đã lên 144.126.243.188
, ta mở ở trình duyệt là sẽ thấy như sau:
Dùng Helm giờ deploy cực dễ nhỉ, nãy giờ chưa có viết tí code YAML nào. Đỉnh nóc, kịch trần, bay phấp phới 🤣🤣
Ta vọc vạch chút nhé 🧐
Quay lại trang nơi ta tìm kiếm nginx chart: https://artifacthub.io/packages/helm/bitnami/nginx
Ở đó ta thấy Templates và Default values. Ta mở Templates lên:
Ta thấy rằng bản chất 1 cái Helm chart nó cũng chỉ là các file manifest mà ta vẫn thường viết deployment.yaml, svc.yaml,...
, và thay vì hardcode các giá trị vào thì ở đây ta để các placeholder (là template expression), giá trị thực tế sẽ được truyền vào từ file Default Values
:
Mặc định không nói gì thì Helm sẽ dùng Default Values
, ta có thể viết file values của riêng chúng ta và bảo Helm dùng cái đó thay vì Default Values
Ý tưởng của Helm cơ bản theo mình thấy là vậy, cũng khá là dễ hiểu ấy nhỉ 😍
Giờ ta sang phần tiếp theo đó là tự viết 1 chart và đẩy lên Github cho mọi người dùng nhé 💪💪
À trước khi làm thì ta uninstall
nginx chart mà ta đang chạy đi nhé:
helm uninstall my-nginx --kubeconfig=kubernetes-config
>>> release "my-nginx" uninstalled
Sau khi uninstall thì tất cả các resource liên quan lúc ta install đều sẽ bay màu luôn, rất tiện 🥰🥰
Tự viết Helm Chart đầu tiên
Tổng quan
Ta sẽ follow theo official Docs của Helm về cách tạo Chart mới nhé: https://helm.sh/docs/chart_template_guide/getting_started/#a-starter-chart
Đầu tiên các bạn chạy cho mình command sau (ở bất kì folder nào mà các bạn muốn):
helm create mychart
>>> Creating mychart
Sau khi tạo xong ta có như sau:
Xem qua cấu trúc folder của 1 chart thì ta có những thứ sau:
NOTES.txt
: đây là file note sẽ được hiển thị cho user khi họinstall
chart của các bạn, giống như ở bên trên ta đã làm- trong folder
templates
ta có các fileyaml
dành cho Deployment, Service,... như những gì ta đã từng làm _helpers.tpl
: ở đây ta viết cáchelpers
kiểu share properties, và ta có thể sử dụng các helpers đó ở bất kì đâu trong các file template. Ví dụ ởdeployment.yaml
ta cóname: {{ include "mychart.fullname" . }}
thì tương ứng ở_helpers.tpl
ta phảidefine "mychart.fullname"
Ta cũng đi qua deployment.yaml
xem ở đó ta có những gì nhé:
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.labels" . | nindent 8 }}
{{- with .Values.podLabels }}
{{- toYaml . | nindent 8 }}
{{- end }}
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: {{ .Values.service.port }}
protocol: TCP
livenessProbe:
{{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
{{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.volumeMounts }}
volumeMounts:
{{- toYaml . | nindent 12 }}
{{- end }}
{{- with .Values.volumes }}
volumes:
{{- toYaml . | nindent 8 }}
{{- 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 }}
Đầu tiên ta có name: {{ include "mychart.fullname" . }}
, ở đây ta đang dùng include
function, truyền vào 1 cái named template
(gọi cách khác nó giống như tham số của function), ta cũng cần truyền vào context
bằng dấu "chấm" .
nữa, để ở bên trong phần xử lý "mychart.fullname"
ta có thể truy cập tới các thông tin của chart. Phần này được xử lý ở bên file _helpers.tpl
lát nữa mình sẽ nói tới nhé
Tiếp theo ta có labels
, cũng tương tự ở trên dùng include
, nhưng để ý rằng ta cần phải thêm nindent 4
, để kết quả trả về được indent vào trong 4 spaces, lí do là vì content của ta trả về ở đây có thể là multiline, chứ không như name
bên trên content trả về là inline luôn
Đọc tiếp xuống dưới nữa ta sẽ thấy các phần tương tự, có 1 vài cái ta cần để ý:
.Values.XXX
: đây chính là value lấy từ filevalues.yml
của chúng ta. Chú ý nó có dấu "chấm" ở đầu, vì Helm nó có scope/namespaced, để dấu chấm ở đầu ý bảo là ta bắt đầu từ cái top-most scope/namespace -> truy cập vào objectValues
cái này thì khi nào các bạn có subchart hoặc muốn truy cập từ 1 scope/namespace khác mới cần quan tâm (theo mình thấy thì khá ít khi sử dụng)- Ta thấy rằng ở đây ta cũng có thể dùng các toán tử
if/else
các kiểu. Helm support các syntax của Go template ở đây: https://pkg.go.dev/text/template, và họ thêm cả các helper function vào ở đây nữa: https://masterminds.github.io/sprig/
Ta có thể thay đổi scope/namespace bằng việc dùng with
, ví dụ ở metadata
ta có đoạn:
{{- with .Values.podAnnotations }}
annotations:
{{- toYaml . | nindent 8 }}
{{- end }}
Ở đây ý của ta là:
- lấy scope từ
podAnnotations
trong.Values
- truyền cái context hiện tại vào
toYaml
, kết quả trả về như nào thì indent nó 8 spaces
Và khi chạy thật thì Helm sẽ tìm tới object podAnnotations
trong file values.yml
, lấy toàn bộ content bên trong và convert nó thành YAML rồi indent 8 spaces, kết quả được đặt vào annotations
trong file deployment.yml
ở vị trí chỉ định
toYaml
là built-in function
Ta chú ý rằng với nhiều toán tử nó yêu cầu ta cần phải khai báo {{- end }}
để chỉ định rõ toán tử được áp dụng từ đâu tới đâu
Ta cũng có thể concat (nối) nhiều value với nhau, ví dụ:
image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}"
Cái dấu |
gọi là pipeline
, đây là một trong những cái ta dùng nhiều nhất luôn, thường dùng để ta transform cái giá trị của template được trả về, ta có thể áp dụng nhiều pipeline 1 lúc cũng được, ví dụ:
{{ .Values.favorite.drink | default "tea" | quote }}
Tiếp theo tới file _helpers.tpl
, ở đây ta sẽ có những cái custom logics cho template được share trên toàn bộ các file YAML template, thường là các function đơn giản để transform string, append value,...phía template sẽ dùng include
như ta đã nói ở trên để truy cập tới helpers
Ngoài folder templates
thì ta cũng có folder charts
để lưu các subchart
, cái này nếu các bạn thật sự cần thì tìm hiểu thêm nhé, thường mình thấy cũng ít khi dùng: https://helm.sh/docs/chart_template_guide/subcharts_and_globals/
Bên cạnh đó ta có file Chart.yml
ở đó ta khai báo các thông tin về chart, values.yml
là Default values
cho chart của chúng ta
Test local
Giờ ta sẽ cùng chạy chart ta vừa tạo lên nhé:
helm install my-nginx ./mychart --kubeconfig=kubernetes-config
Ở trên thay vì truyền vào tên repo như khi nãy thì ta truyền vào đường dẫn tới folder chưa cấu hình Chart
Sau khi chạy command thì ta sẽ thấy log báo như sau:
NAME: my-nginx
LAST DEPLOYED: Sun Oct 20 22:14:48 2024
NAMESPACE: lk8s-bbf2d1
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace lk8s-bbf2d1 -l "app.kubernetes.io/name=mychart,app.kubernetes.io/instance=my-nginx" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace lk8s-bbf2d1 $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace lk8s-bbf2d1 port-forward $POD_NAME 8080:$CONTAINER_PORT
Mỗi 1 lần ta install
1 chart thì nó tạo ra 1 release
Ta helm list
xem nha:
helm list --kubeconfig=kubernetes-config
>>>
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
my-nginx lk8s-bbf2d1 1 2024-10-20 22:14:48.782839 +0800 +08 deployed mychart-0.1.0 1.16.0
Oke release được deployed
rồi 👍️
Tiếp theo ta get pod:
kubectl get po --kubeconfig=./kubernetes-config
>>> No resources found in lk8s-bbf2d1 namespace.
Ủa gì z??? sao lại không có pod nào? 🧐🧐
Service thì sao nhỉ?
kubectl get svc --kubeconfig=./kubernetes-config
>>>
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-nginx-mychart ClusterIP 10.245.199.40 <none> 80/TCP 4m7s
Ủa service lên rồi mà....🙄🙄
Ta get deployment xem nha:
kubectl get deploy --kubeconfig=./kubernetes-config
>>>
NAME READY UP-TO-DATE AVAILABLE AGE
my-nginx-mychart 0/1 0 0 5m35s
Hừm... READY 0/1
, có vấn đề gì vậy nhờ 🧐🧐
Ta lại tiếp tục get replicaset
nhé (vì ta dùng replicas
ở file deployment.yml
nên k8s sẽ tương ứng tạo 1 replicaset):
kubectl get rs --kubeconfig=./kubernetes-config
>>>
NAME DESIRED CURRENT READY AGE
my-nginx-mychart-6bc999bf79 1 0 0 7m9s
Ở trên mình dùng dạng viết tắt
rs
, ta viết rõreplicaset
cũng được
Ta thấy rằng trạng thái của replicaset Current=0
, trong khi mong muốn Desired=1
, giờ ta sẽ describe replicaset
xem cụ thể trạng thái nó là gì nhé
kubectl describe rs --kubeconfig=./kubernetes-config my-nginx-mychart-6bc999bf79
Thay tên replicaset của các bạn vào cho đúng nhé
Log in ra như sau:
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Warning FailedCreate 20m replicaset-controller Error creating: pods "my-nginx-mychart-6bc999bf79-zls7t" is forbidden: failed quota: lk8s-bbf2d1: must specify cpu for: mychart; memory for: mychart
Warning FailedCreate 20m replicaset-controller Error creating: pods "my-nginx-mychart-6bc999bf79-hbfdt" is forbidden: failed quota: lk8s-bbf2d1: must specify cpu for: mychart; memory for: mychart
Warning FailedCreate 20m replicaset-controller Error creating: pods "my-nginx-mychart-6bc999bf79-wnjkh" is forbidden: failed quota: lk8s-bbf2d1: must specify cpu for: mychart; memory for: mychart
Warning FailedCreate 20m replicaset-controller Error creating: pods "my-nginx-mychart-6bc999bf79-j4s7x" is forbidden: failed quota: lk8s-bbf2d1: must specify cpu for: mychart; memory for: mychart
Warning FailedCreate 20m replicaset-controller Error creating: pods "my-nginx-mychart-6bc999bf79-xd5c7" is forbidden: failed quota: lk8s-bbf2d1: must specify cpu for: mychart; memory for: mychart
Warning FailedCreate 20m replicaset-controller Error creating: pods "my-nginx-mychart-6bc999bf79-fkr6j" is forbidden: failed quota: lk8s-bbf2d1: must specify cpu for: mychart; memory for: mychart
Warning FailedCreate 20m replicaset-controller Error creating: pods "my-nginx-mychart-6bc999bf79-lfbhp" is forbidden: failed quota: lk8s-bbf2d1: must specify cpu for: mychart; memory for: mychart
Warning FailedCreate 20m replicaset-controller Error creating: pods "my-nginx-mychart-6bc999bf79-r5p9j" is forbidden: failed quota: lk8s-bbf2d1: must specify cpu for: mychart; memory for: mychart
Warning FailedCreate 20m replicaset-controller Error creating: pods "my-nginx-mychart-6bc999bf79-t9hd6" is forbidden: failed quota: lk8s-bbf2d1: must specify cpu for: mychart; memory for: mychart
Warning FailedCreate 9m26s (x9 over 20m) replicaset-controller (combined from similar events): Error creating: pods "my-nginx-mychart-6bc999bf79-jgvw6" is forbidden: failed quota: lk8s-bbf2d1: must specify cpu for: mychart; memory for: mychart
Ầuuuu, thì ra là ta quên khai báo resource request/limit cho deployment, lí do là vì khi thực hành thì mỗi bạn sẽ có 1 namespace, và mình đã set hard resource limit
cho từng namespace 😁
Giờ ta mở file values.yaml
ở mychart
và khai báo resource request/limit nhé.....
Ê nhưng mà làm vậy không hay, vì sau này ta sẽ public chart này cho mọi người dùng, trong khi file values.yaml
trong mychart
chỉ là default values
thôi, ta nên để mặc định
Giờ ta tạo 1 file tên là custom-values.yaml
nhé:
resources:
limits:
cpu: 200m
memory: 256Mi
requests:
cpu: 100m
memory: 128Mi
Ta để file này ở ngoài (không phải bên trong mychart
), cấu trúc nom như sau:
Giờ thay vì helm install
thì ta sẽ helm upgrade
cái hiện tại mà ta đang có nhé:
helm upgrade my-nginx ./mychart --kubeconfig=kubernetes-config -f custom-values.yaml
Ở trên các bạn để ý rằng ta có -f custom-values.yaml
, ý bảo rằng: "Ê Helm, upgrade cái release my-nginx
mà khi nãy tôi install
, dùng file custom-values.yaml
để override Default values
" 😎
Sau đó ta get po xem oke chưa nhé:
kubectl get po --kubeconfig=./kubernetes-config
>>>
NAME READY STATUS RESTARTS AGE
my-nginx-mychart-795fb79b6c-9n7ds 1/1 Running 0 2m57s
Okela ngon rồi 🤗🤗🤗
Ta test xem là có truy cập được vào nginx không dùng port-forward
nhé (vì service của ta có type=ClusterIP
nên là ta không có public IP để truy cập trực tiếp đâu):
kubectl port-forward svc/my-nginx-mychart 9000:80 --kubeconfig=kubernetes-config
>>>
Forwarding from 127.0.0.1:9000 -> 80
Forwarding from [::1]:9000 -> 80
Sau đó ta mở trình duyệt ở địa chỉ http://localhost:9000
:
Pòm pòm chíu chíu, ngon rồiiii 🤪🤪
Ta Helm list xem release ta install như nào rồi nhé:
helm list --kubeconfig=kubernetes-config
>>>
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
my-nginx lk8s-bbf2d1 2 2024-10-20 22:46:33.383153 +0800 +08 deployed mychart-0.1.0 1.16.0
Ta để ý rằng REVISION=2
, mỗi 1 lần ta upgrade
thì REVISION sẽ tăng lên để sau này ta có thể rollback về nếu muốn
Giờ ta test rollback luôn xem thế nào nha, trước đó thì ta check helm history
xem đã nhé:
helm history my-nginx --kubeconfig=kubernetes-config
>>>
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Sun Oct 20 22:14:48 2024 superseded mychart-0.1.0 1.16.0 Install complete
2 Sun Oct 20 22:46:33 2024 deployed mychart-0.1.0 1.16.0 Upgrade complete
Ở trên ta thấy thì lịch sử của ta có 2 phiên bản REVISION=1 và 2
, lần đầu là install
, lần sau là upgrade
, lần 1 đã bị superseded=thay thế
, phiên bản hiện tại là REVISION=2
đã được deployed
và đang chạy
Giờ ta rollback release my-chart
về v1 nhé:
helm rollback my-nginx 1 --kubeconfig=kubernetes-config
>>>
Rollback was a success! Happy Helming!
Sau đó ta helm history
xem nhé:
helm history my-nginx --kubeconfig=kubernetes-config
>>>
REVISION UPDATED STATUS CHART APP VERSION DESCRIPTION
1 Sun Oct 20 23:13:13 2024 superseded mychart-0.1.0 1.16.0 Install complete
2 Sun Oct 20 23:13:25 2024 superseded mychart-0.1.0 1.16.0 Upgrade complete
3 Sun Oct 20 23:13:35 2024 deployed mychart-0.1.0 1.16.0 Rollback to 1
Ở trên ta thấy có thêm REVISION=3 và description là Rollback về 1
Giờ ta get po
nhé:
kubectl get po --kubeconfig=./kubernetes-config
>>>
NAME READY STATUS RESTARTS AGE
my-nginx-mychart-795fb79b6c-9n7ds 1/1 Running 0 5m9s
Ủa sao rollback về V1 rồi mà không thấy gặp lỗi như vừa nãy ta??? 🧐🧐 Tưởng lúc đầu V1 bị thiếu resource request/limit nên bị lỗi, giờ ta rollback về 1 rồi mà không thấy lỗi nữa à ta??? 🙄
Thực tế thì đây là behaviours của K8S đó các bạn ạ, Deployment của ta trước đó có resources request/limit mà sau đó ta apply phiên bản mới mà không có resources thì K8S sẽ không làm gì cả. Các bạn có thể get po -o yaml
để kiểm chứng 🤪
Publish Chart lên Github
Các bạn có để ý ở đầu bài mình nói là chart sẽ được lưu ở một nơi nào đó ta gọi là repo
, repo có thể là bất kì đâu, miễn là mọi người có thể access vào và download chart về. Và ở bài này ta sẽ lưu chart ở trên github-pages
nhé
Ta lên github tạo 1 repo mới tên là helm-charts
, ta đặt tên là gì cũng được nhé
Sau đó ta clone repo về local:
git clone https://github.com/maitrungduc1410/helm-charts.git
Thay tên username thành username của các bạn nha
Bên trong helm-charts
ta tạo folder charts
, folder này sẽ chứa tất cả các charts mà ta muốn public, sau đó ta copy mychart
vào helm-charts/charts
, trông như sau:
Sau đó ta tạo file github workflow .github/workflows/release.yml
:
name: Release Charts
on:
push:
branches:
- master
jobs:
release:
permissions:
contents: write
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Configure Git
run: |
git config user.name "$GITHUB_ACTOR"
git config user.email "$GITHUB_ACTOR@users.noreply.github.com"
- name: Run chart-releaser
uses: helm/chart-releaser-action@v1.6.0
env:
CR_TOKEN: "${{ secrets.GITHUB_TOKEN }}"
Ở trên mình dùng
chart-releaser-action
: https://github.com/helm/chart-releaser-action, nó sẽ "biến" repo github của chúng ta thành Helm chart repo
Sau khi tạo xong thì cấu trúc folder của ta như sau:
Giờ ta push code lên nha:
git add .
git commit -m "feat: first commit"
git push -u origin master
Sau khi push thì ta check Github Actions sẽ thấy như sau:
Oke success hết rồi, giờ làm gì tiếp?? 🙄🙄
Giờ ta cần tạo 1 branch mới với tên là gh-pages
nhé, đây là 1 branch đặc biệt, tên phải chính xác như vậy nha các bạn:
Sau đó ta vô Settings > Pages
, đảm bảo là ta đang chọn Deploy from source Branch
và chọn gh-pages
:
Sau đó ta quay lại trang chính của repo sẽ thấy có 1 Environment mới được tạo ra, ta click vào đó:
Sau đó ta click vào URL này sẽ dẫn tới trang Github pages của repo:
Và ta sẽ thấy như sau:
Đừng hoảng nhé, vì do ta không có file index.html
ở root folder project nên không có gì để show ở đây cả, ta thay đổi url để truy cập thẳng vào đường dẫn của chart là được, ví dụ: https://maitrungduc1410.github.io/helm-charts/charts/mychart/Chart.yaml
Thay tên username của các bạn vào cho đúng nhé
Và ta sẽ thấy như sau:
Âu cây ngon rồi, chart của ta đã được host trên Github Pages 💪💪
Test Github Chart
Oke giờ repo Github của ta đã thành 1 Helm repo, ta cần add nó về local nhé:
helm repo add github https://maitrungduc1410.github.io/helm-charts
Thay tên github username của các bạn vào cho đúng nha
Error: looks like "https://maitrungduc1410.github.io/helm-charts" is not a valid chart repository or cannot be reached: failed to fetch https://maitrungduc1410.github.io/helm-charts/index.yaml : 404 Not Found
Ủa lỗi gì zị?? 🧐🧐 Tưởng là dùng cái cái chart-releaser-action
thì từ A->Z luôn cho rồi chớ????
Như lỗi in ra thì ta không có file index.yaml
, ở cả 2 branch là master
và gh-pages
, cái gh-pages
quan trọng hơn vì nó là branch mà dùng để deploy ra Github Pages
Đọc kĩ lại docs của https://github.com/helm/chart-releaser-action thì ta phát hiện ra rằng, mỗi khi ta push code lên master thì cái chart-releaser-action
đó sẽ làm một số thứ và sau đó gen ra file index.yaml
và tự động commit vào branch gh-pages
. Tức là yêu cầu phải có sẵn branch gh-pages
lúc ta push code lên master
, ấy thế nhưng vừa nãy tại thời điểm push code lên master thì ta chưa có branch gh-pages
(ta tạo sau thông qua Web Github).
Do vậy giờ ta update bất kì cái gì và push lại vào master cho nó chạy lại Github Actions là được, ta sửa description
ở file Chart.yaml
ở local nhé:
Sau đó ta push lại:
git add .
git commit -m "chore: change chart description"
git push origin master
Sau đó ta chờ cho Github Actions chạy lại + release lại trang Github pages, chắc độ 1-2 phút, khi thành công thì ở trang chủ repo ta sẽ thấy có release như sau:
Ngon luôn 😎😎
Sau đó ta cần kiểm tra xem file index.yaml
đã có hay chưa, ta mở trình duyệt ở địa chỉ: https://maitrungduc1410.github.io/helm-charts/index.yaml
thay tên github username của các bạn vào URL cho đúng nhé
Âu cây ngon rồi, giờ ta về local và thêm Helm repo vào thôi:
helm repo add github https://maitrungduc1410.github.io/helm-charts
>>>
"github" has been added to your repositories
Xờiii, mượt luôn 🥰
Và giờ ta deploy chart để tạo release mới nhé:
helm install my-nginx github/mychart --kubeconfig=kubernetes-config -f custom-values.yaml
>>>
NAME: my-nginx
LAST DEPLOYED: Mon Oct 21 22:31:58 2024
NAMESPACE: lk8s-473421
STATUS: deployed
REVISION: 1
NOTES:
1. Get the application URL by running these commands:
export POD_NAME=$(kubectl get pods --namespace lk8s-473421 -l "app.kubernetes.io/name=mychart,app.kubernetes.io/instance=my-nginx" -o jsonpath="{.items[0].metadata.name}")
export CONTAINER_PORT=$(kubectl get pod --namespace lk8s-473421 $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}")
echo "Visit http://127.0.0.1:8080 to use your application"
kubectl --namespace lk8s-473421 port-forward $POD_NAME 8080:$CONTAINER_PORT
Ở trên chú ý rằng ta đã không còn truyền vào đường dẫn local của mychart
nữa, mà là github/mychart
Sau đó ta kiểm tra xem pod lên chưa là oke nha:
kubectl get po --kubeconfig=./kubernetes-config
>>>
NAME READY STATUS RESTARTS AGE
my-nginx-mychart-795fb79b6c-2q7dg 1/1 Running 0 82s
Phần còn lại các bạn tự vọc vạch nhé 😘😘😘
FAQ
Không publish chart lên Github có được không?
Thoải mái nhé các bạn, thực tế là mình thấy cũng khá nhiều nơi người ta không publish chart lên public hoặc private repo, mà chỉ đơn giản là lưu nó ở 1 git repo nào đó và khi install
thì vẫn truyền vào đường dẫn
Giờ có Helm chart tiện thế này rồi thì cần gì dùng cách cũ?
Công nhận là việc có Helm chart tiện hơn cho chúng ta rất nhiều, kiểu muốn deploy cái gì thì lên repo Bitnami kiếm là xong: https://github.com/bitnami/charts/tree/main/bitnami Đỡ phải viết manifest rất nhiều 😂😂
Nhưng thực tế thì không phải lúc nào cũng vậy, vì dùng Helm chart cho những trường hợp đơn giản tới "vừa vừa" lắm khi lại thành ra phức tạp hoá vấn đề, không thật sự cần thiết.
Vậy nên các bạn cân nhắc dùng linh hoạt nhé
Best practices
Commands hữu ích
Dưới đây là một số command mình thấy là hữu ích và hay dùng trong quá trình dev Helm chart
helm lint: check chart của chúng ta xem có follow các best practices không.
Ở folder mychart
ta chạy command sau:
helm lint
>>>
==> Linting .
[INFO] Chart.yaml: icon is recommended
1 chart(s) linted, 0 chart(s) failed
Ở trên ta thấy nó báo rằng ta nên có trường icon
ở file Chart.yaml
helm template --debug: test generate file manifest
Vẫn ở folder mychart
ta chạy command:
helm template my-nginx . --debug
Sau đó ta thấy in ra giá trị thực tế của các file K8S manifest ở terminal:
Cái này khá hữu ích, nhất là khi ta cần debug
helm install --dry-run --debug: giống helm template --debug nhưng có thêm là check xem có conflict gì với các resources khác đang chạy trên K8S cluster hay không. Chú ý là cái này cũng chỉ là test thôi chứ không tạo release đâu nha 😁
Ở folder mychart
ta chạy command sau:
helm install my-nginx . --kubeconfig=kubernetes-config --dry-run
helm get manifest: get các file manifest đã thật sự được apply vào release lúc ta chạy helm install
Giả sử ta đã install thành công 1 release tên là my-nginx
và nó đang chạy ngon, thì ta có thể get manifest xem là thực tế giá trị của các file manifest là như thế nào bằng:
# Mặc định lấy REVISION mới nhất
helm get manifest my-nginx --kubeconfig=kubernetes-config
# Hoặc get manifest của 1 revision cụ thể
helm get manifest my-nginx --revision 2 --kubeconfig=./kubernetes-config
Lưu lịch sử của release trên Git
Như các bạn để ý, ban đầu thì ta install
, sau đó cần update gì thì ta sửa values và helm upgrade...
. Nếu như bình thường thì ta sẽ chỉ commit file custom-values.yaml
lên Git, và dựa vào đó để biết giá trị thay đổi theo thời gian.
Vậy nhưng thực tế cái values
đó được dùng bởi Helm, cái thực tế là file K8S Manifest, cái mà thật sự được dùng để apply
tạo các resources trên K8S thì ta lại không biết, không rõ nó là gì. Cái này sẽ cực kì mệt mỏi nếu ta cần debug một lỗi nào đó và muốn so sánh thay đổi theo thời gian.
Hơn thế nữa có 1 vấn đề đó là khi ta dùng các Chart public do người ta build, thì nếu người ta thay đổi 1 cái gì đó breaking change, xong ta không để ý, cứ thế install/upgrade
xong 1 ngày trước thì manifest như này, 1 ngày sau manifest gen ra có khi đã khác hẳn 😜
Do vậy một trong những cách, mà mình thấy khá là oke khi làm thực tế đó là thay vì chạy helm install
, thì ta dùng helm để generate ra file K8S manifest, rồi ta vẫn dùng kubectl apply
để apply các file đó. Ta thử nhé 😉😉
Ở folder chứa custom-values.yaml
các bạn chạy command sau:
helm template my-nginx github/mychart --output-dir release -f custom-values.yaml
>>>
wrote release/mychart/templates/serviceaccount.yaml
wrote release/mychart/templates/service.yaml
wrote release/mychart/templates/deployment.yaml
wrote release/mychart/templates/tests/test-connection.yaml
Chú ý rằng ta đang tạo file local chứ chưa có deploy resource gì trên k8s nên ta không cần --kubeconfig=kubernetes-config
Command này sẽ generate ra các file manifest từ Chart và output ra folder release
như sau:
Quá trình này gọi đúng tiếng Anh phải là "render templates"
Và giờ để deploy thì ta dùng:
kubectl apply -f deployment.yaml --kubeconfig=kubernetes-config
# Hoặc apply cả folder
kubectl apply -f release/mychart/templates --kubeconfig=kubernetes-config
Giờ đây khi ta Git commit thì ta nên commit cả file custom-values.yaml
và folder release
, folder release
này sẽ thay đổi theo thời gian, ta sẽ dựa vào Git history để kiểm tra chính xác là tại một thời điểm đã có những gì được apply
vào K8S
Kết bài
Hi vọng ra qua bài hôm nay các bạn đã biết về Helm, 1 công cụ cực kì hữu ích và phổ biến khi làm việc với Kubernetes và một vài Best Practices
Và từ giờ về sau trong series này cũng sẽ có rất nhiều bài ta dùng Helm chart đó, bởi vì dùng Helm khá là tiện 😉
Chúc các bạn đầu tuần vui vẻ, hẹn gặp lại các bạn vào những bài sau 👋👋
All rights reserved