Skip to content

Commit 4efd64f

Browse files
author
Zhang Jun
committed
add codegen
1 parent 55eb288 commit 4efd64f

File tree

1 file changed

+359
-0
lines changed

1 file changed

+359
-0
lines changed
+359
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,359 @@
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

Comments
 (0)