2022-01-17 11:47:51 +08:00
|
|
|
|
# 定制Kubernetes资源
|
|
|
|
|
|
|
|
|
|
[定制资源(Custom Resource)](https://kubernetes.io/zh/docs/concepts/extend-kubernetes/api-extension/custom-resources/) 是对 Kubernetes API 的扩展,可以开发定制的资源及控制器,这些资源仍旧可以通过kubernetes api来访问。
|
|
|
|
|
|
|
|
|
|
可以单独开发定制资源,也可以连定制同控制器一起开发。
|
|
|
|
|
|
|
|
|
|
## 准备
|
|
|
|
|
|
|
|
|
|
开发定制资源(CRD)需要安装controller-gen、kubebuilder、kustomize这三个工具。
|
|
|
|
|
|
|
|
|
|
***这里所有工具都默认安装到`${HOME}/go/bin/`***
|
|
|
|
|
|
|
|
|
|
### controller-gen
|
|
|
|
|
|
|
|
|
|
[controller-gen](https://github.com/kubernetes-sigs/controller-tools) 用于生成crd controller的基础代码, 省去不少工作。
|
|
|
|
|
|
|
|
|
|
安装
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
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
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
使用方法
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# 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](https://github.com/kubernetes-sigs/kubebuilder)用于生成crd基础代码。
|
|
|
|
|
|
|
|
|
|
安装
|
|
|
|
|
|
|
|
|
|
```bash
|
2024-09-13 15:10:58 +08:00
|
|
|
|
wget https://github.com/kubernetes-sigs/kubebuilder/releases/download/v3.13.0/kubebuilder_linux_amd64 -O ${HOME}/go/bin/kubebuilder
|
2022-01-17 11:47:51 +08:00
|
|
|
|
chmod +x ${HOME}/go/bin/kubebuilder
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
方法2
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
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详细使用方法参见[官方文档](https://book.kubebuilder.io/)
|
|
|
|
|
|
|
|
|
|
创建一个项目
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
mkdir $GOPATH/src/example
|
|
|
|
|
cd $GOPATH/src/example
|
|
|
|
|
kubebuilder init --domain project.demo.io
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
创建一个api资源
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
kubebuilder create api --group project.demo.io --version v1 --kind Demo
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
***这个api资源就是project.demo.io/v1/demos***
|
|
|
|
|
|
|
|
|
|
### kustomize
|
|
|
|
|
|
|
|
|
|
[kustomize](https://github.com/kubernetes-sigs/kustomize)把简明的kustomization 模板转换成kubernetes的资源配置。kustomize 可以用于crd也可以用于内置资源(参见文档[manage-kubernetes-objects](https://kubernetes.io/zh/docs/tasks/manage-kubernetes-objects/kustomization/))的管理。
|
|
|
|
|
|
|
|
|
|
安装
|
|
|
|
|
|
|
|
|
|
```bash
|
2024-09-13 15:10:58 +08:00
|
|
|
|
go get sigs.k8s.io/kustomize/kustomize/v5.3.0
|
2022-01-17 11:47:51 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
使用方法参见[官方指南](https://kubectl.docs.kubernetes.io/zh/guides)
|
|
|
|
|
|
|
|
|
|
## 开始
|
|
|
|
|
|
|
|
|
|
安装好相关工具之后,就可以开始愉快的玩耍了。
|
|
|
|
|
|
|
|
|
|
### 初始化项目
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
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` 目录下文件(夹)如下
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
config Dockerfile go.mod go.sum hack main.go Makefile PROJECT
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
- `config` 存放kustomization配置
|
|
|
|
|
- `Dockerfile` 生产docker镜像的配置,这个文件包含了代码编译操作
|
|
|
|
|
- `hack` 这个文件夹存放了一个名为`boilerplate.go.txt`的文件
|
|
|
|
|
- `main.go` 程序入口文件,启动一个manager,负责管理crd
|
|
|
|
|
- `Makefile` `make`命令的配置文件
|
|
|
|
|
- `PROJECT` 项目配置文件
|
|
|
|
|
|
|
|
|
|
#### main.go
|
|
|
|
|
|
|
|
|
|
main.go 是manager启动的入口文件。
|
|
|
|
|
|
|
|
|
|
##### 处理启动参数
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
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`
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
|
|
|
|
|
Scheme: scheme,
|
|
|
|
|
MetricsBindAddress: metricsAddr,
|
|
|
|
|
Port: 9443,
|
|
|
|
|
HealthProbeBindAddress: probeAddr,
|
|
|
|
|
LeaderElection: enableLeaderElection,
|
|
|
|
|
LeaderElectionID: "e71592d6.demo",
|
|
|
|
|
})
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
##### 为manager添加healthy和ready检查
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
//+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
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
|
|
|
|
|
setupLog.Error(err, "problem running manager")
|
|
|
|
|
os.Exit(1)
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 创建一个CRD
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
kubebuilder create api --group ${API_DOMAIN} --version v1 --kind Demo
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
***创建时会询问是否创建resource及controller***
|
|
|
|
|
|
|
|
|
|
现在项目目录下多了几个文件夹
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
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的新增部分
|
|
|
|
|
|
|
|
|
|
```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`,
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
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状态
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
type DemoStatus struct {
|
|
|
|
|
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
|
|
|
|
|
// Important: Run "make" to regenerate code after modifying this file
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
注册资源
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
func init() {
|
|
|
|
|
SchemeBuilder.Register(&Demo{}, &DemoList{})
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
#### `api/v1/groupversion_info.go`
|
|
|
|
|
|
|
|
|
|
api 资源组配置。
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 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`对象定义。一般来说,无需为其添加任何内容,除非有特别需求
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
|
|
// 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的变更都会调用此方法。
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
|
|
|
|
|
//+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其他资源的时候。
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
// 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`
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
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` 默认已经添加了该注释,如果误删除了,需要添加上。
|
|
|
|
|
|
|
|
|
|
```go
|
|
|
|
|
//+kubebuilder:object:root=true
|
|
|
|
|
//+kubebuilder:subresource:status
|
|
|
|
|
|
|
|
|
|
// Demo is the Schema for the demoes API
|
|
|
|
|
type Demo struct {
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
**注意这里注释的位置和空行。**
|
|
|
|
|
|
|
|
|
|
### 多group
|
|
|
|
|
|
|
|
|
|
初始化
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
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
|
2022-06-18 16:02:45 +08:00
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### 创建clientset
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
client-gen --clientset-name="slmclient" --input-dirs="./pkg/api/v1alpha1" -h hack/boilerplate.go.txt --output-package pkg/slmclient
|
2022-01-17 11:47:51 +08:00
|
|
|
|
```
|