一、什么是Operator

Operator是CoreOS(一种操作系统,用于大量基于云计算的虚拟服务器)推出的,旨在简化复杂有状态应用管理。

Operator是一个感知应用状态的控制器,通过扩展k8s API来自动创建、管理和配置应用实例。Operator基于CRD(Custom Resource Definition)扩展资源对象,并通过控制器来保证应用处于预期状态。

Operator可用于:

  • 通过k8s API观察集群的当前状态
  • 分析当前状态与期望状态的差别
  • 调用k8s API消除这些差别

二、为什么使用CRD

k8s内置的controller可满足大多数的使用场景,但对于很多定制化需求,其表达能力是不够的。因此,k8s支持了CRD(Custom Resource Definition),让用户可以自己定义资源类型,k8s会把用户通过CRD定义的资源视为资源的一种,对其提供像内置资源对象一样的支持。

CRD主要用于提高k8s的扩展能力。

三、Operator设计初衷

k8s管理应用时,应用分无状态和有状态:无状态管理比较简单,有状态管理比较复杂。Operator的的目的就是用于简化复杂的有状态的应用管理,Operator通过CRD扩展k8s API来自动创建、管理、和配置应用实例。其本质是针对特定的场景去做有状态服务。

Operator以deployment的形式部署到k8s中。部署完这个operator后,想要部署一个集群,就会很方便。因为不需要再去管理这个集群的配置信息了,只需要创建一个CRD,指定创建多少个节点,需要什么版本,Operator会监听该资源对象,创建出符合配置要求的集群,从而大大简化运维的难度和成本。

白话理解:Operator就是调用k8s的API来管理集群的,如对集群创建、删除;新增和减少节点。

四、开发Redis Operator

开发不同中间件operator流程基本相同,以redis operator为例说明:

4.1、首先准备

首先要一个资源定义(CRD)yaml,operator代码中会根据该yaml去组装并创建CRD:

apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
  name: redisclusters.redis.middleware.hc.cn
spec:
  group: redis.middleware.hc.cn
  version: v1alpha1
  scope: Namespaced
  names:
    kind: RedisCluster
    singular: rediscluster
    listKind: RedisClusterList
    plural: redisclusters
    shortNames:
    - rec

后面创建的该CRD类型的资源对象(CR),其kind为该yaml描述中spec.names.kind的值。CR相当于CRD的具体实现(不同operator, CRD和CR定义不同)。

  • 准备一个CR yaml文件,后面Operator代码要根据该yaml结构在types.go中定义结构体。redis的CR yaml如下。operator最终会监听该CR,解析里面定义的节点数、版本号等参数,驱动做一些事情。
apiVersion: redis.middleware.hc.cn/v1alpha1
kind: RedisCluster
metadata: 
  name: example000-redis-cluster
  namespace: kube-system
spec:
  # 代表redis集群的个数
  replicas: 7
  # 代表是否进入维修状态
  pause: true
  # 是否删除crd以及redis集群
  finalizers: foreground
  # 镜像地址
  repository: library/redis
  # 镜像版本,便于后续多版本特化支持
  version: 3.2.8
  #redis集群升级策略
  updateStrategy:
    # 升级类型为AutoReceive(自动分配,不用AssignStrategies), AssignReceive(指定值分配,需要用AssignStrategies)
    type: AssignReceive
    pipeline: "100"
    assignStrategies:
       - 
        slots: 2000
        fromReplicas: nodeId1
       - 
        # 从nodeId3,nodeId4一共分配1000个卡槽
        slots: 1000 
        # 多个nodeId用逗号分隔
        fromReplicas: nodeId3,nodeId4
  # redis 实例配置详情
  pod:
    # 标签管理:map[string][string]
  - labels:
      key: value
    # 备注管理:map[string][string]
    annotations:
      key: value
    # 环境变量管理
    env:
    - name: tony
      value: aa
    - name: MAXMEMORY
      value: 2gb    
    # 亲和性管理
    affinity: 
      nodeAffinity:
      requiredDuringSchedulingIgnoredDuringExecution:
        nodeSelectorTerms:
        - matchExpressions:
          - key: HC_Status
            operator: In
            values:
            - C
      podAntiAffinity: {}
    # 资源管理
    resources:
      limits: 
        #cpu, memory, storage,ephemeral-storage
        cpu: "2"
        memory: 4Gi
      requests:
        cpu: "1"
        memory: 2Gi
    #statefulset更新模式
    updateStrategy:
      type: RollingUpdate
    # 支持挂载形式: hostPath(不需要persistentVolumeClaimName),nfs(需要persistentVolumeClaimName)
    volumes:
      type: nfs
      persistentVolumeClaimName: pvcName
    # 配置文件模板名
    configmap: name
    # 监控镜像
    monitorImage: string
    # 初始化镜像
    initImage: string
    # 中间件容器镜像
    middlewareImage: string

