背景
还是之前的需求,上一篇文章:Operator示例:通过Operator+CRD实现部署自动化是基于官方的sample-controller来修改,实现我们的逻辑。这次使用kubebuilder来生成代码。
主要步骤
- 使用kubebuilder生成项目代码
- 定义 CRD 的 struct
- 重新生成CRD资源
- 实现Reconcile逻辑
- 部署CRD
- 运行自定义controller
- 测试
具体实现
使用kubebuilder生成项目代码
kubebuilder create api --group ingress --version v1beta1 --kind App
- 可以用brew安装kubebuilder
定义 CRD 的 struct
修改api/v1beta1/app_types.go
里的AppSpec
type AppSpec struct {
// INSERT ADDITIONAL SPEC FIELDS - desired state of cluster
// Important: Run "make" to regenerate code after modifying this file
// omitempty,非必填
EnableIngress bool `json:"enable_ingress,omitempty"`
EnableService bool `json:"enable_service"`
Replicas int32 `json:"replicas"`
Image string `json:"image"`
}
-
json标记和注释会影响CRD的Scheme
config/crd/bases/ingress.yuyy.com_apps.yaml
spec: versions: - name: v1beta1 schema: openAPIV3Schema: properties: spec: description: AppSpec defines the desired state of App properties: enable_ingress: description: omitempty,非必填 type: boolean enable_service: type: boolean image: type: string replicas: format: int32 type: integer required: - enable_service - image - replicas type: object
重新生成CRD资源
make manifests
实现Reconcile逻辑
还是之前的逻辑,监听CR,创建、修改对应的deployment,service,ingress
- 修改
internal/controller/app_controller.go
里的Reconcile函数
func (r *AppReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) {
logger := log.FromContext(ctx)
app := &ingressv1beta1.App{}
if err := r.Get(ctx, req.NamespacedName, app); err != nil {
return ctrl.Result{}, client.IgnoreNotFound(err)
}
deployment := utils.NewDeployment(app)
if err := ctrl.SetControllerReference(app, deployment, r.Scheme); err != nil {
return ctrl.Result{}, err
}
if err := r.Get(ctx, req.NamespacedName, deployment); err != nil {
if errors.IsNotFound(err) {
if err := r.Create(ctx, deployment); err != nil {
logger.Error(err, "create deployment failed")
return ctrl.Result{}, err
}
} else {
return ctrl.Result{}, err
}
}
service := utils.NewService(app)
if err := ctrl.SetControllerReference(app, service, r.Scheme); err != nil {
return ctrl.Result{}, err
}
if err := r.Get(ctx, req.NamespacedName, service); err != nil {
if errors.IsNotFound(err) && app.Spec.EnableService {
if err := r.Create(ctx, service); err != nil {
return ctrl.Result{}, err
}
}
} else {
if !app.Spec.EnableService {
if err := r.Delete(ctx, service); err != nil {
return ctrl.Result{}, err
}
}
}
ingress := utils.NewIngress(app)
if err := controllerutil.SetControllerReference(app, ingress, r.Scheme); err != nil {
return ctrl.Result{}, err
}
i := &netv1.Ingress{}
if err := r.Get(ctx, types.NamespacedName{Name: app.Name, Namespace: app.Namespace}, i); err != nil {
if errors.IsNotFound(err) && app.Spec.EnableIngress {
if err := r.Create(ctx, ingress); err != nil {
logger.Error(err, "create ingress failed")
return ctrl.Result{}, err
}
}
if !errors.IsNotFound(err) && app.Spec.EnableIngress {
return ctrl.Result{}, err
}
} else {
if app.Spec.EnableIngress {
logger.Info("skip update")
} else {
if err := r.Delete(ctx, i); err != nil {
return ctrl.Result{}, err
}
}
}
return ctrl.Result{}, nil
}
-
此函数返回的result对象包含两个字段,控制是否重新放入工作队列,多久后放入。
type Result struct { Requeue bool RequeueAfter time.Duration }
-
这在某些场景下很有用,例如一个Pod依赖于一个PV,那么在清理阶段,Pod删除应先于PV删除。
-
如果是k8s内建资源,在pod使用pv时,pv会在finalizers里增加一个标记,从而禁止pod存在时,意外删除pv。只有pod删除,controller移除该标记,pv才能正常删除。
-
如果是自定义资源,就要在自定义controller里实现这部分逻辑。例如删除pv时,自定义controller检测到存在该标记,就可以等待一阵后重试。
return ctrl.Result{ Requeue: true, RequeueAfter: 5 * time.Second, }, nil
-
- 向manager注册controller
func (r *AppReconciler) SetupWithManager(mgr ctrl.Manager) error {
return ctrl.NewControllerManagedBy(mgr).
For(&ingressv1beta1.App{}).
Owns(&v1.Deployment{}).
Owns(&corev1.Service{}).
Owns(&netv1.Ingress{}).
Complete(r)
}
- 除CR外,还监听了deployment,service,ingress资源的事件,实现删除他们时会自动重建。
部署CRD
make install
运行自定义controller
测试
- 开始之前清空资源
- 创建CR
- 查看自定义controller是否自动创建deployment,service,ingress
- 修改CR也会影响其他资源,这里就不演示了