使用Kubebuilder实现自定义Controller

背景

还是之前的需求,上一篇文章:Operator示例:通过Operator+CRD实现部署自动化是基于官方的sample-controller来修改,实现我们的逻辑。这次使用kubebuilder来生成代码。

主要步骤

  1. 使用kubebuilder生成项目代码
  2. 定义 CRD 的 struct
  3. 重新生成CRD资源
  4. 实现Reconcile逻辑
  5. 部署CRD
  6. 运行自定义controller
  7. 测试

具体实现

完整代码:https://github.com/EchoGroot/kubebuilder-demo

使用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的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

重新生成CRD资源

make manifests

实现Reconcile逻辑

还是之前的逻辑,监听CR,创建、修改对应的deployment,service,ingress

  1. 修改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
  1. 向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

image-20240128111348984

运行自定义controller

测试

  1. 开始之前清空资源

image-20240128111516520

  1. 创建CR

image-20240128111822092

  1. 查看自定义controller是否自动创建deployment,service,ingress

image-20240128112031651

  1. 修改CR也会影响其他资源,这里就不演示了
作者:Yuyy
博客:https://yuyy.info
暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