如何在 Helm Chart 中兼容不同的 Kubernetes 版本?
隨著 Kubernetes 的版本不斷迭代發布,很多 Helm Chart 包壓根跟不上更新的進度,導致在使用較新版本的 Kubernetes 的時候很多 Helm Chart 包不兼容,所以我們在開發 Helm Chart 包的時候有必要考慮到對不同版本的 Kubernetes 進行兼容。
要實現對不同版本的兼容核心就是利用 Helm Chart 模板提供的內置對象 Capabilities,該對象提供了關于 Kubernetes 集群支持功能的信息,包括如下特性:
- Capabilities.APIVersions 獲取集群版本集合
- Capabilities.APIVersions.Has $version 判斷集群中的某個版本 (e.g., batch/v1) 或是資源 (e.g., apps/v1/Deployment) 是否可用
- Capabilities.KubeVersion 和 Capabilities.KubeVersion.Version 可以獲取 Kubernetes 版本號
- Capabilities.KubeVersion.Major 獲取 Kubernetes 的主版本
- Capabilities.KubeVersion.Minor 獲取 Kubernetes 的次版本
- Capabilities.HelmVersion 包含 Helm 版本詳細信息的對象,和 helm version 的輸出一致
- Capabilities.HelmVersion.Version 是當前 Helm 版本的語義格式
- Capabilities.HelmVersion.GitCommit Helm 的 git sha1 值
- Capabilities.HelmVersion.GitTreeState 是 Helm git 樹的狀態
- Capabilities.HelmVersion.GoVersion 使用的 Go 編譯器版本
利用上面的幾個對象我們可以判斷資源對象需要使用的 API 版本或者屬性,下面我們以 Ingress 資源對象為例進行說明。
Kubernetes 在 1.19 版本為 Ingress 資源引入了一個新的 API:networking.k8s.io/v1,這與之前的 networking.k8s.io/v1beta1 beta 版本使用方式基本一致,但是和前面的 extensions/v1beta1 這個版本在使用上有很大的不同,資源對象的屬性上有一定的區別,所以要兼容不同的版本,我們就需要對模板中的 Ingress 對象做兼容處理。
新版本的資源對象格式如下所示:
- apiVersion: networking.k8s.io/v1
- kind: Ingress
- metadata:
- name: minimal-ingress
- annotations:
- nginx.ingress.kubernetes.io/rewrite-target: /
- spec:
- rules:
- - http:
- paths:
- - path: /testpath
- pathType: Prefix
- backend:
- service:
- name: test
- port:
- number: 80
而舊版本的資源對象格式如下:
- apiVersion: extensions/v1beta1
- kind: Ingress
- metadata:
- name: minimal-ingress
- annotations:
- nginx.ingress.kubernetes.io/rewrite-target: /
- spec:
- rules:
- - http:
- paths:
- - path: /testpath
- backend:
- serviceName: test
- servicePort: 80
具體使用哪種格式的資源對象需要依賴我們的集群版本,首先我們在 Chart 包的 _helpers.tpl 文件中添加幾個用于判斷集群版本或 API 的命名模板:
- {{/* Allow KubeVersion to be overridden. */}}
- {{- define "ydzs.kubeVersion" -}}
- {{- default .Capabilities.KubeVersion.Version .Values.kubeVersionOverride -}}
- {{- end -
- {{/* Get Ingress API Version */}}
- {{- define "ydzs.ingress.apiVersion" -}}
- {{- if and (.Capabilities.APIVersions.Has "networking.k8s.io/v1") (semverCompare ">= 1.19-0" (include "ydzs.kubeVersion" .)) -}}
- {{- print "networking.k8s.io/v1" -}}
- {{- else if .Capabilities.APIVersions.Has "networking.k8s.io/v1beta1" -}}
- {{- print "networking.k8s.io/v1beta1" -}}
- {{- else -}}
- {{- print "extensions/v1beta1" -}}
- {{- end -}}
- {{- end -}}
- {{/* Check Ingress stability */}}
- {{- define "ydzs.ingress.isStable" -}}
- {{- eq (include "ydzs.ingress.apiVersion" .) "networking.k8s.io/v1" -}}
- {{- end -}}
- {{/* Check Ingress supports pathType */}}
- {{/* pathType was added to networking.k8s.io/v1beta1 in Kubernetes 1.18 */}}
- {{- define "ydzs.ingress.supportsPathType" -}}
- {{- or (eq (include "ydzs.ingress.isStable" .) "true") (and (eq (include "ydzs.ingress.apiVersion" .) "networking.k8s.io/v1beta1") (semverCompare ">= 1.18-0" (include "ydzs.kubeVersion" .))) -}}
- {{- end -}}
上面我們通過 .Capabilities.APIVersions.Has 來判斷我們應該使用的 APIVersion,如果版本為 networking.k8s.io/v1,則定義為 isStable,此外還根據版本來判斷是否需要支持 pathType 屬性,然后在 Ingress 對象模板中就可以使用上面定義的命名模板來決定應該使用哪些屬性,如下 ingress.yaml 文件所示:
- {{- $apiIsStable := eq (include "ydzs.ingress.isStable" .) "true" -}}
- {{- $ingressSupportsPathType := eq (include "ydzs.ingress.supportsPathType" .) "true" -}}
- {{- $ingressClass := index .Values "ingress-nginx" "controller" "ingressClass" }}
- apiVersion: {{ include "ydzs.ingress.apiVersion" . }}
- kind: Ingress
- metadata:
- name: portal-ingress
- annotations:
- {{- if $ingressClass }}
- kubernetes.io/ingress.class: {{ $ingressClass }}
- {{- end }}
- nginx.ingress.kubernetes.io/proxy-connect-timeout: "120"
- nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
- nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
- nginx.ingress.kubernetes.io/ssl-redirect: "false"
- labels:
- {{- include "ydzs.labels" . | nindent 4 }}
- spec:
- rules:
- {{- if eq .Values.endpoint.type "FQDN" }}
- - host: {{ required ".Values.endpoint.FQDN is required for FQDN" .Values.endpoint.FQDN }}
- http:
- {{- else }}
- - http:
- {{- end }}
- paths:
- - path: /
- {{- if $ingressSupportsPathType }}
- pathType: Prefix
- {{- end }}
- backend:
- {{- if $apiIsStable }}
- service:
- name: portal
- port:
- number: 80
- {{- else }}
- serviceName: portal
- servicePort: 80
- {{- end }}
在 Ingress 模板中使用命名模板中的變量來判斷應該使用哪些屬性,這樣我們定義的這個 Chart 模板就可以兼容 Kubernetes 的不同版本了,如果還有其他版本之間的差異,我們也可以分別判斷進行定義即可,對于其他的資源對象,比如 Deployment 也可以用同樣的方式進行兼容。