blog/kubernetes/custom_resources/readme.md

433 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 定制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
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
```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
go get sigs.k8s.io/kustomize/kustomize/v5.3.0
```
使用方法参见[官方指南](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
```
### 创建clientset
```bash
client-gen --clientset-name="slmclient" --input-dirs="./pkg/api/v1alpha1" -h hack/boilerplate.go.txt --output-package pkg/slmclient
```