入门 构建简单的Operator
使用 kubebuilder 创建 Kubernetes Operator 的示例
前提:准备 kubernetes 集群
名词:
- CRD:自定义资源定义,Kubernetes 中的资源类型。
- CR:Custom Resource,对使用 CRD 创建出来的自定义资源的统称。
安装 Kubebuilder
详见:https://book.kubebuilder.io/quick-start.html#installation
创建项目
首先将使用自动配置创建一个项目,该项目在创建 CR 时不会触发任何资源生成
在项目路径下使用下面的命令初始化项目
mkdir test-prj && cd test-prj
kubebuilder init --domain code.liaosirui.com \
--repo code.liaosirui.com/srliao/test-prj
# --domain string domain for groups (default "my.domain")
# --repo string name to use for go module (e.g., github.com/user/repo), defaults to the go package of the current working directory.
生成后会有如下提示
Writing kustomize manifests for you to edit...
Writing scaffold for you to edit...
Get controller runtime:
$ go get sigs.k8s.io/controller-runtime@v0.12.1
Update dependencies:
$ go mod tidy
Next: define a resource with:
$ kubebuilder create api
创建 API
在项目根目录下执行下面的命令创建 API
kubebuilder create api --group webapp --version v1 --kind Guestbook
# 如果要在一个项目中创建多个 group 的资源
# refer: https://kubebuilder.io/migration/multi-group.html
# kubebuilder edit --multigroup=true
按照提示进行生成即可
> kubebuilder create api --group webapp --version v1 --kind Guestbook
Create Resource [y/n]
y
Create Controller [y/n]
y
Writing kustomize manifests for you to edit...
Writing scaffold for you to edit...
api/v1/guestbook_types.go
controllers/guestbook_controller.go
Update dependencies:
$ go mod tidy
go: downloading github.com/onsi/ginkgo/v2 v2.0.0
Running make:
$ make generate
mkdir -p /code/srliao/mytest/test-prj/bin
GOBIN=/code/srliao/mytest/test-prj/bin go install sigs.k8s.io/controller-tools/cmd/controller-gen@v0.9.0
go: downloading sigs.k8s.io/controller-tools v0.9.0
go: downloading github.com/spf13/cobra v1.4.0
go: downloading github.com/fatih/color v1.12.0
go: downloading golang.org/x/tools v0.1.10-0.20220218145154-897bd77cd717
go: downloading github.com/gobuffalo/flect v0.2.5
go: downloading github.com/mattn/go-isatty v0.0.12
go: downloading github.com/mattn/go-colorable v0.1.8
go: downloading golang.org/x/mod v0.6.0-dev.0.20220106191415-9b9b3d81d5e3
/code/srliao/mytest/test-prj/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
Next: implement your new API and generate the manifests (e.g. CRDs,CRs) with:
$ make manifests
项目结构
API 创建完成后,在项目根目录下查看目录结构
.
├── api
│ └── v1
│ ├── groupversion_info.go
│ ├── guestbook_types.go
│ └── zz_generated.deepcopy.go
├── bin
│ └── controller-gen
├── config
│ ├── crd # 新增 CRD 定义
│ │ ├── kustomization.yaml
│ │ ├── kustomizeconfig.yaml
│ │ └── patches
│ │ ├── cainjection_in_guestbooks.yaml
│ │ └── webhook_in_guestbooks.yaml
│ ├── default
│ │ ├── kustomization.yaml
│ │ ├── manager_auth_proxy_patch.yaml
│ │ └── manager_config_patch.yaml
│ ├── manager
│ │ ├── controller_manager_config.yaml
│ │ ├── kustomization.yaml
│ │ └── manager.yaml
│ ├── prometheus
│ │ ├── kustomization.yaml
│ │ └── monitor.yaml
│ ├── rbac
│ │ ├── auth_proxy_client_clusterrole.yaml
│ │ ├── auth_proxy_role_binding.yaml
│ │ ├── auth_proxy_role.yaml
│ │ ├── auth_proxy_service.yaml
│ │ ├── guestbook_editor_role.yaml
│ │ ├── guestbook_viewer_role.yaml
│ │ ├── kustomization.yaml
│ │ ├── leader_election_role_binding.yaml
│ │ ├── leader_election_role.yaml
│ │ ├── role_binding.yaml
│ │ └── service_account.yaml
│ └── samples
│ └── webapp_v1_guestbook.yaml # CRD 示例
├── controllers # 新增 controller
│ ├── guestbook_controller.go
│ └── suite_test.go
├── Dockerfile # 用于构建 Operator 镜像
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
├── main.go # 新增处理逻辑
├── Makefile # 构建时使用
├── PROJECT # 项目配置
└── README.md
以上就是自动初始化出来的文件
安装 CRD
安装完成后查看 CRD
> kubectl get crd | grep code.liaosirui.com
guestbooks.webapp.code.liaosirui.com 2022-08-05T03:34:05Z
部署 controller
有两种方式运行 controller:
- 本地运行,用于调试
- 部署到 Kubernetes 上运行,作为生产使用
本地运行
要想在本地运行 controller,只需要执行下面的命令。
将看到 controller 启动和运行时输出
将 controller 部署到 Kubernetes
执行下面的命令部署 controller 到 Kubernetes 上,这一步将会在本地构建 controller 的镜像,并推送到 DockerHub 上,然后在 Kubernetes 上部署 Deployment 资源。
make docker-build docker-push IMG=registry.cn-chengdu.aliyuncs.com/custom-srliao/kubebuilder-example:latest
make deploy IMG=registry.cn-chengdu.aliyuncs.com/custom-srliao/kubebuilder-example:latest
如果要取消部署,使用
在初始化项目时,kubebuilder 会自动根据项目名称创建一个 Namespace,如本文中的 test-prj-system,查看 Deployment 对象和 Pod 资源。
> kubectl get deployment -n test-prj-system
NAME READY UP-TO-DATE AVAILABLE AGE
test-prj-controller-manager 1/1 1 1 6m40s
> kubectl get pod -n test-prj-system
NAME READY STATUS RESTARTS AGE
test-prj-controller-manager-65c54d5b56-zmqrx 2/2 Running 0 22s
创建 CR
Kubebuilder 在初始化项目的时候已生成了示例 CR,执行下面的命令部署 CR。
查看新创建的 CR
apiVersion: webapp.code.liaosirui.com/v1
kind: Guestbook
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"webapp.code.liaosirui.com/v1","kind":"Guestbook","metadata":{"annotations":{},"name":"guestbook-sample","namespace":"default"},"spec":null}
creationTimestamp: "2022-08-05T06:13:11Z"
generation: 1
name: guestbook-sample
namespace: default
resourceVersion: "10125401"
uid: 195559b3-9677-4bdc-b19a-e0301c9dbcee
至此一个基本的 Operator 框架已经创建完成,但这个 Operator 只是修改了 etcd 中的数据而已,实际上什么事情也没做,因为我们没有在 Operator 中的增加业务逻辑
增加业务逻辑
将修改 CRD 的数据结构并在 controller 中增加一些日志输出
修改 CRD
将修改上文中使用 kubebuilder 命令生成的默认 CRD 配置,在 CRD 中增加 FirstName、LastName 和 Status 字段
增加字段:
// line 26
// GuestbookSpec defines the desired state of Guestbook
type GuestbookSpec 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 Guestbook. Edit guestbook_types.go to remove/update
FirstName string `json:"firstname"`
LastName string `json:"lastname"`
}
// GuestbookStatus defines the observed state of Guestbook
type GuestbookStatus struct {
// INSERT ADDITIONAL STATUS FIELD - define observed state of cluster
// Important: Run "make" to regenerate code after modifying this file
Status string `json:"Status"`
}
修改 Reconcile 函数
Reconcile 函数是 Operator 的核心逻辑,Operator 的业务逻辑都位于 controllers/guestbook_controller.go
文件的 func (r *GuestbookReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error)
函数中。
修改代码:
func (r *GuestbookReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
// TODO(user): your logic here
logger = logger.WithValues("api-example-a", req.NamespacedName)
// 获取当前的 CR,并打印
obj := &webappv1.Guestbook{}
if err := r.Get(ctx, req.NamespacedName, obj); err != nil {
logger.V(1).Info(err.Error(), "Unable to fetch object")
} else {
logger.V(1).Info(fmt.Sprintf("Greeting from Kubebuilder to <%v> <%v>", obj.Spec.FirstName, obj.Spec.LastName))
}
// 初始化 CR 的 Status 为 Running
obj.Status.Status = "Running"
if err := r.Status().Update(ctx, obj); err != nil {
logger.V(1).Info(err.Error(), "unable to update status")
}
return ctrl.Result{}, nil
}
这段代码的业务逻辑是当发现有 guestbook.webapp.code.liaosirui.com
的 CR 变更时,在控制台中输出日志
运行测试
修改好 Operator 的业务逻辑后,再测试一下新的逻辑是否可以正常运行。
- 部署 CRD
- 运行 controller
为了方便起见,将在本地运行 controller
保持该窗口在前台运行
- 部署 CR
修改 config/samples/webapp_v1_guestbook.yaml
文件中的配置。
apiVersion: webapp.code.liaosirui.com/v1
kind: Guestbook
metadata:
name: guestbook-sample
spec:
# TODO(user): Add fields here
firstname: Jimmy
lastname: Song
将其应用到 Kubernetes
此时转到上文中运行 controller 的窗口,将在命令行前台中看到如下输出
1.6596835013825462e+09 DEBUG Greeting from Kubebuilder to <Sirui> <Liao> {"controller": "guestbook", "controllerGroup": "webapp.code.liaosirui.com", "controllerKind": "Guestbook", "guestbook": {"name":"guestbook-sample","namespace":"default"}, "namespace": "default", "name": "guestbook-sample", "reconcileID": "ee988ac2-2b26-4321-b5eb-49c22a2ffbf3", "api-example-a": "default/guestbook-sample", "Sirui": "Liao"}
- 获取当前的 CR
apiVersion: webapp.code.liaosirui.com/v1
kind: Guestbook
metadata:
annotations:
kubectl.kubernetes.io/last-applied-configuration: |
{"apiVersion":"webapp.code.liaosirui.com/v1","kind":"Guestbook","metadata":{"annotations":{},"name":"guestbook-sample","namespace":"default"},"spec":{"firstname":"Sirui","lastname":"Liao"}}
creationTimestamp: "2022-08-05T07:05:56Z"
generation: 2
name: guestbook-sample
namespace: default
resourceVersion: "10141376"
uid: ddaa4a3b-129f-4bde-bcff-0e2cbe823d89
spec:
firstname: Sirui
lastname: Liao
status:
Status: Running