+12

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

Screenshot 2024-10-19 at 6.01.19 PM.png

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/

Screenshot 2024-10-19 at 11.30.54 PM.png

Sau đó ta vô phần Docs > Introduction > Installing Helm (https://helm.sh/docs/intro/install/)

Screenshot 2024-10-19 at 11.32.54 PM.png

Ở đó 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é:

Screenshot 2024-10-20 at 11.13.22 AM.png

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:

Screenshot 2024-10-19 at 11.46.57 PM.png

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)

Screenshot 2024-10-19 at 11.51.09 PM.png

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:

Screenshot 2024-10-19 at 11.53.28 PM.png

Hướng dẫn install chart hiện ra như sau:

Screenshot 2024-10-19 at 11.53.33 PM.png

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è:

Screenshot 2024-10-20 at 10.23.44 AM.png

Ở 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:

Screenshot 2024-10-20 at 10.39.03 AM.png

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

Screenshot 2024-10-20 at 10.45.19 AM.png

Ở đó ta thấy Templates và Default values. Ta mở Templates lên:

Screenshot 2024-10-20 at 10.46.03 AM.png

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:

Screenshot 2024-10-20 at 10.47.57 AM.png

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:

Screenshot 2024-10-20 at 11.06.07 AM.png

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 file yaml dành cho Deployment, Service,... như những gì ta đã từng làm
  • _helpers.tpl: ở đây ta viết các helpers 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.yamlta có name: {{ include "mychart.fullname" . }} thì tương ứng ở _helpers.tpl ta phải define "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ừ file values.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 object Values 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.ymlDefault 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.yamlmychart 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:

Screenshot 2024-10-20 at 10.45.03 PM.png

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:

Screenshot 2024-10-20 at 10.52.44 PM.png

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:

Screenshot 2024-10-20 at 11.39.29 PM.png

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:

Screenshot 2024-10-20 at 11.44.19 PM.png

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:

Screenshot 2024-10-20 at 11.59.51 PM.png

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:

Screenshot 2024-10-21 at 12.02.08 AM.png

Screenshot 2024-10-21 at 12.03.07 AM.png

Screenshot 2024-10-21 at 12.02.19 AM.png

Sau đó ta vô Settings > Pages, đảm bảo là ta đang chọn Deploy from source Branch và chọn gh-pages:

Screenshot 2024-10-21 at 12.04.00 AM.png

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 đó:

Screenshot 2024-10-21 at 12.06.01 AM.png

Sau đó ta click vào URL này sẽ dẫn tới trang Github pages của repo:

Screenshot 2024-10-21 at 12.06.09 AM.png

Và ta sẽ thấy như sau:

Screenshot 2024-10-21 at 12.07.28 AM.png

Đừ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:

Screenshot 2024-10-21 at 12.08.54 AM.png

Â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à mastergh-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é:

Screenshot 2024-10-21 at 10.22.12 PM.png

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:

Screenshot 2024-10-21 at 10.28.48 PM.png

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

Screenshot 2024-10-21 at 10.29.52 PM.png

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:

Screenshot 2024-10-21 at 11.03.15 PM.png

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:

Screenshot 2024-10-21 at 11.16.59 PM.png

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

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí