近期的一个工作项是通过使用openkruise中的daemonset来替代原生daemonset,目标是能够借用kruise daemonset支持的热升级能力(Surging)以及灰度控制能力(selector/partition)来提升daemonset使用的稳定性,满足业务需求。
接管的操作流程暂且不表,这里说一下遇到的一个问题:如何让kustomize支持自定义CR的SMP(strategic merge patch)。这个问题的解决还走了一些弯路,做一下记录。
原始玩法
我们通过git来维护daemonset定义,基于kustomize的base/overlays的玩法来实现基础配置的共享+集群个性化配置的支持。
配置的示例如下:
# tree daemonset_demodaemonset_demo├── base│ ├── daemonset.yaml│ └── kustomization.yaml└── overlays└── cluster1├── daemonset_site.yaml└── kustomization.yaml
文件内容如下:
# cat daemonset_demo/base/daemonset.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: base-image-ds
namespace: default
labels:
agent-name: base-image-ds
spec:
minReadySeconds: 300
selector:
matchLabels:
component: base-image-ds
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1%
template:
metadata:
labels:
component: base-image-ds
spec:
hostNetwork: true
tolerations:
- operator: Exists
restartPolicy: Always
initContainers:
- name: base-image-ds-1
image: image1
command:
- /bin/true
containers:
- name: main
image: image1
command:
- cat
# cat daemonset_demo/base/kustomization.yaml
resources:
- daemonset.yaml
# cat daemonset_demo/overlays/cluster1/daemonset_site.yaml
apiVersion: apps/v1
kind: DaemonSet
metadata:
name: base-image-ds
namespace: default
spec:
template:
metadata:
labels:
site:
spec:
initContainers:
- name: base-image-ds-new
image: image-new
command:
- /bin/true
# cat daemonset_demo/overlays/cluster1/kustomization.yaml
bases:
- ../../base
patches:
- daemonset_site.yaml
# kustomize build daemonset_demo/overlays/cluster1/ > ori.yaml
基于上述玩法,通过kruise daemonset接管cluster1之后,配置修改如下
# cat daemonset_demo/base-kruise/daemonset.yaml
apiVersion: apps.kruise.io/v1alpha1 # 修改
kind: DaemonSet
metadata:
name: base-image-ds
namespace: default
annotations:
daemonset.kruise.io/ignore-unscheduable: "true" # 修改
daemonset.kruise.io/ignore-not-ready: "true" # 修改
labels:
daemonset.kruise.io/is-first-deployed: "false" # 修改
agent-name: base-image-ds
spec:
minReadySeconds: 300
selector:
matchLabels:
component: base-image-ds
updateStrategy:
type: RollingUpdate
rollingUpdate:
maxUnavailable: 1%
template:
metadata:
labels:
component: base-image-ds
spec:
hostNetwork: true
tolerations:
- operator: Exists
restartPolicy: Always
initContainers:
- name: base-image-ds-1
image: image1
command:
- /bin/true
containers:
- name: main
image: image1
command:
- cat
# cat daemonset_demo/base-kruise/kustomization.yaml
resources:
- daemonset.yaml
#cat daemonset_demo/overlays/cluster1/daemonset_site.yaml
apiVersion: apps.kruise.io/v1alpha1 # 修改
kind: DaemonSet
metadata:
name: base-image-ds
namespace: default
spec:
template:
metadata:
labels:
site:
spec:
initContainers:
- name: base-image-ds-new
image: image-new
command:
- /bin/true
# cat daemonset_demo/overlays/cluster1/kustomization.yaml
bases:
- ../../base-kruise # 修改
patches:
- daemonset_site.yaml
# kustomize build daemonset_demo/overlays/cluster1/ > new.yaml
# tree daemonset_demo
daemonset_demo
├── base
│ ├── daemonset.yaml
│ └── kustomization.yaml
├── base-kruise
│ ├── daemonset.yaml
│ └── kustomization.yaml
└── overlays
└── cluster1
├── daemonset_site.yaml
└── kustomization.yaml
接管之后,对比ori.yaml和new.yaml,会发现两者并不一致
问题根因
根因在于kustomize自带了原始K8S核心Api对象的types信息,原生对象支持SMP,kustomize就能识别。而自定义类型CRD,kustomize并不能感知到其真实的type详情,只能使用JsonPatch来做patch。JsonPatch的显著的缺点,就是无法在一个list(例如上图中的initContainers list)中,根据定义的key来进行识别,针对性的进行patch。
原生DaemonSet支持name作为key来进行SMP
解决方案
考虑到用户已经习惯了kustomize的玩法,大幅修改原来的玩法对用户不太友好,也对之前的配套建设造成冲击。所以最小化的解决方案肯定还是基于kustomize来进行开发、改造。
由于对kustomize的架构不熟悉,先后摸索了三个方案,最终采用第三个方案:
方案1)【放弃】使用JsonPatch来替代StrategicMergePatch
这条路对用户并不友好,以patch修改list为例,JsonPatch需要指定修改list中指定对象的index,这个非常不人性且容易出错,并且对现有的overlays修改较大,最终放弃!
方案2)【不可行】尝试通过config+transformer
kustomize提供了generator和transformer机制,可以允许我们指定告知CR中某些部分是label,哪些是annotation,哪些字段是引用了Pod之类的,类似于argo-rollouts的做法。
后来发现,如果要识别kruise daemonset的CR,我们需要能告知例如spec/template对应的是k8s中的PodTemplate结构,而这种kustomize并没有原生识别,它能识别的如Pod,Deployment等,并不适用于本CR的场景,所以无法描述清楚我们CR的结构,kustomize也就无法做到对其支持SMP,不可行!
方案3)【最终方案】使用transformer + kustomize go plugin
这个方案来自于已有的issue,通过这种变通的方式,通过plugin的方式让kustomize直接感知到了我们指定的GroupVersionKind的真实结构,并由于结构本身支持SMP,完美的实现了对CR的SMP支持。选中此方案!
具体操作步骤记录如下
a) 开发go plugin用于注册CRD的GVK
基于kustomize源码库,在sigs.k8s.io/kustomize/pkg/plugin/下增加apps.kruise.io/v1alpha1/daemonsetregister/DaemonSetRegister.go
//go:generate go run sigs.k8s.io/kustomize/v3/cmd/pluginator
package main
import (
"k8s.io/client-go/kubernetes/scheme"
"sigs.k8s.io/kustomize/v3/pkg/ifc"
"sigs.k8s.io/kustomize/v3/pkg/resmap"
"sigs.k8s.io/kustomize/v3/pkg/types"
v1alpha1 "sigs.k8s.io/kustomize/v3/pkg/apis/kruiseapps/v1alpha1"
)
type plugin struct {
rf *resmap.Factory
types.ObjectMeta `json:"metadata,omitempty" yaml:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
}
//noinspection GoUnusedGlobalVariable
var KustomizePlugin plugin
func (p *plugin) Config(ldr ifc.Loader, rf *resmap.Factory, config []byte) error {
return v1alpha1.SchemeBuilder.AddToScheme(scheme.Scheme)
}
func (p *plugin) Transform(m resmap.ResMap) error {
return nil
}
如上,这里把kruise DaemonSet的types信息迁移到了sigs.k8s.io/kustomize/v3/pkg/apis/kruiseapps/,以便识别(直接引用github.com/openkruise/kruise对应路径时遇到了依赖相关的问题,这里简化处理)
b) 生成golang plugin so文件
基于上述代码,生成kusomize plugin的文件
go build -buildmode=plugin -o DaemonSetRegister.so src/sigs.k8s.io/kustomize/plugin/apps.kruise.io/v1alpha1/daemonsetregister/DaemonSetRegister.go
拷贝至kustomize的plugin搜索路径即可。
搜索路径有如下两种:
- 默认$HOME/.config/kustomize/plugin
- 如果指定了环境变量XDG_CONFIG_HOME,则路径为$XDG_CONFIG_HOME/kustomize/plugin
这里我直接拷贝DaemonSetRegister.so到$HOME/.config/kustomize/plugin下(路径不存在则创建)。
注意:kustomize启用插件需要指定``--enable_alpha_plugins选项。
c) 修改kustomization相关配置
将原来的base/overlays配置做如下修改
base-kruise下新增transformer.yaml,用来引入plugin
apiVersion: apps.kruise.io/v1alpha1 kind: DaemonSetRegister metadata: name: daemonsetregisterbase-kruise的kustomization.yaml中引入transformer.yaml ```yaml resources:
- daemonset.yaml
transformers:
- transformer.yaml
修改完成后查看文件和差异如下yamltree daemonset_demo
daemonset_demo ├── base │ ├── daemonset.yaml │ └── kustomization.yaml ├── base-kruise │ ├── daemonset.yaml │ ├── kustomization.yaml │ └── transformer.yaml └── overlays └── cluster1├── daemonset_site.yaml └── kustomization.yaml
kustomize build —enable_alpha_plugins daemonset_demo/overlays/cluster1/ > final.yaml
d) 平台支持且开启—enable_alpha_plugins
上面的plugin还仅仅是放在本地,验证可行。真正管理DaemonSet时需要将对应版本的kusomize和plugin部署到平台镜像中。(如果kustmoize和plugin镜像不一致,还可能出错。。)
另外,需要将原始的kustomize build xxx命令,改成kustomize build --enable_alpha_plugins xxx
参考:
- https://github.com/kubernetes-sigs/kustomize/issues/1510
- https://argoproj.github.io/argo-rollouts/features/kustomize/
- https://kubernetes-sigs.github.io/kustomize/guides/plugins/
- https://github.com/kubernetes-sigs/kustomize/tree/master/examples/transformerconfigs
- https://kubernetes-sigs.github.io/kustomize/api-reference/kustomization/
- https://github.com/kubernetes/community/blob/master/contributors/devel/sig-api-machinery/strategic-merge-patch.md
