➜ Old React website
Chung Cheuk Hang MichaelJava Web Developer
Istio 入門Spring 項目中訂閱 Kafka

Helm chart 入門

Continued from previous post:
Spring Boot + Docker + K8s

Table of contents

1 Helm 簡介

自動化部署、everything as code 係今時今日既 best practices,而通常部署一個 business application,甚至乎只係部署一個 microservice,都會涉及好幾個 Kubernetes(K8s)既資源。
而 Helm 就具備左以下功能:
Helm 功能描述
打包相關資源將唔同類型既 K8s 資源打包成一舊野,易於一同部署、管理版本。
製作範本我地可以用 Helm 製作一啲 generic Helm templates,畀差唔多既 microservices 用,可能只係 app name、version、某啲 K8s 資源既配置有分別。
通常啲 Helm charts 都會體現曬以上既功能。
此外,
  • Helm 只係一個本地既 CLI 工具,唔似 Istio 咁要安裝喺 K8s cluster 上面運行。
    • 話雖如此,我地都可以喺自動化 CI/CD pipeline 度用 Helm CLI 執行 Helm commands。
  • 雖然我地可以用 Helm 管理 K8s 資源,但唔代表用左 Helm 之後就唔可以用 kubectl edit command 去臨時修改由 Helm 部署既 K8s 資源。

2 準備工作

2.1 安裝 K8s

我地可以安裝 Docker Desktop,然後開啟內置既 Kubernetes 功能。

2.2 用 Nexus 建立 Helm repository

可以睇返呢篇:自建 Nexus Maven repo
之後,我地需要:
  1. 登入 admin 帳號。
  2. 去 Administration 頁面。
  3. Repository > Repositories > 撳「Create repository」 > 揀「helm (hosted)」。
  4. 喺「Name」輸入 helm
  5. 撳「Create repository」。

2.3 安裝 Helm CLI

  1. 去 Helm 既 GitHub releases 頁面下載:GitHub - helm/helm - Releases
  2. 解壓縮 ZIP 檔。
  3. helm.exe 註冊落系統既 PATH variable。

3 動手寫

喺呢篇入門文章裡面,我地會將以下既 K8s 資源打包成一個 Helm chart,然後用 Helm CLI 既 command 部署喺 K8s。
  • Deployment
  • Service
  • HorizontalPodAutoscaler
  • ConfigMap
  • Secret

3.1 Spring Boot web app Docker image

可以參考返呢篇:Spring Boot + Docker + K8s
呢個 web app 需要:
  • Expose port 8080
  • 用 Spring Boot Actuator(expose HTTP endpoints 畀 K8s probes 用)。
  • 用 Spring Data JPA 或者 Spring Data JDBC(會用到一啲 Spring 配置,當中包括密碼)。
  • 用 H2(in-memory database)。
因為個 project 都係啲簡單 Spring Boot 野,所以呢篇文章就省略 implementation。
之後我地要將佢整成 Docker image(app 既版本係 1.5.0),就要喺有 Dockerfile 既 directory 執行以下 command:
docker image build --no-cache -t spring-boot-3-helm-demo:1.5.0 -f Dockerfile .

3.2 Helm chart

Project structure:
  • /templates
    • ConfigMap.yml
    • Deployment.yml
    • HorizontalPodAutoscaler.yml
    • Secret.yml
    • Service.yml
  • Chart.yaml
  • values.yaml
註:Chart.yaml 以及 values.yaml 一定要係 .yaml 副檔名,唔可以係 .yml 副檔名。

3.2.1 Template 檔案

Deployment.yml
1kind: Deployment 2apiVersion: apps/v1 3metadata: 4 name: spring-boot-3-helm-demo 5 namespace: default 6spec: 7 replicas: 2 # 呢個會係固定數量 8 selector: 9 matchLabels: 10 app: spring-boot-3-helm-demo 11 template: 12 metadata: 13 labels: 14 app: spring-boot-3-helm-demo 15 spec: 16 terminationGracePeriodSeconds: 120 # 唔可以少過 Spring Boot 既 graceful shutdown 時限配置 17 containers: 18 - name: spring-boot-3-helm-demo 19 image: spring-boot-3-helm-demo:{{ .Values.appVersion | default .Chart.AppVersion }} 20 imagePullPolicy: Never # 只限本地測試用 21 securityContext: 22 readOnlyRootFilesystem: true # 安全需要 23 ports: 24 - containerPort: 8080 25 startupProbe: 26 httpGet: 27 path: /actuator/health/liveness 28 port: 8080 29 initialDelaySeconds: 5 30 periodSeconds: 5 31 failureThreshold: 30 32 timeoutSeconds: 5 33 livenessProbe: 34 httpGet: 35 path: /actuator/health/liveness 36 port: 8080 37 initialDelaySeconds: 5 38 periodSeconds: 10 39 failureThreshold: 3 40 timeoutSeconds: 5 41 readinessProbe: 42 httpGet: 43 path: /actuator/health/readiness 44 port: 8080 45 initialDelaySeconds: 5 46 periodSeconds: 10 47 failureThreshold: 3 48 timeoutSeconds: 5 49 resources: 50 limits: 51 cpu: 1000m 52 requests: 53 cpu: 500m 54 envFrom: 55 - configMapRef: 56 name: config-spring-boot-3-helm-demo 57 - secretRef: 58 name: secret-spring-boot-3-helm-demo 59 60# 用 emptyDir 方法,令 /tmp 可以寫入 61 volumeMounts: 62 - name: empty-tmp-dir 63 mountPath: /tmp 64 volumes: 65 - name: empty-tmp-dir 66 emptyDir: {}
註:
  • Docker Desktop 既 K8s 會喺 Docker Desktop 內置既 image repository 度 pull Docker image。
  • {{ .Values.appVersion | default .Chart.AppVersion }} 係 Helm 自帶既 Go template syntax。解釋:
    • 如果 values.yaml 或者 command 既 --set option 有 appVersion,就用佢,否則默認值係 Chart.yaml 裡面既 appVersion
