Kubernetes Operator 模式应用介绍
介绍通过创建自己的 kubernetes operator 来优雅部署服务

Kubernetes Operator 模式应用介绍

Operator 简介

对于 Kubernetes 服务的部署,通常需要在运维过程中涉及对服务部署所依赖的各种资源的管理,包括但不限于 StatefulSet, Deployment, ConfigMap 等资源配置。在出现需要大量部署相同类型的服务,且又存在差异化的领域配置情况时,需要大量的重复操作,通过大量的冗余配置存储来进行管理。尤其是在对于有状态服务的维护上,这一问题尤其明显。在更新相关服务的过程中,逐一更新也会变成繁琐复杂的过程。即使使用 helm,jsonnet 这类编排工具,也无法简化操作。

于是 CoreOS 基于这些困境,参考 Kubernetes 控制器本身的设计理念,提出了 Kubernetes Operator 模式。

在 Kubernetes 中,控制平面的控制器实施控制循环,反复比较集群的理想状态和实际状态。如果集群的实际状态与理想状态不符,控制器将采取措施解决此问题。Operator 基于基本 Kubernetes 资源和控制器概念构建,但又涵盖了特定于域或应用的知识,用于实现其所管理软件的整个生命周期的自动化。

Operator 使用了 Kubernetes 的自定义资源扩展 API 机制,通过 CRD (CustomResourceDefinition) 来创建、配置和管理应用程序。Operator 可以被理解为一种定制化的资源控制器,对应用服务执行部署、更新、配置调整等维护性操作。

Operator

通过 Operator 的使用,可以对应用服务的部署起到以下的效果。

  • 使得应用部署流程可扩展、可重复且标准化,实现服务的开箱即用
  • 使得部署和运行其应用所依赖的基础服务变得更简单
  • 提供在 Kubernetes 环境分发软件的一致方式,并通过识别和纠正应用问题降低了支持成本
  • 对所有服务提供标准化的配置管理入口

Operator 发布模式

自从 CoreOS 在 2016 年首次提出 Kubernetes Operator 的概念以后,大量的工具厂商或者社区,都开始为自己的工具组件提供对应的 Operator 来进行标准化的分发管理。Github 上专门收集 Operator 的 awesome list 每月都在增长。从最早期 CoreOS 自己的 Prometheus Operator,到出现 ElastiSearch Operator,Mongodb Operator 这些厂商自己提供的工具,发展相当迅速。

https://github.com/operator-framework/awesome-operators

当然 Operator 也并非完全没有弊端,对于 Operator 本身的使用,也会存在以下的限制。

  • 仅支持 Kubernetes 环境的平台限制。
  • 无法完全消除对于应用服务本身配置方言的学习成本。
  • 在运维管理层增加了一层抽象层,随之对应用部署的调度又增加了一层。服务资源管理会严格受到 Operator 行为的限制,无法进行灵活 patch。
  • 存在设计复杂度,需要自行对 CRD 进行合理的数据结构设计,并实现其调度逻辑,本身会存在工具的开发及维护成本。

对于 Saas 服务按租户部署实例,以及私有化部署应用额场景,原本可以无状态配置的应用服务会因为租户相关领域配置信息的存在,而变成存在服务状态管理的应用。从而出现存在对大量服务实例的重复性配置管理,在这种场景下,使用 Operator 进行标准统一的自动化管理,是一个有价值的实际应用场景。

Operator-SDK 选择

自行开发 Operator 时,推荐选择 Opertator SDK 工具来辅助开发,避免从头开始进行工程准备的麻烦。目前可选的两个主流方案是 CoreOS 主导的 operator-sdk 以及 Kubernetes SIG 主导的 kubebuilder。

  • operator-sdk

    https://github.com/operator-framework/operator-sdk

    由 CoreOS 支持的 Operator Framework 团队维护的 SDK 项目。特点是在标准的 golang SDK 以外,提供了更丰富的 ansible operator 以及 helm operator 的方案,直接通过胶水的形式将 ansible 或者 helm 编排项目转为 Kubernetes Operator。

  • kubebuilder

    https://github.com/kubernetes-sigs/kubebuilder

    由 Kubernetes 社区的核心团队 SIG (Special Interest Group 特别兴趣小组) 组织的 SDK 项目。特点是对社区工具开发技术栈的支持更加友好,比如原生支持了同在 SIG 下的 Kustomize 等工具。另外,kubebuilder 在 controller 的基础上还额外提供了 manager。

  • 非 Golang 实现

    在 github 上也存在有诸如 python, typescript 实现的 operator sdk。然而这类 SDK 普遍存在一个问题是,使用非 golang 实现的 sdk,无法直接使用 Kubernetes 官方提供的 controller 接口模型,造成更大的维护代价,进而导致社区项目维度的不及时,使得此类 SDK 一般会出现 API 对接上的功能缺失。功能缺失又造成了相关工具无法成为主流流行项目,进一步降低了维护的活跃度,无法作为主流的 SDK 工具方案。

