在使用Helm过程中, 经常会遇到编排需要兼容不同K8S版本的问题. 考虑如下场景:
-
以前编写的Deployment资源, 其apiVersion为 apps/v1beta1, 但后来新的版本中已经改为 apps/v1,希望能兼容
-
在K8S 1.11以前, 默认CRD既不支持subResources, 也不能够通过 UpdateStatus 更新状态, 必须使用 Update. 而在这之后, 必须使用 UpdateStatus 才能更新状态.
Helm 内置了一系列内部对象,可以针对这些情况进行编排.
Deployment兼容多版本K8S
针对以上第一个问题, 我们可以直接在 _helpers.tpl 中加入以下内容:
{{/*
Define apiVersion for Deployment
*/}}
{{- define "deployApiVersion" -}}
{{- if .Capabilities.APIVersions.Has "apps/v1beta1/Deployment" -}}
apps/v1beta1
{{- else -}}
apps/v1
{{- end -}}
{{- end -}}
这里判断这套K8S是否具备 apps/v1beta/Deployment , 如果有, 使用 apps/v1 ,否则就是旧版本的 apps/v1beta1
然后, Deployment引用这一段即可:
kind: Deployment
apiVersion: {{ include "deployApiVersion" . }}
检测配置 disableSubresources 是否开启
经过查询文档, disableSubresources 默认开启是在 1.11 版本, 因此我们可以简单一点, 只判断小版本是否大于等于 "11", 当然这里没有判断大版本.
disableSubresources: {{ if ge .Capabilities.KubeVersion.Minor "11" }}false{{ else }}true{{ end }}
Capabilities 实现
我们可能在某些场景下, 希望自己的程序能够直接去判断我们对接的K8S集群有哪些能力. 这部分实现我们可以参考 helm 源码. 在 pkg/action/action.go
中揭示了其实现方式:
// capabilities builds a Capabilities from discovery information.
func (c *Configuration) getCapabilities() (*chartutil.Capabilities, error) {
if c.Capabilities != nil {
return c.Capabilities, nil
}
dc, err := c.RESTClientGetter.ToDiscoveryClient()
if err != nil {
return nil, errors.Wrap(err, "could not get Kubernetes discovery client")
}
// force a discovery cache invalidation to always fetch the latest server version/capabilities.
dc.Invalidate()
kubeVersion, err := dc.ServerVersion()
if err != nil {
return nil, errors.Wrap(err, "could not get server version from Kubernetes")
}
// Issue #6361:
// Client-Go emits an error when an API service is registered but unimplemented.
// We trap that error here and print a warning. But since the discovery client continues
// building the API object, it is correctly populated with all valid APIs.
// See https://github.com/kubernetes/kubernetes/issues/72051#issuecomment-521157642
apiVersions, err := GetVersionSet(dc)
if err != nil {
if discovery.IsGroupDiscoveryFailedError(err) {
c.Log("WARNING: The Kubernetes server has an orphaned API service. Server reports: %s", err)
c.Log("WARNING: To fix this, kubectl delete apiservice <service-name>")
} else {
return nil, errors.Wrap(err, "could not get apiVersions from Kubernetes")
}
}
c.Capabilities = &chartutil.Capabilities{
APIVersions: apiVersions,
KubeVersion: chartutil.KubeVersion{
Version: kubeVersion.GitVersion,
Major: kubeVersion.Major,
Minor: kubeVersion.Minor,
},
}
return c.Capabilities, nil
}
dry-run 没问题的chart一定能够在集群上部署吗?
在解决第一个问题时, 我事先通过 -dry-run 测试我写出的chart, 结果安装到集群却失败了. 其实, 在Helm Chart进行dryrun时, 是不会与集群进行交互的.
在 helm 源码中, 它的解释是编排chart的作者的期望可能是连接到集群,但用户却不一定如此期望:
// A `helm template` or `helm install --dry-run` should not talk to the remote cluster.
// It will break in interesting and exotic ways because other data (e.g. discovery)
// is mocked. It is not up to the template author to decide when the user wants to
// connect to the cluster. So when the user says to dry run, respect the user's
// wishes and do not connect to the cluster.
if !dryRun && c.RESTClientGetter != nil {
rest, err := c.RESTClientGetter.ToRESTConfig()
if err != nil {
return hs, b, "", err
}
files, err2 = engine.RenderWithClient(ch, values, rest)
} else {
files, err2 = engine.Render(ch, values)
}