还是之前的需求,上一篇文章:Operator示例:通过Operator+CRD实现部署自动化是基于官方的sample-controller来修改,实现我们的逻辑。这次使用kubebuilder来生成代码。
kubebuilder create api --group ingress --version v1beta1 --kind App
修改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的Schemeconfig/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
make manifests
还是之前的逻辑,监听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
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)
}
make install