客座文章最初由红帽服务可靠性工程师Alexandre Menezes在CloudOps博客上发表
大多数应用程序都需要来自其运行环境的资源:内存、CPU、存储器、网络等等。这些资源中的大多数可以很容易地透明地使用,有些可能不依赖于应用程序。大多数应用程序在部署之前都需要一些前面的配置步骤,并且需要一些(或许很多)特殊的维护任务,这些任务可能与备份、恢复、文件压缩、高可用性检查、日志维护、数据库增长和健全例程等相关。例如,在升级时可能需要将它们置于某种特殊状态,以确保它们不会删除用户。
我们刚才描述的所有这些东西,都是应用程序之上的实用技术知识。在软件和服务软件的生命周期中,所有这些操作工作都要重复多次。当然,很多时候他们有一些脚本来自动化这些任务。但是,如果应用程序在容器中运行,在一个由Kubernetes或OpenShift编排的Pod中呢?有没有更好的方法,来实现自动化呢?可以“容错性好、易于管理和便于观察的松耦合系统。结合可靠的自动化手段,云原生技术使工程师能够轻松地对系统作出频繁和可预测的重大变更。”(来自云原生定义)
这个问题的答案就是操作器模式(operator pattern)。又称为Kubernetes Operators。那么,它们是什么呢?如何开发一个?它们可以向我们的应用程序添加什么?通过将它们发布到操作器中心,它们是如何添加到我们的软件作为一种服务体验的?
我个人喜欢给出的最好定义是,操作器是Kubernetes API的扩展,以自定义资源的形式,由部署后运行在Pod中的标准控制器协调/管理。似乎很复杂,对吧?让我们看一下这些部分。
扩展Kubernetes API
首先,让我们稍微后退一步,试着一点一点地理解它。我想问的第一个问题是,我们如何与Kubernetes互动?我们使用kubectl来从独立管理的角度部署和维护我们的应用程序,我们使用client-go和其他库来自动化与Kubernetes API的通信。好酷。API给了我们什么?
让我们看看Kubernetes API给我们什么:
所有这些特性在原生Kubernetes对象之间共享。许多设计良好的操作,如创建、读取、更新和删除、监视端点的功能、身份验证和授权等等。
我们知道Kubernetes资源构建在定义之上,这些定义来自于这个存储库中的Kubernetes API:
https://github.com/kubernetes...
在那里我们可以找到这些资源的组、版本和种类,对吧?这是直接进入名为TypeMeta字段的信息。让我们看一看!
如果我们得到一个资源,例如DaemonSet和运行:
$ kubectl get DaemonSet myDS -o yaml
在一开始,我们会看到如下内容:
apiVersion: apps/v1
kind: DaemonSet
这告诉我们DaemonSet在apps组下,版本是v1,是一种DaemonSet。我们在哪里可以找到对应的golang类型呢?我们只需要到存储库并找到types.go文档。像下图:
$ tree -L 2
...
├── apps
│ ├── OWNERS
│ ├── v1
│ ├── v1beta1
│ └── v1beta2
...
在v1文件夹中,我们有types.go,我们可以寻找DaemonSet类型如下:
type DaemonSet struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty" protobuf:"bytes,1,opt,name=metadata"`
// The desired behavior of this daemon set.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
// +optional
Spec DaemonSetSpec `json:"spec,omitempty" protobuf:"bytes,2,opt,name=spec"`
// The current status of this daemon set. This data may be
// out of date by some window of time.
// Populated by the system.
// Read-only.
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#spec-and-status
// +optional
Status DaemonSetStatus `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
}
如果我们可以以这种方式开发我们的应用程序,使其成为Kubernetes的原生部分,或者至少利用所有这些特性,只需输入kubectl get myapplication,并根据我的特定需求接收回信息,会怎么样呢?更进一步,如果我们能创建自己的更新例程和函数呢?如果我们可以利用嵌入的指标标准,并从Kubernetes构建深刻的见解,就像我们使用原始资源一样,会怎么样呢?
共享Kubernetes提供的所有好东西的很酷的功能是自定义资源(Custom Resources)和自定义资源定义(Custom Resource Definitions)。它们的行为与我们之前看到的原生Daemonsets非常相似。它们是Kubernetes API的扩展,允许我们创建自己的字段,构建完美的数据结构来表示我们的应用程序需求。它们允许我们有自己的API组、版本和类型。
这里你可以查看有关CRD和API扩展的更多信息。但我们已经完成了一半。我们还需要什么来实现这些定制资源?控制器(controller)。让我们检查一下!
控制器:使它成为Kubernetes原生
控制器只不过是一个循环。其思想是一个控制循环,在每次迭代中检查某些资源的状态。通过读取所需资源来检查其状态之后,控制循环将运行我们称为reconcile的函数,该函数将活动状态与给定对象的预期状态进行比较。这是Kubernetes的标准工作方式。
如果我们定义了代表应用程序的自定义对象,包括所有字段和必需的数据结构,那么后面的部分就是这个控制器及其reconcile函数。通过运行嵌入我们之前讨论过的操作知识的自定义逻辑,它确实让我们能够控制应用程序的状态。
如果你想了解更多关于控制器,可到这里。
Operator SDK:引导和构建
理解Kubernetes API的内部工作原理(符合OpenAPI标准)并不是一项容易的任务。我们也可以用API machinery SIG和控制器运行时库提供的所有工具,来创建与原生控制器运行时完全相同的控制器,以方便操作器框架的创建。在操作器框架提供的工具中,有operator-sdk命令行工具。让我们来看看它是如何帮助我们快速搭建所有必要的工具,以便只关注运算符逻辑的。
初始化一个新的操作器项目:
$ mkdir myproject
$ cd myproject
$ operator-sdk init --domain mydomain.com --group myapp --kind MyApp --version v1alpha1
运行后产生一个go项目,文件夹将包括最小的元素以开发和构建操作器。
.
├── Dockerfile
├── Makefile
├── PROJECT
├── bin
├── config
├── go.mod
├── go.sum
├── hack
└── main.go
我们有用于构建操作器的基本Dockerfile,一个具有测试和构建所需的所有自动化的Makefile,还有一个配置文件夹,其中所有yaml工件都将在Kustomize和main.go的支持下运行,从运行控制器的管理器开始。要为我们的自定义应用程序添加一个带有控制器的新的API/CRD端点,我们运行下面的例子:
$ operator-sdk create api
--group=myapp
--version=v1alpha1
--kind=MyApp
--resource
--controller
现在我们有两个新的文件夹:
.
├── Dockerfile
├── Makefile
├── PROJECT
├── api
├── bin
├── config
├── controllers
├── go.mod
├── go.sum
├── hack
└── main.go
文件夹api和controllers。我们能找到所有自动生成的代码来开始过程。
在api中我们发现:
$ tree -L 2 api
api
└── v1alpha1
├── groupversion_info.go
├── myapp_types.go
└── zz_generated.deepcopy.go
最后在控制器端,我们有:
$ tree -L 2 controllers
controllers
├── myapp_controller.go
└── suite_test.go
myapp_controller.go会为我们提供所有控制器逻辑。
也为你准备好reconcile函数插入你的代码:
func (r *MyAppReconciler) Reconcile(req ctrl.Request) (ctrl.Result, error) {
_ = context.Background()
_ = r.Log.WithValues("myapp", req.NamespacedName)
// your logic here
return ctrl.Result{}, nil
}
为了更好地理解这个过程,我强烈推荐两个教程:
kubebuilder手册。Kubebuilder刚刚合并到operator-sdk中,其中很好的一部分逻辑来自kubebuilder项目。因此,要更深入地理解Kubernetes API和控制器逻辑,这里可能是最好的起点。
最后,我十分建议你查看一下operatoror-sdk网站,在那里你也可以找到大量的资源和示例。
操作器生命周期管理器:发布操作器
操作器框架中的另一个关键项目是Operator Lifecycle Manager(操作器生命周期管理器),它充当你的软件目录,向kubernetes提供一个作为服务应用程序的软件,所有公开发布的操作器都可以从该应用程序中安装。了解项目和更多相关信息。
总结
我们讨论了Kubernetes操作器是什么,以及它们是如何由Kubernetes自定义资源和控制器这两个基本但强大的部分组成的。我们略微涉及了operator-sdk,它帮助我们搭建所有代码,以便轻松开始开发Kubernetes原生应用程序,该应用程序将与api通信,并控制表示集群内应用程序的自定义资源。我们建议在网站上查看Kubebuilder手册和operator-sdk文档。最后,我们指出操作器生命周期管理器是所有公共操作器都可以在其中找到的官方目录。
CNCF (Cloud Native Computing Foundation)成立于2015年12月,隶属于Linux Foundation,是非营利性组织。
CNCF(云原生计算基金会)致力于培育和维护一个厂商中立的开源生态系统,来推广云原生技术。我们通过将最前沿的模式民主化,让这些创新为大众所用。扫描二维码关注CNCF微信公众号。