k8s使用声明式API的设计,那么将一个YAML文件提交给k8s之后,是如何创建出一个API对象的? 首先了解一下声明式API的设计。
声明式API的设计
在k8s中,一个API对象在Etcd中的完整资源路径是由:Group(API组)、Version(API版本)和Resource(API资源类型) 三个部分组成的。
一个k8s中API对象可以用一个树形结构表示:

例如下面的YAML:
apiVersion: batch/v2alpha1
kind: CronJob
...CronJob是资源类型,batch是组,v2alpha1是版本。
提交之后,k8s就会把这个YAML描述的内容转化为一个CronJob对象。
k8s如何查找对象
1. 匹配API对象的组
首先要明确的是,k8s中的核心对象,例如 Pod、Node等是不需要组的(group为""),这些API对象会直接在/api这个层级往下找。
非核心对象必须在/apis层级往下找对应的组。例如组为batch,那么就要找到/apis/batch。
2. 匹配API对象版本号
找到了组之后,就继续查找匹配版本号,这里版本号是v2alpha1,那么就找到/apis/batch/v2alpha1。
同一对象可以有多个版本,这也是k8s进行API版本化管理的重要手段。对于会影响到用户的变更就可以通过升级新版本来处理,从而保证了向后兼容。
3. 匹配API对象的资源类型
现在就可以在/apis/batch/v2alpha1下创建指定类型的资源对象了。
流程如下:
- 发起创建CrobJob的POST请求后,编写的YAML提交给了APIServer。APIServer首先要做的就是过滤请求,并完成前置性的工作,比如授权、超时处理、审计等。
- 进入MUX和Routes流程。按照前面将的匹配过程,找到对应的CronJob类型定义。
- 根据CrobJob类型定义和用户提交的YAML文件中的字段,创建一个CronJob对象。而在这个过程中,APIServer 会进行一个 Convert 工作,即:把用户提交的 YAML 文件,转换成一个叫作 Super Version 的对象,这是这个资源类型所有版本的字段合集,通过Super Version可以对所有版本同一操作。
- 接下来先后进行Admission()和Validate()操作。在声明式API与k8s编程范式中的Admission Control和initializer就是Admission()中的内容。Validate就是验证字段是否合法,验证过的都保存在一个叫做Registry结构中。
- APIServer将验证过的API对象转换成用户最初提交的版本,进行序列化操作并调用Etcd保存。
自定义API资源
APIServer是k8s项目的重中之重,使用了很多Go语言的代码生成功能来自动化部分操作。在1.7版本之后,得益于一个API插件:CRD(Custom Resource Definition),用户可以在k8s中添加一个与Pod、Node类似的新的API资源类型。
编写CRD的YAML文件
要像创建一个自定义资源,那么就需要想让k8s明白自定义的资源是什么。
比如想要创建一个Network的自定义资源,
apiVersion: samplecrd.k8s.io/v1
kind: Network
metadata:
name: example-network
spec:
cidr: "192.168.0.0/16"
gateway: "192.168.0.1"那么用户就需要知道,Network这个自定义资源的分组是什么,版本是什么等等。。。。所以需要编写一个CRD的yaml文件用来描述资源定义。
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: networks.samplecrd.k8s.io
spec:
group: samplecrd.k8s.io
version: v1
names:
kind: Network
plural: networks
scope: Namespaced这里描述了有一个自定义资源,类型为NetWork,复数(plural)是networks,组为samplecrd.k8s.io,版本为v1,scope属于Namespace的对象,类似于Pod。
这样k8s就能够知道Network是什么了,但是作为一个API对象,在其YAML文件中有很多字段,创建对象的时候需要验证字段是否合法,那么k8s如何知道这个自定义资源有哪些字段。
编写自定义资源的具体类型代码
这就需要做些代码工作了。首先,在GOPATH下,创建如下目录结构的项目:
$ tree $GOPATH/src/github.com/<your-name>/k8s-controller-custom-resource
.
├── controller.go
├── crd
│ └── network.yaml
├── example
│ └── example-network.yaml
├── main.go
└── pkg
└── apis
└── samplecrd
├── register.go
└── v1
├── doc.go
├── register.go
└── types.go其中pkg/apis/samplecrd中的samplecrd是组名,v1是版本名。
v1下面的types.go中则定义了Network对象的完整描述。samplecrd/register.go中定义全局变量。
在samplecrd/v1下创建doc.go文件,添加如下内容,利用codegen来生成代码:
// +k8s:deepcopy-gen=package
// +groupName=samplecrd.k8s.io
package v1+<tag_name>[=value]格式的注释,这就是 Kubernetes 进行代码生成要用的 Annotation 风格的注释。这里的例子中表示为包下的所有类型生成DeepCopy方法,这个包对应的api组为samplecrd.k8s.io。
在type.go文件下就是自定义类型的字段:
package v1
...
// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// Network describes a Network resource
type Network struct {
// TypeMeta is the metadata for the resource, like kind and apiversion
metav1.TypeMeta `json:",inline"`
// ObjectMeta contains the metadata for the particular object, including
// things like...
// - name
// - namespace
// - self link
// - labels
// - ... etc ...
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec networkspec `json:"spec"`
}
// networkspec is the spec for a Network resource
type networkspec struct {
Cidr string `json:"cidr"`
Gateway string `json:"gateway"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// NetworkList is a list of Network resources
type NetworkList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []Network `json:"items"`
}其中的Spec字段就是需要我们自己定义的部分。json标签的名字也是在yaml中的名字。还用用来描述一组对象应该具有哪些字段,比如NetworkList。
+genclient表示为API资源生成对应的Client代码。+genclient:noStatus的意思是:这个 API 资源类型定义里,没有 Status 字段,否则会自动添加UpdateStatus字段,当然如果有Status字段,那么就不需要添加这个注释了。+k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object的注释。它的意思是,请在生成 DeepCopy 的时候,实现 Kubernetes 提供的 runtime.Object 接口。否则,在某些版本的 Kubernetes 里,你的这个类型定义会出现编译错误。这是一个固定的操作。
最后编写samplecrd/v1/register.go文件。就是为了让客户端也能够知道Network资源类型。最主要的功能就是定义了下面的addKnownTypes方法。
package v1
...
// addKnownTypes adds our types to the API scheme by registering
// Network and NetworkList
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(
SchemeGroupVersion,
&Network{},
&NetworkList{},
)
// register the type in the scheme
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}然后使用k8s提供的代码生成工具k8s.io/code-generator,为上面定义的Network资源类型自动生成clientset, informer和lister。client就是操作Network对象需要使用的客户端。
使用方法如下:
# 代码生成的工作目录,也就是我们的项目路径
$ ROOT_PACKAGE="github.com/resouer/k8s-controller-custom-resource"
# API Group
$ CUSTOM_RESOURCE_NAME="samplecrd"
# API Version
$ CUSTOM_RESOURCE_VERSION="v1"
# 安装k8s.io/code-generator
$ go get -u k8s.io/code-generator/...
$ cd $GOPATH/src/k8s.io/code-generator
# 执行代码自动生成,其中pkg/client是生成目标目录,pkg/apis是类型定义目录
$ ./generate-groups.sh all "$ROOT_PACKAGE/pkg/client" "$ROOT_PACKAGE/pkg/apis" "$CUSTOM_RESOURCE_NAME:$CUSTOM_RESOURCE_VERSION"这样就生成了如下的代码结构:
$ tree
.
├── controller.go
├── crd
│ └── network.yaml
├── example
│ └── example-network.yaml
├── main.go
└── pkg
├── apis
│ └── samplecrd
│ ├── constants.go
│ └── v1
│ ├── doc.go
│ ├── register.go
│ ├── types.go
│ └── zz_generated.deepcopy.go
└── client
├── clientset
├── informers
└── listerspkg/apis/samplecrd/v1/zz_generated.deepcopy.go就是自动生成的DeepCopy代码。
使用
现在就可以在集群里创建Network类型的API对象了。
首先创建Network的CRD:
$ kubectl apply -f crd/network.yaml
customresourcedefinition.apiextensions.k8s.io/networks.samplecrd.k8s.io created然后,创建一个Network对象。
$ kubectl apply -f example/example-network.yaml
network.samplecrd.k8s.io/example-network created可以通过kubectl get netword和kubectl describe network来查看创建的对象的信息。