进行 operator 开发之前要先了解 CRD 的使用,以及了解下 operator 出现的背景和它要解决的问题:
从原理上来说,只要能够监听并操作集群中的 CR 就可以开发 operator 了,在 自动生成的 CRD 代码 基础上直接写 operator 是可行的。但这种方式需要从头实现所有逻辑,而 operator 中有相当一部分逻辑是可以通用的。这些通用部分并且已经被整理成了多种开发框架,比如 kubernetes controller-runtime project 、kubebuilder、operator-framework/operator-sdk 等,使用这些框架可以大幅提高 operator 的开发效率。
kubernetes controller-runtime project 的用法参照 example-builder。
mkdir -p crontab/cmd/operator/controller-runtime
go get sigs.k8s.io/[email protected]
通过 builder 创建一个 controller, For() 指定监听资源类型,Owns() 表示索引的子资源类型。当 Pod 发生变化时,Pod 所属的 ReplicaSet 也会被传入 Reconcile() 中。
package main
import (
"context"
"fmt"
"os"
appsv1 "k8s.io/api/apps/v1"
corev1 "k8s.io/api/core/v1"
"sigs.k8s.io/controller-runtime/pkg/builder"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/config"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"sigs.k8s.io/controller-runtime/pkg/log/zap"
"sigs.k8s.io/controller-runtime/pkg/manager"
"sigs.k8s.io/controller-runtime/pkg/manager/signals"
"sigs.k8s.io/controller-runtime/pkg/reconcile"
)
func main() {
logf.SetLogger(zap.New())
var log = logf.Log.WithName("builder-examples")
mgr, err := manager.New(config.GetConfigOrDie(), manager.Options{})
if err != nil {
log.Error(err, "could not create manager")
os.Exit(1)
}
err = builder.
ControllerManagedBy(mgr). // Create the ControllerManagedBy
For(&appsv1.ReplicaSet{}). // ReplicaSet is the Application API
Owns(&corev1.Pod{}). // ReplicaSet owns Pods created by it
Complete(&ReplicaSetReconciler{})
if err != nil {
log.Error(err, "could not create controller")
os.Exit(1)
}
if err := mgr.Start(signals.SetupSignalHandler()); err != nil {
log.Error(err, "could not start manager")
os.Exit(1)
}
}
在 Reconcile() 中进行处理,传入的是发生变化的资源名称:
type ReplicaSetReconciler struct {
client.Client
}
func (a *ReplicaSetReconciler) Reconcile(ctx context.Context, req reconcile.Request) (reconcile.Result, error) {
fmt.Printf("Reconcile: %s\n", req.String())
rs := &appsv1.ReplicaSet{}
err := a.Get(ctx, req.NamespacedName, rs)
if err != nil {
return reconcile.Result{}, err
}
pods := &corev1.PodList{}
err = a.List(ctx, pods, client.InNamespace(req.Namespace), client.MatchingLabels(rs.Spec.Template.Labels))
if err != nil {
return reconcile.Result{}, err
}
fmt.Printf("Reconcile: pod num %v\n", len(pods.Items))
rs.Labels["pod-count"] = fmt.Sprintf("%v", len(pods.Items))
err = a.Update(ctx, rs)
if err != nil {
return reconcile.Result{}, err
}
return reconcile.Result{}, nil
}
func (a *ReplicaSetReconciler) InjectClient(c client.Client) error {
a.Client = c
return nil
}