Service.yml
1kind: Service 2apiVersion: v1 3metadata: 4 name: svc-spring-boot-3-helm-demo 5 namespace: default 6spec: 7 type: ClusterIP 8 selector: 9 app: spring-boot-3-helm-demo 10 ports: 11 - port: 8080 12 targetPort: 8080 13 protocol: TCP
HorizontalPodAutoscaler.yml
1kind: HorizontalPodAutoscaler 2apiVersion: autoscaling/v2 3metadata: 4 name: hpa-spring-boot-3-helm-demo 5 namespace: default 6spec: 7 scaleTargetRef: 8 apiVersion: apps/v1 9 kind: Deployment 10 name: spring-boot-3-helm-demo 11 minReplicas: 4 12 maxReplicas: 8 13 metrics: 14 - type: Resource 15 resource: 16 name: cpu 17 target: 18 type: Utilization 19 averageUtilization: 50 20 - type: Resource 21 resource: 22 name: memory 23 target: 24 type: Utilization 25 averageUtilization: 50
註:要用到 K8s HorizontalPodAutoscaler,我地需要喺 K8s 裡面運行 metrics server。
ConfigMap.yml
1kind: ConfigMap 2apiVersion: v1 3metadata: 4 name: config-spring-boot-3-helm-demo 5 namespace: default 6data: {{- toYaml .Values.container.env | nindent 2 }}
註:
  • {{- toYaml .Values.container.env | nindent 2 }} 係 Helm 自帶既 Go template syntax。解釋:
    • values.yaml 或者 command 既 --set option 既 container.env 轉換成 YAML 格式,並且每一個 element 都用 nindent function 加入 newline 以及 2 格 spaces 既 indentation。
Secret.yml
1kind: Secret 2apiVersion: v1 3metadata: 4 name: secret-spring-boot-3-helm-demo 5 namespace: default 6type: Opaque 7stringData: {{- toYaml .Values.secrets.raw | nindent 2 }} 8data: {{- toYaml .Values.secrets.base64 | nindent 2 }}
註:
  • stringData 裡面既 values 係正常 string。
  • data 裡面既 values 要係 Base64-encoded string。
    • 例如用 JavaScript 既 btoa("StrongPassword123") function call 就會得到 U3Ryb25nUGFzc3dvcmQxMjM=

3.2.2 Chart.yaml

1apiVersion: v2 2name: spring-boot-3-helm-demo 3type: application 4description: A Helm chart for spring-boot-3-helm-demo 5version: 1.2.3 6appVersion: "0.0.0"
解釋:
Field描述
apiVersion呢個係 Helm chart 既 API 版本,同 Helm CLI 版本(3.x)係兩樣野黎。
name用黎定義呢個 Helm chart 既名。如果用呢個 Helm chart 黎做多個 apps 既 K8s resource template,咁呢個就應該係個 generic 既名。呢個同 Helm release 名係兩樣野黎。
version用黎定義呢個 Helm chart 既版本。當 K8s YAML definitions、helpers 有變更,我地就要打包成一個新版本既 Helm chart。呢個同 app 既版本係兩樣野黎。
appVersion用黎定義呢個 Helm chart 裡面既 business app 既版本。如果用呢個 Helm chart 黎做單一 app 既 K8s resource template,咁喺 Chart.yaml 檔 set 呢個就合理。如果用呢個 Helm chart 黎做多個 apps 既 K8s resource template,咁就應該用 helm upgrade 既 --set option 會 override appVersion 既 value。通常會用 appVersion 黎改變個 app 既 K8s Deployment 用既 Docker image tag 版本。

3.2.3 values.yaml

