Kubernetes-算子教程-全-
Kubernetes 算子教程(全)
原文:
zh.annas-archive.org/md5/f314a3d1af11ee8eb7b2bffa3d672926译者:飞龙
序言
Kubernetes 是一个流行的容器编排器。它将许多计算机汇集在一起,形成一个大型计算资源,并通过 Kubernetes 应用程序编程接口(API)建立了一种访问该资源的方式。Kubernetes 是由 Google 发起的开源软件,在 Cloud Native Computing Foundation (CNCF) 的赞助下,通过一大群合作者在过去五年中开发而成。
运算符扩展 Kubernetes,自动化特定应用程序的整个生命周期管理。运算符作为在 Kubernetes 上分发应用程序的打包机制,它们监视、维护、恢复和升级它们所部署的软件。
本书的读者对象
如果您已在 Kubernetes 集群上部署过应用程序,您将熟悉一些塑造运算符模式的挑战和愿望。如果您已经在自己的编排集群之外维护基础服务(如数据库和文件系统),并且希望将它们引入这个领域,那么这本关于 Kubernetes 运算符的指南就是为您准备的。
您将学到什么
本书解释了什么是运算符,以及运算符如何扩展 Kubernetes API。它展示了如何部署和使用现有的运算符,以及如何使用 Red Hat Operator Framework 为您的应用程序创建和分发运算符。我们介绍了设计、构建和分发运算符的良好实践,并解释了通过可靠性工程(SRE)原则赋予运算符活力的思维方式。
在第一章描述了运算符及其概念之后,我们将建议如何访问 Kubernetes 集群,在这里您可以进行本书其余部分的练习。有了运行中的集群,您将部署一个运算符,并观察其在应用程序失败、扩展或升级到新版本时的行为。
后来,我们将探索运算符 SDK,并向您展示如何使用它将一个示例应用程序建立为 Kubernetes 的一流公民。在此实际基础上,我们将讨论运算符源自的 SRE 思想以及它们所共享的目标:减少操作工作和成本,提高服务可靠性,并通过解放团队免受重复性维护工作来推动创新。
运算符框架和 SDK
运算符模式是在 CoreOS 提出的,作为在 Kubernetes 集群上自动化越来越复杂应用程序的一种方式,包括管理 Kubernetes 本身以及其核心的 etcd 键值存储。运算符的工作通过被 Red Hat 收购继续进行,导致了开源运算符框架和 SDK 在 2018 年的发布。本书的示例使用了 Red Hat Operator SDK 和加入 Operator Framework 的分发机制。
其他运算符工具
已经形成了围绕 Operators 的社区,仅在 Red Hat 的分发渠道中就有超过一百个 Operators,涵盖了各种供应商和项目的应用。还存在其他几个 Operator 构建工具。我们不会详细讨论它们,但在阅读本书后,您将能够将它们与 Operator SDK 和 Framework 进行比较。用于构建 Operators 的其他开源工具包括Kopf(用于 Python)、Kubebuilder(来自 Kubernetes 项目)和Java Operator SDK。
本书使用的约定
本书使用以下排版约定:
Italic
表示新术语、URL、电子邮件地址、文件名和文件扩展名。
Constant width
用于程序列表以及段落内用来指代程序元素(如变量或函数名称、数据库、数据类型、环境变量、语句和关键字)的代码。
Constant width bold
展示用户应该按字面意思输入的命令或其他文本。
Constant width italic
展示应该由用户提供的值或由上下文确定的值替换的文本。
提示
此元素表示提示或建议。
注意
此元素表示一般注释。
警告
此元素表示警告或注意事项。
使用代码示例
附加材料(代码示例、练习等)可在https://github.com/kubernetes-operators-book/下载。
如果您有技术问题或使用代码示例时遇到问题,请发送电子邮件至bookquestions@oreilly.com。
本书旨在帮助您完成工作。一般来说,如果本书提供了示例代码,您可以在您的程序和文档中使用它。除非您正在复制代码的大部分内容,否则您无需联系我们以获取许可。例如,编写一个使用本书多个代码片段的程序并不需要许可。出售或分发来自 O'Reilly 书籍的示例代码需要许可。通过引用本书并引用示例代码来回答问题不需要许可。将本书大量示例代码纳入产品文档中需要许可。
我们感谢,但通常不需要归属。归属通常包括标题、作者、出版商和 ISBN。例如:“Kubernetes Operators by Jason Dobies and Joshua Wood (O’Reilly). Copyright 2020 Red Hat, Inc., 978-1-492-04804-6.”
如果您觉得您使用的代码示例超出了合理使用范围或上述许可,请随时通过permissions@oreilly.com与我们联系。
O’Reilly Online Learning
注意
O’Reilly Media 在过去 40 多年里,提供技术和商业培训、知识和见解,帮助公司取得成功。
我们独特的专家和创新者网络通过图书、文章、会议和我们的在线学习平台分享他们的知识和专长。O’Reilly 的在线学习平台为您提供按需访问实时培训课程、深入学习路径、交互式编码环境,以及来自 O’Reilly 和其他 200 多家出版商的大量文本和视频。有关更多信息,请访问 http://oreilly.com。
如何联系我们
请将有关本书的评论和问题发送至出版商:
-
O’Reilly Media, Inc.
-
1005 Gravenstein Highway North
-
Sebastopol, CA 95472
-
800-998-9938(美国或加拿大)
-
707-829-0515(国际或本地)
-
707-829-0104(传真)
我们为这本书建立了一个网页,列出勘误、示例和任何额外信息。您可以访问此页面 https://oreil.ly/Kubernetes_Operators。
发送电子邮件至 bookquestions@oreilly.com 进行评论或提出技术问题。
有关我们的图书、课程和会议的更多信息,请访问 http://www.oreilly.com。
在 Facebook 上找到我们:http://facebook.com/oreilly
在 Twitter 上关注我们:http://twitter.com/oreillymedia
在 YouTube 上关注我们:http://www.youtube.com/oreillymedia
致谢
我们要感谢 Red Hat 公司和 OpenShift 倡导团队的支持,特别感谢 Ryan Jarvinen 的坚定和全方位的帮助。我们还要感谢许多人对这项工作进行了审查、检查、建议,以及其他付出时间使这项工作更加连贯和完整的人,其中包括 Anish Asthana、Evan Cordell、Michael Gasch、Michael Hausenblas、Shawn Hurley 和 Jess Males。
第一章:操作员教 Kubernetes 新技巧
操作员是打包、运行和维护 Kubernetes 应用程序的一种方式。Kubernetes 应用程序不仅部署在 Kubernetes 上,还设计为与 Kubernetes 的设施和工具协同使用和运行。
操作员建立在 Kubernetes 抽象之上,自动化管理它所管理的软件的整个生命周期。因为它们扩展了 Kubernetes,操作员在一个庞大且不断增长的社区中提供应用程序特定的自动化。对于应用程序程序员来说,操作员使得部署和运行其应用程序所依赖的基础服务变得更容易。对于基础设施工程师和供应商来说,操作员提供了在 Kubernetes 集群上分发软件的一致方法,并通过在报警器响起之前识别和修正应用程序问题来减少支持负担。
在我们开始描述操作员如何完成这些工作之前,让我们定义一些 Kubernetes 术语,以提供上下文和共享语言,描述操作员的概念和组件。
Kubernetes 的工作原理
Kubernetes 自动化状态无关应用程序(如静态 Web 服务器)的生命周期。没有状态,应用程序的任何实例都是可互换的。这个简单的 Web 服务器检索文件并将它们发送到访客的浏览器上。因为服务器不跟踪状态或存储任何输入或数据,当一个服务器实例失败时,Kubernetes 可以用另一个替换它。Kubernetes 将这些实例称为集群中运行的应用程序副本的每个副本。
Kubernetes 集群是一组计算机,称为 节点。所有集群工作在一个、一些或所有集群节点上运行。工作的基本单位和复制单位是 Pod。Pod 是一组一个或多个 Linux 容器,具有共享网络、存储和访问共享内存等资源。
注意
Kubernetes Pod 文档 是了解 Pod 抽象更多信息的好起点。
从高层次来看,Kubernetes 集群可以分为两个平面。控制平面 简单来说就是 Kubernetes 本身。一组 Pod 组成了控制平面,并实现了 Kubernetes 应用程序编程接口(API)和集群编排逻辑。
应用平面 或 数据平面 是其他所有的内容。这是应用 Pod 运行的节点组。通常有一个或多个节点专门用于运行应用程序,而有一个或多个节点通常被隔离出来,仅用于运行控制平面 Pod。与应用 Pod 一样,控制平面组件的多个副本可以在多个控制器节点上运行,以提供冗余性。
控制平面的控制器实现控制循环,不断比较集群的期望状态与实际状态。当两者不一致时,控制器会采取行动使它们匹配。Operator 扩展了这种行为。图 1-1 中的示意图展示了主要的控制平面组件,工作节点上运行应用工作负载。
尽管控制平面和应用平面之间的严格分隔是一种便捷的心理模型和部署 Kubernetes 集群的常见方式,但控制平面组件是运行在节点上的一组 Pod,就像任何其他应用程序一样。在小集群中,控制平面组件通常与应用工作负载共享同一或两个节点。
被隔离的控制平面的概念模型也并非如此整洁。例如,运行在每个节点上的 kubelet 代理就是控制平面的一部分。同样,Operator 是一种控制器类型,通常被视为控制平面组件。然而,Operator 可能会模糊这两个平面之间的明确边界。将控制和应用平面视为孤立的域是一种有用的简化抽象,而非绝对真理。

图 1-1. Kubernetes 控制平面和工作节点
示例:无状态 Web 服务器
由于您尚未设置集群,本章节的示例更像是终端摘录的“屏幕截图”,显示了 Kubernetes 与应用程序之间的基本交互。不要期望像在本书的其余部分一样执行这些命令。在这个第一个示例中,Kubernetes 管理一个相对简单的应用程序,并且没有涉及 Operator。
考虑一个运行单个无状态静态 Web 服务器副本的集群:
$ kubectl get pods>
NAME READY STATUS RESTARTS AGE
staticweb-69ccd6d6c-9mr8l 1/1 Running 0 23s
在声明应有三个副本后,集群的实际状态与期望状态不同,Kubernetes 启动了两个新的 Web 服务器实例来调和二者,扩展 Web 服务器部署:
$ kubectl scale deployment staticweb --replicas=3
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
staticweb-69ccd6d6c-4tdhk 1/1 Running 0 6s
staticweb-69ccd6d6c-9mr8l 1/1 Running 0 100s
staticweb-69ccd6d6c-m9qc7 1/1 Running 0 6s
删除其中一个 Web 服务器 Pod 将触发控制平面中的工作,以恢复三个副本的期望状态。Kubernetes 启动一个新的 Pod 来替换已删除的 Pod。在本节中,替换的 Pod 显示为STATUS为ContainerCreating:
$ kubectl delete pod staticweb-69ccd6d6c-9mr8l
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
staticweb-69ccd6d6c-4tdhk 1/1 Running 0 2m8s
staticweb-69ccd6d6c-bk27p 0/1 ContainerCreating 0 14s
staticweb-69ccd6d6c-m9qc7 1/1 Running 0 2m8s
这个静态站点的 Web 服务器可以与任何其他副本互换,或者与替换副本之一的新 Pod 互换。它不会以任何方式存储数据或维护状态。Kubernetes 不需要做任何特殊安排来替换失败的 Pod,或者通过添加或删除服务器的副本来扩展应用程序。
状态固态难
大多数应用程序都有状态。它们还有启动细节、组件相互依赖和配置的特定内容。它们通常有自己对“集群”含义的理解。它们需要可靠地存储关键且有时是庞大的数据。这些只是现实世界中应用程序必须维护状态的三个方面之一。理想情况下,可以通过统一的机制管理这些应用程序,同时自动化它们复杂的存储、网络和集群连接需求。
Kubernetes 不能同时了解每个有状态、复杂且集群化的应用程序的所有细节,同时保持通用、适应性和简单性。它的目标是提供一组灵活的抽象,涵盖调度、复制和故障转移自动化的基本应用程序概念,同时为更高级或特定于应用程序的操作提供清晰的扩展机制。Kubernetes 本身不应该也不会知道例如 PostgreSQL 数据库集群的配置值,以及其安排的成员和有状态的持久存储。
操作员是软件 SRE
站点可靠性工程(SRE)是运行大型系统的一组模式和原则。起源于 Google,SRE 对行业实践产生了显著影响。实践者必须解释并应用 SRE 理念到特定的情况中,但其关键原则之一是通过编写软件来运行您的软件来自动化系统管理。从机械性维护工作中解放出来的团队有更多时间创建新功能、修复错误并总体改进其产品。
操作员类似于应用程序的自动化站点可靠性工程师。它将一个专家管理员的技能编码到软件中。例如,操作员可以管理一个数据库服务器集群。它了解配置和管理应用程序的详细信息,并且可以安装声明的软件版本和成员数量的数据库集群。操作员在应用程序运行时继续监视其运行状态,可以备份数据、从故障中恢复,并自动随时间升级应用程序。集群用户使用kubectl和其他标准工具与操作员和它们管理的应用程序一起工作,因为操作员扩展了 Kubernetes 的功能。
操作员的工作原理
操作员通过扩展 Kubernetes 控制平面和 API 来工作。在其最简单的形式中,操作员向 Kubernetes API 添加一个称为自定义资源(CR)的端点,以及一个监视和维护新类型资源的控制平面组件。然后,操作员可以根据资源的状态采取行动。这在图 1-2 中有所说明。

图 1-2. 操作员是监视自定义资源的自定义控制器
Kubernetes CRs
CR 是 Kubernetes 中的 API 扩展机制。自定义资源定义(CRD)定义了一个 CR;它类似于 CR 数据的模式。与官方 API 的成员不同,给定的 CRD 不会存在于每个 Kubernetes 集群中。CRD 扩展了它们定义的特定集群的 API。CR 为读写结构化数据提供端点。集群用户可以像处理其他 API 资源一样使用 kubectl 或其他 Kubernetes 客户端与 CR 交互。
如何创建操作员
Kubernetes 将一组资源与实际情况进行比较;即集群的运行状态。它采取行动使实际情况与这些资源描述的期望状态匹配。操作员将此模式扩展到特定集群上的特定应用程序。操作员是一个自定义 Kubernetes 控制器,监视一个 CR 类型,并采取特定应用程序的操作,以使实际情况与该资源中的 spec 匹配。
制作一个操作员意味着创建一个 CRD 并提供一个在循环中运行的程序,监视该类 CR。操作员对 CR 中的更改作出响应的方式取决于操作员管理的应用程序。操作员执行的操作几乎可以是任何事情:扩展复杂应用程序,应用程序版本升级,甚至管理具有专用硬件的计算集群节点的内核模块。
示例:etcd 操作员
etcd 是一个分布式键值存储。换句话说,它是一种轻量级数据库集群。etcd 集群通常需要一个熟练的管理员来管理。etcd 管理员必须知道如何:
-
加入新节点到 etcd 集群,包括配置其端点,建立与持久存储的连接,并让现有成员知道它。
-
备份 etcd 集群数据和配置。
-
将 etcd 集群升级到新的 etcd 版本。
etcd 操作员知道如何执行这些任务。操作员了解其应用程序的内部状态,并定期采取行动,以使该状态与一个或多个自定义资源规范中表达的期望状态保持一致。
与前面的示例类似,接下来的 shell 摘录仅作说明,如果没有事先设置,您将无法执行它们。您将在 第二章 中进行设置并运行操作员。
缺失成员的案例
由于 etcd 操作员了解 etcd 的状态,它可以像 Kubernetes 在前面示例中替换已删除的无状态 Web 服务器 pod 一样从 etcd 集群成员的故障中恢复。假设由 etcd 操作员管理的三节点 etcd 集群本身和 etcd 集群成员都作为 pod 运行:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
etcd-operator-6f44498865-lv7b9 1/1 Running 0 1h
example-etcd-cluster-cpnwr62qgl 1/1 Running 0 1h
example-etcd-cluster-fff78tmpxr 1/1 Running 0 1h
example-etcd-cluster-lrlk7xwb2k 1/1 Running 0 1h
删除一个 etcd Pod 将触发一次协调过程,而 etcd Operator 知道如何恢复到三个副本的所需状态——这是 Kubernetes 单独无法完成的。但与无状态 Web 服务器的空白重启不同,Operator 必须安排新的 etcd Pod 的集群成员身份,配置它以适应现有的端点,并与其余的 etcd 成员建立连接:
$ kubectl delete pod example-etcd-cluster-cpnwr62qgl
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
etcd-operator-6f44498865-lv7b9 1/1 Running 0 1h
example-etcd-cluster-fff78tmpxr 1/1 Running 0 1h
example-etcd-cluster-lrlk7xwb2k 1/1 Running 0 1h
example-etcd-cluster-r6cb8g2qqw 0/1 PodInitializing 0 4s 
替换的 Pod 处于PodInitializing状态。
在 Operator 修复 etcd 集群时,etcd API 仍然对客户端可用。在第二章中,您将部署 etcd Operator 并使用 etcd API 来读取和写入数据。现在值得记住的是,向运行中的 etcd 集群添加成员并不像只运行新的 etcd Pod 那样简单,而 etcd Operator 隐藏了这种复杂性并自动修复 etcd 集群。
Operator 适合谁?
Operator 模式是为了响应基础设施工程师和开发人员希望扩展 Kubernetes 以提供特定于其站点和软件的功能而出现的。Operators 使集群管理员能够更轻松地启用,并使开发人员能够使用像数据库和存储系统这样的基础软件组件,减少管理开销。例如,如果“killernewdb”数据库服务器适合您的应用程序的后端,并且有一个 Operator 来管理它,您可以部署 killernewdb 而无需成为专家级的 killernewdb DBA。
应用程序开发人员构建 Operators 来管理他们正在交付的应用程序,简化其客户的 Kubernetes 集群上的部署和管理体验。基础设施工程师创建 Operators 来控制已部署的服务和系统。
Operator 采用
许多开发人员和公司已经采用了 Operator 模式,并且已经有许多 Operators 可用,使得作为应用程序组件使用关键服务更加容易。例如,CrunchyData 开发了一个管理 PostgreSQL 数据库集群的 Operator。还有流行的 MongoDB 和 Redis 的 Operators。Rook 管理在 Kubernetes 集群上的 Ceph 存储,而其他 Operators 则提供对外部存储服务(如 Amazon S3)的集群内管理。
此外,基于 Kubernetes 的发行版,如 Red Hat 的 OpenShift,使用 Operators 在 Kubernetes 核心之上构建功能,以确保 OpenShift Web 控制台可用且最新。例如,在用户端,OpenShift 已添加了在 Web 控制台中进行点-and-click Operator 安装和使用的机制,并为 Operator 开发人员提供了与OperatorHub.io集成的方法,详见第八章和第十章。
让我们开始吧!
运营商需要一个 Kubernetes 集群来运行。在下一章中,我们将向您展示几种获取集群访问权限的方式,无论是在笔记本电脑上的本地虚拟 Kubernetes,还是在某些节点上的完整安装,或者是外部服务。一旦您具有 Kubernetes 集群的管理员访问权限,您将部署 etcd Operator 并查看它如何代表您管理 etcd 集群。
第二章:运行运算符
在本章的第一节中,我们概述了运行本书示例的要求,并提供建议,说明如何建立符合这些要求的 Kubernetes 集群访问权限。在第二节中,您将使用该集群安装和使用运算符,了解运算符的功能。
到最后,您将拥有一个 Kubernetes 集群作为运算符测试基地,并且将知道如何从一组清单中在其上部署现有的运算符。您还将看到一个运算符在面对变化和故障时管理其应用程序特定内部状态的示例,这将增强您对运算符架构和后续章节中介绍的构建工具的理解。
设置运算符实验室
要在以下章节中构建、测试和运行运算符,您需要cluster-admin访问权限,访问运行 Kubernetes 版本 v1.11.0 或更高版本的集群。如果您已经满足这些要求,可以直接跳到下一节。在本节中,我们为需要设置 Kubernetes 集群或需要运算符开发和测试的本地环境的读者提供一般建议。
集群版本要求
我们已经测试了本书中的示例,涵盖 Kubernetes 版本 v1.11 到 v1.16。当我们检查的任何功能或操作需要比 v1.11 更高版本时,我们将声明。
控制平面的可扩展性
Kubernetes 1.2 版本引入了 API 扩展机制,被称为第三方资源(TPR),基本形式是图 2-1 中所示。自那时起,构建运算符的组件已经多样化和成熟化。CRD 在 Kubernetes 1.7 版本中正式化。

