前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Gitlab CI/CD 实践四:Golang 项目 CI/CD 流水线配置

Gitlab CI/CD 实践四:Golang 项目 CI/CD 流水线配置

作者头像
Yuyy
发布2024-02-03 12:47:49
3550
发布2024-02-03 12:47:49
举报
文章被收录于专栏:yuyy.info技术专栏

背景

还是之前的需求,上一篇文章: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生成项目代码

代码语言:javascript
复制
kubebuilder create api --group ingress --version v1beta1 --kind App
  • 可以用brew安装kubebuilder

定义 CRD 的 struct

修改api/v1beta1/app_types.go里的AppSpec

代码语言:javascript
复制
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

代码语言:javascript
复制
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函数
代码语言:javascript
复制
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对象包含两个字段,控制是否重新放入工作队列,多久后放入。

代码语言:javascript
复制
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检测到存在该标记,就可以等待一阵后重试。

代码语言:javascript
复制
return ctrl.Result{
    Requeue: true,
    RequeueAfter: 5 * time.Second,
}, nil
  1. 向manager注册controller
代码语言:javascript
复制
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

测试

  1. 开始之前清空资源

  1. 创建CR

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2023-6-02 1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 背景
  • 主要步骤
  • 具体实现
    • 使用kubebuilder生成项目代码
      • 定义 CRD 的 struct
        • 重新生成CRD资源
          • 实现Reconcile逻辑
            • 部署CRD
              • 运行自定义controller
                • 测试
                领券
                问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档