|
| 1 | +# Kubernetes Clientset |
| 2 | + |
| 3 | +<!-- TOC --> |
| 4 | + |
| 5 | +- [Kubernetes Clientset](#kubernetes-clientset) |
| 6 | + - [资源类型 Scheme](#资源类型-scheme) |
| 7 | + - [types.go 文件](#typesgo-文件) |
| 8 | + - [zz_generated.deepcopy.go 文件](#zz_generateddeepcopygo-文件) |
| 9 | + - [register.go 文件](#registergo-文件) |
| 10 | + - [注册所有内置资源类型到 Scheme 对象](#注册所有内置资源类型到-scheme-对象) |
| 11 | + - [创建和使用 Kubernetes Clientset](#创建和使用-kubernetes-clientset) |
| 12 | + - [创建支持所有资源类型的全局 Clientset](#创建支持所有资源类型的全局-clientset) |
| 13 | + - [各资源类型的 Clientset](#各资源类型的-clientset) |
| 14 | + - [各资源类型的 RestFul 方法](#各资源类型的-restful-方法) |
| 15 | + - [使用资源类型的 Clientset 创建 Informer 和 Lister](#使用资源类型的-clientset-创建-informer-和-lister) |
| 16 | + - [使用 client-gen 工具生成资源类型的 Clientset](#使用-client-gen-工具生成资源类型的-clientset) |
| 17 | + - [使用 informer-gen 工具生成资源类型的 Informer](#使用-informer-gen-工具生成资源类型的-informer) |
| 18 | + - [使用 lister-gen 工具生成资源类型的 Lister](#使用-lister-gen-工具生成资源类型的-lister) |
| 19 | + |
| 20 | +<!-- /TOC --> |
| 21 | + |
| 22 | +## 资源类型 Scheme |
| 23 | + |
| 24 | +Clienset 和 apiserver 通信时,需要根据资源对象的类型生成 Resource URL、对 Wire-data 进行**编解码(序列化/反序列化)**。 |
| 25 | + |
| 26 | +资源类型的 Group、Version、Kind、go struct 定义、编解码(序列化/反序列化) 等内容构成了它的 `Scheme`。 |
| 27 | + |
| 28 | +K8S 内置资源类型的 Scheme 位于 `k8s.io/api/<group>/<version>` 目录下,以 `Deployment` 为例: |
| 29 | + |
| 30 | +``` bash |
| 31 | +$ pwd |
| 32 | +/Users/zhangjun/go/src/gitlab.4pd.io/pht3/aol/vendor/k8s.io/api/extensions/v1beta1 |
| 33 | + |
| 34 | +$ ls -l |
| 35 | +total 1048 |
| 36 | +-rw-r--r-- 1 zhangjun staff 642 Jan 22 15:16 doc.go |
| 37 | +-rw-r--r-- 1 zhangjun staff 308747 Jan 22 15:16 generated.pb.go |
| 38 | +-rw-r--r-- 1 zhangjun staff 49734 Jan 22 15:16 generated.proto |
| 39 | +-rw-r--r-- 1 zhangjun staff 2042 Jan 22 15:16 register.go |
| 40 | +-rw-r--r-- 1 zhangjun staff 69022 Jan 23 22:30 types.go |
| 41 | +-rw-r--r-- 1 zhangjun staff 47996 Jan 22 15:16 types_swagger_doc_generated.go |
| 42 | +-rw-r--r-- 1 zhangjun staff 41555 Jan 22 15:16 zz_generated.deepcopy.go |
| 43 | +``` |
| 44 | + |
| 45 | +可以暂时忽略无关的文件,我们主要分析 `types.go`、`zz_generated.deepcopy.go` 和 `register.go` 三个文件。 |
| 46 | + |
| 47 | +1. types.go:定义本 `<group>/<version>` 下所有的资源类型,即 codegen 注释; |
| 48 | +2. zz_generated.deepcopy.go:`deepcopy-gen` 工具自动创建的、包含各资源类型的 `DeepCopyObject()` 方法的文件; |
| 49 | +3. register.go:定义了 `AddToScheme()` 函数,用于将本 `<group>/<version>` 下的各资源类型注册到 Clientset 使用的 Scheme 对象中(k8s.io/client-go/kubernetes/scheme/); |
| 50 | + |
| 51 | +### types.go 文件 |
| 52 | + |
| 53 | +该文件定义了资源类型相关的 go struct 及 codegen 命令行工具使用的注释: |
| 54 | + |
| 55 | +``` go |
| 56 | +// 来源于:k8s.io/api/extensions/v1beta1/types.go |
| 57 | + |
| 58 | +// +genclient |
| 59 | +// +genclient:method=GetScale,verb=get,subresource=scale,result=Scale |
| 60 | +// +genclient:method=UpdateScale,verb=update,subresource=scale,input=Scale,result=Scale |
| 61 | +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object |
| 62 | + |
| 63 | +// DEPRECATED - This group version of Deployment is deprecated by apps/v1beta2/Deployment. See the release notes for |
| 64 | +// more information. |
| 65 | +// Deployment enables declarative updates for Pods and ReplicaSets. |
| 66 | +type Deployment struct { |
| 67 | + metav1.TypeMeta `json:",inline"` |
| 68 | + // Standard object metadata. |
| 69 | + // +optional |
| 70 | + metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"` |
| 71 | + |
| 72 | + // Specification of the desired behavior of the Deployment. |
| 73 | + // +optional |
| 74 | + Spec DeploymentSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"` |
| 75 | + |
| 76 | + // Most recently observed status of the Deployment. |
| 77 | + // +optional |
| 78 | + Status DeploymentStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"` |
| 79 | +} |
| 80 | + |
| 81 | +// DeploymentSpec is the specification of the desired behavior of the Deployment. |
| 82 | +type DeploymentSpec struct { |
| 83 | + Replicas *int32 `json:"replicas,omitempty" protobuf:"varint,1,opt,name=replicas"` |
| 84 | + Selector *metav1.LabelSelector `json:"selector,omitempty" protobuf:"bytes,2,opt,name=selector"` |
| 85 | + Template v1.PodTemplateSpec `json:"template" protobuf:"bytes,3,opt,name=template"` |
| 86 | + Strategy DeploymentStrategy `json:"strategy,omitempty" patchStrategy:"retainKeys" protobuf:"bytes,4,opt,name=strategy"` |
| 87 | + MinReadySeconds int32 `json:"minReadySeconds,omitempty" protobuf:"varint,5,opt,name=minReadySeconds"` |
| 88 | + RevisionHistoryLimit *int32 `json:"revisionHistoryLimit,omitempty" protobuf:"varint,6,opt,name=revisionHistoryLimit"` |
| 89 | + Paused bool `json:"paused,omitempty" protobuf:"varint,7,opt,name=paused"` |
| 90 | + RollbackTo *RollbackConfig `json:"rollbackTo,omitempty" protobuf:"bytes,8,opt,name=rollbackTo"` |
| 91 | + ProgressDeadlineSeconds *int32 `json:"progressDeadlineSeconds,omitempty" protobuf:"varint,9,opt,name=progressDeadlineSeconds"` |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +### zz_generated.deepcopy.go 文件 |
| 96 | + |
| 97 | +所有注册到 Scheme 的资源类型都要实现 `runtime.Object` 接口: |
| 98 | + |
| 99 | +``` go |
| 100 | +// 来源于:k8s.io/apimachinery/pkg/runtime/interfaces.go |
| 101 | +type Object interface { |
| 102 | + GetObjectKind() schema.ObjectKind |
| 103 | + DeepCopyObject() Object |
| 104 | +} |
| 105 | +``` |
| 106 | + |
| 107 | +K8S 各资源类型的 go struct 定义都嵌入了 `metav1.TypeMeta` 类型,而该类型实现了 `GetObjectKind()` 方法,故各资源类型只需要实现 `DeepCopyObject()` 方法。 |
| 108 | + |
| 109 | +我们不需要手动为各资源类型定义 `DeepCopyObject()` 方法,而是使用 `deepcopy-gen` 工具命令统一、自动地为各资源类型生成该方法。 |
| 110 | + |
| 111 | +deepcopy-gen 工具读取 `types.go` 文件中的 `+k8s:deepcopy-gen` 注释,如: |
| 112 | + |
| 113 | + // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object |
| 114 | + |
| 115 | +然后将生成的结果保存到 `zz_generated.deepcopy.go` 文件,以 `Deployment` 类型为例: |
| 116 | + |
| 117 | +``` go |
| 118 | +// 来源于:k8s.io/api/extensions/v1beta1/zz_generated.deepcopy.go |
| 119 | +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Deployment. |
| 120 | +func (in *Deployment) DeepCopy() *Deployment { |
| 121 | + if in == nil { |
| 122 | + return nil |
| 123 | + } |
| 124 | + out := new(Deployment) |
| 125 | + in.DeepCopyInto(out) |
| 126 | + return out |
| 127 | +} |
| 128 | + |
| 129 | +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. |
| 130 | +func (in *Deployment) DeepCopyObject() runtime.Object { |
| 131 | + if c := in.DeepCopy(); c != nil { |
| 132 | + return c |
| 133 | + } |
| 134 | + return nil |
| 135 | +} |
| 136 | +``` |
| 137 | + |
| 138 | +### register.go 文件 |
| 139 | + |
| 140 | +使用 `deepcopy-gen` 工具命令统一、自动地为各资源类型生成`DeepCopyObject()` 方法后,各资源类型就满足了 `runtime.Object` 接口,进而可以注册到 Scheme 中。 |
| 141 | + |
| 142 | +各 API Group 的各 Version 目录下,都有一个 `register.go` 文件,该文件对外提供的 `AddToScheme()` 方法用于将本 Group/Version 下的各资源类型注册到**传入**的 Scheme 对象中(k8s.io/client-go/kubernetes/scheme/register.go 中创建该 Scheme 对象),然后 Clientset 就可以使用它对各类型对象进行编解码了。 |
| 143 | + |
| 144 | +``` go |
| 145 | +// 来源于:k8s.io/api/extensions/v1beta1/register.go |
| 146 | +// 本 package 的 Group 名称 |
| 147 | +const GroupName = "extensions" |
| 148 | + |
| 149 | +// 注册时提供的 Group/Version 信息 |
| 150 | +// 一个 Group 目录下,有可能有多个 Version 的子目录 |
| 151 | +var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1beta1"} |
| 152 | + |
| 153 | +// Resource 实际就是资源类型的完整路径 <Group>/<Version>/<Plural>,如 extensions/v1beta1/deployments |
| 154 | +// Plural 是资源类型的复数形式 |
| 155 | +func Resource(resource string) schema.GroupResource { |
| 156 | + return SchemeGroupVersion.WithResource(resource).GroupResource() |
| 157 | +} |
| 158 | + |
| 159 | +var ( |
| 160 | + SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes) |
| 161 | + localSchemeBuilder = &SchemeBuilder |
| 162 | + // 对外暴露的 AddToScheme() 方法用于注册该 Group/Verion 下的所有资源类型 |
| 163 | + AddToScheme = localSchemeBuilder.AddToScheme |
| 164 | +) |
| 165 | + |
| 166 | +// 将本 Group/Version 下的所有资源类型注册到传入的 scheme |
| 167 | +func addKnownTypes(scheme *runtime.Scheme) error { |
| 168 | + scheme.AddKnownTypes(SchemeGroupVersion, |
| 169 | + &Deployment{}, |
| 170 | + &DeploymentList{}, |
| 171 | + ... |
| 172 | + ) |
| 173 | + // Add the watch version that applies |
| 174 | + metav1.AddToGroupVersion(scheme, SchemeGroupVersion) |
| 175 | + return nil |
| 176 | +} |
| 177 | +``` |
| 178 | + |
| 179 | +## 注册所有内置资源类型到 Scheme 对象 |
| 180 | + |
| 181 | +需要将 `k8s.io/api/<group>/<version>` 目录下的各资源类型注册到**全局 Scheme 对象**,这样 Clienset 才能识别和使用它们的对象。 |
| 182 | + |
| 183 | +client-go 的 `scheme package`(k8s.io/client-go/kubernetes/scheme/)定义了这个全局 `Scheme` 对象,并将各 `k8s.io/api/<Group>/<Version>` 目录下的资源类型注册到它上面。 |
| 184 | + |
| 185 | +Scheme 对象还被用于创建另外两个外部对象: |
| 186 | +1. 对资源类型对象进行编解码(序列化/反序列化)的工厂对象 `Codecs`,后续使用它配置 `rest.Config.NegotiatedSerializer`; |
| 187 | +2. 参数编解码对象 `ParameterCodec`,后续调用 RestFul 的方法时使用,如 `VersionedParams(&options, scheme.ParameterCodec)`。 |
| 188 | + |
| 189 | +``` go |
| 190 | +// 来源于 k8s.io/client-go/kubernetes/scheme/register.go |
| 191 | +// 新建一个 Scheme,后续所有 K8S 类型均添加到该 Scheme; |
| 192 | +var Scheme = runtime.NewScheme() |
| 193 | +// 为 Scheme 中的所有类型创建一个编解码工厂; |
| 194 | +var Codecs = serializer.NewCodecFactory(Scheme) |
| 195 | +// 为 Scheme 中的所有类型创建一个参数编解码工厂 |
| 196 | +var ParameterCodec = runtime.NewParameterCodec(Scheme) |
| 197 | + |
| 198 | +// 将各 `k8s.io/api/<Group>/<Version>` 目录下的资源类型的 AddToScheme() 方法注册到 SchemeBuilder 中 |
| 199 | +var localSchemeBuilder = runtime.SchemeBuilder{ |
| 200 | + ... |
| 201 | + extensionsv1beta1.AddToScheme, |
| 202 | + ... |
| 203 | +} |
| 204 | +var AddToScheme = localSchemeBuilder.AddToScheme |
| 205 | + |
| 206 | +func init() { |
| 207 | + v1.AddToGroupVersion(Scheme, schema.GroupVersion{Version: "v1"}) |
| 208 | + // 调用 SchemeBuilder 中各资源对象的 AddToScheme() 方法,将它们注册到到 Scheme 对象。 |
| 209 | + utilruntime.Must(AddToScheme(Scheme)) |
| 210 | +} |
| 211 | +``` |
| 212 | + |
| 213 | +## 创建和使用 Kubernetes Clientset |
| 214 | + |
| 215 | +经过前面的铺垫分析后,我们开始分析 Kubernetes Clientset 的创建过程。 |
| 216 | + |
| 217 | +先从使用者的角度看看如何创建和使用 Kubernetes Clientset: |
| 218 | + |
| 219 | +``` go |
| 220 | +var err error |
| 221 | +var config *rest.Config |
| 222 | +// 使用 ServiceAccount 创建集群配置 |
| 223 | +if config, err = rest.InClusterConfig(); err != nil { |
| 224 | + // 使用 kubeConfig 指向的配置文件创建集群配置 |
| 225 | + if config, err = clientcmd.BuildConfigFromFlags("", *kubeConfig); err != nil { |
| 226 | + panic(err.Error()) |
| 227 | + } |
| 228 | +} |
| 229 | + |
| 230 | +// 创建 clientset |
| 231 | +clientset, err = kubernetes.NewForConfig(config) |
| 232 | +if err != nil { |
| 233 | + panic(err.Error()) |
| 234 | +} |
| 235 | + |
| 236 | +// 使用 clienset 创建一个 Deploy |
| 237 | +deploy, err := c.kubeclientset.ExtensionsV1beta1().Deployments(aolDeploy.ObjectMeta.Namespace).Create(myDeploy) |
| 238 | +``` |
| 239 | + |
| 240 | +1. 使用 Kubeconfig 文件或 ServiceAccount 创建 Kubernetes 的 RestFul 配置参数; |
| 241 | +2. 使用 Kubernetes 的 RestFul 配置参数,创建 Clientset; |
| 242 | +3. 调用 Clientset 的方法对资源对象进行 CRUD; |
| 243 | + |
| 244 | +## 创建支持所有资源类型的全局 Clientset |
| 245 | + |
| 246 | +`k8s.io/client-go/kubernetes/clientset.go` 文件中创建的 Clientset 实际上是对各资源类型的 Clientset 做了一次封装: |
| 247 | + |
| 248 | +1. 调用各资源类型的 NewForConfig() 函数创建对应的 Clientset; |
| 249 | +2. 后续可以使用 Clientset.<Group><Version>(),如 Clientset.ExtensionsV1beta1() 来调用具体资源类型的 Clientset; |
| 250 | + |
| 251 | +``` go |
| 252 | +// 来源于 k8s.io/client-go/kubernetes/clientset.go |
| 253 | +// 传入的 rest.Config 包含 apiserver 服务器和认证信息 |
| 254 | +func NewForConfig(c *rest.Config) (*Clientset, error) { |
| 255 | + configShallowCopy := *c |
| 256 | + ... |
| 257 | + // 透传 rest.COnfig,调用具体分组和版本的资源类型的 ClientSet 构造函数 |
| 258 | + cs.extensionsV1beta1, err = extensionsv1beta1.NewForConfig(&configShallowCopy) |
| 259 | + if err != nil { |
| 260 | + return nil, err |
| 261 | + } |
| 262 | + ... |
| 263 | + cs.DiscoveryClient, err = discovery.NewDiscoveryClientForConfig(&configShallowCopy) |
| 264 | + if err != nil { |
| 265 | + return nil, err |
| 266 | + } |
| 267 | + return &cs, nil |
| 268 | +} |
| 269 | + |
| 270 | +// ExtensionsV1beta1 retrieves the ExtensionsV1beta1Client |
| 271 | +func (c *Clientset) ExtensionsV1beta1() extensionsv1beta1.ExtensionsV1beta1Interface { |
| 272 | + return c.extensionsV1beta1 |
| 273 | +} |
| 274 | +``` |
| 275 | + |
| 276 | +## 各资源类型的 Clientset |
| 277 | + |
| 278 | +各资源类型的 Clientset 定义位于 `k8s.io/client-go/kubernetes/typed/<group>/<version>/<plug>_client.go` 文件中,如 |
| 279 | +`k8s.io/client-go/kubernetes/typed/extensions/v1beta1/extensions_client.go`。 |
| 280 | + |
| 281 | +比较关键的是 `setConfigDefaults()` 函数,它负责为 Clientset 配置参数: |
| 282 | +1. 资源对象的 GroupVersion; |
| 283 | +2. 资源对象的 root path; |
| 284 | +3. 对 wired data 进行编解码(序列化/反序列化)的 `NegotiatedSerializer`,使用的 `scheme.Codecs` 为前面介绍过的 `scheme package`; |
| 285 | + |
| 286 | +RESTClient 根据配置的 root path 和 GroupVersion,构造 Resource 地址(格式为 `/apis/<group>/<version>/<name>`)。 |
| 287 | + |
| 288 | +``` go |
| 289 | +// 来源于 k8s.io/client-go/kubernetes/typed/extensions/v1beta1/extensions_client.go |
| 290 | +// 传入的 rest.Config 包含 apiserver 服务器和认证信息 |
| 291 | +func NewForConfig(c *rest.Config) (*ExtensionsV1beta1Client, error) { |
| 292 | + config := *c |
| 293 | + // 为 rest.Config 设置资源对象相关的参数 |
| 294 | + if err := setConfigDefaults(&config); err != nil { |
| 295 | + return nil, err |
| 296 | + } |
| 297 | + // 创建 ExtensionsV1beta1 的 RestClient |
| 298 | + client, err := rest.RESTClientFor(&config) |
| 299 | + if err != nil { |
| 300 | + return nil, err |
| 301 | + } |
| 302 | + return &ExtensionsV1beta1Client{client}, nil |
| 303 | +} |
| 304 | + |
| 305 | +func setConfigDefaults(config *rest.Config) error { |
| 306 | + // 资源对象的 GroupVersion |
| 307 | + gv := v1beta1.SchemeGroupVersion |
| 308 | + config.GroupVersion = &gv |
| 309 | + // 资源对象的 root path |
| 310 | + config.APIPath = "/apis" |
| 311 | + // 使用注册的资源类型 Schema 对请求和响应进行编解码 |
| 312 | + // scheme 为前面分析过的 k8s.io/client-go/kubernetes/scheme package |
| 313 | + config.NegotiatedSerializer = serializer.DirectCodecFactory{CodecFactory: scheme.Codecs} |
| 314 | + |
| 315 | + if config.UserAgent == "" { |
| 316 | + config.UserAgent = rest.DefaultKubernetesUserAgent() |
| 317 | + } |
| 318 | + |
| 319 | + return nil |
| 320 | +} |
| 321 | + |
| 322 | +func (c *ExtensionsV1beta1Client) Deployments(namespace string) DeploymentInterface { |
| 323 | + return newDeployments(c, namespace) |
| 324 | +} |
| 325 | +``` |
| 326 | + |
| 327 | +## 各资源类型的 RestFul 方法 |
| 328 | + |
| 329 | +使用各资源类型的 Clientset 创建资源类型的 RestFul 方法,参数的编解码工厂 `scheme.ParameterCodec` 来源于前面介绍的 `scheme package` 中。 |
| 330 | + |
| 331 | +``` go |
| 332 | +// 来源于 k8s.io/client-go/kubernetes/typed/extensions/v1beta1/deployment.go |
| 333 | +// newDeployments returns a Deployments |
| 334 | +func newDeployments(c *ExtensionsV1beta1Client, namespace string) *deployments { |
| 335 | + return &deployments{ |
| 336 | + client: c.RESTClient(), |
| 337 | + ns: namespace, |
| 338 | + } |
| 339 | +} |
| 340 | + |
| 341 | +// Get takes name of the deployment, and returns the corresponding deployment object, and an error if there is any. |
| 342 | +func (c *deployments) Get(name string, options v1.GetOptions) (result *v1beta1.Deployment, err error) { |
| 343 | + result = &v1beta1.Deployment{} |
| 344 | + // 发起实际的 RestFul 请求; |
| 345 | + err = c.client.Get(). |
| 346 | + Namespace(c.ns). |
| 347 | + Resource("deployments"). |
| 348 | + Name(name). |
| 349 | + VersionedParams(&options, scheme.ParameterCodec). |
| 350 | + Do(). |
| 351 | + Into(result) |
| 352 | + return |
| 353 | +} |
| 354 | +``` |
| 355 | + |
| 356 | +## 使用资源类型的 Clientset 创建 Informer 和 Lister |
| 357 | +## 使用 client-gen 工具生成资源类型的 Clientset |
| 358 | +## 使用 informer-gen 工具生成资源类型的 Informer |
| 359 | +## 使用 lister-gen 工具生成资源类型的 Lister |
0 commit comments