Operator-sdk 与 Kubebuilder 两者都是采用了官方 Kubernetes Controller 实现方案实现,对于原生实现 golang Operator 的场景下,开发的复杂度和实现形式几乎完全相同,甚至连工程目录组织形式的也是相近的。SIG 给出的文档说明相对更加完整,且由于支持的工具更为齐全,实际体验对比下来,Kubebuilder 会相对更好用。

Operator 开发示例

无论是 kubebuilder 还是 Operator-SDK,官方发布的二进制工具组件支持都是以 Linux 环境为主,建议在 Linux 环境或者 WSL 环境下进行 Operator 下开发。虽然最终编译 Operator 本身可以在任何环境下进行,且实测 Windows 编译的 Operator 也能正常工作,但是官方并未提供完整的兼容性测试支持。

本节以 Kubebuilder 为例,大致介绍 Operator 的开发方式。

环境准备

首先在准备好必要都 golang 运行环境以后,安装必要的 kustomize 工具,以及用于管理 SDK 的 kubebuilder。kustomize 本身也是 SIG 出品的 yaml 编排工具,可以实现与 helm , jsonnet 相似的 yaml 编排组织功能,也正在逐步被众多 Kuberenets 工具吸收。

# 安装 kustomize
curl -s "https://raw.githubusercontent.com/\
kubernetes-sigs/kustomize/master/hack/install_kustomize.sh"  | bash
sudo mv ./kustomize /usr/local/bin/kustomize

# 安装 kubebuilder
os=$(go env GOOS)
arch=$(go env GOARCH)

curl -L https://go.kubebuilder.io/dl/2.3.1/${os}/${arch} | tar -xz -C /tmp/

sudo mv /tmp/kubebuilder_2.3.1_${os}_${arch} /usr/local/kubebuilder
export PATH=$PATH:/usr/local/kubebuilder/bin

接下来准备下 golang 工程使用的 GOPATH 项目环境,随后便可以使用 kubebuilder 来初始化 Operator 的自定义域以及 API 信息了。

# GOPATH 与工作目录准备
mkdir -p ./golang
cd ./golang && export GOPATH=$PWD
mkdir -p ${GOPATH}/src/operator
cd ${GOPATH}/src/operator

# 工程初始化
kubebuilder init --domain jiandaoyun.com
kubebuilder create api --group k8s --version v1alpha1 --kind DemoServer

Operator 开发

工程初始化完成后,可以得到一个如下的目录结构。

.
├── api
│   └── v1alpha1
│       ├── groupversion_info.go
│       ├── demoserver_types.go
│       └── zz_generated.deepcopy.go
├── bin
│   └── manager
├── config
│   ├── certmanager
│   ├── crd
│   │   ├── kustomization.yaml
│   │   ├── kustomizeconfig.yaml
│   │   └── patches
│   ├── default
│   ├── manager
│   ├── prometheus
│   ├── rbac
│   ├── samples
│   │   └── k8s_v1alpha1_demoserver.yaml
│   └── webhook
├── controllers
│   ├── demoserver_controller.go
│   └── suite_test.go
├── Dockerfile
├── go.mod
├── go.sum
├── hack
├── main.go
├── Makefile
└── PROJECT

开发一个 Operator 核心需要关注的是 API 中的数据结构定义,以及 controller 中被触发的配置变更执行方法。另外也可以看到 kubebuilder 本身初始化了额外的管理类资源文件,比如用于控制权限的 rbac 权限角色定义,用于采集监控指标的 prometheus 定义等。除了核心的数据结构和调度逻辑文件以外的内容,对于一般场景而言,并不需要过多关注。

