新钛云服已为您服务1489天
概述
随着我们对 Kubernetes 的逐步了解,可能就会发现 Kubernetes 中内置的对象定义,比如 Deployment
、StatefulSet
、ConfigMap
,可能已经不能满足我们的需求。我们希望在 Kubernetes 定义一些自己的对象,一是可以通过 kube-apiserver
提供统一的访问入口,二是可以像其他内置对象一样,通过 kubectl
命令管理这些自定义的对象。
Kubernetes 中提供了这种自定义对象的方式,其中之一就是 CRD
。
CRD 介绍
CRD
(CustomResourceDefinitions
)在 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 的控制器模式。
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 中的自定义对象,即 CR
(Custom Resource
)。我们通过 CRD
来定义一个对象,CR
则是 CRD
实例化的对象。
Operator
会持续跟踪这些 CR
的变化事件,比如 ADD
、UPDATE
、DELETE
,然后采取一系列操作,使其达到期望的状态。上述的流程其实还是有些复杂的,尤其是对运维同学有一定的门槛。好在社区提供了一些脚手架,可以方便我们快速地构建自己的 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:
使用 SDK 创建一个新的 Operator 项目
通过添加自定义资源(CRD)定义新的资源 API
指定使用 SDK API 来 watch 的资源
定义 Operator 的协调(reconcile)逻辑
使用 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