引言
kubernetes已是云原生领域事实上的标准,operator作为kubernetes的扩展插件应用也越来越多,几乎所有运行与kubernetes之上的应用程序大都提供相应的operator。本文将描述了如何从零开始构建一个operator的开发过程,其中脚手架框架使用的是operator SDK。
前置条件
- 已安装golang开发环境,参见安装golang;
- 已安装operator SDK,参见安装operator SDK;
- 已部署kubernetes集群,推荐使用sealos部署集群;
- 本地用户对kubernetes集群具有cluster-admin访问权限;
创建一个新工程
operator SDK提供了命令行工具,帮助我们创建工程以及其他需要的资源。
mkdir -p $HOME/code/znbase-operator
cd $HOME/code/znbase-operator
# we'll use a domain of inspur.com
# so all API groups will be <group>.inspur.com
operator-sdk init --domain inspur.com --repo github.com/inspur/znbase-operator
其中:
- domain:domain将作为API组的后缀,格式为:<group>.<domain>
- repo: 本工程的golang包名,如果当前工作目录不是
$GOPATH/src
,该值不能省略;
当看到如下信息,说明已创建成功:
operator-sdk init --domain inspur.com --repo github.com/inspur/znbase-operator
Writing kustomize manifests for you to edit...
Writing scaffold for you to edit...
Get controller runtime:
$ go get sigs.k8s.io/controller-runtime@v0.8.3
Update dependencies:
$ go mod tidy
Next: define a resource with:
$ operator-sdk create api
工程目录结构
生成的工程目录结构如下:
├── config
│ ├── default
│ ├── manager
│ ├── manifests
│ ├── prometheus
│ ├── rbac
│ └── scorecard
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│ └── boilerplate.go.txt
├── main.go
├── Makefile
└── PROJECT
其中比较重要的文件和目录如下:
- config: 目录下是所有的yaml配置文件,包括创建CRD、role、监控、controller相关的deployment和service等。
- Dockerfile:用于生成docker镜像的文件,使用的基础镜像国内用户可能无法下载,需要替换为其他可下载镜像;
- Makefile:编译控制文件,内部定义了所以的编译、部署命令;
- main.go:应用程序入口文件;
如需详细信息,请查看官方文档:kubebuilder项目布局。
至此,我们已经构建了一个最简单的可运行operator项目。
编译
执行make build
命令,显示如下:
make build
/home/chensj/code/znbase-operator/bin/controller-gen object:headerFile="hack/boilerplate.go.txt" paths="./..."
go fmt ./...
go vet ./...
go build -o bin/manager main.go
生成可执行文件bin/manager,该文件是可以直接运行的,当然这个operator什么事也没干。
在goland中调试
点击run -> Edit Configurations…,然后点击“+”添加一个go build实例,配置项保持默认即可。
点击run -> Debug '…'开始进入调试模式。
需要注意的是在调试带有webhook的operator时在本地需要有TLS证书,所以建议初次调试时最好不要带webhook,当熟悉了operator开发模式后再调试webhook。
main.go解析
main.go是operator的入口文件,整个文件包括注释不到100行,很简洁。接下来,我们看看main里都做了哪些事。
解析启动参数
func main() {
...
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.")
opts.BindFlags(flag.CommandLine)
flag.Parse()
...
}
首先看到解析启动参数metrics-bind-address、health-probe-bind-address和leader-elect。分别代表采集监控指标地址、健康检查地址和operator是否是高可用部署,一般情况下保持默认即可。
创建Manager对象
manager是operator的核心对象,其定义位于sigs.k8s.io/controller-runtime@v0.8.3/pkg/manager/manager.go
,其内部维护了对kubernetes集群的操作client,以及operator运行所需的资源。在main.go中创建manager对象的代码如下:
func main() {
...
mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
Scheme: scheme,
MetricsBindAddress: metricsAddr,
Port: 9443,
HealthProbeBindAddress: probeAddr,
LeaderElection: enableLeaderElection,
LeaderElectionID: "308d9f16.inspur.com",
})
...
看起来比较复杂,主要是通过ctrl.Options
配置对象向构造函数里传递不同的参数,以此控制manager的行为。ctrl.Options
的成员很多,但一般情况下这段代码保持原样即可,如需修改可以通过源码查看其成员的说明,这里不在详述。
添加健康检查handler
这段代码的作用是添加健康检查点的处理函数,这里使用的是healthz包提供的默认方法,一般情况下不需要修改。
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)
}
启动manager
这段代码的作用是启动operator,之后operator将一直运行下去。
if err := mgr.Start(ctrl.SetupSignalHandler()); err != nil {
setupLog.Error(err, "problem running manager")
os.Exit(1)
}
至此我们完成最简单的operator创建工作,该operator是在本地运行,但是什么也没干。接下来我们给这个operator添加新的API和controller,让它真正工作起来。