1.创建CRD
2.自动生成代码
3.编写controller代码
1.创建CRD
目标如下:
创建自定义API对象(Custom Resource Definition),名为Student;
用代码生成工具生成informer和client相关代码;
创建并运行自定义控制器,k8s环境中所有Student相关的"增、删、改"操作都会被此控制器监听到,可以根据实际需求在控制器中编写业务代码;
创建CRD的第一步是通过官方文档做初步了解,地址:https://kubernetes.io/docs/tasks/access-kubernetes-api/custom-resources/custom-resource-definitions/#create-a-customresourcedefinition
登录可以执行kubectl命令的机器,创建student.yaml文件,内容如下:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
# name must match the spec fields below, and be in the form: .
name: students.stable.example.com
spec:
# group name to use for REST API: /apis//
group: stable.example.com
# list of versions supported by this CustomResourceDefinition
versions:
- name: v1
# Each version can be enabled/disabled by Served flag.
served: true
# One and only one version must be marked as the storage version.
storage: true
schema:
openAPIV3Schema:
type: object
properties:
spec:
type: object
properties:
name:
type: string
school:
type: string
replicas:
type: integer
# either Namespaced or Cluster
scope: Namespaced
names:
# plural name to be used in the URL: /apis///
plural: students
# singular name to be used as an alias on the CLI and for display
singular: students
# kind is normally the CamelCased singular type. Your resource manifests use this.
kind: Students
# shortNames allow shorter string to match your resource on the CLI
shortNames:
- stu
在student.yaml所在目录执行命令kubectl apply -f student.yaml,即可在k8s环境创建Student的定义,今后如果发起对类型为Student的对象的处理,k8s的api server就能识别到该对象类型了
访问k8s的etcd上存储的数据,那么执行以下命令,就可以看到新的CRD已经保存在etcd中
ETCDCTL_API=3 etcdctl --endpoints="192.168.204.129:2379,192.168.204.130:2379,192.168.204.131:2379" --cacert=/etc/kubernetes/pki/etcd/etcd-ca.pem --cert=/etc/kubernetes/pki/etcd/etcd.pem --key=/etc/kubernetes/pki/etcd/etcd-key.pem get /registry/apiextensions.k8s.io/customresourcedefinitions/ --prefix
创建Student对象
前面的步骤使得k8s能识别Student类型了,接下来创建个Student对象
apiVersion: "stable.example.com/v1"
kind: Student
metadata:
name: object-student
spec:
name: "李四"
school: "北京中学"
在object-student.yaml文件所在目录执行命令kubectl apply -f object-student.yaml
执行命令kubectl get stu可见已创建成功的Student对象
创建成功的Stutend对象存储在etcd中是什么样的呢,如果您的etcdctl已经配置好,执行以下命令
ETCDCTL_API=3 etcdctl --endpoints="192.168.204.129:2379,192.168.204.130:2379,192.168.204.131:2379" --cacert=/etc/kubernetes/pki/etcd/etcd-ca.pem --cert=/etc/kubernetes/pki/etcd/etcd.pem --key=/etc/kubernetes/pki/etcd/etcd-key.pem get /registry/stable.example.com/students/default/object-student --print-value-only
至此,自定义API对象(也就是CRD)就创建成功了,此刻我们只是让k8s能识别到Student这个对象的身份,但是当我们创建Student对象的时候,还没有触发任何业务(相对于创建Pod对象的时候,会触发kubelet在node节点创建docker容器)
2.自动生成代码
为什么要做controller
但如果仅仅是在etcd保存Student对象是没有什么意义的,试想通过deployment创建pod时,如果只在etcd创建pod对象,而不去node节点创建容器,那这个pod对象只是一条数据而已,没有什么实质性作用,其他对象如service、pv也是如此;
controller的作用就是监听指定对象的新增、删除、修改等变化,针对这些变化做出相应的响应(例如新增pod的响应为创建docker容器),关于controller的详细设计,最好的参考就是Harry (Lei) Zhang老师在twitter上的分享,地址是:https://twitter.com/resouer/status/1009996649832185856
API对象的变化会通过Informer存入队列(WorkQueue),在Controller中消费队列的数据做出响应,响应相关的具体代码就是我们要做的真正业务逻辑;
自动生成代码是什么
接下来要做的事情就是编写API对象Student相关的声明的定义代码,然后用代码生成工具结合这些代码,自动生成Client、Informet、WorkQueue相关的代码;
在$GOPATH/src/目录下创建一个文件夹k8s_customize_controller:
进入文件夹k8s_customize_controller,执行如下命令创建三层目录:
mkdir -p pkg/apis/stable
在新建的stable目录下创建文件register.go,内容如下:
package stable
const (
GroupName = "stable.example.com"
Version = "v1"
)
在新建的stable目录下创建名为v1的文件夹;
在新建的v1文件夹下创建文件doc.go,内容如下:
// +k8s:deepcopy-gen=package
// +groupName=stable.example.com
package v1
上述代码中的两行注释,都是代码生成工具会用到的,一个是声明为整个v1包下的类型定义生成DeepCopy方法,另一个声明了这个包对应的API的组名,和CRD中的组名一致;
在v1文件夹下创建文件types.go,里面定义了Student对象的具体内容:
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// +genclient
// +genclient:noStatus
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type Student struct {
metav1.TypeMeta `json:",inline"`
metav1.ObjectMeta `json:"metadata,omitempty"`
Spec StudentSpec `json:"spec"`
}
type StudentSpec struct {
name string `json:"name"`
school string `json:"school"`
}
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// StudentList is a list of Student resources
type StudentList struct {
metav1.TypeMeta `json:",inline"`
metav1.ListMeta `json:"metadata"`
Items []Student `json:"items"`
}
从上述源码可见,Student对象的内容已经被设定好,主要有name和school这两个字段,表示学生的名字和所在学校,因此创建Student对象的时候内容就要和这里匹配了;
在v1目录下创建register.go文件,此文件的作用是通过addKnownTypes方法使得client可以知道Student类型的API对象:
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s_customize_controller/pkg/apis/stable"
)
var SchemeGroupVersion = schema.GroupVersion{
Group: stable.GroupName,
Version: stable.Version,
}
var (
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
AddToScheme = SchemeBuilder.AddToScheme
)
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
func Kind(kind string) schema.GroupKind {
return SchemeGroupVersion.WithKind(kind).GroupKind()
}
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(
SchemeGroupVersion,
&Student{},
&StudentList{},
)
// register the type in the scheme
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}
至此,为自动生成代码做的准备工作已经完成了,目前为止,$GOPATH/src/k8s_customize_controller目录下的文件和目录结构是这样的:
.
└── pkg
└── apis
└── bolingcavalry
├── register.go
└── v1
├── doc.go
├── register.go
└── types.go
4 directories, 4 files
执行下面的命令,要在$GOPATH/src下执行:
go get -u k8s.io/[email protected]
go get -u k8s.io/[email protected]
./k8s.io/code-generator/generate-groups.sh all k8s_customize_controller/pkg/client k8s_customize_controller/pkg/apis stable:v1
会看到以下输出:
Generating deepcopy funcs
Generating clientset for stable:v1 at k8s_customize_controller/pkg/client/clientset
Generating listers for stable:v1 at k8s_customize_controller/pkg/client/listers
Generating informers for stable:v1 at k8s_customize_controller/pkg/client/informers
在$GOPATH/src/k8s_customize_controller目录下执行tree命令,可见已生成了很多内容:
.
├── go.mod
├── go.sum
└── pkg
├── apis
│ └── stable
│ ├── register.go
│ └── v1
│ ├── doc.go
│ ├── register.go
│ ├── types.go
│ └── zz_generated.deepcopy.go
└── client
├── clientset
│ └── versioned
│ ├── clientset.go
│ ├── doc.go
│ ├── fake
│ │ ├── clientset_generated.go
│ │ ├── doc.go
│ │ └── register.go
│ ├── scheme
│ │ ├── doc.go
│ │ └── register.go
│ └── typed
│ └── stable
│ └── v1
│ ├── doc.go
│ ├── fake
│ │ ├── doc.go
│ │ ├── fake_stable_client.go
│ │ └── fake_student.go
│ ├── generated_expansion.go
│ ├── stable_client.go
│ └── student.go
├── informers
│ └── externalversions
│ ├── factory.go
│ ├── generic.go
│ ├── internalinterfaces
│ │ └── factory_interfaces.go
│ └── stable
│ ├── interface.go
│ └── v1
│ ├── interface.go
│ └── student.go
└── listers
└── stable
└── v1
├── expansion_generated.go
└── student.go
21 directories, 29 files
如上所示,zz_generated.deepcopy.go就是DeepCopy代码文件,client目录下的内容都是客户端相关代码,在开发controller时会用到;
client目录下的clientset、informers、listers的身份和作用可以和前面的自动生成代码-controller的详细设计结合来理解;
3.编写controller代码
我们创建了CRD,再通过自动生成代码的工具将controller所需的informer、client等依赖全部准备好,到了本章,就该编写controller的代码了,也就是说,现在已经能监听到Student对象的增删改等事件,接下来就是根据这些事件来做不同的事情,满足个性化的业务需求;