status:
  #当前statefulset replicas情况
  replicas: 6
  # 集群阶段,None,Creating,Running,Failed,Scaling
  # None 或 “”, 就是代表该CRD刚创建
  # Creating 代表等待redis资源对象创建完毕(operator 发现CRD创建,创建资源对象,更新状态)
  # Running 代表已进行初始化操作(在Creating之后,发现实例起来完毕,初始化操作)
  # Failed 代表着某异常故障
  # ---------------------
  # Scaling 代表着实例不一致(用户修改实例,operator发现实例不一致,更新statefulset,更新状态)
  # Upgrading 代表着升级中
  # ---------------------
  phase: Creating
  # 异常问题解释
  reason: "异常问题"
  conditions:
  - name: redis-cluster-0
    instance: 10.168.78.90:6379
    type: master
    masterNodeId: allkk111snknkcs
    nodeId: allkk111snknkcs
    domainName: redis-cluster-0.redis-cluster.kube-system.svc.cluster.local
    slots: 1024-2048
    hostname: docker-vm-3
    hostIP: 192.168.26.122
    # true or flase 
    status: "True"
    reason: xxxx
    message: xxxx
    lastTransitionTime: 2019-03-25T03:10:29Z

4.2、代码生成

生成符合k8s风格的代码

4.3、operator主流程代码开发

首先operator的入口为operator-manager.go里的main函数:

package main

import (
    "fmt"
    "github.com/spf13/pflag"
    "harmonycloud.cn/middleware-operator-manager/cmd/operator-manager/app"
    "harmonycloud.cn/middleware-operator-manager/cmd/operator-manager/app/options"
    "k8s.io/apiserver/pkg/util/flag"
    "k8s.io/apiserver/pkg/util/logs"
    "k8s.io/kubernetes/pkg/version/verflag"
    "os"
)

func main() {
    //参数初始化配置
    s := options.NewOMServer()
    s.AddFlags(pflag.CommandLine, app.KnownOperators())

    flag.InitFlags()
    //日志初始化
    logs.InitLogs()
    defer logs.FlushLogs()

    verflag.PrintAndExitIfRequested()
    //进行operator初始化
    if err := app.Run(s); err != nil {
        fmt.Fprintf(os.Stderr, "%v\n", err)
        os.Exit(1)
    }
}

main函数中首先进行对参数的初始化,其中主要包括:operator多实例时的选主配置;事件同步时间;集群创建、升级超时时间;是否启用leader功能;是否开启pprof分析功能等,代码在options.go中。

app.Run(s)根据参数配置进行operator初始化:

  • 首先根据参数配置,构建默认客户端(操作k8s已有资源对象)、leader选举客户端、操作扩展资源客户端等。
  • 之后创建CRD资源对象定义,后续创建的CR对象都是该CRD的实例;
  • 注册健康检查接口,根据启动参数配置决定是否开启pprof分析接口功能;
  • 创建recorder,主要用于记录events(k8s资源),用于操作审计;
  • 定义Run函数,进行启动operator,选举结果的leader执行该函数;
  • 判断是否开启leader选举功能;
  • 创建leader选举资源锁,目前资源锁实现了configmaps和endpoints方式,具体代码在client-go下,默认使用endpoints方式。
  • 启动Leader选举机制,争抢到锁,选举为leader的实例执行OnStartedLeading,即上面定义的Run函数;失去锁的实例执行OnStoppedLeading函数。
Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