12 KiB
定制Kubernetes资源
定制资源(Custom Resource) 是对 Kubernetes API 的扩展,可以开发定制的资源及控制器,这些资源仍旧可以通过kubernetes api来访问。
可以单独开发定制资源,也可以连定制同控制器一起开发。
准备
开发定制资源(CRD)需要安装controller-gen、kubebuilder、kustomize这三个工具。
这里所有工具都默认安装到${HOME}/go/bin/
controller-gen
controller-gen 用于生成crd controller的基础代码, 省去不少工作。
安装
git clone https://github.com/kubernetes-sigs/controller-tools.git
cd controller-tools/cmd/controller-gen/
go build -o ${HOME}/go/bin/controller-gen main.go
使用方法
# controller-gen crd --paths=[api code path]/... output:dir=[output dir]
controller-gen crd --paths=/git/demo/api/... output:dir=/git/demo/crd
kubebuilder
kubebuilder用于生成crd基础代码。
安装
wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.13.0/kubebuilder_linux_amd64 -O ${HOME}/go/bin/kubebuilder
chmod +x ${HOME}/go/bin/kubebuilder
方法2
curl -L -o kubebuilder https://go.kubebuilder.io/dl/latest/$(go env GOOS)/$(go env GOARCH)
chmod +x kubebuilder && mv kubebuilder /usr/local/bin/
使用方法
kubebuilder详细使用方法参见官方文档
创建一个项目
mkdir $GOPATH/src/example
cd $GOPATH/src/example
kubebuilder init --domain project.demo.io
创建一个api资源
kubebuilder create api --group project.demo.io --version v1 --kind Demo
这个api资源就是project.demo.io/v1/demos
kustomize
kustomize把简明的kustomization 模板转换成kubernetes的资源配置。kustomize 可以用于crd也可以用于内置资源(参见文档manage-kubernetes-objects)的管理。
安装
go get sigs.k8s.io/kustomize/kustomize/v5.3.0
使用方法参见官方指南
开始
安装好相关工具之后,就可以开始愉快的玩耍了。
初始化项目
PROJECT_DIR=demo #
API_DOMAIN=demo
GO_MODULE_DOMAIN=crd.io
OWNER=bing
mkdir ${PROJECT_DIR}
cd ${PROJECT_DIR}
kubebuilder init --plugins go/v3 --domain ${API_DOMAIN} --owner ${OWNER} --repo ${GO_MODULE_DOMAIN} --skip-go-version-check
PROJECT_DIR
是项目目录GO_MODULE_DOMAIN
是go mod 的名称, 也是api resource group的后缀,如果使用GOPATH则不需要配置API_DOMAIN
是api resource的域名,也是group名的一部分OWNER
是项目所有人
最终apiVersion:${API_DOMAIN}.${GO_MODULE_DOMAIN}
现在,PROJECT_DIR
目录下文件(夹)如下
config Dockerfile go.mod go.sum hack main.go Makefile PROJECT
config
存放kustomization配置Dockerfile
生产docker镜像的配置,这个文件包含了代码编译操作hack
这个文件夹存放了一个名为boilerplate.go.txt
的文件main.go
程序入口文件,启动一个manager,负责管理crdMakefile
make
命令的配置文件PROJECT
项目配置文件
main.go
main.go 是manager启动的入口文件。
处理启动参数
flag.StringVar(&metricsAddr, "metrics-bind-address", ":8080", "The address the metric endpoint binds to.")
flag.StringVar(&probeAddr, "health-probe-bind-address", ":8081", "The address the probe endpoint binds to.")
flag.BoolVar(&enableLeaderElection, "leader-elect", false,
"Enable leader election for controller manager. "+
"Enabling this will ensure there is only one active controller manager.")
opts := zap.Options{
Development: true,
}
opts.BindFlags(flag.CommandLine)
flag.Parse()
创建manager
创建了一个manager mgr
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Port: 9443,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "e71592d6.demo",
})
为manager添加healthy和ready检查
//+kubebuilder:scaffold:builder
if err := mgr.AddHealthzCheck("healthz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up health check")
os.Exit(1)
}
if err := mgr.AddReadyzCheck("readyz", healthz.Ping); err != nil {
setupLog.Error(err, "unable to set up ready check")
os.Exit(1)
}
注意:注释//+kubebuilder:scaffold:builder
也是代码的一部分。所有`//+kubebuilder:开头的注释都有他的特殊意义。
启动manager
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
创建一个CRD
kubebuilder create api --group ${API_DOMAIN} --version v1 --kind Demo
创建时会询问是否创建resource及controller
现在项目目录下多了几个文件夹
api bin config controllers Dockerfile go.mod go.sum hack main.go Makefile PROJECT
-
api
api resource 的代码目录,目录下是版本子目录,resource的代码文件位于版本子目录内 -
bin
controller-gen
可执行文件位于此处 -
controllers
controller的代码目录
main.go
看一下main.go的新增部分
if err = (&controllers.DemoReconciler{
Client: mgr.GetClient(),
Log: ctrl.Log.WithName("controllers").WithName("Demo"),
Scheme: mgr.GetScheme(),
}).SetupWithManager(mgr); err != nil {
setupLog.Error(err, "unable to create controller", "controller", "Demo")
os.Exit(1)
}
创建一个对应的controller对象(DemoReconciler
), 并设置这个controller的manager为mgr
api/v1/demo_types.go
定义Demo资源规约DemoSpec
,
type DemoSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// Foo is an example field of Demo. Edit demo_types.go to remove/update
Foo string `json:"foo,omitempty"`
}
定义Demo状态
type DemoStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
}
注册资源
func init() {
SchemeBuilder.Register(&Demo{}, &DemoList{})
}
api/v1/groupversion_info.go
api 资源组配置。
// Package v1 contains API Schema definitions for the demo.io v1 API group
//+kubebuilder:object:generate=true
//+groupName=demo.crd.io
package v1
import (
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/scheme"
)
var (
// GroupVersion is group version used to register these objects
GroupVersion = schema.GroupVersion{Group: "demo.crd.io", Version: "v1"}
// SchemeBuilder is used to add go types to the GroupVersionKind scheme
SchemeBuilder = &scheme.Builder{GroupVersion: GroupVersion}
// AddToScheme adds the types in this group-version to the given scheme.
AddToScheme = SchemeBuilder.AddToScheme
)
注意:注释+groupName=demo.crd.io
不可删除,与+kubebuilder:object:generate=true
一样,有他的特殊意义。后续还能看到用于设定的很多注释
api/v1/zz_generated.deepcopy.go
这是一个自动生成的深度复制的文件,不要随意修改此文件,除非明确知道要做什么。kubebuilder会自动配置及更新该文件。
controllers/demo_controller.go
资源demo的控制器文件。
DemoReconciler
DemoReconciler
对象定义。一般来说,无需为其添加任何内容,除非有特别需求
// DemoReconciler reconciles a Demo object
type DemoReconciler struct {
client.Client
Log logr.Logger
Scheme *runtime.Scheme
}
-
DemoReconciler
对象有三个成员,client.Client
是kubernetes client接口,提供如Get、Update、Create、Delete等方法。 -
Log
统一的log处理 -
Scheme
这个功能比较多,比如序列化与反序列化对象
Reconcile
处理程序。每一次CRD的变更都会调用此方法。
//+kubebuilder:rbac:groups=demo.demo,resources=demoes,verbs=get;list;watch;create;update;patch;delete
//+kubebuilder:rbac:groups=demo.demo,resources=demoes/status,verbs=get;update;patch
//+kubebuilder:rbac:groups=demo.demo,resources=demoes/finalizers,verbs=update
func (r *DemoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = r.Log.WithValues("demo", req.NamespacedName)
// your logic here
return ctrl.Result{}, nil
}
注意:+kubebuilder:rbac
是用于授权的注释,只有在此授权了的资源,才能够使用对应权限访问。kustomize
会依据此配置生成config/rbac
配置。
SetupWithManager
设置controller的manager,通常不需要修改这部分代码就可以正常工作,但有时需要进行改动,比如watch其他资源的时候。
// SetupWithManager sets up the controller with the Manager.
func (r *DemoReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&demov1.Demo{}).
Complete(r)
}
为controller添加功能
添加字段
首先为crd添加两个字段。一个规约字段Bar
和一个状态字段UpdatedAt
type DemoSpec struct {
Foo string `json:"foo,omitempty"`
Bar string `json:"Bar,omitempty"`
}
// DemoStatus defines the observed state of Demo
type DemoStatus struct {
UpdatedAt *metav1.Time `json:"UpdatedAt,omitempty"`
}
修改Reconcile
import apiv1 "demo.crd.io/api/v1"
func (r *DemoReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
_ = r.Log.WithValues("demo", req.NamespacedName)
// your logic here
demo := apiv1.Demo{}
err := r.Get(context.Background(), req.NamespacedName, &demo)
if err != nil {
r.Log.Error(err, "get demo resource error")
return ctrl.Result{}, nil
}
//更新操作
if demo.ObjectMeta.DeletionTimestamp.IsZero() {
//更新
err = r.processUpdate(&demo)
}
return ctrl.Result{}, nil
}
//处理更新
func (r *DemoReconciler)processUpdate(demo *apiv1.Demo)error{
r.Log.Info("the bar of %s is %s", demo.GetName(), demo.Spec.Bar)
now := metav1.Now()
demo.Status.UpdatedAt = &now
err := r.Status().Update(context.Background(), demo)
return err
}
这里首先获取到触发此次reconcile的对象demo
, 然后通过demo.ObjectMeta.DeletionTimestamp.IsZero()
来判断是删除操作还是更新操作(创建对象也是更新操作)。对于更新操作,执行方法processUpdate
在processUpdate
中,先将demo.Spec.Bar
打印到日志:r.Log.Info("the bar of %s is %s", demo.GetName(), demo.Spec.Bar)
, 之后更新demo.Status.UpdatedAt
字段。
这里使用了r.Status().Update(context.Background(), demo)
,这需要有注释// +kubebuilder:subresource:status
默认已经添加了该注释,如果误删除了,需要添加上。
//+kubebuilder:object:root=true
//+kubebuilder:subresource:status
// Demo is the Schema for the demoes API
type Demo struct {
注意这里注释的位置和空行。
多group
初始化
PROJECT_DIR=demo #
GO_MODULE_DOMAIN=crd.io
OWNER=bing
CRD_VERSION=v1beta1
echo "init crd env"
kubebuilder init --plugins go/v3 --owner ${OWNER} --repo ${GO_MODULE_DOMAIN} --skip-go-version-check
echo "open multi group..."
kubebuilder edit --multigroup
kubebuilder edit --multigroup
开启多组支持
创建CRD
kubebuilder create api --group group1 --version ${CRD_VERSION} --kind Demo --crd-version ${CRD_VERSION} --controller --resource
kubebuilder create api --group group2 --version ${CRD_VERSION} --kind Demo --crd-version ${CRD_VERSION} --controller --resource
创建clientset
client-gen --clientset-name="slmclient" --input-dirs="./pkg/api/v1alpha1" -h hack/boilerplate.go.txt --output-package pkg/slmclient