图 2-1. Kubernetes 发布的可扩展性功能
正如您在第一章中所看到的,CRD 是特定集群 Kubernetes API 中新的、特定站点资源(或 API 端点)的定义。CRD 是运算符模式的两个基本构建块之一:一个自定义控制器管理 CR。
授权要求
由于运算符扩展了 Kubernetes 本身,您需要拥有特权的、整个集群范围的 Kubernetes 访问权限来部署它们,例如常见的cluster-admin集群角色。
注意
较低权限的用户可以使用运算符管理的服务和应用程序——“操作数”。
尽管在生产环境中应配置更精细的 Kubernetes 基于角色的访问控制(RBAC),但完全控制您的集群意味着您可以立即部署 CRD 和运算符。随着您开发运算符及其管理的应用程序的角色、服务帐户和绑定,您还将有权声明更详细的 RBAC。
您可以询问 Kubernetes API 关于 cluster-admin 角色是否存在于您的集群中。以下 shell 摘录显示了如何使用kubectl的describe子命令获取角色的摘要:
$ `kubectl` `describe` `clusterrole` `cluster-admin`
Name: cluster-admin 
Labels: kubernetes.io/bootstrapping=rbac-defaults
PolicyRule:
Resources Non-Resource URLs Resource Names Verbs
--------- ----------------- -------------- -----
*.* [] [] [*]
[*] [] [*]
RBAC cluster-admin ClusterRole:任何事情都可以。
标准工具和技术
运营商旨在使其管理的复杂应用程序成为 Kubernetes API 的一级公民。我们将在后续章节的示例中展示这意味着什么。在此阶段,这意味着部署和与集群上的基本运营商交互的唯一要求是使用最新版本的命令行 Kubernetes API 工具kubectl。
需要安装或更新kubectl的读者应参考当前的 kubectl 文档。
注意
使用 Red Hat OpenShift Kubernetes 分发的用户(如下所述),可以选择(并可以互换地)使用oc OpenShift API 实用程序代替kubectl。
建议的集群配置
在你可以部署运营商的 Kubernetes 集群中,有许多方法。如前所述,如果你已经在运行最新的 Kubernetes 版本,则可以跳过这些建议,直接进入“运行一个简单的运营商”。如果没有,我们已经测试了本节描述的 Kubernetes 打包或分发版本,足以支持本书中的练习。
Minikube
Minikube v1.5.2 部署 Kubernetes v1.16.2。它在您本地系统的虚拟机(VM)上运行单节点 Kubernetes 集群的超级处理器。默认情况下,Minikube 期望使用 VirtualBox,因为其广泛可用,但通过几个额外步骤,它也可以使用您平台的本机超级处理器,如 Linux 上的 KVM,Windows 上的 Hyper-V,或 macOS 上的 HyperKit 和 Hypervisor.framework。我们在这里避免详细的安装说明,因为这些最好留给Minikube 文档。我们已经通过 Minikube 最彻底地测试了本书中的示例,并且基于便利性和成本的考虑,我们建议您使用类似的本地环境,如 CodeReady Containers(请参阅下一节),或者使用Kubernetes in Docker (kind)。
Red Hat OpenShift
OpenShift 是 Red Hat 的 Kubernetes 发行版。您可以在 OpenShift 上进行与等效核心版本的 Kubernetes 相同的操作。(OpenShift 还内置了一些基于 Kubernetes 的特定功能,但这超出了本书的范围。)OpenShift 版本 4 提供了一个全功能的 Kubernetes 发行版,它本身使用 Operators 进行设计、交付和管理。它是一个“自托管”的 Kubernetes,能够在不影响托管工作负载的情况下执行原地平台升级。OpenShift 包括 Operator 生命周期管理器,详见第四章,以及一个操作员目录分发机制的图形界面。
您可以通过访问 Red Hat 的免费试用许可证,部署完整的 OpenShift v4 集群到 Amazon Web Services(AWS)、Microsoft Azure 或 Google Cloud Platform,网址为https://try.openshift.com。
提示
要在您的笔记本上运行 OpenShift,请查看 Minikube 的等效工具,Red Hat CodeReady Containers。
检查您的集群版本
通过运行kubectl version命令验证您的集群是否运行在 Kubernetes v1.11 或更高版本。该命令将为您的kubectl二进制文件返回一个 API 版本字符串,并为其连接的集群返回第二个版本字符串:
$ kubectl version
Client Version: version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.2",
GitCommit:"c97fe5036ef3df2967d086711e6c0c405941e14b", GitTreeState:"clean",
BuildDate:"2019-10-15T19:18:23Z", GoVersion:"go1.12.10", Compiler:"gc",
Platform:"darwin/amd64"}
Server Version: version.Info{Major:"1", Minor:"16", GitVersion:"v1.16.2",
GitCommit:"c97fe5036ef3df2967d086711e6c0c405941e14b", GitTreeState:"clean",
BuildDate:"2019-10-15T19:09:08Z", GoVersion:"go1.12.10", Compiler:"gc",
Platform:"linux/amd64"}
在上述输出中,客户端和服务器均运行 Kubernetes 版本 1.16.2。虽然a kubectl客户端在服务器上的一次更新之后应该能正常工作,为简单起见,确保您的客户端和服务器次要版本匹配。如果您有 v1.11 或更高版本,则可以开始尝试使用 Operators。
运行一个简单的 Operator
一旦验证您具有兼容版本的 Kubernetes 集群的特权访问权限,您就可以部署一个 Operator 并查看 Operators 的功能。稍后,您将再次看到相同过程的框架,当您部署和测试构建的 Operator 时。etcd Operator 通过简单的自动化恢复和升级展示了 Kubernetes Operators 的原理和目标。
一个常见的起点
etcd是一个分布式键值存储,起源于 CoreOS,现在由 Cloud Native Computing Foundation 管理。它是 Kubernetes 核心下的基础数据存储,并且是几个分布式应用程序的关键部分。etcd 通过实现一种称为Raft的协议来提供可靠的存储,保证了多数成员之间的一致性。
etcd 操作员通常作为操作员模式的价值和机制的“Hello World”示例,我们在这里遵循这一传统。我们之所以回到它,是因为展示 etcd 的最基本用法并不难,但 etcd 集群的设置和管理需要精通特定应用程序知识的操作员。要使用 etcd,您可以通过名称put键和值,并通过名称get它们。创建一个可靠的最小三个或更多节点的 etcd 集群需要配置端点、认证和其他通常留给 etcd 专家(或其自定义 shell 脚本集合)的问题。随着时间的推移保持 etcd 的运行和升级需要持续的管理。etcd 操作员知道如何做到这一点。
在接下来的章节中,您将部署 etcd 操作员,然后让它根据您的规范创建 etcd 集群。您将让操作员从故障中恢复并执行版本升级,同时 etcd API 继续服务读写请求,展示了操作员如何自动化基础软件的生命周期。
注意
您可以在运行的 OpenShift 集群上无需任何设置即可按照OpenShift 学习门户上的示例进行操作。
获取 etcd 操作员清单
本书为每章的示例代码提供了一个伴随的 Git 仓库。获取chapters仓库并切换到第三章的示例目录,如下所示:
$ `git` `clone` `https://github.com/kubernetes-operators-book/chapters.git`
$ `cd` `chapters/ch03`
CRs:自定义 API 端点
与 Kubernetes 中的几乎所有内容一样,YAML 清单描述了 CRD。CR 是 Kubernetes API 中的命名端点。名为etcdclusters.etcd.database.coreos.com的 CRD 表示新的端点类型。
创建 CRD
CRD 定义了 CR 实例内的类型和值。此示例定义了新的kind资源类型,即 EtcdCluster。
使用cat、less或您喜欢的分页程序阅读名为etcd-operator-crd.yaml的文件。您将看到类似以下内容的 YAML,该 YAML 指定了 EtcdCluster CRD 的规范:
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: etcdclusters.etcd.database.coreos.com
spec:
group: etcd.database.coreos.com
names:
kind: EtcdCluster
listKind: EtcdClusterList
plural: etcdclusters
shortNames:
- etcdclus
- etcd
singular: etcdcluster
scope: Namespaced
versions:
- name: v1beta2
served: true
storage: true
CRD 定义了 Kubernetes API 应如何引用此新资源。在这里也定义了缩写的昵称,以帮助您在kubectl中少打一些字。
在您的集群上创建 CRD:
$ `kubectl` `create` `-f` `etcd-operator-crd.yaml`
一次快速检查显示新的 CRD,etcdclusters.etcd.database.coreos.com:
$ `kubectl` `get` `crd`
NAME CREATED AT
etcdclusters.etcd.database.coreos.com 2019-11-15T02:50:14Z
注意
CR 的组、版本和类型共同形成 Kubernetes 资源类型的完全限定名称。该规范名称在整个集群中必须是唯一的。您创建的 CRD 代表了etcd.database.coreos.com组的v1beta2版本和EtcdCluster类型的资源。
我是谁:定义操作员服务账户
在第三章中,我们概述了 Kubernetes 授权并定义了服务账户、角色和其他授权概念。目前,我们只想初步了解服务账户的基本声明及其运行 etcd 操作员所需的能力。
文件 etcd-operator-sa.yaml 定义了服务账户:
apiVersion: v1
kind: ServiceAccount
metadata:
name: etcd-operator-sa
使用 kubectl create 创建服务账户:
$ `kubectl` `create` `-f` `etcd-operator-sa.yaml`
serviceaccount/etcd-operator-sa created
如果你检查集群服务账户列表,你会看到它出现了:
$ kubectl get serviceaccounts
NAME SECRETS AGE
builder 2 2h
default 3 2h
deployer 2 2h
etcd-operator-sa `2` 3s
[...]
角色
管理服务账户的角色定义在名为 etcd-operator-role.yaml 的文件中。我们将详细讨论 RBAC 留在后面的章节和附录 C 中,但角色清单中的关键项目是非常明显的。我们给角色一个名称,以便我们可以在其他地方引用它:etcd-operator-role。YAML 继续列出角色可能使用的资源类型以及它们可以做的事情,也就是说,它可以说的动词:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: etcd-operator-role
rules:
- apiGroups:
- etcd.database.coreos.com
resources:
- etcdclusters
- etcdbackups
- etcdrestores
verbs:
- '*'
- apiGroups:
- ""
resources:
- pods
- services
- endpoints
- persistentvolumeclaims
- events
verbs:
- '*'
- apiGroups:
- apps
resources:
- deployments
verbs:
- '*'
- apiGroups:
- ""
resources:
- secrets
verbs:
- get
如同服务账户一样,用 kubectl 创建角色:
$ `kubectl` `create` `-f` `etcd-operator-role.yaml`
role.rbac.authorization.k8s.io/etcd-operator-role created
角色绑定
最后一部分 RBAC 配置,RoleBinding,将角色分配给 etcd 操作员的服务账户。它声明在文件 etcd-operator-rolebinding.yaml 中:
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: etcd-operator-rolebinding
roleRef:
apiGroup: rbac.authorization.k8s.io
kind: Role
name: etcd-operator-role
subjects:
- kind: ServiceAccount
name: etcd-operator-sa
namespace: default
注意最后一行。如果你在一个全新的 OpenShift 集群上,比如由 CodeReady Containers 提供的,默认情况下你的 kubectl 或 oc 命令将在命名空间 myproject 中运行。如果你在一个类似未配置的 Kubernetes 集群上,你的上下文默认通常会是命名空间 default。无论你在哪里,在这个 RoleBinding 中的 namespace 值必须与你正在工作的集群上的命名空间匹配。
现在创建绑定:
$ `kubectl` `create` `-f` `etcd-operator-rolebinding.yaml`
rolebinding.rbac.authorization.k8s.io/etcd-operator-rolebinding created
部署 etcd 操作员
操作员是一个自定义控制器,运行在一个 pod 中,并监视你之前定义的 EtcdCluster CR。清单文件 etcd-operator-deployment.yaml 概述了操作员 pod 的规格,包括你正在部署的操作员的容器镜像。注意,它没有定义 etcd 集群的规格。一旦操作员运行起来,你将在一个 CR 中描述想要的 etcd 集群给部署的 etcd 操作员:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
name: etcdoperator
name: etcd-operator
spec:
replicas: 1
selector:
name: etcd-operator
template:
name: etcd-operator
spec:
containers:
- name: etcd-operator
image: quay.io/coreos/etcd-operator:v0.9.4
command:
- etcd-operator
- --create-crd=false
[...]
imagePullPolicy: IfNotPresent
serviceAccountName: etcd-operator-sa
部署为你的操作员提供标签和名称。这里需要注意的一些关键项是在此部署的 pod 中运行的容器镜像 etcd-operator:v0.9.4,以及部署的资源应该使用的服务账户来访问集群的 Kubernetes API。etcd-operator 部署使用为其创建的 etcd-operator-sa 服务账户。
和往常一样,你可以从清单在集群上创建这个资源:
$ kubectl create -f etcd-operator-deployment.yaml
deployment.apps/etcd-operator created
$ kubectl get deployments
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
etcd-operator 1 1 1 1 19s
etcd 操作员本身是在该部署中运行的一个 pod。在这里,你可以看到它正在启动:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
etcd-operator-594fbd565f-4fm8k 0/1 ContainerCreating 0 4s
声明一个 etcd 集群
早些时候,您创建了一个 CRD 来定义一种新的资源类型,即 EtcdCluster。现在您有一个监视 EtcdCluster 资源的 Operator,您可以声明一个具有所需状态的 EtcdCluster。为此,请提供 Operator 识别的两个spec元素:size,etcd 集群成员的数量,和每个成员应运行的 etcdversion。
您可以在名为etcd-cluster-cr.yaml的文件中看到spec部分:
apiVersion: etcd.database.coreos.com/v1beta2
kind: EtcdCluster
metadata:
name: example-etcd-cluster
spec:
size: 3
version: 3.1.10
这份简短的清单声明了三个集群成员的期望状态,每个成员运行的是 etcd 服务器的 3.1.10 版本。使用熟悉的kubectl语法创建这个 etcd 集群:
$ kubectl create -f etcd-cluster-cr.yaml
etcdcluster.etcd.database.coreos.com/example-etcd-cluster created
$ kubectl get pods -w
NAME READY STATUS RESTARTS AGE
etcd-operator-594fbd565f-4fm8k 1/1 Running 0 3m
example-etcd-cluster-95gqrthjbz 1/1 Running 2 38s
example-etcd-cluster-m9ftnsk572 1/1 Running 0 34s
example-etcd-cluster-pjqhm8d4qj 1/1 Running 0 31s
此示例 etcd 集群是您集群 API 中的一级资源,即EtcdCluster。由于它是一个 API 资源,您可以直接从 Kubernetes 获取 etcd 集群的规范和状态。尝试kubectl describe以报告您的 etcd 集群的大小、etcd 版本和状态,如下所示:
$ `kubectl` `describe` `etcdcluster/example-etcd-cluster`
Name: example-etcd-cluster
Namespace: default
API Version: etcd.database.coreos.com/v1beta2
Kind: EtcdCluster
[...]
Spec:
Repository: quay.io/coreos/etcd
Size: 3
Version: 3.1.10
Status:
Client Port: 2379
Conditions:
Last Transition Time: 2019-11-15T02:52:04Z
Reason: Cluster available
Status: True
Type: Available
Current Version: 3.1.10
Members:
Ready:
example-etcd-cluster-6pq7qn82g2
example-etcd-cluster-dbwt7kr8lw
example-etcd-cluster-t85hs2hwzb
Phase: Running
Service Name: example-etcd-cluster-client
运行 etcd
您现在拥有一个运行中的 etcd 集群。etcd Operator 在 etcd 集群的命名空间中创建了一个 Kubernetes service。服务是客户端可以获取访问一组 Pod 的终结点,即使组成员可能会发生变化的地方。服务默认具有在集群中可见的 DNS 名称。操作员通过将-client附加到 CR 中定义的 etcd 集群名称来构造 etcd API 客户端使用的服务名称。在这里,客户端服务名为example-etcd-cluster-client,并且监听通常的 etcd 客户端 IP 端口 2379。kubectl 可以列出与 etcd 集群关联的服务:
$ `kubectl` `get` `services` `--selector` `etcd_cluster``=``example-etcd-cluster`
NAME TYPE CLUSTER-IP ... PORT(S) AGE
example-etcd-cluster ClusterIP None ... 2379/TCP,2380/TCP 21h
example-etcd-cluster-client ClusterIP 10.96.46.231 ... 2379/TCP 21h
注意
etcd Operator 创建的另一个服务example-etcd-cluster被 etcd 集群成员使用,而不是 etcd API 客户端。
您可以在集群上运行 etcd 客户端,并使用它连接到客户端服务,并与 etcd API 交互。以下命令将您置于 etcd 容器的 shell 中:
$ `kubectl` `run` `--rm` `-i` `--tty` `etcdctl` `--image` `quay.io/coreos/etcd` \
`--restart``=``Never` `--` `/bin/sh`
从 etcd 容器的 shell 中,使用etcdctl的put和get动词在 etcd 中创建和读取键值对:
$ `export` `ETCDCTL_API``=``3`
$ `export` `ETCDCSVC``=``http://example-etcd-cluster-client:2379`
$ `etcdctl` `--endpoints` `$ETCDCSVC` `put` `foo` `bar`
$ `etcdctl` `--endpoints` `$ETCDCSVC` `get` `foo`
foo
bar
在您进行每项更改后,重复这些查询或在etcdctl shell 中运行新的put和get命令。您将看到 etcd Operator 在增长集群、替换成员和升级 etcd 版本时,etcd API 服务的持续可用性。
扩展 etcd 集群
您可以通过修改声明的size规范来扩展 etcd 集群。编辑etcd-cluster-cr.yaml文件,并将size从3更改为4个 etcd 成员。将更改应用于 EtcdCluster CR:
$ kubectl apply -f etcd-cluster-cr.yaml
检查运行中的 Pods 显示 Operator 正在向 etcd 集群添加新的 etcd 成员:
$ kubectl get pods
NAME READY STATUS RESTARTS AGE
etcd-operator-594fbd565f-4fm8k 1/1 Running 1 16m
example-etcd-cluster-95gqrthjbz 1/1 Running 2 15m
example-etcd-cluster-m9ftnsk572 1/1 Running 0 15m
example-etcd-cluster-pjqhm8d4qj 1/1 Running 0 15m
example-etcd-cluster-w5l67llqq8 0/1 Init:0/1 0 3s
提示
您还可以尝试使用kubectl edit etcdcluster/example-etcd-cluster进入编辑器,实时更改集群大小。
失败和自动恢复
你在 第一章 中看到 etcd Operator 替换失败的成员。在你现场看到之前,值得再次强调一下你必须手动处理这个问题的一般步骤。不像无状态程序,没有一个 etcd pod 是独立运行的。通常,人类 etcd “操作员” 必须注意到成员的失败,执行一个新副本,并提供配置使其能够加入剩余成员的 etcd 集群中。etcd Operator 理解 etcd 的内部状态并使恢复过程自动化。
从失败的 etcd 成员中恢复
运行 kubectl get pods -l app=etc 快速获取你的 etcd 集群中的 pod 列表。选择一个你不喜欢的,并告诉 Kubernetes 删除它:
$ kubectl delete pod example-etcd-cluster-95gqrthjbz
pod "example-etcd-cluster-95gqrthjbz" deleted
Operator 察觉到集群现实状态与期望状态之间的差异,并添加一个 etcd 成员以替换你删除的成员。当检索 pod 列表时,你可以在 PodInitializing 状态中看到新的 etcd 集群成员,如下所示:
$ kubectl get pods -w
NAME READY STATUS RESTARTS AGE
etcd-operator-594fbd565f-4fm8k 1/1 Running 1 18m
example-etcd-cluster-m9ftnsk572 1/1 Running 0 17m
example-etcd-cluster-pjqhm8d4qj 1/1 Running 0 17m
example-etcd-cluster-r6cb8g2qqw 0/1 PodInitializing 0 31s
-w 开关告诉 kubectl “监视” pod 列表,并在列表的每次更改时通过标准输出打印更新。你可以按 Ctrl-C 停止监视并返回你的 shell 提示符。
你可以检查 Events 查看记录在 example-etcd-cluster CR 中的恢复操作:
$ `kubectl` `describe` `etcdcluster/example-etcd-cluster`
[...]
Events:
Normal Replacing Dead Member 4m etcd-operator-589c65bd9f-hpkc6
The dead member example-etcd-cluster-95gqrthjbz is being replaced
Normal Member Removed 4m etcd-operator-589c65bd9f-hpkc6
Existing member example-etcd-cluster-95gqrthjbz removed from the cluster
[...]
在恢复过程中,如果再次启动 etcd 客户端 pod,则可以向 etcd 集群发出请求,包括检查其总体健康状况:
$ kubectl run --rm -i --tty etcdctl --image quay.io/coreos/etcd \
--restart=Never -- /bin/sh
If you don't see a command prompt, try pressing enter.
$ etcdctl --endpoints http://example-etcd-cluster-client:2379 cluster-health
member 5ee0dd47065a4f55 is healthy: got healthy result ...
member 70baca4290889c4a is healthy: got healthy result ...
member 76cd6c58798a7a4b is healthy: got healthy result ...
cluster is healthy
$ exit
pod "etcdctl" deleted
etcd Operator 在其复杂的有状态应用中从故障中恢复的方式与 Kubernetes 自动化处理无状态应用的恢复相同。这在概念上很简单,但在操作上非常强大。建立在这些概念之上,Operators 可以执行更高级的技巧,比如升级它们管理的软件。自动化升级可以通过确保事物保持最新状态,对安全性产生积极影响。当一个 Operator 在保持服务可用性的同时执行其应用程序的滚动升级时,更容易保持软件补丁更新到最新修复版本。
升级 etcd 集群
如果你已经是 etcd 的用户,可能会注意到我们指定了一个较旧的版本,3.1.10。我们创造了这个版本以便可以探索 etcd Operator 的升级技能。
以困难的方式升级
此时,你有一个运行版本为 3.1.10 的 etcd 集群。要升级到 etcd 3.2.13,你需要执行一系列步骤。由于本书讨论的是 Operators,而不是 etcd 管理,我们在这里概述了该过程,略去了网络和主机级别的考虑。手动升级的步骤如下:
-
检查每个 etcd 节点的版本和健康状态。
-
为灾难恢复创建集群状态的快照。
-
停止一个 etcd 服务器。用 v3.2.13 二进制文件替换现有版本。启动新版本。
-
在一个三个成员的集群中,至少还需重复两次。
欲知详细信息,请参阅 etcd 升级文档。
简单的方法:让 Operator 完成
通过手动升级过程中的重复和易出错性感受到,将 etcd 特定知识编码到 etcd Operator 中更容易看到其威力。Operator 可以管理 etcd 版本,升级变成了在 EtcdCluster 资源中声明新的期望版本的事情。
触发 etcd 升级
通过查询某个 etcd-cluster pod 获取当前 etcd 容器镜像的版本,将输出进行过滤以查看版本:
$ kubectl get pod example-etcd-cluster-795649v9kq `\`
-o yaml | grep `"image:"` | uniq
image: quay.io/coreos/etcd:v3.1.10
image: busybox:1.28.0-glibc
或者,既然您已向 Kubernetes API 添加了 EtcdCluster 资源,您可以通过使用 kubectl describe 直接总结 example-etcd-cluster 的 Operator 图像,就像您之前所做的那样:
$ `kubectl` `describe` `etcdcluster/example-etcd-cluster`
您会看到集群正在运行 etcd 版本 3.1.10,如文件 etcd-cluster-cr.yaml 和从中创建的 CR 所指定的。
编辑 etcd-cluster-cr.yaml 并将 version 规范从 3.1.10 更改为 3.2.13。然后将新的规范应用到集群中的资源:
$ `kubectl` `apply` `-f` `etcd-cluster-cr.yaml`
再次使用 describe 命令查看当前版本和目标版本,以及 Events 部分中成员升级通知:
$ `kubectl` `describe` `etcdcluster/example-etcd-cluster`
Name: example-etcd-cluster
Namespace: default
API Version: etcd.database.coreos.com/v1beta2
Kind: EtcdCluster
[...]
Status:
Conditions:
[...]
Message: upgrading to 3.2.13
Reason: Cluster upgrading
Status: True
Type: Upgrading
Current Version: 3.1.10
[...]
Size: 3
Target Version: 3.2.13
Events:
Type Reason Age From ...
---- ------ --- ---- ---
Normal Member Upgraded 3s etcd-operator-594fbd565f-4fm8k ...
Normal Member Upgraded 5s etcd-operator-594fbd565f-4fm8k ...
升级升级
通过一些 kubectl 技巧,您可以直接通过 Kubernetes API 进行相同的编辑。这一次,让我们从 3.2.13 升级到此刻可用的最新的 etcd 小版本,版本为 3.3.12:
$ kubectl patch etcdcluster example-etcd-cluster --type='json' \
-p `'[{"op": "replace", "path": "/spec/version", "value":3.3.12}]'`
请记住,您始终可以在 etcd 集群的 CR 清单中进行此更改,然后使用 kubectl apply 应用它,就像触发第一个升级时所做的那样。
连续的 kubectl describe etcdcluster/example-etcd-cluster 命令将显示从旧版本到目标版本的过渡,直到它成为当前版本,此时您将看到 当前版本: 3.3.12。Events 部分记录了每次升级:
Normal Member Upgraded 1m etcd-operator-594fbd565f-4fm8k
Member example-etcd-cluster-pjqhm8d4qj upgraded from 3.1.10 to 3.2.23
Normal Member Upgraded 27s etcd-operator-594fbd565f-4fm8k
Member example-etcd-cluster-r6cb8g2qqw upgraded from 3.2.23 to 3.3.12
清理
在继续之前,如果您删除了用于尝试 etcd Operator 的资源并操纵它们将会很有帮助。如下面的 shell 摘录所示,您可以使用用于创建它们的清单删除资源。首先确保您的当前工作目录是您之前克隆的 chapters Git 存储库中的 ch03 目录 (cd chapters/ch03):
$ kubectl delete -f etcd-operator-sa.yaml
$ kubectl delete -f etcd-operator-role.yaml
$ kubectl delete -f etcd-operator-rolebinding.yaml
$ kubectl delete -f etcd-operator-crd.yaml
$ kubectl delete -f etcd-operator-deployment.yaml
$ kubectl delete -f etcd-cluster-cr.yaml
serviceaccount "etcd-operator-sa" deleted
role.rbac.authorization.k8s.io "etcd-operator-role" deleted
rolebinding.rbac.authorization.k8s.io "etcd-operator-rolebinding" deleted
customresourcedefinition.apiextensions.k8s.io \
"etcdclusters.etcd.database.coreos.com" deleted
deployment.apps "etcd-operator" deleted
etcdcluster.etcd.database.coreos.com "example-etcd-cluster" deleted
摘要
我们在这里使用 etcdctl 工具的 etcd API,为了简单起见,但是一个应用程序使用相同的 API 请求与 etcd 交互,存储、检索和监视键和范围。etcd Operator 自动化了 etcd 集群部分,使得可靠的键值存储对更多应用程序可用。
Operator 变得更加复杂,管理各种关注点,正如您从应用程序特定扩展中所期望的那样。尽管如此,大多数 Operator 遵循在 etcd Operator 中可辨认的基本模式:CR 指定了某些期望的状态,如应用程序的版本,自定义控制器监视资源,在集群上维护期望的状态。
现在,您已经拥有一个用于处理运算符的 Kubernetes 集群。您已经看到如何部署运算符并触发其执行特定于应用程序的状态协调。接下来,我们将介绍运算符构建所依赖的 Kubernetes API 元素,然后介绍运算符框架和 SDK,这是您用来构建运算符的工具包。
第三章:操作员在 Kubernetes 接口中
操作员扩展了 Kubernetes 的两个关键概念:资源 和 控制器。Kubernetes API 包括一种机制,即 CRD,用于定义新资源。本章探讨了操作员建立在其中的 Kubernetes 对象,以添加到集群中的新功能。它将帮助您了解操作员如何适应 Kubernetes 架构,并解释为何将应用程序变成 Kubernetes 本地应用程序是有价值的。
标准缩放:ReplicaSet 资源
查看标准资源 ReplicaSet,可以了解到资源如何构成 Kubernetes 核心的应用管理数据库。与 Kubernetes API 中的任何其他资源一样,ReplicaSet 是 API 对象的集合。 ReplicaSet 主要收集形成应用程序运行副本列表的 Pod 对象。另一个对象类型的规范定义了集群上应保持的这些副本数量。第三个对象规范指向创建新 Pod 的模板,当运行的 Pod 较少时。ReplicaSet 中还收集了更多对象,但这三种类型定义了集群上运行的可扩展 Pod 集的基本状态。在此,我们可以看到来自 第一章 的 staticweb ReplicaSet 的这三个关键部分(Selector、Replicas 和 Pod Template 字段):
$ kubectl describe replicaset/staticweb-69ccd6d6c
Name: staticweb-69ccd6d6c
Namespace: default
Selector: pod-template-hash=69ccd6d6c,run=staticweb
Labels: pod-template-hash=69ccd6d6c
run=staticweb
Controlled By: Deployment/staticweb
Replicas: 1 current / 1 desired
Pods Status: 1 Running / 0 Waiting / 0 Succeeded / 0 Failed
Pod Template:
Labels: pod-template-hash=69ccd6d6c
run=staticweb
Containers:
staticweb:
Image: nginx
标准 Kubernetes 控制平面组件 ReplicaSet 控制器管理 ReplicaSets 及其属于它们的 Pod。 ReplicaSet 控制器创建 ReplicaSets 并持续监视它们。当运行的 Pod 数量与 Replicas 字段中期望的数量不匹配时,ReplicaSet 控制器启动或停止 Pod,以使实际状态与期望状态匹配。
ReplicaSet 控制器采取的操作是有意通用且应用程序无关的。它根据 Pod 模板启动新副本,或删除多余的 Pod。它不应知道每个可能在 Kubernetes 集群上运行的应用程序的启动和关闭顺序的具体细节。
操作员是应用程序特定的 CR 和自定义控制器的组合,它确实了解有关启动、缩放、恢复和管理其应用程序的所有细节。操作员的 操作数 是我们称之为应用程序、服务或操作员管理的任何资源。
自定义资源
CR 作为 Kubernetes API 的扩展,包含一个或多个字段,就像本地资源一样,但不是默认的 Kubernetes 部署的一部分。CR 包含结构化数据,API 服务器提供了一个机制,通过使用 kubectl 或另一个 API 客户端,读取和设置其字段,就像操作本地资源字段一样。用户通过提供 CR 定义在运行的集群上定义 CR。CRD 类似于 CR 的架构,定义了 CR 的字段及其字段包含的值的类型。
CR 还是 ConfigMap?
Kubernetes 提供了一个标准资源,ConfigMap,用于使配置数据对应用程序可用。ConfigMaps 似乎与 CRs 的可能用途重叠,但这两个抽象针对不同的情况。
ConfigMaps 最适合为在集群上的 pod 中运行的程序提供配置——想象一下应用程序的配置文件,比如 httpd.conf 或 MySQL 的 mysql.cnf。应用程序通常希望从其 pod 中读取这种配置,作为文件或环境变量值的形式,而不是从 Kubernetes API 中读取。
Kubernetes 提供了 CRs 来表示 API 中的新对象集合。CRs 由标准 Kubernetes 客户端(如 kubectl)创建和访问,并遵循 Kubernetes 的约定,如资源 .spec 和 .status。在其最有用的情况下,CRs 被自定义控制器代码监视,进而创建、更新或删除其他集群对象,甚至是集群外的任意资源。
自定义控制器
CRs 是 Kubernetes API 数据库中的条目。它们可以通过常见的 kubectl 命令创建、访问、更新和删除——但单独的 CR 只是一组数据。要为在集群上运行的特定应用程序提供声明性 API,您还需要捕获管理该应用程序流程的活动代码。
我们已经看过一个标准的 Kubernetes 控制器,即 ReplicaSet 控制器。要创建一个操作员,为应用程序的主动管理提供 API,您需要构建控制器模式的实例来控制您的应用程序。此自定义控制器检查并维护应用程序的期望状态,以 CR 中表示。每个操作员都有一个或多个自定义控制器,实现其特定于应用程序的管理逻辑。
操作员范围
Kubernetes 集群被划分为命名空间。命名空间是集群对象和资源名称的边界。在单个命名空间内名称必须唯一,但在不同命名空间之间可以重复。这使得多个用户或团队可以共享单个集群变得更容易。可以对每个命名空间应用资源限制和访问控制。一个操作员(Operator)可以被限制在一个命名空间中,或者可以在整个集群中维护其操作数。
注意
有关 Kubernetes 命名空间的详细信息,请参阅 Kubernetes 命名空间文档。
命名空间范围
通常,将您的操作员限制在单个命名空间内是有意义的,并且对于多个团队使用的集群更加灵活。限定在命名空间中的操作员可以独立升级,这允许某些便利设施。例如,您可以在测试命名空间中测试升级,或者从不同的命名空间为兼容性提供旧的 API 或应用程序版本。
集群范围的操作员(Cluster-Scoped Operators)
有些情况下,希望运算符能够监视和管理整个集群中的应用程序或服务。例如,管理服务网格(如 Istio)或为应用程序端点发放 TLS 证书(如 cert-manager)的运算符,在监视并操作集群范围的状态时可能效果最佳。
默认情况下,本书中使用的 Operator SDK 创建部署和授权模板,将运算符限制在单个命名空间内。可以将大多数运算符更改为在集群范围内运行。这需要在运算符的清单中进行更改,以指定它应监视集群中的所有命名空间,并且应在 ClusterRole 和 ClusterRoleBinding 的授权对象的权力下运行,而不是命名空间角色和 RoleBinding。
授权
在 Kubernetes 中,授权——通过 API 在集群上执行操作的权力——由几种可用的访问控制系统之一定义。基于角色的访问控制(RBAC)是这些中首选且最紧密集成的。RBAC 根据系统用户执行的角色来调控对系统资源的访问。角色是一组能够在特定 API 资源上执行某些操作的能力,如创建、读取、更新或删除。角色描述的能力通过 RoleBinding 赋予或绑定给用户。
服务账户
在 Kubernetes 中,常规的人类用户账户不由集群管理,也没有描述它们的 API 资源。在集群上标识你的用户来自外部提供者,可以是任何东西,从文本文件中的用户列表到通过你的 Google 账户代理认证的 OpenID Connect(OIDC)提供者。
注意
查看“Kubernetes 中的用户”文档,了解更多关于 Kubernetes 服务账户的信息。
另一方面,服务账户由 Kubernetes 管理,并且可以通过 Kubernetes API 创建和操作。服务账户是一种特殊类型的集群用户,用于授权程序而不是人员。运算符是使用 Kubernetes API 的程序,大多数运算符应该从服务账户派生其访问权限。在部署运算符时创建服务账户是标准步骤。服务账户标识运算符,账户的角色表示授予运算符的权限。
角色
Kubernetes RBAC 默认拒绝权限,因此角色定义授予的权利。一个常见的 Kubernetes 角色的“Hello World”示例如下 YAML 摘录:
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
namespace: default
name: pod-reader
rules:
- apiGroups: [""]
resources: ["pods"] 
verbs: ["get", "watch", "list"] 
此角色授予的权限仅在 Pod 上有效。
此列表允许在允许资源上执行特定操作。包含对只读访问 Pod 的动词可供绑定了该角色的账户使用。
RoleBindings
RoleBinding 将角色绑定到一个或多个用户列表。这些用户被授予绑定中引用角色定义的权限。RoleBinding 只能引用其自身命名空间中的角色。当部署限制在命名空间内的操作员时,RoleBinding 将适当的角色绑定到标识操作员的服务账户上。
ClusterRoles 和 ClusterRoleBindings
正如前文讨论的,大多数操作员限制在一个命名空间内。角色(Roles)和角色绑定(RoleBindings)也受限于命名空间。ClusterRoles 和 ClusterRoleBindings 则是它们的集群范围等价物。标准的命名空间角色绑定只能引用其命名空间内的角色,或整个集群定义的 ClusterRoles。当角色绑定引用 ClusterRole 时,ClusterRole 声明的规则仅适用于绑定所在命名空间中指定的资源。通过这种方式,一组通用角色可以定义为 ClusterRoles,但可以在特定命名空间中重复使用并授予用户。
ClusterRoleBinding 授予用户在整个集群中所有命名空间的能力。负责集群范围职责的操作员通常将 ClusterRole 绑定到操作员服务账户上,通过 ClusterRoleBinding 实现。
概要
操作员是 Kubernetes 的扩展。我们已经概述了用于构建知道如何管理其负责应用程序的操作员的 Kubernetes 组件。因为操作员建立在核心 Kubernetes 概念之上,它们可以使应用程序显著地“原生于 Kubernetes”。了解其环境的这类应用程序能够利用平台的设计模式和现有功能,以提高可靠性和减少依赖性。因为操作员礼貌地扩展 Kubernetes,它们甚至可以管理平台本身的部分和流程,正如在 Red Hat 的 OpenShift Kubernetes 发行版中所见。
第四章:操作者框架
开发操作者以及管理其分发、部署和生命周期中存在不可避免的复杂性。Red Hat 操作者框架使创建和分发操作者变得更简单。它通过软件开发工具包(SDK)自动化了大部分重复的实现工作,使操作者构建更加容易。框架还提供了部署和管理操作者的机制。操作者生命周期管理器(OLM)是一个安装、管理和升级其他操作者的操作者。操作者计量是一个度量系统,用于记录操作者对集群资源的使用情况。本章概述了框架的这三个关键部分,以准备好使用这些工具来构建和分发一个示例操作者。在此过程中,您将安装 operator-sdk 命令行工具,这是 SDK 功能的主要接口。
操作者框架的起源
操作者 SDK 构建在 Kubernetes 的 controller-runtime 之上,这是一组使用 Go 编程语言提供基本 Kubernetes 控制器例程的库。作为操作者框架的一部分,SDK 提供集成点用于通过 OLM 分发和管理操作者,并通过操作者计量对其进行衡量。SDK 和整个 Red Hat 操作者框架都是开源的,社区和其他组织的贡献者参与其中,并正在向供应商中立的 Cloud Native Computing Foundation 捐赠过程中。
操作者成熟度模型
操作者成熟度模型,如 图 4-1 所示,描绘了一种思考不同操作者功能级别的方式。您可以从一个最小可行产品开始,安装其操作对象,然后添加生命周期管理和升级能力,随着时间的推移迭代,最终实现应用程序的完全自动化。

图 4-1 操作者成熟度模型
一个操作者可以起源于不起眼的地方,并在一系列开发周期中不断发展复杂性。模型的第一阶段仅需要足够的特定于应用程序的代码来创建操作对象所需的任何资源。换句话说,第一阶段是应用程序的准备、自动化安装。
操作者 SDK
Operator SDK 是一组用于搭建、构建和准备操作符进行部署的工具。SDK 目前主要支持使用 Go 编程语言构建操作符,并计划支持其他语言。SDK 还为Helm图表或Ansible playbooks 提供了可以描述为适配器架构的支持。我们将在第六章中介绍这些适配器操作符。在第七章中,我们将展示如何在 Go 中实现应用程序特定的管理例程,以使用 SDK 工具构建自定义操作符。
安装 Operator SDK 工具
Operator SDK 围绕一个名为 operator-sdk 的命令行工具展开,帮助您构建操作符。SDK 强制执行标准项目布局,并生成基本的 Kubernetes API 控制器实现的 Go 源代码和应用程序特定处理程序的占位符。从那里开始,SDK 提供方便的命令来构建操作符,并将其包装在 Linux 容器中,生成部署操作符所需的 YAML 格式 Kubernetes 清单。
二进制安装
要为您的操作系统安装二进制文件,请从Kubernetes SDK 仓库下载 operator-sdk,使其可执行,并将其移动到您的 $PATH 目录中。该程序是静态链接的,因此准备在已发布版本上运行的平台上使用。在撰写本文时,该项目为 x86-64 架构的 macOS 和 Linux 操作系统提供了构建。
提示
使用像 operator-sdk 这样快速发展的项目时,查看项目的安装说明获取最新的安装方法是个好主意。
从源代码安装
要获取最新的开发版本,或者对于没有二进制发行版的平台,请从源代码构建 operator-sdk。我们假设您已安装了 git 和 go:
$ `go` `get` `-d` `github.com/operator-framework/operator-sdk`
$ `cd` `$GOPATH``/src/github.com/operator-framework/operator-sdk`
$ `git` `checkout` `master`
$ `make` `tidy`
$ `make` `install`
成功的构建过程会将 operator-sdk 二进制文件写入您的 *\(GOPATH/bin* 目录。运行 `operator-sdk version` 来检查它是否在您的 `\)PATH` 中。
这是获取 SDK 工具最常见且最少依赖的两种方式。查看项目的安装文档以获取其他选项。本书后续示例使用版本系列 0.11.x 的 operator-sdk。
运算符生命周期管理器
操作符解决了任何平台上获取、部署和随时间管理应用程序的一般原则。操作符本身是 Kubernetes 应用程序。虽然操作符管理其操作数,但谁管理操作符呢?
操作员生命周期管理器将操作员模式提升到堆栈的更高层次:它是一个在 Kubernetes 集群上获取、部署和管理操作员的操作员。像任何应用程序的操作员一样,OLM 通过自定义资源和自定义控制器扩展了 Kubernetes,以便也可以根据 Kubernetes API 的术语以声明方式管理操作员。
OLM 为操作员元数据定义了一种架构,称为集群服务版本(CSV),用于描述操作员及其依赖关系。具有 CSV 的操作员可以列为可供运行在 Kubernetes 集群上的 OLM 使用的目录中的条目。然后用户可以从目录中订阅操作员,告知 OLM 要为其提供所需的操作员并管理。而该操作员反过来在集群上提供和管理其应用程序或服务。
根据操作员在其 CSV 中提供的描述和参数,OLM 可以管理操作员的整个生命周期:监控其状态、采取维持其运行所需的措施、在集群中的多个实例之间进行协调,并将其升级到新版本。操作员反过来可以使用最新的自动化功能控制其应用程序的最新版本。
操作员计量
操作员计量是一个分析在 Kubernetes 集群上运行的操作员资源使用情况的系统。计量分析 Kubernetes 的 CPU、内存和其他资源指标,以计算基础设施服务的成本。它还可以检查特定于应用程序的指标,例如根据使用情况计费应用程序用户所需的指标。计量为运维团队提供了一个模型,可以将云服务或集群资源的成本映射到消费它的应用程序、命名空间和团队。它是一个平台,您可以在其上构建特定于您的操作员和其管理的应用程序的定制报告,帮助进行三个主要活动:
预算编制
团队在其集群上使用操作员时,可以深入了解基础设施资源的使用情况,特别是在自动缩放集群或混合云部署中,帮助改善预测和分配,以避免浪费。
计费
当您构建一个向付费客户提供服务的操作员时,可以通过反映操作员和应用程序内部结构的计费代码或标签来跟踪资源使用情况,以计算准确和详细的账单。
指标聚合
可跨命名空间或团队查看服务使用情况和指标。例如,它可以帮助您分析在运行许多数据库服务器集群和多个数据库的 PostgreSQL 数据库操作程序的资源消耗,这些数据库为共享大型 Kubernetes 集群的不同团队提供服务。
总结
本章介绍了运算符框架的三大支柱:用于构建和开发运算符的 Operator SDK;用于分发、安装和升级运算符的 Operator Lifecycle Manager;以及用于测量运算符性能和资源消耗的 Operator Metering。这些框架元素共同支持制作运算符并保持其运行的过程。
你还安装了operator-sdk工具,因此你已经配备了构建运算符的主要工具。要开始,我们将首先介绍示例应用程序,您将构建一个运算符来管理访客站点。
第五章:示例应用程序:访客站点
真实的生产级应用程序很难。基于容器的架构通常由多个服务组成,每个服务都需要自己的配置和安装过程。维护这些类型的应用程序,包括各个组件及其交互,是一个耗时且容易出错的过程。操作员的设计目的在于简化此过程的复杂性。
简单的单容器“Hello World”应用程序不足以展示操作员的全部功能。为了帮助您真正理解操作员的能力,我们需要一个需要多个 Kubernetes 资源、配置值交叉使用的应用程序作为演示。
在本章中,我们介绍了访客站点应用程序,这将作为接下来涵盖操作员编写的章节的示例。我们将查看应用程序架构以及运行站点和通过传统的 Kubernetes 清单安装它的过程。在接下来的章节中,我们将使用 Operator SDK(Helm、Ansible 和 Go 提供的各种方法)创建操作员来部署此应用程序,并探讨每种方法的利弊。
应用程序概述
访客站点追踪有关每个对其主页的请求的信息。每次刷新页面时,都会存储一个包含客户端、后端服务器和时间戳详细信息的条目。主页显示最近访问的列表(如图 5-1 所示)。

图 5-1。访客站点主页
尽管主页本身相当简单,但架构使其成为一个探索操作员的有趣示例。访客站点是一个传统的三层应用程序,包括:
如图 5-2 所示,每个组件都部署为单独的容器。流程简单,用户与 web 接口交互,接口本身调用后端 REST API。提交到 REST API 的数据持久存储在 MySQL 数据库中,该数据库也作为其自己的容器运行。

图 5-2。访客站点架构
请注意,数据库不连接到持久卷,并且临时存储其数据。虽然这不是适合生产的解决方案,但在本示例中,重要的是部署和容器之间的交互。
使用清单进行安装
访客站点中的每个组件都需要两个 Kubernetes 资源:
部署
包含创建容器所需的信息,包括镜像名称、公开端口和单个部署的特定配置。
服务
部署中的所有容器之间的网络抽象。如果部署的容器数量超过一个(这将在后端完成),服务将位于前端并在所有副本之间平衡传入的请求。
第三个资源用于存储数据库的认证详细信息。MySQL 容器在启动时使用此密钥,后端容器在发出请求时使用它进行数据库认证。
此外,组件之间必须保持一致的配置值。例如,后端需要知道连接的数据库服务的名称。在通过清单部署应用程序时,需要意识到这些关系,以确保这些值对应正确。
在以下清单中,提供的数值将产生一个可工作的访客网站部署。每个部分将突出需要用户干预的具体实例。
你可以在书籍的 GitHub 存储库中找到所有的清单。
部署 MySQL
在部署数据库之前必须创建密钥,因为它在容器启动期间被使用:
apiVersion: v1
kind: Secret
metadata:
name: mysql-auth 
type: Opaque
stringData:
username: visitors-user 
password: visitors-pass 
当数据库和后端部署使用密钥时,它被此名称引用。
简单起见,在本示例中,用户名和密码默认为测试值。
你可以在本书的GitHub 存储库的database.yaml文件中找到密钥资源的定义。
一旦密钥就位,使用以下清单部署 MySQL 实例到 Kubernetes 中:
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql 
spec:
replicas: 1
selector:
matchLabels:
app: visitors
tier: mysql
template:
metadata:
labels:
app: visitors
tier: mysql
spec:
containers:
- name: visitors-mysql
image: "mysql:5.7" 
imagePullPolicy: Always
ports:
- name: mysql
containerPort: 3306 
protocol: TCP
env: 
- name: MYSQL_ROOT_PASSWORD
value: password
- name: MYSQL_DATABASE
value: visitors_db
- name: MYSQL_USER
valueFrom:
secretKeyRef:
name: mysql-auth 
key: username
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-auth 
key: password
部署名称必须在部署的命名空间中是唯一的。
部署需要包含其名称和托管存储库的映像的详细信息。
用户必须了解镜像公开的每个端口,并明确引用它们。
用于配置此特定部署的容器的值通过环境变量传递。
该密钥提供了数据库认证凭据的值。
要记住容器端口的值,以及每个环境变量的值,因为其他清单使用这些值。
部署将创建 MySQL 容器;然而,它不提供任何入口配置来访问它。为此,我们将需要一个服务。以下清单将创建一个 Kubernetes 服务,提供对 MySQL 部署的访问:
apiVersion: v1
kind: Service
metadata:
name: mysql-service 
labels:
app: visitors
tier: mysql
spec:
clusterIP: None
ports:
- port: 3306 
selector:
app: visitors
tier: mysql
与部署一样,服务名称在给定的命名空间中必须是唯一的。这也适用于后端和前端组件的部署和服务。
服务映射到部署暴露的端口,因此此值必须与部署的ports部分中的值相同。
后端
与 MySQL 资源类似,后端需要部署和服务。然而,与数据库独立不同,后端的配置严重依赖于为数据库设置的值。虽然这并不是不合理的要求,但用户需要确保这些值在两个资源中保持一致。一个错误可能导致后端无法与数据库通信。这是部署后端的清单:
apiVersion: apps/v1
kind: Deployment
metadata:
name: visitors-backend
spec:
replicas: 1 
selector:
matchLabels:
app: visitors
tier: backend
template:
metadata:
labels:
app: visitors
tier: backend
spec:
containers:
- name: visitors-backend
image: "jdob/visitors-service:1.0.0"
imagePullPolicy: Always
ports:
- name: visitors
containerPort: 8000
env:
- name: MYSQL_DATABASE 
value: visitors_db
- name: MYSQL_SERVICE_HOST 
value: mysql-service
- name: MYSQL_USERNAME
valueFrom:
secretKeyRef:
name: mysql-auth 
key: username
- name: MYSQL_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-auth 
key: password
每个部署配置都包括它应该生成的容器数量。
必须手动检查这些值,以确保它们与 MySQL 部署上设置的值匹配。否则,后端将无法与数据库建立连接。
此值告诉后端应用程序在哪里找到数据库,必须与先前创建的 MySQL 服务的名称匹配。
与数据库部署一样,密钥提供数据库的身份验证凭据。
使用容器化应用程序的主要好处之一是它们使您能够单独扩展特定组件。在此处显示的后端部署中,可以修改replicas字段来扩展后端。以下章节中的示例操作员使用自定义资源来将此副本计数公开为访问者站点自定义资源的一流配置值。用户不需要手动导航到特定后端部署,就像使用清单时一样。操作员知道如何适当地使用输入的值。
服务清单看起来与您为数据库创建的服务清单类似:
apiVersion: v1
kind: Service
metadata:
name: visitors-backend-service
labels:
app: visitors
tier: backend
spec:
type: NodePort
ports:
- port: 8000 
targetPort: 8000
nodePort: 30685 
protocol: TCP
selector:
app: visitors
tier: backend
与数据库服务一样,服务定义中引用的端口必须与部署暴露的端口相匹配。
在此示例中,后端配置为通过与 Minikube 相同的 IP 地址的端口 30685 运行。前端在获取数据时使用此端口进行后端调用。为简化起见,前端默认使用此值,因此在部署前端时无需指定。
前端
与后端部署类似,前端需要与后端部署一致的配置。再次,用户需要手动验证这些值在两个位置上是否一致。以下是创建前端部署的清单:
apiVersion: apps/v1
kind: Deployment
metadata:
name: visitors-frontend
spec:
replicas: 1
selector:
matchLabels:
app: visitors
tier: frontend
template:
metadata:
labels:
app: visitors
tier: frontend
spec:
containers:
- name: visitors-frontend
image: "jdob/visitors-webui:1.0.0"
imagePullPolicy: Always
ports:
- name: visitors
containerPort: 3000
env:
- name: REACT_APP_TITLE 
value: "Visitors Dashboard"
为使访客站点应用程序更加有趣,您可以通过环境变量覆盖主页标题。在接下来的章节中,您将学习如何通过 CR 将其公开为访客站点的值,使终端用户无需知道在哪个部署中指定该值。
与 MySQL 和后端部署类似,以下清单创建了一个服务,用于访问前端部署:
apiVersion: v1
kind: Service
metadata:
name: visitors-frontend-service
labels:
app: visitors
tier: frontend
spec:
type: NodePort
ports:
- port: 3000
targetPort: 3000
nodePort: 30686 
protocol: TCP
selector:
app: visitors
tier: frontend
前端服务看起来与后端服务非常相似,但有一个显著的区别,即它在端口 30686 上运行。
部署清单
您可以使用kubectl命令自行运行访客站点:
$ `kubectl` `apply` `-f` `ch05/database.yaml`
secret/mysql-auth created
deployment.apps/mysql created
service/mysql-service created
$ `kubectl` `apply` `-f` `ch05/backend.yaml`
deployment.apps/visitors-backend created
service/visitors-backend-service created
$ `kubectl` `apply` `-f` `ch05/frontend.yaml`
deployment.apps/visitors-frontend created
service/visitors-frontend-service created
访问访客站点
使用这些清单,您可以通过使用 Minikube 实例的 IP 地址并在浏览器中指定端口 30686 来找到主页。minikube命令提供了访问的 IP 地址:
$ `minikube` `ip`
192.168.99.100
对于此 Minikube 实例,您可以通过打开浏览器并转到http://192.168.99.100:30686来访问访客站点。
点击几次刷新按钮将在该页面上的表格中填充内部集群 IP 和每个请求的时间戳的详细信息,如前文中所示的图 5-1。
清理工作
类似于部署清单,您可以使用kubectl命令删除创建的资源:
$ kubectl delete -f ch05/frontend.yaml
deployment.apps "visitors-frontend" deleted
service "visitors-frontend-service" deleted
$ kubectl delete -f ch05/backend.yaml
deployment.apps "visitors-backend" deleted
service "visitors-backend-service" deleted
$ kubectl delete -f ch05/database.yaml
secret "mysql-auth" deleted
deployment.apps "mysql" deleted
service "mysql-service" deleted
摘要
我们将在接下来的章节中使用这个示例应用程序来演示多种技术,您可以在其上构建运算符。
除了运算符实现外,还要考虑最终用户的体验。在本章中,我们展示了基于清单的安装,需要进行多项手动更改和内部引用。所有以下运算符实现都会创建一个自定义资源定义,作为创建和配置访客站点实例的唯一 API。
第六章:适配器运算符
考虑从头开始编写 Operator 可能需要的众多步骤。您将不得不创建 CRDs 来指定最终用户的接口。Kubernetes 控制器不仅需要用 Operator 的领域特定逻辑编写,还需要正确地连接到运行中的集群以接收适当的通知。还需要创建角色和服务账户以允许 Operator 按其需要的方式运行。Operator 作为集群内的一个 pod 运行,因此需要构建一个镜像,以及其伴随的部署清单。
许多项目已经投资于应用程序部署和配置技术。Helm 项目允许用户在格式化的文本文件中定义其集群资源,并通过 Helm 命令行工具部署它们。Ansible 是一个流行的自动化引擎,用于创建可重用脚本来配置和配置一组资源。这两个项目都有专注的开发者群体,他们可能缺乏资源来迁移到使用 Operator 来管理其应用程序。
Operator SDK 通过其适配器运算符解决了这两个问题。通过命令行工具,SDK 生成运行 Helm 和 Ansible 等技术所需的代码。这使您能够快速将基础架构迁移到 Operator 模型,无需编写必要的支持 Operator 代码。这样做的优势包括:
-
通过 CRDs 提供一致的接口,无论底层技术是 Helm、Ansible 还是 Go。
-
允许 Helm 和 Ansible 解决方案利用 Operator 生命周期管理器提供的部署和生命周期优势(有关更多信息,请参见第八章)。
-
允许将这些解决方案托管在 OperatorHub.io 等 Operator 仓库上(有关更多信息,请参见第十章)。
在本章中,我们展示了如何使用 SDK 在前一章介绍的访客网站应用程序中构建和测试适配器运算符。
Helm 运算符
Helm是 Kubernetes 的一个包管理工具。它简化了部署具有多个组件的应用程序,但每次部署仍然是一个手动过程。如果您正在部署多个 Helm 打包的应用程序实例,使用 Operator 自动化这些部署将非常方便。
Helm 的复杂性超出了本书的范围——您可以查阅文档以获取更多详细信息——但一些背景知识将帮助您理解 Helm Operator。Helm 定义了构成应用程序的 Kubernetes 资源,如部署和服务,在一个称为chart的文件中。图表支持配置变量,因此您可以自定义应用程序实例而无需编辑图表本身。这些配置值在名为values.yaml的文件中指定。Helm Operator 可以使用不同版本的values.yaml部署每个应用程序实例。
当传递--type=helm参数时,Operator SDK 为 Helm Operator 生成 Kubernetes 控制器代码。您为应用程序提供一个 Helm 图表,生成的 Helm Operator 会监视其给定类型的新 CR。当它找到其中一个 CR 时,它会根据资源中定义的值构建一个 Helm values.yaml文件。然后,Operator 根据values.yaml中的设置创建其 Helm 图表中指定的应用程序资源。要配置另一个应用程序实例,您需要创建一个包含适当值的新 CR。
SDK 提供了两种构建基于 Helm 的 Operator 的变体:
-
项目生成过程在 Operator 项目代码中构建了一个空白的 Helm 图表结构。
-
在 Operator 创建时指定现有图表,创建过程将使用它来填充生成的 Operator。
在接下来的章节中,我们将讨论这些方法的每一个。作为先决条件,请确保在您的计算机上安装了 Helm 命令行工具。您可以在Helm 的安装文档中找到有关如何执行此操作的信息。
构建 Operator
SDK 的new命令为新 Operator 创建了框架项目文件。这些文件包括调用适当的 Helm 图表以处理 CR 请求的 Kubernetes 控制器所需的所有代码。我们稍后将在本节更详细地讨论这些文件。
创建一个新图表
使用--type=helm参数创建一个具有新 Helm 图表框架的 Operator。以下示例创建了一个访客站点应用程序的 Helm Operator 的基础(参见第五章):
$ `OPERATOR_NAME``=``visitors-helm-operator`
$ `operator-sdk` `new` `$OPERATOR_NAME` `--api-version``=``example.com/v1` `\ ` `--kind``=``VisitorsApp` `--type``=``helm`
INFO[0000] Creating new Helm operator 'visitors-helm-operator'.
INFO[0000] Created helm-charts/visitorsapp
INFO[0000] Generating RBAC rules
WARN[0000] The RBAC rules generated in deploy/role.yaml are based on
the chart's default manifest. Some rules may be missing for resources
that are only enabled with custom values, and some existing rules may
be overly broad. Double check the rules generated in deploy/role.yaml
to ensure they meet the operator's permission requirements.
INFO[0000] Created build/Dockerfile
INFO[0000] Created watches.yaml
INFO[0000] Created deploy/service_account.yaml
INFO[0000] Created deploy/role.yaml
INFO[0000] Created deploy/role_binding.yaml
INFO[0000] Created deploy/operator.yaml
INFO[0000] Created deploy/crds/example_v1_visitorsapp_crd.yaml
INFO[0000] Created deploy/crds/example_v1_visitorsapp_cr.yaml
INFO[0000] Project creation complete.
visitors-helm-operator是生成的 Operator 的名称。另外两个参数,--api-version和--kind,描述了此 Operator 管理的 CR。这些参数导致为新类型创建基本 CRD。
SDK 创建一个与$OPERATOR_NAME相同名称的新目录,其中包含所有 Operator 的文件。有几个文件和目录需要注意:
deploy/
此目录包含用于部署和配置 Operator 的 Kubernetes 模板文件,包括 CRD、Operator 部署资源本身以及 Operator 运行所需的 RBAC 资源。
helm-charts/
此目录包含与 CR 类型同名的 Helm chart 的骨架目录结构。其中的文件与 Helm CLI 在初始化新图表时创建的文件类似,包括一个values.yaml文件。每个 Operator 管理的新 CR 类型都将向此目录添加一个新图表。
watches.yaml
该文件将每种 CR 类型映射到用于处理它的特定 Helm chart。
现在,一切都准备就绪,可以开始实现您的图表。但是,如果您已经编写了一个图表,还有一种更简单的方法。
使用现有图表
从现有 Helm chart 构建 Operator 的过程与使用新 chart 创建 Operator 的过程类似。除了--type=helm参数外,还有一些额外的参数需要考虑:
--helm-chart
告知 SDK 使用现有图表初始化 Operator。该值可以是:
-
图表存档的 URL
-
远程图表的存储库和名称
-
本地目录的位置
--helm-chart-repo
指定图表的远程存储库 URL(除非另有指定本地目录)。
--helm-chart-version
告知 SDK 获取特定版本的图表。如果省略此项,则使用最新可用版本。
在使用--helm-chart参数时,--api-version和--kind参数变为可选项。api-version默认为charts.helm.k8s.io/v1alpha1,并且kind名称将根据 chart 名称推断而来。然而,由于api-version携带有关 CR 创建者的信息,我们建议您明确地适当填充这些值。您可以在本书的Github 存储库中找到部署 Visitors Site 应用程序的示例 Helm chart。
下面的示例演示了如何构建一个 Operator,并使用 Visitors Site Helm chart 的存档进行初始化:
$ `OPERATOR_NAME``=``visitors-helm-operator`
$ `wget` `https://github.com/kubernetes-operators-book/``\ ` `chapters/releases/download/1.0.0/visitors-helm.tgz` 
$ `operator-sdk` `new` `$OPERATOR_NAME` `--api-version``=``example.com/v1` `\ ` `--kind``=``VisitorsApp` `--type``=``helm` `--helm-chart``=``./visitors-helm.tgz`
INFO[0000] Creating new Helm operator 'visitors-helm-operator'.
INFO[0000] Created helm-charts/visitors-helm
INFO[0000] Generating RBAC rules
WARN[0000] The RBAC rules generated in deploy/role.yaml are based on
the chart's default manifest. Some rules may be missing for resources
that are only enabled with custom values, and some existing rules may
be overly broad. Double check the rules generated in deploy/role.yaml
to ensure they meet the operator's permission requirements.
INFO[0000] Created build/Dockerfile
INFO[0000] Created watches.yaml
INFO[0000] Created deploy/service_account.yaml
INFO[0000] Created deploy/role.yaml
INFO[0000] Created deploy/role_binding.yaml
INFO[0000] Created deploy/operator.yaml
INFO[0000] Created deploy/crds/example_v1_visitorsapp_crd.yaml
INFO[0000] Created deploy/crds/example_v1_visitorsapp_cr.yaml
INFO[0000] Project creation complete.
由于 Operator SDK 处理重定向的方式存在问题,您必须手动下载图表 tarball 并将其作为本地引用传递。
前述示例生成了与使用新 Helm chart 创建 Operator 时相同的文件,唯一的例外是图表文件是从指定的存档中填充的:
$ `ls` `-l` `$OPERATOR_NAME``/helm-charts/visitors-helm/templates`
_helpers.tpl
auth.yaml
backend-deployment.yaml
backend-service.yaml
frontend-deployment.yaml
frontend-service.yaml
mysql-deployment.yaml
mysql-service.yaml
tests
SDK 使用图表的values.yaml文件中的值填充示例 CR 模板。例如,Visitors Site Helm chart 具有以下values.yaml文件:
$ `cat` `$OPERATOR_NAME``/helm-charts/visitors-helm/values.yaml`
backend:
size: 1
frontend:
title: Helm Installed Visitors Site
SDK 生成的示例 CR 位于 Operator 项目根目录中的deploy/crds目录中,并在其spec部分包含相同的值:
$ `cat` `$OPERATOR_NAME``/deploy/crds/example_v1_visitorsapp_cr.yaml`
apiVersion: example.com/v1
kind: VisitorsApp
metadata:
name: example-visitorsapp
spec:
# Default values copied from <proj_dir>/helm-charts/visitors-helm/values.yaml
backend:
size: 1
frontend:
title: Helm Installed Visitors Site
在运行图表之前,Operator 将将自定义资源的spec字段中的值映射到values.yaml文件中。
Fleshing Out the CRD
生成的 CRD 不包括 CR 类型的值输入和状态值的具体细节。附录 B 描述了完成 CR 定义的步骤。
审查操作员权限
生成的部署文件包括操作员将用于连接 Kubernetes API 的角色。默认情况下,此角色权限非常宽泛。附录 C 讨论如何精细调整角色定义以限制操作员的权限。
运行 Helm Operator
操作员作为普通容器映像交付。然而,在开发和测试周期中,跳过映像创建过程并在集群外部简单运行操作员通常更容易。本节描述了这些步骤(请参阅 附录 A 了解有关在集群内部署操作员的信息)。请从操作员项目根目录内运行所有命令:
-
创建本地观察文件。生成的 watches.yaml 文件引用特定路径,该路径在部署的操作员场景中与 Helm chart 相对应。镜像创建过程负责将 chart 复制到必要的位置。当在集群外运行操作员时,仍需要此 watches.yaml 文件,因此您需要手动确保 chart 可以在该位置找到。
最简单的方法是复制已存在于操作员项目根目录中的 watches.yaml 文件:
$ `cp` `watches.yaml` `local``-watches.yaml`在 local-watches.yaml 文件中,编辑
chart字段,以包含您机器上 Helm chart 的完整路径。请记住本地观察文件的名称;稍后在启动操作员过程中会用到它。 -
使用
kubectl命令在集群中创建 CRDs:$ `kubectl` `apply` `-f` `deploy/crds/*_crd.yaml` -
创建完 CRDs 后,使用以下 SDK 命令启动操作员:
$ `operator-sdk` `up` `local` `--watches-file` `./local-watches.yaml` INFO[0000] Running the operator locally. INFO[0000] Using namespace default. 当操作员启动并处理 CR 请求时,操作员日志消息将显示在此运行过程中。
此命令启动一个运行过程,其行为方式与在集群内部署为 pod 的操作员相同。我们将在 “测试操作员” 中更详细地讨论测试。
Ansible Operator
Ansible 是一种流行的管理工具,用于自动化常见任务的配置和部署。与 Helm chart 类似,Ansible playbook 定义了一系列在一组服务器上运行的 tasks。可重用的 roles 可通过自定义功能扩展 Ansible,以增强 playbook 中的任务集合。
一个有用的角色集合是 k8s,它提供了与 Kubernetes 集群交互的任务。使用此模块,您可以编写 playbook 来处理应用程序的部署,包括所有必要的支持 Kubernetes 资源。
Operator SDK 提供了一种构建 Operator 的方式,该 Operator 将运行 Ansible playbook 以响应 CR 更改。SDK 提供了用于 Kubernetes 组件(例如控制器)的代码,使您可以专注于编写 playbook 本身。
构建 Operator
与其 Helm 支持一样,Operator SDK 生成项目框架。使用--type=ansible参数运行时,项目框架包含空白 Ansible 角色的结构。角色名称由指定的 CR 类型名称派生。
以下示例演示了创建定义 Visitors Site 应用程序 CR 的 Ansible Operator:
$ `OPERATOR_NAME``=``visitors-ansible-operator`
$ `operator-sdk` `new` `$OPERATOR_NAME` `--api-version``=``example.com/v1` `\ ` `--kind``=``VisitorsApp` `--type``=``ansible`
INFO[0000] Creating new Ansible operator 'visitors-ansible-operator'.
INFO[0000] Created deploy/service_account.yaml
INFO[0000] Created deploy/role.yaml
INFO[0000] Created deploy/role_binding.yaml
INFO[0000] Created deploy/crds/example_v1_visitorsapp_crd.yaml
INFO[0000] Created deploy/crds/example_v1_visitorsapp_cr.yaml
INFO[0000] Created build/Dockerfile
INFO[0000] Created roles/visitorsapp/README.md
INFO[0000] Created roles/visitorsapp/meta/main.yml
INFO[0000] Created roles/visitorsapp/files/.placeholder
INFO[0000] Created roles/visitorsapp/templates/.placeholder
INFO[0000] Created roles/visitorsapp/vars/main.yml
INFO[0000] Created molecule/test-local/playbook.yml
INFO[0000] Created roles/visitorsapp/defaults/main.yml
INFO[0000] Created roles/visitorsapp/tasks/main.yml
INFO[0000] Created molecule/default/molecule.yml
INFO[0000] Created build/test-framework/Dockerfile
INFO[0000] Created molecule/test-cluster/molecule.yml
INFO[0000] Created molecule/default/prepare.yml
INFO[0000] Created molecule/default/playbook.yml
INFO[0000] Created build/test-framework/ansible-test.sh
INFO[0000] Created molecule/default/asserts.yml
INFO[0000] Created molecule/test-cluster/playbook.yml
INFO[0000] Created roles/visitorsapp/handlers/main.yml
INFO[0000] Created watches.yaml
INFO[0000] Created deploy/operator.yaml
INFO[0000] Created .travis.yml
INFO[0000] Created molecule/test-local/molecule.yml
INFO[0000] Created molecule/test-local/prepare.yml
INFO[0000] Project creation complete.
此命令生成类似于 Helm Operator 示例的目录结构。SDK 创建包含 CRD 和部署模板等相同文件集的deploy目录。
与 Helm Operator 相比,有几个显著的区别:
watches.yaml
-
这样做的目的与 Helm Operator 相同:它将 CR 类型映射到在其解析期间执行的文件位置。但是,Ansible Operator 支持两种不同类型的文件(这些字段是互斥的):
-
如果包含
role字段,则必须指向在资源协调期间执行的 Ansible role目录。 -
如果包含
playbook字段,则必须指向要运行的playbook文件。
-
-
SDK 默认将此文件指向在生成期间创建的角色。
roles/
-
此目录包含所有操作员可能运行的 Ansible 角色。项目创建时,SDK 会生成新角色的基本文件。
-
如果 Operator 管理多个 CR 类型,则将多个角色添加到此目录中。此外,对于每种类型及其关联的角色,watches 文件都会添加一个条目。
接下来,您将为 CR 实现 Ansible 角色。角色的具体操作取决于应用程序:一些常见任务包括创建部署和服务以运行应用程序的容器。有关编写 Ansible 角色的更多信息,请参阅Ansible 文档。
您可以在本书的GitHub 存储库中找到部署 Visitors Site 的 Ansible 角色。为了简便起见,按照示例应用程序进行跟随时,角色文件作为发布可用。与以前的 Operator 创建命令类似,您可以通过以下方式添加 Visitors Site 角色:
$ `cd` `$OPERATOR_NAME``/roles/visitorsapp`
$ `wget` `https://github.com/kubernetes-operators-book/``\ ` `chapters/releases/download/1.0.0/visitors-ansible.tgz`
$ `tar` `-zxvf` `visitors-ansible.tgz` 
$ `rm` `visitors-ansible.tgz`
此命令使用运行 Visitors Site 角色所需的文件覆盖默认生成的角色文件。
本书不涵盖编写 Ansible 角色,但重要的是您了解如何将用户输入的配置值传播到 Ansible 角色中。
与 Helm 操作员类似,配置值来自 CR 的 spec 部分。然而,在 Playbooks 和 Roles 中,Ansible 使用标准的 {{ variable_name }} 语法。Kubernetes 中的字段名称通常使用驼峰命名(例如,camelCase),因此 Ansible 操作员会在将参数传递给 Ansible 角色之前将每个字段的名称转换为蛇形命名(例如,snake_case)。也就是说,字段名称 serviceAccount 将转换为 service_account。这样可以使用标准的 Ansible 约定重用现有的角色,同时也遵循 Kubernetes 资源约定。你可以在书籍的GitHub 仓库中找到部署访问者站点的 Ansible 角色的源代码。
扩展自定义资源定义(CRD)
与 Helm 操作员类似,您需要扩展生成的 CRD 来包含您的 CR 的具体信息。有关更多信息,请参阅附录 B。
检查操作员权限
Ansible 操作员还包括一个生成的角色,操作员用它来连接 Kubernetes API。更多关于优化默认权限的信息,请查看附录 C。
运行 Ansible 操作员
与 Helm 操作员类似,测试和调试 Ansible 操作员的最简单方法是在集群外运行它,避免构建和推送镜像的步骤。
然而,在你执行此操作之前,还有一些额外的步骤你需要完成:
-
首先,在运行操作员的机器上安装 Ansible。请查阅Ansible 文档获取有关如何在您的本地操作系统上安装 Ansible 的具体信息。
-
还必须安装其他与 Ansible 相关的软件包,包括以下内容(有关安装详细信息,请查阅文档):
-
与 Helm 操作员类似,SDK 生成的 watches.yaml 文件引用特定目录中的 Ansible 角色。因此,您需要复制 watches 文件并根据需要进行修改。再次强调,从操作员项目根目录中运行这些命令:
$ `cp` `watches.yaml` `local``-watches.yaml`在 local-watches.yaml 文件中,更改
role字段以反映您计算机上的目录结构。 -
使用
kubectl命令在集群中创建自定义资源定义(CRD):$ `kubectl` `apply` `-f` `deploy/crds/*_crd.yaml` -
一旦 CRD 在集群中部署,就可以使用 SDK 运行操作员:
$ `operator-sdk` `up` `local` `--watches-file` `./local-watches.yaml` INFO[0000] Running the operator locally. INFO[0000] Using namespace default. 操作员日志消息将在这个运行中的进程中显示,随着其启动和处理 CR 请求。
该命令启动一个运行中的进程,其行为类似于如果操作员作为一个 Pod 部署在集群内部的话。
现在让我们一起逐步走过测试操作员的步骤。
测试操作员
您可以使用相同的方法测试适配器运算符:通过部署 CR。Kubernetes 会通知运算符 CR 的存在,然后执行底层文件(Helm 图表或 Ansible 角色)。SDK 在 deploy/crds 目录中生成一个示例 CR 模板,您可以使用它,或者手动创建一个。
按照以下步骤测试本章讨论的两种运算符类型:
-
编辑示例 CR 模板的
spec部分(在访问者网站示例中,它命名为 example_v1_visitorsapp_cr.yaml),填入与您的 CR 相关的任何值。 -
使用 Kubernetes CLI 在运算符项目根目录中创建资源:
$ `kubectl` `apply` `-f` `deploy/crds/*_cr.yaml`运算符的输出将显示在您运行
operator-sdk up local命令的同一终端中。测试完成后,通过按下Ctrl-C结束运行进程。 -
按照 第五章 中描述的步骤导航到访问者网站,以验证应用程序按预期工作。
-
测试完成后,请使用
kubectl delete命令删除 CR:$ `kubectl` `delete` `-f` `deploy/crds/*_cr.yaml`
在开发过程中,重复此过程以测试更改。在每次迭代中,请确保重新启动运算符进程,以获取对 Helm 或 Ansible 文件的任何更改。
摘要
你无需成为程序员就能编写一个运算符。Operator SDK 简化了将两种现有的配置和配置技术(Helm 和 Ansible)打包为运算符的过程。SDK 还提供了一种快速测试和调试更改的方式,通过在集群之外运行运算符,跳过耗时的镜像构建和托管步骤。
在下一章中,我们将介绍一种更强大和灵活的运算符实现方式,使用 Go 语言。
资源
第七章:使用运算符 SDK 中的 Go 运算符
虽然 Helm 和 Ansible 运算符可以快速简单地创建,但它们的功能最终受到这些基础技术的限制。像动态响应应用程序或整个集群中特定变化这样的高级用例需要更灵活的解决方案。
运算符 SDK 提供了这种灵活性,使开发者能够轻松使用 Go 编程语言,包括其外部库生态系统,在他们的运算符中使用。
由于该过程比 Helm 或 Ansible 运算符稍微复杂一些,因此从高层次步骤的摘要开始是有道理的:
-
创建必要的代码,将其与 Kubernetes 绑定,允许其作为控制器运行运算符。
-
创建一个或多个 CRD 来建模应用程序的基础业务逻辑,并为用户提供与之交互的 API。
-
为每个 CRD 创建一个控制器,以处理其资源的生命周期。
-
构建运算符镜像并创建相关的 Kubernetes 清单以部署运算符及其 RBAC 组件(服务账户、角色等)。
虽然您可以手动编写所有这些部分,但运算符 SDK 提供了命令,可以自动创建大部分支持代码,使您可以专注于实现运算符的实际业务逻辑。
本章使用运算符 SDK 构建项目框架,实现 Go 中的运算符(参见第四章以获取 SDK 安装说明)。我们将探讨需要用自定义应用逻辑编辑的文件,并讨论运算符开发的一些常见实践。一旦运算符准备就绪,我们将在开发模式下运行它以进行测试和调试。
初始化运算符
由于运算符是用 Go 编写的,项目框架必须遵循语言约定。特别是,运算符代码必须位于您的 $GOPATH 中。有关更多信息,请参阅 GOPATH 文档。
SDK 的 new 命令创建了运算符所需的基础文件。如果未指定特定的运算符类型,该命令将生成一个基于 Go 的运算符项目:
$ OPERATOR_NAME=visitors-operator
$ operator-sdk new $OPERATOR_NAME
INFO[0000] Creating new Go operator 'visitors-operator'.
INFO[0000] Created go.mod
INFO[0000] Created tools.go
INFO[0000] Created cmd/manager/main.go
INFO[0000] Created build/Dockerfile
INFO[0000] Created build/bin/entrypoint
INFO[0000] Created build/bin/user_setup
INFO[0000] Created deploy/service_account.yaml
INFO[0000] Created deploy/role.yaml
INFO[0000] Created deploy/role_binding.yaml
INFO[0000] Created deploy/operator.yaml
INFO[0000] Created pkg/apis/apis.go
INFO[0000] Created pkg/controller/controller.go
INFO[0000] Created version/version.go
INFO[0000] Created .gitignore
INFO[0000] Validating project
[...] 
输出已截断以提高可读性。生成过程可能需要几分钟,因为需要下载所有 Go 依赖项。这些依赖项的详细信息将显示在命令输出中。
SDK 创建一个与 $OPERATOR_NAME 同名的新目录。生成过程会产生数百个文件,包括生成的文件和供运算符使用的供应商文件。方便的是,大多数文件无需手动编辑。我们将向您展示如何生成满足运算符自定义逻辑所需的文件,详见“自定义资源定义”。
运算符范围
您需要首先做出的第一个决定是运算符的范围。有两个选项:
命名空间
限制运算符管理单个命名空间中的资源
Cluster
允许运算符管理整个集群中的资源
默认情况下,SDK 生成的运算符是命名空间范围的。
尽管命名空间范围的运算符通常更可取,但可以将 SDK 生成的运算符更改为集群范围的。执行以下更改以使运算符能够在集群级别运行:
deploy/operator.yaml
- 将
WATCH_NAMESPACE变量的值更改为"",表示将监视所有命名空间,而不仅仅是运算符 Pod 所部署的命名空间。
deploy/role.yaml
- 将
kind从Role更改为ClusterRole,以便在运算符 Pod 的命名空间之外启用权限。
deploy/role_binding.yaml
-
将
kind从RoleBinding更改为ClusterRoleBinding。 -
在
roleRef下,将kind更改为ClusterRole。 -
在
subjects下,添加键为namespace,值为运算符 Pod 所部署的命名空间。
此外,您需要更新生成的 CRD(在下一节中讨论)以指示定义是集群范围的:
-
在 CRD 文件的
spec部分中,将scope字段更改为Cluster,而不是默认值Namespaced。 -
在 CRD 的_types.go文件中,在 CR 的结构体上方添加标签
// +genclient:nonNamespaced(这将与您用于创建它的kind字段具有相同的名称)。这样可以确保将来调用运算符 SDK 刷新 CRD 时不会将值重置为默认值。
例如,对VisitorsApp结构体的以下修改表明它是集群范围的:
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// VisitorsApp is the Schema for the visitorsapps API // +k8s:openapi-gen=true // +kubebuilder:subresource:status // +genclient:nonNamespaced 
type VisitorsApp struct {
标签必须放在资源类型结构体之前。
自定义资源定义
在第六章中,我们讨论了创建运算符时 CRD 的角色。您可以使用 SDK 的add api命令将新的 CRD 添加到运算符中。该命令从运算符项目的根目录运行,为本书中使用的访客站点示例生成 CRD(使用任意的“example.com”进行演示):
$ `operator-sdk` `add` `api` `--api-version``=``example.com/v1` `--kind``=``VisitorsApp`
INFO[0000] Generating api version example.com/v1 for kind VisitorsApp.
INFO[0000] Created pkg/apis/example/group.go
INFO[0000] Created pkg/apis/example/v1/visitorsapp_types.go
INFO[0000] Created pkg/apis/addtoscheme_example_v1.go
INFO[0000] Created pkg/apis/example/v1/register.go
INFO[0000] Created pkg/apis/example/v1/doc.go
INFO[0000] Created deploy/crds/example_v1_visitorsapp_cr.yaml
INFO[0001] Created deploy/crds/example_v1_visitorsapp_crd.yaml
INFO[0001] Running deepcopy code-generation for Custom Resource group versions:
[example:[v1], ]
INFO[0001] Code-generation complete.
INFO[0001] Running OpenAPI code-generation for Custom Resource group versions:
[example:[v1], ]
INFO[0003] Created deploy/crds/example_v1_visitorsapp_crd.yaml
INFO[0003] Code-generation complete.
INFO[0003] API generation complete.
该命令生成了多个文件。在以下列表中,请注意api-version和 CR 类型名称(kind)如何影响生成的名称(文件路径相对于运算符项目的根目录):
deploy/crds/example_v1_visitorsapp-cr.yaml
这是生成类型的示例 CR。它预先填充了适当的api-version、kind以及资源的名称。您需要填写spec部分,使用与您创建的 CRD 相关的值。
deploy/crds/example_v1_visitorsapp_crd.yaml
该文件是 CRD 清单的开头。SDK 生成与资源类型名称相关的许多字段(例如复数形式和列表变体),但您需要添加自定义字段,特定于您的资源类型。附录 B 详细介绍了如何完善此文件。
pkg/apis/example/v1/visitorsapp_types.go
此文件包含操作员代码库利用的多个结构对象。与许多生成的 Go 文件不同,此文件旨在进行编辑。
add api 命令构建适当的框架代码,但在您可以使用资源类型之前,必须定义创建新资源时指定的配置值集合。您还需要添加描述 CR 报告其状态时将使用的字段。您将在定义模板本身以及 Go 对象中添加这些值集合。以下两个部分详细说明每个步骤。
定义 Go 类型
在 *_types.go 文件中(在本例中为 visitorsapp_types.go),有两个结构对象需要处理:
-
规格对象(在本示例中为
VisitorsAppSpec)必须包括可为此类型资源指定的所有可能配置值。每个配置值由以下内容组成:-
变量的名称将在操作员代码中引用(遵循 Go 惯例,为了语言可见性目的以大写字母开头)
-
用于变量的 Go 类型
-
变量名称将在 CR 中指定的字段名称(换句话说,用户将编写的 JSON 或 YAML 清单)
-
-
状态对象(在本示例中为
VisitorsAppStatus)必须包括操作员可能设置的所有可能值,以传达 CR 的状态。每个值由以下内容组成:-
变量的名称将在操作员代码中引用(遵循 Go 惯例,为了可见性目的以大写字母开头)
-
用于变量的 Go 类型
-
作为在获取
-o yaml标志的资源时,将出现在 CR 描述中的字段名称
-
访问者站点示例支持其访问者应用程序 CR 中的以下数值:
Size
要创建的后端副本数量
Title
前端网页显示的文本
重要的是要意识到,尽管您在应用程序的不同 Pod 中使用这些值,但将它们包含在单个 CRD 中。从最终用户的角度来看,它们是整个应用程序的属性。操作员的责任是确定如何使用这些值。
每个资源状态中的 VisitorsApp CR 使用以下数值:
BackendImage
指示用于部署后端 Pod 的映像和版本
FrontendImage
指示用于部署前端 Pod 的映像和版本
visitorsapp_types.go 文件的以下片段演示了这些添加:
type VisitorsAppSpec struct {
Size int32 `json:"size"`
Title string `json:"title"`
}
type VisitorsAppStatus struct {
BackendImage string `json:"backendImage"`
FrontendImage string `json:"frontendImage"`
}
visitorsapp_types.go 文件的其余部分不需要进一步的更改。
在对**_types.go* 文件进行任何更改后,您需要使用 SDK 的generate命令(从项目的根目录)更新与这些对象一起工作的任何生成代码:
$ `operator-sdk` `generate` `k8s`
INFO[0000] Running deepcopy code-generation for Custom Resource
group versions: [example:[v1], ]
INFO[0000] Code-generation complete.
CRD 清单
类型文件的增加对 Operator 代码非常有用,但不提供给创建资源的最终用户任何洞察力。这些添加是对 CRD 本身进行的。
类似于类型文件,您将在spec和status部分中对 CRD 进行增加。附录 B 描述了编辑这些部分的过程。
运算权限
除了生成 CRD 外,Operator SDK 还创建 Operator 运行所需的 RBAC 资源。默认情况下,生成的角色权限非常宽松,您应在将 Operator 部署到生产环境之前优化其授予的权限。附录 C 涵盖了所有与 RBAC 相关的文件,并讨论了如何将权限范围限定于适用于 Operator 的内容。
控制器
CRD 及其相关的 Go 类型文件定义了用户将通过其中进行通信的入站 API。在 Operator Pod 内部,您需要一个控制器来监视 CR 的变化并相应地做出反应。
与添加 CRD 类似,您可以使用 SDK 生成控制器的骨架代码。您将使用先前生成的资源定义的api-version和kind将控制器范围限定到该类型。以下片段继续介绍访客站点示例:
$ `operator-sdk` `add` `controller` `--api-version``=``example.com/v1` `--kind``=``VisitorsApp`
INFO[0000] Generating controller version example.com/v1 for kind VisitorsApp.
INFO[0000] Created pkg/controller/visitorsapp/visitorsapp_controller.go 
INFO[0000] Created pkg/controller/add_visitorsapp.go
INFO[0000] Controller generation complete.
注意此文件的名称。它包含实现 Operator 自定义逻辑的 Kubernetes 控制器。
与 CRD 一样,此命令生成多个文件。特别感兴趣的是控制器文件,其根据关联的kind进行定位和命名。您无需手动编辑其他生成的文件。
控制器负责“协调”特定资源。单个协调操作的概念与 Kubernetes 遵循的声明模型一致。控制器不会明确处理诸如添加、删除或更新等事件,而是传递资源的当前状态给控制器。由控制器决定要进行的更改集,以更新现实以反映资源描述中所需的状态。有关 Kubernetes 控制器的更多信息,请参阅官方 Kubernetes 文档。
除了调和逻辑外,控制器还需要建立一个或多个“监视器”。监视器表示当“监视”的资源发生更改时,Kubernetes 应调用此控制器。虽然运算符逻辑的大部分位于控制器的Reconcile函数中,但add函数建立了将触发调和事件的监视器。SDK 在生成的控制器中添加了两个这样的监视器。
第一个监视器监听控制器监视的主资源的更改。SDK 根据生成控制器时使用的kind参数生成此监视器。在大多数情况下,这不需要更改。以下代码片段创建了 VisitorsApp 资源类型的监视器:
// Watch for changes to primary resource VisitorsApp
err = c.Watch(&source.Kind{Type: &examplev1.VisitorsApp{}},
&handler.EnqueueRequestForObject{})
if err != nil {
return err
}
第二个监视器,更确切地说,是一系列监视器,用于监听运算符创建以支持主资源的任何子资源的更改。例如,创建 VisitorsApp 资源会导致创建多个部署和服务对象以支持其功能。控制器为每种这些子类型创建一个监视器,注意仅作用于所有者与主资源相同类型的子资源。例如,以下代码创建了两个监视器,一个用于部署,一个用于服务,其父资源类型为 VisitorsApp:
err = c.Watch(&source.Kind{Type: &appsv1.Deployment{}},
&handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &examplev1.VisitorsApp{},
})
if err != nil {
return err
}
err = c.Watch(&source.Kind{Type: &corev1.Service{}},
&handler.EnqueueRequestForOwner{
IsController: true,
OwnerType: &examplev1.VisitorsApp{},
})
if err != nil {
return err
}
对于此代码片段创建的监视器,有两个感兴趣的区域:
-
构造函数中
Type的值表示 Kubernetes 监视的子资源类型。每个子资源类型都需要有自己的监视器。 -
每个子资源类型的监视器将
OwnerType的值设置为主资源类型,作用域限定监视器并导致 Kubernetes 触发父资源的调和。如果没有这个设置,Kubernetes 将对此控制器触发关于所有服务和部署更改的调和,而不管它们是否属于运算符。
调和函数
Reconcile函数,也称为调和循环,是运算符逻辑所在的地方。此函数的目的是根据资源请求的期望状态解决系统的实际状态。有关帮助编写此函数的更多信息,请参见下一节。
警告
由于 Kubernetes 在资源的生命周期中多次调用Reconcile函数,因此实现必须是幂等的,以防止创建重复的子资源。更多信息请参见“幂等性”。
Reconcile函数返回两个对象:一个ReconcileResult实例和一个错误(如果有的话)。这些返回值指示 Kubernetes 是否应重新排队请求。换句话说,运算符告诉 Kubernetes 是否应该再次执行调和循环。基于返回值的可能结果是:
return reconcile.Result{}, nil
调解过程完成且无错误,并且不需要通过调解循环再次进行。
return reconcile.Result{}, err
调解由于错误失败,Kubernetes 应重新排队以再试。
return reconcile.Result{Requeue: true}, nil
调解未遇到错误,但 Kubernetes 应重新排队以进行另一个迭代运行。
return reconcile.Result{RequeueAfter: time.Second*5}, nil
与前面的结果类似,但这将在重新排队请求之前等待指定的时间。当必须按顺序运行多个步骤但可能需要一些时间才能完成时,这种方法很有用。例如,如果后端服务在启动之前需要运行数据库,则可以延迟重新排队调解以使数据库有时间启动。一旦数据库运行,操作员就不会重新排队调解请求,其余步骤将继续。
操作员编写提示
在一本书中不可能涵盖所有操作员的可预见用途和复杂性。单单是应用程序的安装和升级的差异就太多了,这些仅代表操作员成熟模型的前两层。相反,我们将介绍一些通常由操作员执行的基本功能的一般指南,以帮助您入门。
由于基于 Go 的运算符大量使用 Go Kubernetes 库,因此审查API 文档可能很有用。特别是,核心/v1 和 apps/v1 模块经常用于与常见的 Kubernetes 资源交互。
检索资源
Reconcile函数通常执行的第一步是检索触发调解请求的主资源。Operator SDK 为此生成代码,其示例如下所示,类似于 Visitors Site 示例:
// Fetch the VisitorsApp instance instance := &examplev1.VisitorsApp{}
err := r.client.Get(context.TODO(), request.NamespacedName, instance) 
if err != nil {
if errors.IsNotFound(err) {
return reconcile.Result{}, nil 
}
// Error reading the object - requeue the request.
return reconcile.Result{}, err
}
使用触发调解的资源的值填充先前创建的 VisitorsApp 对象。
变量r是调解器对象,Reconcile函数在其上调用。它提供了客户端对象,该对象是 Kubernetes API 的经过身份验证的客户端。
当资源被删除时,Kubernetes 仍然调用Reconcile函数,在这种情况下,Get调用会返回错误。在此示例中,操作员不需要进一步清理已删除的资源,只需返回调解成功即可。我们在“子资源删除”中提供有关处理已删除资源的更多信息。
检索到的实例有两个主要目的:
-
从其
Spec字段中检索有关资源配置值 -
使用其
Status字段设置资源的当前状态,并将更新后的信息保存到 Kubernetes 中。
除了Get函数外,客户端还提供了一个更新资源值的函数。当更新资源的Status字段时,你将使用此函数将更改持久化到 Kubernetes 中。以下代码片段更新了先前检索到的 VisitorsApp 实例状态中的一个字段,并将更改保存回 Kubernetes:
instance.Status.BackendImage = "example"
err := r.client.Status().Update(context.TODO(), instance)
子资源创建
在操作员常见的第一个任务中,是部署必要的资源以使应用程序运行起来。这个操作至关重要,必须是幂等的;对Reconcile函数的后续调用应确保资源正在运行,而不是创建重复的资源。
这些子资源通常包括但不限于部署和服务对象。它们的处理方式类似且简单:检查命名空间中资源是否存在,如果不存在,则创建。
以下示例代码片段检查目标命名空间中是否存在部署:
found := &appsv1.Deployment{}
findMe := types.NamespacedName{
Name: "myDeployment", 
Namespace: instance.Namespace, 
}
err := r.client.Get(context.TODO(), findMe, found)
if err != nil && errors.IsNotFound(err) {
// Creation logic 
}
操作员知道它创建的子资源的名称,或者至少知道如何推断它们(详见“子资源命名”进行更深入的讨论)。在实际用例中,"myDeployment"将被替换为操作员在创建部署时使用的相同名称,需要确保相对于命名空间的唯一性。
instance变量是关于资源检索的早期片段中设置的,并且指代表示正在协调的主资源的对象。
在这一点上,未找到子资源,并且从 Kubernetes API 中未检索到进一步的错误,因此应执行资源创建逻辑。
操作员通过填充必要的 Kubernetes 对象并使用客户端请求创建资源。请查阅 Kubernetes Go 客户端 API 以获取每种类型资源实例化的规范。你可以在 core/v1 或者 apps/v1 模块中找到许多所需的规范。
例如,以下代码片段创建了访客网站示例应用程序中使用的 MySQL 数据库的部署规范:
labels := map[string]string {
"app": "visitors",
"visitorssite_cr": instance.Name,
"tier": "mysql",
}
size := int32(1) 
userSecret := &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: mysqlAuthName()},
Key: "username",
},
}
passwordSecret := &corev1.EnvVarSource{
SecretKeyRef: &corev1.SecretKeySelector{
LocalObjectReference: corev1.LocalObjectReference{Name: mysqlAuthName()},
Key: "password",
},
}
dep := &appsv1.Deployment{
ObjectMeta: metav1.ObjectMeta{
Name: "mysql-backend-service", 
Namespace: instance.Namespace,
},
Spec: appsv1.DeploymentSpec{
Replicas: &size,
Selector: &metav1.LabelSelector{
MatchLabels: labels,
},
Template: corev1.PodTemplateSpec{
ObjectMeta: metav1.ObjectMeta{
Labels: labels,
},
Spec: corev1.PodSpec{
Containers: []corev1.Container{{
Image: "mysql:5.7",
Name: "visitors-mysql",
Ports: []corev1.ContainerPort{{
ContainerPort: 3306,
Name: "mysql",
}},
Env: []corev1.EnvVar{ 
{
Name: "MYSQL_ROOT_PASSWORD",
Value: "password",
},
{
Name: "MYSQL_DATABASE",
Value: "visitors",
},
{
Name: "MYSQL_USER",
ValueFrom: userSecret,
},
{
Name: "MYSQL_PASSWORD",
ValueFrom: passwordSecret,
},
},
}},
},
},
},
}
controllerutil.SetControllerReference(instance, dep, r.scheme) 
在许多情况下,操作员将从主资源的规范中读取部署的 Pod 数目。为简单起见,在此示例中,将其硬编码为1。
当你尝试查看部署是否存在时,这就是在前面片段中使用的值。
对于此示例,这些是硬编码的值。请注意根据需要生成随机化值。
这可以说是定义中最重要的一行。它建立了主资源(VisitorsApp)与子资源(deployment)之间的父/子关系。Kubernetes 使用此关系执行某些操作,如下一节所示。
deployment 的 Go 表示结构与 YAML 定义紧密相关。再次查阅 API 文档,了解如何使用 Go 对象模型的具体细节。
无论子资源类型是部署、服务等,都要使用客户端创建它:
createMe := // Deployment instance from above
// Create the service
err = r.client.Create(context.TODO(), createMe)
if err != nil {
// Creation failed
return &reconcile.Result{}, err
} else {
// Creation was successful
return nil, nil
}
子资源删除
在大多数情况下,删除子资源比创建它们简单得多:Kubernetes 会为您完成。如果子资源的所有者类型正确设置为主资源,则在删除父资源时,Kubernetes 垃圾回收将自动清理其所有子资源。
重要的是要理解,当 Kubernetes 删除资源时,仍会调用Reconcile函数。仍然执行 Kubernetes 垃圾回收,并且运算符将无法检索到主资源。请参见“检索资源”中检查此情况的代码示例。
但是,在某些情况下,需要特定的清理逻辑。在这种情况下的方法是通过使用finalizer阻止主资源的删除。
finalizer 只是资源上的一系列字符串。如果一个或多个 finalizer 存在于资源上,则对象的metadata.deletionTimestamp字段将被填充,表示最终用户希望删除该资源。然而,只有当所有 finalizer 都被移除后,Kubernetes 才会执行实际的删除。
使用此结构,您可以阻止资源的垃圾回收,直到运算符有机会执行自己的清理步骤。一旦运算符完成了必要的清理工作,它将删除 finalizer,解除 Kubernetes 执行其正常删除步骤的阻塞。
以下片段演示了使用 finalizer 提供一个窗口,其中运算符可以执行预删除步骤。此代码在检索实例对象之后执行,如“检索资源”中所述:
finalizer := "visitors.example.com"
beingDeleted := instance.GetDeletionTimestamp() != nil 
if beingDeleted {
if contains(instance.GetFinalizers(), finalizer) {
// Perform finalization logic. If this fails, leave the finalizer
// intact and requeue the reconcile request to attempt the clean
// up again without allowing Kubernetes to actually delete
// the resource.
instance.SetFinalizers(remove(instance.GetFinalizers(), finalizer)) 
err := r.client.Update(context.TODO(), instance)
if err != nil {
return reconcile.Result{}, err
}
}
return reconcile.Result{}, nil
}
删除时间戳的存在表明,一个或多个 finalizer 正在阻止所请求的删除。
一旦清理任务完成,运算符会删除 finalizer,因此 Kubernetes 可以继续进行资源清理。
子资源命名
虽然最终用户在创建 CR 时提供 CR 的名称,但操作员负责生成它创建的任何子资源的名称。在创建这些名称时,请考虑以下原则:
-
资源名称在给定命名空间内必须是唯一的。
-
子资源名称应动态生成。如果在同一命名空间中存在多个相同类型的资源,则硬编码子资源名称会导致冲突。
-
子资源名称必须可复制和一致。在未来迭代中,操作员可能需要通过协调循环访问资源的子项,并且必须能够可靠地通过名称检索这些资源。
幂等性
许多开发人员在编写控制器时面临的最大障碍之一是 Kubernetes 使用声明式API 的概念。最终用户不会立即发出 Kubernetes 立即执行的命令。相反,他们请求集群应达到的最终状态。
因此,控制器(及其扩展的操作员)的接口不包括“添加资源”或“更改配置值”等命令。相反,Kubernetes 只是要求控制器协调资源的状态。然后,操作员确定要采取哪些步骤(如果有的话)以确保达到最终状态。
因此,操作员的幂等性至关重要。对于未更改的资源的多次协调请求,必须每次产生相同的效果。
以下提示可以帮助您确保操作员中的幂等性:
-
在创建子资源之前,请检查它们是否已存在。请记住,Kubernetes 可能出于各种原因调用协调循环,而不仅仅是在用户首次创建 CR 时。因此,您的控制器不应在每次循环迭代中复制 CR 的子资源。
-
对资源规范(即其配置值)的更改会触发协调循环。因此,仅仅检查预期子资源的存在通常是不够的。操作员还需要验证,在协调时,子资源配置是否与父资源中定义的相匹配。
-
并非每次对资源的更改都会调用协调。可能一次协调中包含多个更改。操作员必须小心确保所有子资源都代表了 CR 的整个状态。
-
即使操作员确定不需要对现有资源进行任何更改,也不意味着它不需要更新 CR 的
Status字段。根据 CR 状态中捕获的值,可能有必要更新这些值,即使操作员确定不需要对现有资源进行任何更改。
操作员的影响
重要的是要意识到您的 Operator 将对集群产生的影响。在大多数情况下,您的 Operator 将创建一个或多个资源。它还需要通过 Kubernetes API 与集群进行通信。如果 Operator 错误处理这些操作,可能会对整个集群的性能产生负面影响。
如何处理这一最佳实践因 Operator 而异。没有一套规则可以确保 Operator 不会过度负担集群。然而,您可以使用以下指南作为分析 Operator 方法的起点:
-
在频繁调用 Kubernetes API 时要小心。确保在重复检查 API 以满足特定状态时使用合理的延迟(以秒为单位,而不是毫秒)。
-
可能时,尽量不要阻塞调解方法长时间。例如,如果在继续之前等待子资源可用,请考虑在延迟后触发另一个调解(有关触发调解循环中后续迭代的更多信息,请参见 “调解函数”)。这种方法允许 Kubernetes 管理其资源,而不是让调解请求长时间等待。
-
如果要部署大量资源,请考虑通过调解循环的多次迭代来限制部署请求。请记住集群上还同时运行其他工作负载。您的 Operator 不应通过同时发出许多创建请求来对集群资源造成过多压力。
本地运行 Operator
Operator SDK 提供了在运行中的集群之外运行 Operator 的方法。这有助于通过省去镜像创建和托管步骤来加快开发和测试速度。运行 Operator 的进程可能位于集群之外,但 Kubernetes 将其视为任何其他控制器。
测试 Operator 的高级步骤如下:
-
部署 CRD. 您只需执行一次此操作,除非需要对 CRD 进行进一步更改。在这些情况下,从 Operator 项目根目录再次运行
kubectl apply命令以应用任何更改:$ `kubectl` `apply` `-f` `deploy/crds/*_crd.yaml` -
以本地模式启动 Operator. Operator SDK 使用来自
kubectl配置文件的凭据连接到集群并附加 Operator。运行的进程将作为在集群内运行的 Operator pod,并将日志信息写入标准输出:$ `export` `OPERATOR_NAME``=``<``operator-name>` $ `operator-sdk` `up` `local` `--namespace` `default`--namespace标志指示 Operator 将显示为运行的命名空间。 -
部署示例资源. SDK 生成了一个示例 CR 和 CRD。它们位于同一目录中,文件名以 _cr.yaml 结尾来表示其功能。
在大多数情况下,您将希望编辑此文件的
spec部分,以提供资源的相关配置值。完成必要的更改后,使用kubectl部署 CR(从项目根目录):$ `kubectl` `apply` `-f` `deploy/crds/*_cr.yaml` -
停止正在运行的运算符进程。 通过按下
Ctrl+C停止运算符进程。除非运算符将 finalizers 添加到 CR 中,在删除 CR 本身之前,这样做是安全的,因为 Kubernetes 将使用其资源的父/子关系清理任何依赖对象。
注意
此处描述的流程对于开发目的很有用,但对于生产环境,运算符作为镜像交付。有关如何在集群内作为容器构建和部署运算符的更多信息,请参见 附录 A。
Visitors Site 示例
Visitors Site 运算符的代码库过大而无法包含。您可以在 本书的 GitHub 存储库 中找到完整构建的运算符。
Operator SDK 生成了该存储库中的许多文件。修改以运行 Visitors Site 应用程序的文件包括:
deploy/crds/
-
example_v1_visitorsapp_crd.yaml
- 此文件包含了 CRD。
-
example_v1_visitorsapp_cr.yaml
- 这个文件定义了一个带有合理示例数据的 CR。
pkg/apis/example/v1/visitorsapp_types.go
- 该文件包含表示 CR 的 Go 对象,包括其
spec和status字段。
pkg/controller/visitorsapp/
-
backend.go, frontend.go, mysql.go
- 这些文件包含部署 Visitors Site 组件的所有特定信息。这包括运算符维护的部署和服务,以及处理最终用户更改 CR 时更新现有资源的逻辑。
-
common.go
- 此文件包含用于确保部署和服务运行的实用方法,必要时创建它们。
-
visitorsapp_controller.go
- Operator SDK 最初生成了此文件,然后为 Visitors Site 特定逻辑进行了修改。
Reconcile方法包含了大部分更改;通过调用先前描述的文件中的函数,它驱动运算符的整体流程。
- Operator SDK 最初生成了此文件,然后为 Visitors Site 特定逻辑进行了修改。
概要
编写一个运算符需要大量的代码来将其作为控制器与 Kubernetes 集成。Operator SDK 通过生成大部分样板代码来简化开发,让你专注于业务逻辑方面。SDK 还提供了构建和测试运算符的实用工具,大大减少了从构思到运行运算符所需的工作量。
资源
第八章:运算符生命周期管理器(Operator Lifecycle Manager,OLM)。
一旦您编写了操作员(Operator),就该将注意力转向其安装和管理。由于部署操作员涉及多个步骤,包括创建部署、添加自定义资源定义以及配置必要的权限,因此需要管理层来促进这一过程。
操作员生命周期管理器(OLM)通过引入交付操作员和在兼容 UI 中可视化所需元数据的打包机制来履行这一角色,其中包括安装说明和 CRD 描述符形式的 API 提示。
OLM 的好处不仅限于安装,还包括 Day 2 运维,例如管理现有操作员的升级、通过版本通道传达操作员稳定性以及将多个操作员托管源聚合到单一接口的能力。
我们通过介绍 OLM 及其接口来开始本章,包括用户将在集群内部与之交互的 CRD 以及它用于操作员的打包格式。接下来,我们将展示 OLM 的操作,使用它连接到 OperatorHub.io 来安装操作员。我们以开发者为重点,探讨编写必要的元数据文件以使操作员可在 OLM 中使用,并在本地集群中进行测试的过程。
OLM 自定义资源。
正如您所知,操作员拥有的 CRD 构成了操作员的 API。因此,逐个查看由 OLM 安装的每个 CRD 及其用途是有意义的。
ClusterServiceVersion(集群服务版本)。
ClusterServiceVersion(CSV)是描述操作员的主要元数据资源。每个 CSV 表示操作员的一个版本,并包含以下内容:
-
操作员的通用元数据,包括其名称、版本、描述和图标。
-
描述操作员安装信息,包括创建的部署和所需的权限。
-
操作员拥有的自定义资源定义(CRD)以及操作员依赖的任何 CRD 的引用。
-
CRD 字段上的注解,为用户提供如何正确指定字段值的提示。
当学习 CSV 时,将其概念与传统 Linux 系统进行关联可能很有用。您可以将 CSV 视为类似于 Linux 软件包(如 Red Hat Package Manager(RPM)文件)的东西。与 RPM 文件类似,CSV 包含有关如何安装操作员及其所需依赖项的信息。基于这种类比,您可以将 OLM 视为类似于 yum 或 DNF 的管理工具。
另一个重要的方面是理解 CSV 与其管理的运算符部署资源之间的关系。就像部署描述了它创建的 pod 的“pod 模板”一样,CSV 包含了用于运算符 pod 部署的“部署模板”。这是在 Kubernetes 意义上的正式所有权;如果运算符部署被删除,CSV 将重新创建它以将集群恢复到所需状态,类似于部署将导致删除的 pod 重新创建。
集群服务版本(ClusterServiceVersion)资源通常是从集群服务版本 YAML 文件中填充的。我们将在“编写集群服务版本文件”中提供有关如何编写此文件的更多详细信息。
CatalogSource
CatalogSource 包含访问运算符存储库的信息。OLM 提供了一个名为packagemanifests的实用 API,用于查询目录源,该 API 提供了运算符列表以及它们所在的目录。它使用这种资源来填充可用运算符列表。以下是针对默认目录源使用packagemanifests API 的示例:
$ `kubectl` `-n` `olm` `get` `packagemanifests`
NAME CATALOG AGE
akka-cluster-operator Community Operators 19s
appsody-operator Community Operators 19s
[...]
订阅
最终用户创建订阅来安装,并随后更新,OLM 提供的运算符。订阅是针对通道进行的,通道是运算符版本的流,例如“稳定”或“夜间”。
继续之前对 Linux 软件包的类比,订阅等同于安装软件包的命令,例如yum install。通过 yum 的安装命令通常会引用软件包的名称而不是特定版本,将最新软件包的确定留给 yum 自己。同样,通过名称和通道订阅运算符让 OLM 根据特定通道中可用的内容解析版本。
用户使用批准模式配置订阅。此值设置为manual或automatic,告诉 OLM 在安装运算符之前是否需要手动用户审查。如果设置为manual approval,OLM 兼容的用户界面会向用户呈现 OLM 在运算符安装期间将创建的资源的详细信息。用户可以选择批准或拒绝运算符,OLM 会采取适当的下一步。
InstallPlan
订阅创建一个InstallPlan,描述了 OLM 将创建的用于满足 CSV 资源需求的完整资源列表。对于需要手动批准的订阅,最终用户会在此资源上设置批准,以通知 OLM 安装应该继续。否则,用户无需明确与这些资源交互。
OperatorGroup
最终用户通过OperatorGroup控制运算符的多租户性。这些指定可以由单个运算符访问的命名空间。换句话说,属于 OperatorGroup 的运算符将不会对未在组指示的命名空间中的自定义资源更改作出反应。
尽管您可以使用 OperatorGroups 对一组命名空间进行精细化控制,但它们通常有两种常见用法:
-
将运算符范围限定为单个命名空间
-
允许运算符在所有命名空间全局运行
例如,以下定义创建了一个组,该组将其内的运算符范围限定为单个命名空间ns-alpha:
apiVersion: operators.coreos.com/v1alpha2
kind: OperatorGroup
metadata:
name: group-alpha
namespace: ns-alpha
spec:
targetNamespaces:
- ns-alpha
完全省略指示符将导致该组覆盖集群中所有命名空间:
apiVersion: operators.coreos.com/v1alpha2
kind: OperatorGroup
metadata:
name: group-alpha
namespace: ns-alpha 
请注意,作为 Kubernetes 资源,OperatorGroup 仍必须驻留在特定命名空间中。但是,缺少targetNamespaces指定意味着 OperatorGroup 将覆盖所有命名空间。
注意
此处显示的两个示例涵盖了大多数用例;创建范围超过一个特定命名空间的精细 OperatorGroups 超出了本书的范围。您可以在OLM 的 GitHub 存储库中找到更多信息。
安装 OLM
在本章的其余部分中,我们将探讨使用和为 OLM 开发。由于大多数 Kubernetes 发行版默认未安装 OLM,第一步是安装运行所需的资源。
警告
OLM 是一个不断发展的项目。因此,请务必查阅其 GitHub 存储库,以找到当前发布版的最新安装说明。您可以在 OLM 项目的 GitHub 存储库上找到最新的发布版本。
在当前版本(0.11.0)中,安装执行两项主要任务。
首先,您需要安装 OLM 所需的 CRDs。这些 CRD 作为 API 进入 OLM,并提供配置外部源的能力,这些外部源提供运算符以及用于使这些运算符对用户可用的集群端资源。您可以通过kubectl apply命令创建这些 CRD,如下所示:
$ `kubectl` `apply` `-f` `\` `https://github.com/operator-framework/operator-lifecycle-manager/releases/``\` `download/0.11.0/crds.yaml`
clusterserviceversions.operators.coreos.com created
installplans.operators.coreos.com created
subscriptions.operators.coreos.com created
catalogsources.operators.coreos.com created
operatorgroups.operators.coreos.com created
注意
此处的示例使用了 0.11.0 版本,这是书写时的最新版本;您可以根据阅读时的最新版本更新这些命令。
第二步是创建构成 OLM 本身的所有 Kubernetes 资源。这些资源包括将驱动 OLM 的运算符以及使其正常运行所需的 RBAC 资源(ServiceAccounts、ClusterRoles 等)。
与 CRD 创建一样,您可以通过kubectl apply命令执行此步骤:
$ `kubectl` `apply` `-f` `\` `https://github.com/operator-framework/operator-lifecycle-manager/``\` `releases/download/0.11.0/olm.yaml`
namespace/olm created
namespace/operators created
system:controller:operator-lifecycle-manager created
serviceaccount/olm-operator-serviceaccount created
clusterrolebinding.rbac.authorization.k8s.io/olm-operator-binding-olm created
deployment.apps/olm-operator created
deployment.apps/catalog-operator created
clusterrole.rbac.authorization.k8s.io/aggregate-olm-edit created
clusterrole.rbac.authorization.k8s.io/aggregate-olm-view created
operatorgroup.operators.coreos.com/global-operators created
operatorgroup.operators.coreos.com/olm-operators created
clusterserviceversion.operators.coreos.com/packageserver created
catalogsource.operators.coreos.com/operatorhubio-catalog created
您可以通过查看创建的资源来验证安装是否成功:
$ kubectl get ns olm
NAME STATUS AGE
olm Active 43s
$ kubectl get pods -n olm
NAME READY STATUS RESTARTS AGE
catalog-operator-7c94984c6c-wpxsv 1/1 Running 0 68s
olm-operator-79fdbcc897-r76ss 1/1 Running 0 68s
olm-operators-qlkh2 1/1 Running 0 57s
operatorhubio-catalog-9jdd8 1/1 Running 0 57s
packageserver-654686f57d-74skk 1/1 Running 0 39s
packageserver-654686f57d-b8ckz 1/1 Running 0 39s
$ kubectl get crd
NAME CREATED AT
catalogsources.operators.coreos.com 2019-08-07T20:30:42Z
clusterserviceversions.operators.coreos.com 2019-08-07T20:30:42Z
installplans.operators.coreos.com 2019-08-07T20:30:42Z
operatorgroups.operators.coreos.com 2019-08-07T20:30:42Z
subscriptions.operators.coreos.com 2019-08-07T20:30:42Z
使用 OLM
现在我们已经介绍了围绕 OLM 的基本概念,让我们看看如何使用它来安装运算符。我们将使用 OperatorHub.io 作为运算符的源代码库。我们将在第十章中更详细地介绍 OperatorHub.io,但现在需要知道的重要信息是,它是一个由社区策划的公开可用运算符列表,用于与 OLM 一起使用。与本章前面的 Linux 软件包类比一致,您可以将其视为类似于 RPM 存储库。
安装 OLM 将在 olm 命名空间中创建一个默认的目录源。您可以使用 CLI 验证该源是否存在,名称为 operatorhubio-catalog:
$ `kubectl` `get` `catalogsource` `-n` `olm`
NAME NAME TYPE PUBLISHER AGE
operatorhubio-catalog Community Operators grpc OperatorHub.io 4h20m
您可以使用 describe 命令找到有关源的更多详细信息:
$ `kubectl` `describe` `catalogsource/operatorhubio-catalog` `-n` `olm`
Name: operatorhubio-catalog
Namespace: olm
Labels: <none>
Annotations: kubectl.kubernetes.io/last-applied-configuration...
API Version: operators.coreos.com/v1alpha1
Kind: CatalogSource
Metadata:
Creation Timestamp: 2019-09-23T13:53:39Z
Generation: 1
Resource Version: 801
Self Link: /apis/operators.coreos.com/v1alpha1/...
UID: 45842de1-3b6d-4b1b-bd36-f616dec94c6a
Spec:
Display Name: Community Operators 
Image: quay.io/operator-framework/upstream-community-operators:latest
Publisher: OperatorHub.io
Source Type: grpc
Status:
Last Sync: 2019-09-23T13:53:54Z
Registry Service:
Created At: 2019-09-23T13:53:44Z
Port: 50051
Protocol: grpc
Service Name: operatorhubio-catalog
Service Namespace: olm
Events: <none>
注意显示名称仅为“Community Operators”,而不指示任何关于 OperatorHub.io 的信息。此值将出现在下一个命令的输出中,当我们查看可能的运算符列表时。
此目录源已配置为读取 OperatorHub.io 托管的所有运算符。您可以使用 packagemanifest 实用程序 API 获取发现的运算符列表:
$ `kubectl` `get` `packagemanifest` `-n` `olm`
NAME CATALOG AGE
akka-cluster-operator Community Operators 4h47m
appsody-operator Community Operators 4h47m
aqua Community Operators 4h47m
atlasmap-operator Community Operators 4h47m
[...] 
截至撰写时,OperatorHub.io 上有接近 80 个运算符。为简洁起见,我们截取了此命令的输出。
对于此示例,您将安装 etcd 运算符。第一步是定义一个 OperatorGroup 来指定运算符将管理哪些命名空间。您将要使用的 etcd 运算符仅限于单个命名空间(稍后您将看到我们如何确定),因此您将为默认命名空间创建一个组:
apiVersion: operators.coreos.com/v1alpha2
kind: OperatorGroup
metadata:
name: default-og
namespace: default
spec:
targetNamespaces:
- default
使用 kubectl 的 apply 命令创建该组(本示例假定前一片段的 YAML 已保存为名为 all-og.yaml 的文件):
$ `kubectl` `apply` `-f` `all-og.yaml`
operatorgroup.operators.coreos.com/default-og created
订阅的创建会触发运算符的安装。在进行此操作之前,您需要确定要订阅哪个通道。OLM 除了提供运算符的通道信息外,还提供了丰富的其他详细信息。
您可以使用 packagemanifest API 查看此信息:
$ `kubectl` `describe` `packagemanifest/etcd` `-n` `olm`
Name: etcd
Namespace: olm
Labels: catalog=operatorhubio-catalog
catalog-namespace=olm
provider=CNCF
provider-url=
Annotations: <none>
API Version: packages.operators.coreos.com/v1
Kind: PackageManifest
Metadata:
Creation Timestamp: 2019-09-23T13:53:39Z
Self Link: /apis/packages.operators.coreos.com/v1/namespaces/...
Spec:
Status:
Catalog Source: operatorhubio-catalog
Catalog Source Display Name: Community Operators
Catalog Source Namespace: olm
Catalog Source Publisher: OperatorHub.io
Channels:
Current CSV: etcdoperator.v0.9.4-clusterwide
Current CSV Desc:
Annotations:
Alm - Examples: [...] 
[...] 
Install Modes: 
Type: OwnNamespace
Supported: true 
Type: SingleNamespace
Supported: true Type: MultiNamespace
Supported: false Type: AllNamespaces
Supported: false 
Provider:
Name: CNCF
Version: 0.9.4
Name: singlenamespace-alpha 
Default Channel: singlenamespace-alpha
Package Name: etcd
Provider:
Name: CNCF
[...]
包清单的示例部分包含一系列清单,您可以使用这些清单部署此运算符定义的自定义资源。为简洁起见,我们从此输出中省略了它们。
为了可读性,我们剪裁了文件的大部分内容。我们将在“编写群集服务版本文件”部分讨论这些字段中的许多内容。
安装模式部分描述了最终用户可能部署此运算符的情况。我们稍后将在本章中详细介绍这些内容。
此特定通道提供了一个配置为在部署的同一命名空间中监视的运算符。
类似地,最终用户无法安装此 Operator 来监视集群中的所有命名空间。如果您在包清单数据中查找,将会找到另一个名为 clusterwide-alpha 的通道,适用于此目的。
此部分的 Name 字段指示订阅引用的通道名称。
由于这个 Operator 来自 OperatorHub.io,直接查看其页面可能会很有帮助。包清单中包含的所有数据都显示在各个 Operator 页面上,但以更易读的方式格式化显示。您可以在 etcd Operator 页面 查看这些内容。
一旦确定了通道,最后一步是创建订阅资源本身。以下是一个示例清单:
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: etcd-subscription
namespace: default 
spec:
name: etcd 
source: operatorhubio-catalog 
sourceNamespace: olm
channel: singlenamespace-alpha 
此清单在默认命名空间中安装了订阅,从而安装了 Operator 部署本身。
通过 packagemanifest API 调用找到要安装的 Operator 的名称。
source 和 sourceNamespace 描述了提供 Operator 的目录源的位置。
OLM 将从 singlenamespace-alpha 通道安装 Operators。
与其他资源一样,您可以使用 kubectl apply 创建订阅(此命令假定上述订阅 YAML 已保存在名为 sub.yaml 的文件中):
$ `kubectl` `apply` `-f` `sub.yaml`
subscription.operators.coreos.com/etcd-subscription created
探索 Operator
当您创建订阅时,会发生一些事情。在资源层次结构的最高级别上,OLM 在默认命名空间中创建一个 ClusterServiceVersion 资源:
$ `kubectl` `get` `csv` `-n` `default`
NAME DISPLAY VERSION REPLACES PHASE
etcdoperator.v0.9.4 etcd 0.9.4 etcdoperator.v0.9.2 Succeeded
CSV 实际上是订阅安装的内容——就像 RPM 比方中的软件包一样。OLM 执行在 CSV 中定义的 Operator 安装步骤来创建 Operator Pods 本身。此外,OLM 还将存储有关此过程中事件的信息,您可以使用 describe 命令查看:
$ `kubectl` `describe` `csv/etcdoperator.v0.9.4` `-n` `default`
[...]
Events:
operator-lifecycle-manager requirements not yet checked
one or more requirements couldn't be found
all requirements found, attempting install
waiting for install components to report healthy
installing: ComponentMissing: missing deployment with name=etcd-operator
installing: ComponentMissing: missing deployment with name=etcd-operator
installing: Waiting: waiting for deployment etcd-operator to become ready:
Waiting for rollout to finish: 0 of 1 updated replicas are available...
install strategy completed with no errors
注意
此处的输出已编辑以适应页面。您的输出将略有不同,并包含更多事件数据。
OLM 负责按照 CSV 中包含的部署模板来创建 Operator Pod 本身。在资源所有权层次结构下进一步查看,您可以看到 OLM 还创建了一个部署资源:
$ kubectl get deployment -n default
NAME READY UP-TO-DATE AVAILABLE AGE
etcd-operator 1/1 1 1 3m42s
明确查看部署的详细信息显示了 CSV 与此部署之间的所有权关系:
$ `kubectl` `get` `deployment/etcd-operator` `-n` `default` `-o` `yaml`
[...]
ownerReferences:
- apiVersion: operators.coreos.com/v1alpha1
blockOwnerDeletion: false controller: false kind: ClusterServiceVersion
name: etcdoperator.v0.9.4
uid: 564c15d9-ab49-439f-8ea4-8c140f55e641
[...]
不出所料,部署根据其资源定义创建了多个 Pod。对于 etcd Operator,CSV 将部署定义为需要三个 Pod:
$ kubectl get pods -n default
NAME READY STATUS RESTARTS AGE
etcd-operator-c4bc4fb66-zg22g 3/3 Running 0 6m4s
总结一下,创建订阅导致以下事件发生:
-
OLM 在与订阅相同的命名空间中创建一个 CSV 资源。此 CSV 包含了部署 Operator 本身的清单,以及其他信息。
-
OLM 使用部署清单为 Operator 创建部署资源。该资源的所有者是 CSV 本身。
-
部署导致为 Operator 本身创建副本集和 pod。
删除 Operator
与处理简单部署资源时一样,删除通过 OLM 部署的 Operator 并不那么简单。
部署资源充当 pod 的安装说明。如果通过用户干预或 pod 自身错误删除 pod,则 Kubernetes 检测到部署的期望状态与 pod 的实际数量之间的差异。
类似地,CSV 资源作为 Operator 的安装说明。通常,CSV 指示必须存在部署以执行此计划。如果该部署不存在,OLM 将采取必要步骤,使系统的实际状态与 CSV 的期望状态匹配。
因此,仅仅删除 Operator 的部署资源是不够的。相反,由 OLM 部署的 Operator 将通过删除 CSV 资源来删除:
$ kubectl delete csv/etcdoperator.v0.9.4
clusterserviceversion.operators.coreos.com "etcdoperator.v0.9.4" deleted
当最初部署时,OLM 负责删除 CSV 创建的资源,包括 Operator 的部署资源。
另外,您需要删除订阅以防止 OLM 在未来安装新的 CSV 版本:
$ kubectl delete subscription/etcd-subscription
subscription.operators.coreos.com "etcd-subscription" deleted
OLM Bundle 元数据文件
“OLM 包” 提供了可以安装的 Operator 的详细信息。该包含有关 Operator 所有可用版本的所有必要信息,以便:
-
通过提供用户可以订阅的一个或多个 通道,为 Operator 提供灵活的交付结构。
-
部署 Operator 所需的 CRD。
-
指导 OLM 如何创建 Operator 部署。
-
包括每个 CRD spec 字段的额外信息,包括如何在 UI 中呈现这些字段的提示。
OLM 包包括三种类型的文件:自定义资源定义、集群服务版本文件和软件包清单文件。
自定义资源定义
由于 Operator 需要其 CRD 来运行,OLM 包将其包括在内。OLM 与 Operator 一起安装 CRD。作为 OLM 包开发者,您无需对 CRD 文件进行任何更改或添加,只需支持 Operator 的已有内容即可。
请记住,只应包括 Operator 拥有的 CRD。任何其他 Operator 提供的依赖 CRD 将由 OLM 的依赖解析自动安装(有关所需 CRD 的概念在“Owned CRDs”中解决)。
注意
每个 CRD 必须在其自己的文件中定义。
集群服务版本文件
CSV 文件包含关于 Operator 的大部分元数据,包括:
-
如何部署 Operator
-
Operator 使用的 CRD 列表(包括它拥有的以及其他 Operator 的依赖项)
-
关于 Operator 的元数据,包括描述、标志、成熟级别和相关链接
考虑到此文件的重要角色,我们在下一节中详细介绍如何编写此文件。
包清单文件
包清单文件描述了一系列指向特定 Operator 版本的频道。由于频道和它们各自的交付节奏的分解由 Operator 拥有者确定。我们强烈建议频道设置关于稳定性、功能和变更速率的期望。
用户订阅频道。OLM 将使用包清单确定是否在订阅的频道中提供了 Operator 的新版本,并允许用户根据需要采取更新步骤。我们将在“编写包清单文件”中详细介绍此文件。
编写集群服务版本文件
每个 Operator 版本都将有自己的集群服务版本文件。CSV 文件是一种标准的 Kubernetes Manifest,类型为 ClusterServiceVersion,这是 OLM 提供的自定义资源之一。
此文件中的资源向 OLM 提供有关特定 Operator 版本的信息,包括安装说明以及用户如何与 Operator 的 CRD 进行交互的额外细节。
生成文件骨架
鉴于 CSV 文件中包含的数据量,最简单的起点是使用 Operator SDK 生成一个骨架。SDK 将以 Operator 本身的基本结构为基础构建此骨架,并尽可能填充其余详细信息。这为您提供了一个良好的基础,可以在此基础上完善其余细节。
由于每个 CSV 对应于特定的 Operator 版本,因此版本信息反映在文件名方案中。文件名模式是使用 Operator 名称并附加语义版本号。例如,Visitors Site Operator 的 CSV 文件将命名为类似 visitors-operator.v1.0.0.yaml 的东西。
为了使 Operator SDK 能够用特定 Operator 的信息填充骨架 CSV 文件,您必须从 Operator 项目源代码的根目录运行生成命令。这个命令的一般形式如下:
$ operator-sdk olm-catalog gen-csv --csv-version *x.y.z*
再次强调,由 Operator 开发团队决定他们自己的版本编号策略。为了一致性和一般用户友好性,我们建议 Operator 的发布遵循语义化版本原则。
运行 Visitors Site Operator 的 CSV 生成命令将产生以下输出:
$ `operator-sdk` `olm-catalog` `gen-csv` `--csv-version` `1.0.0`
INFO[0000] Generating CSV manifest version 1.0.0
INFO[0000] Fill in the following required fields in file
visitors-operator/1.0.0/visitors-operator.v1.0.0.clusterserviceversion.yaml:
spec.keywords
spec.maintainers
spec.provider
INFO[0000] Created
visitors-operator/1.0.0/visitors-operator.v1.0.0.clusterserviceversion.yaml
即使只有基本的 CSV 结构,生成的文件已经相当详细。在高层次上,它包括以下内容:
-
所有 Operator 拥有的 CRD 的引用(换句话说,那些在 Operator 项目中定义的)
-
Operator 部署资源的部分定义
-
Operator 需要的一组 RBAC 规则
-
描述 Operator 将监视的命名空间范围的指示器
-
可以根据需要修改的自定义资源示例(在
metadata.annotations.alm-examples中找到)
我们将深入探讨每个组件及其应进行的更改类型,在接下来的章节中进行详细讨论。
警告
SDK 将不知道用于 Operator 本身的图像名称。骨架文件包括在部署描述符中的image: REPLACE_IMAGE字段。您必须更新此值,以指向 OLM 将部署的 Operator 的托管图像(例如在 Docker Hub 或 Quay.io 上)。
元数据
如前所述,metadata.annotations.alm-examples字段包含 Operator 拥有的每个 CRD 的示例。SDK 将首先使用在 Operator 项目的deploy/crds目录中找到的自定义资源清单来填充此字段。务必查看并填写实际数据,以便最终用户可以根据自己的需求进一步定制。
除了alm-examples之外,您可以在清单的spec部分找到 Operator 的其余元数据。SDK 生成命令的输出将强调三个特定的必填字段:
关键字
描述 Operator 的类别列表;兼容 UI 用于发现
维护者
代码库维护 Operator 的姓名和电子邮件对列表
提供者
Operator 的发布实体名称
这段来自 etcd Operator 的片段展示了三个必填字段:
keywords: ['etcd', 'key value', 'database', 'coreos', 'open source']
maintainers:
- name: etcd Community
email: etcd-dev@googlegroups.com
provider:
name: CNCF
我们还鼓励您提供以下元数据字段,这些字段可以在 OperatorHub.io 等目录中生成更强大的列表:
显示名称
Operator 的用户友好名称
描述
描述 Operator 功能的字符串;您可以使用 YAML 结构的多行字符串来提供更多的显示信息
版本
Operator 的语义版本,每次发布新的 Operator 镜像时都应递增
替代项
本 CSV 更新的 Operator 版本(如果有)
图标
兼容 UI 使用的 Base64 编码图像
成熟度
包含在此版本中的 Operator 的成熟级别,例如alpha、beta或stable
链接
Operator 的相关链接列表,例如文档、快速入门指南或博客文章
minKubeVersion
Operator 必须部署的 Kubernetes 的最低版本,使用“Major.Minor.Patch”格式(例如,1.13.0)
拥有的 CRDs
要安装 Operator,OLM 必须了解其使用的所有 CRD。这些 CRD 分为两种形式:由 Operator 拥有的 CRD 和用作依赖项的 CRD(在 CSV 术语中,这些被称为“必需”CRD;我们将在下一节中介绍这些内容)。
SDK 骨架生成将spec.customresourcedefinitions部分添加到 CSV 文件中。它还使用由操作员定义的每个 CRD 填充owned部分,包括诸如kind、name和version的标识信息。但是,在 OLM 捆绑包有效之前,您必须手动添加更多字段。
您必须为每个拥有的 CRD 设置以下必需字段:
displayName
自定义资源的用户友好名称
描述
关于自定义资源表示的信息
resources
将由自定义资源创建的 Kubernetes 资源类型列表
resources 列表不需要详尽无遗。相反,它应该仅列出用户相关的可见资源。例如,您应该列出用户与之交互的事物,如服务和部署资源,但应省略用户不直接操作的内部 ConfigMap。
每个资源类型只需包含一个实例,无论操作员创建该类型的资源多少个。例如,如果自定义资源创建多个部署,则只需列出部署资源类型一次。
创建一个或多个部署和服务的自定义资源的示例列表如下:
resources:
- kind: Service
version: v1
- kind: Deployment
version: v1
您需要为每个拥有的资源添加两个字段:specDescriptors 和 statusDescriptors。这些字段提供有关将出现在自定义资源中的 spec 和 status 字段的附加元数据。兼容的 UI 可以使用此额外信息为用户呈现界面。
对于自定义资源规范中的每个字段,都要向 specDescriptors 字段添加一个条目。每个条目应包含以下内容:
displayName
字段的用户友好名称
描述
关于字段表示的信息
路径
对象中字段的点分路径
x-descriptors
关于字段能力的 UI 组件信息
表 8-1 列出了兼容 UI 常见支持的描述符。
表 8-1. 常用的规范描述符
| 类型 | 描述符字符串 |
|---|---|
| 布尔开关 | urn:alm:descriptor:com.tectonic.ui:booleanSwitch |
| 复选框 | urn:alm:descriptor:com.tectonic.ui:checkbox |
| 端点列表 | urn:alm:descriptor:com.tectonic.ui:endpointList |
| 镜像拉取策略 | urn:alm:descriptor:com.tectonic.ui:imagePullPolicy |
| 标签 | urn:alm:descriptor:com.tectonic.ui:label |
| 命名空间选择器 | urn:alm:descriptor:com.tectonic.ui:namespaceSelector |
| 节点亲和性 | urn:alm:descriptor:com.tectonic.ui:nodeAffinity |
| 数字 | urn:alm:descriptor:com.tectonic.ui:number |
| 密码 | urn:alm:descriptor:com.tectonic.ui:password |
| Pod 亲和性 | urn:alm:descriptor:com.tectonic.ui:podAffinity |
| Pod 反亲和性 | urn:alm:descriptor:com.tectonic.ui:podAntiAffinity |
| 资源需求 | urn:alm:descriptor:com.tectonic.ui:resourceRequirements |
| 选择器 | urn:alm:descriptor:com.tectonic.ui:selector: |
| 文本 | urn:alm:descriptor:com.tectonic.ui:text |
| 更新策略 | urn:alm:descriptor:com.tectonic.ui:updateStrategy |
statusDescriptors 字段的结构类似,包括需要指定的相同字段。唯一的区别在于有效描述符的集合;这些列在表 8-2 中列出。
表 8-2. 常用状态描述符
| 类型 | 描述符字符串 |
|---|---|
| 条件 | urn:alm:descriptor:io.kubernetes.conditions |
| k8s 阶段原因 | urn:alm:descriptor:io.kubernetes.phase:reason |
| k8s 阶段 | urn:alm:descriptor:io.kubernetes.phase |
| Pod 计数 | urn:alm:descriptor:com.tectonic.ui:podCount |
| Pod 状态 | urn:alm:descriptor:com.tectonic.ui:podStatuses |
| Prometheus 终端 | urn:alm:descriptor:prometheusEndpoint |
| 文本 | urn:alm:descriptor:text |
| W3 链接 | urn:alm:descriptor:org.w3:link |
例如,以下片段包含了 etcd Operator 的描述符子集:
specDescriptors:
- description: The desired number of member Pods for the etcd cluster.
displayName: Size
path: size
x-descriptors:
- 'urn:alm:descriptor:com.tectonic.ui:podCount'
- description: Limits describes the minimum/maximum amount of compute
resources required/allowed
displayName: Resource Requirements
path: pod.resources
x-descriptors:
- 'urn:alm:descriptor:com.tectonic.ui:resourceRequirements'
statusDescriptors:
- description: The status of each of the member Pods for the etcd cluster.
displayName: Member Status
path: members
x-descriptors:
- 'urn:alm:descriptor:com.tectonic.ui:podStatuses'
- description: The current size of the etcd cluster.
displayName: Cluster Size
path: size
- description: The current status of the etcd cluster.
displayName: Status
path: phase
x-descriptors:
- 'urn:alm:descriptor:io.kubernetes.phase'
- description: Explanation for the current status of the cluster.
displayName: Status Details
path: reason
x-descriptors:
- 'urn:alm:descriptor:io.kubernetes.phase:reason'
需要的 CRD
使用但不由 Operator 拥有的自定义资源被指定为所需。在安装 Operator 时,OLM 将找到提供所需 CRD 的适当 Operator 并安装它。这允许 Operator 在必要时维持有限的范围,同时利用组合和依赖解析。
CSV 的所需部分是可选的。只有需要使用其他非 Kubernetes 资源的 Operator 需要包括这些。
每个所需 CRD 都使用其:
名称
用于识别所需 CRD 的完整名称
版本
所需的 CRD 版本
种类
Kubernetes 资源类型;在兼容的 UI 中显示给用户。
显示名称
字段的用户友好名称;在兼容的 UI 中显示给用户。
描述
所需 CRD 的使用信息;在兼容的 UI 中显示给用户。
例如,以下指示 EtcdCluster 是不同 Operator 的所需 CRD:
required:
- name: etcdclusters.etcd.database.coreos.com
version: v1beta2
kind: EtcdCluster
displayName: etcd Cluster
description: Represents a cluster of etcd nodes.
每个所需 CRD 下 required 字段需要一个条目。
安装模式
CSV 的安装模式部分告诉 OLM 如何部署 Operator。有四个选项,所有选项都必须在 installModes 字段中以自己的标志存在,指示它们是否受支持。当生成 CSV 时,Operator SDK 包含每个选项的默认值集。
支持以下安装模式:
拥有命名空间
Operator 可以部署到选择自己命名空间的 OperatorGroup。
单个命名空间
Operator 可以部署到选择一个命名空间的 OperatorGroup。
多命名空间
Operator 可以部署到选择多个命名空间的 OperatorGroup。
所有命名空间
Operator 可以部署到选择所有命名空间的 OperatorGroup(定义为 targetNamespace: "")。
下面的片段显示了正确构建此字段的方式,以及 SDK 在生成期间设置的默认值:
installModes:
- type: OwnNamespace
supported: true
- type: SingleNamespace
supported: true
- type: MultiNamespace
supported: false
- type: AllNamespaces
supported: true
版本和更新
忠实于其名称,每个集群服务版本文件代表一个操作员的单个版本。操作员的后续版本将分别具有自己的 CSV 文件。在许多情况下,这可以是前一个版本的副本,并进行适当的更改。
下面描述了操作员版本之间需要进行的一般更改(这不是详尽列表;请务必检查文件的全部内容,以确保不需要进一步的更改):
-
将新的 CSV 文件名更改为反映操作员的新版本。
-
更新 CSV 文件的
metadata.name字段以及新版本。 -
更新
spec.version字段为新版本。 -
更新
spec.replaces字段以指示新版本升级的先前 CSV 版本。 -
在大多数情况下,新的 CSV 将引用操作员本身的新镜像。请确保根据需要更新
spec.containers.image字段以引用正确的镜像。 -
如果 CRD 发生变化,则可能需要更新 CSV 文件中 CRD 引用的
specDescriptor和statusDescriptor字段。
尽管这些更改将导致操作员的新版本,但用户在频道中存在该版本之前无法访问该版本。更新 *.package.yaml 文件以引用适当频道的新 CSV 文件(有关此文件的更多信息,请参见下一节)。
警告
一旦 OLM 发布并使用现有 CSV 文件,请勿修改现有 CSV 文件。请改在新版本的文件中进行更改,并通过频道将其传播给用户。
编写包清单文件
与编写集群服务版本文件相比,编写包清单要简单得多。包文件需要三个字段:
包名称
操作员本身的名称;这应该与 CSV 文件中使用的值匹配
频道
用于传送操作员版本的所有频道的列表
默认频道
用户应默认订阅的频道的名称
channels 字段中的每个条目由两个项目组成:
名称
频道的名称;这是用户将要订阅的内容
currentCSV
安装通过频道当前已安装的 CSV 文件的完整名称(包括操作员名称但不包括 .yaml 后缀)
由操作员团队决定将支持哪些频道的政策留给操作员。
下面的示例通过两个频道分发 Visitors Site 操作员:
packageName: visitors-operator
channels:
- name: stable
currentCSV: visitors-operator.v1.0.0
- name: testing
currentCSV: visitors-operator.v1.1.0
defaultChannel: stable
本地运行
一旦编写了必要的捆绑文件,下一步就是构建捆绑并针对本地集群(例如 Minikube 启动的集群)进行测试。在接下来的章节中,我们将描述安装 OLM 到集群、构建 OLM 捆绑和订阅频道以部署操作员的过程。
先决条件
本节涵盖了您需要对集群进行的更改,以运行 OLM,并配置它查看您的 bundles 存储库。对于一个集群,您只需要完成这些步骤一次;我们在“构建 OLM Bundle”中讨论了 Operator 的迭代开发和测试。
安装 Marketplace Operator
Marketplace Operator 从外部数据存储中导入 Operators。在本章中,您将使用 Quay.io 来托管您的 OLM 包。
注意
尽管其名称如此,Marketplace Operator 并未绑定到特定的 Operators 来源。它只是作为一个导管,从任何兼容的外部存储中获取 Operators。OperatorHub.io 是这样一个站点,我们在第十章中讨论它。
符合 CRDs 代表 Operator API 的概念,安装 Marketplace Operator 会引入两个 CRDs:
-
OperatorSource 资源描述了用于 OLM 包的外部托管注册表。在本例中,我们使用 Quay.io,一个免费的图像托管站点。
-
CatalogSourceConfig 资源连接了 OperatorSource 和 OLM 本身。OperatorSource 会自动创建 CatalogSourceConfig 资源,您无需显式与此类型交互。
警告
与 OLM 类似,Marketplace Operator 是一个不断发展的项目。因此,请务必查阅其 GitHub 存储库以获取当前发布的最新安装说明。
由于目前没有 Marketplace Operator 的正式发布,因此通过克隆上游存储库并使用其中的清单来安装它:
$ `git` `clone` `https://github.com/operator-framework/operator-marketplace.git`
$ `cd` `operator-marketplace`
$ `kubectl` `apply` `-f` `deploy/upstream/`
namespace/marketplace created
customresourcedefinition.apiextensions.k8s.io/catalogsourceconfigs.....
customresourcedefinition.apiextensions.k8s.io/operatorsources.operators....
serviceaccount/marketplace-operator created
clusterrole.rbac.authorization.k8s.io/marketplace-operator created
role.rbac.authorization.k8s.io/marketplace-operator created
clusterrolebinding.rbac.authorization.k8s.io/marketplace-operator created
rolebinding.rbac.authorization.k8s.io/marketplace-operator created
operatorsource.operators.coreos.com/upstream-community-operators created
deployment.apps/marketplace-operator created
您可以通过确保创建了marketplace命名空间来验证安装:
$ `kubectl` `get` `ns` `marketplace`
NAME STATUS AGE
marketplace Active 4m19s
安装 Operator Courier
Operator Courier 是一个客户端工具,用于构建并推送 OLM 包到存储库。它还用于验证包文件的内容。
您可以通过 Python 包安装程序pip安装 Operator Courier:
$ `pip3` `install` `operator-courier`
安装完成后,您可以从命令行运行 Operator Courier:
$ `operator-courier`
usage: operator-courier <command> [<args>]
These are the commands you can use:
verify Create a bundle and test it for correctness.
push Create a bundle, test it, and push it to an app registry.
nest Take a flat to-be-bundled directory and version nest it.
flatten Create a flat directory from versioned operator bundle yaml
files.
检索一个 Quay token
Quay.io 是一个免费的容器镜像托管站点。我们将使用 Quay.io 来托管 OLM 包以供 Operator Marketplace 使用。
新用户可以通过网站注册免费的 Quay.io 帐户。
为了让 Operator Courier 能够将 OLM 包推送到您的 Quay.io 帐户,您需要一个认证令牌。虽然令牌可通过 Web UI 访问,但您也可以使用以下脚本从命令行检索它,按指示替换您的用户名和密码:
USERNAME=<quay.io username>
PASSWORD=<quay.io password>
URL=https://quay.io/cnr/api/v1/users/login
TOKEN_JSON=$(curl -s -H "Content-Type: application/json" -XPOST $URL -d \
'{"user":{"username":"'"${USERNAME}"'","password": "'"${PASSWORD}"'"}}')
echo `echo $TOKEN_JSON | awk '{split($0,a,"\""); print a[4]}'`
本书的 GitHub 存储库提供了此脚本的交互版本,位于这里。
以后在将 bundle 推送到 Quay.io 时,您将使用此令牌,因此请将其保存在可以访问的地方。脚本的输出提供了一个命令将其保存为环境变量。
创建 OperatorSource
OperatorSource 资源定义了用于托管 Operator 包的外部数据存储。在本例中,您将定义一个 OperatorSource 来指向您的 Quay.io 账户,这将提供对其托管的 OLM 包的访问。
以下是一个示例 OperatorSource 清单;您应该将其中的两个<QUAY_USERNAME>替换为您的 Quay.io 用户名:
apiVersion: operators.coreos.com/v1
kind: OperatorSource
metadata:
name: <QUAY_USERNAME>-operators 
namespace: marketplace
spec:
type: appregistry
endpoint: https://quay.io/cnr
registryNamespace: <QUAY_USERNAME>
在这里使用您的用户名并不是硬性要求;这只是确保 OperatorSource 名称唯一的一种简单方式。
编写了 OperatorSource 清单后,使用以下命令创建资源(假设清单文件名为operator-source.yaml):
$ `kubectl` `apply` `-f` `operator-source.yaml`
要验证 OperatorSource 是否正确部署,请在marketplace命名空间中查找所有已知的 OperatorSources 列表:
$ `kubectl` `get` `opsrc` `-n` `marketplace`
NAME TYPE ENDPOINT REGISTRY STATUS
jdob-operators appregistry https://quay.io/cnr jdob Failed 
如果在创建源时终点没有包,则状态将为Failed。现在您可以忽略这一点;一旦上传了包,稍后您将刷新此列表。
注意
此处显示的输出已经截断以提高可读性;您的结果可能略有不同。
当首次创建 OperatorSource 时,如果在用户的 Quay.io 应用程序列表中找不到 OLM 包,则可能会失败。在稍后的步骤中,您将创建和部署这些包,之后 OperatorSource 将正确启动。我们将此步骤包含为先决条件,因为您只需执行一次;在相同的 Quay.io 命名空间中更新 OLM 包或创建新的 OLM 包时,您将重用 OperatorSource 资源。
此外,OperatorSource 的创建会导致 CatalogSource 的创建。对于这个资源不需要进一步的操作,但您可以通过检查marketplace命名空间来确认其存在:
$ `kubectl` `get` `catalogsource` `-n` `marketplace`
NAME NAME TYPE PUBLISHER AGE
jdob-operators grpc 6m5s
[...]
构建 OLM 包
安装了初始先决条件后,大部分时间将花在构建和测试循环上。本节涵盖了在 Quay.io 上构建和托管 OLM 包所需的步骤。
执行代码检查
使用 Operator Courier 的verify命令验证 OLM 包:
$ `operator-courier` `verify` `$OLM_FILES_DIRECTORY`
将包推送到 Quay.io
当元数据文件通过验证并准备好进行测试时,Operator Courier 将 OLM 包上传到您的 Quay.io 账户。在使用push命令时有一些必需的参数(和一些可选参数):
$ `operator-courier` `push`
usage: operator-courier [-h] [--validation-output VALIDATION_OUTPUT]
source_dir namespace repository release token
这里是 Visitors Site Operator 的推送示例:
OPERATOR_DIR=visitors-olm/
QUAY_NAMESPACE=jdob
PACKAGE_NAME=visitors-operator
PACKAGE_VERSION=1.0.0
QUAY_TOKEN=***** 
$ `operator-courier` `push` `"``$OPERATOR_DIR``"` `"``$QUAY_NAMESPACE``"` `\` `"``$PACKAGE_NAME``"` `"``$PACKAGE_VERSION``"` `"``$QUAY_TOKEN``"`
QUAY_TOKEN是完整的令牌,包括“basic”前缀。您可以使用本节早期介绍的脚本来设置此变量。
警告
默认情况下,以这种方式推送到 Quay.io 的包被标记为私有。转到https://quay.io/application/的图像,并将其标记为公共,以便集群可以访问。
现在 Operator 捆绑包已准备好测试。对于后续版本,请根据 CSV 文件的新版本更新PACKAGE_VERSION变量(有关更多信息,请参阅“版本控制和更新”)并推送新的捆绑包。
重新启动 OperatorSource
OperatorSource 在启动时读取配置的 Quay.io 帐户中的 Operator 列表。上传新 Operator 或 CSV 文件的新版本后,您需要重新启动 OperatorSource Pod 以应用更改。
Pod 的名称以 OperatorSource 的相同名称开头。使用前一节中的示例 OperatorSource,将“jdob”作为 Quay.io 用户名,以下演示如何重新启动 OperatorSource:
$ kubectl get pods -n marketplace
NAME READY STATUS RESTARTS AGE
jdob-operators-5969c68d68-vfff6 1/1 Running 0 34s
marketplace-operator-bb555bb7f-sxj7d 1/1 Running 0 102m
upstream-community-operators-588bf67cfc 1/1 Running 0 101m
$ kubectl delete pod jdob-operators-5969c68d68-vfff6 -n marketplace
pod "jdob-operators-5969c68d68-vfff6" deleted
$ kubectl get pods -n marketplace
NAME READY STATUS RESTARTS AGE
jdob-operators-5969c68d68-6w8tm 1/1 Running 0 12s 
marketplace-operator-bb555bb7f-sxj7d 1/1 Running 0 102m
upstream-community-operators-588bf67cfc 1/1 Running 0 102m
新启动的 Pod 名称后缀与原始 Pod 不同,证实已创建了新的 Pod。
在任何时候,您都可以查询 OperatorSource 以查看其已知 Operator 的列表:
$ `OP_SRC_NAME``=``jdob-operators`
$ `kubectl` `get` `opsrc` `$OP_SRC_NAME` `\` `-o``=``custom-columns``=``NAME:.metadata.name,PACKAGES:.status.packages` `\` `-n` `marketplace`
NAME PACKAGES
jdob-operators visitors-operator
通过 OLM 安装 Operator
在配置 Marketplace Operator 以检索您的捆绑包后,通过创建订阅到其支持的频道之一来测试它。OLM 会响应订阅并安装相应的 Operator。
创建 OperatorGroup
您将需要一个 OperatorGroup 来指定 Operator 应该监视哪些命名空间。它必须存在于您希望部署 Operator 的命名空间中。为了在测试时简化,此处定义的示例 OperatorGroup 将 Operator 部署到现有的marketplace命名空间中:
apiVersion: operators.coreos.com/v1alpha2
kind: OperatorGroup
metadata:
name: book-operatorgroup
namespace: marketplace
spec:
targetNamespaces:
- marketplace
与其他 Kubernetes 资源一样,使用kubectl的apply命令创建 OperatorGroup:
$ `kubectl` `apply` `-f` `operator-group.yaml`
operatorgroup.operators.coreos.com/book-operatorgroup created
创建订阅
通过选择 Operator 和其一个频道,订阅将前面的步骤连接在一起。OLM 使用此信息来启动相应的 Operator Pod。
以下示例为访客站点操作员创建了一个新的稳定频道订阅:
apiVersion: operators.coreos.com/v1alpha1
kind: Subscription
metadata:
name: book-sub
namespace: marketplace 
spec:
channel: stable 
name: visitors-operator
source: jdob-operators 
sourceNamespace: marketplace 
指示将在其中创建订阅的命名空间。
选择包清单中定义的一个频道。
标识要查看其相应 Operator 和频道的 OperatorSource。
指定 OperatorSource 的命名空间。
使用apply命令创建订阅:
$ `kubectl` `apply` `-f` `subscription.yaml`
subscription.operators.coreos.com/book-sub created
OLM 将收到新订阅的通知,并在marketplace命名空间中启动 Operator Pod:
$ kubectl get pods -n marketplace
NAME READY STATUS RESTARTS AGE
jdob-operators-5969c68d68-6w8tm 1/1 Running 0 143m
visitors-operator-86cb966f59-l5bkg 1/1 Running 0 12s
注意
我们已为了易读性截断此处的输出;您的结果可能略有不同。
测试正在运行的 Operator
一旦 OLM 启动了 Operator,您可以通过创建相同类型的自定义资源来测试它。有关测试正在运行的 Operator 的更多信息,请参阅第六章和第七章。
访客站点操作员示例
您可以在书籍的 GitHub 存储库中找到访客站点运营商的 OLM 捆绑文件。
有两个值得注意的目录:
bundle
此目录包含实际的 OLM 捆绑文件,包括 CSV、CRD 和软件包文件。您可以使用本章概述的过程来构建和部署访客站点运营商。
testing
此目录包含了从 OLM 部署运营商所需的额外资源。这些资源包括 OperatorSource、OperatorGroup、订阅和一个用于测试运营商的示例自定义资源。
欢迎读者通过 GitHub 的问题选项卡提交有关这些文件的反馈、问题和疑问。
摘要
如同任何软件,管理安装和升级对于运营商至关重要。运营商生命周期管理器(Operator Lifecycle Manager,OLM)扮演了这一角色,为您提供了一个机制来发现运营商、处理更新并确保稳定性。
资源
第九章:操作员哲学
我们已经注意到操作员试图解决的问题,并且您已经详细了解了如何使用 SDK 构建操作员的示例。您还看到了如何使用 OLM 以一致的方式分发操作员。让我们试图将这些策略与支撑它们的战略思想联系起来,以理解一个存在的问题:操作员的目的是什么?
操作员概念源于站点可靠性工程(SRE)。在第一章中,我们谈到操作员作为软件 SRE。让我们回顾一些关键的 SRE 原则,以了解操作员如何应用它们。
每个应用程序的 SRE
SRE 始于 Google,以应对运行大型系统以及用户和功能数量不断增加的挑战。关键的 SRE 目标之一是使服务能够在不迫使运行它们的团队按比例增长的情况下增长。为了在没有庞大团队的情况下以戏剧性规模运行系统,SRE 编写代码来处理部署、运营和维护任务。SRE 创建的软件运行其他软件,保持其运行并随时间管理它。SRE 是一组广泛的管理和工程技术,其自动化是一个核心原则。您可能已经听说过它的目标被称为不同的名称,如“自治”或“自动驾驶”软件。在我们引入的操作员成熟度模型中,我们将其称为“自动驾驶”。
操作员和操作员框架使在 Kubernetes 上运行的应用程序实现这种自动化变得更加容易。Kubernetes 编排服务部署,使得对无状态应用程序进行一些水平扩展或故障恢复工作自动化。它将分布式系统资源表示为 API 抽象。使用操作员,开发人员可以将这些实践扩展到复杂的应用程序。
众所周知的“SRE 书籍” Site Reliability Engineering(O’Reilly),由贝茜·拜尔等人(编辑)编写,是 SRE 原则的权威指南。Google 工程师卡拉·盖瑟在其中的评论代表了 SRE 的自动化元素:“如果在正常操作期间需要人工操作员接触您的系统,则存在错误。”^(1) SRE 通过编写代码来修复这些错误。操作员是在 Kubernetes 上为广泛的应用程序类别编程这些修复的逻辑位置。通过自动化其应用程序运行的常规杂务,操作员减少了人为干预的错误。
不要搞琐事
SRE 试图通过创建软件来执行操作系统所需的任务来减少琐事。在这个背景下,“琐事”被定义为“可自动化,战术性的,缺乏持久价值的工作,并且随着服务增长而线性扩展。”^(2)
可自动化:让您的计算机喜欢的工作
如果一个任务可以由机器完成,那么它是可以自动化的。如果任务需要人类判断的洞察力,那么机器无法完成。例如,费用报告会经过各种机器驱动的边界检查,但通常还需要最终的人工审核——检查自动流程标记为超出界限的条目,即使不是每一张收据都要审核。在界限内的报告的批准可能是可以自动化的;超出界限情况的最终接受或拒绝可能不能。如果软件能够自动化可以自动化的工作,并且这些工作是重复的,那么应该通过软件来自动化。建立软件来执行重复任务的成本可以在重复的生命周期中分摊。
跑步停留:没有持久价值的工作
将一些工作视为没有价值可能会让人感到不舒服,但在 SRE 的术语中,如果某项工作不会改变服务,则称其为“缺乏持久价值”。例如,备份数据库服务器就是一个例子。备份数据库不会使其运行更快、服务更多请求或在本质上更可靠。它也不会停止工作。尽管没有持久价值,备份显然是值得做的。这种工作通常是运维人员的一个很好的工作机会。
成长的烦恼:随系统扩展的工作
您可能会设计一个服务,使其在水平平面上扩展,以服务更多请求或运行更多服务实例。但是,如果添加新实例需要工程师配置计算机并将其连接到网络,那么扩展服务就远非自动化。在这种类型的繁重工作的最坏情况下,运维工作可能与您的服务呈线性增长。每增长 10%的服务——增加 10%的用户、每秒请求增加 10%,或使用 10%更多 CPU 的新功能——意味着增加 10%的守护劳动。
手动扩展:就像过去的糟糕日子一样
想象从 第一章 运行无状态 Web 服务器。您在三个虚拟机上部署了三个实例。要增加更多的 Web 服务器容量,您需要启动新的虚拟机,为它们分配(唯一的)IP 地址,并分配(每个 IP)端口,以便 Web 服务器二进制文件侦听。接下来,您需要通知负载均衡器新的端点,以便它可以将一些请求路由到那里。
如设计和配置的那样,您的简单无状态 Web 服务器确实可以根据需求增长。它可以通过将逐渐增加的负载分散到多个实例来为更多用户提供服务并添加更多功能。但是管理服务的团队将始终随之增长。随着系统变得更大,这种效果会变得更糟,因为增加一个虚拟机不会显著增加千个实例的容量。
自动化水平扩展:Kubernetes 副本
如果您将无状态的 Web 服务器部署在 Kubernetes 上,您可以通过几乎只需一个 kubectl 命令来进行水平扩展和缩减。这是 Kubernetes 作为 SRE 平台级自动化原则实施的一个例子。Kubernetes 抽象出了 Web 服务器运行的基础设施以及它们提供连接的 IP 地址和端口。在扩展时,您无需为每个新的 Web 服务器实例进行配置,或者在缩减时故意从您的 IP 范围中释放 IP。您无需编程负载均衡器以将流量传送到新实例。软件会自动完成这些繁琐的工作。
操作员:Kubernetes 应用可靠性工程
操作员扩展了 Kubernetes 的能力,将自动化原则应用于在平台上运行的复杂、有状态的应用程序。考虑一个管理具有其自身集群概念的应用程序的操作员。例如,当 etcd 操作员替换失败的 etcd 集群成员时,它通过配置新的 Pod 和现有集群的端点和认证来安排新的成员。
如果您负责管理内部服务的团队,操作员将使您能够将专家程序捕捉到软件中,并扩展系统的“正常运行”:即它可以自动处理的条件集。如果您正在开发一个 Kubernetes 本地应用程序,操作员可以让您考虑用户为运行您的应用程序而劳碌,并为他们省去麻烦。您可以构建不仅运行和升级应用程序的操作员,还可以响应错误或性能下降。
Kubernetes 中的控制循环会监视资源,并在它们不符合某些期望状态时做出反应。操作员允许您为代表您的应用程序的资源定制控制循环。通常首要的操作员关注点是操作数的自动部署和自助配置。在成熟模型的第一个级别之上,操作员应了解其应用程序的关键状态以及如何修复它。然后,操作员可以扩展以观察关键应用程序指标,并在需要时进行调整、修复或报告。
管理应用程序状态
应用程序通常具有需要在副本之间进行同步或维护的内部状态。一旦操作员处理安装和部署,它可以通过在动态群组的一组 Pod 中保持这种共享状态来沿着成熟模型走得更远。任何具有其自身集群概念的应用程序,如许多数据库和文件服务器,都具有这种共享应用程序状态。它可能包括认证资源、复制安排或编写者/读者关系。操作员可以为新的副本配置这种共享状态,允许其扩展或恢复应用程序的集群,并在新成员加入时修复它。操作员可能会处理其应用程序所需的外部资源。例如,考虑在副本死亡并被新副本替换时操作外部负载均衡器的路由规则。
发送到软件的黄金信号
Beyer 等人建议监控系统健康的“四个黄金信号”^(3),这些是服务基本运行特征的良好起点。这些黄金信号在 SRE 书籍中得到了推广,传达了系统状态的重要信息,足以触发对工程师的呼叫^(4)。在设计 Operator 时,你应该将可能导致呼叫工程师的任何事情视为需要修复的错误。
Site Reliability Engineering 将四个黄金信号列为 延迟、流量、错误 和 饱和度。^(5) 对这四个领域的准确测量,根据最能代表特定应用程序状况的度量标准,确保了对应用健康的合理理解。当这些信号显示出已知条件、问题或错误时,Operator 可以采取特定于应用程序的措施。
延迟
延迟是完成某事所需的时间。通常被理解为请求和完成之间的经过时间。例如,在网络中,延迟被定义为在两点间发送数据包所需的时间。Operator 可以测量应用程序特定的内部延迟,如游戏客户端动作与游戏引擎响应之间的时间差。
流量
流量指服务请求的频率。每秒 HTTP 请求是衡量 Web 服务流量的标准。监控制度通常会将此测量分为静态资源和动态生成资源。对于数据库或文件服务器,监控每秒事务更有意义。
错误
错误是失败的请求,如 HTTP 500 系列错误。在 Web 服务中,你可能会得到 HTTP 成功代码,但在成功交付页面时看到脚本异常或其他客户端错误。超过某些延迟保证或性能策略,如在规定的时间内响应任何请求,也可能属于错误。
饱和度
饱和度是服务对有限资源的消耗的一种衡量。这些测量侧重于系统中最受限制的资源,通常是 CPU、内存和 I/O。监控饱和度的两个关键思想。首先,性能在资源完全利用之前就会变差。例如,当文件系统超过约 90%的容量时,某些文件系统的性能会变差,因为随着可用空间的减少,创建文件所需的时间也会增加。由于几乎任何系统中类似的效果,饱和度监视器通常应响应低于 100%的高水位标记。其次,测量饱和度可以帮助您在问题发生之前预测一些问题。通过将文件服务的剩余空间除以应用程序写入数据的速率,您的操作符可以估算剩余存储空间满时的时间。
操作符可以通过测量和响应需要越来越复杂的操作任务的黄金信号来迭代地朝着自动驾驶运行服务的方向发展。每当你的应用程序需要人工帮助时应用这种分析,你就有了一个操作符迭代开发的基本计划。
高效操作符的七个习惯
操作符起源于 2015 年至 2016 年期间在 CoreOS 的工作。在那里构建的操作符的用户体验以及在 Red Hat 和更广泛的社区中继续发展的操作符已帮助完善了作为 Kubernetes 操作符概念的七项指南:^(6)
-
操作符应作为单个 Kubernetes 部署运行。
在第二章中,你通过一个清单安装了 etcd 操作符,而没有使用第八章介绍的 OLM 机制。虽然你提供了 CSV 和其他资产来为操作符创建 OLM 捆绑包,但 OLM 仍然使用单个清单来代表你部署操作符。
举例说明,尽管通常需要配置 RBAC 和服务账户,但你可以用一个命令将 etcd 操作符添加到 Kubernetes 集群中。这只是一个部署:
$ `kubectl` `create` `-f` `https://raw.githubusercontent.com/``\ ` `kubernetes-operators-book/chapters/master/ch03/` `etcd-operator-deployment.yaml` -
操作符应在集群上定义新的自定义资源类型。
回想起第二章中的 etcd 示例。你创建了一个 CRD,在其中定义了一种新类型的资源,即 EtcdCluster。该类型代表由操作符管理的运行中的 etcd 集群的实例。用户通过创建新的应用程序实例的自定义资源来创建新的应用程序实例。
-
操作符在可能的情况下应使用适当的 Kubernetes 抽象。
当 API 调用可以以更一致和广泛测试的方式完成相同的操作时,不要编写新代码。一些非常有用的操作符只是以适合其应用程序的方式操作一些标准资源集。
-
操作符的终止不应影响操作数。
当一个操作符停止或从集群中删除时,它管理的应用程序应继续运行。返回你的集群,并删除 etcd 或访问者站点操作符。虽然你不会自动从故障中恢复,但在操作符不存在的情况下,你仍然可以使用操作对象应用程序的功能。即使相应的操作符没有运行,你仍然可以访问访问者站点或从 etcd 检索键值对。
注意,删除一个 CRD 确实会影响操作对象应用程序。实际上,删除 CRD 将会删除其 CR 实例。
-
操作符应理解由任何先前版本创建的资源类型。
操作符应与其前身的结构向后兼容。这要求设计时要非常小心和简洁,因为你在版本 1 中定义的资源将长期存在。
-
操作符应协调应用程序的升级。
操作符应协调其操作对象的升级,可能包括在应用程序集群中进行滚动升级,几乎肯定包括在出现问题时回滚到先前版本的能力。保持软件最新是必要的努力,因为只有最新的软件才具有对错误和安全漏洞的最新修复。自动化这种升级工作是操作符的理想任务。
-
操作符应进行彻底测试,包括混沌测试。
你的应用程序及其与基础设施的关系构成一个复杂的分布式系统。你将依赖你的操作符来管理这个系统。混沌测试是有意引发系统组件故障,以发现意外错误或降级的方法。建立一个测试套件,让你的操作符经历模拟错误和 pod、节点以及网络突然消失的情况,从而查看故障在哪里出现,或在它们的依赖关系崩溃时如何在组件之间进行级联。
总结
操作符往往会通过自动安装、无缝应用升级等不同成熟阶段发展,最终进入“自动驾驶”的稳定状态,在这种状态下,它们会响应和纠正性能和稳定性的突发问题。每个阶段的目标都是减少人工操作。
制作一个操作符来分发、部署和管理你的应用程序,使其更容易在 Kubernetes 上运行,并允许应用程序利用 Kubernetes 的功能。一个遵循此处概述的七个习惯的操作符可以轻松部署,并可以通过 OLM 管理其整个生命周期。这种操作符使其操作对象更易于运行、管理,可能还可以实现。通过监控其应用程序的关键信号,操作符可以做出明智的决策,解放工程师免受机械性操作任务的困扰。
^(1) Beyer 等人(编辑),网站可靠性工程,119 页。
^(2) 贝尔等人(编),《现场可靠性工程》,120 页。
^(3) 贝尔等人(编),《现场可靠性工程》,139 页。
^(4) 贝尔等人(编),《现场可靠性工程》,140 页。
^(5) 贝尔等人(编),《现场可靠性工程》,139 页。
^(6) 布兰登·菲利普斯,《引入操作员》,CoreOS 博客,2016 年 11 月 3 日,https://oreil.ly/PtGuh。
第十章:参与
Operator 框架中的所有组件,包括 Operator SDK、Operator Lifecycle Manager 和 Operator Metering,都仍处于生命周期的早期阶段。有多种方式可以参与它们的开发,从简单的提交 bug 报告到成为积极的开发者都可以。
与 Operator Framework 的用户和开发者互动的最简单方式之一是通过其专业兴趣小组,简称 SIG。该 SIG 使用邮件列表讨论包括即将发布的信息、最佳实践和用户问题在内的各种主题。您可以通过他们的网站免费加入该 SIG。
要进行更直接的互动,Kubernetes Slack 团队是一个活跃的用户和开发者社区。特别是“kubernetes-operators”频道涵盖了与本书相关的主题。
Operator Framework 的 GitHub 组织包含每个组件的项目仓库。还有多种补充仓库,例如 Operator SDK 示例仓库,进一步帮助 Operator 的开发和使用。
功能请求和报告 Bug
参与 Operator Framework 最简单但也非常有价值的方式之一是提交 bug 报告。框架项目团队使用 GitHub 内置的问题跟踪来对未解决的问题进行分类和修复。您可以在 GitHub 项目页面的 Issues 标签下找到每个特定项目的跟踪器。例如,Operator SDK 的问题跟踪器可以在 Operator Framework GitHub 仓库 找到。
此外,项目团队使用问题跟踪器来跟踪功能请求。新问题按钮提示提交者在 bug 报告和功能请求之间进行选择,并自动适当地打上标签。提交功能请求提供了广泛的用例,并根据社区需求推动项目方向。
在提交新问题时,有一些一般原则^(1)需要记住:
-
要具体. 对于 bug,请尽可能提供关于运行环境的详细信息,包括项目版本和集群详情。如果可能,请提供详细的复现步骤。对于功能请求,请从包括所请求功能解决的用例开始。这有助于功能优先级的确定,并帮助团队决定是否有更好或现有的方式来满足请求。
-
保持范围限制在一个单一的 bug 上。 多个报告比单一、多方面的问题报告更容易进行分类和跟踪。
-
请尽量选择适用的项目。 例如,如果问题特别适用于与 OLM 一起工作,请在该存储库中创建问题。对于一些错误,不总是能确定问题的来源。在这些情况下,您可以选择最适用的项目存储库,让团队适当地对其进行分类。
-
如果找到现有问题,请使用该问题。 使用 GitHub 的问题跟踪器搜索功能,看看是否存在类似的错误或功能请求,然后再创建新报告。此外,请检查已关闭问题的列表,如果可能,请重新打开现有的错误。
贡献
当然,如果您习惯于处理代码,欢迎贡献源代码。您可以在开发者指南中找到当前的设置开发环境的指南。在提交任何拉取请求之前,请务必查阅最新的贡献指南。
作为参考,三个主要 Operator Framework 组件的存储库如下:
如果您不擅长编码,仍然可以通过更新和完善项目文档来进行贡献。“kind/documentation”标签用于识别突出的错误和增强请求。
共享 Operators
OperatorHub.io是一个社区编写的运营商托管站点。该站点包含来自各种类别的运营商,包括:
-
数据库
-
机器学习
-
监控
-
网络
-
存储
-
安全性
社区为在此站点上展示的运营商提供自动化测试和手动审核。它们已打包为必要的元数据文件,可以通过 OLM 安装和管理(详见第八章了解更多信息)。
您可以通过向Community Operators repository提交拉取请求来将运营商提交至 OperatorHub.io。请查看此 OperatorHub.io 页面,其中包含最新的提交说明,包括打包指南。
此外,OperatorHub.io 提供了一种预览您的 CSV 文件在被接受并托管在网站上后的外观方式。这是确保您输入了正确的元数据字段的好方法。您可以在Operator 预览页面了解更多信息。
Awesome Operators 仓库 维护了一个更新的运算符列表,这些运算符并未托管在 OperatorHub.io 上。虽然这些运算符未经过与托管在 OperatorHub.io 上相同的审核,但它们都是开源的,并列有其对应的 GitHub 仓库。
摘要
作为一个开源项目,Operator Framework 依赖社区的参与而茁壮成长。每一点贡献都很重要,从参与邮件列表的讨论到贡献代码修复错误和新增功能。同时,贡献到 OperatorHub.io 还有助于推广您的运算符,并扩展可用功能的生态系统。
附录 A. 在集群内作为部署运行操作员
在集群外部运行操作员,对于测试和调试目的非常方便,但是生产环境中的操作员以 Kubernetes 部署方式运行。对于此部署方式,涉及一些额外的步骤:
-
构建图像。操作员 SDK 的
build命令链接到底层的 Docker 守护进程以构建操作员图像,并在运行时提供完整的图像名称和版本:$ `operator-sdk` `build` `jdob/visitors-operator:0.1` -
配置部署。更新 SDK 生成的 deploy/operator.yaml 文件,其中包含图像的名称。要更新的字段名为
image,可以在以下位置找到:spec -> template -> spec -> containers生成的文件默认将值设置为
REPLACE_IMAGE,您应该更新以反映在上一条命令中构建的图像名称。构建完成后,请将图像推送到诸如 Quay.io 或 Docker Hub 这样的外部可访问的仓库。
-
部署 CRD。SDK 生成一个基本的 CRD 框架,可以正常运行,但请参阅 附录 B 获取有关完善此文件的更多信息:
$ `kubectl` `apply` `-f` `deploy/crds/*_crd.yaml` -
部署服务账户和角色。SDK 生成操作员所需的服务账户和角色。更新这些内容以限制角色的权限,使其仅限于操作员所需的最小权限。
一旦适当地确定了角色权限的范围,将资源部署到集群中:
$ `kubectl` `apply` `-f` `deploy/service_account.yaml` $ `kubectl` `apply` `-f` `deploy/role.yaml` $ `kubectl` `apply` `-f` `deploy/role_binding.yaml`警告
您必须按照列出的顺序部署这些文件,因为角色绑定需要角色和服务账户同时存在。
-
创建操作员部署。最后一步是部署操作员本身。您可以使用先前编辑的 operator.yaml 文件将操作员图像部署到集群中:
$ `kubectl` `apply` `-f` `deploy/operator.yaml`
附录 B. 自定义资源验证
在添加新的 API 时,Operator SDK 会生成一个骨架自定义资源定义。这个骨架是可用的;创建自定义资源无需进行进一步的更改或添加。
通过简单地定义 spec 和 status 部分,分别表示用户输入和自定义资源状态的开放对象,骨架 CRD 实现了这种灵活性:
spec:
type: object
status:
type: object
这种方法的缺点是 Kubernetes 无法验证这两个字段中的任何数据。由于 Kubernetes 不知道应该允许什么值或不允许什么值,只要清单解析,这些值就是允许的。
为解决这个问题,CRD 包括对 OpenAPI 规范 的支持,以描述每个字段的验证约束。您需要手动将此验证添加到 CRD 中,以描述 spec 和 status 部分的允许值。
您将对 CRD 的 spec 部分进行两个主要更改:
-
添加一个
properties映射。对于可以为此类型的自定义资源指定的每个属性,将一个条目添加到此映射中,并提供有关参数类型和允许值的信息。 -
可选地,您可以添加一个
required字段,列出 Kubernetes 应强制执行其存在的属性。将每个必需属性的名称作为此列表中的条目添加。如果在资源创建过程中省略了这些属性中的任何一个,Kubernetes 将拒绝该资源。
您还可以按照与 spec 相同的约定填充 status 部分的属性信息;但是,无需添加 required 字段。
警告
在这两种情况下,现有的行 type: object 保持不变;您将新添加的内容插入到与此“type”声明相同级别的位置。
您可以在 CRD 的以下部分找到 spec 和 status 字段:
spec -> validation -> openAPIV3Schema -> properties
例如,对 VisitorsApp CRD 的添加如下:
spec:
type: object
properties:
size:
type: integer
title:
type: string
required:
- size
status:
type: object
properties:
backendImage:
type: string
frontendImage:
type: string
这个片段只是使用 OpenAPI 验证可以实现的示例。您可以在 Kubernetes 文档 中找到有关创建自定义资源定义的详细信息。
附录 C. 基于角色的访问控制(RBAC)
当操作员 SDK 生成操作员项目(无论是基于 Helm、Ansible 还是 Go 的操作员)时,它会创建多个清单文件来部署操作员。其中许多文件授予部署操作员在其生命周期中执行各种任务所需的权限。
操作员 SDK 生成了三个与操作员权限相关的文件:
deploy/service_account.yaml
Kubernetes 不提供作为用户进行身份验证的方法,而是提供了一种程序化的身份验证方法,即服务帐户。服务帐户在操作员 pod 对 Kubernetes API 发出请求时充当身份。此文件仅定义服务帐户本身,您无需手动编辑它。有关服务帐户的更多信息,请参阅Kubernetes 文档。
deploy/role.yaml
此文件为服务帐户创建和配置了一个角色。角色在与集群 API 交互时决定了服务帐户拥有的权限。出于安全原因,操作员 SDK 生成此文件时授予了极其广泛的权限,因此您在将操作员部署到生产环境之前需要进行编辑。在下一节中,我们将详细解释如何在此文件中精细调整默认权限。
deploy/role_binding.yaml
此文件创建了一个角色绑定,将服务帐户映射到角色。您无需对生成的文件进行任何更改。
精细调整角色
在其最基本的层面上,角色将资源类型映射到用户或服务帐户可以对这些类型资源执行的操作(在角色资源术语中称为“动词”)。例如,以下角色为部署授予了查看权限(但不包括创建或删除权限):
- apiGroups: ["apps"]
resources: ["deployments"]
verbs: ["get", "list", "watch"]
由于操作员 SDK 不知道操作员需要与集群交互的程度,因此默认角色允许在多种 Kubernetes 资源类型上执行所有操作。以下片段摘自由 SDK 生成的操作员项目,通配符*允许对给定资源执行所有操作:
...
- apiGroups:
- ""
resources:
- pods
- services
- endpoints
- persistentvolumeclaims
- events
- configmaps
- secrets
verbs:
- '*'
- apiGroups:
- apps
resources:
- deployments
- daemonsets
- replicasets
- statefulsets
verbs:
- '*'
...
毫不奇怪,将如此开放和广泛的权限授予服务帐户被视为一种不良实践。您应根据操作员的范围和行为对所做的特定更改进行限制。一般来说,您应尽可能限制访问权限,同时确保操作员可以正常运行。
例如,以下角色片段提供了访客站点操作员所需的最小功能:
...
- apiGroups:
- ""
resources:
- pods
- services
- secrets
verbs:
- create
- list
- get
- apiGroups:
- apps
resources:
- deployments
verbs:
- create
- get
- update
...
关于配置 Kubernetes 角色的详细信息超出了本书的范围。您可以在Kubernetes RBAC 文档中找到更多信息。


浙公网安备 33010602011771号