涉及 Operator 的实现主要关注以下文件:

  • api/v1alpha1/demoserver_types.go - 数据结构类型定义

    主要用于声明自定义 CRD 所对应 API 中的数据结构,以及其结构在配置结构中对应的属性。通过 sdk 初始化默认生成的 DemoServer 结构如下。

    type DemoServer struct {
        metav1.TypeMeta   `json:",inline"`
        metav1.ObjectMeta `json:"metadata,omitempty"`
    
        Spec   DemoServerSpec   `json:"spec,omitempty"`
        Status DemoServerStatus `json:"status,omitempty"`
    }
    

    可以通过调整代码对结构补充定义的方式,增加需要添加的自定义属性,例如可以增加 DemoServerSpec 的属性,定义 DemoServer 资源所使用的资源镜像。

    type DemoServerSpec struct {
        Image string         `json:"image"`
    }
    

    通过使用 golang 的结构体定义,可以高度自由的来设计所需要的 CRD 资源配置结构。

    在完成 sdk 定义以后,可以使用 sdk 预先生成的 makefile 工具来生成对应自定义结构对象实体的操作方法。输出内容会生成在 zz_generated.deepcopy.go 文件中,该文件无需手动编辑。

  • controllers/demoserver_controller.go - Controller 核心控制逻辑

    Controller 中框架本身也十分简单,唯一需要关注设计调度业务的方法是 Reconcile 调和方法。当 k8s 集群中的 CRD 配置定义发生变更 (新增,修改,删除) 后,会触发到调和行为,用于将自定义的 CRD 资源,转译为标准的 Kubernetes 部署逐渐,可以在这一步中,创建或修改 k8s 集群中与所需要编排服务相关的各种资源,比如发布 Deployment,Statefulset,定义 Service 入口,创建管理 ConfigMap 内容等等。

    func (r *DemoServerReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
        _ = context.Background()
        _ = r.Log.WithValues("demoserver", req.NamespacedName)
    
        // your logic here
    
        return ctrl.Result{}, nil
    }
    

    Reconcile 逻辑的实现可以非常灵活,根据实际的业务需要,可以模拟出自己到 k8s 集群中部署服务的各种行为。使得能够以标准程序化的方式,自动完成所定义对象的所有资源的编排配置。对于复杂的业务逻辑,使用 golang 的模块组织方式,拆分到独立的业务功能函数以及文件中维护也是完全可信的。

    所有在发生 CRD 资源定义变更的场景,都会触发到 Reconcile 方法的执行,一般会将调和逻辑设计为,由 Operator 基于 CRD 配置生成实际的资源配置后,与当前集群中的配置进行对比,选择进行创建,修改,保持不变,抑或是其他操作。在 Operator 启动时,也会读取 CRD 配置定义进行调和过程,这个过程可以用于通过 Operator 更新的方式,来更新集群中的服务,保证实际部署服务,与 CRD 所描述的行为对应 Operator 的目标配置始终一致。同时,Reconcile 的并发数也是受控的,默认情况下不会同时修改所有资源,而是会逐个调整,避免对线上业务产生突发影响。

    Reconcile 过程生成 K8s 中组件定义配置并不一定完全按照 golang 的标准接口一个个定义所有的属性,完全可以在外部定义一个完整的 yaml 模板文件,将其结构解析后,加载到特定对象实例上,如 Deployment,随后再把 CRD 中定义的自定义额外配置修改到基于模板 yaml 生成的对象上进行部署,可以大大简化编排复杂度。甚至 yaml 模板本身也可以实现成 golang 的模板字符串,在读取过程直接,完成变量参数的注入,效果与 helm value 注入行为类似。

  • config/samples/k8s_v1alpha1_demoserver.yaml - CRD 示例

    最后一个需要关注的主要文件是 CRD 配置的示例文件,主要用于在开发过程中,实际调试修改需要在 kubernetes 集群中测试生效的自定义资源配置。

至于其他的非核心文件,对于简单的 Operator 实现并不是必要的组件,这里的简单说明就不再进行额外关注。

更多实际的 Operator 实现技巧,可以参考 Awesome Cloud Native 列表中的相关项目实现。

https://jimmysong.io/awesome-cloud-native/#kubernetes-operators

Operator 执行与发布

在完成 Operator 业务逻辑实现的开发以后,接下来的工作就是实际执行测试,验证 Operator 的有效性,以及构建并发布 Operator 容器镜像。

  • 编译构建

    对本地开发环境编写的 Operator 工程进行编译,生成可执行文件,同时也会自动根据结构定义更新 zz_generated 中的实体对象操作方法。

    make install
    
  • 本地运行

    在开发环境本地直接执行 operator,对本地配置中当前实际连接的 kubernetes context 产生实际效果,用于测试评估 operator 是否正确执行。

    make run
    
  • 构建发布镜像

    构建可用于 kubernetes 环境下正式部署的 operator 镜像,发布到指定的 docker registry 上,并进行最终部署。

    make docker-build docker-push IMG=<some-registry>/<project-name>:tag
    make deploy IMG=<some-registry>/<project-name>:tag
    

总结

Kubernetes Operator 的实现复杂度总体并不高,本质上只是将需要人工执行的运维操作进行自动化的实现。通过使用 Operator,可以节省服务实例化部署场景下,大量分发,更新的维护操作成本,进行一致化统一化的管理。适用于对 Saas 应用实例化部署,以及私有化 k8s 环境下服务部署的支持以及自动化效能的提升。


参考内容:

  1. https://kubernetes.io/zh/docs/concepts/extend-kubernetes/operator/
  2. https://jimmysong.io/kubernetes-handbook/develop/kubebuilder-example.html

最后修改于 2020-09-29