万字长文 | 面向k8s编程,如何写一个Operator

本文详述了如何使用 Kubernetes Operator 编程,介绍 CRD、Operator 工作原理,并通过 Operator SDK 演示创建与管理自定义资源的完整流程,包括构建、安装、运行 Operator,以及使用 Operator 部署和管理应用。
摘要由CSDN通过智能技术生成

062965d4a8a9a4dec8547ed5b3e36e1c.gif

新钛云服已为您服务1489

cfd50464a76093a3c6ff8784f944eb6b.gif

概述

随着我们对 Kubernetes 的逐步了解,可能就会发现 Kubernetes 中内置的对象定义,比如 DeploymentStatefulSetConfigMap,可能已经不能满足我们的需求。我们希望在 Kubernetes 定义一些自己的对象,一是可以通过 kube-apiserver 提供统一的访问入口,二是可以像其他内置对象一样,通过 kubectl 命令管理这些自定义的对象。

Kubernetes 中提供了这种自定义对象的方式,其中之一就是 CRD

CRD 介绍

CRDCustomResourceDefinitions)在 v1.7 刚引入进来的时候,其实是 ThirdPartyResources(TPR)的升级版本,而 TPR 在 v1.8 的版本被剔除了。CRD 目前使用非常广泛,各个周边项目都在使用它,比如 Ingress、Rancher 等。

我们来看一下官方提供的一个例子,通过如下的 YAML 文件,我们可以创建一个 API:

apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
 # 名字须与下面的 spec 字段匹配,并且格式为 '<名称的复数形式>.<组名>'
  name: crontabs.stable.example.com
spec: # 组名称,用于 REST API: /apis/<组>/<版本>
  group: stable.example.com
  # 列举此 CustomResourceDefinition 所支持的版本
  versions:
    - name: v1
      # 每个版本都可以通过 served 标志来独立启用或禁止
      served: true
      # 其中一个且只有一个版本必需被标记为存储版本
      storage: true
      schema:
        openAPIV3Schema:
          type: object
          properties:
            spec:
              type: object
              properties:
                cronSpec:
                  type: string
                image:
                  type: string
                replicas:
                  type: integer
 # 可以是 Namespaced 或 Cluster
  scope: Namespaced
  names:
    # 名称的复数形式,用于 URL:/apis/<组>/<版本>/<名称的复数形式>
    plural: crontabs
    # 名称的单数形式,作为命令行使用时和显示时的别名
    singular: crontab
    # kind 通常是单数形式的驼峰编码(CamelCased)形式。我们的资源清单会使用这一形式。
    kind: CronTab
    # shortNames 允许我们在命令行使用较短的字符串来匹配资源
    shortNames:
    - ct

这样我们就可以像创建其他对象一样,通过 kubectl create 命令创建。创建完成以后,一个类型为 CronTab 的对象就在 kube-apiserver 中注册好了,我们可以通过如下的 REST 接口访问,比如查看命名空间 ns1 下的 CronTab 对象,可以通过这个 URL “/apis/stable.example.com/v1/namespaces/ns1/crontabs/” 访问。这种接口跟 Kubernetes 内置的其他对象的接口风格是一模一样的。

声明好了 CronTab,我们就来看看如何创建一个 CronTab 类型的对象。下面依然是来自官方的一个例子:

apiVersion: "stable.example.com/v1"
kind: CronTab
metadata:
  name: new-cron-object
spec:
  cronSpec: "* * * * */5"
  image: awesome-cron-image

通过 kubectl create 创建 new-cron-object 后,就可以通过 kubectl get 查看,并使用 kubectl 管理这个 CronTab 对象了。例如:

kubectl get crontab
NAME              AGE
new-cron-object   6s

这里的资源名是大小写不敏感的,我们在这里可以使用缩写 kubectl get ct,也可以使用 kubectl get crontabs。同时原生内置的 API 对象一样,这些 CRD 不仅可以通过 kubectl 来创建、查看、修改,删除等操作,还可以给其配置 RBAC 规则。

