目录

Client-go简单控制器-Ops-resource-controller 集群通用工具

使用 client-go 模仿 kube-controller-manager 实现一个简单的控制器。

需求背景

之前开发了一些关于 kubernetes 集群运维相关的工具有以下几个问题:

  • 独立部署,比较分散,无法统一管理。

  • 没有实现高可用,只要运行期间出现出问题工具就失效了。

  • 无通知告警功能,且部分代码实现开销较大。

  • 通用,适用于目前 kubernetes 开源社区的集群。

因此,针对以上问题,我们需要将集群相关工具的所有功能集成到一起,并且优化相关功能代码。

原理与实现

离不开 informer 哪一套东西。

1.原理示意图

关于功能集成问题:

  • 控制器实现模块化管理,如果以后有其他需求,可以直接在原有的基础上新增控制器而不会干扰到其他控制器代码。

  • CRUD 相关操作是从本地缓存获取的 api-server 数据,减轻资源同步对 api-server 造成的额外负担。

  • 多线程操作,运行速度快。

  • 新增告警功能和控制器日志按等级显示(平时不显示详细日志),方便第一时间知道控制器处理了什么,和问题排查。

/client-go%E7%AE%80%E5%8D%95%E6%8E%A7%E5%88%B6%E5%99%A8-ops-resource-controller-%E9%9B%86%E7%BE%A4%E9%80%9A%E7%94%A8%E5%B7%A5%E5%85%B7/ops-resource-controller.png
ops-resource-controller

2.关键启动流程

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// 存放所有控制器启动函数
func NewControllerInitializers() map[string]InitFunc {
    controllers := map[string]InitFunc{}
    controllers["SendReport"] = startSendReportController
    controllers["AutoCordon"] = startAutoCordonController
    controllers["DelException"] = startDelExceptionController

    return controllers
}

// 所有控制器启动入口函数
func StartControllers(ctx ControllerContext, controllers map[string]InitFunc) error {
    for controllerName, initFn := range controllers {
        if !ctx.IsControllerEnabled(controllerName) {
            klog.Warningf("%q is disabled", controllerName)
            continue
        }

        // time.Sleep(wait.Jitter(ctx.ComponentConfig.Generic.ControllerStartInterval.Duration, ControllerStartJitter))

        klog.V(1).Infof("Starting %q", controllerName)
        _, started, err := initFn(ctx)
        if err != nil {
            klog.Errorf("Error starting %q", controllerName)
            return err
        }
        if !started {
            klog.Warningf("Skipping %q", controllerName)
            continue
        }
        klog.Infof("Started %q", controllerName)
    }

    return nil
}
  • 所有控制器对象都抽象为 InitFunc 接口对象,以后新增控制器只要实现 InitFunc 接口即可,无需改到其他代码,就可以作为控制器加入到原有的架构里面。

3.主要功能

  1. 生成重启次数大于指定阈值的报告并发送到飞书。

  2. 清理异常状态 pods ,目前只清理一直卡在 terminating 和被驱逐后残留的 pod ,后续觉得好用的话,可以把状态字段提取出来,放到 comfigmap ,以实现清理我们指定状态的 pod 。

  3. 自动给节点打污点。

  4. 待有需求再添加其他功能。

注意

注意以下几种情况:

  • 人为打了污点的,且节点 labels 没有包含 “node.kubernetes.io/autocordon” 的标签 key 的节点,控制器不会干预。

  • 为方便维护,给节点施加 “node.kubernetes.io/donotcordon” 的 labels 标签 key ,值为任意值,则控制器会忽略该节点,即节点 pod 过多也不会打污点。

方案实施步骤

部署到集群。

1.配置授权

  • 控制器首先查找家目录下的 .kube/config 配置文件,如果不存在,则任务是在 kubernetes 集群内运行的,会尝试使用 serviceAccount 的权限去跟 api server 通讯,所以我们要确保这两种方式的其中一种可用即可。

  • 以下两种方式二选一。

1
2
3
4
5
6
7
8
9
    // 使用 KubeConfig 文件创建集群配置 Config 对象
    if config, err = clientcmd.BuildConfigFromFlags("", *kubeconfig); err != nil {
        klog.Errorln(err.Error())
        if config, err = rest.InClusterConfig(); err != nil {
            // 首先使用 inCluster 模式(需要去配置对应的 RBAC 权限,默认的sa是default->是没有获取deployments的List权限)
            panic(err.Error())
        }
        
    }

ServiceAccount

