As Kubernetes releases iterate, many Helm Chart packages simply cannot keep up with the updates, resulting in many Helm Chart packages being incompatible when using newer versions of Kubernetes, so it is necessary to consider compatibility with different versions of Kubernetes when developing Helm Chart packages.

The core of achieving compatibility with different versions is to make use of the built-in object Capabilities provided by the Helm Chart template, which provides information about Kubernetes cluster support features, including the following features.

  • Capabilities.APIVersions Get the cluster version set
  • Capabilities.APIVersions.Has $version determines if a version (e.g., batch/v1) or resource (e.g., apps/v1/Deployment) is available in the cluster
  • Capabilities.KubeVersion and Capabilities.KubeVersion.Version to get the Kubernetes version number
  • Capabilities.KubeVersion.Major Get the major version of Kubernetes
  • Capabilities.KubeVersion.Minor Get the sub-version of Kubernetes
  • Capabilities.HelmVersion object containing Helm version details, consistent with the output of helm version
  • Capabilities.HelmVersion.Version is the semantic format of the current Helm version
  • Capabilities.HelmVersion.GitCommit Helm’s git sha1 value
  • Capabilities.HelmVersion.GitTreeState is the state of the Helm git tree
  • Capabilities.HelmVersion.GoVersion The version of the Go compiler used

Using the above objects we can determine the API version or properties that the resource object needs to use, and we will use the Ingress resource object as an example.

Kubernetes introduced a new API for Ingress resources in version 1.19: networking.k8s.io/v1, which is basically the same as the previous networking.k8s.io/v1beta1 beta version, but is very different from the previous extensions/ v1beta1 is very different from the previous extensions/v1beta1 version, there are some differences in the properties of the resource objects, so to be compatible with the different versions, we need to do compatibility with the Ingress object in the template.

The format of the resource objects in the new version is shown below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
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

And the old version of the resource object format is as follows.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
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

The exact format of the resource object to use depends on our cluster version, so first we add a few named templates to the _helpers.tpl file in the Chart package to determine the cluster version or API

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
{{/* 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 to determine which APIVersion we should use, and if the version is networking.k8s.io/v1, then isStable is defined, in addition to determining whether the pathType property needs to be supported based on the version. The naming template defined above can then be used in the Ingress object template to determine which properties should be used, as shown in the ingress.yaml file below.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
{{- $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 }}

The Ingress template uses variables in the named template to determine which properties should be used, so that the Chart template we define is compatible with different versions of Kubernetes, and if there are differences between other versions, we can define them separately.