我们还可以开发自定义的控制器,来感知和操作这些自定义的 API。接下来我们就开始介绍。可以参考 定制资源 | Kubernetes(https://kubernetes.io/zh/docs/concepts/extend-kubernetes/api-extension/custom-resources/#我是否应该向我的-kubernetes-集群添加定制资源) 这份说明确定是否需要在 Kubernetes 中定义 API,还是让我们的 API 独立运行。

什么是 Kubernetes Operator

我们可能对 Operator 这个名字比较陌生。这个名字最早由 CoreOS(https://coreos.com/operators/) 在 2016 年提出来,我们来看看他们给出的定义:

An operator is a method of packaging, deploying and managing a Kubernetes application. A Kubernetes application is an application that is both deployed on Kubernetes and managed using the Kubernetes APIs and kubectl tooling.

To be able to make the most of Kubernetes, you need a set of cohensive APIs to extend in order to service and manage your applications that run on Kubernetes. You can think of Operators as the runtime that manages this type of application on Kubernetes.

简单概括一下,所谓的 Kubernetes Operator 其实就是借助 Kubernetes 的控制器模式,配合一些自定义的 API,完成对某一类应用的操作,比如资源创建、变更、删除等操作。

这里对 Kubernetes 的控制器模式做个简要说明。Kubernetes 通过声明式 API 来定义对象,各个控制器负责实时查看对应对象的状态,确保达到定义的期望状态。这就是 Kubernetes 的控制器模式。

fb2a7df6d3421395b9b26a96af99a599.png

kube-controller-manager 就是由这样一组控制器组成的。我们以 StatefulSet 为例来简单说明下控制器的具体逻辑。

假设我们声明了一个 StatefulSet,并将其副本数设置为 3。kube-controller-manager 中以 goroutine 方式运行的 StatefulSet 控制器在观察 kube-apiserver 的时候,发现了这个新创建的对象,它会先创建一个 index 为 0 的 Pod ,并实时观察这个 Pod 的状态,待其状态变为 Running 后,再创建 index 为 1 的 Pod。后续该控制器会一直观察并维护这些 Pod 的状态,保证 StatefulSet 的有效副本数始终为 3。

所以我们在声明完成 CRD 之后,也需要创建一个控制器,即 Operator,来完成对应的控制逻辑。在了解了 Operator 的概念和控制器模式后,我们来看看 Operator 是如何工作的。

Kubernetes Operator 是如何工作的

Operator 工作的时候采用上述的控制器模式,会持续地观察 Kubernetes 中的自定义对象,即 CRCustom Resource)。我们通过 CRD 来定义一个对象,CR 则是 CRD 实例化的对象。

609cf7900d31d39c7da12876dae30e28.png

Operator 会持续跟踪这些 CR 的变化事件,比如 ADDUPDATEDELETE,然后采取一系列操作,使其达到期望的状态。上述的流程其实还是有些复杂的,尤其是对运维同学有一定的门槛。好在社区提供了一些脚手架,可以方便我们快速地构建自己的 Operator

构建一个自己的 Kubernetes Operator

目前社区有一些可以用于创建 Kubernetes Operator 的开源项目,例如:Operator SDK(https://github.com/operator-framework/operator-sdk)、Kubebuilder(https://github.com/kubernetes-sigs/kubebuilder)、KUDO(https://github.com/kudobuilder/kudo)。我们这里以 Operator SDK 为例,接下来就安装 Operator SDK。

二进制安装 Operator SDK

前提条件

  • curl(https://curl.haxx.se/)

  • gpg(https://gnupg.org/) version 2.0+

  • 版本信息请参考:kubernetes/client-go: Go client for Kubernetes(https://github.com/kubernetes/client-go#compatibility-matrix). (github.com)

1、下载二进制文件

设置平台信息:

[root@blog ~]# export ARCH=$(case $(uname -m) in x86_64) echo -n amd64 ;; aarch64) echo -n arm64 ;; *) echo -n $(uname -m) ;; esac)
[root@blog ~]# export OS=$(uname | awk '{print tolower($0)}')

下载指定的文件:

[root@blog ~]# export OPERATOR_SDK_DL_URL=https://github.com/operator-framework/operator-sdk/releases/download/v1.16.0
[root@blog ~]# curl -LO ${OPERATOR_SDK_DL_URL}/operator-sdk_${OS}_${ARCH}

2、验证已下载的文件(可选)

keyserver.ubuntu.com 导入 operator-sdk 发行版的 GPG key :

[root@blog ~]# gpg --keyserver keyserver.ubuntu.com --recv-keys 052996E2A20B5C7E

下载 checksums 文件及其签名,然后验证签名:

[root@blog ~]# curl -LO ${OPERATOR_SDK_DL_URL}/checksums.txt
[root@blog ~]# curl -LO ${OPERATOR_SDK_DL_URL}/checksums.txt.asc
[root@blog ~]# gpg -u "Operator SDK (release) <cncf-operator-sdk@cncf.io>" --verify checksums.txt.asc

我们会看到一些类似下面的一些输出信息:

gpg: assuming signed data in 'checksums.txt'
gpg: Signature made Fri 30 Oct 2020 12:15:15 PM PDT
gpg:                using RSA key ADE83605E945FA5A1BD8639C59E5B47624962185
gpg: Good signature from "Operator SDK (release) <cncf-operator-sdk@cncf.io>" [ultimate]

确保 checksums 匹配:

[root@blog ~]# grep operator-sdk_${OS}_${ARCH} checksums.txt | sha256sum -c -
operator-sdk_linux_amd64: OK

确保类似下面的输出信息:

operator-sdk_linux_amd64: OK

3、把二进制文件放到 PATH 下面

[root@blog ~]# chmod +x operator-sdk_${OS}_${ARCH} && sudo mv operator-sdk_${OS}_${ARCH} /usr/local/bin/operator-sdk

源码编译安装 Operator SDK

前提条件

  • git(https://git-scm.com/downloads)

  • go version 1.16+

    • 确保 GOPROXY 设置为 "https://goproxy.cn"

[root@blog ~]# export GO111MODULE=on
[root@blog ~]# export GOPROXY=https://goproxy.cn

[root@blog ~]# git clone https://github.com/operator-framework/operator-sdk
[root@blog ~]# cd operator-sdk
[root@blog operator-sdk]# make install

验证版本:

[root@blog operator-sdk]# operator-sdk version
operator-sdk version: "v1.16.0", commit: "560044140c4f3d88677e4ef2872931f5bb97f255", kubernetes version: "1.21", go version: "go1.16.13", GOOS: "linux", GOARCH: "amd64"

# 由上述命令的输出来看,我们应该可以看出要使用的版本信息。
# 如我们使用的 operator-sdk 版本为:v1.16.0
# Go 的版本为:1.16.13
# Kubernetes 版本为:1.21

[root@blog operator-sdk]# go version
go version go1.16.13 linux/amd64

通过上述任何一种形式,就可以完成基础环境的搭建。接下来我们就创建一个 Operator。我们可以使用 Ansible、Helm 及 Go 结合 SDK 创建 Operator,使用 Ansible 及 Helm 的形式相对简单些。本文将使用 Go 的形式及 Operator SDK 来进行演示。

使用 Go 创建 Operator

Operator SDK 提供以下工作流来开发一个新的 Operator:

  1. 使用 SDK 创建一个新的 Operator 项目

  2. 通过添加自定义资源(CRD)定义新的资源 API

  3. 指定使用 SDK API 来 watch 的资源

  4. 定义 Operator 的协调(reconcile)逻辑

  5. 使用 Operator SDK 构建并生成 Operator 部署清单文件

前提条件

  • 参照前面的介绍进行安装 operator-sdk。

  • 要有 cluster-admin 权限。

  • 一个可以访问的 Operator 镜像(例如 hub.docker.com、quay.io),并可以在命令行环境中登录。

    • example.com 本例中在 Dockers Hub 上的一个命名空间。如果我们使用其他 registry 或命名空间的话,请相应的替换掉即可。

    • 如果 registry 是私有的,请准备好相关的认证或证书。

接下来我们就会按照下面的流程创建一个工程:

  • 如果不存在 Memcached Deployment 就创建一个

  • 确保 Deployment 中的 size 与 Memcached CR 中的 size一致

  • 使用带有 CR pod 名称的状态写入器更新 Memcached CR 的状态

创建工程

接下来使用命令行工具创建一个名为 memcached-operator 的工程:

[root@blog operator-sdk]# mkdir /root/memcached-operator
[root@blog operator-sdk]# cd /root/memcached-operator

[root@blog memcached-operator]# export GO111MODULE=on && export GOPROXY=https://goproxy.cn

[root@blog memcached-operator]# operator-sdk init \
--domain example.com \
--repo github.com/example/memcached-operator

Writing kustomize manifests for you to edit...
Writing scaffold for you to edit...
Get controller runtime:
$ go get sigs.k8s.io/controller-runtime@v0.10.0
Update dependencies:
$ go mod tidy
Next: define a resource with:
$ operator-sdk create api

创建完成之后,我们看一下代码的目录结构:

[root@blog memcached-operator]# tree -L 2
.
├── config
│   ├── default
│   ├── manager
│   ├── manifests
│   ├── prometheus
│   ├── rbac
│   └── scorecard
├── Dockerfile
├── go.mod
├── go.sum
├── hack
│   └── boilerplate.go.txt
├── main.go
├── Makefile
└── PROJECT

8 directories, 7 files

operator-sdk init 生成了一个 go.mod 文件。当我们的工程不在 $GOPATH/src 下面,则 --repo=<path> 选项是必须的,因为脚手架需要一个有效的 module 路径。

在使用 SDK 前,我们要确保开启了模块支持。需要设置:export GO111MODULE=on。为了加速下载 Go 的依赖,需要设置合适的代理。如:export GOPROXY=https://goproxy.cn

此时,我们可以使用 go build 命令构建:

[root@blog memcached-operator]# go build
[root@blog memcached-operator]# ll
total 44788
drwx------ 8 root root      100 Mar 30 21:01 config
-rw------- 1 root root      776 Mar 30 20:59 Dockerfile
-rw------- 1 root root      162 Mar 30 21:01 go.mod
-rw-r--r-- 1 root root    77000 Mar 30 21:01 go.sum
drwx------ 2 root root       32 Mar 30 20:59 hack
-rw------- 1 root root     2780 Mar 30 20:59 main.go
-rw------- 1 root root     9449 Mar 30 21:01 Makefile
-rwxr-xr-x 1 root root 45754092 Mar 30 21:02 memcached-operator
-rw------- 1 root root      235 Mar 30 21:01 PROJECT

目录结构中,还有一个 PROJECT 的文件,我们看看它里面有什么内容。

[root@blog memcached-operator]# cat PROJECT
domain: example.com
layout:
- go.kubebuilder.io/v3
plugins:
  manifests.sdk.operatorframework.io/v2: {}
  scorecard.sdk.operatorframework.io/v2: {}
projectName: memcached-operator
repo: github.com/example/memcached-operator
version: "3"

它主要是一些我们工程的配置信息。

Manager(管理器)

Operator 的主代码 main.go 主要是初始化并运行 Manager(https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/manager#Manager).有关管理器如何为自定义资源 API 定义注册 Scheme 以及设置和运行的更多详细信息,请参阅 Kubebuilder 入口文档(https://book.kubebuilder.io/cronjob-tutorial/empty-main.html) 控制器和 webhook。Manager 可以限制所有控制器监视资源的命名空间:

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
    Scheme:                 scheme,
    MetricsBindAddress:     metricsAddr,
    Port:                   9443,
    HealthProbeBindAddress: probeAddr,
    LeaderElection:         enableLeaderElection,
    LeaderElectionID:       "86f835c3.my.domain",
})

当然,我们也可以使用 MultiNamespacedCacheBuilder 来 watch 一组 namespace:

var namespaces []string // List of Namespaces

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), ctrl.Options{
    Scheme:                 scheme,
    NewCache:               cache.MultiNamespacedCacheBuilder(namespaces),
    MetricsBindAddress:     fmt.Sprintf("%s:%d", metricsHost, metricsPort),
    Port:                   9443,
    HealthProbeBindAddress: probeAddr,
    LeaderElection:         enableLeaderElection,
    LeaderElectionID:       "86f835c3.my.domain",
})

对于更新详细的信息,我们可以阅读 MultiNamespacedCacheBuilder (https://pkg.go.dev/sigs.k8s.io/controller-runtime/pkg/cache?tab=doc#MultiNa

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值