创建 ServiceAccount

1
2
3
4
5
6
7
apiVersion: v1
kind: ServiceAccount
metadata:
  labels:
    k8s-app: ops-resource-controller
  name: ops-resource-controller
  namespace: kube-system

将这个 ServiceAccount 跟 ClusterRole 进行绑定:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: ops-resource-controller
  labels:
    k8s-app: ops-resource-controller
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: cluster-admin
subjects:
- kind: ServiceAccount
  name: ops-resource-controller
  namespace: kube-system

在 Pod 中使用 ServiceAccount 。

serviceAccountName: ops-resource-controller

配置文件

在 deployment 配置文件中,配置挂载 master 宿主机目录(此方式适合 master 节点在自己手上,否则按这个思路需要将 config 配置文件放在 configmap 或 secret 挂载进控制器容器。)

1
2
3
4
5
6
7
...
      volumes:
      - hostPath:
          path: /root/.kube
          type: DirectoryOrCreate
        name: kubeconfig
...        

容器挂载该路径,并使其能正确读取到 config 配置文件。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
...
        volumeMounts:
        - mountPath: /root/.kube
          name: kubeconfig
          readOnly: true
      nodeSelector:
        node-role.kubernetes.io/master: ""
      tolerations:
      - key: "node-role.kubernetes.io/master"
        operator: "Exists"
        effect: "NoSchedule" 
...   

2.configmap

完成前置工作以后,我们还得配置供控制器运行的配置文件,具体含义看注释。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
apiVersion: v1
data:
  config.yml: |
    # 周期性发送重启次数过多的服务
    sendReport:
      total: 100
      # 秒分时日月周
      schedule: "00 00 10 * * 1"
      # 告警信息title
      msgtitle: "pod重启次数过多"
      apiurl: "https://open.feishu.cn/open-apis/bot/v2/hook/xxx"
      enable: true
    # 自动给节点打污点
    autoCordon:
      enable: true
      interval: 1m
      maxPods: 100
      minPods: 90
    # 删除一直卡在 terminating 状态和 Evicted 状态的 pod
    delException:
      enable: true
      # 执行周期
      interval: 2m
      msgtitle: "删除异常状态 Pod"
      apiurl: "https://open.feishu.cn/open-apis/bot/v2/hook/xxx"
    # 控制器的全局配置
    controller:
      # 单位是秒,不清楚什么意思不要改
      controllerResyncPeriod: 10
      # 单位是秒,不清楚什么意思不要改
      InformersResyncPeriod: 30
      # leader 选举,高可用的同时防止多个 pod 同时启动,不然定时类任务会被多次执行
      LeaderElect: true
      leaseLockName: ops-resource-controller
      leaseLockNamespace: kube-system    
kind: ConfigMap
metadata:
  name: ops-resource-controller-cm
  namespace: kube-system     

运行时间间隔,控制器是否启用,按需设置。

3.deployment

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
apiVersion: apps/v1
kind: Deployment
metadata:
  name: ops-resource-controller
  namespace: kube-system
spec:
  replicas: 1
  selector:
    matchLabels:
      app: ops-resource-controller
  template:
    metadata:
      labels:
        app: ops-resource-controller
    spec:
      containers:
      - name: ops-resource-controller
        image: yourrepositryurl/ops-resource-controller:v1.1
        env:
        - name: TZ
          value: Asia/Shanghai
        imagePullPolicy: IfNotPresent
        volumeMounts:
        - mountPath: /root/.kube
          name: kubeconfig
          readOnly: true
        - mountPath: /opt/config.yml
          name: config
          subPath: config.yml
      nodeSelector:
        node-role.kubernetes.io/master: ""
      tolerations:
      - key: "node-role.kubernetes.io/master"
        operator: "Exists"
        effect: "NoSchedule"
      volumes:
      - hostPath:
          path: /root/.kube
          type: DirectoryOrCreate
        name: kubeconfig
      - name: config
        configMap:
          name: ops-resource-controller-cm
          items:
          - key: config.yml
            path: config.yml

应用到k8s集群

kubectl apply -f ops-resource-controller.yaml

观察效果

/client-go%E7%AE%80%E5%8D%95%E6%8E%A7%E5%88%B6%E5%99%A8-ops-resource-controller-%E9%9B%86%E7%BE%A4%E9%80%9A%E7%94%A8%E5%B7%A5%E5%85%B7/ops-resource-controller-%E6%95%88%E6%9E%9C.png
ops-resource-controller-效果