Kubernetes-设计模式与最佳实践-全-
Kubernetes 设计模式与最佳实践(全)
原文:
annas-archive.org/md5/c649c950786aac7ce9e6bdeaa16980df
译者:飞龙
前言
在深入了解 Kubernetes 工作原理之前,本书会首先向你介绍容器编排的世界,并描述应用开发中的最新变化。它帮助你理解 Kubernetes 解决的问题,并展示如何使用 Kubernetes 资源来部署应用。你还将学习如何应用 Kubernetes 集群的安全模型。本书还描述了运行在 Kubernetes 中的服务如何利用平台的安全特性。你将学习如何排查 Kubernetes 集群问题并调试 Kubernetes 应用。你还将学习分析 Kubernetes 中的网络模型及其替代方案,并将设计模式作为最佳实践应用于 Kubernetes。等你读完本书后,你将全面掌握使用 Kubernetes 管理容器的能力。
完成本书后,你将能够:
-
理解并根据云原生范式对软件设计模式进行分类
-
将设计模式作为最佳实践应用于 Kubernetes
-
使用客户端库通过编程方式访问 Kubernetes API
-
使用自定义资源和控制器扩展 Kubernetes
-
集成访问控制机制并与 Kubernetes 中的资源生命周期进行交互
-
在 Kubernetes 中开发和运行自定义调度器
本书适合的人群
如果你对配置和排查 Kubernetes 集群问题、以及在 Kubernetes 集群上开发基于微服务的应用感兴趣,那么这本书将对你非常有用。具备基础 Docker 知识的 DevOps 工程师将发现这本书很有价值。
本书内容简介
第一章,Kubernetes 设计模式,将帮助你理解 Kubernetes 模式,并通过 Kubernetes 本身及外部应用的示例进行讲解。
第二章,Kubernetes 客户端库,将帮助你了解如何通过原始 HTTP 查询到复杂的库,涵盖集群内外的访问 Kubernetes API 示例。
第三章,Kubernetes 扩展,将介绍 Kubernetes 的扩展功能,包括自定义资源定义、自定义控制器、动态准入控制器和自定义调度器。
如何最大化地利用本书
我们假设你已经熟悉命令行工具和计算机编程概念及语言。最低硬件要求为:Intel Core i7 或同等性能处理器,8GB RAM,35GB 硬盘,稳定的互联网连接。你还需要提前安装以下软件:
-
访问版本为 1.10 或更高的 Kubernetes 集群
本地 Kubernetes 解决方案,如 minikube 或托管在云提供商中的集群:
github.com/kubernetes/minikube
-
Kubernetes 命令行工具 kubectl 是通过终端访问 Kubernetes 的必要工具:
-
构建和测试客户端库需要 Docker 客户端和服务器的最低版本为 18.03:
-
安装 Python 和 Go 并非必需,但建议在本地玩转客户端库时安装:
下载示例代码文件
您可以从 www.packt.com 的帐户下载本书的示例代码文件。如果您是在其他地方购买本书,您可以访问 www.packt.com/support 并注册,将文件直接通过电子邮件发送给您。
您可以按照以下步骤下载代码文件:
-
在 www.packt.com 登录或注册。
-
选择 SUPPORT 标签页。
-
点击“代码下载与勘误”。
-
在搜索框中输入书名,并按照屏幕上的指示操作。
文件下载完成后,请确保使用最新版本的以下工具解压或提取文件夹:
-
适用于 Windows 的 WinRAR/7-Zip
-
适用于 Mac 的 Zipeg/iZip/UnRarX
-
适用于 Linux 的 7-Zip/PeaZip
本书的代码包也托管在 GitHub 上,地址是 github.com/TrainingByPackt/Kubernetes-Design-Patterns-and-Extensions
。如果代码有更新,将会在现有的 GitHub 仓库中更新。
我们的丰富书籍和视频目录中也有其他代码包,您可以在 github.com/PacktPublishing/
上查看!快去看看吧!
使用的约定
本书中使用了若干文本约定。
CodeInText
:表示文本中的代码字、数据库表名、文件夹名称、文件名、文件扩展名、路径名、虚拟网址、用户输入以及 Twitter 用户名。以下是一个示例:“使用这种方法,kubectl
会使用其自身凭证安全地连接到 API 服务器,并为本地系统上的应用程序创建代理。”
代码块如下所示:
{
"apiVersion":"v1",
"kind":"Namespace",
"metadata":{
"name":"packt-client”
}
}
当我们希望您特别关注代码块的某部分时,相关行或项目会以粗体显示:
curl -X POST http://localhost:8080/api/v1/namespaces/ \
--header "Content-Type:application/json" \
活动:这些基于场景的活动将让您在完整章节学习过程中实际应用所学内容。它们通常是基于真实问题或情境。
警告或重要说明以此方式显示。
联系我们
我们总是欢迎读者的反馈。
一般反馈:请发送邮件至 feedback@packtpub.com
,并在邮件主题中提及书名。如果您对本书的任何方面有疑问,请通过 questions@packtpub.com
与我们联系。
勘误:虽然我们已尽最大努力确保内容的准确性,但错误总会发生。如果您在本书中发现任何错误,我们将感激您向我们报告。请访问www.packt.com/submit-errata,选择您的书籍,点击“勘误提交表格”链接并填写相关信息。
盗版:如果您在互联网上发现任何形式的非法复制我们作品的情况,我们将感激您提供相关位置地址或网站名称。请通过copyright@packt.com
与我们联系,并附上相关材料的链接。
如果您有兴趣成为作者:如果您在某个领域具有专长,并且有兴趣撰写或为书籍做贡献,请访问authors.packtpub.com。
评论
请留下评论。一旦您阅读并使用了本书,为什么不在您购买书籍的网站上留下评论呢?潜在读者可以参考您的公正意见做出购买决策,我们 Packt 公司可以了解您对我们产品的看法,而我们的作者也能看到您对他们书籍的反馈。感谢您的支持!
想了解更多关于 Packt 的信息,请访问packt.com。
所有活动的解决方案都在附录部分中。
第一章:Kubernetes 设计模式
设计模式是日常问题最佳实践的形式化。将设计模式应用于日常工作中,为您创建了一个共同的语言和沟通平台。在实际工作中,经验丰富的工程师不会解释如何将一个接口转换为另一个接口;相反,他们会决定实现一个适配器。设计模式隐藏了复杂性和通信细节,并创造了一个共同的平台。此外,通过积累的设计模式知识,将业务需求转化为代码变得更加轻松。
Kubernetes 是一个崛起的、著名的开源容器编排系统,由 Google 设计。它的基本功能包括自动化、扩展和容器化应用程序的调度。为了构建一个可扩展且可靠的云原生应用程序,Kubernetes 是您工具集中的关键部分。从初创公司到大型企业,各种规模的公司都在使用 Kubernetes 来安装和管理云原生应用程序。
这一软件开发的范式转变始于创建微服务,而不是构建庞大的软件系统块。随着 "新" 最佳实践的出现,它们与事实上的云原生编排工具 Kubernetes 对接。全书将介绍 Kubernetes 设计模式和扩展能力。书中首先解释最佳实践,展示如何创建 Kubernetes 原生应用程序。接下来,将解释如何通过编程访问 Kubernetes,并丰富这一有史以来最强大的编排工具。最后,将通过更高层次的自动化扩展 Kubernetes 本身。最终,您将拥有不仅能创建运行在 Kubernetes 上的应用程序的技术知识和实践经验,还能扩展 Kubernetes 系统本身的能力。
在本章的第一部分,将介绍 Kubernetes 设计模式,从设计模式的基础开始。接下来,您将使用结构模式构建 Kubernetes 解决方案,使用行为模式组装系统,最后根据部署策略安装应用程序。
本章结束时,您将能够:
-
定义设计模式的基本概念
-
解释模式的分类
-
使用 Kubernetes 设计模式解决实际问题
-
使用结构模式构建解决方案
-
使用行为模式组装复杂系统
-
使用部署策略安装应用程序
软件设计模式
在软件开发中,设计模式是解决广泛问题的可重复解决方案,因为它普遍适用于解决您之前遇到的相同问题。设计模式有两个主要优点。第一个优点是它们是经过验证的解决方案,第二个优点是它们为开发人员之间创造了一个沟通平台。凭借这些优点,模板和规范经过多年的规范化,形成了一个知识和经验池。
设计模式并不是可以直接转化为代码的完成设计——它们只是最佳实践和一系列方法。
软件开发被视为一个相对年轻且不断发展的学科领域;然而,在各种情况下解决的大多数问题都是相似的。例如,在各种软件系统中创建单一实例组件是常见的做法,如支付系统、日志管理器、企业资源规划 (ERP) 系统或在线游戏。因此,利用过去积累的知识可以帮助开发团队快速进步。
设计模式和相应的业务需求看起来可能显得人工且仅与软件相关。然而,问题和解决方案的根源实际上源自现实生活。例如,单例模式被提出作为实现配置管理器的最佳实践。采用相同的思路,适配器模式被提出作为与不同版本 API 协作的最佳实践。正如其名字所示,它类似于在现实生活中使用电气适配器来适配不同国家的插头和插座类型。正如这些例子所示,软件设计模式及其背后的理念都源于现实生活经验。
软件设计模式的用途
设计模式有两个主要用途。首先,设计模式为开发者创建了一个通用的平台,带有专有术语。例如,在一次技术讨论中,假设已经做出了使用组件单例的设计决策。所有其他开发人员,至少是那些了解设计模式的开发人员,将不需要进一步了解单例模式的属性。尽管这看起来微不足道,但它是技术沟通的丰富化,带有最佳实践的技术专业知识。其次,了解并利用工程学中的最佳实践能让开发进程更加迅速。假设你在设计一辆车——你应该始终从发明最好的轮子开始。这使得整个过程更加高效,并且避免了从过去的经验中吸取教训的重复过程。
错误。
软件设计模式的分类
设计模式有三种分类方法。随着新技术和编程语言的不断涌现,新的分类方法也被提出,但分类的主要思想保持不变——它们的控制器之间的交互。
以及其他应用:
总结这一部分,设计模式是正式化的最佳实践,源于现实生活和软件设计。它们为开发者创造了一个共同的沟通平台,并作为一个知识集合,具有重要的价值。
在下一章节中,介绍了 Kubernetes 的设计模式,作为创建、管理和部署现代云原生应用的最佳实践。
Kubernetes 设计模式
微服务和容器技术的发展改变了软件应用程序的设计、开发和部署方式。如今,现代云原生应用程序更加关注可扩展性、灵活性和可靠性,以满足业务需求。例如,十年前,数千个实例可靠运行的应用程序可扩展性并不是一个问题。同样,当没有 Kubernetes 这种编排工具来处理这些问题时,自我意识和自我修复功能也不在考虑范围之内。
新的需求和云原生特性揭示了它们自身的最佳实践和设计模式。Kubernetes 被各种公司广泛使用并被采纳,包括初创公司和大型企业,这使得最佳实践能够被形式化并汇集起来。在本节中,将描述 Kubernetes 中广为人知的基本设计模式和部署策略。借助这些模式,你将能够利用 Kubernetes 自身所形式化的最佳实践。
结构模式
结构模式侧重于将构建块组合成更高层次的复杂资源。在微服务架构中,应用程序被打包并作为容器部署。这种方法使得应用程序可以更容易地扩展,减少了开销并增强了隔离性。然而,这也使得在成千上万节点的集群中并排或按顺序运行相关容器变得困难。例如,如果你想将前端和后端容器一起运行在一个集群中,你需要找到一种机制,使得它们始终能够被调度到同一节点上。同样,如果在启动应用程序之前需要填充配置文件模板,那么就需要确保配置处理容器在应用程序之前已经运行。
在 Kubernetes 中,容器是被封装在 Pod 中的构建块。作为容器编排工具,Kubernetes 提供了内置功能,用于在 Pod 内组织容器。在本节中,将解释 Kubernetes 的辅助车模式和初始化结构设计模式。
Pod 是 Kubernetes 中最小的可部署资源,由一个或多个共享资源的容器组成。Pod 中的容器始终被调度到同一节点,以便它们可以共享网络和存储等资源。
关于 Pod 概念的更多信息可以在 Kubernetes 官方文档中找到:kubernetes.io/docs/concepts/workloads/pods/pod
。
辅助车模式
现代软件应用程序通常需要外部功能,如监控、日志记录、配置和网络。当这些功能紧密集成到应用程序中时,它们可以作为单个进程运行。然而,
这违反了隔离原则,并且为单点故障提供了机会。基于这一思想,云原生应用中的容器应该遵循 Unix 哲学:
-
容器应该只有一个任务
-
容器应该协同工作
-
容器应该处理文本流
单点故障(SPOF)是指系统中的一个组件,其失败可能导致整个系统的崩溃。为了拥有可靠且可扩展的云原生应用程序,SPOF 是不可取的,系统设计应该检查是否存在 SPOF。
Unix 哲学专注于设计一个具有清晰服务接口的小型操作系统。为了拥有这样一个系统,应当进行简单、精确、清晰且模块化的软件开发,同时考虑开发人员和维护人员。此外,哲学还强调应该组合子系统,而不是创建一个庞大、单一的设计。
基于这个思想,分离主应用程序并运行一些附加的 sidecar 容器已成为一种常规方法,这些 sidecar 提供额外的功能。使用 sidecar 的主要优势如下:
-
它们有独立的编程语言和运行时依赖
-
它们密切监控主应用程序,并最小化延迟
-
它们扩展了黑箱应用程序
在以下活动中,将在 Kubernetes 中安装一个基于 Web 的游戏。正如预期的那样,应该至少有一个容器在运行 Web 服务器。然而,还有一个附加要求是持续的源代码同步。
根据 Unix 哲学,期望容器只承担一个主要任务。它们还应该独立工作,并且协同工作以实现必要的需求。最后,这些容器应该更新它们的状态,并将日志信息通知控制台。以下活动将涵盖这三点。
活动:运行带同步功能的 Web 服务器
场景
你被分配的任务是在 Kubernetes 中部署一个基于 Web 的 2048 游戏。然而,游戏仍在开发中,开发人员一天之内会频繁推送更改。在这个安装中,游戏应该经常
更新以包括最近的更改。
目标
在成功部署后,Kubernetes 中应该有一个 Pod 正在运行,且包含两个容器。一个容器应该负责提供游戏,另一个容器,即 sidecar 容器,应该持续更新游戏的源代码。通过 Kubernetes,你需要创建一个云原生解决方案,其中服务器和同步任务协同工作,但彼此独立。
前提条件
使用 git Docker 镜像进行 sidecar 容器中的持续同步,并且使用 2048 游戏的开源代码库。
完成步骤
-
创建一个 Pod 定义。
-
包括两个容器:
-
使用 httpd 为游戏提供服务。
-
使用 git 进行源代码同步。
-
-
部署 Pod。
-
显示同步的日志。
-
检查游戏是否已启动。
本章中的所有代码文件都可以在 GitHub 上的 Lesson-1
文件夹中找到,网址是 goo.gl/gM8W3p
。
你需要创建一个 2048 游戏,如下图所示:
初始化模式
初始化是软件工程各个领域的广泛模式,包括编程语言和操作系统。为了处理 Kubernetes 中应用的初始化,提出了初始化容器。初始化容器,也叫initContainers,是 pod 定义的一部分。通过将容器的生命周期分离来处理关注点分离:
-
初始化容器
-
主容器
定义为 initContainers 的容器会依次执行,且都期望成功退出。完成后,主容器将启动。以下是启动容器的一些示例用途:
-
创建配置文件
-
等待并确保依赖服务可用
-
创建卷并准备文件
-
将服务注册到发现系统
在接下来的活动中,将在 Kubernetes 中安装一个 Web 服务器。如预期所示,应该至少有一个容器正在运行 Web 服务器。然而,还有一个附加要求,即在 Web 服务器启动之前更改提供的文件。这些操作的生命周期被分离到初始化容器和主容器中。这些容器可能具有不同的容器镜像和可执行文件;然而,它们预计会在共享资源上工作。生命周期分离和协作问题将在接下来的活动中处理,并在 Kubernetes 中实现初始化模式。
活动:在内容准备完成后运行 Web 服务器
场景
你被分配了一个任务,需要为一个 Web 服务器制作 Kubernetes 安装。然而,在提供服务之前,需要执行一些手动任务来更改文件。你需要确保在内容提供之前始终应用这些更改。此外,还需要显示初始化的状态,并检查 Web 服务器的最终输出。
目标
在成功部署后,Kubernetes 中应该有一个 pod 正在运行,包含一个主容器和一个初始化容器。在初始化容器中,将 Welcome from Packt
写入主索引文件。当主容器启动时,该文件应由主容器提供服务。
前提条件
-
使用基本的 Docker 镜像作为初始化容器,例如 busybox。
-
对于主容器,应使用一个能够运行 Web 服务器的镜像,例如 nginx。
完成步骤
-
创建一个 pod 定义。
-
包含一个初始化容器:
- 更改文件的内容。
-
包含一个主容器:
- 创建一个 Web 服务器。
-
部署 pod。
-
检查初始化容器的状态。
-
检查 Web 服务器的输出。
你应该看到以下输出:
本章中活动的所有代码文件都可以在 GitHub 的 Lesson-1
文件夹中找到,链接地址:goo.gl/gM8W3p
。
行为模式
在软件开发中,行为设计模式专注于对象之间的通信和交互。交互和通信包括责任分配、行为封装和请求委托。在微服务架构中,行为模式专注于微服务之间的通信以及服务与编排工具的交互。
例如,假设我们考虑一个每天检查用户配额的微服务执行。它可以通过一个包含 24 小时睡眠和执行配额检查的无限循环来实现。尽管它有效,但在睡眠期间会消耗额外资源,且创建了低效的架构。通过“定时作业模式”的行为模式,编排工具可以处理微服务的调度,并确保它每 24 小时执行一次。
在 Kubernetes 中,容器被封装在 Pod 内,Pod 是行为模式的主要关注点。行为模式专注于 Kubernetes 资源之间的交互和通信,即 Pod 与 Kubernetes 服务之间的交互。控制器系统中的通信和交互可能包括将 Pod 分配到节点、Pod 的调度,或由 Kubernetes 主控和节点组件进行元数据分发。
以下行为模式将在接下来的章节中介绍:
-
作业模式
-
定时作业模式
-
守护进程服务模式
-
单例服务模式
-
内省模式
作业模式
在 Kubernetes 中,Pod 的一般使用场景是用于长时间运行、始终保持启动的进程。为此,Kubernetes 提供了更高级别的资源,如副本集或部署。这些高级资源通过创建副本、检查健康状态和控制更新机制来管理 Pod 的生命周期。另一方面,微服务有时需要执行一个任务,并在完成后成功退出。例如,数据库初始化、备份或视频转换等应该只执行一次并退出,不消耗额外的资源。针对这种需求,Kubernetes 提供了一个名为 Job 的高级资源。Kubernetes 作业表示一个独立的工作执行直到完成,非常适合只需要运行一次的用例。
作业和由副本控制的 Pod 之间最重要的区别是 restartPolicy
字段:
-
对于始终运行的 Pod,
restartPolicy
被设置为Always
-
对于作业,
restartPolicy
可以设置为OnFailure
或Never
定时作业模式
分布式系统和微服务架构不依赖于时间性事件来运行服务;相反,它们关注触发事件,如 HTTP 请求、新的数据库条目或消息队列。然而,调度任务并期望它在指定时间间隔内运行是“定时作业模式”的常见方法。用于维护操作、发送每日电子邮件或检查过期文件的微服务应按固定间隔调度,并且必须运行以满足业务需求。Kubernetes 提供 CronJob 资源来创建定时任务,并确保这些作业在运行。
守护进程服务模式
守护进程应用程序通常用于操作系统和编程语言中,作为长期运行的应用程序或线程,以后台进程的形式运行。守护进程应用程序的名称包括 httpd、sshd 和 containerd;结尾的字母表示这些服务是守护进程。在微服务架构中,有些服务应该独立运行和扩展,而与消费者使用无关。这些应用程序应该在集群中的每个节点上运行,以确保守护进程应用程序的要求,例如:
-
集群存储守护进程:glusterd,ceph。
-
日志收集守护进程:fluentd,logstash。
-
节点监控守护进程:collectd,Datadog 代理,New Relic 代理。
在 Kubernetes 中,DaemonSets旨在部署需要在所有或某些节点上运行的持续后台任务。在没有其他管理考虑的情况下,Kubernetes 会自动处理这些守护进程 Pod 在新加入节点上的运行。
单例服务模式
运行一个——且仅有一个——实例是应用程序的要求,因为多个实例可能会导致不稳定。尽管这似乎与可扩展的微服务架构相悖,但仍有一些应用程序需要遵循单例模式:
-
数据库实例和连接器
-
配置管理器
-
尚未扩展的应用程序
Kubernetes StatefulSet 的副本数为 1,确保集群中只运行一个 Pod 实例。通过这种小型配置,可以在云原生环境中创建并使用单例服务。然而,集群中只有一个应用实例也带来了缺点。例如,应该小心处理单例应用程序的停机问题。这个问题的主要关注点是如何处理由于自动
StatefulSet 的更新。在 Kubernetes 中有两种可能的解决方案:
-
容忍更新期间的偶尔停机
-
设置
PodDistruptionBudget
,minAvailable=1
:-
由于已设置
PodDistruptionBudget
,取消自动更新 -
通过操作步骤为中断做准备
-
删除
PodDistruptionBudget
资源并继续更新 -
为下次中断重新创建
PodDistruptionBudget
-
内省模式
运行在裸金属集群上的应用程序能够准确知道它们运行的位置、系统规格以及网络信息。这些信息帮助它们在自我感知的环境中工作。例如,这些应用可以调整资源使用,增强日志内容,或者发送与节点相关的度量数据。在微服务架构中,应用程序被认为是短暂的,其对环境的依赖较少。然而,对运行时信息的自我感知,即内省模式,可以提高应用在分布式系统中的实用性和可发现性。
在 Kubernetes 中,Downward API 确保为 pod 提供以下环境和运行时数据:
-
环境: 节点名称、命名空间、CPU 和内存限制。
-
网络配置: Pod IP。
-
授权: 服务账户。
在以下活动中,将创建并安装一个简单但具备自我感知的 Kubernetes 应用程序。该应用程序拥有所有的运行时信息,这些信息由 Kubernetes 作为环境变量提供。作为单一应用,它将所有这些变量记录到控制台。然而,本活动中开发的 pod 定义展示了如何在容器内部使用 Downward API 来实现 Kubernetes 中的内省模式。
活动:将数据注入应用程序
场景
你被指派的任务是为一个简单的具备自我感知功能的应用程序做 Kubernetes 安装。在这个安装中,应用程序应收集来自 Kubernetes 的所有运行时信息并写入其日志。
目标
在成功部署后,Kubernetes 中应有一个仅包含一个容器的 pod 在运行。在这个容器中,所有可用的运行时信息应作为环境变量注入。此外,应用程序应记录运行时信息。
前提条件
使用 Kubernetes Downward API 收集运行时信息。
完成步骤
-
创建一个包含一个容器的 pod 定义:
-
从 Downward API 定义环境变量。
-
创建一个 shell 脚本以写入环境变量。
-
-
部署 pod。
-
检查 pod 的状态。
-
检查容器的日志。
本章活动的所有代码文件已上传至 GitHub,位于 Lesson-1
文件夹,地址为 goo.gl/gM8W3p
。
部署策略
设计和开发云原生应用程序,并采用微服务架构,对于未来的可靠且可扩展的应用至关重要。同样,云中的应用程序部署和更新与设计和开发一样重要。交付应用程序有多种技术,因此选择合适的配置对于最大限度地利用变更对消费者的影响至关重要。通过使用合适的 Kubernetes 资源子集并选择合适的部署策略,可以实现可扩展和可靠的云原生应用。
在本节中,介绍了以下部署策略,并且你需要完成这些练习,以便能够看到 Kubernetes 资源的实际操作:
-
重建策略
-
滚动更新策略
-
蓝绿策略
-
A/B 测试策略
重建策略
重建策略基于关闭旧版本实例,然后创建下一个版本实例的思想。使用这种策略时,停机是不可避免的,这取决于应用程序的关闭和启动时间。在 Kubernetes 中,可以使用重建策略来创建部署资源,并采用重建策略。
剩下的操作由 Kubernetes 处理。在幕后,重建策略的步骤如下:
- 用户请求通过负载均衡器路由到V1实例:
- V1实例被关闭,且由于没有可用的实例,停机时间已开始:
- V2实例被创建;然而,直到它们准备好之前,不会处理用户请求:
- 用户请求被路由到V2实例:
重建策略的主要优点是其简单明了,并且没有额外的开销。除此之外,每次更新时,实例都会完全重建,并且不会有两个版本同时运行。然而,这种策略的缺点是在更新过程中不可避免地会有停机时间。
使用重建策略部署应用
你正在 Kubernetes 上运行一个高可用性应用,需要一个部署策略来处理更新。这个应用能够承受短暂的停机时间,但不应有任何时候两个版本一起运行。这个应用依赖的服务无法处理同时运行两个不同版本的情况。我们希望使用重建部署策略,这样 Kubernetes 就能通过删除旧实例并创建新实例来处理更新。
你可以在以下链接找到recreate.yaml
文件:goo.gl/2woHbx
。
让我们按照以下步骤开始:
- 使用以下命令创建部署:
kubectl apply -f recreate.yaml
- 在另一个终端中,监控部署变化,直到所有三个实例都可用:
kubectl get deployment recreate -w
- 更新部署的版本:
kubectl patch deployment recreate -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx", "image":"nginx:1.11"}]}}}}'
- 在我们在步骤 2中打开的终端中,应该能够看到 pod 的删除和停机情况,此时
Available
为零。之后,可以跟踪新实例的创建,Available
从0
增加到3
:
- 你可以运行以下命令进行清理:
kubectl delete -f recreate.yaml
滚动更新策略
滚动更新策略,也称为增量或逐步更新,是通过替换先前版本来慢慢发布新版本的策略。在此策略中,首先,一组应用程序在负载均衡器后面运行。然后,启动新版本实例,当它们启动并运行时,负载均衡器将请求重定向到新实例。同时,之前版本的实例将被关闭。在 Kubernetes 中,滚动更新策略是部署的默认策略,因此对deployment的任何更新都已经在实施滚动更新策略。
在 Kubernetes 处理此过程时,滚动更新策略的步骤可以如下跟踪:
- 用户的请求通过负载均衡器路由到V1实例:
- V2实例被创建,用户被引导到它们,同时,V1实例被删除。在这个阶段,两个版本都在运行并提供请求服务:
- V2实例的创建和V1实例的删除是逐一进行,直到没有V1实例剩余:
- 最后,所有来自用户的请求都被路由到V2实例:
滚动更新策略的主要优点是简单易行,并且是 Kubernetes 的默认方法。它还有助于通过在新实例启动期间平衡负载,慢慢发布新版本。另一方面,Kubernetes 自动处理版本的发布和回滚,且这些操作的持续时间无法预知。滚动更新中最复杂的问题之一是集群中两个版本同时运行,且流量无法控制。
使用滚动更新策略部署应用程序
你正在 Kubernetes 上运行一个高可用的应用程序,需要一个部署策略来处理更新。假设这个应用程序是你客户公司的前端,必须始终保持可用,停机是不可接受的。在任何时候,多个版本的应用程序都可能同时工作;然而,客户不希望在更新过程中增加额外资源的成本。我们将使用滚动更新部署策略来运行应用程序,以便 Kubernetes 通过增量创建新实例并逐个删除旧实例来处理更新。我们按照以下步骤开始:
- 使用以下命令创建部署:
kubectl apply -f rolling.yaml
- 在另一个终端中,启动 Kubernetes 集群中的 cURL 实例:
kubectl run curl --image=tutum/curl --rm -it
- 当命令提示符准备好时,通过使用 HTTP 请求查看部署的版本:
while sleep 0.5; do curl -s http://rolling/version | grep nginx; done
- 使用以下命令更新部署的版本:
kubectl patch deployment rolling -p '{"spec":{"template":{"spec":{"containers":[{"name":"nginx", "image":"nginx:1.11"}]}}}}'
- 在我们在步骤 2中打开的终端中,预计我们应该看到 Kubernetes 处理版本的增量变化。在更新过程中,服务不会中断。然而,两个版本同时运行,并且处理请求的版本分别是 1.10.3 和 1.11.13,并且它们可以互换使用:
- 清理时,使用Ctrl + C停止 cURL 命令并通过输入 exit 退出 Pod。退出时,Pod 会被 Kubernetes 删除。运行以下命令:
kubectl delete -f rolling.yaml
你可以在以下位置找到rolling.yaml
文件:goo.gl/eo8cJw
。
蓝绿策略
蓝绿策略的思想是有两个活跃的生产环境,即蓝色和绿色。蓝色环境是活跃的并且在处理请求。绿色环境包含新版本,并且正在进行更新测试。当测试成功完成后,负载均衡器会从蓝色实例切换到绿色实例。在 Kubernetes 中,蓝绿部署是通过安装两个版本并更改服务或资源的配置来处理的。
- 蓝绿策略的主要步骤可以定义如下:V1和V2实例都已部署,负载均衡器已为V1实例配置:
- 测试后,负载均衡器已为V2实例配置:
蓝绿策略的优点包括即时的发布和回滚,以及在更新过程中没有版本不匹配。另一方面,这种策略的缺点是你需要使用双倍的资源,因为需要维护两个环境。需要注意的是,在使用此策略之前,应该进行详尽的端到端应用程序测试。
使用蓝绿策略部署应用程序
你正在 Kubernetes 上运行一个高可用的应用,需要一个部署策略来处理更新。例如,这个应用是一个抵押贷款计算引擎 API,拥有不同版本,因此会导致不同的计算结果。因此,客户端需要在发布前进行广泛的测试,并且能够在版本之间进行即时切换。我们将使用蓝绿部署策略运行一个应用,使得两个版本都在运行,并且 Kubernetes 服务处理版本的即时切换。让我们开始执行以下步骤:
- 使用以下命令创建部署的两个版本以及公共服务:
kubectl apply -f blue-green.yaml
- 使用以下命令检查集群中是否部署了两个版本:
kubectl get pods
你应该看到以下输出:
- 在一个单独的终端中,启动 Kubernetes 集群中的 cURL 实例:
kubectl run curl --image=tutum/curl --rm -it
- 当命令提示符准备好时,通过使用 HTTP 请求监控部署的版本:
while sleep 0.5; do curl -s http://blue-green/version | grep nginx; done
- 使用以下命令更新服务,将流量路由到新版本:
kubectl patch service blue-green -p '{"spec":{"selector":{"version":"1.11"}}}'
- 在我们在第 3 步中打开的终端中,我们应该看到 Kubernetes 服务在没有任何中断的情况下处理版本的即时切换:
- 为了清理,使用 Ctrl + C 停止 cURL 命令,并通过输入 exit 退出 pod。退出时,Kubernetes 将删除该 pod。运行以下命令:
kubectl delete -f blue-green.yaml
A/B 测试策略
A/B 测试策略基于消费者分离的思想,并提供不同功能子集。A/B 测试允许你并行运行多个功能变体。通过用户行为分析,可以将用户路由到更合适的版本。以下是可以用于分散流量的条件示例:
-
Cookies
-
位置
-
技术,如浏览器、屏幕大小和移动性
-
语言
在下图中,两个版本都已安装,用户根据其技术特征(即移动设备或桌面)被路由:
A/B 测试策略的主要优势在于你可以完全控制流量。然而,分发流量需要一个智能负载均衡器,而不是常规的 Kubernetes 服务。以下是一些流行的应用:
-
Linkerd
-
Traefik
-
NGINX
-
HAProxy
-
Istio
Linkerd、Traefik、NGINX 和 HAProxy 是数据平面应用,专注于在服务实例之间转发和观察网络数据包。另一方面,Istio 是一个控制平面应用,专注于配置和
管理路由流量的代理。
部署策略总结
在选择部署策略之前,应该考虑到没有一种“灵丹妙药”能够解决所有生产环境的需求。因此,检查和比较策略的优缺点并选择最合适的方案是至关重要的:
总结
本章介绍了 Kubernetes 设计模式,这些设计模式是云原生应用程序的最佳实践。首先,通过它们的使用和分类解释了设计模式的概念。接着,介绍了 Kubernetes 设计模式,并进行了若干课堂活动。作为云原生应用程序的突出框架,Kubernetes 拥有满足所有业务需求的灵活性和覆盖范围。然而,如何有效地使用 Kubernetes 资源至关重要,这正是本章所讲解的内容。
第二章:Kubernetes 客户端库
Kubernetes 提供多种选项来使用 Kubernetes API 创建应用程序。这些选项包括 kubectl
、helm
、kops
和 kubeadm
等工具;以及官方支持的或社区维护的客户端库。然而,了解客户端的能力和局限性是至关重要的,只有这样才能创建与 Kubernetes 交互的应用程序。
在本章中,您将学习如何直接访问 Kubernetes API 并使用 Kubernetes 客户端库。首先,将解释如何直接访问 Kubernetes API,并演示从 Kubernetes API 返回的响应。接下来,将介绍官方和社区维护的库,包括详细信息和示例应用程序。在本章中,您将开发与 Kubernetes API 连接的应用程序,既包括集群内的,也包括集群外的。
本章结束时,您将能够:
-
评估 Kubernetes API 请求和响应风格
-
使用 HTTP 连接到 Kubernetes API
-
查找并使用官方客户端库
-
编写、运行并部署客户端库应用程序
-
评估社区维护的客户端库,以支持进一步的项目
访问 Kubernetes API
Kubernetes 由多个松散耦合的组件组成,主要目的是管理版本化资源。Kubernetes 组件可以分为两大部分:控制平面和节点组件。控制平面包括 API 服务器、控制器管理器和调度器。API 服务器是核心管理组件,具有以下功能:
-
为集群外的客户端和集群内的 Kubernetes 组件提供 REST API 服务
-
创建、删除和更新所有 Kubernetes 资源,例如 Pod、部署和服务
-
将对象的状态存储在分布式键值存储中
Kubernetes API 风格
Kubernetes API 是一种 RESTful 服务,要求所有客户端使用 HTTP 请求(如 GET
、PUT
、POST
和 DELETE
)来创建、读取、更新和删除资源。客户端应用程序,如 kubectl
或各种编程语言中的客户端库,实施 API 的请求和响应类型。为了通信,Kubernetes API 接受并返回 JSON 数据,就像大多数可用的 RESTful 服务一样。
表现状态转移(REST)是一种 Web 应用程序架构风格,使其能够使用 HTTP 请求。按照惯例,GET
用于读取资源,POST
用于创建资源,PUT
用于更新资源,DELETE
用于删除资源。
应用 RESTful API 的服务器应确保客户端无需了解服务器结构。服务器也应提供所有相关信息,以便客户端能够操作并与其交互。
JavaScript 对象表示法(JSON)是一种流行的轻量级数据交换格式。JSON 适用于机器解析和生成,并且具有可读性和表达性。尽管 JSON 是用 JavaScript 编写的,但它被多种语言支持,并且是现代异步浏览器/服务器通信中的关键数据类型。
在接下来的部分,将通过调用 API 服务器的 REST 端点来探索 Kubernetes API 响应的风格。
从 Kubernetes API 获取 JSON 响应
本部分展示了如何通过使用 kubectl
从 Kubernetes API 获取原始数据,并将数据作为 JSON 对象进行分析,获取资源的各个部分。
让我们开始实现以下步骤:
- 使用以下命令获取原始数据:
kubectl get --raw /api/v1/namespaces/kube-system
- 结果是,你将看到一个 JSON 响应。让我们获取相同的命令并格式化输出:
kubectl get --raw /api/v1/namespaces/kube-system | python -m json.tool
如果 Python 未安装,可以通过复制并粘贴输出,使用任何在线 JSON 格式化工具。
- JSON 响应显示了 Kubernetes API 资源的结构:
{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": {
"creationTimestamp": "2018-04-15T10:21:34Z",
"name": "kube-system",
"resourceVersion": "81",
"selfLink": "/api/v1/namespaces/kube-system",
"uid": "c5db1188-4096-11e8-903d-0800273b4d24"
},
Kubernetes API 资源有 "apiVersion"
,因为所有资源在系统中都是版本化的。"kind"
显示资源的类型,"metadata"
包含所有元数据,如创建时间戳、标签或注解。"spec"
是列出所有资源属性的部分。最后,大多数资源有一个 "status"
部分,用来显示它们的状态、错误或信息(如果有的话)。
访问 Kubernetes API
Kubernetes API 服务器是安全的,要求所有传入的连接都进行身份验证。有两种常见的连接方式,可以与 Kubernetes API 服务器进行安全通信。第一种是通过 kubectl 使用反向代理功能,第二种是通过 API 服务器凭证。这些方法可以总结如下:
-
使用 kubectl 反向代理 Kubernetes API:
-
Kubectl 代理命令启动了一个本地主机与 Kubernetes API 服务器之间的代理服务器。
-
所有传入请求都被转发到远程 Kubernetes API 服务器端口。
-
API 服务器身份通过自签名证书进行验证,以防止发生中间人攻击(MITM)。
-
kubectl 负责与 API 服务器的身份验证。这是官方 Kubernetes 文档中推荐的做法。
-
进一步的开发正在进行中;未来可能会提供客户端负载均衡和故障转移功能。
-
-
直接提供 API 服务器地址和凭证:
-
API 服务器的地址和凭证可以在集群内部或外部获取,并且可以作为参数提供。
-
这是一种替代方法,应该在客户端应用无法与反向代理协作时作为最后手段使用。
-
为了防止 MITM 攻击,证书应导入客户端,例如通过浏览器。
-
在接下来的活动中,通过使用kubectl
代理连接到 Kubernetes API,以创建一个新的 Kubernetes 命名空间。通过这种方法,kubectl
使用自己的凭证安全地连接到 API 服务器,并为本地系统上的应用程序创建代理。
连接到 Kubernetes API 并创建命名空间
在本节中,你的任务是通过使用 Kubernetes API 为测试创建命名空间。测试在集群外部运行,并与 Kubernetes API 进行通信。为了在自己的命名空间中运行测试,你需要创建一个命名空间。在完成此任务后,通过发送 JSON 数据将在 Kubernetes 中创建一个新的命名空间。让我们在开始示例之前确保遵循以下步骤:
-
使用 kubectl 创建代理,并使其对本地系统上的所有应用程序可用
-
使用 JSON 和 cURL 与 Kubernetes API 进行通信
-
通过查询 kubesystem 来获取命名空间资源的 JSON 结构,并将其作为模板使用
让我们从实现以下步骤开始:
- 使用以下命令启动反向代理:
kubectl proxy --port=8080
- 在另一个终端中,创建一个指向转发端口的 HTTP 请求:
curl http://localhost:8080/api/v1/namespaces/kube-system
预期的响应应为类似以下的 JSON 结构:
- 使用步骤 2的响应作为模板,创建一个简单的命名空间 JSON 对象:
{
"apiVersion":"v1",
"kind":"Namespace",
"metadata":{
"name":"packt-client"
}
}
- 使用
curl
并使用步骤 3中的负载数据创建新的命名空间:
curl -X POST http://localhost:8080/api/v1/namespaces/ \
--header "Content-Type: application/json" \
--data \
'{"apiVersion":"v1","kind":"Namespace","metadata":{"name":"packt-client"}}'
执行此命令后,将接收到新创建的命名空间数据:
- 你可以运行以下命令进行清理:
kubectl delete namespace packt-client
访问集群内的 Kubernetes API
访问集群外部的 Kubernetes API 大多数是用于需要人工交互的操作基础。此外,访问 Kubernetes API 并向 API 服务器发送请求也可以在集群内部进行。这使得可以在集群内部编写应用程序并运行它们,从而将操作知识转化为应用程序。
对于集群中的所有 Pod,Kubernetes 会注入服务账户——它们是推荐的认证 Kubernetes API 服务器的方式。对于每个 Pod,以下与服务账户相关的信息和凭证都会被挂载
默认情况下:
-
服务账户和令牌:
/var/run/secrets/kubernetes.io/serviceaccount/token
-
证书包:
/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
-
命名空间:
/var/run/secrets/kubernetes.io/serviceaccount/namespace
在集群内部使用这些信息形成了一种安全的方式来连接 Kubernetes API 服务器并进行请求。服务账户作为 Kubernetes 中的一种身份验证机制,使用签名的令牌来验证请求。这些令牌由 Kubernetes API 服务器创建和管理。对于在 Kubernetes 中运行的每个 Pod,都会挂载服务账户令牌,它们使 Pod 能够与 Kubernetes API 服务器进行通信。更多信息可以参考官方文档:kubernetes.io/docs/admin/authentication
。
在集群内部连接 Kubernetes API
在本节中,我们将创建一个简单的应用程序,用于查询 Kubernetes API 并获取 kube-system 命名空间的详细信息。然而,这个应用程序应该在集群内部运行,并作为 Kubernetes 本地应用程序工作。我们将在集群内部查询 Kubernetes API,并在 Pod 中注入环境变量和证书。
让我们从实现以下步骤开始:
- 在集群内部启动一个 cURL 实例,并等待它启动并运行:
kubectl run curl --image=tutum/curl --rm -it
- 在 Pod 内部检查安全凭证:
ls /var/run/secrets/kubernetes.io/serviceaccount/
您将获得以下输出:
- 检查 Kubernetes API 服务器是否具有相关的环境变量:
env | grep KUBE
您将获得以下输出:
- 使用以下命令将所有凭据和地址信息结合起来:
APISERVER=https://$KUBERNETES_SERVICE_HOST:$KUBERNETES_SERVICE_PORT
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
CACERT=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt
NAMESPACE=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
- 使用 步骤 4 中收集到的环境变量,通过 cURL 创建并发送 HTTP 请求:
curl --header "Authorization: Bearer $TOKEN" --cacert
$CACERT $APISERVER/api/v1/namespaces/kube-system
使用上述命令时,将向 /api/v1/namespaces/kube-system
端点发送一个 GET 请求。为了对 API 服务器进行身份验证,将作为标头发送一个承载令牌,并提供证书授权信息。
执行此命令后,将从 API 服务器获取请求的命名空间信息:
Kubernetes API 是核心管理服务,它是一个安全的 RESTful 服务,使用 JSON 格式。它要求所有客户端都进行身份验证,并且既可以是外部连接,也可以是集群内部连接。在接下来的部分中,将介绍多种编程语言的客户端库,这些库实现了 Kubernetes API。
官方客户端库
消费 Kubernetes REST API 的应用程序应实现 API 调用,包括请求和响应类型。考虑到 Kubernetes 提供的丰富资源集,开发和维护 API 实现变得复杂。幸运的是,Kubernetes 提供了一套丰富的官方客户端库,涵盖了各种编程语言。这些客户端库不仅处理请求和响应,还处理与 API 服务器的身份验证。此外,大多数客户端库能够发现并连接到集群内部运行的 Kubernetes API 服务器。
本节将介绍官方的 Go 和 Python 客户端库,包括客户端代码库、文档、安装方法,以及如何创建在集群内外运行的简单应用程序。
Go 客户端库
Go,也被称为 Golang,是 Google 在 2009 年创建的编程语言。Go 的显著特点包括:
-
它是静态类型的,因此编译器可以确保对象类型和转换的正确性
-
它具有内存安全性,且无需担心开发问题
-
它具有垃圾回收功能,且开销最小
-
对象的结构化类型基于其组成
-
它具有原生的并发处理能力,提供了诸如 Go 协程和通道等原语
Go 是一种免费、开源的编程语言,具有编译器和环境工具。Go 因其上述特点在云原生应用中得到了广泛应用,因这些特点非常适合可扩展和可靠应用的需求。一些最著名的使用 Go 作为主要语言的项目如下:
-
Docker
-
Kubernetes
-
Terraform
-
OpenShift
-
Consul
-
比特币闪电网络
-
InfluxDB
-
CockroachDB
仓库
Kubernetes 的 Go 客户端,即 client-go,是 Kubernetes 官方项目的一部分,代码库可在 github.com/kubernetes/client-go
查阅。
它是最古老且最全面的客户端库。该客户端库的 Kubernetes 资源处理程序是通过 Kubernetes 官方源代码生成器生成的。此外,client-go 在 Kubernetes 项目中得到了广泛应用。
如 kubectl、helm 和 kops 等工具。
文档
Go 客户端代码库包含以下包及其相关领域:
-
kubernetes
:访问 Kubernetes API 的客户端集 -
discovery
:发现 Kubernetes API 服务器支持的 API -
dynamic
:执行通用 API 访问的动态客户端 -
transport
:认证和连接启动 -
tools/cache
:用于编写控制器的助手
Go 客户端遵循 Go 语言官方文档风格,文档可在 godoc.org/k8s.io/client-go
查阅。
安装
在 Go 语言中,其工具集提供了 go get
命令作为下载和安装包及其依赖项的标准方法。此命令从源控制版本提供者下载默认分支和最新更改。然而,Kubernetes 客户端的特定版本是设计为与特定版本的依赖项一起工作的。因此,标准的 go get
命令不可用。相反,应该使用为 Go 提出的依赖管理解决方案,以便可靠地使用 client-go
。
换句话说,应该确定所需的 client-go 版本,然后通过依赖管理器下载它和相应的依赖。这种处理依赖关系的方式叫做 vendoring。因此,依赖管理器将收集依赖库并将其放入 vendor
文件夹。
对于一个使用 client-go
库的 Go 应用程序,所有相关的库及其依赖项应当被收集到 vendor 文件夹中,以便进行可靠且可重复的构建。
Kubernetes Go 客户端支持多种依赖管理工具,如 dep、godeps 和 glide。此外,对于不想使用任何依赖管理工具的普通用户,client-go 官方文档中也提供了所需的步骤:github.com/kubernetes/client-go/blob/master/INSTALL.md
。
创建配置
Go 客户端库提供了连接 Kubernetes API 服务器所需的功能。它可以轻松地创建配置,以便与集群外部和内部进行通信。你可以使用以下代码片段来实现:
// Create configuration outside the cluster config, err = clientcmd.BuildConfigFromFlags("", kubeconfigPath)
// Create configuration inside the cluster config, err = rest.InClusterConfig()
创建 Clientset
Clientset 包含每组资源的客户端,并提供访问这些资源的权限。在其简化版本中,如以下代码所示,可以看到每一组资源的客户端都在客户端库中得到了实现:
type Clientset struct {
...
appsV1 *appsv1.AppsV1Client
...
batchV1 *batchv1.BatchV1Client
coreV1 *corev1.CoreV1Client
eventsV1beta1 *eventsv1beta1.EventsV1beta1Client
networkingV1 *networkingv1.NetworkingV1Client
rbacV1 *rbacv1.RbacV1Client
storageV1beta1 *storagev1beta1.StorageV1beta1Client
storageV1 *storagev1.StorageV1Client
}
使用前一步骤中的配置,可以使用以下代码片段创建 clientset:
// Create clientset from configuration
clientset, err := kubernetes.NewForConfig(config)
进行 API 调用
创建配置和 clientset 后,API 调用最终可以执行。可以使用提供的 clientset 中的客户端列出、更新、创建或删除所有 Kubernetes 资源。以下代码片段展示了一些示例:
// Request all pods from all namespaces
pods, err :=
clientset.CoreV1().Pods(v1.NamespaceAll).List(metav1.ListOptions{})
// Get deployment packt from the default namespace
deployments, err := clientset.AppsV1().Deployments(v1.NamespaceDefault).Get("packt", metav1.GetOptions{})
// Delete statefulset test from namespace packt
clientset.AppsV1().StatefulSets("packt").Delete("test", &metav1.DeleteOptions{})
之前的章节中提供了配置、客户端创建和使用 Kubernetes Go 客户端进行 API 调用的代码片段。完整的应用程序代码可以在 go/main.go
中找到,它将所有片段整合在一起,链接地址为 goo.gl/wJBjG5
。
我们可以注意到在 main.go
文件中的以下几点:
-
在第 19 行启动的主函数中,所有变量都被定义,并且在第 30 行解析了命令行参数。
-
配置是从
kubeconfig
创建的,并且作为备用方法,它是通过集群内的方法在第 33 行和第 42 行之间创建的。 -
Clientset 在第 45 行创建。
-
在第 51 行和第 65 行之间,定义了一个无限循环,迭代结束时会有 10 秒的休眠。
-
在此循环的每次迭代中,来自所有命名空间的 pods 会在第 53 行被请求。响应会在第 58 行和第 62 行之间打印到控制台。
在以下示例中,构建并运行了一个结合了前面各节代码片段的应用程序。它向你展示了如何构建一个 Go 应用并在集群外使用它。尽管该应用看起来很简单,但其流程和代码库为复杂的自动化需求奠定了基础。
在集群外使用 Kubernetes Go 客户端
本节中,我们将学习如何构建并运行一个 Go 应用,使用 Kubernetes Go 客户端,并将应用连接到集群外部。Go 应用是通过使用 Go 工具集命令(如 go build)构建的。然而,这需要在本地安装 Go。在本示例中,我们将使用 Go 语言的官方 Docker 镜像,而无需在本地机器上进行任何安装:
- 使用以下命令通过官方 Docker 容器创建跨平台构建:
cd go
make build
- 使用我们在步骤 1中创建的可执行文件和
kubeconfig
文件位置启动应用:
./client --kubeconfig=$HOME/.kube/config
你将看到以下输出:
活动:在集群内使用 Kubernetes Go 客户端
场景
你被分配了一个任务,部署一个列出 Kubernetes 中所有 Pod 的 Go 应用。除此之外,该应用将运行在集群内,并获取有关集群的信息。
目标
运行一个消耗 Go 客户端库的应用程序,位于 Kubernetes 集群内。
前提条件
-
使用 Docker 镜像
onuryilmaz/k8s-client-example:go
,该镜像包含了前面示例中的可执行文件。 -
部署应用并检查日志,查看它是否按预期工作。
完成步骤
-
使用前一个示例中的 Docker 镜像创建一个部署。
-
等待直到 Pod 正在运行。
-
获取部署 Pod 的日志。
使用此命令,通过子命令获取 Pod 的日志。在子命令中,通过 run
标签选择器等于 go-client
获取所有 Pod,并收集第一个 Pod 的名称。日志应显示客户端本身以及集群中的其他 Pod:
- 运行以下命令进行清理:
kubectl delete deployment go-client
Python 客户端库
Python 是一种高级通用编程语言,首次发布于 1990 年。它是最受欢迎的开源编程语言之一,广泛应用于多个领域,包括机器学习、数据处理、Web 开发和脚本编写。Python 的主要特点是该语言是解释型的,且支持动态类型检查。Python 的流行得益于其简洁的编程风格和注重代码可读性。在现代云原生环境中,Python 主要用于基础设施和自动化。除了其流行度和广泛应用外,Kubernetes 也有一个官方的客户端库,使用 Python 实现。
仓库
Kubernetes Python 客户端是官方客户端库的一部分,可以在 github.com/kubernetes-client/python
上找到。
Python 客户端是符合 OpenAPI 标准的客户端,这意味着 Swagger 工具会生成资源定义。客户端库仍在进行中,使用前应检查其功能是否适用于生产环境。像其他 Kubernetes 客户端一样,Python 客户端试图支持一组预定义的功能,根据其覆盖范围,它被分类为“银级”。
OpenAPI 是一种描述 RESTful API 的规范。通过使用 OpenAPI 规范,可以为客户端和服务创建实现,包括所有相关操作。
Swagger 是用于开发 API 的工具生态系统,它在 OpenAPI 中有所定义。Swagger 提供了开源和商业工具,用于根据所提供的规范创建应用程序。
安装
安装客户端库有两种方法,您可以创建开发环境。第一种方法是下载源代码并构建:
$ git clone --recursive https://github.com/kubernetes-client/
python.git
$ cd python
$ python setup.py install
第二种方法是通过使用像 pip
这样的包管理器,从 Python 包索引下载包:
$ pip install kubernetes
客户端使用
在前一章节中,开发了一个列出所有 pods 的 Go 应用程序。本章节用 Python 实现了与前一个应用相同的功能。凭借 Python 的简洁代码和可读性哲学,相同的功能大约用十行代码实现,如下所示:
from kubernetes import client, config
import time
config.load_incluster_config()
v1 = client.CoreV1Api()
while True:
ret = v1.list_pod_for_all_namespaces(watch=False)
print('There are {:d} pods in the cluster:'.format(len(ret.items)))
for i in ret.items:
print('{:s}/{:s}'.format((i.metadata.namespace, i.metadata.name))
time.sleep(10)
以下是关于前面代码片段的关键点:
-
在第 3 行,创建了集群内配置,在第 5 行,为
corev1
API 创建了客户端。 -
从第 8 行开始,进入一个无限循环,每次迭代暂停 10 秒。
-
在第 9 行,从
v1
客户端请求所有 pods,并解析响应并写入控制台。
打包
Python 应用程序应该像 Kubernetes 上运行的所有服务一样,运行在容器内。因此,本节中定义的客户端库与以下 Dockerfile 一起打包。此容器定义使应用程序能够在包含其依赖项的隔离环境中运行:
FROM python:3
RUN pip install kubernetes
ADD . /client.py
CMD ["python", "./client.py"]
请参阅完整代码:goo.gl/z78SKr
。
以下是对前面代码的注释:
-
容器基于支持 Python 3 版本的基础构建。
-
Kubernetes Python 客户端库通过
pip
在第 3 行安装。 -
客户端应用程序在第 5 行被复制到容器中,并在第 7 行启动。
在接下来的章节中,展示的 Python 代码片段用于在 Kubernetes 集群中工作。完整的代码被打包成一个包含其依赖的 Docker 容器。通过这个容器,应用程序以隔离的方式部署到 Kubernetes 中,遵循微服务架构。
在集群内使用 Kubernetes Python 客户端
在本节中,我们将部署一个 Python 应用程序,该应用程序列出所有 pod,并在 Kubernetes 内部使用 Python 客户端库。此外,应用程序将在集群内运行并收集关于集群的信息。
在开始实现之前,我们需要使用 Docker 镜像 onuryilmaz/k8s-client-example:python
,该镜像是通过上一节中的 Dockerfile 构建的。我们还需要将应用程序作为部署进行部署,并检查日志以查看其是否按预期工作。让我们从执行以下步骤开始:
- 使用示例客户端的 Docker 镜像创建部署:
kubectl run python-client -it --image=onuryilmaz/k8sclient-example:python
使用此命令,将创建一个名为 python-client 的部署,使用 Docker 镜像 onuryilmaz/k8s-client-example:python
以交互模式运行,以便日志能够打印到控制台。
日志应显示客户端本身,以及集群中的其他 pod:
- 运行以下命令进行清理:
kubectl delete deployment python-client
其他官方客户端库
在本章中,介绍了两个官方的 Kubernetes 客户端库:
-
Go:这是一种静态类型的基于编译器的语言
-
Python:这是一种动态类型和解释型语言
官方客户端库还包括一些额外的编程语言:
-
JavaScript:
github.com/kubernetes-client/javascript
对于这些库的功能和难题,您应该查看它们相应的仓库,因为它们都仍处于开发阶段。
社区维护的客户端库
Kubernetes 拥有一个活跃且合作的开源社区,这也提高了它的普及度。在 Kubernetes 文档中列出了大约 20 个由社区维护的客户端库,涵盖了以下语言:
-
Clojure
-
Go
-
Java
-
Lisp
-
Node.js
-
Perl
-
PHP
-
Python
-
Ruby
-
Scala
-
dotNet
-
Elixir
在使用社区维护的客户端库之前,有一些关键点需要考虑:
-
库的目标:考虑开发团队和库的目标非常重要。尽管这看起来与软件本身没有直接关系,但它会影响客户端库的开发方式。例如,一些库注重简洁性,可能在功能覆盖上做出妥协。如果你的应用程序愿景与客户端库不匹配,长远来看将难以维护应用程序。
-
版本和支持:官方库支持特定的 Kubernetes API 版本,并维护兼容性矩阵。使用与 Kubernetes 集群兼容的客户端库至关重要,同时确保能够获得未来 Kubernetes 版本的支持。一个由社区维护的客户端库今天可能非常适合,但如果没有得到支持,六个月后可能会被弃用。
-
社区兴趣:如果考虑的客户端库是开源的,那么它的社区应该是活跃的,并且有兴趣使该库变得更好。我们常常看到一些库起步很好,但由于缺乏社区支持,最终没有得到维护。不建议使用那些存在旧问题且很久没有评论或没有经过审查的拉取请求的客户端库。
总结
本章讨论了 Kubernetes API 访问和客户端库。尽管有多种工具可以与 Kubernetes 进行通信,但了解 Kubernetes API 本身和客户端库对于创建具有革命性的自动化和编排任务至关重要。
首先,介绍了 Kubernetes API 的风格以及如何使用 HTTP 客户端进行连接。接着,讲解了 Kubernetes 的客户端库,重点介绍了两个官方客户端库。我们通过演示和实践,展示了如何安装、编写代码、打包并将代码部署到集群的步骤。
最后,展示了针对不同语言偏好或自定义需求的社区维护库。通过掌握 Kubernetes 客户端库的知识和实践经验,能够实现更高层次的自动化,并扩展 Kubernetes。在接下来的章节中,将第一章介绍的最佳实践与本章包含的客户端库汇总在一起,创建扩展 Kubernetes 的应用程序。
第三章:Kubernetes 扩展
Kubernetes 是高度可定制和可扩展的,因此系统的任何部分都可以进行全面配置并扩展新功能。Kubernetes 的扩展点并不专注于内建资源的低级配置,如 Pods 或 StatefulSets。然而,扩展 Kubernetes 意味着扩展 Kubernetes 本身的操作。这些扩展点使得许多实践成为可能,包括创建新的 Kubernetes 资源、自动化 Kubernetes 和人类交互,以及干预资源的创建或编辑及其调度机制。
本章将介绍扩展点和模式,并涵盖最常见和最基本的扩展点。首先,将增强 Kubernetes API,并将人类知识转化为 Kubernetes 操作员的自动化。其次,将通过 Webhooks 和 Initializers 扩展 Kubernetes 的访问控制机制。最后,将以高度可定制的选项配置 Kubernetes 的默认调度器,并演示如何开发和部署自定义调度器。在这些章节中,你将能够通过创建使用 Kubernetes API 的应用程序来实现并部署扩展。
Kubernetes 扩展点
Kubernetes 本身及其内建资源是高度可配置的,因此任何现代云原生应用都可以配置在云环境中运行。在添加新功能、将人类知识转化为代码并实现更多自动化时,Kubernetes 扩展提供了帮助。幸运的是,为了扩展 Kubernetes 的功能,用户无需下载源代码、进行修改、构建和部署完整的系统。凭借其模块化,Kubernetes 的扩展点已经定义并准备好使用。
Kubernetes 扩展点专注于当前 Kubernetes 及其环境的功能。内建组件及如何扩展 Kubernetes 总结如下几类:
-
Kubernetes 客户端:通过编写
kubectl
插件,可以扩展客户端应用程序,如kubectl
。这些扩展将帮助你减少与kubectl
的人工交互,例如自动选择 Kubernetes 集群上下文。同样,使用 OpenAPI 规范生成的客户端可以扩展客户端库,如client-go
。使用这些生成的客户端,你可以在自定义应用程序中以编程方式使用 Kubernetes API。 -
Kubernetes API 类型:Kubernetes API 资源,如 Pods、Deployments 等,具有高度的可配置性,但也可以添加新的资源,称为自定义资源。
-
Kubernetes API 控制器:Kubernetes 的控制平面,包括 Kubernetes API 服务器,处理所有操作,如自动扩展或自我修复;但是,也可以开发自定义控制器。
-
访问控制器:处理身份验证、授权和准入控制的访问控制机制可以通过连接到 Webhook 服务器或介入初始化程序来扩展。
-
调度:
kube-scheduler
已经处理了将 pods 调度到节点的任务;然而,也可以创建自定义调度器并将其部署到集群中。 -
基础设施:Kubernetes 的基础设施部分是标准化的,包括服务器、网络和存储,使用容器运行时接口(CRI)、容器网络接口(CNI)和容器存储接口(CSI)。这些接口的实现提供了扩展底层 Kubernetes 集群基础设施的方法。
为了方便使用,我将前面提到的各个类别放入了以下表格中:
扩展 Kubernetes 客户端
Kubernetes 客户端应用程序和库是访问 Kubernetes API 的主要入口点。通过这些应用程序和库,可以自动化并扩展 Kubernetes 操作。
对于官方 Kubernetes 客户端应用程序,可以通过编写插件应用程序来扩展kubectl
。一些最受欢迎的插件增强了kubectl
的功能:
-
它自动切换 Kubernetes 集群上下文。
-
它计算并显示 pods 的正常运行时间信息。
-
它通过 SSH 连接到具有特定用户的容器。
官方 Kubernetes 代码生成器可以生成官方 Kubernetes 客户端库和 Kubernetes 服务器代码。这些生成器为内部版本化的类型、客户端信息器和 protobuf 编解码器创建所需的源代码。
通过客户端应用程序和库的扩展点,可以增强与 Kubernetes 交互的操作。如果你的自定义需求超出了kubectl
或客户端库的功能,Kubernetes 提供了定制化的扩展点。
扩展 Kubernetes API
Kubernetes 已经拥有丰富的资源,从 pods 作为构建块,到 stateful sets 和 deployments 等更高级别的资源。现代云原生应用可以通过 Kubernetes 资源及其高级配置选项进行部署。然而,当需要人工专业知识和操作时,这些资源并不充分。Kubernetes 使得通过新资源扩展自己的 API 成为可能,并将其作为 Kubernetes 原生对象操作,具有以下特点:
-
RESTful API:新资源直接包含在 RESTful API 中,以便通过它们的特殊端点进行访问。
-
身份验证与授权:所有新的资源请求都需要经过身份验证和授权步骤,就像本地请求一样。
-
OpenAPI 发现:可以发现新资源并将其集成到 OpenAPI 规范中。
-
客户端库:如
kubectl
或client-go
等客户端库可以用来与新资源进行交互。
扩展 Kubernetes API 时涉及两个主要步骤:
-
创建一个新的 Kubernetes 资源来引入新的 API 类型
-
控制和自动化操作,以实现作为附加 API 控制器的自定义逻辑。
自定义资源定义
在 Kubernetes 中,所有资源都有自己的 REST 端点,这些端点位于 Kubernetes API 服务器中。REST 端点通过使用/api/v1/namespaces/default/pods
来执行特定对象(如 Pods)的操作。自定义资源是 Kubernetes API 的扩展,可以在运行时动态地添加或移除。它们使集群的用户能够对扩展资源进行操作。
自定义资源是在自定义资源定义(CRD)对象中定义的。通过使用内置的 Kubernetes 资源,即 CRD,可以通过 Kubernetes API 本身来添加新的 Kubernetes API 端点。
在接下来的部分中,将为那些通常需要人工交互的 Kubernetes 需求创建一个新的自定义资源。
创建和部署自定义资源定义
假设一个客户端希望以可扩展的云原生方式在 Kubernetes 中查看天气报告。我们需要扩展 Kubernetes API,以便客户端和未来的应用程序能够本地使用天气报告资源。我们希望创建CustomResourceDefinitions
并将其部署到集群中,以检查它们的效果,并使用新定义的资源来创建扩展对象。
你可以在以下位置找到crd.yaml
文件:goo.gl/ovwFX1
。
让我们开始执行以下步骤:
- 使用 kubectl 部署自定义资源定义,执行以下命令:
kubectl apply -f k8s-operator-example/deploy/crd.yaml
自定义资源定义是 Kubernetes 资源,它们支持新自定义资源的动态注册。一个关于WeatherReport
的示例自定义资源可以在k8s-operator-example/deploy/crd.yaml
文件中定义,示例如下:
apiVersion: apiextensions.k8s.io/v1beta1
kind: CustomResourceDefinition
metadata:
name: weatherreports.k8s.packt.com
spec:
group: k8s.packt.com
names:
kind: WeatherReport
listKind: WeatherReportList
plural: weatherreports
singular: weatherreport
scope: Namespaced
version: v1
和所有其他 Kubernetes 资源一样,CRD 包含 API 版本、类型、元数据和规格组。此外,CRD 的规格包括新自定义资源的定义。对于WeatherReport
,将会在k8s.packt.com
下创建一个版本为v1
的 REST 端点,并且其复数和单数形式将在客户端中使用。
- 使用以下命令检查部署到集群中的自定义资源:
kubectl get crd
你将得到以下输出:
如上图所示,天气报告的 CRD 已经定义了复数名称和组名称。
- 检查 API 服务器中新自定义资源的 REST 端点:
kubectl proxy &
curl -s localhost:8001 |grep packt
你将得到以下输出:
创建了新的端点,这表明 Kubernetes API 服务器已经扩展,可以与我们的新自定义资源weatherreports
一起工作。
- 使用 Kubernetes 客户端(如
kubectl
)检查天气报告实例:
kubectl get weatherreports
你将得到以下输出:
尽管No resources found
的输出看起来像是错误的指示,但它告诉我们没有如预期那样的weatherreports
资源的活动实例。这告诉我们,在没有任何额外配置的情况下,仅创建CustomResourceDefinition
,Kubernetes API 服务器已通过新端点进行扩展,客户端已准备好与新的自定义资源进行交互。
在定义自定义资源后,现在可以使用WeatherReport
创建、更新和删除资源。WeatherReport
的示例如下所示,可以在k8s-operator-example/deploy/cr.yaml
文件中定义:
apiVersion: "k8s.packt.com/v1"
kind: WeatherReport
metadata:
name: amsterdam-daily
spec:
city: Amsterdam
days: 1
你可以在以下链接找到cr.yaml
文件:goo.gl/4A3VD2
。
WeatherReport
资源具有相同的结构,包含内置资源,并由 API 版本、类型、元数据和规范组成。在此示例中,规范表示该资源是针对阿姆斯特丹
城市过去 1 天的天气报告。
- 使用以下命令部署天气报告示例:
kubectl apply -f k8s-operator-example/deploy/cr.yaml
- 使用以下命令检查新创建的天气报告:
kubectl get weatherreports
你将看到以下输出:
- 使用以下命令进行清理:
kubectl delete -f k8s-operator-example/deploy/cr.yaml
kubectl delete -f k8s-operator-example/deploy/crd.yaml
自定义控制器
在上一节和练习中,我们展示了自定义资源使我们能够扩展 Kubernetes API。然而,针对自定义资源采取行动和自动化任务也是必要的。换句话说,当创建一个新的weatherreport
资源时,谁来生成天气报告并收集结果?这个问题的答案是 Kubernetes 中的自定义控制器,也被称为操作器。
借助 Kubernetes 内置资源,可以轻松部署、扩展和管理无状态的 Web 应用程序、移动后端和 API 服务。当涉及到需要额外操作的有状态应用程序时,诸如初始化、存储、备份和监控等操作需要领域知识和人类专长。
对于有状态应用程序,诸如初始化、存储、备份和监控等操作需要领域知识和人类专长。
自定义控制器,也称为操作器,是一个将领域知识和人类专长转化为代码的应用程序。操作器与自定义资源一起工作,并在自定义资源被创建、更新或删除时采取所需的行动。操作器的主要任务可以分为三个部分,观察、分析和行动,如下图所示:
各个阶段解释如下:
-
观察:监视自定义资源和相关的内置资源(如 pods)的变化。
-
分析:对观察到的变化进行分析,并决定采取哪些行动。
-
行动:根据分析和需求采取行动,并继续观察变化。
对于天气报告示例,期望的操作模式如下:
-
观察:等待天气报告资源的创建、更新和删除。
-
分析:
-
如果请求新的报告,则创建一个 pod 以收集天气报告结果并更新天气报告资源。
-
如果天气报告更新,则更新 pod 以收集新的天气报告结果。
-
如果天气报告被删除,则删除相应的 pod。
-
-
操作:对集群执行分析步骤中的操作,并继续使用观察进行监控。
操作员已经在 Kubernetes 环境中得到应用,因为它们使复杂的应用程序能够在云端运行,且需要最小的人类干预。存储提供商(Rook)、数据库应用程序(MySQL、CouchDB、PostgreSQL)、
大数据解决方案(Spark)、分布式键值存储(Consul、etcd)以及更多现代云原生应用程序,都通过其官方操作员在 Kubernetes 上安装。
操作员开发
操作员是原生 Kubernetes 应用程序,并且它们与 Kubernetes API 进行了广泛的交互。因此,符合 Kubernetes API 的要求,并通过简单明了的方式将领域知识转化为软件,对于操作员开发至关重要。考虑到这些因素,开发操作员有两条路径,具体内容将在以下章节中解释。
Kubernetes 示例控制器
在官方 Kubernetes 仓库中,维护了一个示例控制器,用于实现自定义资源的监控。该仓库展示了如何注册新的自定义资源以及如何对新资源执行基本操作,如创建、更新或列出。此外,还实现了控制器逻辑,展示了如何执行操作。该仓库与 Kubernetes API 的交互展示了如何创建类似 Kubernetes 的自定义控制器。
Operator Framework
Operator Framework 于 2018 年 KubeCon 上宣布,是一个用于管理 Kubernetes 原生应用程序的开源工具包。Operator SDK 是该框架的一部分,它通过提供更高级的 API 抽象和代码生成来简化操作员开发。Operator Framework 及其环境工具集是开源的,由社区维护,控制权归 CoreOS 所有。
本章中选择了 Operator Framework 中的 Operator SDK,因为 SDK 抽象了许多低级操作,如工作队列、处理程序注册和通知管理。通过这些抽象,使用 SDK 包处理观察和操作部分变得更加简单,这样我们可以专注于分析部分。
在以下部分,将涵盖操作员开发的完整生命周期,主要步骤如下:
-
创建操作项目:对于
WeatherReport
自定义资源,通过使用 Operator Framework SDK CLI 创建了一个 Go 语言的操作项目。 -
定义自定义资源规范:
WeatherReport
自定义资源的规范在 Go 中定义。 -
实现处理程序逻辑:手动操作所需的天气报告收集逻辑在 Go 中实现。
-
构建操作符:操作符项目使用 Operator Framework SDK CLI 构建。
-
部署操作符:操作符被部署到集群中,并通过创建自定义资源进行测试。
创建和部署 Kubernetes 操作符
一个客户希望自动化天气报告收集的操作。他们目前正在连接第三方数据提供者并获取结果。此外,他们希望在集群中使用云原生的 Kubernetes 解决方案。
我们预计通过实现一个 Kubernetes 操作符来自动化天气报告数据收集的操作。
我们将通过使用 Operator Framework SDK 创建一个 Kubernetes 操作符,并通过创建自定义资源、自定义控制器逻辑,最终部署到集群中来使用它。让我们开始实现以下步骤:
- 使用以下命令,利用 Operator Framework SDK 工具创建操作符项目:
operator-sdk new k8s-operator-example --api-version=k8s.
packt.com/v1 --kind=WeatherReport
该命令创建一个全新的 Kubernetes 操作符项目,名称为 k8s-operator-example
,并监控 WeatherReport
自定义资源的变化,该资源在 k8s.packt.com/v1
下定义。生成的操作符项目保存在 k8s-operator-example
文件夹中。
- 一个自定义资源定义已经在
deploy/crd.yaml
文件中生成。然而,自定义资源的规范被留空,供开发人员填写。自定义资源的规范和状态在 Go 中编码,如pkg/apis/k8s/v1/types.go
中所示:
type WeatherReport struct {
metav1.TypeMeta 'json:",inline"'
metav1.ObjectMeta 'json:"metadata"'
Spec WeatherReportSpec
'json:"spec"'
Status WeatherReportStatus
'json:"status,omitempty"'
}
type WeatherReportSpec struct {
City string 'json:"city"'
Days int 'json:"days"'
}
你可以参考完整的代码,网址是:goo.gl/PSyf25
。
在前面的代码片段中,WeatherReport
由 metadata
、spec
和 status
组成,就像任何内置的 Kubernetes 资源一样。WeatherReportSpec
包括配置项,在我们的示例中是 City
和 Days
。WeatherReportStatus
包括状态和 Pod,用于跟踪状态以及为天气报告收集创建的 pod。
- 操作符的一个最关键的部分是处理程序逻辑,在这里,领域知识和专业知识被转化为代码。在这个示例活动中,当创建一个新的
WeatherReport
对象时,我们将发布一个查询天气服务的 pod,并将结果写入控制台输出。所有这些步骤都在pkg/stub/handler.go
文件中编码,如下所示:
func (h *Handler) Handle(ctx types.Context, event types.Event) error {
switch o := event.Object.(type) {
case *apiv1.WeatherReport:
if o.Status.State == "" {
weatherPod := weatherReportPod(o)
err := action.Create(weatherPod)
if err != nil && !errors.IsAlreadyExists(err) {
logrus.Errorf("Failed to create weather report pod : %v", err)
你可以参考完整的代码,网址是:goo.gl/uxW4jv
。
在 Handle
函数中,处理携带对象的事件。此处理程序函数是从监视已注册对象变化的 informer 中调用的。如果对象是 WeatherReport
且其状态为空,则会创建一个新的天气报告 pod,并使用结果更新状态。
- 使用 Operator SDK 和工具集构建完整的项目作为 Docker 容器:
operator-sdk build <DOCKER_IMAGE:DOCKER_TAG>
结果 Docker 容器推送到 Docker Hub,命名为onuryilmaz/k8s-operator-example
,以便在集群中进一步使用。
- 使用以下命令将操作符部署到集群中:
kubectl create -f deploy/crd.yaml
kubectl create -f deploy/operator.yaml
成功部署操作符后,可以按如下方式检查日志:
kubectl logs -l name=k8s-operator-example
输出如下:
- 部署自定义资源定义和自定义控制器后,接下来是创建一些资源并收集结果。按照以下方式创建一个新的
WeatherReport
实例:
kubectl create -f deploy/cr.yaml
通过其成功创建,可以检查WeatherReport
的状态:
kubectl describe weatherreport amsterdam-daily
你将看到以下输出:
- 由于操作符为新的天气报告创建了一个 pod,我们应该看到它的运行并收集结果:
kubectl get pods
你将看到以下结果:
- 使用以下命令获取天气报告的结果:
kubectl logs $(kubectl get weatherreport amsterdam-daily -o jsonpath={.status.pod})
你将看到以下输出:
- 使用以下命令清理:
kubectl delete -f deploy/cr.yaml
kubectl delete -f deploy/operator.yaml
kubectl delete -f deploy/crd.yaml
Kubernetes 动态准入控制
Kubernetes API 服务器负责每个请求。API 服务器请求生命周期中的扩展点是用于动态准入控制。准入控制器是请求生命周期中最重要的阶段之一,因为它拦截并检查请求是否应该被批准。
对每个 API 请求,首先会通过身份验证和授权检查请求者。之后,准入控制器运行并决定是否批准或拒绝请求。最后,进行验证步骤,并将结果对象存储:
Kubernetes API 请求的生命周期
准入控制的动态部分源于它们可以在 Kubernetes 集群运行时动态添加、移除或更新。除了内置的准入控制器外,还有扩展准入控制器的方法:
-
用于限制集群中镜像的镜像策略 webhook
-
用于批准或拒绝创建或更新的准入 webhook
-
用于修改对象的初始化器,在对象创建之前进行更改
准入 Webhook
准入 webhook 是扩展点,API 服务器可以接收准入请求,然后返回接受或拒绝的响应。由于它们是 webhook,API 服务器期望收到 HTTP 请求和响应。支持两种类型的准入 webhook:
-
用于拒绝或接受 CRUD 请求的验证准入 webhook
-
用于更改请求以强制执行自定义默认值的变更准入 webhook
动态准入 Webhook 配置在运行时作为MutatingWebhookConfiguration
或ValidatingWebhookConfiguration
对象部署到集群中。当收到 API 请求时,API 服务器会在准入 Webhook 阶段创建必要的控制。如果为该请求定义了 Webhook 配置,准入控制器会向指定的服务器发送请求并收集响应。如果所有检查都通过,则继续进行验证和持久化步骤以处理 API 请求。
Admission Webhook 适用于所有请求类型,如创建、更新或删除,并且它们功能强大且广泛使用。然而,它们无法查询资源,因为 Webhook 不是 Kubernetes API 服务器的一部分。此外,Admission Webhook 尚未完全可用,仍在开发中。
初始化器
初始化器是 Kubernetes 工作流中的动态运行时元素,允许在资源实际创建之前对其进行修改。换句话说,初始化器允许开发人员干预并对资源进行任何更改,
例如部署或 Pod,并包括自定义修改逻辑,以便在 Kubernetes 资源生命周期中使用。
初始化器的一些可能使用案例如下:
-
注入侧车容器
-
注入带有证书的卷
-
防止创建违反自定义限制的资源
初始化器是动态控制器,它们在运行时通过InitializerConfiguration
资源进行定义或移除。InitializerConfiguration
将一组资源和初始化器结合在一起,以便当创建匹配的资源时,API 服务器将相应的初始化器添加到资源定义中。
初始化器的列表保存在metadata.initializers.pending
字段中。另一方面,初始化器始终在监视新的资源,以便它们可以对对象实现自定义逻辑。当Initializer X在第一个位置,即metadata.initializers.pending[0]
时,Initializer X获取资源和修改器。然后,它将自己,Initializer X,从metadata.initializers.pending
列表中移除,以便下一个初始化器可以工作。当所有初始化器完成其操作并且列表为空时,资源被释放并继续创建生命周期。
初始化器易于开发,并且是扩展准入控制机制的极其灵活的方式。然而,初始化器的正常运行时间至关重要,因为它们会阻塞 API 服务器。此外,初始化器尚未完全可用,仍在开发中。
扩展 Kubernetes 调度器
Pod 是 Kubernetes 调度运行在节点上的基本工作单元。默认情况下,Kubernetes 具有内置调度器,它通过确保有足够的空闲资源来尽量均匀地将 Pod 分配到节点上。针对可扩展和可靠的云原生应用的自定义需求,有一些使用场景需要配置和扩展 Kubernetes 的调度器行为:
-
在专用硬件上运行特定的 Pod。
-
将一些 Pod 与交互服务共同部署。
-
将一些节点专门分配给特定用户。
调度器定制和扩展模式,从基础到复杂,列举如下:
-
分配节点标签并使用节点选择器。
-
使用亲和性和反亲和性规则。
-
为节点标记污点,并为 Pod 配置容忍度。
-
创建和部署自定义调度算法。
节点标签。
调度的基本概念是基于 Kubernetes 中节点的标签。内置调度器以及任何自定义调度器都需要根据节点的标签来检查节点的规格。基于这一概念,存在一些
集成的节点标签,例如以下标签:
kubernetes.io/hostname
failure-domain.beta.kubernetes.io/zone
failure-domain.beta.kubernetes.io/region
beta.kubernetes.io/instance-type
beta.kubernetes.io/os
beta.kubernetes.io/arch
这些标签及其值由云服务提供商分配,但请注意,标签值尚未标准化。对于 Minikube,只有一个主节点,可以通过以下命令检查其标签:
$ kubectl get nodes --show-labels
NAME STATUS ROLES AGE VERSION LABELS
minikube Ready master 9m v1.10.0 beta.
kubernetes.io/arch=amd64,beta.kubernetes.io/os=linux,kubernetes.
io/hostname=minikube, node-role.kubernetes.io/master=
如上所述,主机名为minikube
的节点,其架构为 amd64,操作系统为linux
,并且node-role
为master
。
节点选择器。
节点选择器是可以与 Kubernetes 调度器一起使用的最简单的约束条件。节点选择器是 Pod 规格的一部分,它们是键值对映射。节点选择器的键需要与节点标签匹配。
这些值是调度器的约束条件。
它们包含在 Pod 规格中,如下所示:
apiVersion: v1
kind: Pod
metadata:
name: nginx
spec:
containers:
- name: nginx
image: nginx
nodeSelector:
beta.kubernetes.io/arch: amd64
使用该 Pod 定义后,Kubernetes 调度器会限制将 Pod nginx
分配给架构为amd64
的节点。如果没有符合约束的节点,Pod 将处于 Pending 状态,直到有满足条件的节点出现。
限制加入集群。
节点亲和性。
节点亲和性是比nodeSelector
更具表现力的节点选择器规范,它包含了两组约束条件:
-
requiredDuringSchedulingIgnoredDuringExecution
:这组约束表示在调度 Pod 到节点之前必须满足的条件。这组约束类似于nodeSelector
,但是它支持更灵活的定义。 -
preferredDuringSchedulingIgnoredDuringExecution
:这组约束表示在调度时优先考虑的条件,但不保证满足。
简而言之,第一组包含了调度器的硬性限制,而第二组则包含了软性限制。IgnoredDuringExecution
部分表示,如果标签在运行时发生变化且约束未满足,调度器将不做任何更改。
使用这些节点亲和性规则,可以轻松定义复杂的规则以限制调度器。例如,在下面的 pod 定义中,使用 requiredDuringSchedulingIgnoredDuringExecution
组,pods 被限制仅在 PowerPC 环境中运行。此外,使用 preferredDuringSchedulingIgnoredDuringExecution
组时,pods 会尽可能尝试在可用区 A 的节点上运行:
apiVersion: v1
...
requiredDuringSchedulingIgnoredDuringExecution:
...
spec:
affinity:
- key: kubernetes.io/arch
operator: In
values:
- ppc64_le
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 1
preference:
matchExpressions:
- key: failure-domain.beta.kubernetes.io/zone
operator: In
values:
- availability-zone-a
Pod 亲和性
上一节中的节点亲和性规则定义了 pod 和节点的分配关系。它们描述了一组限制条件,要求 pods 只能在一组节点上运行。采用相同的方法,Pod 间亲和性和反亲和性规则可以根据其他 pods 定义约束。例如,使用 pod 亲和性规则时,pods 可以在有限的节点集上一起调度。同样,使用 pod 反亲和性规则时,pods 可以基于特定的拓扑键(例如,节点)相互排斥。对于 pod 亲和性,可以使用 requiredDuringSchedulingIgnoredDuringExecution
和 preferredDuringSchedulingIgnoredDuringExecution
定义硬性和软性限制。
在以下 pod 定义中,pod 亲和性确保 pods 仅在同一可用区的节点上运行,即具有 service=backend
标签的 pods。换句话说,亲和性规则会尽量确保我们的 pod 被调度到同一可用区,与后端服务一起运行,因为它们正在相互交互。使用 pod 反亲和性时,调度器将尽量避免在已经运行 service=backend
标签的节点上运行 pods。换句话说,若可能,它们将不会与后端服务调度到相同节点,以避免单点故障的出现:
apiVersion: v1
...
podAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
...
spec:
affinity:
- key: service
operator: In
values:
- backend
topologyKey: failure-domain.beta.kubernetes.io/zone podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- key: service
operator: In
values:
- backend
topologyKey: kubernetes.io/hostname
污点和容忍
亲和性规则为调度器定义了约束条件,使其能够将 pods 调度到合适的节点上。另一方面,Kubernetes 提供了一种通过污点和容忍从节点的角度拒绝 pods 的方式。污点和容忍协同工作,以确保一组 pods 不会被调度到一组节点上。污点应用于节点,用于拒绝某些 pods,而容忍则允许 pod 在某些节点上接受调度。
污点为节点打上标签,标记为“不可调度”。例如,使用以下命令,除非为键和值定义了匹配的容忍,否则不会将任何 pods 调度到 nodeA
:
kubectl taint nodes nodeA key=value:NoSchedule
容忍为 pods 打上标签,以确保污点不会应用到这些 pods。例如,在 pod 规范中使用以下容忍,前面的污点将不会被应用:
apiVersion: v1
kind: Pod
metadata:
name: with-pod-toleration
spec:
tolerations:
- key: "key"
operator: "Equal"
value: "value"
effect: "NoSchedule"
containers:
- name: with-pod-toleration
image: k8s.gcr.io/pause:2.0
容忍和污点协同工作,使节点可以带有某些用户组或特定标签,容忍则可以在 pod 定义中为以下使用场景定义:
-
专用节点
-
带有特殊硬件的节点
-
基于污点的驱逐行为,以应对节点故障
自定义调度器开发
Kubernetes 调度器可以通过节点选择器、节点亲和性、pod 亲和性、污点和容忍规则进行高度配置。在有自定义调度需求的情况下,也可以开发并部署自定义调度器。
Kubernetes 集群。Kubernetes 默认支持运行多个调度器。Kubernetes 中的自定义调度器可以使用任何编程语言开发。然而,由于它将与 Kubernetes API 广泛交互,通常使用具有 Kubernetes 客户端库的编程语言:
通常使用具有 Kubernetes 客户端库的编程语言:
调度器的基本工作流可以分为三个主要的连续阶段。调度器等待具有特定调度器名称且未分配节点的 pod。当找到这样的 pod 时,调度器运行其自定义算法以找到合适的节点。最后,调度器创建一个绑定,它是 Kubernetes 中 pod 的内置子资源。
使用 Go 实现的自定义调度器位于 k8s-scheduler-example/main.go
文件中,以下代码片段将 等待、查找合适的节点 和 创建 pod 绑定 阶段组合在一起:
for {
// Request pods from all namespaces
pods, err := clientset.CoreV1().Pods(v1.NamespaceAll).List(metav1.ListOptions{})
...
// Check for pods
for _, pod := range pods.Items {
// If scheduler name is set and node is not assigned
if pod.Spec.SchedulerName == *schedulerName && pod.Spec.
NodeName == "" {
// Schedule the pod to a random node
err := schedule(pod.Name, randomNode(), pod.Namespace)
...
}
}
...
}
以下代码片段中的 schedule
函数用于在 pod 和节点之间创建绑定。Bind
方法在函数的最后一行调用,它位于 clientset
中的 pod 下,因为它是 pod 的一个子资源:
func schedule(pod, node, namespace string) error {
fmt.Printf("Assigning %s/%s to %s\n", namespace, pod, node)
// Create a binding with pod and node
binding := v1.Binding{
ObjectMeta: metav1.ObjectMeta{
Name: pod,
},
Target: v1.ObjectReference{
Kind: "Node",
APIVersion: "v1",
Name: node,
}}
return clientset.CoreV1().Pods(namespace).Bind(&binding)
}
这个自定义调度器会将节点随机分配给名为 packt-scheduler
的 pod。构建文件和文档已在 k8s-scheduler-example
文件夹下提供,并准备好部署到集群中。在接下来的部分中,将介绍在 Kubernetes 集群中部署和使用多个调度器。
部署并使用自定义 Kubernetes 调度器
假设有一个客户端拥有一个 Kubernetes 集群,并需要为带有预定义标签的 pod 配置一个额外的调度器。新的调度器应与内置调度器并行工作,并且需要部署到集群中。我们将部署并使用一个自定义的 Kubernetes 调度器,并检查调度器在集群中的工作情况。在部署 Kubernetes 调度器之前,需要确保完成以下步骤:
-
使用本练习中的随机分配调度器。
-
调度器容器已经存在于 Docker hub 上:
onuryilmaz/k8sscheduler-example
。 -
使用
packt-scheduler
作为自定义调度器名称。 -
如果自定义调度器未运行,则显示 pod 的状态。
你可以在以下链接找到 pod.yaml
文件:goo.gl/aCRppt
。
让我们从实现开始:
- 创建一个使用自定义调度器名称的 pod,名称定义为
packt-scheduler
:
kubectl apply -f k8s-scheduler-example/deploy/pod.yaml
部署 pod 后,可以检查其状态:
kubectl get pods
你应该看到以下输出:
由于没有名为 packt-scheduler
的调度器部署到集群中,因此其状态将永远停留在 Pending
。
- 使用以下命令将调度器部署到集群中:
kubectl apply -f k8s-scheduler-example/deploy/scheduler.yaml
您可以在以下地址找到 scheduler.yaml
文件:goo.gl/AaSu8o
。
- 使用以下命令检查 Pods:
kubectl get pods
您将获得以下输出:
如前所示,调度器在一个 Pod 中运行,并且此前处于 Pending 状态的 nginx
Pod,现在已经显示为 Running
状态。
- 此外,请检查调度器的日志:
kubectl logs scheduler
您将获得以下输出:
- 使用以下命令进行清理:
kubectl delete -f k8s-scheduler-example/deploy/pod.yaml
kubectl delete -f k8s-scheduler-example/deploy/scheduler.yaml
扩展 Kubernetes 基础设施
Kubernetes 集群运行在实际的裸金属集群上,并与运行在服务器上的基础设施系统进行交互。基础设施的扩展点仍处于设计阶段,尚不成熟,无法进行标准化。然而,它们可以按如下方式分组:
-
服务器:Kubernetes 节点组件与 Docker 等容器运行时进行交互。目前,Kubernetes 被设计为可以与任何实现 容器运行时接口 (CRI) 规范的容器运行时一起工作。CRI 包括库、协议缓冲区和 gRPC API,用于定义 Kubernetes 与容器环境之间的交互。
-
网络:Kubernetes 和容器架构需要高性能的网络,且与容器运行时解耦。容器与网络接口之间的连接通过 容器网络接口 (CNI) 的抽象进行定义。CNI 包含一组接口,用于将容器添加到或从 Kubernetes 网络中移除。
-
存储:Kubernetes 资源的存储由与云提供商或主机系统通信的存储插件提供。例如,运行在 AWS 上的 Kubernetes 集群可以轻松地从 AWS 获取存储并附加到其有状态集合。包括存储提供和在容器运行时中消费的操作在 容器存储接口 (CSI) 下进行了标准化。在 Kubernetes 中,任何实现 CSI 的存储插件都可以作为存储提供者使用。
Kubernetes 的基础设施可以扩展,以便与实现 CRI 的服务器、符合 CNI 的网络提供商和实现 CSI 的存储提供商一起工作。
总结
本章讨论了扩展 Kubernetes,我们启用了将领域知识转化为自动化并介入 Kubernetes 操作的能力。首先,介绍了 Kubernetes 中的扩展点,展示了其内置的扩展功能。
功能。在本章中,Kubernetes API 添加了新的资源,并自动化了它们的操作,以便 Kubernetes 除了支持内置资源外,还能为自定义资源提供服务。接着,资源创建逻辑通过动态准入控制器进行了扩展,展示了如何将操作要求纳入 Kubernetes API 资源生命周期。
最后,展示了 Kubernetes 调度器的配置,涵盖了所有广泛的
节点和 Pod 之间关系的要求。如何编写、部署和使用自定义调度器也有展示。通过本章中包含的扩展功能,可以将 Kubernetes 用作容器编排器,不仅如此,还可以作为一个
平台,能够处理云原生应用程序的所有自定义需求。
本书介绍了 Kubernetes 设计模式和扩展,从其基础到在云原生微服务架构中的实现。首先,在第一章中,涵盖了 Kubernetes 的最佳实践。设计模式及其在 Kubernetes 云原生架构中的反映得到了说明,以便创建最佳实践知识。在第二章中,展示了如何以编程方式连接到 Kubernetes。关于客户端库的实操活动旨在为与 Kubernetes 通信的应用程序做好准备。这些 Kubernetes API 消费应用程序将帮助你与 Kubernetes 深度互动,实现比普通 Kubernetes 用户更多的功能。最后一章,讲解了 Kubernetes 的扩展点。Kubernetes 扩展点使得将领域专长转化为自动化并介入 Kubernetes 操作成为可能。通过本章中包含的扩展能力,可以将 Kubernetes 用作容器编排器,不仅如此,还可以作为一个平台,能够处理云原生应用程序复杂的需求。
第四章:解决方案
本节包含了每个课时活动的解答。请注意,对于描述性问题,您的答案可能与本节提供的答案不完全一致。只要答案的本质保持相同,就可以认为它们是正确的。
第一章:Kubernetes 设计模式
以下是本章活动的解决方案。
活动:运行带有同步的 Web 服务器
在sidecar.yaml
文件中,提供了一个包含两个容器的 Pod 定义,分别为server
和sync
。在 server 容器中,httpd 在 80 端口提供源卷服务。在 sync 容器中,git 每 30 秒运行一次以同步源卷。这两个容器独立工作;然而,它们共享源卷来实现文件同步:
apiVersion: v1
kind: Pod
metadata:
name: sidecar
spec:
containers:
...
volumes:
- emptyDir: {}
name: source
按照以下步骤获取解决方案:
- 使用以下命令创建 Pod:
kubectl apply -f sidecar.yaml
- 检查名为 sidecar 的 Pod 是否准备好:
kubectl get pod sidecar
- 当 Pod 准备好时,检查同步 sidecar 容器的日志:
kubectl logs sidecar -c sync
- 使用以下命令将 Pod 的服务器端口转发到 localhost:
kubectl port-forward sidecar 8000:80
- 在浏览器中打开
localhost:8000
。预期看到 2048 游戏。
活动:在内容准备后运行 Web 服务器
在init-container.yaml
文件中,提供了一个包含初始化容器和主容器的 Pod 定义,分别为content
和server
。在 content 容器中,"Welcome from Packt"
被写入索引文件。在 server 容器中,nginx 在 80 端口提供源卷服务。这两个容器独立工作;然而,它们共享workdir
卷来进行文件准备:
apiVersion: v1
kind: Pod
metadata:
name: init-container
spec:
initContainers:
-
...
volumes:
- name: workdir
emptyDir: {}
按照以下步骤获取解决方案:
- 使用以下命令创建 Pod:
kubectl apply -f init-container.yaml
- 检查初始化容器的状态:
kubectl describe pod init-container
- 当 Pod 正在运行时,使用以下命令将 Pod 的服务器端口转发到 localhost:
kubectl port-forward init-container 8000:80
- 在另一个终端中,使用以下命令检查服务器的内容:
curl localhost:8000
- 运行以下命令进行清理:
kubectl delete deployment go-client
活动:向应用程序注入数据
在inject.yaml
文件中,有一个 Pod 定义,其中的容器每 10 秒记录一次运行时信息。为内存和 CPU 定义了资源请求和限制,用于利用率和性能信息。
环境变量通过valueFrom
块定义,表示在运行时填充的值:
按照以下步骤获取解决方案:
- 使用以下命令创建 Pod:
kubectl apply -f inject.yaml
- 检查 inject Pod 是否准备好:
kubectl get pod inject
- 当 Pod 准备好时,检查日志:
kubectl logs inject
第二章:Kubernetes 客户端库
以下是本章活动的解决方案。
活动:在集群内使用 Kubernetes Go 客户端
- 使用前一个练习中的示例客户端 Docker 镜像创建一个部署:
kubectl run go-client --image=onuryilmaz/k8s-clientexample:go
- 使用以下命令等待 Pod 启动:
kubectl get pods -w
- 使用以下命令获取部署 Pod 的日志:
kubectl logs $(kubectl get pods --selector run=go-client -o jsonpath="{.items[0].metadata.name}")