values.yaml 既作用係提供 template variables,避免將太多野 hardcode 入啲 template 檔案裡面。
1container: 2 env: 3 logging.level.root: WARN 4 management.endpoints.web.exposure.include: "*" 5 server.shutdown: graceful 6 spring.lifecycle.timeout-per-shutdown-phase: 120s 7 spring.jpa.show-sql: "true" 8 spring.jpa.open-in-view: "false" 9 spring.jpa.hibernate.ddl-auto: update 10 11secrets: 12 raw: 13 spring.datasource.url: jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;TRACE_LEVEL_FILE=4;MODE=MSSQLServer;DATABASE_TO_UPPER=FALSE;CASE_INSENSITIVE_IDENTIFIERS=TRUE 14 spring.datasource.username: sa 15 base64: 16 spring.datasource.password: U3Ryb25nUGFzc3dvcmQxMjM=
注意:唔可以將 container.env 用 YAML 既 flattened key 寫法,一定要分成兩行帶有 indentation 咁寫。
# 錯誤示範 container.env: # Helm 唔畀我地咁寫 foo: bar

3.2.4 檢查 Helm chart 問題

我地可以喺開發 Helm chart 既時候(打包 Helm chart 之前)檢查 coding issues:
helm lint .
如果冇問題,Helm 會出以下既 log:
==> Linting . [INFO] Chart.yaml: icon is recommended 1 chart(s) linted, 0 chart(s) failed

3.2.5 註冊 Helm repository

執行以下 command:
helm repo add helm http://localhost:8081/repository/helm/
Helm 會用 Chart.yaml、template 檔案、values.yaml 等等去做檢查。

3.2.6 打包 Helm chart

喺 Helm chart 既 project root directory 執行以下 command:
helm package .

3.2.7 上載 Helm chart 到 Helm repository

執行以下 command:
curl -u admin:password -T spring-boot-3-helm-demo-1.2.3.tgz http://localhost:8081/repository/helm/

3.2.8 確認 Helm chart 喺 Helm repository

我地要先更新一下本地既 repository cache data。
helm repo update
列出所有 Helm charts:
helm search repo -l
驗證 spring-boot-3-helm-demo 既 Helm chart 存唔存在:
helm search repo -l spring-boot

3.3 測試 Helm release

測試既 command 同部署既 command 一樣,只需要加上 --dry-run --debug 既 options:
helm upgrade --install --atomic --timeout 10m --cleanup-on-fail spring-boot-3-helm-demo-1.5.0 helm/spring-boot-3-helm-demo --version 1.2.3 --set appVersion=1.5.0 --dry-run --debug

3.4 部署 Helm release

執行以下 command:
helm upgrade --install --atomic --timeout 10m --cleanup-on-fail spring-boot-3-helm-demo-1.5.0 helm/spring-boot-3-helm-demo --version 1.2.3 --set appVersion=1.5.0
註:
  • spring-boot-3-helm-demo-1.5.0 係個 release 既名。
  • --version 1.2.3 係個 Helm chart 既版本。
  • --set appVersion=1.5.0 係個 app 既版本。
等一陣之後,Helm 會出以下既 log:
1Release "spring-boot-3-helm-demo-1.5.0" does not exist. Installing it now. 2NAME: spring-boot-3-helm-demo-1.5.0 3LAST DEPLOYED: Tue Feb 25 19:03:34 2025 4NAMESPACE: default 5STATUS: deployed 6REVISION: 1 7TEST SUITE: None
再用 kubectl command 驗證所有 K8s 資源都被成功創建:
kubectl get deployment,svc,hpa,configmap,secret
就會出以下既結果:
1NAME READY UP-TO-DATE AVAILABLE AGE 2deployment.apps/spring-boot-3-helm-demo 4/4 4 4 2m46s 3 4NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE 5service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 39d 6service/svc-spring-boot-3-helm-demo ClusterIP 10.106.127.253 <none> 8080/TCP 2m46s 7 8NAME REFERENCE TARGETS MINPODS MAXPODS REPLICAS AGE 9horizontalpodautoscaler.autoscaling/hpa-spring-boot-3-helm-demo Deployment/spring-boot-3-helm-demo 0%/50%, <unknown>/50% 4 8 4 2m46s 10 11NAME DATA AGE 12configmap/config-spring-boot-3-helm-demo 7 2m46s 13configmap/kube-root-ca.crt 1 39d 14 15NAME TYPE DATA AGE 16secret/secret-spring-boot-3-helm-demo Opaque 3 2m46s 17secret/sh.helm.release.v1.spring-boot-3-helm-demo-1.5.0.v1 helm.sh/release.v1 1 2m46s

3.5 列出所有 Helm releases

執行以下 command:
helm list --all

3.6 刪除已部署既 Helm release

執行以下 command:
helm uninstall spring-boot-3-helm-demo-1.5.0
註:
  • Helm 3.x 用既係 3-way strategic merge patches。
    • In Helm 3, we now use a three-way strategic merge patch. Helm considers the old manifest, its live state, and the new manifest when generating a patch.
    • 除非明確加上 --keep-history option,否則默認係會完全刪除曬個 Helm release,唔留低 old manifest。

4 參考資料