精通Kubernetes全绝不原创的飞龙

Kubernetes是一个开源系统,自动化部署、扩展和管理容器化应用程序。如果您运行的不仅仅是一些容器,或者想要自动化管理容器,您就需要Kubernetes。本书重点介绍了如何通过高级管理Kubernetes集群。

本书首先解释了Kubernetes架构背后的基本原理,并详细介绍了Kubernetes的设计。您将了解如何在Kubernetes上运行复杂的有状态微服务,包括水平Pod自动缩放、滚动更新、资源配额和持久存储后端等高级功能。通过真实的用例,您将探索网络配置的选项,并了解如何设置、操作和排除各种Kubernetes网络插件。最后,您将学习自定义资源开发和在自动化和维护工作流中的利用。本书还将涵盖基于Kubernetes1.10发布的一些额外概念,如Promethus、基于角色的访问控制和API聚合。

通过本书,您将了解从中级到高级水平所需的一切。

本书适用于具有Kubernetes中级知识水平的系统管理员和开发人员,现在希望掌握其高级功能。您还应该具备基本的网络知识。这本高级书籍为掌握Kubernetes提供了一条路径。

为了跟随每一章的示例,您需要在您的计算机上安装最新版本的Docker和Kubernetes,最好是Kubernetes1.10。如果您的操作系统是Windows10专业版,您可以启用hypervisor模式;否则,您需要安装VirtualBox并使用Linux客户操作系统。

本书中使用了许多文本约定。

CodeInText:表示文本中的代码词、数据库表名、文件夹名、文件名、文件扩展名、路径名、虚拟URL、用户输入和Twitter句柄。这是一个例子:“让我们使用getnodes来检查集群中的节点。”

代码块设置如下:

typeSchedulerstruct{config*Config}任何命令行输入或输出都以以下方式编写:

>kubectlcreate-fcandy.yamlcandy"chocolate"created粗体:表示新术语、重要单词或屏幕上看到的单词。例如,菜单或对话框中的单词会以这种方式出现在文本中。这是一个例子:“让我们点击kubednspod。”

警告或重要提示会以这种方式出现。提示和技巧会以这种方式出现。

Kubernetes是一个庞大的开源项目和生态系统,拥有大量的代码和功能。Kubernetes由谷歌开发,但加入了CloudNativeComputingFoundation(CNCF),成为容器应用领域的明确领导者。简而言之,它是一个用于编排基于容器的应用程序部署、扩展和管理的平台。您可能已经了解过Kubernetes,甚至在一些项目中使用过它,甚至在工作中使用过它。但要理解Kubernetes的全部内容,如何有效使用它以及最佳实践是什么,需要更多的知识。在本章中,我们将建立必要的知识基础,以充分利用Kubernetes的潜力。我们将首先了解Kubernetes是什么,Kubernetes不是什么,以及容器编排的确切含义。然后,我们将介绍一些重要的Kubernetes概念,这些概念将构成我们在整本书中将使用的词汇。之后,我们将更详细地深入了解Kubernetes的架构,并看看它如何为用户提供所有这些功能。然后,我们将讨论Kubernetes支持的各种运行时和容器引擎(Docker只是其中一种选择),最后,我们将讨论Kubernetes在完整的持续集成和部署流水线中的作用。

在本章结束时,您将对容器编排有扎实的了解,了解Kubernetes解决了哪些问题,Kubernetes设计和架构的基本原理,以及它支持的不同运行时。您还将熟悉开源存储库的整体结构,并准备好随时跳入并找到任何问题的答案。

Kubernetes是一个涵盖大量服务和功能的平台,不断增长。它的核心功能是在您的基础设施中安排容器工作负载,但它并不止步于此。以下是Kubernetes带来的其他一些功能:

挂载存储系统

分发密钥

检查应用程序健康状况

复制应用程序实例

使用水平Pod自动缩放

命名和发现

负载均衡

滚动更新

监控资源

访问和摄取日志

调试应用程序

Kubernetes不是平台即服务(PaaS)。它不规定您所需系统的许多重要方面;相反,它将它们留给您或其他构建在Kubernetes之上的系统,如Deis、OpenShift和Eldarion。例如:

Kubernetes不需要特定的应用程序类型或框架

Kubernetes不需要特定的编程语言

Kubernetes不提供数据库或消息队列

Kubernetes不区分应用程序和服务

Kubernetes没有点击即部署的服务市场

Kubernetes允许用户选择自己的日志记录、监控和警报系统

Kubernetes的主要责任是容器编排。这意味着确保执行各种工作负载的所有容器都被安排在物理或虚拟机上运行。容器必须被有效地打包,并遵循部署环境和集群配置的约束。此外,Kubernetes必须监视所有运行的容器,并替换死掉的、无响应的或其他不健康的容器。Kubernetes提供了许多其他功能,您将在接下来的章节中了解到。在本节中,重点是容器及其编排。

一切都始于硬件,也以硬件结束。为了运行您的工作负载,您需要一些真实的硬件。这包括实际的物理机器,具有一定的计算能力(CPU或核心)、内存和一些本地持久存储(旋转磁盘或固态硬盘)。此外,您还需要一些共享的持久存储和网络,以连接所有这些机器,使它们能够找到并相互通信。在这一点上,您可以在物理机器上运行多个虚拟机,或者保持裸金属级别(没有虚拟机)。Kubernetes可以部署在裸金属集群(真实硬件)或虚拟机集群上。反过来,Kubernetes可以在裸金属或虚拟机上直接编排它管理的容器。理论上,Kubernetes集群可以由裸金属和虚拟机的混合组成,但这并不常见。

容器代表了大型复杂软件系统开发和运行中的真正范式转变。以下是与传统模型相比的一些好处:

敏捷的应用程序创建和部署

持续开发、集成和部署

在开发、测试和生产环境中保持环境一致性

云和操作系统的可移植性

以应用为中心的管理

松散耦合、分布式、弹性、自由的微服务

资源隔离

资源利用

容器非常适合打包微服务,因为它们在为微服务提供隔离的同时非常轻量,并且在部署许多微服务时不会产生很多开销,就像使用虚拟机一样。这使得容器非常适合云部署,因为为每个微服务分配整个虚拟机的成本是禁止的。

所有主要的云服务提供商,如亚马逊AWS、谷歌的GCE、微软的Azure,甚至阿里巴巴云,现在都提供容器托管服务。谷歌的GKE一直以来都是基于Kubernetes。AWSECS基于他们自己的编排解决方案。微软Azure的容器服务是基于ApacheMesos的。Kubernetes可以部署在所有云平台上,但直到今天它才没有与其他服务深度集成。但在2017年底,所有云服务提供商宣布直接支持Kubernetes。微软推出了AKS,AWS发布了EKS,阿里巴巴云开始开发一个Kubernetes控制器管理器,以无缝集成Kubernetes。

在过去,当系统规模较小时,每台服务器都有一个名字。开发人员和用户清楚地知道每台机器上运行的软件是什么。我记得,在我工作过的许多公司中,我们经常讨论几天来决定服务器的命名主题。例如,作曲家和希腊神话人物是受欢迎的选择。一切都非常舒适。你对待你的服务器就像珍爱的宠物一样。当一台服务器死掉时,这是一场重大危机。每个人都争先恐后地想弄清楚从哪里获取另一台服务器,死掉的服务器上到底运行了什么,以及如何在新服务器上让它工作。如果服务器存储了一些重要数据,那么希望你有最新的备份,也许你甚至能够恢复它。

显然,这种方法是不可扩展的。当你有几十台或几百台服务器时,你必须开始像对待牲畜一样对待它们。你考虑的是整体,而不是个体。你可能仍然有一些宠物,但你的Web服务器只是牲畜。

在这一部分,我们涵盖了容器编排的概念,并讨论了主机(物理或虚拟)和容器之间的关系,以及在云中运行容器的好处,并最后讨论了牲畜与宠物的区别。在接下来的部分,我们将了解Kubernetes的世界,并学习它的概念和术语。

在这一部分,我将简要介绍许多重要的Kubernetes概念,并为您提供一些背景,说明它们为什么需要以及它们如何与其他概念互动。目标是熟悉这些术语和概念。稍后,我们将看到这些概念如何被编织在一起,并组织成API组和资源类别,以实现令人敬畏的效果。你可以把许多这些概念看作是构建块。一些概念,比如节点和主节点,被实现为一组Kubernetes组件。这些组件处于不同的抽象级别,我会在专门的部分*Kubernetes组件*中详细讨论它们。

这是著名的Kubernetes架构图:

集群是Kubernetes用来运行组成系统的各种工作负载的计算、存储和网络资源的集合。请注意,您的整个系统可能由多个集群组成。我们将在后面详细讨论联邦的这种高级用例。

主节点是Kubernetes的控制平面。它由多个组件组成,如API服务器、调度程序和控制器管理器。主节点负责全局的集群级别的pod调度和事件处理。通常,所有主节点组件都设置在单个主机上。在考虑高可用性场景或非常大的集群时,您会希望有主节点冗余。我将在第四章中详细讨论高可用性集群,高可用性和可靠性。

Pod是Kubernetes中的工作单位。每个pod包含一个或多个容器。Pod总是一起调度(即它们总是在同一台机器上运行)。Pod中的所有容器具有相同的IP地址和端口空间;它们可以使用localhost或标准的进程间通信进行通信。此外,pod中的所有容器都可以访问托管pod的节点上的共享本地存储。共享存储可以挂载在每个容器上。Pod是Kubernetes的一个重要特性。通过在单个Docker容器中运行多个应用程序,例如通过将supervisord作为运行多个进程的主要Docker应用程序,可以实现这种做法,但出于以下原因,这种做法经常受到指责:

透明度:使得pod内的容器对基础设施可见,使得基础设施能够为这些容器提供服务,如进程管理和资源监控。这为用户提供了许多便利的功能。

解耦软件依赖关系:单个容器可以独立进行版本控制、重建和重新部署。Kubernetes可能会支持单个容器的实时更新。

易用性:用户不需要运行自己的进程管理器,担心信号和退出代码的传播等问题。

效率:由于基础设施承担了更多的责任,容器可以更轻量化。

标签是用于将一组对象(通常是pod)分组在一起的键值对。这对于其他一些概念非常重要,比如复制控制器、副本集和操作动态对象组并需要识别组成员的服务。对象和标签之间存在NxN的关系。每个对象可能有多个标签,每个标签可能应用于不同的对象。标签有一些设计上的限制。对象上的每个标签必须具有唯一的键。标签键必须遵守严格的语法。它有两部分:前缀和名称。前缀是可选的。如果存在,则它与名称之间用斜杠(/)分隔,并且必须是有效的DNS子域。前缀最多可以有253个字符。名称是必需的,最多可以有63个字符。名称必须以字母数字字符(a-z,A-Z,0-9)开头和结尾,并且只能包含字母数字字符、点、破折号和下划线。值遵循与名称相同的限制。请注意,标签专用于识别对象,而不是附加任意元数据到对象。这就是注释的作用(请参见下一节)。

注释允许您将任意元数据与Kubernetes对象关联起来。Kubernetes只存储注释并提供它们的元数据。与标签不同,它们对允许的字符和大小限制没有严格的限制。

根据我的经验,对于复杂的系统,你总是需要这样的元数据,很高兴Kubernetes认识到了这个需求,并且提供了这个功能,这样你就不必自己想出一个单独的元数据存储并将对象映射到它们的元数据。

我们已经涵盖了大多数,如果不是全部,Kubernetes的概念;我简要提到了一些其他概念。在下一节中,我们将继续探讨Kubernetes的架构,深入了解其设计动机、内部和实现,甚至研究源代码。

标签选择器用于根据它们的标签选择对象。基于相等性的选择器指定键名和值。有两个运算符,=(或==)和!=,表示基于值的相等性或不相等性。例如:

role=webserver这将选择所有具有该标签键和值的对象。

标签选择器可以有多个要求,用逗号分隔。例如:

role=webserver,application!=foo基于集合的选择器扩展了功能,并允许基于多个值进行选择:

rolein(webserver,backend)复制控制器和副本集复制控制器和副本集都管理由标签选择器标识的一组pod,并确保某个特定数量始终处于运行状态。它们之间的主要区别在于,复制控制器通过名称相等来测试成员资格,而副本集可以使用基于集合的选择。副本集是更好的选择,因为它们是复制控制器的超集。我预计复制控制器在某个时候会被弃用。

Kubernetes保证您始终会有与复制控制器或副本集中指定的相同数量的运行中的pod。每当数量因托管节点或pod本身的问题而下降时,Kubernetes都会启动新的实例。请注意,如果您手动启动pod并超出指定数量,复制控制器将会终止额外的pod。

复制控制器曾经是许多工作流程的核心,比如滚动更新和运行一次性作业。随着Kubernetes的发展,它引入了对许多这些工作流程的直接支持,使用了专门的对象,比如Deployment、Job和DaemonSet。我们稍后会遇到它们。

服务用于向用户或其他服务公开某种功能。它们通常包括一组pod,通常由标签标识。您可以拥有提供对外部资源或直接在虚拟IP级别控制的pod的访问权限的服务。原生Kubernetes服务通过便捷的端点公开。请注意,服务在第3层(TCP/UDP)操作。Kubernetes1.2添加了Ingress对象,它提供对HTTP对象的访问——稍后会详细介绍。服务通过DNS或环境变量之一进行发布或发现。Kubernetes可以对服务进行负载均衡,但开发人员可以选择在使用外部资源或需要特殊处理的服务的情况下自行管理负载均衡。

Pod上的本地存储是临时的,并随着Pod的消失而消失。有时这就是您所需要的,如果目标只是在节点的容器之间交换数据,但有时对数据的存活超过Pod或者需要在Pod之间共享数据是很重要的。卷的概念支持这种需求。请注意,虽然Docker也有卷的概念,但它相当有限(尽管它变得更加强大)。Kubernetes使用自己独立的卷。Kubernetes还支持其他容器类型,如rkt,因此即使原则上也不能依赖Docker卷。

有许多卷类型。Kubernetes目前直接支持许多卷类型,但通过容器存储接口(CSI)来扩展Kubernetes的现代方法是我稍后会详细讨论的。emptyDir卷类型在每个容器上挂载一个卷,该卷默认由主机上可用的内容支持。如果需要,您可以请求内存介质。当Pod因任何原因终止时,此存储将被删除。针对特定云环境、各种网络文件系统甚至Git存储库有许多卷类型。一个有趣的卷类型是persistentDiskClaim,它稍微抽象了一些细节,并使用环境中的默认持久存储(通常在云提供商中)。

可用于DNS的稳定主机名

序数索引

StatefulSet可以帮助进行对等发现,以及添加或删除宠物。

Secrets是包含敏感信息(如凭据和令牌)的小对象。它们存储在etcd中,可以被KubernetesAPI服务器访问,并且可以被挂载为文件到需要访问它们的pod中(使用专用的秘密卷,这些卷依附在常规数据卷上)。同一个秘密可以被挂载到多个pod中。Kubernetes本身为其组件创建秘密,您也可以创建自己的秘密。另一种方法是将秘密用作环境变量。请注意,pod中的秘密始终存储在内存中(在挂载秘密的情况下为tmpfs),以提高安全性。

Kubernetes中的每个对象都由UID和名称标识。名称用于在API调用中引用对象。名称应该长达253个字符,并使用小写字母数字字符、破折号(-)和点(.)。如果删除一个对象,您可以创建另一个具有与已删除对象相同名称的对象,但UID必须在集群的生命周期内是唯一的。UID是由Kubernetes生成的,所以您不必担心这个问题。

命名空间是一个虚拟集群。您可以拥有一个包含多个由命名空间隔离的虚拟集群的单个物理集群。每个虚拟集群与其他虚拟集群完全隔离,它们只能通过公共接口进行通信。请注意,node对象和持久卷不属于命名空间。Kubernetes可能会调度来自不同命名空间的pod在同一节点上运行。同样,来自不同命名空间的pod可以使用相同的持久存储。

在使用命名空间时,您必须考虑网络策略和资源配额,以确保对物理集群资源的适当访问和分配。

Kubernetes有非常雄心勃勃的目标。它旨在管理和简化在各种环境和云提供商中的分布式系统的编排、部署和管理。它提供了许多能力和服务,应该能够在所有这些多样性中工作,同时演变并保持足够简单,以便普通人使用。这是一个艰巨的任务。Kubernetes通过遵循清晰的高级设计,并使用深思熟虑的架构来实现这一目标,促进可扩展性和可插拔性。Kubernetes的许多部分仍然是硬编码或环境感知的,但趋势是将它们重构为插件,并保持核心的通用性和抽象性。在本节中,我们将像剥洋葱一样剥开Kubernetes,从各种分布式系统设计模式开始,以及Kubernetes如何支持它们,然后介绍Kubernetes的机制,包括其一套API,然后看一下组成Kubernetes的实际组件。最后,我们将快速浏览源代码树,以更好地了解Kubernetes本身的结构。

在本节结束时,您将对Kubernetes的架构和实现有扎实的了解,以及为什么会做出某些设计决策。

所有快乐(工作)的分布式系统都是相似的,借用托尔斯泰在《安娜·卡列尼娜》中的话。这意味着,为了正常运行,所有设计良好的分布式系统都必须遵循一些最佳实践和原则。Kubernetes不只是想成为一个管理系统。它希望支持和促进这些最佳实践,并为开发人员和管理员提供高级服务。让我们来看看其中一些设计模式。

边车模式是指在一个pod中除了主应用容器之外,还有另一个容器。应用容器对边车容器一无所知,只是按照自己的业务进行操作。一个很好的例子是中央日志代理。你的主容器可以直接记录到stdout,但是边车容器会将所有日志发送到一个中央日志服务,这样它们就会与整个系统的日志聚合在一起。使用边车容器与将中央日志添加到主应用容器中相比的好处是巨大的。首先,应用不再被中央日志所拖累,这可能会很麻烦。如果你想升级或更改你的中央日志策略,或者切换到一个全新的提供者,你只需要更新边车容器并部署它。你的应用容器都不会改变,所以你不会意外地破坏它们。

大使模式是指将远程服务表示为本地服务,并可能强制执行某种策略。大使模式的一个很好的例子是,如果你有一个Redis集群,有一个主节点用于写入,还有许多副本用于读取。一个本地的大使容器可以作为代理,将Redis暴露给主应用容器在本地主机上。主应用容器只需连接到localhost:6379(Redis的默认端口),但它连接到在同一个pod中运行的大使,大使会过滤请求,将写请求发送到真正的Redis主节点,将读请求随机发送到其中一个读取副本。就像我们在边车模式中看到的一样,主应用并不知道发生了什么。这在对真实的本地Redis进行测试时会有很大帮助。此外,如果Redis集群配置发生变化,只需要修改大使;主应用仍然毫不知情。

适配器模式是关于标准化主应用程序容器的输出。考虑一个逐步推出的服务的情况:它可能生成的报告格式与以前的版本不符。消费该输出的其他服务和应用程序尚未升级。适配器容器可以部署在与新应用程序容器相同的pod中,并可以修改其输出以匹配旧版本,直到所有消费者都已升级。适配器容器与主应用程序容器共享文件系统,因此它可以监视本地文件系统,每当新应用程序写入内容时,它立即进行适应。

Kubernetes直接支持单节点模式,通过pod。多节点模式,如领导者选举、工作队列和分散收集,不受直接支持,但通过使用标准接口组合pod来实现它们是一种可行的方法。

--runtime-config=batch/v1=false,batch/v2alpha=true默认情况下启用以下资源,除了核心资源:

DaemonSets

Deployments

HorizontalPodAutoscalers

Ingress

Jobs

ReplicaSets

默认情况下,工作负载只能在集群内访问,必须使用LoadBalancer或NodePort服务将其外部暴露。在开发过程中,可以通过使用kubectlproxy命令通过API主机访问内部可访问的工作负载:

Endpoints:核心

Ingress:扩展

Service:核心

除了API组之外,可用API的另一个有用的分类是功能。KubernetesAPI非常庞大,将其分成不同类别在你试图找到自己的路时非常有帮助。Kubernetes定义了以下资源类别:

工作负载:您用来管理和运行集群上的容器的对象。

发现和负载平衡:您用来将工作负载暴露给外部可访问、负载平衡服务的对象。

配置和存储:您用来初始化和配置应用程序,并持久化容器外的数据的对象。

集群:定义集群本身配置的对象;这些通常只由集群操作员使用。

元数据:您用来配置集群内其他资源行为的对象,例如用于扩展工作负载的HorizontalPodAutoscaler。

在以下小节中,我将列出属于每个组的资源,以及它们所属的API组。我不会在这里指定版本,因为API从alpha迅速转移到beta到一般可用性(GA),然后从V1到V2,依此类推。

工作负载API包含以下资源:

Container:核心

CronJob:批处理

DaemonSet:应用

Deployment:应用

Job:批处理

Pod:核心

ReplicaSet:应用

ReplicationController:核心

StatefulSet:应用

容器是由控制器使用pod创建的。Pod运行容器并提供环境依赖项,如共享或持久存储卷,以及注入到容器中的配置或秘密数据。

以下是最常见操作之一的详细描述,它以RESTAPI的形式获取所有pod的列表:

GET/api/v1/pods它接受各种查询参数(全部可选):

pretty:如果为true,则输出将被漂亮地打印

labelSelector:限制结果的选择器表达式

watch:如果为true,则会监视更改并返回事件流

resourceVersion:仅返回在该版本之后发生的事件

timeoutSeconds:列表或监视操作的超时

Kubernetes的动态配置而无需重新部署是在您的Kubernetes集群上运行复杂分布式应用的基石:

ConfigMap:核心

Secret:核心

PersistentVolumeClaim:核心

StorageClass:存储

VolumeAttachment:存储

集群类别中的资源是为集群操作员设计的,而不是开发人员。这个类别中也有许多资源。以下是一些最重要的资源:

Namespace:核心

Node:核心

PersistentVolume:核心

ResourceQuota:核心

ClusterRole:Rbac

NetworkPolicy:网络

Kubernetes集群有几个主要组件,用于控制集群,以及在每个集群节点上运行的节点组件。让我们了解所有这些组件以及它们如何一起工作。

主要组件通常在一个节点上运行,但在高可用性或非常大的集群中,它们可能分布在多个节点上。

KubeAPI服务器公开了KubernetesRESTAPI。由于它是无状态的,并且将所有数据存储在etcd集群中,因此可以轻松地进行水平扩展。API服务器是Kubernetes控制平面的具体体现。

Etcd是一个高度可靠的分布式数据存储。Kubernetes使用它来存储整个集群状态。在一个小型的瞬态集群中,一个etcd的实例可以在与所有其他主要组件相同的节点上运行,但对于更大的集群,通常会有一个三节点甚至五节点的etcd集群,以实现冗余和高可用性。

Kube控制器管理器是各种管理器的集合,汇总成一个二进制文件。它包含复制控制器、Pod控制器、服务控制器、端点控制器等。所有这些管理器通过API监视集群的状态,它们的工作是将集群引导到期望的状态。

在云中运行时,Kubernetes允许云提供商集成其平台,以管理节点、路由、服务和卷。云提供商代码与Kubernetes代码进行交互。它替换了Kube控制器管理器的一些功能。在使用云控制器管理器运行Kubernetes时,必须将Kube控制器管理器标志--cloud-provider设置为external。这将禁用云控制器管理器正在接管的控制循环。云控制器管理器是在Kubernetes1.6中引入的,并且已被多个云提供商使用。

关于Go的一个快速说明,以帮助您解析代码:方法名首先出现,然后是方法的参数在括号中。每个参数都是一对,由名称和类型组成。最后,指定返回值。Go允许多个返回类型。通常会返回一个error对象,除了实际结果。如果一切正常,error对象将为nil。

这是cloudprovider包的主要接口:

packagecloudproviderimport("errors""fmt""strings""k8s.io/api/core/v1""k8s.io/apimachinery/pkg/types""k8s.io/client-go/informers""k8s.io/kubernetes/pkg/controller")//Interfaceisanabstract,pluggableinterfaceforcloudproviders.typeInterfaceinterface{Initialize(clientBuildercontroller.ControllerClientBuilder)LoadBalancer()(LoadBalancer,bool)Instances()(Instances,bool)Zones()(Zones,bool)Clusters()(Clusters,bool)Routes()(Routes,bool)ProviderName()stringHasClusterID()bool}大多数方法返回具有自己方法的其他接口。例如,这是LoadBalancer接口:

typeLoadBalancerinterface{GetLoadBalancer(clusterNamestring,service*v1.Service)(status*v1.LoadBalancerStatus,existsbool,errerror)EnsureLoadBalancer(clusterNamestring,service*v1.Service,nodes[]*v1.Node)(*v1.LoadBalancerStatus,error)UpdateLoadBalancer(clusterNamestring,service*v1.Service,nodes[]*v1.Node)errorEnsureLoadBalancerDeleted(clusterNamestring,service*v1.Service)error}Kube-schedulerkube-scheduler负责将Pod调度到节点。这是一个非常复杂的任务,因为它需要考虑多个相互作用的因素,例如:

资源要求

服务要求

硬件/软件策略约束

节点亲和性和反亲和性规范

Pod亲和性和反亲和性规范

污点和容忍

数据本地性

截止日期

如果您需要一些默认Kube调度程序未涵盖的特殊调度逻辑,可以用自己的自定义调度程序替换它。您还可以将自定义调度程序与默认调度程序并行运行,并且让自定义调度程序仅调度一部分Pod。

自Kubernetes1.3以来,DNS服务已成为标准Kubernetes集群的一部分。它被安排为一个常规的pod。每个服务(除了无头服务)都会收到一个DNS名称。Pods也可以收到DNS名称。这对于自动发现非常有用。

集群中的节点需要一些组件来与集群主组件交互,并接收要执行的工作负载并更新其状态。

Kube代理在每个节点上进行低级别的网络维护。它在本地反映Kubernetes服务,并可以进行TCP和UDP转发。它通过环境变量或DNS找到集群IP。

kubelet是节点上的Kubernetes代表。它负责与主组件通信并管理正在运行的pod。这包括以下操作:

从API服务器下载pod的秘密

挂载卷

运行pod的容器(通过CRI或rkt)

报告节点和每个pod的状态

运行容器的活动探测

在本节中,我们深入研究了Kubernetes的内部,探索了其架构(从非常高层次的角度),并支持了设计模式,通过其API和用于控制和管理集群的组件。在下一节中,我们将快速浏览Kubernetes支持的各种运行时。

Kubernetes最初只支持Docker作为容器运行时引擎。但现在不再是这样。Kubernetes现在支持几种不同的运行时:

Docker(通过CRIshim)

Rkt(直接集成将被rktlet替换)

Cri-o

Frakti(在hypervisor上的Kubernetes,以前是Hypernetes)

Rktlet(rkt的CRI实现)

cri-containerd

一个主要的设计政策是Kubernetes本身应该完全与特定的运行时解耦。容器运行时接口(CRI)使这成为可能。

在本节中,您将更仔细地了解CRI,并了解各个运行时引擎。在本节结束时,您将能够就哪种运行时引擎适合您的用例做出明智的决定,并在何种情况下可以切换或甚至在同一系统中组合多个运行时。

CRI是一个gRPCAPI,包含容器运行时与节点上的kubelet集成的规范/要求和库。在Kubernetes1.7中,Kubernetes中的内部Docker集成被CRI-based集成所取代。这是一件大事。它为利用容器领域的进步打开了多种实现的大门。Kubelet不需要直接与多个运行时进行接口。相反,它可以与任何符合CRI的容器运行时进行通信。以下图表说明了流程:

有两个gRPC服务接口——ImageService和RuntimeService——CRI容器运行时(或shims)必须实现。ImageService负责管理镜像。以下是gRPC/protobuf接口(这不是Go):

serviceImageService{rpcListImages(ListImagesRequest)returns(ListImagesResponse){}rpcImageStatus(ImageStatusRequest)returns(ImageStatusResponse){}rpcPullImage(PullImageRequest)returns(PullImageResponse){}rpcRemoveImage(RemoveImageRequest)returns(RemoveImageResponse){}rpcImageFsInfo(ImageFsInfoRequest)returns(ImageFsInfoResponse){}}RuntimeService负责管理pod和容器。以下是gRPC/profobug接口:

messageCreateContainerRequest{stringpod_sandbox_id=1;ContainerConfigconfig=2;PodSandboxConfigsandbox_config=3;}正如您所看到的,消息可以嵌套在彼此之内。CreateContainerRequest消息有一个字符串字段和另外两个字段,它们本身也是消息:ContainerConfig和PodSandboxConfig。

现在您已经在代码级别熟悉了Kubernetes运行时引擎,让我们简要地看一下各个运行时引擎。

当然,Docker是容器的大象级存在。Kubernetes最初设计仅用于管理Docker容器。多运行时功能首次在Kubernetes1.3中引入,而CRI则在Kubernetes1.5中引入。在那之前,Kubernetes只能管理Docker容器。

安全性

难以设置多容器应用程序(特别是网络)

开发、监控和日志记录

Docker容器运行一个命令的限制

发布不完善的功能太快

Docker意识到了这些批评,并解决了其中一些问题。特别是,Docker已经投资于其DockerSwarm产品。DockerSwarm是一个与Kubernetes竞争的Docker本地编排解决方案。它比Kubernetes更容易使用,但不如Kubernetes强大或成熟。

自Docker1.12以来,swarm模式已经内置在Docker守护程序中,这让一些人感到不满,因为它的臃肿和范围扩大。这反过来使更多的人转向CoreOSrkt作为替代解决方案。

自Docker1.11发布于2016年4月以来,Docker已经改变了运行容器的方式。运行时现在使用containerd和runC来在容器中运行OpenContainerInitiative(OCI)图像:

Rkt是来自CoreOS的容器管理器(CoreOSLinux发行版、etcd、flannel等的开发者)。Rkt运行时以其简单性和对安全性和隔离性的强调而自豪。它没有像Docker引擎那样的守护程序,而是依赖于操作系统的init系统,比如systemd,来启动rkt可执行文件。Rkt可以下载图像(包括应用容器(appc)图像和OCI图像),验证它们,并在容器中运行。它的架构要简单得多。

CoreOS在2014年12月启动了一个名为appc的标准化工作。这包括标准图像格式(ACI)、运行时、签名和发现。几个月后,Docker开始了自己的标准化工作,推出了OCI。目前看来,这些努力将会融合。这是一件好事,因为工具、图像和运行时将能够自由地互操作。但我们还没有达到这一点。

Cri-o是一个Kubernetes孵化器项目。它旨在为Kubernetes和符合OCI标准的容器运行时(如Docker)之间提供集成路径。其想法是Cri-O将提供以下功能:

支持多种图像格式,包括现有的Docker图像格式

支持多种下载图像的方式,包括信任和图像验证

容器镜像管理(管理镜像层、叠加文件系统等)

容器进程生命周期管理

满足CRI所需的监控和日志记录

根据CRI所需的资源隔离

然后任何符合OCI标准的容器运行时都可以被插入,并将与Kubernetes集成。

Rktnetes是Kubernetes加上rkt作为运行时引擎。Kubernetes仍在抽象化运行时引擎的过程中。Rktnetes实际上并不是一个单独的产品。从外部来看,只需要在每个节点上运行Kubelet并加上几个命令行开关。

我对rkt没有太多的实际经验。然而,它被Tectonic使用——这是基于CoreOS的商业Kubernetes发行版。如果你运行不同类型的集群,我建议你等到rkt通过CRI/rktlet与Kubernetes集成。在使用rkt与Kubernetes相比,有一些已知的问题需要注意,例如,缺少的卷不会自动创建,Kubectl的attach和getlogs不起作用,以及init容器不受支持,还有其他问题。

超级容器是另一个选择。超级容器具有轻量级虚拟机(自己的客户机内核),并在裸金属上运行。它不依赖于Linuxcgroups进行隔离,而是依赖于一个虚拟化程序。与难以设置的标准裸金属集群和在重量级虚拟机上部署容器的公共云相比,这种方法呈现出有趣的混合。

Stackube(之前称为Hypernetes)是一个多租户分发,它使用超级容器以及一些OpenStack组件进行身份验证、持久存储和网络。由于容器不共享主机内核,因此可以安全地在同一物理主机上运行不同租户的容器。当然,Stackube使用Frakti作为其容器运行时。

在本节中,我们已经涵盖了Kubernetes支持的各种运行时引擎,以及标准化和融合的趋势。在下一节中,我们将退一步,看看整体情况,以及Kubernetes如何适应CI/CD流水线。

Kubernetes是运行基于微服务的应用程序的绝佳平台。但归根结底,它只是一个实现细节。用户,甚至大多数开发人员,可能不知道系统是部署在Kubernetes上的。但Kubernetes可以改变游戏规则,使以前难以实现的事情成为可能。

在本节中,我们将探讨CI/CD流水线以及Kubernetes带来了什么。在本节结束时,您将能够设计利用Kubernetes属性的CI/CD流水线,例如易扩展性和开发-生产一致性,以提高您日常开发和部署的生产力和稳健性。

CI/CD流水线是由开发人员或运营人员实施的一组步骤,用于修改系统的代码、数据或配置,对其进行测试,并将其部署到生产环境。一些流水线是完全自动化的,而一些是半自动化的,需要人工检查。在大型组织中,可能会有测试和暂存环境,更改会自动部署到这些环境,但发布到生产环境需要手动干预。下图描述了一个典型的流水线。

值得一提的是,开发人员可以完全与生产基础设施隔离开来。他们的接口只是一个Git工作流程——Deis工作流程(在Kubernetes上的PaaS;类似于Heroku)就是一个很好的例子。

当你的部署目标是一个Kubernetes集群时,你应该重新思考一些传统的做法。首先,打包是不同的。你需要为你的容器烘焙镜像。使用智能标签可以轻松且即时地回滚代码更改。这给了你很多信心,即使一个糟糕的更改通过了测试网,你也能立即回滚到上一个版本。但你要小心。模式更改和数据迁移不能自动回滚。

Kubernetes的另一个独特能力是开发人员可以在本地运行整个集群。当你设计你的集群时,这需要一些工作,但由于构成系统的微服务在容器中运行,并且这些容器通过API进行交互,这是可能和实际可行的。与往常一样,如果你的系统非常依赖数据,你需要为此做出调整,并提供数据快照和合成数据供开发人员使用。

在本章中,我们涵盖了很多内容,你了解了Kubernetes的设计和架构。Kubernetes是一个用于运行容器化微服务应用程序的编排平台。Kubernetes集群有主节点和工作节点。容器在pod中运行。每个pod在单个物理或虚拟机上运行。Kubernetes直接支持许多概念,如服务、标签和持久存储。您可以在Kubernetes上实现各种分布式系统设计模式。容器运行时只需实现CRI。支持Docker、rkt、Hyper容器等等。

在第二章中,创建Kubernetes集群,我们将探讨创建Kubernetes集群的各种方式,讨论何时使用不同的选项,并构建一个多节点集群。

在上一章中,我们了解了Kubernetes的全部内容,它的设计方式,支持的概念,如何使用其运行时引擎,以及它如何适用于CI/CD流水线。

创建Kubernetes集群是一项非常重要的任务。有许多选择和工具可供选择,需要考虑许多因素。在本章中,我们将动手构建一些Kubernetes集群。我们还将讨论和评估诸如Minikube、kubeadm、kube-spray、bootkube和stackube等工具。我们还将研究部署环境,如本地、云和裸机。我们将涵盖的主题如下:

使用Minikube创建单节点集群

使用kubeadm创建多节点集群

在云中创建集群

从头开始创建裸机集群

审查其他创建Kubernetes集群的选项

在本章结束时,您将对创建Kubernetes集群的各种选项有扎实的了解,并了解支持创建Kubernetes集群的最佳工具;您还将构建一些集群,包括单节点和多节点。

在本节中,我们将在Windows上创建一个单节点集群。我们之所以使用Windows,是因为Minikube和单节点集群对于本地开发者机器非常有用。虽然Kubernetes通常在生产环境中部署在Linux上,但许多开发人员使用WindowsPC或Mac。也就是说,如果您确实想在Linux上安装Minikube,也没有太多区别:

在创建集群之前,有一些先决条件需要安装。这些包括VirtualBox,用于Kubernetes的kubectl命令行界面,当然还有Minikube本身。以下是撰写时的最新版本列表:

安装VirtualBox并确保kubectl和Minikube在你的路径上。我个人只是把我使用的所有命令行程序都放在c:\windows中。你可能更喜欢另一种方法。我使用优秀的ConEMU来管理多个控制台、终端和SSH会话。它可以与cmd.exe、PowerShell、PuTTY、Cygwin、msys和Git-Bash一起使用。在Windows上没有比这更好的了。

在Windows10Pro中,你可以选择使用Hyper-Vhypervisor。这在技术上是比VirtualBox更好的解决方案,但它需要Windows的专业版,并且完全是Windows特有的。当使用VirtualBox时,这些说明是通用的,并且很容易适应其他版本的Windows,或者其他操作系统。如果你已经启用了Hyper-V,你必须禁用它,因为VirtualBox无法与Hyper-V共存。

我建议在管理员模式下使用PowerShell。你可以将以下别名和函数添加到你的PowerShell配置文件中:

Set-Alias-Namek-Valuekubectlfunctionmk{minikube-windows-amd64`--show-libmachine-logs`--alsologtostderr`@args}在macOS上你可以在你的.bashrc文件中添加别名(类似于Windows上的PowerShell别名和函数):

aliask='kubectl'aliasmk='/usr/local/bin/minikube'现在我可以使用k和mk并且输入更少。mk函数中的Minikube标志提供更好的日志记录方式,并将输出直接输出到控制台,以及文件中(类似于tee)。

输入mkversion来验证Minikube是否正确安装并运行:

>mkversionminikubeversion:v0.26.0输入kversion来验证kubectl是否正确安装并运行:

>kversionClientVersion:version.Info{Major:"1",Minor:"9",GitVersion:"v1.9.0",GitCommit:"925c127ec6b946659ad0fd596fa959be43f0cc05",GitTreeState:"clean",BuildDate:"2017-12-16T03:15:38Z",GoVersion:"go1.9.2",Compiler:"gc",Platform:"darwin/amd64"}Unabletoconnecttotheserver:dialtcp192.168.99.100:8443:getsockopt:operationtimedout不要担心最后一行的错误。没有运行的集群,所以kubectl无法连接到任何东西。这是预期的。

你可以探索Minikube和kubectl的可用命令和标志。我不会逐个介绍每一个,只介绍我使用的命令。

Minikube工具支持多个版本的Kubernetes。在撰写本文时,支持的版本列表如下:

>mkget-k8s-versionsThefollowingKubernetesversionsareavailablewhenusingthelocalkubebootstrapper:-v1.10.0-v1.9.4-v1.9.0-v1.8.0-v1.7.5-v1.7.4-v1.7.3-v1.7.2-v1.7.0-v1.7.0-rc.1-v1.7.0-alpha.2-v1.6.4-v1.6.3-v1.6.0-v1.6.0-rc.1-v1.6.0-beta.4-v1.6.0-beta.3-v1.6.0-beta.2-v1.6.0-alpha.1-v1.6.0-alpha.0-v1.5.3-v1.5.2-v1.5.1-v1.4.5-v1.4.3-v1.4.2-v1.4.1-v1.4.0-v1.3.7-v1.3.6-v1.3.5-v1.3.4-v1.3.3-v1.3.0我将选择1.10.0,最新的稳定版本。让我们使用start命令并指定v1.10.0作为版本来创建集群。

>mkstart--kubernetes-version="v1.10.0"StartinglocalKubernetesv1.10.0cluster...StartingVM...GettingVMIPaddress...Movingfilesintocluster...FinishedDownloadingkubeadmv1.10.0**FinishedDownloadingkubeletv1.10.0**Settingupcerts...Connectingtocluster...Settingupkubeconfig...Startingclustercomponents...Kubectlisnowconfiguredtousethecluster.Loadingcachedimagesfromconfigfile.让我们通过跟踪输出来回顾Minikube的操作。当从头开始创建集群时,你需要做很多这样的操作:

启动VirtualBox虚拟机

为本地机器和虚拟机创建证书

下载镜像

在本地机器和虚拟机之间设置网络

在虚拟机上运行本地Kubernetes集群

配置集群

启动所有Kubernetes控制平面组件

配置kubectl以与集群通信

如果在过程中出现问题,请尝试遵循错误消息。您可以添加--alsologtostderr标志以从控制台获取详细的错误信息。Minikube所做的一切都整齐地组织在~/.minikube下。以下是目录结构:

>tree~/.minikube-L2/Users/gigi.sayfan/.minikube├──addons├──apiserver.crt├──apiserver.key├──ca.crt├──ca.key├──ca.pem├──cache│├──images│├──iso│└──localkube├──cert.pem├──certs│├──ca-key.pem│├──ca.pem│├──cert.pem│└──key.pem├──client.crt├──client.key├──config│└──config.json├──files├──key.pem├──last_update_check├──logs├──machines│├──minikube│├──server-key.pem│└──server.pem├──profiles│└──minikube├──proxy-client-ca.crt├──proxy-client-ca.key├──proxy-client.crt└──proxy-client.key13directories,21files检查集群既然我们已经有一个运行中的集群,让我们来看看里面。

首先,让我们ssh进入虚拟机:

>mkssh____()()______(_)___(_)||/')__||___/'_`_`\||/'_`\|||,<()()|'_`\/'__`\|()()||||()|||||\`\|(_)|||_))(___/(_)(_)(_)(_)(_)(_)(_)(_)(_)`\___/'(_,__/'`\____)$uname-aLinuxminikube4.9.64#1SMPFriMar3021:27:22UTC2018x86_64GNU/Linux$太棒了!成功了。奇怪的符号是minikube的ASCII艺术。现在,让我们开始使用kubectl,因为它是Kubernetes的瑞士军刀,并且对所有集群(包括联合集群)都很有用。

我们将在我们的旅程中涵盖许多kubectl命令。首先,让我们使用cluster-info检查集群状态:

要进一步调试和诊断集群问题,请使用kubectlcluster-infodump。您可以看到主节点正在正常运行。要以JSON类型查看集群中所有对象的更详细视图,请使用kcluster-infodump。输出可能有点令人生畏,因此让我们使用更具体的命令来探索集群。

让我们使用getnodes检查集群中的节点:

>kgetnodesNAMESTATUSROLESAGEVERSIONNAMESTATUSROLESAGEVERSIONminikubeReadymaster15mv1.10.0所以,我们有一个名为minikube的节点。要获取有关它的大量信息,请输入kdescribenodeminikube。输出是冗长的;我会让您自己尝试。

我们有一个漂亮的空集群正在运行(好吧,不完全是空的,因为DNS服务和仪表板作为kube-system命名空间中的pod运行)。现在是时候运行一些pod了。让我们以echo服务器为例:

krunecho--image=gcr.io/google_containers/echoserver:1.8--port=8080deployment"echo"createdKubernetes创建了一个部署,我们有一个正在运行的pod。注意echo前缀:

>kgetpodsNAMEREADYSTATUSRESTARTSAGEecho-69f7cfb5bb-wqgkh1/1Running018s要将我们的pod公开为服务,请输入以下内容:

>kexposedeploymentecho--type=NodePortservice"echo"exposed将服务公开为NodePort类型意味着它对主机公开端口,但它不是我们在其上运行pod的8080端口。端口在集群中映射。要访问服务,我们需要集群IP和公开的端口:

>mkip192.168.99.101>kgetserviceecho--output='jsonpath="{.spec.ports[0].nodePort}"'30388现在我们可以访问echo服务,它会返回大量信息:

Kubernetes有一个非常好的web界面,当然是部署为一个pod中的服务。仪表板设计得很好,提供了对集群的高级概述,还可以深入到单个资源,查看日志,编辑资源文件等。当你想要手动检查你的集群时,它是一个完美的武器。要启动它,输入minikubedashboard。

Minikube将打开一个带有仪表板UI的浏览器窗口。请注意,在Windows上,MicrosoftEdge无法显示仪表板。我不得不在不同的浏览器上运行它。

这是工作负载视图,显示部署、副本集、复制控制器和Pod:

它还可以显示守护进程集、有状态集和作业,但在这个集群中我们没有这些。

在这一部分,我们在Windows上创建了一个本地的单节点Kubernetes集群,使用kubectl进行了一些探索,部署了一个服务,并尝试了webUI。在下一部分,我们将继续创建一个多节点集群。

在这一部分,我将向您介绍kubeadm,这是在所有环境中创建Kubernetes集群的推荐工具。它仍在积极开发中,但这是因为它是Kubernetes的一部分,并且始终体现最佳实践。为了使其对整个集群可访问,我们将以虚拟机为基础。这一部分是为那些想要亲自部署多节点集群的读者准备的。

在踏上这段旅程之前,我想明确指出,这可能不会一帆风顺。kubeadm的任务很艰巨:它必须跟随Kubernetes本身的发展,而Kubernetes是一个不断变化的目标。因此,它并不总是稳定的。当我写第一版《精通Kubernetes》时,我不得不深入挖掘并寻找各种解决方法来使其正常工作。猜猜?我在第二版中也不得不做同样的事情。准备好做一些调整并寻求帮助。如果你想要一个更简化的解决方案,我将在后面讨论一些非常好的选择。

Kubeadm在预配置的硬件(物理或虚拟)上运行。在创建Kubernetes集群之前,我们需要准备一些虚拟机并安装基本软件,如docker、kubelet、kubeadm和kubectl(仅在主节点上需要)。

以下vagrant文件将创建一个名为n1,n2,n3和n4的四个VM的集群。键入vagrantup以启动并运行集群。它基于Bento/Ubuntu版本16.04,而不是Ubuntu/Xenial,后者存在各种问题:

#-*-mode:ruby-*-#vi:setft=ruby:hosts={"n1"=>"192.168.77.10","n2"=>"192.168.77.11","n3"=>"192.168.77.12","n4"=>"192.168.77.13"}Vagrant.configure("2")do|config|#alwaysuseVagrantsinsecurekeyconfig.ssh.insert_key=false#forwardsshagenttoeasilysshintothedifferentmachinesconfig.ssh.forward_agent=truecheck_guest_additions=falsefunctional_vboxsf=falseconfig.vm.box="bento/ubuntu-16.04"hosts.eachdo|name,ip|config.vm.hostname=nameconfig.vm.definenamedo|machine|machine.vm.network:private_network,ip:ipmachine.vm.provider"virtualbox"do|v|v.name=nameendendendend安装所需的软件我非常喜欢Ansible进行配置管理。我在运行Ubuntu16.04的n4VM上安装了它。从现在开始,我将使用n4作为我的控制机器,这意味着我们正在在Linux环境中操作。我可以直接在我的Mac上使用Ansible,但由于Ansible无法在Windows上运行,我更喜欢更通用的方法:

vagrant@vagrant:~$ansible--versionansible2.5.0configfile=Noneconfiguredmodulesearchpath=[u'/home/vagrant/.ansible/plugins/modules',u'/usr/share/ansible/plugins/modules']ansiblepythonmodulelocation=/home/vagrant/.local/lib/python2.7/site-packages/ansibleexecutablelocation=/home/vagrant/.local/bin/ansiblepythonversion=2.7.12(default,Dec42017,14:50:18)[GCC5.4.020160609]pythonversion=2.7.12(default,Dec42017,14:50:18)[GCC5.4.020160609]我安装的sshpass程序将帮助ansible连接到所有带有内置vagrant用户的vagrantVM。这仅对本地基于VM的多节点集群重要。

我创建了一个名为ansible的目录,并在其中放置了三个文件:hosts,vars.yml和playbook.yml。

host文件是清单文件,告诉ansible目录要在哪些主机上操作。这些主机必须可以从控制机器进行SSH访问。以下是将安装集群的三个VM:

[all]192.168.77.10ansible_user=vagrantansible_ssh_pass=vagrant192.168.77.11ansible_user=vagrantansible_ssh_pass=vagrant192.168.77.12ansible_user=vagrantansible_ssh_pass=vagrantvars.yml文件vars.yml文件只是保留了我想要在每个节点上安装的软件包列表。vim,htop和tmux是我在需要管理的每台机器上安装的喜爱软件包。其他软件包是Kubernetes所需的:

---PACKAGES:-vim-htop-tmux-docker.io-kubelet-kubeadm-kubectl-kubernetes-cniplaybook.yml文件playbook.yml文件是您在所有主机上安装软件包时运行的文件:

连接到n4:

>vagrantsshn4您可能需要对n1,n2和n3节点中的每一个进行一次ssh:

vagrant@vagrant:~$ssh192.168.77.10vagrant@vagrant:~$ssh192.168.77.11vagrant@vagrant:~$ssh192.168.77.12一个更持久的解决方案是添加一个名为~/.ansible.cfg的文件,其中包含以下内容:

[defaults]host_key_checking=False从n4运行playbook如下:

vagrant@n4:~$ansible-playbook-ihostsplaybook.yml如果遇到连接失败,请重试。KubernetesAPT存储库有时会响应缓慢。您只需要对每个节点执行一次此操作。

现在是创建集群本身的时候了。我们将在第一个VM上初始化主节点,然后设置网络并将其余的VM添加为节点。

让我们在n1(192.168.77.10)上初始化主节点。在基于vagrantVM的云环境中,使用--apiserver-advertise-address标志是至关重要的:

>vagrantsshn1vagrant@n1:~$sudokubeadminit--apiserver-advertise-address192.168.77.10在Kubernetes1.10.1中,这导致了以下错误消息:

[init]UsingKubernetesversion:v1.10.1[init]UsingAuthorizationmodes:[NodeRBAC][preflight]Runningpre-flightchecks.[WARNINGFileExisting-crictl]:crictlnotfoundinsystempath[preflight]Somefatalerrorsoccurred:[ERRORSwap]:runningwithswaponisnotsupported.Pleasedisableswap[preflight]Ifyouknowwhatyouaredoing,youcanmakeachecknon-fatalwith`--ignore-preflight-errors=...`原因是默认情况下未安装所需的cri-tools。我们正在处理Kubernetes的最前沿。我创建了一个额外的playbook来安装Go和cri-tools,关闭了交换,并修复了vagrantVM的主机名:

----hosts:allbecome:truestrategy:freetasks:-name:Addthelongsleeprepoforrecentgolangversionapt_repository:repo='ppa:longsleep/golang-backports'state=present-name:updateaptcachedirectly(aptmodulenotreliable)shell:'apt-getupdate'args:warn:False-name:InstallGoapt:name=golang-gostate=presentforce=yes-name:Installcrictlshell:'gogetgithub.com/kubernetes-incubator/cri-tools/cmd/crictl'become_user:vagrant-name:Createsymlinkin/usr/local/binforcrictlfile:src:/home/vagrant/go/bin/crictldest:/usr/local/bin/crictlstate:link-name:Sethostnameproperlyshell:"hostnamen$((1+$(ifconfig|grep192.168|awk'{print$2}'|tail-c2)))"-name:Turnoffswapshell:'swapoff-a'–记得再次在n4上运行它,以更新集群中的所有节点。

以下是成功启动Kubernetes的一些输出:

vagrant@n1:~$sudokubeadminit--apiserver-advertise-address192.168.77.10[init]UsingKubernetesversion:v1.10.1[init]UsingAuthorizationmodes:[NodeRBAC][certificates]Generatedcacertificateandkey.[certificates]Generatedapiservercertificateandkey.[certificates]Validcertificatesandkeysnowexistin"/etc/kubernetes/pki"...[addons]Appliedessentialaddon:kube-dns[addons]Appliedessentialaddon:kube-proxyYourKubernetesmasterhasinitializedsuccessfully!以后加入其他节点到集群时,你需要写下更多的信息。要开始使用你的集群,你需要以普通用户身份运行以下命令:

vagrant@n1:~$mkdir-p$HOME/.kubevagrant@n1:~$sudocp-i/etc/kubernetes/admin.conf$HOME/.kube/configvagrant@n1:~$sudochown$(id-u):$(id-g)$HOME/.kube/config现在你可以通过在每个节点上以root身份运行一个命令来加入任意数量的机器。使用从kubeadminit命令返回的命令:sudokubeadmjoin--token<>--discovery-token-ca-cert-hash<>--skip-prflight-cheks。

集群的网络是重中之重。Pod需要能够相互通信。这需要一个Pod网络插件。有几种选择。由kubeadm生成的集群需要基于CNI的插件。我选择使用WeaveNet插件,它支持网络策略资源。你可以选择任何你喜欢的。

在主VM上运行以下命令:

serviceaccount"weave-net"createdclusterrole.rbac.authorization.k8s.io"weave-net"createdclusterrolebinding.rbac.authorization.k8s.io"weave-net"createdrole.rbac.authorization.k8s.io"weave-net"createdrolebinding.rbac.authorization.k8s.io"weave-net"createddaemonset.extensions"weave-net"created要验证,请使用以下命令:

vagrant@n1:~$kubectlgetpo--all-namespacesNAMESPACENAMEREADYSTATUSRESTARTSAGEkube-systemetcd-n11/1Running02mkube-systemkube-apiserver-n11/1Running02mkube-systemkube-controller-manager-n11/1Running02mkube-systemkube-dns-86f4d74b45-jqctg3/3Running03mkube-systemkube-proxy-l54s91/1Running03mkube-systemkube-scheduler-n11/1Running02mkube-systemweave-net-fl7wn2/2Running031s最后一个Pod是我们的weave-net-fl7wn,这正是我们要找的,以及kube-dnspod。两者都在运行。一切都很好!

现在我们可以使用之前获得的令牌将工作节点添加到集群中。在每个节点上,运行以下命令(不要忘记sudo)并使用在主节点上初始化Kubernetes时获得的令牌:

sudokubeadmjoin--token<>--discovery-token-ca-cert-hash<>--ignore-preflight-errors=all在撰写本书时(使用Kubernetes1.10),一些预检查失败,但这是一个错误的负面结果。实际上一切都很好,你可以通过添加--ignore-preflight-errors=all来跳过这些预检查。希望当你阅读本书时,这些问题已经解决。你应该看到以下内容:

*Certificatesigningrequestwassenttomasterandaresponsewasreceived.*TheKubeletwasinformedofthenewsecureconnectiondetails.在主节点上运行kubectlgetnodes,查看此节点加入集群。

由于CNI插件初始化的问题,某些组合可能无法正常工作。

在本地创建集群很有趣,在开发过程中以及在尝试在本地解决问题时很重要。但最终,Kubernetes是为云原生应用程序(在云中运行的应用程序)而设计的。Kubernetes不希望了解单个云环境,因为这不可扩展。相反,Kubernetes具有云提供程序接口的概念。每个云提供程序都可以实现此接口,然后托管Kubernetes。请注意,截至1.5版本,Kubernetes仍在其树中维护许多云提供程序的实现,但在将来,它们将被重构。

typeInterfaceinterface{Initialize(clientBuildercontroller.ControllerClientBuilder)LoadBalancer()(LoadBalancer,bool)Instances()(Instances,bool)Zones()(Zones,bool)Clusters()(Clusters,bool)Routes()(Routes,bool)ProviderName()stringHasClusterID()bool}这很清楚。Kubernetes以实例,区域,集群和路由运行,并且需要访问负载均衡器和提供者名称。主要接口主要是一个网关。大多数方法返回其他接口。

例如,Clusters接口非常简单:

typeClustersinterface{ListClusters()([]string,error)Master(clusterNamestring)(string,error)}ListClusters()方法返回集群名称。Master()方法返回主节点的IP地址或DNS名称。

其他接口并不复杂。整个文件有214行(截至目前为止),包括很多注释。重点是,如果您的云平台使用这些基本概念,实现Kubernetes提供程序并不太复杂。

谷歌云平台(GCP)支持Kubernetes开箱即用。所谓的谷歌Kubernetes引擎(GKE)是建立在Kubernetes上的容器管理解决方案。您不需要在GCP上安装Kubernetes,可以使用GoogleCloudAPI创建Kubernetes集群并进行配置。Kubernetes作为GCP的内置部分意味着它将始终被很好地集成和经过充分测试,您不必担心底层平台的更改会破坏云提供程序接口。

总的来说,如果您计划基于Kubernetes构建系统,并且在其他云平台上没有任何现有代码,那么GCP是一个可靠的选择。

它支持以下功能:

云端(AWS)自动化Kubernetes集群CRUD

高可用(HA)的Kubernetes集群

它使用状态同步模型进行干运行和自动幂等性

kubectl的自定义支持插件

Kops可以生成Terraform配置

它基于一个在目录树中定义的简单元模型

简单的命令行语法

社区支持

要创建一个集群,你需要通过route53进行一些最小的DNS配置,设置一个S3存储桶来存储集群配置,然后运行一个命令:

在2017年底,AWS加入了CNCF,并宣布了两个关于Kubernetes的重大项目:自己的基于Kubernetes的容器编排解决方案(EKS)和一个按需的容器解决方案(Fargate)。

亚马逊弹性容器服务用于Kubernetes是一个完全托管且高可用的Kubernetes解决方案。它有三个主节点在三个可用区运行。EKS还负责升级和打补丁。EKS的好处是它运行的是原始的Kubernetes,没有任何改动。这意味着你可以使用社区开发的所有标准插件和工具。它还为与其他云提供商和/或你自己的本地Kubernetes集群方便的集群联合开启了大门。

EKS与AWS基础设施深度集成。IAM认证与Kubernetes的基于角色的访问控制(RBAC)集成。

如果你想直接从你自己的AmazonVPC访问你的Kubernetes主节点,你也可以使用PrivateLink。使用PrivateLink,你的Kubernetes主节点和AmazonEKS服务端点将显示为弹性网络接口,具有AmazonVPC中的私有IP地址。

拼图的另一个重要部分是一个特殊的CNI插件,它让您的Kubernetes组件可以使用AWS网络相互通信。

Fargate让您可以直接运行容器,而不必担心硬件配置。它消除了操作复杂性的很大一部分,但代价是失去了一些控制。使用Fargate时,您将应用程序打包到容器中,指定CPU和内存要求,并定义网络和IAM策略,然后就可以运行了。Fargate可以在ECS和EKS上运行。它是无服务器阵营中非常有趣的一员,尽管它与Kubernetes没有直接关联。

这个过程非常简单。您需要安装Docker、make和kubectl,当然还需要您的Azure订阅ID。然后,您克隆kubernetes-anywhere存储库,运行一些make命令,您的集群就可以运行了。

然而,在2017年下半年,Azure也跳上了Kubernetes的列车,并推出了AKS-Azure容器服务。它类似于AmazonEKS,尽管在实施上稍微领先一些。

AKS提供了一个RESTAPI,以及一个CLI,用于管理您的Kubernetes集群,但您也可以直接使用kubectl和任何其他Kubernetes工具。

以下是使用AKS的一些好处:

自动化的Kubernetes版本升级和修补

轻松扩展集群

自愈托管控制平面(主控)

节省成本-只为运行的代理节点付费

在本节中,我们介绍了云服务提供商接口,并介绍了在各种云服务提供商上创建Kubernetes集群的各种推荐方法。这个领域仍然很年轻,工具在迅速发展。我相信融合很快就会发生。诸如kubeadm、kops、Kargo和kubernetes-anywhere等工具和项目最终将合并,并提供一种统一且简单的方式来引导Kubernetes集群。

裸机集群是一种特殊情况,特别是如果您自己管理它们。有一些公司提供裸机Kubernetes集群的商业支持,比如Platform9,但这些产品尚不成熟。一个坚实的开源选择是Kubespray,它可以在裸机、AWS、GCE、Azure和OpenStack上部署工业强度的Kubernetes集群。

以下是一些情况下使用裸机集群是有意义的:

预算问题:如果您已经管理了大规模的裸机集群,那么在您的物理基础设施上运行Kubernetes集群可能会更便宜

低网络延迟:如果您的节点之间必须有低延迟,那么虚拟机的开销可能会太大

监管要求:如果您必须遵守法规,可能不允许使用云服务提供商

您想要对硬件拥有完全控制权:云服务提供商为您提供了许多选择,但您可能有特殊需求

从头开始创建集群的复杂性是显著的。Kubernetes集群并不是一个微不足道的东西。关于如何设置裸机集群的文档很多,但随着整个生态系统的不断发展,许多这些指南很快就会过时。

有很多事情要做。以下是您需要解决的一些问题的列表:

实现自己的云提供商接口或绕过它

选择网络模型以及如何实现它(使用CNI插件或直接编译)

是否使用网络策略

选择系统组件的镜像

安全模型和SSL证书

管理员凭据

组件的模板,如API服务器、复制控制器和调度器

集群服务,如DNS、日志记录、监控和GUI

如果您的用例属于裸机用例,但您没有必要的熟练人手或者不愿意处理裸机的基础设施挑战,您可以选择使用私有云,比如OpenStack(例如,使用stackube)。如果您想在抽象层次上再高一点,那么Mirantis提供了一个建立在OpenStack和Kubernetes之上的云平台。

在本节中,我们考虑了构建裸机集群Kubernetes集群的选项。我们研究了需要它的用例,并突出了挑战和困难。

Bootkube也非常有趣。它可以启动自托管的Kubernetes集群。自托管意味着大多数集群组件都作为常规pod运行,并且可以使用与您用于容器化应用程序相同的工具和流程进行管理、监控和升级。这种方法有显著的好处,简化了Kubernetes集群的开发和运行。

在第二章中,创建Kubernetes集群,您学习了如何在不同环境中创建Kubernetes集群,尝试了不同的工具,并创建了一些集群。

创建Kubernetes集群只是故事的开始。一旦集群运行起来,您需要确保它是可操作的,所有必要的组件都齐全并正确配置,并且部署了足够的资源来满足要求。响应故障、调试和故障排除是管理任何复杂系统的重要部分,Kubernetes也不例外。

本章将涵盖以下主题:

使用Heapster进行监控

使用Kubernetes仪表板进行性能分析

中央日志记录

在节点级别检测问题

故障排除场景

使用Prometheus

在本章结束时,您将对监视Kubernetes集群的各种选项有扎实的了解,知道如何访问日志以及如何分析它们。您将能够查看健康的Kubernetes集群并验证一切正常。您还将能够查看不健康的Kubernetes集群,并系统地诊断它,定位问题并解决它们。

Heapster是一个为Kubernetes集群提供强大监控解决方案的Kubernetes项目。它作为一个pod(当然)运行,因此可以由Kubernetes本身管理。Heapster支持Kubernetes和CoreOS集群。它具有非常模块化和灵活的设计。Heapster从集群中的每个节点收集操作指标和事件,将它们存储在持久后端(具有明确定义的模式)中,并允许可视化和编程访问。Heapster可以配置为使用不同的后端(或在Heapster术语中称为sinks)及其相应的可视化前端。最常见的组合是InfluxDB作为后端,Grafana作为前端。谷歌云平台将Heapster与谷歌监控服务集成。还有许多其他不太常见的后端,如下所示:

日志

谷歌云监控

谷歌云日志

Hawkular-Metrics(仅指标)

OpenTSDB

Monasca(仅指标)

Kafka(仅指标)

Riemann(仅指标)

Elasticsearch

您可以通过在命令行上指定sinks来使用多个后端:

如果您想快速验证特定节点是否设置正确,例如,在Heapster尚未连接时创建新集群,那么cAdvisorUI非常有用。

这是它的样子:

Heapster组件可能已安装或尚未安装在您的Kubernetes集群中。如果Heapster尚未安装,您可以使用几个简单的命令进行安装。首先,让我们克隆Heapster存储库:

>gitdiffdeploy/kube-config/influxdb/influxdb.yamldiff--gita/deploy/kube-config/influxdb/influxdb.yamlb/deploy/kube-config/influxdb/influxdb.yamlindex29408b81..70f52d2c100644---a/deploy/kube-config/influxdb/influxdb.yaml+++b/deploy/kube-config/influxdb/influxdb.yaml@@-33,6+33,7@@metadata:name:monitoring-influxdbnamespace:kube-systemspec:+type:NodePortports:-port:8086targetPort:8086我对deploy/kube-config/influxdb/grafana.yaml进行了类似的更改,其中+type:NodePort这一行被注释掉了,所以我只是取消了注释。现在,我们实际上可以安装InfluxDB和Grafana:

>kubectlcreate-fdeploy/kube-config/influxdb您应该看到以下输出:

pod_id:一个pod的唯一ID

pod_name:pod的用户提供的名称

pod_namespace:pod的命名空间

container_base_image:容器的基础镜像

container_name:容器的用户提供的名称或系统容器的完整cgroup名称

host_id:云服务提供商指定或用户指定的节点标识符

hostname:容器运行的主机名

labels:用户提供的标签的逗号分隔列表;格式为key:value

namespace_id:pod命名空间的UID

resource_id:用于区分同一类型多个指标的唯一标识符,例如,文件系统/使用下的FS分区

以下是按类别分组的所有指标,可以看到,它非常广泛。

CPU指标包括:

cpu/limit:毫核的CPU硬限制

cpu/node_capacity:节点的CPU容量

cpu/node_allocatable:节点的可分配CPU

cpu/node_reservation:节点可分配的CPU保留份额

cpu/node_utilization:CPU利用率占节点可分配资源的份额

cpu/request:CPU请求(资源的保证数量)(毫核)

cpu/usage:所有核心的累积CPU使用率

cpu/usage_rate:所有核心的CPU使用率(毫核)

文件系统指标包括:

filesystem/usage:文件系统上消耗的总字节数

filesystem/limit:文件系统的总大小(字节)

filesystem/available:文件系统中剩余的可用字节数

内存指标包括:

memory/limit:内存的硬限制(字节)

memory/major_page_faults:主要页面错误的数量

memory/major_page_faults_rate:每秒的主要页面错误数

memory/node_capacity:节点的内存容量

memory/node_allocatable:节点的可分配内存

memory/node_reservation:节点可分配内存上保留的份额

memory/node_utilization:内存利用率占内存可分配资源的份额

memory/page_faults:页面错误的数量

memory/page_faults_rate:每秒的页面错误数

memory/request:内存请求(资源的保证数量)(字节)

memory/usage:总内存使用量

memory/working_set:总工作集使用量;工作集是内存的使用部分,不容易被内核释放

网络指标包括:

network/rx:累积接收的网络字节数

network/rx_errors:接收时的累积错误数

网络

network/rx_errors_rate:在网络接收过程中每秒发生的错误次数

network/rx_rate:每秒通过网络接收的字节数

network/tx:通过网络发送的累积字节数

network/tx_errors:在网络发送过程中的累积错误次数

network/tx_errors_rate:在网络发送过程中发生的错误次数

network/tx_rate:每秒通过网络发送的字节数

如果您熟悉InfluxDB,可以直接使用它。您可以使用其自己的API连接到它,也可以使用其Web界面。键入以下命令以查找其端口和端点:

>kdescribeservicemonitoring-influxdb--namespace=kube-system|grepNodePortType:NodePortNodePort:32699/TCP现在,您可以使用HTTP端口浏览InfluxDBWeb界面。您需要将其配置为指向API端口。默认情况下,用户名和密码为root和root:

设置完成后,您可以选择要使用的数据库(请参阅右上角)。Kubernetes数据库的名称为k8s。现在,您可以使用InfluxDB查询语言查询指标。

Grafana在其自己的容器中运行,并提供一个与InfluxDB作为数据源配合良好的复杂仪表板。要找到端口,请键入以下命令:

kdescribeservicemonitoring-influxdb--namespace=kube-system|grepNodePortType:NodePortNodePort:30763/TCP现在,您可以在该端口上访问GrafanaWeb界面。您需要做的第一件事是设置数据源指向InfluxDB后端:

确保测试连接,然后去探索仪表板中的各种选项。有几个默认的仪表板,但您应该能够根据自己的喜好进行自定义。Grafana旨在让您根据自己的需求进行调整。

发现和负载平衡类别通常是您开始的地方。服务是您的Kubernetes集群的公共接口。严重的问题将影响您的服务,从而影响您的用户:

当您通过单击服务进行深入了解时,您将获得有关服务的一些信息(最重要的是标签选择器)和一个Pods视图。

迄今为止,我最喜欢的工具,当我只想知道集群中发生了什么时,就是Kubernetes仪表板。以下是几个原因:

它是内置的(始终与Kubernetes同步和测试)

它很快

它提供了一个直观的深入界面,从集群级别一直到单个容器

它不需要任何定制或配置

您还可以通过上传适当的YAML或JSON文件,使用仪表板部署应用程序并创建任何Kubernetes资源,但我不会涉及这个,因为这对于可管理的基础设施来说是一种反模式。在玩测试集群时可能有用,但对于实际修改集群状态,我更喜欢使用命令行。您的情况可能有所不同。

让我们先找到端口:

有几个顶层类别:

集群

概述

工作负载

发现和负载平衡

配置和存储

您还可以通过特定命名空间过滤所有内容或选择所有命名空间。

集群视图有五个部分:命名空间、节点、持久卷、角色和存储类。它主要是观察集群的物理资源:

一眼就可以获得大量信息:所有节点的CPU和内存使用情况,可用的命名空间,它们的状态和年龄。对于每个节点,您可以看到它的年龄、标签,以及它是否准备就绪。如果有持久卷和角色,您也会看到它们,然后是存储类(在这种情况下只是主机路径)。

如果我们深入节点并点击minikube节点本身,我们会得到有关该节点和分配资源的详细信息,以一个漂亮的饼图显示。这对处理性能问题至关重要。如果一个节点没有足够的资源,那么它可能无法满足其pod的需求:

如果您向下滚动,您会看到更多有趣的信息。条件窗格是最重要的地方。您可以清晰、简洁地查看每个节点的内存和磁盘压力:

还有Pods和Events窗格。我们将在下一节讨论pod。

工作负载类别是主要类别。它组织了许多类型的Kubernetes资源,如CronJobs、DaemonSets、Deployments、Jobs、Pods、ReplicaSets、ReplicationControllers和StatefulSets。您可以沿着任何这些维度进行深入。这是默认命名空间的顶级工作负载视图,目前只部署了echo服务。您可以看到部署、副本集和pod:

让我们切换到所有命名空间并深入研究Pods子类别。这是一个非常有用的视图。在每一行中,您可以看出pod是否正在运行,它重新启动了多少次,它的IP,甚至嵌入了CPU和内存使用历史记录作为漂亮的小图形:

您也可以通过点击文本符号(从右边数第二个)查看任何pod的日志。让我们检查InfluxDBpod的日志。看起来一切都井井有条,Heapster成功地向其写入:

还有一个我们尚未探讨的更详细的层次。我们可以进入容器级别。让我们点击kubednspod。我们得到以下屏幕,显示了各个容器及其run命令;我们还可以查看它们的日志:

中央日志记录或集群级日志记录是任何具有多个节点、pod或容器的集群的基本要求。首先,单独查看每个pod或容器的日志是不切实际的。您无法获得系统的全局图片,而且将有太多的消息需要筛选。您需要一个聚合日志消息并让您轻松地切片和切块的解决方案。第二个原因是容器是短暂的。有问题的pod通常会死掉,它们的复制控制器或副本集将启动一个新实例,丢失所有重要的日志信息。通过记录到中央日志记录服务,您可以保留这些关键的故障排除信息。

在概念上,中央日志非常简单。在每个节点上,您运行一个专用代理,拦截节点上所有pod和容器的所有日志消息,并将它们连同足够的元数据发送到一个中央存储库,其中它们被安全地存储。

它被安装为Elasticsearch和Kibana的一组服务,并且在每个节点上安装了fluentd代理。

CPU问题

内存问题

磁盘问题

内核死锁

损坏的文件系统

Docker守护进程问题

kubelet和cAdvisor无法检测到这些问题,需要另一个解决方案。进入节点问题检测器。

节点问题检测器是在每个节点上运行的一个pod。它需要解决一个困难的问题。它需要检测不同环境、不同硬件和不同操作系统上的各种问题。它需要足够可靠,不受影响(否则,它无法报告问题),并且需要具有相对较低的开销,以避免向主节点发送大量信息。此外,它需要在每个节点上运行。Kubernetes最近收到了一个名为DaemonSet的新功能,解决了最后一个问题。

DaemonSet是每个节点的一个pod。一旦定义了DaemonSet,集群中添加的每个节点都会自动获得一个pod。如果该pod死掉,Kubernetes将在该节点上启动该pod的另一个实例。可以将其视为带有1:1节点-pod亲和性的复制控制器。节点问题检测器被定义为一个DaemonSet,这与其要求完全匹配。可以使用亲和性、反亲和性和污点来更精细地控制DaemonSet的调度。

节点问题检测器的问题(双关语)在于它需要处理太多问题。试图将所有这些问题都塞进一个代码库中会导致一个复杂、臃肿且永远不稳定的代码库。节点问题检测器的设计要求将报告节点问题的核心功能与特定问题检测分离开来。报告API基于通用条件和事件。问题检测应该由单独的问题守护程序(每个都在自己的容器中)来完成。这样,就可以添加和演进新的问题检测器,而不会影响核心节点问题检测器。此外,控制平面可能会有一个补救控制器,可以自动解决一些节点问题,从而实现自愈。

在这个阶段(Kubernetes1.10),问题守护程序已经嵌入到节点问题检测器二进制文件中,并且它们作为Goroutines执行,因此您还没有获得松耦合设计的好处。

在这一部分,我们涵盖了节点问题的重要主题,这可能会妨碍工作负载的成功调度,以及节点问题检测器如何帮助解决这些问题。在下一节中,我们将讨论各种故障场景以及如何使用Heapster、中央日志、Kubernetes仪表板和节点问题检测器进行故障排除。

当您想设计一个强大的系统时,首先需要了解可能的故障模式,每种故障的风险/概率以及每种故障的影响/成本。然后,您可以考虑各种预防和缓解措施、损失削减策略、事件管理策略和恢复程序。最后,您可以制定一个与风险相匹配的缓解方案,包括成本。全面的设计很重要,并且需要随着系统的发展而进行更新。赌注越高,您的计划就应该越彻底。这个过程必须为每个组织量身定制。错误恢复和健壮性的一个角落是检测故障并能够进行故障排除。以下小节描述了常见的故障类别,如何检测它们以及在哪里收集额外信息。

Kubernetes中的硬件故障可以分为两组:

节点无响应

节点有响应

当节点无响应时,有时很难确定是网络问题、配置问题还是实际的硬件故障。显然,您无法使用节点本身的日志或运行诊断。你能做什么?首先,考虑节点是否曾经有响应。如果这是一个刚刚添加到集群中的节点,更有可能是配置问题。如果这是集群中的一个节点,那么您可以查看来自Heapster或中央日志的节点的历史数据,并查看日志中是否有任何错误或性能下降的迹象,这可能表明硬件故障。

Kubernetes是一个多租户系统。它旨在高效利用资源,但是它根据命名空间中可用配额和限制以及pod和容器对保证资源的请求之间的一套检查和平衡系统来调度pod并分配资源。我们将在本书的后面深入讨论细节。在这里,我们只考虑可能出现的问题以及如何检测它。您可能会遇到几种不良结果:

资源不足:如果一个pod需要一定数量的CPU或内存,而没有可用容量的节点,那么该pod就无法被调度。

节点配置不匹配:一个需要大量CPU但很少内存的pod可能被调度到一个高内存的节点上,并使用所有的CPU资源,从而占用了节点,因此无法调度其他pod,但未使用的内存却被浪费了。

查看仪表板是一种通过视觉寻找可疑情况的好方法。过度订阅或资源利用不足的节点和pod都是配额和资源请求不匹配的候选者。

一旦您检测到一个候选项,您可以深入使用describe命令来查看节点或pod级别。在大规模集群中,您应该有自动化检查,以比较利用率与容量规划。这很重要,因为大多数大型系统都有一定程度的波动,而不会期望均匀的负载。确保您了解系统的需求,并且您的集群容量在正常范围内或可以根据需要弹性调整。

错误的配置是一个总称。您的Kubernetes集群状态是配置;您的容器的命令行参数是配置;Kubernetes、您的应用服务和任何第三方服务使用的所有环境变量都是配置;所有配置文件都是配置。在一些数据驱动的系统中,配置存储在各种数据存储中。配置问题非常常见,因为通常没有建立良好的实践来测试它们。它们通常具有各种回退(例如,配置文件的搜索路径)和默认值,并且生产环境配置与开发或暂存环境不同。

在Kubernetes集群级别,可能存在许多可能的配置问题,如下所示:

节点、pod或容器的标签不正确

在没有复制控制器的情况下调度pod

服务端口的规范不正确

不正确的ConfigMap

大多数这些问题可以通过拥有适当的自动化部署流程来解决,但您必须深入了解您的集群架构以及Kubernetes资源如何配合。

配置问题通常发生在您更改某些内容之后。在每次部署或手动更改集群后,验证其状态至关重要。

Heapster和仪表板在这里是很好的选择。我建议从服务开始,并验证它们是否可用、响应和功能正常。然后,您可以深入了解系统是否也在预期的性能参数范围内运行。

日志还提供了有用的提示,并可以确定特定的配置选项。

大型集群并不便宜。特别是在云中运行时。操作大规模系统的一个重要部分是跟踪开支。

云的最大好处之一是它可以满足弹性需求,满足系统根据需要自动扩展和收缩,通过根据需要分配和释放资源。Kubernetes非常适合这种模型,并且可以扩展以根据需要提供更多节点。风险在于,如果不适当地限制,拒绝服务攻击(恶意的、意外的或自我造成的)可能导致昂贵资源的任意分配。这需要仔细监控,以便及早发现。命名空间的配额可以避免这种情况,但您仍然需要能够深入了解并准确定位核心问题。根本原因可能是外部的(僵尸网络攻击),配置错误,内部测试出错,或者是检测或分配资源的代码中的错误。

混合集群在裸金属和云上运行(可能还在私人托管服务上)。考虑因素是相似的,但您可能需要汇总您的分析。我们将在稍后更详细地讨论混合集群。

Heapster和Kubernetes默认的监控和日志记录是一个很好的起点。然而,Kubernetes社区充满了创新,有几种替代方案可供选择。其中最受欢迎的解决方案之一是Prometheus。在本节中,我们将探索运营商的新世界,Prometheus运营商,如何安装它以及如何使用它来监视您的集群。

运营商是一种新型软件,它封装了在Kubernetes之上开发、管理和维护应用程序所需的操作知识。这个术语是由CoreOS在2016年底引入的。运营商是一个特定于应用程序的控制器,它扩展了KubernetesAPI,以代表Kubernetes用户创建、配置和管理复杂有状态应用程序的实例。它建立在基本的Kubernetes资源和控制器概念之上,但包括领域或应用程序特定的知识,以自动化常见任务。

Kubernetes具有内置功能来支持Prometheus指标,而Prometheus对Kuberneres的认识不断改进。PrometheusOperator将所有这些监控功能打包成一个易于安装和使用的捆绑包。

安装Prometheus的最简单方法是使用kube-prometheus。它使用PrometheusOperator以及Grafana进行仪表板和AlertManager的管理。要开始,请克隆存储库并运行deploy脚本:

PrometheusOperator本身

Prometheusnode_exporter

kube-statemetrics

覆盖监控所有Kubernetes核心组件和导出器的Prometheus配置

集群组件健康的默认一组警报规则

为集群指标提供仪表板的Grafana实例

一个由三个节点组成的高可用性Alertmanager集群

让我们验证一切是否正常:

>kgpo--namespace=monitoringNAMEREADYSTATUSRESTARTSAGEalertmanager-main-02/2Running01halertmanager-main-12/2Running01halertmanager-main-20/2Pending01hgrafana-7d966ff57-rvpwk2/2Running01hkube-state-metrics-5dc6c89cd7-s9n4m2/2Running01hnode-exporter-vfbhq1/1Running01hprometheus-k8s-02/2Running01hprometheus-k8s-12/2Running01hprometheus-operator-66578f9cd9-5t6xw1/1Running01h请注意,alertmanager-main-2处于挂起状态。我怀疑这是由于Minikube在两个核心上运行。在我的设置中,这实际上并没有造成任何问题。

一旦PrometheusOperator与Grafana和Alertmanager一起运行,您就可以访问它们的UI并与不同的组件进行交互:

节点端口30900上的PrometheusUI

节点端口30903上的AlertmanagerUI

节点端口30902上的Grafana

要将视图限制为prometheus-k8s服务的仅0.99分位数,请使用以下查询:

Alertmanager是Prometheus监控故事的另一个重要部分。这是一个WebUI的截图,让您可以根据任意指标定义和配置警报。

在本章中,我们讨论了监控、日志记录和故障排除。这是操作任何系统的关键方面,特别是像Kubernetes这样有许多移动部件的平台。每当我负责某件事情时,我最担心的是出现问题,而我没有系统化的方法来找出问题所在以及如何解决它。Kubernetes内置了丰富的工具和设施,如Heapster、日志记录、DaemonSets和节点问题检测器。您还可以部署任何您喜欢的监控解决方案。

在第四章中,高可用性和可靠性,我们将看到高可用和可扩展的Kubernetes集群。这可以说是Kubernetes最重要的用例,它在与其他编排解决方案相比的时候表现出色。

在上一章中,我们讨论了监视您的Kubernetes集群,在节点级别检测问题,识别和纠正性能问题以及一般故障排除。

在本章中,我们将深入探讨高度可用的集群主题。这是一个复杂的主题。Kubernetes项目和社区尚未就实现高可用性的真正方式达成一致。高度可用的Kubernetes集群有许多方面,例如确保控制平面在面对故障时能够继续运行,保护etcd中的集群状态,保护系统的数据,并快速恢复容量和/或性能。不同的系统将有不同的可靠性和可用性要求。如何设计和实现高度可用的Kubernetes集群将取决于这些要求。

在这一部分,我们将通过探索可靠和高可用系统的概念和构建模块来开始我们的高可用性之旅。百万(万亿?)美元的问题是,我们如何从不可靠的组件构建可靠和高可用的系统?组件会失败,你可以把它带到银行;硬件会失败;网络会失败;配置会出错;软件会有bug;人会犯错误。接受这一点,我们需要设计一个系统,即使组件失败,也能可靠和高可用。这个想法是从冗余开始,检测组件故障,并快速替换坏组件。

冗余是可靠和高可用系统在硬件和数据级别的基础。如果关键组件失败并且您希望系统继续运行,您必须准备好另一个相同的组件。Kubernetes本身通过复制控制器和副本集来管理无状态的pod。然而,您的etcd中的集群状态和主要组件本身需要冗余以在某些组件失败时继续运行。此外,如果您的系统的重要组件没有受到冗余存储的支持(例如在云平台上),那么您需要添加冗余以防止数据丢失。

热插拔是指在不关闭系统的情况下替换失败的组件的概念,对用户的中断最小(理想情况下为零)。如果组件是无状态的(或其状态存储在单独的冗余存储中),那么热插拔新组件来替换它就很容易,只需要将所有客户端重定向到新组件。然而,如果它存储本地状态,包括内存中的状态,那么热插拔就很重要。有以下两个主要选项:

放弃在飞行中的交易

保持热备份同步

第一个解决方案要简单得多。大多数系统都足够弹性,可以应对故障。客户端可以重试失败的请求,而热插拔的组件将为它们提供服务。

第二个解决方案更加复杂和脆弱,并且会产生性能开销,因为每次交互都必须复制到两个副本(并得到确认)。对于系统的某些部分可能是必要的。

领导者或主选举是分布式系统中常见的模式。您经常有多个相同的组件协作和共享负载,但其中一个组件被选为领导者,并且某些操作通过领导者进行序列化。您可以将具有领导者选举的分布式系统视为冗余和热插拔的组合。这些组件都是冗余的,当当前领导者失败或不可用时,将选举出一个新的领导者并进行热插拔。

负载平衡是指在多个组件之间分配服务传入请求的工作负载。当一些组件失败时,负载平衡器必须首先停止向失败或不可达的组件发送请求。第二步是提供新的组件来恢复容量并更新负载平衡器。Kubernetes通过服务、端点和标签提供了支持这一点的很好的设施。

许多类型的故障都可能是暂时的。这在网络问题或过于严格的超时情况下最常见。不响应健康检查的组件将被视为不可达,另一个组件将取而代之。原本计划发送到可能失败的组件的工作可能被发送到另一个组件,但原始组件可能仍在工作并完成相同的工作。最终结果是相同的工作可能会执行两次。很难避免这种情况。为了支持精确一次语义,您需要付出沉重的代价,包括开销、性能、延迟和复杂性。因此,大多数系统选择支持至少一次语义,这意味着可以多次执行相同的工作而不违反系统的数据完整性。这种属性被称为幂等性。幂等系统在多次执行操作时保持其状态。

当动态系统中发生组件故障时,通常希望系统能够自我修复。Kubernetes复制控制器和副本集是自愈系统的很好例子,但故障可能远不止于Pod。在上一章中,我们讨论了资源监控和节点问题检测。补救控制器是自愈概念的一个很好的例子。自愈始于自动检测问题,然后是自动解决。配额和限制有助于创建检查和平衡,以确保自动自愈不会因不可预测的情况(如DDOS攻击)而失控。

在本节中,我们考虑了创建可靠和高可用系统涉及的各种概念。在下一节中,我们将应用它们,并展示部署在Kubernetes集群上的系统的最佳实践。

构建可靠和高可用的分布式系统是一项重要的工作。在本节中,我们将检查一些最佳实践,使基于Kubernetes的系统能够可靠地运行,并在面对各种故障类别时可用。

要创建一个高可用的Kubernetes集群,主要组件必须是冗余的。这意味着etcd必须部署为一个集群(通常跨三个或五个节点),KubernetesAPI服务器必须是冗余的。辅助集群管理服务,如Heapster的存储,如果必要的话也可以部署为冗余。以下图表描述了一个典型的可靠和高可用的Kubernetes集群。有几个负载均衡的主节点,每个节点都包含整个主要组件以及一个etcd组件:

这不是配置高可用集群的唯一方式。例如,您可能更喜欢部署一个独立的etcd集群,以优化机器的工作负载,或者如果您的etcd集群需要比其他主节点更多的冗余。

自托管的Kubernetes,其中控制平面组件部署为集群中的pod和有状态集,是简化控制平面组件的健壮性、灾难恢复和自愈的一个很好的方法,通过将Kubernetes应用于Kubernetes。

节点会失败,或者一些组件会失败,但许多故障是暂时的。基本的保证是确保Docker守护程序(或任何CRI实现)和Kubelet在发生故障时能够自动重启。

如果您运行CoreOS,一个现代的基于Debian的操作系统(包括Ubuntu>=16.04),或者任何其他使用systemd作为其init机制的操作系统,那么很容易将Docker和kubelet部署为自启动守护程序:

systemctlenabledockersystemctlenablekublet对于其他操作系统,Kubernetes项目选择了monit作为高可用示例,但您可以使用任何您喜欢的进程监视器。

Kubernetes集群状态存储在etcd中。etcd集群被设计为超级可靠,并分布在多个节点上。利用这些功能对于一个可靠和高可用的Kubernetes集群非常重要。

您的etcd集群中应该至少有三个节点。如果您需要更可靠和冗余性,可以增加到五个、七个或任何其他奇数节点。节点数量必须是奇数,以便在网络分裂的情况下有明确的多数。

为了创建一个集群,etcd节点应该能够发现彼此。有几种方法可以实现这一点。我建议使用CoreOS的优秀的etcd-operator:

操作员负责处理etcd操作的许多复杂方面,例如:

创建和销毁

调整大小

故障转移

滚动升级

备份和恢复

然后,初始化helm:

#Wideopenaccesstothecluster(mostlyforkubelet)kind:ClusterRoleapiVersion:rbac.authorization.k8s.io/v1beta1metadata:name:cluster-writerrules:-apiGroups:["*"]resources:["*"]verbs:["*"]-nonResourceURLs:["*"]verbs:["*"]---#Fullreadaccesstotheapiandresourceskind:ClusterRoleapiVersion:rbac.authorization.k8s.io/v1beta1metadata:name:cluster-readerrules:-apiGroups:["*"]resources:["*"]verbs:["get","list","watch"]-nonResourceURLs:["*"]verbs:["*"]---#Giveadmin,kubelet,kube-system,kube-proxygodaccesskind:ClusterRoleBindingapiVersion:rbac.authorization.k8s.io/v1beta1metadata:name:cluster-writesubjects:-kind:Username:admin-kind:Username:kubelet-kind:ServiceAccountname:defaultnamespace:kube-system-kind:Username:kube-proxyroleRef:kind:ClusterRolename:cluster-writerapiGroup:rbac.authorization.k8s.io您可以像应用其他Kubernetes清单一样应用它:

kubectlapply-frbac.yaml.现在,我们终于可以安装etcd-operator了。我使用x作为一个简短的发布名称,以使输出更简洁。您可能希望使用更有意义的名称:

apiVersion:"etcd.database.coreos.com/v1beta2"kind:"EtcdCluster"metadata:name:"etcd-cluster"spec:size:3version:"3.2.13"要创建集群类型:

>kcreate-fetcd-cluster.yamletcdcluster"etcd-cluster"createdLet'sverifytheclusterpodswerecreatedproperly:>kgetpods|grepetcd-clusteretcd-cluster-00001/1Running04metcd-cluster-00011/1Running04metcd-cluster-00021/1Running04m验证etcd集群一旦etcd集群正常运行,您可以访问etcdctl工具来检查集群状态和健康状况。Kubernetes允许您通过exec命令(类似于Dockerexec)直接在pod或容器内执行命令。

以下是如何检查集群是否健康:

>kexecetcd-cluster-0000etcdctlsettest"Yeah,itworks"Yeah,itworks>kexecetcd-cluster-0000etcdctlgettest是的,它有效!

保护集群状态和配置非常重要,但更重要的是保护您自己的数据。如果一些方式集群状态被损坏,您可以始终从头开始重建集群(尽管在重建期间集群将不可用)。但如果您自己的数据被损坏或丢失,您将陷入深深的麻烦。相同的规则适用;冗余是王道。然而,尽管Kubernetes集群状态非常动态,但您的许多数据可能不太动态。例如,许多历史数据通常很重要,可以进行备份和恢复。实时数据可能会丢失,但整个系统可以恢复到较早的快照,并且只会遭受暂时的损害。

API服务器是无状态的,可以从etcd集群中动态获取所有必要的数据。这意味着您可以轻松地运行多个API服务器,而无需在它们之间进行协调。一旦有多个API服务器运行,您可以在它们前面放置一个负载均衡器,使客户端对此毫无察觉。

一些主要组件,如调度程序和控制器管理器,不能同时处于活动状态。这将是一片混乱,因为多个调度程序尝试将相同的pod调度到多个节点或多次调度到同一节点。拥有高度可扩展的Kubernetes集群的正确方法是使这些组件以领导者选举模式运行。这意味着多个实例正在运行,但一次只有一个实例处于活动状态,如果它失败,另一个实例将被选为领导者并接替其位置。

Kubernetes通过leader-elect标志支持这种模式。调度程序和控制器管理器可以通过将它们各自的清单复制到/etc/kubernetes/manifests来部署为pod。

以下是调度程序清单中显示标志使用的片段:

command:-/bin/sh--c-/usr/local/bin/kube-scheduler--master=127.0.0.1:8080--v=2--leader-elect=true1>>/var/log/kube-scheduler.log2>&1以下是控制器管理器清单中显示标志使用的片段:

-command:-/bin/sh--c-/usr/local/bin/kube-controller-manager--master=127.0.0.1:8080--cluster-name=e2e-test-bburns--cluster-cidr=10.245.0.0/16--allocate-node-cidrs=true--cloud-provider=gce--service-account-private-key-file=/srv/kubernetes/server.key--v=2--leader-elect=true1>>/var/log/kube-controller-manager.log2>&1image:gcr.io/google_containers/kube-controller-manager:fda24638d51a48baa13c35337fcd4793请注意,这些组件无法像其他pod一样由Kubernetes自动重新启动,因为它们正是负责重新启动失败的pod的Kubernetes组件,因此如果它们失败,它们无法重新启动自己。必须已经有一个准备就绪的替代品正在运行。

领导者选举对你的应用也可能非常有用,但实现起来非常困难。幸运的是,Kubernetes来拯救了。有一个经过记录的程序,可以通过Google的leader-elector容器来支持你的应用进行领导者选举。基本概念是使用Kubernetes端点结合ResourceVersion和Annotations。当你将这个容器作为你的应用pod的sidecar时,你可以以非常简化的方式获得领导者选举的能力。

让我们使用三个pod运行leader-elector容器,并进行名为election的选举:

>kubectlgetpods|grepelectleader-elector-57746fd798-7s8861/1Running039sleader-elector-57746fd798-d94zx1/1Running039sleader-elector-57746fd798-xcljl1/1Running039s好了。但是谁是主人?让我们查询选举端点:

>kubectlgetendpointselection-ojson|jq-r.metadata.annotations[]|jq.holderIdentity"leader-elector-57746fd798-xcljl"为了证明领导者选举有效,让我们杀死领导者,看看是否选举出了新的领导者:

>kubectldeletepodleader-elector-916043122-10wjjpod"leader-elector-57746fd798-xcljl"deleted我们有了一个新的领导者:

>kubectlgetendpointselection-ojson|jq-r.metadata.annotations[]|jq.holderIdentity"leader-elector-57746fd798-d94zx"你也可以通过HTTP找到领导者,因为每个leader-elector容器都通过一个本地web服务器(运行在端口4040上)来暴露领导者,尽管一个代理:

高可用性的设置很重要。如果您费心设置高可用性,这意味着存在高可用性系统的业务案例。因此,您希望在部署到生产环境之前测试可靠且高可用的集群(除非您是Netflix,在那里您在生产环境中进行测试)。此外,理论上,对集群的任何更改都可能破坏高可用性,而不会影响其他集群功能。关键点是,就像其他任何事物一样,如果您不进行测试,就假设它不起作用。

我们已经确定您需要测试可靠性和高可用性。最好的方法是创建一个尽可能接近生产环境的分阶段环境。这可能会很昂贵。有几种方法可以管理成本:

临时高可用性(HA)分阶段环境:仅在HA测试期间创建一个大型HA集群

将HA测试与性能和压力测试相结合:在性能和压力测试结束时,超载系统,看可靠性和高可用性配置如何处理负载

测试高可用性需要计划和对系统的深入了解。每项测试的目标是揭示系统设计和/或实施中的缺陷,并提供足够的覆盖范围,如果测试通过,您将对系统的行为感到满意。

在可靠性和高可用性领域,这意味着您需要找出破坏系统并观察其自我修复的方法。

这需要几个部分,如下:

可能故障的全面列表(包括合理的组合)

对于每种可能的故障,系统应该如何做出清晰的响应

诱发故障的方法

观察系统反应的方法

这些部分都不是微不足道的。根据我的经验,最好的方法是逐步进行,并尝试提出相对较少的通用故障类别和通用响应,而不是详尽且不断变化的低级故障列表。

请注意,有时故障无法在单一响应中解决。例如,在我们的节点无响应案例中,如果是硬件故障,那么重启是无济于事的。在这种情况下,第二种响应方式就会发挥作用,也许会启动、配置并连接到节点的新虚拟机。在这种情况下,您不能太通用,可能需要为节点上的特定类型的pod/角色创建测试(etcd、master、worker、数据库和监控)。

最后,一个重要的观点是尽量不要侵入。这意味着,理想情况下,您的生产系统不会具有允许关闭部分系统或导致其配置为以减少容量运行进行测试的测试功能。原因是这会增加系统的攻击面,并且可能会因配置错误而意外触发。理想情况下,您可以在不修改将部署在生产环境中的代码或配置的情况下控制测试环境。使用Kubernetes,通常很容易向暂存环境中的pod和容器注入自定义测试功能,这些功能可以与系统组件交互,但永远不会部署在生产环境中。

在本节中,我们将讨论如何使用各种策略升级您的集群,例如滚动升级和蓝绿升级。我们还将讨论何时适合引入破坏性升级与向后兼容升级。然后,我们将进入关键的模式和数据迁移主题。

滚动升级是逐渐将组件从当前版本升级到下一个版本的升级。这意味着您的集群将同时运行当前版本和新版本的组件。这里有两种情况需要考虑:

新组件向后兼容

新组件不向后兼容

如果新组件向后兼容,那么升级应该非常容易。在Kubernetes的早期版本中,您必须非常小心地使用标签管理滚动升级,并逐渐改变旧版本和新版本的副本数量(尽管kubectl滚动更新是复制控制器的便捷快捷方式)。但是,在Kubernetes1.2中引入的部署资源使其变得更加容易,并支持副本集。它具有以下内置功能:

运行服务器端(如果您的机器断开连接,它将继续运行)

版本控制

多个并发部署

更新部署

汇总所有pod的状态

回滚

金丝雀部署

多种升级策略(滚动升级是默认值)

这是一个部署三个NGINXpod的部署示例清单:

apiVersion:extensions/v1beta1kind:Deploymentmetadata:name:nginx-deploymentspec:replicas:3template:metadata:labels:app:nginxspec:containers:-name:nginximage:nginx:1.7.9ports:-containerPort:80资源种类是部署,它的名称是nginx-deployment,您可以在以后引用此部署(例如,用于更新或回滚)。最重要的部分当然是规范,其中包含一个pod模板。副本确定集群中将有多少个pod,并且模板规范包含每个容器的配置:在这种情况下,只有一个容器。

要开始滚动更新,您需要创建部署资源:

$kubectlcreate-fnginx-deployment.yaml--record您可以稍后使用以下命令查看部署的状态:

$kubectlrolloutstatusdeployment/nginx-deployment复杂的部署当您只想升级一个pod时,部署资源非常有用,但您经常需要升级多个pod,并且这些pod有时存在版本相互依赖关系。在这种情况下,有时您必须放弃滚动更新或引入临时兼容层。例如,假设服务A依赖于服务B。服务B现在有一个破坏性的变化。服务A的v1pod无法与服务B的v2pod进行交互操作。从可靠性和变更管理的角度来看,让服务B的v2pod支持旧的和新的API是不可取的。在这种情况下,解决方案可能是引入一个适配器服务,该服务实现了B服务的v1API。该服务将位于A和B之间,并将跨版本转换请求和响应。这增加了部署过程的复杂性,并需要多个步骤,但好处是A和B服务本身很简单。您可以在不兼容版本之间进行滚动更新,一旦所有人升级到v2(所有Apod和所有Bpod),所有间接性都将消失。

滚动更新对可用性来说非常好,但有时管理正确的滚动更新涉及的复杂性被认为太高,或者它增加了大量工作,推迟了更重要的项目。在这些情况下,蓝绿升级提供了一个很好的替代方案。使用蓝绿发布,您准备了一个完整的生产环境的副本,其中包含新版本。现在你有两个副本,旧的(蓝色)和新的(绿色)。蓝色和绿色哪个是哪个并不重要。重要的是你有两个完全独立的生产环境。目前,蓝色是活动的并处理所有请求。您可以在绿色上运行所有测试。一旦满意,您可以切换绿色变为活动状态。如果出现问题,回滚同样容易;只需从绿色切换回蓝色。我在这里优雅地忽略了存储和内存状态。这种立即切换假设蓝色和绿色只由无状态组件组成,并共享一个公共持久层。

如果存储发生了变化或对外部客户端可访问的API发生了破坏性变化,则需要采取额外的步骤。例如,如果蓝色和绿色有自己的存储,那么所有传入的请求可能需要同时发送到蓝色和绿色,并且绿色可能需要从蓝色那里摄取历史数据以在切换之前同步。

数据合同描述数据的组织方式。这是结构元数据的一个总称。数据库模式是最典型的例子。最常见的例子是关系数据库模式。其他例子包括网络负载、文件格式,甚至字符串参数或响应的内容。如果有配置文件,那么这个配置文件既有文件格式(JSON、YAML、TOML、XML、INI和自定义格式),也有一些内部结构,描述了什么样的层次结构、键、值和数据类型是有效的。有时,数据合同是显式的,有时是隐式的。无论哪种方式,您都需要小心管理它,否则当读取、解析或验证数据时遇到不熟悉的结构时,就会出现运行时错误。

有很多机制可以将数据分割并发送到两个集群,但是我们遇到了新系统的可扩展性问题,我们必须在继续之前解决这些问题。历史数据很重要,但不必以与最近的热数据相同的服务水平访问。因此,我们着手进行了另一个项目,将历史数据发送到更便宜的存储中。当然,这意味着客户库或前端服务必须知道如何查询两个存储并合并结果。当您处理大量数据时,您不能认为任何事情都是理所当然的。您会遇到工具、基础设施、第三方依赖和流程的可扩展性问题。大规模不仅仅是数量的变化,通常也是质量的变化。不要指望一切都会顺利进行。这远不止是从A复制一些文件到B。

如果您有很多用户(或者一些非常重要的用户)使用您的API,您应该非常谨慎地考虑弃用。弃用API意味着您强迫用户更改其应用程序以与您合作,或者保持与早期版本的锁定。

有几种方法可以减轻痛苦:

不要弃用。扩展现有API或保持以前的API活动。尽管这有时很简单,但会增加测试负担。

在前一节中,我们看了现场集群升级。我们探讨了各种技术以及Kubernetes如何支持它们。我们还讨论了一些困难的问题,比如破坏性变化、数据合同变化、数据迁移和API弃用。在本节中,我们将考虑大型集群的各种选项和配置,具有不同的可靠性和高可用性属性。当您设计您的集群时,您需要了解您的选项,并根据您组织的需求进行明智的选择。

在本节中,我们将涵盖各种可用性要求,从尽力而为一直到零停机的圣杯,对于每个可用性类别,我们将考虑从性能和成本的角度来看它意味着什么。

不同的系统对可靠性和可用性有非常不同的要求。此外,不同的子系统有非常不同的要求。例如,计费系统总是高优先级,因为如果计费系统停机,您就无法赚钱。然而,即使在计费系统内部,如果有时无法争议费用,从业务角度来看可能也可以接受。

快速恢复是高可用集群的另一个重要方面。某些时候会出现问题。您的不可用时钟开始运行。您能多快恢复正常?

快速恢复的典范当然是蓝绿部署-如果在发现问题时保持之前的版本运行。

另一方面,滚动更新意味着如果问题早期被发现,那么大多数你的pod仍在运行之前的版本。

像HeptioArk这样的工具在某些情况下可以帮助,它可以创建集群的快照备份,以防出现问题并且你不确定如何解决。

尽力而为意味着,反直觉地,根本没有任何保证。如果有效,太好了!如果不起作用-哦,好吧。你打算怎么办?这种可靠性和可用性水平可能适用于经常更改的内部组件,而使它们健壮的努力不值得。这也可能适用于作为测试版发布到公众的服务。

尽力而为对开发人员来说是很好的。开发人员可以快速移动并破坏事物。他们不担心后果,也不必经历严格测试和批准的考验。尽力而为服务的性能可能比更健壮的服务更好,因为它通常可以跳过昂贵的步骤,比如验证请求、持久化中间结果和复制数据。然而,另一方面,更健壮的服务通常经过了大量优化,其支持硬件也经过了对其工作负载的精细调整。尽力而为服务的成本通常较低,因为它们不需要使用冗余,除非运营商忽视了基本的容量规划,只是不必要地过度配置。

在Kubernetes的背景下,一个重要问题是集群提供的所有服务是否都是尽力而为的。如果是这种情况,那么集群本身就不需要高可用性。你可能只需要一个单一的主节点和一个单一的etcd实例,而Heapster或其他监控解决方案可能不需要部署。

使用Kubernetes,你可以通过将所有传入的请求重定向到负载均衡器上的网页(或JSON响应)来进行维护窗口。

但在大多数情况下,Kubernetes的灵活性应该允许你进行在线维护。在极端情况下,比如升级Kubernetes版本,或从etcdv2切换到etcdv3,你可能需要使用维护窗口。蓝绿部署是另一种选择。但集群越大,蓝绿部署的成本就越高,因为你必须复制整个生产集群,这既昂贵又可能导致你遇到配额不足的问题。

最后,我们来到了零停机系统。没有所谓的零停机系统。所有系统都会失败,所有软件系统肯定会失败。有时,故障严重到足以使系统或其某些服务宕机。把零停机看作是最佳努力的分布式系统设计。你设计零停机是指你提供了大量的冗余和机制来解决预期的故障,而不会使系统宕机。一如既往,要记住,即使有零停机的商业案例,也不意味着每个组件都必须是。

零停机计划如下:

每个级别都要有冗余:这是一个必要的条件。你的设计中不能有单一的故障点,因为一旦它失败,你的系统就会宕机。

自动热插拔故障组件:冗余只有在冗余组件能够在原始组件失败后立即启动时才有效。一些组件可以共享负载(例如无状态的Web服务器),因此不需要明确的操作。在其他情况下,比如Kubernetes调度器和控制器管理器,你需要进行领导者选举,以确保集群保持运行。

部署到生产环境之前的顽强测试:全面的测试已被证明是提高质量的可靠方法。为一个运行庞大的分布式系统的大型Kubernetes集群做全面测试是一项艰苦的工作,但你需要这样做。你应该测试什么?一切。没错,为了实现零停机,你需要同时测试应用程序和基础设施。你的100%通过的单元测试是一个很好的开始,但它们并不能提供足够的信心,即当你在生产Kubernetes集群上部署应用程序时,它仍然会按预期运行。最好的测试当然是在蓝绿部署或相同集群后的生产集群上进行。如果没有完全相同的集群,可以考虑一个尽可能与生产环境相符的暂存环境。以下是你应该运行的测试列表。每个测试都应该全面,因为如果你留下一些未经测试的东西,它可能是有问题的:

验收测试

性能测试

压力测试

回滚测试

数据恢复测试

渗透测试

听起来疯狂吗?很好。零停机的大规模系统很难。微软、谷歌、亚马逊、Facebook和其他大公司有数以万计的软件工程师(合计)专门负责基础设施、运营,并确保系统正常运行。

当你开发或操作分布式系统时,CAP定理应该时刻放在脑后。CAP代表一致性(Consistency)、可用性(Availability)和分区容忍性(PartitionTolerance)。该定理表示你最多只能拥有其中的两个。由于任何分布式系统在实践中都可能遭受网络分区,你可以在CP和AP之间做出选择。CP意味着为了保持一致性,系统在网络分区发生时将不可用。AP意味着系统将始终可用,但可能不一致。例如,来自不同分区的读取可能返回不同的结果,因为其中一个分区没有接收到写入。在本节中,我们将专注于高可用系统,即AP。为了实现高可用性,我们必须牺牲一致性,但这并不意味着我们的系统将具有损坏或任意数据。关键词是最终一致性。我们的系统可能会落后一点,并提供对略微陈旧数据的访问,但最终你会得到你期望的结果。当你开始考虑最终一致性时,这将为潜在的性能改进打开大门。

举例来说,如果某个重要数值频繁更新(例如,每秒一次),但你只每分钟发送一次数值,那么你已经将网络流量减少了60倍,平均只落后实时更新30秒。这非常重要,非常巨大。你刚刚让系统能够处理60倍的用户或请求。

在本章中,我们看了可靠且高可用的大规模Kubernetes集群。这可以说是Kubernetes的甜蜜点。虽然能够编排运行少量容器的小集群很有用,但并非必需,但在大规模情况下,你必须有一个编排解决方案,可以信任其与系统一起扩展,并提供工具和最佳实践来实现这一点。

你现在对分布式系统中可靠性和高可用性的概念有了扎实的理解。你已经深入了解了运行可靠且高可用的Kubernetes集群的最佳实践。你已经探讨了活动Kubernetes集群升级的微妙之处,并且可以在可靠性和可用性水平以及其性能和成本方面做出明智的设计选择。

在第四章中,高可用性和可靠性,我们讨论了可靠且高可用的Kubernetes集群,基本概念,最佳实践,如何进行实时集群升级,以及关于性能和成本的许多设计权衡。

在本章中,我们将探讨安全这一重要主题。Kubernetes集群是由多个层次的相互作用组件组成的复杂系统。在运行关键应用程序时,不同层的隔离和分隔非常重要。为了保护系统并确保对资源、能力和数据的适当访问,我们必须首先了解Kubernetes作为一个运行未知工作负载的通用编排平台所面临的独特挑战。然后,我们可以利用各种安全、隔离和访问控制机制,确保集群和运行在其上的应用程序以及数据都是安全的。我们将讨论各种最佳实践以及何时适合使用每种机制。

在本章的结尾,您将对Kubernetes安全挑战有很好的理解。您将获得如何加固Kubernetes以抵御各种潜在攻击的实际知识,建立深度防御,并且甚至能够安全地运行多租户集群,同时为不同用户提供完全隔离以及对他们在集群中的部分拥有完全控制的能力。

Kubernetes是一个非常灵活的系统,以通用方式管理非常低级别的资源。Kubernetes本身可以部署在许多操作系统和硬件或虚拟机解决方案上,可以部署在本地或云端。Kubernetes运行由运行时实现的工作负载,通过定义良好的运行时接口与之交互,但不了解它们是如何实现的。Kubernetes操作关键资源,如网络、DNS和资源分配,代表或为了应用程序服务,而对这些应用程序一无所知。这意味着Kubernetes面临着提供良好的安全机制和能力的艰巨任务,以便应用程序管理员可以使用,同时保护自身和应用程序管理员免受常见错误的影响。

在本节中,我们将讨论Kubernetes集群的几个层次或组件的安全挑战:节点、网络、镜像、Pod和容器。深度防御是一个重要的安全概念,要求系统在每个层面都保护自己,既要减轻渗透其他层的攻击,也要限制入侵的范围和损害。认识到每个层面的挑战是向深度防御迈出的第一步。

节点是运行时引擎的主机。如果攻击者能够访问节点,这是一个严重的威胁。它至少可以控制主机本身和运行在其上的所有工作负载。但情况会变得更糟。节点上运行着一个与API服务器通信的kubelet。一个复杂的攻击者可以用修改过的版本替换kubelet,并通过与KubernetesAPI服务器正常通信来有效地逃避检测,而不是运行预定的工作负载,收集有关整个集群的信息,并通过发送恶意消息来破坏API服务器和集群的其余部分。节点将可以访问共享资源和秘密,这可能使其渗透得更深。节点入侵非常严重,既因为可能造成的损害,也因为事后很难检测到它。

另一个攻击向量是资源耗尽。想象一下,您的节点成为了一个与您的Kubernetes集群无关的机器人网络的一部分,它只运行自己的工作负载并耗尽CPU和内存。这里的危险在于Kubernetes和您的基础设施可能会自动扩展并分配更多资源。

另一个问题是安装调试和故障排除工具,或者在自动部署之外修改配置。这些通常是未经测试的,如果被遗留并激活,它们至少会导致性能下降,但也可能引起更严重的问题。至少会增加攻击面。

在涉及安全性的地方,这是一个数字游戏。您希望了解系统的攻击面以及您的脆弱性。让我们列出所有的节点挑战:

攻击者控制主机

攻击者替换kubelet

攻击者控制运行主要组件(API服务器、调度器和控制器管理器)的节点

攻击者获得对节点的物理访问权限

攻击者耗尽与Kubernetes集群无关的资源

通过安装调试和故障排除工具或更改配置造成自我伤害

暴露端口和服务的复杂链路:

容器到主机

主机到内部网络中的主机

主机到世界

使用覆盖网络(将在第十章中更多讨论,高级Kubernetes网络)可以帮助进行深度防御,即使攻击者获得对Docker容器的访问权限,它们也会被隔离,无法逃脱到底层网络基础设施。

发现组件也是一个很大的挑战。这里有几个选项,比如DNS、专用发现服务和负载均衡器。每种方法都有一套利弊,需要仔细规划和洞察力才能在您的情况下得到正确的解决方案。

确保两个容器能够找到彼此并交换信息非常重要。

敏感数据必须在进入和离开集群时进行加密,有时也需要在静态状态下进行加密。这意味着密钥管理和安全密钥交换,这是安全领域中最难解决的问题之一。

如果您的集群与其他Kubernetes集群或非Kubernetes进程共享网络基础设施,那么您必须对隔离和分离非常谨慎。

这些要素包括网络策略、防火墙规则和软件定义网络(SDN)。这个方案通常是定制的。这在本地和裸机集群中尤其具有挑战性。让我们回顾一下:

制定连接计划

选择组件、协议和端口

找出动态发现

公共与私有访问

设计防火墙规则

决定网络策略

密钥管理和交换

在网络层面,容器、用户和服务之间相互找到并交流变得更加容易,与此同时,也需要限制访问并防止网络攻击或对网络本身的攻击之间保持不断的紧张关系。

这些挑战中许多并非特定于Kubernetes。然而,Kubernetes是一个管理关键基础设施并处理低级网络的通用平台,这使得有必要考虑动态和灵活的解决方案,可以将系统特定要求整合到Kubernetes中。

Kubernetes运行符合其运行时引擎之一的容器。它不知道这些容器在做什么(除了收集指标)。您可以通过配额对容器施加一定的限制。您还可以通过网络策略限制它们对网络其他部分的访问。然而,最终,容器确实需要访问主机资源、网络中的其他主机、分布式存储和外部服务。图像决定了容器的行为。图像存在两类问题:

恶意图像

易受攻击的图像

恶意图像是包含由攻击者设计的代码或配置的图像,用于造成一些伤害或收集信息。恶意代码可以被注入到您的图像准备流水线中,包括您使用的任何图像存储库。或者,您可能安装了被攻击的第三方图像,这些图像现在可能包含恶意代码。

易受攻击的图像是您设计的图像(或您安装的第三方图像),恰好包含一些漏洞,允许攻击者控制正在运行的容器或造成其他伤害,包括以后注入他们自己的代码。

很难说哪一类更糟。在极端情况下,它们是等价的,因为它们允许完全控制容器。其他防御措施已经就位(记得深度防御吗?),并且对容器施加的限制将决定它可以造成多大的破坏。减少恶意镜像的危险非常具有挑战性。使用微服务的快速移动公司可能每天生成许多镜像。验证镜像也不是一件容易的事。例如,考虑Docker镜像由多层组成。包含操作系统的基础镜像可能在发现新漏洞时随时变得容易受攻击。此外,如果您依赖他人准备的基础镜像(非常常见),那么恶意代码可能会进入这些您无法控制并且绝对信任的基础镜像中。

总结镜像挑战:

Kubernetes不知道镜像在做什么

Kubernetes必须为指定功能提供对敏感资源的访问

保护镜像准备和交付管道(包括镜像仓库)是困难的

快速开发和部署新镜像的速度可能与仔细审查更改的冲突

包含操作系统的基础镜像很容易过时并变得容易受攻击

基础镜像通常不受您控制,更容易受到恶意代码的注入

集成静态镜像分析器,如CoreOSClair,可以帮助很多。

让我们重申挑战:

Kubernetes是远程管理的

具有远程管理访问权限的攻击者可以完全控制集群

配置和部署通常比代码更难测试

远程或外出办公的员工面临延长的暴露风险,使攻击者能够以管理员权限访问他们的笔记本电脑或手机

在Kubernetes中,pod是工作单位,包含一个或多个容器。Pod只是一个分组和部署构造,但在实践中,部署在同一个pod中的容器通常通过直接机制进行交互。所有容器共享相同的本地主机网络,并经常共享来自主机的挂载卷。同一pod中容器之间的轻松集成可能导致主机的部分暴露给所有容器。这可能允许一个恶意或易受攻击的恶意容器打开对其他容器的升级攻击的途径,然后接管节点本身。主要附加组件通常与主要组件共同存在,并呈现出这种危险,特别是因为它们中的许多是实验性的。对于在每个节点上运行pod的守护程序集也是如此。

多容器pod的挑战包括以下内容:

相同的pod容器共享本地主机网络

相同的pod容器有时会共享主机文件系统上的挂载卷

恶意容器可能会影响pod中的其他容器

如果与访问关键节点资源的其他容器共同存在,恶意容器更容易攻击节点

实验性的附加组件与主要组件共同存在时,可能是实验性的并且安全性较低

采用Kubernetes的组织面临的挑战如下:

开发速度可能被认为比安全性更重要

持续部署可能会使难以在达到生产之前检测到某些安全问题

较小的组织可能没有足够的知识和专业技能来正确管理Kubernetes集群的安全性

在本节中,我们回顾了在尝试构建安全的Kubernetes集群时所面临的许多挑战。这些挑战大多数并非特定于Kubernetes,但使用Kubernetes意味着系统的大部分是通用的,并且不知道系统正在做什么。在试图锁定系统时,这可能会带来问题。这些挑战分布在不同的层次上:

节点挑战

网络挑战

镜像挑战

配置和部署挑战

Pod和容器挑战

组织和流程挑战

在下一节中,我们将看一下Kubernetes提供的设施,以解决其中一些挑战。许多挑战需要在更大的系统级别上找到解决方案。重要的是要意识到仅仅使用所有Kubernetes安全功能是不够的。

记住,Kubernetes集群是一个更大系统的一部分,包括其他软件系统、人员和流程。Kubernetes不能解决所有问题。您应始终牢记一般安全原则,如深度防御、需要知道的基础和最小特权原则。此外,记录您认为在攻击事件中可能有用的所有内容,并设置警报,以便在系统偏离其状态时进行早期检测。这可能只是一个错误,也可能是一次攻击。无论哪种情况,您都希望了解并做出响应。

Kubernetes在集群外部管理常规用户,用于连接到集群的人员(例如,通过kubectl命令),并且它还有服务账户。

常规用户是全局的,可以访问集群中的多个命名空间。服务账户受限于一个命名空间。这很重要。它确保了命名空间的隔离,因为每当API服务器从一个pod接收到请求时,其凭据只适用于其自己的命名空间。

Kubernetes代表pod管理服务账户。每当Kubernetes实例化一个pod时,它会为pod分配一个服务账户。当pod进程与API服务器交互时,服务账户将标识所有的pod进程。每个服务账户都有一组凭据挂载在一个秘密卷中。每个命名空间都有一个名为default的默认服务账户。当您创建一个pod时,它会自动分配默认服务账户,除非您指定其他服务账户。

您可以创建额外的服务账户。创建一个名为custom-service-account.yaml的文件,其中包含以下内容:

apiVersion:v1kind:ServiceAccountmetadata:name:custom-service-accountNowtypethefollowing:kubectlcreate-fcustom-service-account.yamlThatwillresultinthefollowingoutput:serviceaccount"custom-service-account"createdHereistheserviceaccountlistedalongsidethedefaultserviceaccount:>kubectlgetserviceAccountsNAMESECRETSAGEcustom-service-account13mdefault129d请注意,为您的新服务账户自动创建了一个秘密。

要获取更多详细信息,请输入以下内容:

>kubectlgetserviceAccounts/custom-service-account-oyamlapiVersion:v1kind:ServiceAccountmetadata:creationTimestamp:2018-01-15T18:24:40Zname:custom-service-accountnamespace:defaultresourceVersion:"1974321"selfLink:/api/v1/namespaces/default/serviceaccounts/custom-service-accountuid:59bc3515-fa21-11e7-beab-080027c94384secrets:-name:custom-service-account-token-w2v7v您可以通过输入以下内容查看秘密本身,其中包括一个ca.crt文件和一个令牌:

kubectlgetsecrets/custom-service-account-token-w2v7v-oyamlKubernetes如何管理服务账户?API服务器有一个名为服务账户准入控制器的专用组件。它负责在pod创建时检查是否有自定义服务账户,如果有,则确保自定义服务账户存在。如果没有指定服务账户,则分配默认服务账户。

它还确保pod具有ImagePullSecrets,当需要从远程镜像注册表中拉取镜像时是必要的。如果pod规范没有任何密钥,它将使用服务账户的ImagePullSecrets。

最后,它添加了一个包含API访问令牌的卷和一个volumeSource挂载在/var/run/secrets/kubernetes.io/serviceaccount上。

API令牌是由另一个名为令牌控制器的组件创建并添加到密钥中,每当创建服务账户时。令牌控制器还监视密钥,并在密钥被添加或从服务账户中删除时添加或删除令牌。

服务账户控制器确保每个命名空间都存在默认的服务账户。

当您首次创建集群时,会为您创建一个客户端证书和密钥。Kubectl使用它们在端口443上通过TLS与API服务器进行身份验证,反之亦然(加密的HTTPS连接)。您可以通过检查您的.kube/config文件找到您的客户端密钥和证书:

>cat~/.kube/config|grepclientclient-certificate:/Users/gigi.sayfan/.minikube/client.crtclient-key:/Users/gigi.sayfan/.minikube/client.key请注意,如果多个用户需要访问集群,创建者应以安全的方式向其他用户提供客户端证书和密钥。

集群管理员通过向API服务器提供各种命令行参数来确定要使用的认证策略:

--client-ca-file=(用于文件中指定的x509客户端证书)

--token-auth-file=(用于文件中指定的持有者令牌)

--basic-auth-file=(用于文件中指定的用户/密码对)

--experimental-bootstrap-token-auth(用于kubeadm使用的引导令牌)

服务账户使用自动加载的认证插件。管理员可以提供两个可选标志:

--service-account-key-file=(用于签署持有者令牌的PEM编码密钥。如果未指定,将使用API服务器的TLS私钥。)

--service-account-lookup(如果启用,从API中删除的令牌将被撤销。)

还有其他几种方法,例如开放ID连接,Web钩子,Keystone(OpenStack身份服务)和认证代理。主题是认证阶段是可扩展的,并且可以支持任何认证机制。

各种认证插件将检查请求,并根据提供的凭据,将关联以下属性:

用户名(用户友好的名称)

uid(唯一标识符,比用户名更一致)

组(用户所属的一组组名)

额外字段(将字符串键映射到字符串值)

模拟用户:要扮演的用户名。

模拟组:这是要扮演的组名,可以多次提供以设置多个组。这是可选的,需要模拟用户。

模拟额外-(额外名称):这是用于将额外字段与用户关联的动态标头。这是可选的,需要模拟用户。

使用kubectl,您可以传递--as和--as-group参数。

支持以下模式:

--authorization-mode=AlwaysDeny拒绝所有请求;在测试期间很有用。

--admission-control=NamespaceLifecycle,LimitRanger,ServiceAccount,PersistentVolumeLabel,DefaultStorageClass,MutatingAdmissionWebhook,ValidatingAdmissionWebhook,ResourceQuota,DefaultTolerationSeconds让我们看一些可用的插件(随时添加更多):

AlwaysAdmit:透传(我不确定为什么需要它)。

AlwaysDeny:这拒绝一切(用于测试很有用)。

AlwaysPullImages:这将新的Pod镜像拉取策略设置为Always(在多租户集群中很有用,以确保没有凭据拉取私有镜像的Pod不使用它们)。

DefaultStorageClass:这为未指定存储类的PersistentVolumeClaim创建请求添加了一个默认存储类。

DenyEscalatingExec:这拒绝对以提升的特权运行并允许主机访问的Pod执行和附加命令。这包括以特权运行、具有主机IPC命名空间访问权限和具有主机PID命名空间访问权限的Pod。

EventRateLimit:这限制了API服务器的事件洪水(Kubernetes1.9中的新功能)。

ExtendedResourceToleration:这将节点上的污点与GPU和FPGA等特殊资源结合起来,与请求这些资源的Pod的容忍结合起来。最终结果是具有额外资源的节点将专门用于具有适当容忍的Pod。

ImagePolicyWebhook:这个复杂的插件连接到外部后端,根据镜像决定是否拒绝请求。

Initializers:这通过修改要创建的资源的元数据来设置挂起的初始化器(基于InitializerConfiguration)。

InitialResources(实验性的):如果未指定,这将根据历史使用情况分配计算资源和限制。

LimitPodHardAntiAffinity:拒绝定义了除kubernetes.io/hostname之外的反亲和拓扑键的任何Pod在requiredDuringSchedulingRequiredDuringExecution中。

LimitRanger:拒绝违反资源限制的请求。

MutatingAdmissionWebhook:按顺序调用已注册的能够修改目标对象的变异Webhook。请注意,由于其他变异Webhook的潜在更改,不能保证更改会生效。

NamespaceLifecycle:拒绝在正在终止或不存在的命名空间中创建对象。

ResourceQuota:拒绝违反命名空间资源配额的请求。

ServiceAccount:这是服务账户的自动化。

ValidatingAdmissionWebhook:此准入控制器调用与请求匹配的任何验证Webhook。匹配的Webhook会并行调用;如果其中任何一个拒绝请求,请求将失败。

这种方法让您非常有信心,您的集群只会拉取您之前审查过的镜像,并且您可以更好地管理升级。您可以在每个节点上配置$HOME/.dockercfg或$HOME/.docker/config.json。但是,在许多云提供商上,您无法这样做,因为节点是自动为您配置的。

这种方法适用于云提供商上的集群。其思想是,注册表的凭据将由pod提供,因此无论它被安排在哪个节点上运行都无所谓。这避开了节点级别的.dockercfg问题。

首先,您需要为凭据创建一个secret对象:

>kubectlcreatesecretthe-registry-secret--docker-server=--docker-username=--docker-password=--docker-email=secret"docker-registry-secret"created.如果需要,您可以为多个注册表(或同一注册表的多个用户)创建secret。kubelet将合并所有ImagePullSecrets。

然而,因为pod只能在其自己的命名空间中访问secret,所以您必须在希望pod运行的每个命名空间中创建一个secret。

一旦定义了secret,您可以将其添加到pod规范中,并在集群上运行一些pod。pod将使用secret中的凭据从目标镜像注册表中拉取镜像:

apiVersion:v1kind:Podmetadata:name:cool-podnamespace:the-namespacespec:containers:-name:cool-containerimage:cool/app:v1imagePullSecrets:-name:the-registry-secret指定安全上下文安全上下文是一组操作系统级别的安全设置,例如UID、gid、功能和SELinux角色。这些设置应用于容器级别作为容器安全内容。您可以指定将应用于pod中所有容器的pod安全上下文。pod安全上下文还可以将其安全设置(特别是fsGroup和seLinuxOptions)应用于卷。

以下是一个示例pod安全上下文:

apiVersion:v1kind:Podmetadata:name:hello-worldspec:containers:...securityContext:fsGroup:1234supplementalGroups:[5678]seLinuxOptions:level:"s0:c123,c456"容器安全上下文应用于每个容器,并覆盖了pod安全上下文。它嵌入在pod清单的容器部分中。容器上下文设置不能应用于卷,卷保持在pod级别。

以下是一个示例容器安全内容:

apiVersion:v1kind:Podmetadata:name:hello-worldspec:containers:-name:hello-world-container#Thecontainerdefinition#...securityContext:privileged:trueseLinuxOptions:level:"s0:c123,c456"使用AppArmor保护您的集群AppArmor是一个Linux内核安全模块。使用AppArmor,您可以限制在容器中运行的进程对一组有限的资源的访问,例如网络访问、Linux功能和文件权限。您可以通过配置AppArmor来配置配置文件。

在Kubernetes1.4中,AppArmor支持作为beta版本添加。它并不适用于每个操作系统,因此您必须选择一个受支持的操作系统发行版才能利用它。Ubuntu和SUSELinux支持AppArmor,并默认启用。其他发行版则具有可选的支持。要检查AppArmor是否已启用,请输入以下代码:

cat/sys/module/apparmor/parameters/enabledY如果结果是Y,则已启用。

配置文件必须加载到内核中。请检查以下文件:

/sys/kernel/security/apparmor/profiles此时,只有Docker运行时支持AppArmor。

由于AppArmor仍处于beta阶段,因此您需要将元数据指定为注释,而不是bonafide字段;当它退出beta阶段时,这将发生变化。

要将配置文件应用于容器,请添加以下注释:

container.apparmor.security.beta.kubernetes.io/:配置文件引用可以是默认配置文件,runtime/default,或者主机localhost/上的配置文件。

以下是一个防止写入文件的示例配置文件:

#includeprofilek8s-apparmor-example-deny-writeflags=(attach_disconnected){#includefile,#Denyallfilewrites.deny/**w,}AppArmor不是Kubernetes资源,因此其格式不是您熟悉的YAML或JSON。

要验证配置文件是否正确附加,请检查进程1的属性:

kubectlexeccat/proc/1/attr/current默认情况下,Pod可以在集群中的任何节点上调度。这意味着配置文件应该加载到每个节点中。这是DaemonSet的一个经典用例。

手动编写AppArmor配置文件很重要。有一些工具可以帮助:aa-genprof和aa-logprof可以为您生成配置文件,并通过在应用程序中使用AppArmor的complain模式来帮助微调它。这些工具会跟踪应用程序的活动和AppArmor警告,并创建相应的配置文件。这种方法有效,但感觉有些笨拙。

Name="nginx-sample"[Filesystem]#readonlypathsforthecontainerReadOnlyPaths=["/bin/**","/boot/**","/dev/**",]#pathswhereyouwanttologonwriteLogOnWritePaths=["/**"]#allowedcapabilities[Capabilities]Allow=["chown","setuid",][Network]Raw=falsePacket=falseProtocols=["tcp","udp","icmp"]生成的AppArmor配置文件相当复杂。

Pod安全策略(PSP)自Kubernetes1.4以来就作为Beta版本可用。必须启用它,并且还必须启用PSP准入控制来使用它们。PSP在集群级别定义,并为Pod定义安全上下文。使用PSP和直接在Pod清单中指定安全内容之间有一些区别,就像我们之前所做的那样:

将相同的策略应用于多个Pod或容器

让管理员控制Pod的创建,以便用户不会创建具有不适当安全上下文的Pod

通过准入控制器为Pod动态生成不同的安全内容

PSPs真的扩展了安全上下文的概念。通常,与Pod(或者说,Pod模板)的数量相比,您将拥有相对较少的安全策略。这意味着许多Pod模板和容器将具有相同的安全策略。没有PSP,您必须为每个Pod清单单独管理它。

这是一个允许一切的示例PSP:

kind:ClusterRoleBindingapiVersion:rbac.authorization.k8s.io/v1metadata:name:roleRef:kind:ClusterRolename:apiGroup:rbac.authorization.k8s.iosubjects:#Authorizespecificserviceaccounts:-kind:ServiceAccountname:namespace:#Authorizespecificusers(notrecommended):-kind:UserapiGroup:rbac.authorization.k8s.ioname:如果使用角色绑定而不是集群角色,则它将仅适用于与绑定相同命名空间中的Pod。这可以与系统组配对,以授予对在命名空间中运行的所有Pod的访问权限:

#Authorizeallserviceaccountsinanamespace:-kind:GroupapiGroup:rbac.authorization.k8s.ioname:system:serviceaccounts#Orequivalently,allauthenticatedusersinanamespace:-kind:GroupapiGroup:rbac.authorization.k8s.ioname:system:authenticated管理网络策略节点、Pod和容器的安全性至关重要,但这还不够。网络分割对于设计安全的Kubernetes集群至关重要,它允许多租户使用,并且可以最小化安全漏洞的影响。深度防御要求您对不需要相互通信的系统部分进行分隔,并允许您仔细管理流量的方向、协议和端口。

网络策略可以让您对集群的命名空间和通过标签选择的Pod进行细粒度控制和适当的网络分割。在其核心,网络策略是一组防火墙规则,应用于一组由标签选择的命名空间和Pod。这非常灵活,因为标签可以定义虚拟网络段,并且可以作为Kubernetes资源进行管理。

一些网络后端不支持网络策略。例如,流行的Flannel无法应用于策略。

这是一个支持的网络后端列表:

Calico

WeaveNet

Canal

Cillium

Kube-Router

Romana

您可以使用标准的YAML清单来定义网络策略。

这是一个示例策略:

apiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:the-network-policynamespace:defaultspec:podSelector:matchLabels:role:dbingress:-from:-namespaceSelector:matchLabels:project:cool-project-podSelector:matchLabels:role:frontendports:-protocol:tcpport:6379spec部分有两个重要部分——podSelector和ingress。podSelector管理此网络策略适用于哪些pod。ingress管理哪些命名空间和pod可以访问这些pod,以及它们可以使用哪些协议和端口。

在示例网络策略中,pod选择器指定了网络策略的目标,即所有标记为role:db的pod。ingress部分有一个from子部分,其中包括一个namespace选择器和一个pod选择器。集群中所有标记为project:cool-project的命名空间,以及这些命名空间中所有标记为role:frontend的pod,都可以访问标记为role:db的目标pod。ports部分定义了一对对(协议和端口),进一步限制了允许的协议和端口。在这种情况下,协议是tcp,端口是6379(Redis标准端口)。

请注意,网络策略是集群范围的,因此集群中多个命名空间的pod可以访问目标命名空间。当前命名空间始终包括在内,因此即使它没有project:cool标签,带有role:frontend的pods仍然可以访问。

网络策略以白名单方式运行很重要。默认情况下,所有访问都被禁止,网络策略可以打开某些协议和端口,以匹配标签的某些pod。这意味着,如果您的网络解决方案不支持网络策略,所有访问将被拒绝。

白名单性质的另一个含义是,如果存在多个网络策略,则所有规则的并集都适用。如果一个策略允许访问端口1234,另一个策略允许访问端口5678,那么一个pod可能访问端口1234或5678。

Kubernetes1.8添加了出口网络策略支持,因此您也可以控制出站流量。以下是一个示例,阻止访问外部IP1.2.3.4。order:999确保在其他策略之前应用该策略:

apiVersion:v1kind:policymetadata:name:default-deny-egressspec:order:999egress:-action:denydestination:net:1.2.3.4source:{}跨命名空间策略如果将集群划分为多个命名空间,有时如果pod跨命名空间通信,这可能会很方便。您可以在网络策略中指定ingress.namespaceSelector字段,以允许从多个命名空间访问。例如,如果您有生产和暂存命名空间,并且定期使用生产数据的快照填充暂存环境。

秘密在安全系统中至关重要。它们可以是凭据,如用户名和密码、访问令牌、API密钥或加密密钥。秘密通常很小。如果您有大量要保护的数据,您应该对其进行加密,并将加密/解密密钥保留为秘密。

Kubernetes默认将秘密以明文存储在etcd中。这意味着对etcd的直接访问是有限的并且受到仔细保护。从Kubernetes1.7开始,您现在可以在休息时加密您的秘密(当它们由etcd存储时)。

秘密是在命名空间级别管理的。Pod可以通过秘密卷将秘密挂载为文件,也可以将其作为环境变量。从安全的角度来看,这意味着可以创建命名空间中的任何用户或服务都可以访问为该命名空间管理的任何秘密。如果要限制对秘密的访问,请将其放在一组有限用户或服务可访问的命名空间中。

当秘密挂载到一个pod上时,它永远不会被写入磁盘。它存储在tmpfs中。当kubelet与API服务器通信时,通常使用TLS,因此秘密在传输过程中受到保护。

启动API服务器时,您需要传递此参数:

--experimental-encryption-provider-config以下是一个样本加密配置:

kind:EncryptionConfigapiVersion:v1resources:-resources:-secretsproviders:-identity:{}-aesgcm:keys:-name:key1secret:c2VjcmV0IGlzIHNlY3VyZQ==-name:key2secret:dGhpcyBpcyBwYXNzd29yZA==-aescbc:keys:-name:key1secret:c2VjcmV0IGlzIHNlY3VyZQ==-name:key2secret:dGhpcyBpcyBwYXNzd29yZA==-secretbox:keys:-name:key1secret:YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=创建秘密在尝试创建需要它们的pod之前,必须先创建秘密。秘密必须存在;否则,pod创建将失败。

您可以使用以下命令创建秘密:

kubectlcreatesecret.在这里,我创建了一个名为hush-hash的通用秘密,其中包含两个键—用户名和密码:

>kubectlcreatesecretgenerichush-hush--from-literal=username=tobias--from-literal=password=cutoffs生成的秘密是Opaque:

>kubectldescribesecrets/hush-hushName:hush-hushNamespace:defaultLabels:Annotations:Type:OpaqueData====password:7bytesusername:6bytes您可以使用--from-file而不是--from-literal从文件创建秘密,并且如果将秘密值编码为base64,还可以手动创建秘密。

秘密中的键名必须遵循DNS子域的规则(不包括前导点)。

要获取秘密的内容,可以使用kubectlgetsecret:

>kubectlgetsecrets/hush-hush-oyamlapiVersion:v1data:password:Y3V0b2Zmcw==username:dG9iaWFzkind:Secretmetadata:creationTimestamp:2018-01-15T23:43:50Zname:hush-hushnamespace:defaultresourceVersion:"2030851"selfLink:/api/v1/namespaces/default/secrets/hush-hushuid:f04641ef-fa4d-11e7-beab-080027c94384type:OpaqueThevaluesarebase64-encoded.Youneedtodecodethemyourself:>echo"Y3V0b2Zmcw=="|base64--decodecutoofs这些值是base64编码的。您需要自己解码它们:

>echo"Y3V0b2Zmcw=="|base64--decodecutoofs在容器中使用秘密容器可以通过从pod中挂载卷来将秘密作为文件访问。另一种方法是将秘密作为环境变量访问。最后,容器(如果其服务账户具有权限)可以直接访问KubernetesAPI或使用kubectlgetsecret。

{"apiVersion":"v1","kind":"Pod","metadata":{"name":"pod-with-secret","namespace":"default"},"spec":{"containers":[{"name":"the-container","image":"redis","volumeMounts":[{"name":"secret-volume","mountPath":"/mnt/secret-volume","readOnly":true}]}],"volumes":[{"name":"secret-volume","secret":{"secretName":"hush-hush"}}]}}卷名称(secret-volume)将pod卷绑定到容器中的挂载点。多个容器可以挂载相同的卷。

当此pod运行时,用户名和密码将作为文件出现在/etc/secret-volume下:

>kubectlexecpod-with-secretcat/mnt/secret-volume/usernametobias>kubectlexecpod-with-secretcat/mnt/secret-volume/passwordcutoffs运行多用户集群在本节中,我们将简要讨论使用单个集群来托管多个用户或多个用户社区的系统的选项。这个想法是这些用户是完全隔离的,甚至可能不知道他们与其他用户共享集群。每个用户社区都将拥有自己的资源,并且它们之间不会有通信(除非通过公共端点)。Kubernetes命名空间概念是这个想法的最终表达。

为什么要为多个隔离的用户或部署运行单个集群?每个用户都有一个专用的集群不是更简单吗?主要有两个原因:成本和运营复杂性。如果您有许多相对较小的部署,并且希望为每个部署创建一个专用的集群,那么您将需要为每个部署单独的主节点,可能还需要一个三节点的etcd集群。这可能会增加成本。运营复杂性也非常重要。管理数十甚至数百个独立的集群并不容易。每次升级和每次补丁都需要应用到每个集群。运营可能会失败,您将不得不管理一群集群,其中一些集群的状态可能与其他集群略有不同。跨所有集群的元操作可能更加困难。您将不得不聚合并编写您的工具来执行操作并从所有集群收集数据。

让我们看一些多个隔离社区或部署的用例和要求:

作为-服务的平台或服务提供商

管理单独的测试、暂存和生产环境

将责任委托给社区/部署管理员

对每个社区强制执行资源配额和限制

用户只能看到他们社区中的资源

Kubernetes命名空间是安全的多租户集群的完美解决方案。这并不奇怪,因为这是命名空间的设计目标之一。

您可以轻松地创建除内置kube系统和默认之外的命名空间。以下是一个将创建一个名为custom-namespace的新命名空间的YAML文件。它只有一个名为name的元数据项。没有比这更简单的了:

apiVersion:v1kind:Namespacemetadata:name:custom-namespace让我们创建命名空间:

>Kubectlcreate-fcustom-namespace.yamlnamespace"custom-namespace"created>kubectlgetnamesapcesNAMESTATUSAGEcustom-namespaceActive39sdefaultActive32dkube-systemActive32d状态字段可以是active或terminating。当您删除一个命名空间时,它将进入terminating状态。当命名空间处于此状态时,您将无法在此命名空间中创建新资源。这简化了命名空间资源的清理,并确保命名空间真正被删除。如果没有它,当现有pod被删除时,复制控制器可能会创建新的pod。

要使用命名空间,您需要在kubectl命令中添加--namespace参数:

>kubectlcreate-fsome-pod.yaml--namespace=custom-namespacepod"some-pod"created在自定义命名空间中列出pod只返回我们刚刚创建的pod:

>kubectlgetpods--namespace=custom-namespaceNAMEREADYSTATUSRESTARTSAGEsome-pod1/1Running06m在不带命名空间的情况下列出pod会返回默认命名空间中的pod:

>KubectlgetpodsNAMEREADYSTATUSRESTARTSAGEecho-3580479493-n66n41/1Running1632dleader-elector-191609294-lt95t1/1Running49dleader-elector-191609294-m6fb61/1Running49dleader-elector-191609294-piu8p1/1Running49dpod-with-secret1/1Running11h避免命名空间陷阱命名空间很棒,但可能会增加一些摩擦。当您只使用默认命名空间时,可以简单地省略命名空间。当使用多个命名空间时,必须使用命名空间限定所有内容。这可能是一个负担,但不会带来任何危险。但是,如果一些用户(例如,集群管理员)可以访问多个命名空间,那么您就有可能意外修改或查询错误的命名空间。避免这种情况的最佳方法是将命名空间密封起来,并要求为每个命名空间使用不同的用户和凭据。

此外,工具可以帮助清楚地显示您正在操作的命名空间(例如,如果从命令行工作,则是shell提示,或者在Web界面中突出显示命名空间)。

确保可以在专用命名空间上操作的用户不能访问默认命名空间。否则,每当他们忘记指定命名空间时,他们将在默认命名空间上悄悄操作。

在本章中,我们介绍了开发人员和管理员在Kubernetes集群上构建系统和部署应用程序时面临的许多安全挑战。但我们也探讨了许多安全功能和灵活的基于插件的安全模型,提供了许多限制、控制和管理容器、pod和节点的方法。Kubernetes已经为大多数安全挑战提供了多功能解决方案,随着诸如AppArmor和各种插件从alpha/beta状态转移到一般可用状态,它将变得更加完善。最后,我们考虑了如何使用命名空间来支持同一Kubernetes集群中的多个用户社区或部署。

在下一章中,我们将深入研究许多Kubernetes资源和概念,以及如何有效地使用它们并将它们组合起来。Kubernetes对象模型是建立在一小部分通用概念(如资源、清单和元数据)的坚实基础之上的。这使得一个可扩展的、但令人惊讶地一致的对象模型能够为开发人员和管理员提供非常多样化的能力集。

在本章中,我们将设计一个挑战Kubernetes能力和可伸缩性的大规模平台。Hue平台的目标是创建一个无所不知、无所不能的数字助手。Hue是你的数字延伸。它将帮助你做任何事情,找到任何东西,并且在许多情况下,将代表你做很多事情。它显然需要存储大量信息,与许多外部服务集成,响应通知和事件,并且在与你的互动方面非常智能。

在本节中,我们将为惊人的Hue平台设定舞台并定义范围。Hue不是老大哥,Hue是小弟!Hue将做任何你允许它做的事情。它可以做很多事情,但有些人可能会担心,所以你可以选择Hue可以帮助你多少。准备好进行一次疯狂的旅行!

Hue将管理你的数字人格。它将比你自己更了解你。以下是Hue可以管理并帮助你的一些服务列表:

搜索和内容聚合

医疗

智能家居

金融-银行、储蓄、退休、投资

办公室

社交

旅行

健康

家庭

安全、身份和隐私:Hue是您在线的代理。有人窃取您的Hue身份,甚至只是窃听您的Hue互动的后果是灾难性的。潜在用户甚至可能不愿意信任Hue组织他们的身份。让我们设计一个非信任系统,让用户随时有权终止Hue。以下是一些朝着正确方向的想法:

频繁更换凭证

快速服务暂停和所有外部服务的身份重新验证(将需要向每个提供者提供原始身份证明)

Hue后端将通过短暂的令牌与所有外部服务进行交互

将Hue构建为一组松散耦合的微服务的架构。

Hue的架构将需要支持巨大的变化和灵活性。它还需要非常可扩展,其中现有的功能和外部服务不断升级,并且新的功能和外部服务集成到平台中。这种规模需要微服务,其中每个功能或服务都与其他服务完全独立,除了通过标准和/或可发现的API进行定义的接口。

在着手进行微服务之旅之前,让我们回顾一下我们需要为Hue构建的组件类型。

用户资料是一个重要组成部分,有很多子组件。它是用户的本质,他们的偏好,跨各个领域的历史,以及Hue对他们了解的一切。

用户图组件模拟了用户在多个领域之间的互动网络。每个用户参与多个网络:社交网络(如Facebook和Twitter)、专业网络、爱好网络和志愿者社区。其中一些网络是临时的,Hue将能够对其进行结构化以使用户受益。Hue可以利用其对用户连接的丰富资料,即使不暴露私人信息,也能改善互动。

身份管理是至关重要的,正如前面提到的,因此它值得一个单独的组件。用户可能更喜欢管理具有独立身份的多个互斥配置文件。例如,也许用户不愿意将他们的健康配置文件与社交配置文件混合在一起,因为这样做可能会意外地向朋友透露个人健康信息的风险。

Hue是外部服务的聚合器。它并非旨在取代您的银行、健康提供者或社交网络。它将保留大量关于您活动的元数据,但内容将保留在您的外部服务中。每个外部服务都需要一个专用组件来与外部服务的API和政策进行交互。当没有API可用时,Hue通过自动化浏览器或原生应用程序来模拟用户。

Hue价值主张的一个重要部分是代表用户行事。为了有效地做到这一点,Hue需要意识到各种事件。例如,如果Hue为您预订了假期,但它感觉到有更便宜的航班可用,它可以自动更改您的航班或要求您确认。有无限多件事情可以感知。为了控制感知,需要一个通用传感器。通用传感器将是可扩展的,但提供一个通用接口,供Hue的其他部分统一使用,即使添加了越来越多的传感器。

每个组件的复杂性都非常巨大。一些组件,比如外部服务、通用传感器和通用执行器,需要跨越数百、数千甚至更多不断变化的外部服务进行操作,而这些服务是在Hue的控制范围之外的。甚至用户学习器也需要学习用户在许多领域和领域的偏好。微服务通过允许Hue逐渐演变并增加更多的隔离能力来满足这种需求,而不会在自身的复杂性下崩溃。每个微服务通过标准接口与通用Hue基础设施服务进行交互,并且可以通过明确定义和版本化的接口与其他一些服务进行交互。每个微服务的表面积是可管理的,微服务之间的编排基于标准最佳实践:

插件是扩展Hue而不会产生大量接口的关键。关于插件的一件事是,通常需要跨越多个抽象层的插件链。例如,如果我们想要为Hue添加与YouTube的新集成,那么你可以收集大量特定于YouTube的信息:你的频道、喜欢的视频、推荐以及你观看过的视频。为了向用户显示这些信息并允许他们对其进行操作,你需要跨越多个组件并最终在用户界面中使用插件。智能设计将通过聚合各种操作类别,如推荐、选择和延迟通知,来帮助许多不同的服务。

插件的好处在于任何人都可以开发。最初,Hue开发团队将不得不开发插件,但随着Hue变得更加流行,外部服务将希望与Hue集成并构建Hue插件以启用其服务。

当然,这将导致插件注册、批准和策划的整个生态系统。

Hue将需要多种类型的数据存储和每种类型的多个实例来管理其数据和元数据:

图数据库

内存缓存

由于Hue的范围,每个数据库都将需要进行集群化和分布式处理。

微服务应该大部分是无状态的。这将允许特定实例被快速启动和关闭,并根据需要在基础设施之间迁移。状态将由存储管理,并由短暂的访问令牌访问微服务。

队列可以用于异步的RPC或请求-响应式的交互,其中调用实例提供一个私有队列名称,被调用者将响应发布到私有队列。

Hue经常需要支持工作流程。典型的工作流程将得到一个高层任务,比如预约牙医;它将提取用户的牙医详情和日程安排,与用户的日程安排匹配,在多个选项之间进行选择,可能与用户确认,预约,并设置提醒。我们可以将工作流程分类为完全自动和涉及人类的人工工作流程。然后还有涉及花钱的工作流程。

自动工作流程不需要人为干预。Hue有完全的权限来执行从开始到结束的所有步骤。用户分配给Hue的自主权越多,它的效果就会越好。用户应该能够查看和审计所有的工作流程,无论是过去还是现在。

有些工作流程,比如支付账单或购买礼物,需要花钱。虽然从理论上讲,Hue可以被授予对用户银行账户的无限访问权限,但大多数用户可能更愿意为不同的工作流程设置预算,或者只是将花钱作为经过人工批准的活动。

通用命令:以通用方式处理资源:create,get,delete,run,apply,patch,replace等等

集群管理命令:处理节点和整个集群:cluster-info,certificate,drain等等

故障排除命令:describe,logs,attach,exec等等

部署命令:处理部署和扩展:rollout,scale,auto-scale等等

设置命令:处理标签和注释:label,annotate等等

Misccommands:help,config,andversion您可以使用Kubernetesconfigview查看配置。

这是Minikube集群的配置:

apiVersion:v1kind:Podmetadata:name:""labels:name:""namespace:""annotations:[]generateName:""spec:...apiVersion:非常重要的KubernetesAPI不断发展,并且可以通过API的不同版本支持相同资源的不同版本。

kind:kind告诉Kubernetes它正在处理的资源类型,在本例中是pod。这是必需的。

metadata:这是描述pod及其操作位置的大量信息:

名称:在其命名空间中唯一标识pod

标签:可以应用多个标签

命名空间:pod所属的命名空间

注释:可查询的注释列表

规范:规范是一个包含启动pod所需的所有信息的pod模板。它可能非常复杂,所以我们将在多个部分中探讨它:

"spec":{"containers":[],"restartPolicy":"","volumes":[]}容器规范:pod规范的容器是容器规范的列表。每个容器规范都有以下结构:{"name":"","image":"","command":[""],"args":[""],"env":[{"name":"","value":""}],"imagePullPolicy":"","ports":[{"containerPort":0,"name":"","protocol":""}],"resources":{"cpu":"""memory":""}}每个容器都有一个镜像,一个命令,如果指定了,会替换Docker镜像命令。它还有参数和环境变量。然后,当然还有镜像拉取策略、端口和资源限制。我们在前几章中已经涵盖了这些。

让我们从一个常规的pod配置文件开始,为创建Hue学习者内部服务。这个服务不需要暴露为公共服务,它将监听一个队列以获取通知,并将其见解存储在一些持久存储中。

我们需要一个简单的容器来运行pod。这可能是有史以来最简单的Docker文件,它将模拟Hue学习者:

我构建了两个标记为g1g1/hue-learn:v3.0和g1g1/hue-learn:v4.0的Docker镜像,并将它们推送到DockerHub注册表(g1g1是我的用户名)。

dockerbuild.-tg1g1/hue-learn:v3.0dockerbuild.-tg1g1/hue-learn:v4.0dockerpushg1g1/hue-learn:v3.0dockerpushg1g1/hue-learn:v4.0现在,这些镜像可以被拉入Hue的pod中的容器。

我们将在这里使用YAML,因为它更简洁和易读。这里是样板和元数据标签:

apiVersion:v1kind:Podmetadata:name:hue-learnerlabels:app:hueruntime-environment:productiontier:internal-serviceannotations:version:"3.0"我使用注释而不是标签的原因是,标签用于标识部署中的一组pod。不允许修改标签。

接下来是重要的容器规范,为每个容器定义了强制的名称和镜像:

spec:containers:-name:hue-learnerimage:g1g1/hue-learn:v3.0资源部分告诉Kubernetes容器的资源需求,这允许更高效和紧凑的调度和分配。在这里,容器请求200毫CPU单位(0.2核心)和256MiB:

resources:requests:cpu:200mmemory:256Mi环境部分允许集群管理员提供将可用于容器的环境变量。这里告诉它通过dns发现队列和存储。在测试环境中,可能会使用不同的发现方法:

env:-name:DISCOVER_QUEUEvalue:dns-name:DISCOVER_STOREvalue:dns用标签装饰pod明智地为pod贴上标签对于灵活的操作至关重要。它让您可以实时演变您的集群,将微服务组织成可以统一操作的组,并以自发的方式深入观察不同的子集。

例如,我们的Hue学习者pod具有以下标签:

运行环境:生产

层级:内部服务

版本注释可用于支持同时运行多个版本。如果需要同时运行版本2和版本3,无论是为了提供向后兼容性还是在从v2迁移到v3期间暂时运行,那么具有版本注释或标签允许独立扩展不同版本的pod并独立公开服务。runtime-environment标签允许对属于特定环境的所有pod执行全局操作。tier标签可用于查询属于特定层的所有pod。这只是例子;在这里,您的想象力是限制。

在大型系统中,pod不应该只是创建并放任不管。如果由于任何原因pod意外死亡,您希望另一个pod替换它以保持整体容量。您可以自己创建复制控制器或副本集,但这也会留下错误的可能性以及部分故障的可能性。在启动pod时指定要创建多少副本更有意义。

让我们使用Kubernetes部署资源部署我们的Hue学习者微服务的三个实例。请注意,部署对象在Kubernetes1.9时变得稳定:

apiVersion:apps/v1(useapps/v1beta2before1.9)kind:Deploymentmetadata:name:hue-learnlabels:app:huespec:replicas:3selector:matchLabels:app:huetemplate:metadata:labels:app:huespec:podspec与我们之前使用的pod配置文件中的spec部分相同。

让我们创建部署并检查其状态:

>kubectlcreate-f.\deployment.yamldeployment"hue-learn"created>kubectlgetdeploymenthue-learnNAMEDESIREDCURRENTUP-TO-DATEAVAILABLEAGEhue-learn33334m>kubectlgetpods|grephue-learnNAMEREADYSTATUSRESTARTSAGEhue-learn-237202748-d770r1/1Running02mhue-learn-237202748-fwv2t1/1Running02mhue-learn-237202748-tpr4s1/1Running02m您可以使用kubectldescribe命令获取有关部署的更多信息。

Hue平台是一个庞大且不断发展的系统。您需要不断升级。部署可以更新以无痛的方式推出更新。您可以更改pod模板以触发由Kubernetes完全管理的滚动更新。

目前,所有的pod都在运行版本3.0:

但是一些服务需要向用户或外部程序公开。让我们看一个虚假的Hue服务,它管理用户的提醒列表。它实际上并不做任何事情,但我们将用它来说明如何公开服务。我将一个虚假的hue-reminders镜像(与hue-learn相同)推送到DockerHub:

dockerpushg1g1/hue-reminders:v2.2部署内部服务这是部署,它与Hue-learner部署非常相似,只是我删除了annotations、env和resources部分,只保留了一个标签以节省空间,并在容器中添加了一个ports部分。这是至关重要的,因为服务必须通过一个端口公开,其他服务才能访问它:

apiVersion:apps/v1a1kind:Deploymentmetadata:name:hue-remindersspec:replicas:2template:metadata:name:hue-reminderslabels:app:hue-remindersspec:containers:-name:hue-remindersimage:g1g1/hue-reminders:v2.2ports:-containerPort:80当我们运行部署时,两个Huereminderspod被添加到集群中:

>kubectlcreate-fhue-reminders-deployment.yaml>kubectlgetpodsNAMEREADYSTATUSRESTARTSAGEhue-learn-56886758d8-h7vm71/1Running049mhue-learn-56886758d8-lqptj1/1Running049mhue-learn-56886758d8-zwkqt1/1Running049mhue-reminders-75c88cdfcf-5xqtp1/1Running050shue-reminders-75c88cdfcf-r6jsx1/1Running050s好的,pod正在运行。理论上,其他服务可以查找或配置其内部IP地址,并直接访问它们,因为它们都在同一个网络空间中。但这并不具有可扩展性。每当一个reminderspod死掉并被新的pod替换,或者当我们只是扩展pod的数量时,所有访问这些pod的服务都必须知道这一点。服务通过提供所有pod的单一访问点来解决这个问题。服务如下:

apiVersion:v1kind:Servicemetadata:name:hue-reminderslabels:app:hue-remindersspec:ports:-port:80protocol:TCPselector:app:hue-reminders该服务具有一个选择器,选择所有具有与其匹配的标签的pod。它还公开一个端口,其他服务将使用该端口来访问它(它不必与容器的端口相同)。

让我们创建服务并稍微探索一下:

>kubectlcreate-fhue-reminders-service.yamlservice"hue-reminders"created>kubectldescribesvchue-remindersName:hue-remindersNamespace:defaultLabels:app=hue-remindersAnnotations:Selector:app=hue-remindersType:ClusterIPIP:10.108.163.209Port:80/TCPTargetPort:80/TCPEndpoints:172.17.0.4:80,172.17.0.6:80SessionAffinity:NoneEvents:服务正在运行。其他pod可以通过环境变量或DNS找到它。所有服务的环境变量都是在pod创建时设置的。这意味着如果在创建服务时已经有一个pod在运行,您将不得不将其终止,并让Kubernetes使用环境变量重新创建它(您通过部署创建pod,对吧?):

>kubectlexechue-learn-56886758d8-fjzdd--printenv|grepHUE_REMINDERS_SERVICEHUE_REMINDERS_SERVICE_PORT=80HUE_REMINDERS_SERVICE_HOST=10.108.163.209但是使用DNS要简单得多。您的服务DNS名称是:

..svc.cluster.local>kubectlexechue-learn-56886758d8-fjzdd--nslookuphue-remindersServer:10.96.0.10Address1:10.96.0.10kube-dns.kube-system.svc.cluster.localName:hue-remindersAddress1:10.108.163.209hue-reminders.default.svc.cluster.local将服务暴露给外部该服务在集群内可访问。如果您想将其暴露给外部世界,Kubernetes提供了两种方法:

为直接访问配置NodePort

如果在云环境中运行,请配置云负载均衡器

在为外部访问配置服务之前,您应该确保其安全。Kubernetes文档中有一个涵盖所有细节的很好的示例:

我们已经在第五章中介绍了原则,“配置Kubernetes安全性、限制和账户”。

以下是通过NodePort向外界暴露Hue-reminders服务的spec部分:

为您的服务提供外部可见的URL

负载均衡流量

终止SSL

提供基于名称的虚拟主机

要使用Ingress,您必须在集群中运行一个Ingress控制器。请注意,Ingress仍处于测试阶段,并且有许多限制。如果您在GKE上运行集群,那么可能没问题。否则,请谨慎操作。Ingress控制器目前的一个限制是它不适用于扩展。因此,它还不是Hue平台的一个好选择。我们将在第十章“高级Kubernetes网络”中更详细地介绍Ingress控制器。

以下是Ingress资源的外观:

让我们创建一个新的服务,Hue-finance,并将其放在一个名为restricted的新命名空间中。

这是新的restricted命名空间的YAML文件:

kind:NamespaceapiVersion:v1metadata:name:restrictedlabels:name:restricted>kubectlcreate-frestricted-namespace.yamlnamespace"restricted"created创建命名空间后,我们需要为命名空间配置上下文。这将允许限制访问仅限于此命名空间:

>kubectlconfigset-contextrestricted--namespace=restricted--cluster=minikube--user=minikubeContext"restricted"set.>kubectlconfiguse-contextrestrictedSwitchedtocontext"restricted".让我们检查我们的cluster配置:

现在,在这个空的命名空间中,我们可以创建我们的hue-finance服务,它将独立存在:

>kubectlcreate-fhue-finance-deployment.yamldeployment"hue-finance"created>kubectlgetpodsNAMEREADYSTATUSRESTARTSAGEhue-finance-7d4b84cc8d-gcjnz1/1Running06shue-finance-7d4b84cc8d-tqvr91/1Running06shue-finance-7d4b84cc8d-zthdr1/1Running06s不需要切换上下文。您还可以使用--namespace=和--all-namespaces命令行开关。

这是一个运行Python进程计算5的阶乘的作业(提示:它是120):

apiVersion:batch/v1kind:Jobmetadata:name:factorial5spec:template:metadata:name:factorial5spec:containers:-name:factorial5image:python:3.6command:["python","-c","importmath;print(math.factorial(5))"]restartPolicy:Never请注意,restartPolicy必须是Never或OnFailure。默认的Always值是无效的,因为作业在成功完成后不应重新启动。

让我们启动作业并检查其状态:

>kubectlcreate-f.\job.yamljob"factorial5"created>kubectlgetjobsNAMEDESIREDSUCCESSFULAGEfactorial51125s默认情况下不显示已完成任务的pod。您必须使用--show-all选项:

>kubectlgetpods--show-allNAMEREADYSTATUSRESTARTSAGEfactorial5-ntp220/1Completed02mhue-finance-7d4b84cc8d-gcjnz1/1Running09mhue-finance-7d4b84cc8d-tqvr91/1Running08mhue-finance-7d4b84cc8d-zthdr1/1Running09mfactorial5pod的状态为Completed。让我们查看它的输出:

>kubectllogsfactorial5-ntp22120并行运行作业您还可以使用并行运行作业。规范中有两个字段,称为completions和parallelism。completions默认设置为1。如果您需要多个成功完成,则增加此值。parallelism确定要启动多少个pod。作业不会启动比成功完成所需的更多的pod,即使并行数更大。

让我们运行另一个只睡眠20秒直到完成三次成功的作业。我们将使用parallelism因子为6,但只会启动三个pod:

apiVersion:batch/v1kind:Jobmetadata:name:sleep20spec:completions:3parallelism:6template:metadata:name:sleep20spec:containers:-name:sleep20image:python:3.6command:["python","-c","importtime;print('started...');time.sleep(20);print('done.')"]restartPolicy:Never>KubectlgetpodsNAMEREADYSTATUSRESTARTSAGEsleep20-1t8sd1/1Running010ssleep20-sdjb41/1Running010ssleep20-wv4jc1/1Running010s清理已完成的作业当作业完成时,它会保留下来-它的pod也是如此。这是有意设计的,这样您就可以查看日志或连接到pod并进行探索。但通常,当作业成功完成后,它就不再需要了。清理已完成的作业及其pod是您的责任。最简单的方法是简单地删除job对象,这将同时删除所有的pod:

在Kubernetes1.4中,它们被称为ScheduledJob。但是,在Kubernetes1.5中,名称更改为CronJob。从Kubernetes1.8开始,默认情况下在API服务器中启用了CronJob资源,不再需要传递--runtime-config标志,但它仍处于beta阶段。以下是启动一个每分钟提醒您伸展的cron作业的配置。在计划中,您可以用替换*:

apiVersion:batch/v1beta1kind:CronJobmetadata:name:stretchspec:schedule:"*/1****"jobTemplate:spec:template:metadata:labels:name:stretchspec:containers:-name:stretchimage:pythonargs:-python--c-fromdatetimeimportdatetime;print('[{}]Stretch'.format(datetime.now()))restartPolicy:OnFailure在pod规范中,在作业模板下,我添加了一个名为name的标签。原因是Kubernetes会为cron作业及其pod分配带有随机前缀的名称。该标签允许您轻松发现特定cron作业的所有pod。请参阅以下命令行:

>kubectlgetpodsNAMEREADYSTATUSRESTARTSAGEstretch-1482165720-qm5bj0/1ImagePullBackOff01mstretch-1482165780-bkqjd0/1ContainerCreating06s请注意,每次调用cron作业都会启动一个新的job对象和一个新的pod:

>kubectlgetjobsNAMEDESIREDSUCCESSFULAGEstretch-14821653001111mstretch-14821653601110mstretch-1482165420119mstretch-1482165480118m当cron作业调用完成时,它的pod进入Completed状态,并且不会在没有-show-all或-a标志的情况下可见:

>Kubectlgetpods--show-allNAMEREADYSTATUSRESTARTSAGEstretch-1482165300-g5ps60/1Completed015mstretch-1482165360-cln080/1Completed014mstretch-1482165420-n8nzd0/1Completed013mstretch-1482165480-0jq310/1Completed012m通常情况下,您可以使用logs命令来检查已完成的cron作业的pod的输出:

>kubectllogsstretch-1482165300-g5ps6[2016-12-1916:35:15.325283]Stretch当您删除一个cron作业时,它将停止安排新的作业,并删除所有现有的作业对象以及它创建的所有pod。

您可以使用指定的标签(在本例中名称等于STRETCH)来定位由cron作业启动的所有作业对象。您还可以暂停cron作业,以便它不会创建更多的作业,而无需删除已完成的作业和pod。您还可以通过设置在spec历史限制中管理以前的作业:spec.successfulJobsHistoryLimit和.spec.failedJobsHistoryLimit。

Kubernetes集群中的大多数实时系统组件将与集群外的组件进行通信。这些可能是完全外部的第三方服务,可以通过某些API访问,但也可能是在同一本地网络中运行的内部服务,由于各种原因,这些服务不是Kubernetes集群的一部分。

这里有两个类别:网络内部和网络外部。为什么这种区别很重要?

这些是在网络内部运行但不受Kubernetes管理的组件。有许多原因可以运行这些组件。它们可能是尚未Kubernetized的传统应用程序,或者是一些不容易在Kubernetes内部运行的分布式数据存储。将这些组件运行在网络内部的原因是为了性能,并且与外部世界隔离,以便这些组件和pod之间的流量更加安全。作为相同网络的一部分确保低延迟,并且减少了身份验证的需求既方便又可以避免身份验证开销。

如果整个系统能够意识到不同组件的就绪状态,或者只有当组件真正就绪时才可见,通常会更好。Kubernetes并不了解Hue,但它提供了几种机制,如活跃性探针、就绪性探针和InitContainers,来支持你的集群的应用程序特定管理。

有两种其他类型的探针:

TcpSocket:只需检查端口是否打开

Exec:运行一个返回0表示成功的命令

就绪探针用于不同的目的。您的容器可能已经启动运行,但可能依赖于此刻不可用的其他服务。例如,Hue-music可能依赖于访问包含您听歌历史记录的数据服务。如果没有访问权限,它将无法执行其职责。在这种情况下,其他服务或外部客户端不应该向Hue音乐服务发送请求,但没有必要重新启动它。就绪探针解决了这种情况。当一个容器的就绪探针失败时,该容器的pod将从其注册的任何服务端点中移除。这确保请求不会涌入无法处理它们的服务。请注意,您还可以使用就绪探针暂时移除过载的pod,直到它们排空一些内部队列。

这是一个示例就绪探针。我在这里使用exec探针来执行一个custom命令。如果命令退出时的退出代码为非零,容器将被关闭:

readinessProbe:exec:command:-/usr/local/bin/checker---full-check---data-service=hue-multimedia-serviceinitialDelaySeconds:60timeoutSeconds:5在同一个容器上同时拥有就绪探针和存活探针是可以的,因为它们有不同的用途。

初始化容器解决了这个问题。一个pod可能有一组初始化容器,在其他容器启动之前完成运行。初始化容器可以处理所有非确定性的初始化,并让应用容器通过它们的就绪探针尽量减少延迟。

初始化容器在Kubernetes1.6中退出了beta版。您可以在pod规范中指定它们,作为initContainers字段,这与containers字段非常相似。以下是一个示例:

这个配置文件的有趣之处在于,hostNetwork、hostPID和hostIPC选项都设置为true。这使得pod能够有效地与代理通信,利用它们在同一物理主机上运行的事实。

apiVersion:apps/v1kind:DaemonSetmetadata:name:hue-collect-proxylabels:tier:statsapp:hue-collect-proxyspec:template:metadata:labels:hue-collect-proxyspec:hostPID:truehostIPC:truehostNetwork:truecontainers:image:the_g1g1/hue-collect-proxyname:hue-collect-proxy使用Kubernetes发展Hue平台在本节中,我们将讨论扩展Hue平台和服务其他市场和社区的其他方法。问题始终是,“我们可以使用哪些Kubernetes功能和能力来解决新的挑战或要求?”

企业通常无法在云中运行,要么是因为安全和合规性原因,要么是因为性能原因,因为系统必须处理数据和传统系统,这些系统不适合迁移到云上。无论哪种情况,企业的Hue必须支持本地集群和/或裸金属集群。

虽然Kubernetes最常部署在云上,甚至有一个特殊的云提供商接口,但它并不依赖于云,可以在任何地方部署。它需要更多的专业知识,但已经在自己的数据中心上运行系统的企业组织拥有这方面的专业知识。

CoreOS提供了大量关于在裸机集群上部署Kubernetes集群的材料。

科学社区的网络可能需要在多个地理分布的集群上部署。这就是集群联邦。Kubernetes考虑到了这种用例,并不断发展其支持。我们将在后面的章节中详细讨论这个问题。

Hue可以用于教育,并为在线教育系统提供许多服务。但隐私问题可能会阻止将Hue作为单一的集中系统用于儿童。一个可能的选择是建立一个单一的集群,为不同学校设立命名空间。另一个部署选项是每个学校或县都有自己的HueKubernetes集群。在第二种情况下,Hue教育必须非常易于操作,以满足没有太多技术专长的学校。Kubernetes可以通过提供自愈和自动扩展功能来帮助Hue,使其尽可能接近零管理。

在本章中,我们设计和规划了Hue平台的开发、部署和管理——一个想象中的全知全能的服务,建立在微服务架构上。当然,我们使用Kubernetes作为底层编排平台,并深入探讨了许多它的概念和资源。特别是,我们专注于为长期运行的服务部署pod,而不是为启动短期或定期作业部署作业,探讨了内部服务与外部服务,还使用命名空间来分割Kubernetes集群。然后,我们研究了像活跃性和就绪性探针、初始化容器和守护进程集这样的大型系统的管理。

现在,您应该能够设计由微服务组成的Web规模系统,并了解如何在Kubernetes集群中部署和管理它们。

在下一章中,我们将深入研究存储这个非常重要的领域。数据为王,但通常是系统中最不灵活的元素。Kubernetes提供了一个存储模型,并提供了许多与各种存储解决方案集成的选项。

在本章中,我们将看一下Kubernetes如何管理存储。存储与计算非常不同,但在高层次上它们都是资源。作为一个通用平台,Kubernetes采取了在编程模型和一组存储提供者插件后面抽象存储的方法。首先,我们将详细介绍存储的概念模型以及如何将存储提供给集群中的容器。然后,我们将介绍常见的云平台存储提供者,如AWS、GCE和Azure。然后我们将看一下著名的开源存储提供者(来自红帽的GlusterFS),它提供了一个分布式文件系统。我们还将研究一种替代方案——Flocker——它将您的数据作为Kubernetes集群的一部分进行管理。最后,我们将看看Kubernetes如何支持现有企业存储解决方案的集成。

在本章结束时,您将对Kubernetes中存储的表示有扎实的了解,了解每个部署环境(本地测试、公共云和企业)中的各种存储选项,并了解如何为您的用例选择最佳选项。

在这一部分,我们将看一下Kubernetes存储的概念模型,并了解如何将持久存储映射到容器中,以便它们可以读写。让我们先来看看存储的问题。容器和Pod是短暂的。当容器死亡时,容器写入自己文件系统的任何内容都会被清除。容器也可以挂载宿主节点的目录并进行读写。这样可以在容器重新启动时保留,但节点本身并不是不朽的。

还有其他问题,比如当容器死亡时,挂载的宿主目录的所有权。想象一下,一堆容器将重要数据写入它们的宿主机上的各个数据目录,然后离开,留下所有这些数据散落在节点上,没有直接的方法告诉哪个容器写入了哪些数据。您可以尝试记录这些信息,但您会在哪里记录呢?很明显,对于大规模系统,您需要从任何节点访问持久存储以可靠地管理数据。

基本的Kubernetes存储抽象是卷。容器挂载绑定到其Pod的卷,并访问存储,无论它在哪里,都好像它在它们的本地文件系统中一样。这并不新鲜,但很棒,因为作为一个需要访问数据的应用程序开发人员,您不必担心数据存储在何处以及如何存储。

使用共享卷在同一Pod中的容器之间共享数据非常简单。容器1和容器2只需挂载相同的卷,就可以通过读写到这个共享空间进行通信。最基本的卷是emptyDir。emptyDir卷是主机上的empty目录。请注意,它不是持久的,因为当Pod从节点中移除时,内容会被擦除。如果容器崩溃,Pod将继续存在,稍后可以访问它。另一个非常有趣的选项是使用RAM磁盘,通过指定介质为Memory。现在,您的容器通过共享内存进行通信,这样做速度更快,但当然更易失。如果节点重新启动,emptyDir卷的内容将丢失。

这是一个pod配置文件,其中有两个容器挂载名为shared-volume的相同卷。这些容器在不同的路径上挂载它,但当hue-global-listener容器将文件写入/notifications时,hue-job-scheduler将在/incoming下看到该文件。

apiVersion:v1kind:Podmetadata:name:hue-schedulerspec:containers:-image:the_g1g1/hue-global-listenername:hue-global-listenervolumeMounts:-mountPath:/notificationsname:shared-volume-image:the_g1g1/hue-job-schedulername:hue-job-schedulervolumeMounts:-mountPath:/incomingname:shared-volumevolumes:-name:shared-volumeemptyDir:{}要使用共享内存选项,我们只需要在emptyDir部分添加medium:Memory:

volumes:-name:shared-volumeemptyDir:medium:Memory使用HostPath进行节点内通信有时,您希望您的Pod可以访问一些主机信息(例如Docker守护程序),或者您希望同一节点上的Pod可以相互通信。如果Pod知道它们在同一主机上,这将非常有用。由于Kubernetes根据可用资源调度Pod,Pod通常不知道它们与哪些其他Pod共享节点。有两种情况下,Pod可以依赖于其他Pod与其一起在同一节点上调度:

在单节点集群中,所有Pod显然共享同一节点

DaemonSetPod始终与与其选择器匹配的任何其他Pod共享节点

例如,在第六章中,使用关键的Kubernetes资源,我们讨论了一个作为聚合代理的DaemonSetpod到其他pod的。实现此行为的另一种方法是让pod将其数据简单地写入绑定到host目录的挂载卷,然后DaemonSetpod可以直接读取并对其进行操作。

在决定使用HostPath卷之前,请确保您了解限制:

具有相同配置的pod的行为可能会有所不同,如果它们是数据驱动的,并且它们主机上的文件不同

它可能会违反基于资源的调度(即将推出到Kubernetes),因为Kubernetes无法监视HostPath资源

访问主机目录的容器必须具有privileged设置为true的安全上下文,或者在主机端,您需要更改权限以允许写入

这是一个配置文件,将/coupons目录挂载到hue-coupon-hunter容器中,该容器映射到主机的/etc/hue/data/coupons目录:

apiVersion:v1kind:Podmetadata:name:hue-coupon-hunterspec:containers:-image:the_g1g1/hue-coupon-huntername:hue-coupon-huntervolumeMounts:-mountPath:/couponsname:coupons-volumevolumes:-name:coupons-volumehost-path:path:/etc/hue/data/coupons由于pod没有privileged安全上下文,它将无法写入host目录。让我们改变容器规范以通过添加安全上下文来启用它:

-image:the_g1g1/hue-coupon-huntername:hue-coupon-huntervolumeMounts:-mountPath:/couponsname:coupons-volumesecurityContext:privileged:true在下图中,您可以看到每个容器都有自己的本地存储区,其他容器或pod无法访问,并且主机的/data目录被挂载为卷到容器1和容器2:

本地卷类似于HostPath,但它们在pod重新启动和节点重新启动时保持不变。在这种意义上,它们被视为持久卷。它们在Kubernetes1.7中添加。截至Kubernetes1.10需要启用功能门。本地卷的目的是支持StatefulSet,其中特定的pod需要被调度到包含特定存储卷的节点上。本地卷具有节点亲和性注释,简化了将pod绑定到它们需要访问的存储的过程:

apiVersion:v1kind:PersistentVolumemetadata:name:example-pvannotations:"volume.alpha.kubernetes.io/node-affinity":'{"requiredDuringSchedulingIgnoredDuringExecution":{"nodeSelectorTerms":[{"matchExpressions":[{"key":"kubernetes.io/hostname","operator":"In","values":["example-node"]}]}]}}'spec:capacity:storage:100GiaccessModes:-ReadWriteOncepersistentVolumeReclaimPolicy:DeletestorageClassName:local-storagelocal:path:/mnt/disks/ssd1提供持久卷emptyDir卷可以被挂载和容器使用,但它们不是持久的,也不需要任何特殊的配置,因为它们使用节点上的现有存储。HostPath卷在原始节点上持久存在,但如果pod在不同的节点上重新启动,它无法访问先前节点上的HostPath卷。Local卷在节点上持久存在,可以在pod重新启动、重新调度甚至节点重新启动时幸存下来。真正的持久卷使用提前由管理员配置的外部存储(不是物理连接到节点的磁盘)。在云环境中,配置可能非常简化,但仍然是必需的,作为Kubernetes集群管理员,您至少要确保您的存储配额是充足的,并且要认真监控使用情况与配额的对比。

请记住,持久卷是Kubernetes集群类似于节点使用的资源。因此,它们不受KubernetesAPI服务器的管理。您可以静态或动态地配置资源。

以下是NFS持久卷的配置文件:

每个卷都有指定的存储量。存储索赔可以由至少具有该存储量的持久卷满足。例如,持久卷的容量为100Gibibytes(23字节)。在分配静态持久卷时,了解存储请求模式非常重要。例如,如果您配置了100GiB容量的20个持久卷,并且容器索赔了150GiB的持久卷,则即使总体容量足够,该索赔也不会得到满足:

capacity:storage:100Gi卷模式可选的卷模式在Kubernetes1.9中作为静态配置的Alpha功能添加(即使您在规范中指定它作为字段,而不是在注释中)。它允许您指定是否需要文件系统("Filesystem")或原始存储("Block")。如果不指定卷模式,则默认值是"Filesystem",就像在1.9之前一样。

有三种访问模式:

ReadOnlyMany:可以由多个节点挂载为只读

ReadWriteOnce:可以由单个节点挂载为读写

ReadWriteMany:可以由多个节点挂载为读写

存储被挂载到节点,所以即使使用ReadWriteOnce,同一节点上的多个容器也可以挂载该卷并对其进行写入。如果这造成问题,您需要通过其他机制来处理(例如,您可以只在您知道每个节点只有一个的DaemonSetpods中索赔该卷)。

不同的存储提供程序支持这些模式的一些子集。当您配置持久卷时,可以指定它将支持哪些模式。例如,NFS支持所有模式,但在示例中,只启用了这些模式:

accessModes:-ReadWriteMany-ReadOnlyMany回收策略回收策略确定持久卷索赔被删除时会发生什么。有三种不同的策略:

Retain:需要手动回收卷

Delete:关联的存储资产,如AWSEBS、GCEPD、Azure磁盘或OpenStackCinder卷,将被删除

Recycle:仅删除内容(rm-rf/volume/*)

Retain和Delete策略意味着持久卷将不再对未来索赔可用。recycle策略允许再次索赔该卷。

目前,只有NFS和HostPath支持回收。AWSEBS、GCEPD、Azure磁盘和Cinder卷支持删除。动态配置的卷总是被删除。

您可以使用规范的可选storageClassName字段指定存储类。如果这样做,那么只有指定相同存储类的持久卷要求才能绑定到持久卷。如果不指定存储类,则只有不指定存储类的持久卷要求才能绑定到它。

卷类型在规范中通过名称指定。没有volumeType部分。

在前面的示例中,nfs是卷类型:

nfs:path:/tmpserver:172.17.0.8每种卷类型可能有自己的一组参数。在这种情况下,它是一个path

和server。

我们将在本章后面讨论各种卷类型。

当容器需要访问某些持久存储时,它们会提出要求(或者说,开发人员和集群管理员会协调必要的存储资源

要求)。以下是一个与上一节中的持久卷匹配的示例要求:

kind:PersistentVolumeClaimapiVersion:v1metadata:name:storage-claimspec:accessModes:-ReadWriteOnceresources:requests:storage:80GistorageClassName:"normal"selector:matchLabels:release:"stable"matchExpressions:-{key:capacity,operator:In,values:[80Gi,100Gi]}名称storage-claim在将要将要求挂载到容器中时将变得重要。

规范中的访问模式为ReadWriteOnce,这意味着如果要求得到满足,则不能满足其他具有ReadWriteOnce访问模式的要求,但仍然可以满足ReadOnlyMany的要求。

资源部分请求80GiB。这可以通过我们的持久卷满足,它的容量为100GiB。但这有点浪费,因为20GiB将不会被使用。

存储类名称为"normal"。如前所述,它必须与持久卷的类名匹配。但是,对于持久卷要求(PVC),空类名("")和没有类名之间存在差异。前者(空类名)与没有存储类名的持久卷匹配。后者(没有类名)只有在关闭DefaultStorageClass准入插件或者打开并且使用默认存储类时才能绑定到持久卷。

Selector部分允许您进一步过滤可用的卷。例如,在这里,卷必须匹配标签release:"stable",并且还必须具有标签capacity:80Gi或capacity:100Gi。假设我们还有其他几个容量为200Gi和500Gi的卷。当我们只需要80Gi时,我们不希望索赔500Gi的卷。

Kubernetes始终尝试匹配可以满足索赔的最小卷,但如果没有80Gi或100Gi的卷,那么标签将阻止分配200Gi或500Gi的卷,并使用动态配置。

重要的是要意识到索赔不会按名称提及卷。匹配是由基于存储类、容量和标签的Kubernetes完成的。

最后,持久卷索赔属于命名空间。将持久卷绑定到索赔是排他的。这意味着持久卷将绑定到一个命名空间。即使访问模式是ReadOnlyMany或ReadWriteMany,所有挂载持久卷索赔的Pod必须来自该索赔的命名空间。

好的。我们已经配置了一个卷并对其进行了索赔。现在是时候在容器中使用索赔的存储了。这其实非常简单。首先,持久卷索赔必须在Pod中用作卷,然后Pod中的容器可以像任何其他卷一样挂载它。这是一个pod配置文件,指定了我们之前创建的持久卷索赔(绑定到我们配置的NFS持久卷)。

kind:PodapiVersion:v1metadata:name:the-podspec:containers:-name:the-containerimage:some-imagevolumeMounts:-mountPath:"/mnt/data"name:persistent-volumevolumes:-name:persistent-volumepersistentVolumeClaim:claimName:storage-claim关键在volumes下的persistentVolumeClaim部分。索赔名称(这里是storage-claim)在当前命名空间内唯一标识特定索赔,并使其作为卷命名为persistent-volume。然后,容器可以通过名称引用它,并将其挂载到/mnt/data。

Kubernetes1.9将此功能作为alpha功能添加。您必须使用功能门控来启用它:--feature-gates=BlockVolume=true。

原始块卷提供对底层存储的直接访问,不经过文件系统抽象。这对需要高存储性能的应用程序非常有用,比如数据库,或者需要一致的I/O性能和低延迟。光纤通道、iSCSI和本地SSD都适用于用作原始块存储。目前(Kubernetes1.10),只有LocalVolume和FiberChannel存储提供程序支持原始块卷。以下是如何定义原始块卷:

apiVersion:v1kind:PersistentVolumemetadata:name:block-pvspec:capacity:storage:10GiaccessModes:-ReadWriteOncevolumeMode:BlockpersistentVolumeReclaimPolicy:Retainfc:targetWWNs:["50060e801049cfd1"]lun:0readOnly:false匹配的PVC必须指定volumeMode:Block。这是它的样子:

apiVersion:v1kind:PersistentVolumeClaimmetadata:name:block-pvcspec:accessModes:-ReadWriteOncevolumeMode:Blockresources:requests:storage:10GiPods将原始块卷作为/dev下的设备而不是挂载的文件系统来消耗。容器可以访问这个设备并对其进行读/写。实际上,这意味着对块存储的I/O请求直接传递到底层块存储,而不经过文件系统驱动程序。理论上这更快,但实际上如果您的应用程序受益于文件系统缓冲,它实际上可能会降低性能。

这是一个带有容器的Pod,它将block-pvc与原始块存储绑定为名为/dev/xdva的设备:

apiVersion:v1kind:Podmetadata:name:pod-with-block-volumespec:containers:-name:fc-containerimage:fedora:26command:["/bin/sh","-c"]args:["tail-f/dev/null"]volumeDevices:-name:datadevicePath:/dev/xvdavolumes:-name:datapersistentVolumeClaim:claimName:block-pvc存储类存储类允许管理员使用自定义持久存储配置集群(只要有适当的插件支持)。存储类在metadata中有一个name,一个provisioner和parameters:

kind:StorageClassapiVersion:storage.k8s.io/v1metadata:name:standardprovisioner:kubernetes.io/aws-ebsparameters:type:gp2您可以为同一个提供程序创建多个存储类,每个提供程序都有自己的参数。

目前支持的卷类型如下:

AwsElasticBlockStore

AzureFile

AzureDisk

CephFS

Cinder

FC

FlexVolume

Flocker

GcePersistentDisk

GlusterFS

ISCSI

PhotonPersistentDisk

Quobyte

NFS

RBD

VsphereVolume

PortworxVolume

ScaleIO

StorageOS

Local

这个列表不包含其他卷类型,比如gitRepo或secret,这些类型不是由典型的网络存储支持的。Kubernetes的这个领域仍然在变化中,将来它会进一步解耦,设计会更清晰,插件将不再是Kubernetes本身的一部分。智能地利用卷类型是架构和管理集群的重要部分。

让我们首先创建一个hostPath卷。将以下内容保存在persistent-volume.yaml中:

kind:PersistentVolumeapiVersion:v1metadata:name:persistent-volume-1spec:StorageClassName:dircapacity:storage:1GiaccessModes:-ReadWriteOncehostPath:path:"/tmp/data">kubectlcreate-fpersistent-volume.yamlpersistentvolume"persistent-volume-1"created要查看可用的卷,可以使用persistentvolumes资源类型,或者简称为pv:

RWO:ReadWriteOnce

ROX:ReadOnlyMany

RWX:ReadWriteMany

kind:PersistentVolumeClaimapiVersion:v1metadata:name:persistent-volume-claimspec:accessModes:-ReadWriteOnceresources:requests:storage:1Gi然后,运行以下命令:

>kubectlcreate-fpersistent-volume-claim.yamlpersistentvolumeclaim"persistent-volume-claim"created让我们检查一下claim和volume:

>kubectlgetpvcNAMESTATUSVOLUMECAPACITYACCESSMODESAGEpersistent-volume-claimBoundpersistent-volume-11GiRWOdir1m>kubectlgetpvNAME:persistent-volume-1CAPACITY:1GiACCESSMODES:RWORECLAIMPOLICY:RetainSTATUS:BoundCLAIM:default/persistent-volume-claimSTORAGECLASS:dirREASON:AGE:3m如您所见,claim和volume已经绑定在一起。最后一步是创建一个pod并将claim分配为volume。将以下内容保存到shell-pod.yaml中:

让我们创建pod并验证两个容器都在运行:

>kubectlcreate-fshell-pod.yamlpod"just-a-shell"created>kubectlgetpodsNAMEREADYSTATUSRESTARTSAGEjust-a-shell2/2Running01m然后,ssh到节点。这是主机,其/tmp/data是pod的卷,挂载为每个正在运行的容器的/data:

>minikubessh$在节点内部,我们可以使用Docker命令与容器进行通信。让我们看一下最后两个正在运行的容器:

$dockerps-n2--format'{{.ID}}\t{{.Image}}\t{{.Command}}'820fc954fb96ubuntu"/bin/bash-c'whi..."cf4502f14be5ubuntu"/bin/bash-c'whi..."然后,在主机的/tmp/data目录中创建一个文件。它应该通过挂载的卷对两个容器都可见:

$sudotouch/tmp/data/1.txt让我们在其中一个容器上执行一个shell,验证文件1.txt确实可见,并创建另一个文件2.txt:

$dockerexec-it820fc954fb96/bin/bashroot@just-a-shell:/#ls/data1.txtroot@just-a-shell:/#touch/data/2.txtroot@just-a-shell:/#exitFinally,wecanrunashellontheothercontainerandverifythatboth1.txtand2.txtarevisible:docker@minikube:~$dockerexec-itcf4502f14be5/bin/bashroot@just-a-shell:/#ls/data1.txt2.txt公共存储卷类型-GCE,AWS和Azure在本节中,我们将介绍一些主要公共云平台中可用的常见卷类型。在规模上管理存储是一项困难的任务,最终涉及物理资源,类似于节点。如果您选择在公共云平台上运行您的Kubernetes集群,您可以让您的云提供商处理所有这些挑战,并专注于您的系统。但重要的是要了解每种卷类型的各种选项、约束和限制。

AWS为EC2实例提供EBS作为持久存储。AWSKubernetes集群可以使用AWSEBS作为持久存储,但有以下限制:

pod必须在AWSEC2实例上作为节点运行

Pod只能访问其可用区中配置的EBS卷

EBS卷可以挂载到单个EC2实例

这些是严重的限制。单个可用区的限制,虽然对性能有很大帮助,但消除了在规模或地理分布系统中共享存储的能力,除非进行自定义复制和同步。单个EBS卷限制为单个EC2实例意味着即使在同一可用区内,pod也无法共享存储(甚至是读取),除非您确保它们在同一节点上运行。

AWS最近推出了一项名为弹性文件系统(EFS)的新服务。这实际上是一个托管的NFS服务。它使用NFS4.1协议,并且与EBS相比有许多优点:

多个EC2实例可以跨多个可用区(但在同一区域内)访问相同的文件

容量根据实际使用情况自动扩展和缩减

您只支付您使用的部分

您可以通过VPN将本地服务器连接到EFS

EFS运行在自动在可用区之间复制的SSD驱动器上

话虽如此,即使考虑到自动复制到多个可用区(假设您充分利用了EBS卷),EFS比EBS更加广泛。它正在使用外部供应商,部署起来并不是微不足道的。请按照这里的说明进行操作:

kind:PersistentVolumeClaimapiVersion:v1metadata:name:efsannotations:volume.beta.kubernetes.io/storage-class:"aws-efs"spec:accessModes:-ReadWriteManyresources:requests:storage:1Mi这是一个使用它的pod:

kind:PodapiVersion:v1metadata:name:test-podspec:containers:-name:test-podimage:gcr.io/google_containers/busybox:1.24command:-"/bin/sh"args:-"-c"-"touch/mnt/SUCCESSexit0||exit1"volumeMounts:-name:efs-pvcmountPath:"/mnt"restartPolicy:"Never"volumes:-name:efs-pvcpersistentVolumeClaim:claimName:efsGCE持久磁盘gcePersistentDisk卷类型与awsElasticBlockStore非常相似。您必须提前规划磁盘。它只能被同一项目和区域中的GCE实例使用。但是同一卷可以在多个实例上以只读方式使用。这意味着它支持ReadWriteOnce和ReadOnlyMany。您可以使用GCE持久磁盘在同一区域的多个pod之间共享数据。

使用ReadWriteOnce模式中的持久磁盘的pod必须由复制控制器、副本集或具有0或1个副本计数的部署控制。尝试扩展到1之外的数量将因明显原因而失败:

apiVersion:v1kind:Podmetadata:name:some-podspec:containers:-image:some-containername:some-containervolumeMounts:-mountPath:/pdname:some-volumevolumes:-name:some-volumegcePersistentDisk:pdName:fsType:ext4Azure数据磁盘Azure数据磁盘是存储在Azure存储中的虚拟硬盘。它的功能类似于AWSEBS。这是一个示例pod配置文件:

cachingMode:磁盘缓存模式。必须是None、ReadOnly或ReadWrite之一。默认值为None。

fsType:文件系统类型设置为mount。默认值为ext4。

readOnly:文件系统是否以readOnly模式使用。默认值为false。

Azure数据磁盘的限制为1,023GB。每个AzureVM最多可以有16个数据磁盘。您可以将Azure数据磁盘附加到单个AzureVM上。

除了数据磁盘,Azure还有一个类似于AWSEFS的共享文件系统。但是,Azure文件存储使用SMB/CIFS协议(支持SMB2.1和SMB3.0)。它基于Azure存储平台,具有与AzureBlob、Table或Queue相同的可用性、耐用性、可扩展性和地理冗余能力。

为了使用Azure文件存储,您需要在每个客户端VM上安装cifs-utils软件包。您还需要创建一个secret,这是一个必需的参数:

apiVersion:v1kind:Secretmetadata:name:azure-file-secrettype:Opaquedata:azurestorageaccountname:azurestorageaccountkey:这是一个Azure文件存储的配置文件:

apiVersion:v1kind:Podmetadata:name:some-podspec:containers:-image:some-containername:some-containervolumeMounts:-name:some-volumemountPath:/azurevolumes:-name:some-volumeazureFile:secretName:azure-file-secretshareName:azure-sharereadOnly:falseAzure文件存储支持在同一地区内共享以及连接本地客户端。以下是说明工作流程的图表:

GlusterFS和Ceph是两个分布式持久存储系统。GlusterFS在其核心是一个网络文件系统。Ceph在核心是一个对象存储。两者都公开块、对象和文件系统接口。两者都在底层使用xfs文件系统来存储数据和元数据作为xattr属性。您可能希望在Kubernetes集群中使用GlusterFS或Ceph作为持久卷的几个原因:

您可能有很多数据和应用程序访问GlusterFS或Ceph中的数据

您具有管理和操作GlusterFS的专业知识

或Ceph

GlusterFS故意简单,将底层目录公开,并留给客户端(或中间件)处理高可用性、复制和分发。GlusterFS将数据组织成逻辑卷,其中包括包含文件的多个节点(机器)的砖块。文件根据DHT(分布式哈希表)分配给砖块。如果文件被重命名或GlusterFS集群被扩展或重新平衡,文件可能会在砖块之间移动。以下图表显示了GlusterFS的构建模块:

要将GlusterFS集群用作Kubernetes的持久存储(假设您已经运行了GlusterFS集群),您需要遵循几个步骤。特别是,GlusterFS节点由插件作为Kubernetes服务进行管理(尽管作为应用程序开发人员,这与您无关)。

这是一个端点资源的示例,您可以使用kubectlcreate创建为普通的Kubernetes资源:

{"kind":"Endpoints","apiVersion":"v1","metadata":{"name":"glusterfs-cluster"},"subsets":[{"addresses":[{"ip":"10.240.106.152"}],"ports":[{"port":1}]},{"addresses":[{"ip":"10.240.79.157"}],"ports":[{"port":1}]}]}添加GlusterFSKubernetes服务为了使端点持久,您可以使用一个没有选择器的Kubernetes服务来指示端点是手动管理的:

{"kind":"Service","apiVersion":"v1","metadata":{"name":"glusterfs-cluster"},"spec":{"ports":[{"port":1}]}}创建Pods最后,在pod规范的volumes部分中,提供以下信息:

"volumes":[{"name":"glusterfsvol","glusterfs":{"endpoints":"glusterfs-cluster","path":"kube_vol","readOnly":true}}]然后容器可以按名称挂载glusterfsvol。

endpoints告诉GlusterFS卷插件如何找到GlusterFS集群的存储节点。

Ceph的对象存储可以使用多个接口访问。Kubernetes支持RBD(块)和CEPHFS(文件系统)接口。以下图表显示了RADOS-底层对象存储-如何在多天内访问。与GlusterFS不同,Ceph会自动完成大量工作。它自行进行分发、复制和自我修复:

Kubernetes通过RadosBlockDevice(RBD)接口支持Ceph。您必须在Kubernetes集群中的每个节点上安装ceph-common。一旦您的Ceph集群正常运行,您需要在pod配置文件中提供CephRBD卷插件所需的一些信息:

monitors:Ceph监视器。

pool:RADOS池的名称。如果未提供,则使用默认的RBD池。

image:RBD创建的镜像名称。

user:RADOS用户名。如果未提供,则使用默认的admin。

keyring:keyring文件的路径。如果未提供,则使用默认的/etc/ceph/keyring。

*secretName:认证密钥的名称。如果提供了一个,则secretName会覆盖keyring。注意:请参阅下一段关于如何创建secret的内容。

fsType:在其上格式化的文件系统类型(ext4、xfs等)。

设备。

如果使用了Ceph认证secret,则需要创建一个secret对象:

apiVersion:v1kind:Secretmetadata:name:ceph-secrettype:"kubernetes.io/rbd"data:key:QVFCMTZWMVZvRjVtRXhBQTVrQ1FzN2JCajhWVUxSdzI2Qzg0SEE9PQ==secret类型为kubernetes.io/rbd。

pod规范的volumes部分看起来与此相同:

"volumes":[{"name":"rbdpd","rbd":{"monitors":["10.16.154.78:6789","10.16.154.82:6789","10.16.154.83:6789"],"pool":"kube","image":"foo","user":"admin","secretRef":{"name":"ceph-secret"},"fsType":"ext4","readOnly":true}}]CephRBD支持ReadWriteOnce和ReadOnlyMany访问模式。

如果您的Ceph集群已经配置了CephFS,则可以非常轻松地将其分配给pod。此外,CephFS支持ReadWriteMany访问模式。

配置类似于CephRBD,只是没有池、镜像或文件系统类型。密钥可以是对Kubernetessecret对象的引用(首选)或secret文件:

apiVersion:v1kind:Podmetadata:name:cephfsspec:containers:-name:cephfs-rwimage:kubernetes/pausevolumeMounts:-mountPath:"/mnt/cephfs"name:cephfsvolumes:-name:cephfscephfs:monitors:-10.16.154.78:6789-10.16.154.82:6789-10.16.154.83:6789user:adminsecretFile:"/etc/ceph/admin.secret"readOnly:true您还可以在cephfs系统中提供路径作为参数。默认为/。

内置的RBD供应程序在外部存储Kubernetes孵化器项目中有一个独立的副本。

到目前为止,我们已经讨论了将数据存储在Kubernetes集群之外的存储解决方案(除了emptyDir和HostPath,它们不是持久的)。Flocker有点不同。它是Docker感知的。它旨在让Docker数据卷在容器在节点之间移动时一起传输。如果你正在将基于Docker的系统从不同的编排平台(如Dockercompose或Mesos)迁移到Kubernetes,并且你使用Flocker来编排存储,你可能想使用Flocker卷插件。就个人而言,我觉得Flocker所做的事情和Kubernetes为抽象存储所做的事情之间存在很多重复。

Flocker有一个控制服务和每个节点上的代理。它的架构与Kubernetes非常相似,其API服务器和每个节点上运行的Kubelet。Flocker控制服务公开了一个RESTAPI,并管理着整个集群的状态配置。代理负责确保其节点的状态与当前配置匹配。例如,如果一个数据集需要在节点X上,那么节点X上的Flocker代理将创建它。

以下图表展示了Flocker架构:

为了在Kubernetes中使用Flocker作为持久卷,你首先必须有一个正确配置的Flocker集群。Flocker可以与许多后备存储一起工作(再次,与Kubernetes持久卷非常相似)。

然后你需要创建Flocker数据集,这时你就可以将其连接为持久卷了。经过你的辛勤工作,这部分很容易,你只需要指定Flocker数据集的名称:

apiVersion:v1kind:Podmetadata:name:some-podspec:containers:-name:some-containerimage:kubernetes/pausevolumeMounts:#namemustmatchthevolumenamebelow-name:flocker-volumemountPath:"/flocker"volumes:-name:flocker-volumeflocker:datasetName:some-flocker-dataset将企业存储集成到Kubernetes中如果你有一个通过iSCSI接口公开的现有存储区域网络(SAN),Kubernetes为你提供了一个卷插件。它遵循了我们之前看到的其他共享持久存储插件的相同模型。你必须配置iSCSI启动器,但你不必提供任何启动器信息。你只需要提供以下内容:

iSCSI目标的IP地址和端口(如果不是默认的3260)

目标的iqn(iSCSI合格名称)—通常是反向域名

LUN—逻辑单元号

文件系统类型

readonly布尔标志

iSCSI插件支持ReadWriteOnce和ReadonlyMany。请注意,目前无法对设备进行分区。以下是卷规范:

volumes:-name:iscsi-volumeiscsi:targetPortal:10.0.2.34:3260iqn:iqn.2001-04.com.example:storage.kube.sys1.xyzlun:0fsType:ext4readOnly:true投影卷可以将多个卷投影到单个目录中,使其显示为单个卷。支持的卷类型有:secret,downwardAPI和configMap。如果您想将多个配置源挂载到一个pod中,这将非常有用。您可以将它们全部捆绑到一个投影卷中,而不必为每个源创建单独的卷。这是一个例子:

apiVersion:v1kind:Podmetadata:name:the-podspec:containers:-name:the-containerimage:busyboxvolumeMounts:-name:all-in-onemountPath:"/projected-volume"readOnly:truevolumes:-name:all-in-oneprojected:sources:-secret:name:the-secretitems:-key:usernamepath:the-group/the-user-downwardAPI:items:-path:"labels"fieldRef:fieldPath:metadata.labels-path:"cpu_limit"resourceFieldRef:containerName:the-containerresource:limits.cpu-configMap:name:the-configmapitems:-key:configpath:the-group/the-config使用FlexVolume的外部卷插件FlexVolume在Kubernetes1.8中已经普遍可用。它允许您通过统一API消耗外部存储。存储提供商编写一个驱动程序,您可以在所有节点上安装。FlexVolume插件可以动态发现现有的驱动程序。以下是使用FlexVolume绑定到外部NFS卷的示例:

apiVersion:v1kind:Podmetadata:name:nginx-nfsnamespace:defaultspec:containers:-name:nginx-nfsimage:nginxvolumeMounts:-name:testmountPath:/dataports:-containerPort:80volumes:-name:testflexVolume:driver:"k8s/nfs"fsType:"nfs"options:server:"172.16.0.25"share:"dws_nas_scratch"容器存储接口容器存储接口(CSI)是标准化容器编排器和存储提供商之间交互的一个倡议。它由Kubernetes、Docker、Mesos和CloudFoundry推动。其想法是存储提供商只需要实现一个插件,容器编排器只需要支持CSI。这相当于存储的CNI。与FlexVolume相比,有几个优点:

CSI是一个行业标准

FlexVolume插件需要访问节点和主节点根文件系统来部署驱动程序

FlexVolume存储驱动程序通常需要许多外部依赖项

FlexVolume的EXEC风格接口很笨拙

这是一个演示CSI在Kubernetes中如何工作的图表:

在第八章中,《使用Kubernetes运行有状态应用程序》,我们将看到Kubernetes如何提高抽象级别,并在存储之上,利用StatefulSets等概念来开发、部署和操作有状态的应用程序。

在Kubernetes中,无状态应用是指不在Kubernetes集群中管理其状态的应用。所有状态都存储在集群外,集群容器以某种方式访问它。在本节中,我们将了解为什么状态管理对于分布式系统的设计至关重要,以及在Kubernetes集群内管理状态的好处。

让我们从基础知识开始。分布式应用程序是在多台计算机上运行的一组进程,处理输入,操作数据,公开API,并可能具有其他副作用。每个进程是其程序、运行时环境和输入输出的组合。你在学校写的程序会作为命令行参数获取输入,也许它们会读取文件或访问数据库,然后将结果写入屏幕、文件或数据库。一些程序在内存中保持状态,并可以通过网络提供请求。简单的程序在单台计算机上运行,可以将所有状态保存在内存中或从文件中读取。它们的运行时环境是它们的操作系统。如果它们崩溃,用户必须手动重新启动它们。它们与它们的计算机绑定在一起。分布式应用程序是一个不同的动物。单台计算机不足以处理所有数据或足够快地提供所有请求。单台计算机无法容纳所有数据。需要处理的数据如此之大,以至于无法以成本效益的方式下载到每个处理机器中。机器可能会出现故障,需要被替换。需要在所有处理机器上执行升级。用户可能分布在全球各地。

考虑所有这些问题后,很明显传统方法行不通。限制因素变成了数据。用户/客户端必须只接收摘要或处理过的数据。所有大规模数据处理必须在数据附近进行,因为传输数据的速度慢且昂贵。相反,大部分处理代码必须在相同的数据中心和网络环境中运行。

Kubernetes为集群中的全局发现提供了几种机制。如果您的存储集群不是由Kubernetes管理,您仍然需要告诉Kubernetespod如何找到它并访问它。主要有两种方法:

DNS

环境变量

在某些情况下,您可能希望同时使用环境变量和DNS,其中环境变量可以覆盖DNS。

在Kubernetes中管理状态的主要原因是,与在单独的集群中管理相比,Kubernetes已经提供了许多监视、扩展、分配、安全和操作存储集群所需的基础设施。运行并行存储集群将导致大量重复的工作。

让我们不排除其他选择。在某些情况下,将状态管理在一个单独的非Kubernetes集群中可能更好,只要它与相同的内部网络共享(数据接近性胜过一切)。

一些有效的原因如下:

您已经有一个单独的存储集群,不想引起麻烦

您的存储集群被其他非Kubernetes应用程序使用

Kubernetes对您的存储集群的支持还不够稳定或成熟

您可能希望逐步在Kubernetes中处理有状态的应用程序,首先从一个单独的存储集群开始,然后再与Kubernetes更紧密地集成。

DNS方法简单直接。假设您的外部存储集群是负载均衡的,并且可以提供稳定的端点,那么pod可以直接命中该端点并连接到外部集群。

另一种简单的方法是使用环境变量传递连接信息到外部存储集群。Kubernetes提供ConfigMap资源作为一种将配置与容器镜像分开的方式。配置是一组键值对。配置信息可以作为环境变量暴露在容器内部以及卷中。您可能更喜欢使用秘密来存储敏感的连接信息。

以下配置文件将创建一个保留地址列表的配置文件:

apiVersion:v1kind:ConfigMapmetadata:name:db-confignamespace:defaultdata:db-ip-addresses:1.2.3.4,5.6.7.8>kubectlcreate-f.\configmap.yamlconfigmap"db-config"createddata部分包含所有的键值对,这种情况下,只有一个键名为db-ip-addresses的键值对。在后面消耗configmap时将会很重要。您可以检查内容以确保它是正确的:

>kubectlgetconfigmapdb-config-oyamlapiVersion:v1data:db-ip-addresses:1.2.3.4,5.6.7.8kind:ConfigMapmetadata:creationTimestamp:2017-01-09T03:14:07Zname:db-confignamespace:defaultresourceVersion:"551258"selfLink:/api/v1/namespaces/default/configmaps/db-configuid:aebcc007-d619-11e6-91f1-3a7ae2a25c7d还有其他创建ConfigMap的方法。您可以直接使用--from-value或--from-file命令行参数来创建它们。

当您创建一个pod时,可以指定一个ConfigMap并以多种方式使用其值。以下是如何将我们的配置映射为环境变量:

apiVersion:v1kind:Podmetadata:name:some-podspec:containers:-name:some-containerimage:busyboxcommand:["/bin/sh","-c","env"]env:-name:DB_IP_ADDRESSESvalueFrom:configMapKeyRef:name:db-configkey:db-ip-addressesrestartPolicy:Never这个pod运行busybox最小容器,并执行envbash命令,然后立即退出。db-config映射中的db-ip-addresses键被映射到DB_IP_ADDRESSES环境变量,并反映在输出中:

一些有状态的应用程序,如分布式数据库或队列,会冗余地管理它们的状态并自动同步它们的节点(我们稍后将深入研究Cassandra)。在这些情况下,重要的是将Pod调度到单独的节点。同样重要的是,Pod应该被调度到具有特定硬件配置的节点,甚至专门用于有状态应用程序。DaemonSet功能非常适合这种用例。我们可以为一组节点打上标签,并确保有状态的Pod被逐个地调度到所选的节点组。

如果有状态的应用程序可以有效地使用共享的持久存储,那么在每个Pod中使用持久卷索赔是正确的方法,就像我们在第七章中演示的那样,处理Kubernetes存储。有状态的应用程序将被呈现为一个看起来就像本地文件系统的挂载卷。

StatefulSet非常适合需要以下一项或多项功能的应用程序:

稳定、独特的网络标识符

稳定的持久存储

有序、优雅的部署和扩展

有序、优雅的删除和终止

有几个部分需要正确配置,才能使StatefulSet正常工作:

一个负责管理StatefulSetpod的网络标识的无头服务

具有多个副本的StatefulSet本身

动态或由管理员持久存储提供

这是一个名为nginx的服务的示例,将用于StatefulSet:

apiVersion:v1kind:Servicemetadata:name:nginxlabels:app:nginxspec:ports:-port:80name:webclusterIP:Noneselector:app:nginx现在,StatefulSet配置文件将引用该服务:

apiVersion:apps/v1kind:StatefulSetmetadata:name:webspec:serviceName:"nginx"replicas:3template:metadata:labels:app:nginx接下来是包含名为www的挂载卷的pod模板:

volumeClaimTemplates:-metadata:name:wwwspec:accessModes:["ReadWriteOnce"]resources:requests:storage:1Gib在Kubernetes中运行Cassandra集群在本节中,我们将详细探讨配置Cassandra集群在Kubernetes集群上运行的一个非常大的示例。完整的示例可以在这里访问:

首先,我们将学习一些关于Cassandra及其特殊性的知识,然后按照逐步的步骤来使其运行,使用我们在前一节中介绍的几种技术和策略。

这是一个图表,显示了Cassandra集群的组织方式,以及客户端如何访问任何节点,请求将如何自动转发到具有所请求数据的节点:

在Kubernetes上部署Cassandra与独立的Cassandra集群部署相反,需要一个特殊的Docker镜像。这是一个重要的步骤,因为这意味着我们可以使用Kubernetes来跟踪我们的Cassandrapod。该镜像在这里可用:

以下是Docker文件的基本部分。该镜像基于UbuntuSlim:

FROMgcr.io/google_containers/ubuntu-slim:0.9添加和复制必要的文件(Cassandra.jar,各种配置文件,运行脚本和读取探测脚本),创建一个data目录供Cassandra存储其SSTable,并挂载它:

#7000:intra-nodecommunication#7001:TLSintra-nodecommunication#7199:JMX#9042:CQL#9160:thriftserviceEXPOSE70007001719990429160最后,使用dumb-init命令运行run.sh脚本,这是一个来自yelp的简单容器init系统:

CMD["/sbin/dumb-init","/bin/bash","/run.sh"]探索run.sh脚本run.sh脚本需要一些shell技能,但这是值得的。由于Docker只允许运行一个命令,对于非平凡的应用程序来说,有一个设置环境并为实际应用程序做准备的启动脚本是非常常见的。在这种情况下,镜像支持几种部署选项(有状态集、复制控制器、DaemonSet),我们稍后会介绍,而运行脚本通过环境变量非常可配置。

首先,为/etc/cassandra/cassandra.yaml中的Cassandra配置文件设置了一些本地变量。CASSANDRA_CFG变量将在脚本的其余部分中使用:

set-eCASSANDRA_CONF_DIR=/etc/cassandraCASSANDRA_CFG=$CASSANDRA_CONF_DIR/cassandra.yaml如果没有指定CASSANDRA_SEEDS,那么设置HOSTNAME,它在StatefulSet解决方案中使用:

#wearedoingStatefulSetorjustsettingourseedsif[-z"$CASSANDRA_SEEDS"];thenHOSTNAME=$(hostname-f)Fi然后是一长串带有默认值的环境变量。语法${VAR_NAME:-}使用VAR_NAME环境变量,如果定义了的话,或者使用默认值。

类似的语法${VAR_NAME:=

如果未定义环境变量,则将默认值分配给它。

这里都用到了两种变体:

echoStartingCassandraon${CASSANDRA_LISTEN_ADDRESS}echoCASSANDRA_CONF_DIR${CASSANDRA_CONF_DIR}...接下来的部分非常重要。默认情况下,Cassandra使用简单的snitch,不知道机架和数据中心。当集群跨多个数据中心和机架时,这并不是最佳选择。

Cassandra是机架和数据中心感知的,可以优化冗余性和高可用性,同时适当地限制跨数据中心的通信:

#ifDCandRACKareset,useGossipingPropertyFileSnitchif[[$CASSANDRA_DC&&$CASSANDRA_RACK]];thenecho"dc=$CASSANDRA_DC">$CASSANDRA_CONF_DIR/cassandra-rackdc.propertiesecho"rack=$CASSANDRA_RACK">>$CASSANDRA_CONF_DIR/cassandra-rackdc.propertiesCASSANDRA_ENDPOINT_SNITCH="GossipingPropertyFileSnitch"fi内存管理很重要,您可以控制最大堆大小,以确保Cassandra不会开始抖动并开始与磁盘交换:

if[-n"$CASSANDRA_MAX_HEAP"];thensed-ri"s/^(#)-Xmx[0-9]+.*/-Xmx$CASSANDRA_MAX_HEAP/""$CASSANDRA_CONF_DIR/jvm.options"sed-ri"s/^(#)-Xms[0-9]+.*/-Xms$CASSANDRA_MAX_HEAP/""$CASSANDRA_CONF_DIR/jvm.options"fiif[-n"$CASSANDRA_REPLACE_NODE"];thenecho"-Dcassandra.replace_address=$CASSANDRA_REPLACE_NODE/">>"$CASSANDRA_CONF_DIR/jvm.options"fi机架和数据中心信息存储在一个简单的Javaproperties文件中:

forrackdcindcrack;dovar="CASSANDRA_${rackdc^^}"val="${!var}"if["$val"];thensed-ri's/^('"$rackdc"'=).*/1'"$val"'/'"$CASSANDRA_CONF_DIR/cassandra-rackdc.properties"fidone接下来的部分循环遍历之前定义的所有变量,在Cassandra.yaml配置文件中找到相应的键,并进行覆盖。这确保了每个配置文件在启动Cassandra本身之前都是动态定制的:

foryamlin\broadcast_address\broadcast_rpc_address\cluster_name\disk_optimization_strategy\endpoint_snitch\listen_address\num_tokens\rpc_address\start_rpc\key_cache_size_in_mb\concurrent_reads\concurrent_writes\memtable_cleanup_threshold\memtable_allocation_type\memtable_flush_writers\concurrent_compactors\compaction_throughput_mb_per_sec\counter_cache_size_in_mb\internode_compression\endpoint_snitch\gc_warn_threshold_in_ms\listen_interface\rpc_interface\;dovar="CASSANDRA_${yaml^^}"val="${!var}"if["$val"];thensed-ri's/^(#)('"$yaml"':).*/\2'"$val"'/'"$CASSANDRA_CFG"fidoneecho"auto_bootstrap:${CASSANDRA_AUTO_BOOTSTRAP}">>$CASSANDRA_CFG接下来的部分都是关于根据部署解决方案(StatefulSet或其他)设置种子或种子提供程序。对于第一个pod来说,有一个小技巧可以作为自己的种子引导:

#settheseedtoitself.Thisisonlyforthefirstpod,otherwise#itwillbeabletogetseedsfromtheseedproviderif[[$CASSANDRA_SEEDS=='false']];thensed-ri's/-seeds:.*/-seeds:"'"$POD_IP"'"/'$CASSANDRA_CFGelse#ifwehaveseedssetthem.ProbablyStatefulSetsed-ri's/-seeds:.*/-seeds:"'"$CASSANDRA_SEEDS"'"/'$CASSANDRA_CFGfised-ri's/-class_name:SEED_PROVIDER/-class_name:'"$CASSANDRA_SEED_PROVIDER"'/'$CASSANDRA_CFG以下部分设置了远程管理和JMX监控的各种选项。在复杂的分布式系统中,拥有适当的管理工具至关重要。Cassandra对普遍的Java管理扩展(JMX)标准有深入的支持:

#sendgctostdoutif[[$CASSANDRA_GC_STDOUT=='true']];thensed-ri's/-Xloggc:\/var\/log\/cassandra\/gc\.log//'$CASSANDRA_CONF_DIR/cassandra-env.shfi#enableRMIandJMXtoworkononeportecho"JVM_OPTS=\"\$JVM_OPTS-Djava.rmi.server.hostname=$POD_IP\"">>$CASSANDRA_CONF_DIR/cassandra-env.sh#gettingWARNINGmessageswithMigrationServiceecho"-Dcassandra.migration_task_wait_in_seconds=${CASSANDRA_MIGRATION_WAIT}">>$CASSANDRA_CONF_DIR/jvm.optionsecho"-Dcassandra.ring_delay_ms=${CASSANDRA_RING_DELAY}">>$CASSANDRA_CONF_DIR/jvm.optionsif[[$CASSANDRA_OPEN_JMX=='true']];thenexportLOCAL_JMX=nosed-ri's/-Dcom\.sun\.management\.jmxremote\.authenticate=true/-Dcom\.sun\.management\.jmxremote\.authenticate=false/'$CASSANDRA_CONF_DIR/cassandra-env.shsed-ri's/-Dcom\.sun\.management\.jmxremote\.password\.file=\/etc\/cassandra\/jmxremote\.password//'$CASSANDRA_CONF_DIR/cassandra-env.shfi最后,CLASSPATH设置为CassandraJAR文件,并将Cassandra作为Cassandra用户在前台(非守护进程)启动:

exportCLASSPATH=/kubernetes-cassandra.jarsucassandra-c"$CASSANDRA_HOME/bin/cassandra-f"连接Kubernetes和Cassandra连接Kubernetes和Cassandra需要一些工作,因为Cassandra被设计为非常自给自足,但我们希望让它在适当的时候连接Kubernetes以提供功能,例如自动重新启动失败的节点、监视、分配Cassandrapods,并在其他pods旁边提供Cassandrapods的统一视图。Cassandra是一个复杂的系统,有许多控制选项。它带有一个Cassandra.yaml配置文件,您可以使用环境变量覆盖所有选项。

Cassandra.yaml中配置的默认seed提供程序只是一个静态的IP地址列表,在这种情况下只有环回接口:

seed_provider:-class_name:SEED_PROVIDERparameters:#seedsisactuallyacomma-delimitedlistofaddresses.#Ex:",,"-seeds:"127.0.0.1"另一个重要的设置是snitch。它有两个角色:

它教会Cassandra足够了解您的网络拓扑以有效地路由请求。

Cassandra预装了几个snitch类,但它们都不了解Kubernetes。默认是SimpleSnitch,但可以被覆盖。

#YoucanuseacustomSnitchbysettingthistothefullclass#nameofthesnitch,whichwillbeassumedtobeonyourclasspath.endpoint_snitch:SimpleSnitch自定义seed提供程序在Kubernetes中将Cassandra节点作为pod运行时,Kubernetes可能会移动pod,包括seeds。为了适应这一点,Cassandraseed提供程序需要与KubernetesAPI服务器进行交互。

这是自定义的KubernetesSeedProviderJava类的一个简短片段,它实现了Cassandra的SeedProviderAPI:

这是配置文件:

apiVersion:v1kind:Servicemetadata:labels:app:cassandraname:cassandraspec:clusterIP:Noneports:-port:9042selector:app:Cassandraapp:Cassandra标签将把所有参与服务的pod分组。Kubernetes将创建端点记录,DNS将返回一个用于发现的记录。clusterIP是None,这意味着服务是无头的,Kubernetes不会进行任何负载平衡或代理。这很重要,因为Cassandra节点直接进行通信。

9042端口被Cassandra用于提供CQL请求。这些可以是查询、插入/更新(Cassandra总是使用upsert),或者删除。

这是基本的元数据。请注意,apiVersion字符串是apps/v1(StatefulSet从Kubernetes1.9开始普遍可用):

apiVersion:"apps/v1"kind:StatefulSetmetadata:name:cassandraStatefulSet的spec定义了无头服务的名称,StatefulSet中有多少个pod,以及pod模板(稍后解释)。replicas字段指定了StatefulSet中有多少个pod:

spec:serviceName:cassandrareplicas:3template:...对于pod来说,术语replicas是一个不幸的选择,因为这些pod并不是彼此的副本。它们共享相同的pod模板,但它们有独特的身份,它们负责一般状态的不同子集。在Cassandra的情况下,这更加令人困惑,因为它使用相同的术语replicas来指代冗余复制一些状态的节点组(但它们并不相同,因为每个节点也可以管理额外的状态)。我向Kubernetes项目提出了一个GitHub问题,要求将术语从replicas更改为members:

Pod模板包含一个基于自定义Cassandra镜像的单个容器。以下是带有app:cassandra标签的Pod模板:

template:metadata:labels:app:cassandraspec:containers:...容器规范有多个重要部分。它以name和我们之前查看的image开始:

containers:-name:cassandraimage:gcr.io/google-samples/cassandra:v12imagePullPolicy:Always然后,它定义了Cassandra节点需要的多个容器端口,用于外部和内部通信:

ports:-containerPort:7000name:intra-node-containerPort:7001name:tls-intra-node-containerPort:7199name:jmx-containerPort:9042name:cql资源部分指定容器所需的CPU和内存。这很关键,因为存储管理层不应因cpu或memory而成为性能瓶颈。

resources:limits:cpu:"500m"memory:1Girequests:cpu:"500m"memory:1GiCassandra需要访问IPC,容器通过安全内容的功能请求它:

securityContext:capabilities:add:-IPC_LOCKenv部分指定容器内可用的环境变量。以下是必要变量的部分列表。CASSANDRA_SEEDS变量设置为无头服务,因此Cassandra节点可以在启动时与seeds通信并发现整个集群。请注意,在此配置中,我们不使用特殊的Kubernetes种子提供程序。POD_IP很有趣,因为它利用向status.podIP的字段引用通过DownwardAPI填充其值:

env:-name:MAX_HEAP_SIZEvalue:512M-name:CASSANDRA_SEEDSvalue:"cassandra-0.cassandra.default.svc.cluster.local"-name:POD_IPvalueFrom:fieldRef:fieldPath:status.podIP容器还有一个就绪探针,以确保Cassandra节点在完全在线之前不会收到请求:

readinessProbe:exec:command:-/bin/bash--c-/ready-probe.shinitialDelaySeconds:15timeoutSeconds:5当然,Cassandra需要读写数据。cassandra-data卷挂载就是这样的:

volumeMounts:-name:cassandra-datamountPath:/cassandra_data容器规范就是这样。最后一部分是卷索赔模板。在这种情况下,使用了动态配置。强烈建议为Cassandra存储使用SSD驱动器,特别是其日志。在这个例子中,请求的存储空间是1Gi。通过实验,我发现单个Cassandra节点的理想存储空间是1-2TB。原因是Cassandra在后台进行大量的数据重排、压缩和数据再平衡。如果一个节点离开集群或一个新节点加入集群,你必须等到数据被正确再平衡,然后才能重新分布来自离开节点的数据或者填充新节点。请注意,Cassandra需要大量的磁盘空间来进行所有这些操作。建议保留50%的空闲磁盘空间。当考虑到你还需要复制(通常是3倍)时,所需的存储空间可能是你的数据大小的6倍。如果你愿意冒险,也许根据你的用例,你可以用30%的空闲空间,甚至只使用2倍的复制。但是,即使是在单个节点上,也不要低于10%的空闲磁盘空间。我以艰难的方式得知,Cassandra会简单地卡住,无法在没有极端措施的情况下进行压缩和再平衡这样的节点。

访问模式当然是ReadWriteOnce:

volumeClaimTemplates:-metadata:name:cassandra-dataannotations:volume.beta.kubernetes.io/storage-class:fastspec:accessModes:["ReadWriteOnce"]resources:requests:storage:1Gi在部署有状态集时,Kubernetes根据索引号按顺序创建pod。当扩展或缩减规模时,也是按顺序进行的。对于Cassandra来说,这并不重要,因为它可以处理节点以任何顺序加入或离开集群。当销毁一个Cassandrapod时,持久卷仍然存在。如果以后创建了具有相同索引的pod,原始的持久卷将被挂载到其中。这种稳定的连接使得Cassandra能够正确管理状态。

StatefulSet非常好,但是如前所述,Cassandra已经是一个复杂的分布式数据库。它有很多机制可以自动分发、平衡和复制集群中的数据。这些机制并不是为了与网络持久存储一起工作而进行优化的。Cassandra被设计为与直接存储在节点上的数据一起工作。当一个节点死机时,Cassandra可以通过在其他节点上存储冗余数据来进行恢复。让我们来看看在Kubernetes集群上部署Cassandra的另一种方式,这种方式更符合Cassandra的语义。这种方法的另一个好处是,如果您已经有一个现有的Kubernetes集群,您不必将其升级到最新版本,只是为了使用一个有状态的集。

我们仍将使用无头服务,但是我们将使用常规的复制控制器,而不是有状态集。有一些重要的区别:

复制控制器而不是有状态集

节点上安排运行的pod的存储

使用了自定义的Kubernetes种子提供程序类

元数据非常简单,只有一个名称(标签不是必需的):

apiVersion:v1kind:ReplicationControllermetadata:name:cassandra#Thelabelswillbeappliedautomatically#fromthelabelsinthepodtemplate,ifnotset#labels:#app:Cassandraspec指定了replicas的数量:

spec:replicas:3#Theselectorwillbeappliedautomatically#fromthelabelsinthepodtemplate,ifnotset.#selector:#app:Cassandrapod模板的元数据是指定app:Cassandra标签的地方。复制控制器将跟踪并确保具有该标签的pod恰好有三个:

template:metadata:labels:app:Cassandrapod模板的spec描述了容器的列表。在这种情况下,只有一个容器。它使用相同的名为cassandra的CassandraDocker镜像,并运行run.sh脚本:

spec:containers:-command:-/run.shimage:gcr.io/google-samples/cassandra:v11name:cassandra在这个例子中,资源部分只需要0.5个CPU单位:

resources:limits:cpu:0.5环境部分有点不同。CASSANDRA_SEED_PROVDIER指定了我们之前检查过的自定义Kubernetes种子提供程序类。这里的另一个新添加是POD_NAMESPACE,它再次使用DownwardAPI从元数据中获取值:

env:-name:MAX_HEAP_SIZEvalue:512M-name:HEAP_NEWSIZEvalue:100M-name:CASSANDRA_SEED_PROVIDERvalue:"io.k8s.cassandra.KubernetesSeedProvider"-name:POD_NAMESPACEvalueFrom:fieldRef:fieldPath:metadata.namespace-name:POD_IPvalueFrom:fieldRef:fieldPath:status.podIPports部分是相同的,暴露节点内通信端口(7000和7001),7199JMX端口用于外部工具(如CassandraOpsCenter)与Cassandra集群通信,当然还有9042CQL端口,通过它客户端与集群通信:

ports:-containerPort:7000name:intra-node-containerPort:7001name:tls-intra-node-containerPort:7199name:jmx-containerPort:9042name:cql一次又一次,卷被挂载到/cassandra_data中。这很重要,因为同样配置正确的Cassandra镜像只期望其data目录位于特定路径。Cassandra不关心后备存储(尽管作为集群管理员,你应该关心)。Cassandra只会使用文件系统调用进行读写。

volumeMounts:-mountPath:/cassandra_dataname:data卷部分是与有状态集解决方案最大的不同之处。有状态集使用持久存储索赔将特定的pod与特定的持久卷连接起来,以便具有稳定身份。复制控制器解决方案只是在托管节点上使用emptyDir。

volumes:-name:dataemptyDir:{}这有许多影响。你必须为每个节点提供足够的存储空间。如果Cassandrapod死掉,它的存储空间也会消失。即使pod在同一台物理(或虚拟)机器上重新启动,磁盘上的数据也会丢失,因为emptyDir一旦其pod被删除就会被删除。请注意,容器重新启动是可以的,因为emptyDir可以在容器崩溃时幸存下来。那么,当pod死掉时会发生什么呢?复制控制器将启动一个带有空数据的新pod。Cassandra将检测到集群中添加了一个新节点,为其分配一些数据,并通过从其他节点移动数据来自动开始重新平衡。这就是Cassandra的亮点所在。它不断地压缩、重新平衡和均匀地分布数据到整个集群中。它会自动弄清楚该为你做什么。

复制控制器方法的主要问题是多个pod可以被调度到同一Kubernetes节点上。如果你的复制因子是三,负责某个键空间范围的所有三个pod都被调度到同一个Kubernetes节点上会怎么样?首先,所有对该键范围的读取或写入请求都将发送到同一个节点,增加了更多的压力。但更糟糕的是,我们刚刚失去了冗余性。我们有一个单点故障(SPOF)。如果该节点死掉,复制控制器将愉快地在其他Kubernetes节点上启动三个新的pod,但它们都不会有数据,而且集群中的其他Cassandra节点(其他pod)也没有数据可供复制。

这可以通过使用Kubernetes调度概念中的反亲和性来解决。在将pod分配给节点时,可以对pod进行注释,以便调度程序不会将其调度到已经具有特定标签集的节点上。将此添加到pod的spec中,以确保最多只有一个Cassandrapod被分配给一个节点:

spec:affinity:podAntiAffinity:requiredDuringSchedulingIgnoredDuringExecution:-labelSelector:matchExpressions:-key:appoperator:Invalues:-cassandratopologyKey:kubernetes.io/hostname使用DaemonSet来分发Cassandra解决将Cassandrapod分配给不同节点的问题的更好方法是使用DaemonSet。DaemonSet具有类似于复制控制器的pod模板。但是DaemonSet有一个节点选择器,用于确定在哪些节点上调度其pod。它没有特定数量的副本,它只是在与其选择器匹配的每个节点上调度一个pod。最简单的情况是在Kubernetes集群中的每个节点上调度一个pod。但是节点选择器也可以使用标签的匹配表达式来部署到特定的节点子集。让我们为在Kubernetes集群上部署我们的Cassandra集群创建一个DaemonSet:

apiVersion:apps/v1kind:DaemonSetmetadata:name:cassandra-daemonsetDaemonSet的spec包含一个常规的pod模板。nodeSelector部分是魔术发生的地方,它确保每个带有app:Cassandra标签的节点上始终会被调度一个且仅有一个pod:

spec:template:metadata:labels:app:cassandraspec:#Filteronlynodeswiththelabel"app:cassandra":nodeSelector:app:cassandracontainers:其余部分与复制控制器相同。请注意,预计nodeSelector将被弃用,而亲和性将被取代。这将在何时发生,目前尚不清楚。

在本章中,我们涵盖了有关有状态应用程序以及如何将其与Kubernetes集成的主题。我们发现有状态应用程序很复杂,并考虑了几种发现机制,例如DNS和环境变量。我们还讨论了几种状态管理解决方案,例如内存冗余存储和持久存储。本章的大部分内容围绕在Kubernetes集群内部部署Cassandra集群,使用了几种选项,例如有状态集、复制控制器和DaemonSet。每种方法都有其优缺点。在这一点上,您应该对有状态应用程序有深入的了解,以及如何在基于Kubernetes的系统中应用它们。您已经掌握了多种用例的多种方法,也许甚至学到了一些关于Cassandra的知识。

在下一章中,我们将继续我们的旅程,探讨可扩展性的重要主题,特别是自动扩展性,以及在集群动态增长时如何部署和进行实时升级和更新。这些问题非常复杂,特别是当集群上运行有状态应用程序时。

在本章中,我们将探讨Kubernetes提供的自动Pod可伸缩性,以及它如何影响滚动更新,以及它如何与配额交互。我们将涉及重要的供应主题,以及如何选择和管理集群的大小。最后,我们将介绍Kubernetes团队如何测试5000节点集群的极限。以下是我们将涵盖的主要内容:

水平Pod自动缩放器

使用自动缩放执行滚动更新

使用配额和限制处理稀缺资源

推动Kubernetes性能的边界

在本章结束时,您将能够规划一个大规模的集群,经济地进行供应,并就性能、成本和可用性之间的各种权衡做出明智的决策。您还将了解如何设置水平Pod自动缩放,并聪明地使用资源配额,让Kubernetes自动处理体积的间歇性波动。

Kubernetes可以监视您的Pod,并在CPU利用率或其他指标超过阈值时对其进行扩展。自动缩放资源指定了细节(CPU百分比,检查频率),相应的自动缩放控制器会调整副本的数量,如果需要的话。

以下图表说明了不同参与者及其关系:

正如您所看到的,水平Pod自动缩放器不会直接创建或销毁Pod。相反,它依赖于复制控制器或部署资源。这非常聪明,因为您不需要处理自动缩放与复制控制器或部署尝试扩展Pod数量而不知道自动缩放器的努力之间的冲突。

自动缩放器会自动执行我们以前必须自己执行的操作。如果没有自动缩放器,如果我们有一个副本控制器,副本设置为3,但我们确定基于平均CPU利用率实际上需要4,那么我们将把副本控制器从3更新到4,并继续手动监视所有Pod中的CPU利用率。自动缩放器会为我们完成这项工作。

apiVersion:v1kind:ReplicationControllermetadata:name:nginxspec:replicas:3template:metadata:labels:run:nginxspec:containers:-name:nginximage:nginxports:-containerPort:80autoscaling资源引用了scaleTargetRef中的NGINX复制控制器:

apiVersion:autoscaling/v1kind:HorizontalPodAutoscalermetadata:name:nginxnamespace:defaultspec:maxReplicas:4minReplicas:2targetCPUUtilizationPercentage:90scaleTargetRef:apiVersion:v1kind:ReplicationControllername:nginxminReplicas和maxReplicas指定了扩展的范围。这是为了避免因某些问题而发生的失控情况。想象一下,由于某个错误,每个pod立即使用100%的CPU,而不考虑实际负载。如果没有maxReplicas限制,Kubernetes将不断创建更多的pod,直到耗尽所有集群资源。如果我们在具有自动缩放VM的云环境中运行,那么我们将产生巨大的成本。这个问题的另一面是,如果没有minReplicas并且活动出现了停滞,那么所有的pod都可能被终止,当新的请求进来时,所有的pod都将被重新创建和调度。如果存在开关型活动模式,那么这个循环可能会重复多次。保持最小数量的副本运行可以平滑这种现象。在前面的例子中,minReplicas设置为2,maxReplicas设置为4。Kubernetes将确保始终有2到4个NGINX实例在运行。

目标CPU利用率百分比是一个冗长的词。让我们把它缩写为TCUP。您可以指定一个像80%这样的单个数字。如果平均负载在TCUP周围徘徊,这可能会导致不断的抖动。Kubernetes将频繁地在增加更多副本和删除副本之间交替。这通常不是期望的行为。为了解决这个问题,您可以为扩展或缩减指定延迟。kube-controller-manager有两个标志来支持这一点:

CPU利用率是一个重要的指标,用于判断是否应该扩展受到过多请求的Pod,或者它们是否大部分处于空闲状态并且可以缩小规模。但是CPU并不是唯一的,有时甚至不是最好的指标。内存可能是限制因素,还有更专业的指标,例如Pod内部磁盘队列的深度、请求的平均延迟或服务超时的平均次数。

水平Pod自定义指标在1.2版本中作为alpha扩展添加。在1.6版本中,它们升级为beta状态。现在可以根据多个自定义指标自动调整Pod的规模。自动缩放器将评估所有指标,并根据所需的最大副本数量进行自动缩放,因此会尊重所有指标的要求。

下一步是使用以下标志启动kube-controller-manager:

--horizontal-pod-autoscaler-use-rest-clients=true--kubeconfigOR--master如果同时指定了--master标志和--kubeconfig标志,则--master标志将覆盖--kubeconfig标志。这些标志指定了API聚合层的位置,允许控制器管理器与API服务器通信。

在Kubernetes1.7中,Kubernetes提供的标准聚合层与kube-apiserver一起运行,因此可以使用以下命令找到目标IP地址:

>kubectlgetpods--selectork8s-app=kube-apiserver--namespacekube-system-ojsonpath='{.items[0].status.podIP}'使用kubectl进行自动缩放kubectl可以使用标准的create命令并接受一个配置文件来创建自动缩放资源。但是kubectl还有一个特殊的命令autoscale,可以让您轻松地在一个命令中设置自动缩放器,而无需特殊的配置文件:

>kubectlautoscalercbash-loop-rc--min=2--max=6--cpu-percent=50replicationcontroller"bash-loop-rc"autoscaled好吧,复制控制器仍然保持其四个副本,这在范围内:

>kubectlgetrcNAMEDESIREDCURRENTREADYAGEbash-loop-rc44429m然而,实际CPU利用率为零,或接近零。副本计数应该已经缩减到两个副本,但由于水平pod自动缩放器没有从Heapster接收到CPU指标,它不知道需要缩减复制控制器中的副本数。

滚动更新是管理大型集群的基石。Kubernetes支持在复制控制器级别和使用部署进行滚动更新。使用复制控制器进行滚动更新与水平pod自动缩放器不兼容。原因是在滚动部署期间,会创建一个新的复制控制器,而水平pod自动缩放器仍然绑定在旧的复制控制器上。不幸的是,直观的kubectlrolling-update命令会触发复制控制器的滚动更新。

由于滚动更新是如此重要的功能,我建议您始终将水平Pod自动缩放器绑定到部署对象,而不是复制控制器或副本集。当水平Pod自动缩放器绑定到部署时,它可以设置部署规范中的副本,并让部署负责必要的底层滚动更新和复制。

这是我们用于部署hue-reminders服务的部署配置文件:

apiVersion:extensions/v1beta1kind:Deploymentmetadata:name:hue-remindersspec:replicas:2template:metadata:name:hue-reminderslabels:app:hue-remindersspec:containers:-name:hue-remindersimage:g1g1/hue-reminders:v2.2ports:-containerPort:80为了支持自动缩放并确保我们始终有10到15个实例在运行,我们可以创建一个autoscaler配置文件:

apiVersion:autoscaling/v1kind:HorizontalPodAutoscalermetadata:name:hue-remindersnamespace:defaultspec:maxReplicas:15minReplicas:10targetCPUUtilizationPercentage:90scaleTargetRef:apiVersion:v1kind:Deploymentname:hue-remindersscaleTargetRef字段的kind现在是Deployment,而不是ReplicationController。这很重要,因为我们可能有一个同名的复制控制器。为了消除歧义并确保水平Pod自动缩放器绑定到正确的对象,kind和name必须匹配。

或者,我们可以使用kubectlautoscale命令:

>kubectlautoscaledeploymenthue-reminders--min=10--max=15--cpu-percent=90处理稀缺资源的限制和配额随着水平Pod自动缩放器动态创建pod,我们需要考虑如何管理我们的资源。调度很容易失控,资源的低效使用是一个真正的问题。有几个因素可以以微妙的方式相互作用:

整个集群的容量

每个节点的资源粒度

按命名空间划分工作负载

StatefulSets

亲和性、反亲和性、污点和容忍

首先,让我们了解核心问题。Kubernetes调度器在调度pod时必须考虑所有这些因素。如果存在冲突或许多重叠的要求,那么Kubernetes可能会在安排新的pod时遇到问题。例如,一个非常极端但简单的情况是,一个守护进程集在每个节点上运行一个需要50%可用内存的pod。现在,Kubernetes无法安排任何需要超过50%内存的pod,因为守护进程集pod具有优先级。即使您提供新节点,守护进程集也会立即占用一半的内存。

Statefulsets类似于守护程序集,因为它们需要新节点来扩展。向Statefulset添加新成员的触发器是数据的增长,但影响是从Kubernetes可用于调度其他成员的池中获取资源。在多租户情况下,嘈杂的邻居问题可能会在供应或资源分配上出现。您可能会在命名空间中精确地计划不同pod和它们的资源需求之间的比例,但您与来自其他命名空间的邻居共享实际节点,甚至可能无法看到。

大多数这些问题可以通过谨慎使用命名空间资源配额和对跨多个资源类型(如CPU、内存和存储)的集群容量进行仔细管理来缓解。

大多数Kubernetes发行版都支持开箱即用的资源配额。API服务器的--admission-control标志必须将ResourceQuota作为其参数之一。您还必须创建一个ResourceQuota对象来强制执行它。请注意,每个命名空间最多只能有一个ResourceQuota对象,以防止潜在的冲突。这是由Kubernetes强制执行的。

我们可以管理和控制不同类型的配额。这些类别包括计算、存储和对象。

limits.cpu:在非终端状态的所有pod中,CPU限制的总和不能超过此值

limits.memory:在非终端状态的所有pod中,内存限制的总和不能超过此值

requests.cpu:在非终端状态的所有pod中,CPU请求的总和不能超过此值

requests.memory:在非终端状态的所有pod中,内存请求的总和不能超过此值

存储资源配额类型有点复杂。您可以限制每个命名空间的两个实体:存储量和持久卷索赔的数量。但是,除了全局设置总存储配额或持久卷索赔总数之外,您还可以按storage类别设置。storage类别资源配额的表示法有点冗长,但它可以完成工作:

requests.storage:在所有持久卷索赔中,存储请求的总和不能超过此值

persistentvolumeclaims:可以存在于命名空间中的持久卷索赔的总数

Kubernetes1.8还增加了对临时存储配额的alpha支持:

requests.ephemeral-storage:在命名空间中的所有Pod中,本地临时存储请求的总和不能超过此值

limits.ephemeral-storage:在命名空间中的所有Pod中,本地临时存储限制的总和不能超过此值

可以限制的对象的超额有点零散。例如,可以限制复制控制器的数量,但不能限制副本集的数量,副本集几乎是复制控制器的改进版本,如果有太多副本集存在,它们可能会造成完全相同的破坏。

最明显的遗漏是命名空间。对命名空间的数量没有限制。由于所有限制都是针对命名空间的,因此通过创建太多的命名空间,可以轻松地压倒Kubernetes,因为每个命名空间只有少量的API对象。

以下是所有支持的对象:

配置映射:可以存在于命名空间中的配置映射的总数。

持久卷索赔:可以存在于命名空间中的持久卷索赔的总数。

Pods:可以存在于命名空间中的非终端状态的Pod的总数。如果status.phase在(Failed,Succeeded)中为true,则Pod处于终端状态。

复制控制器:可以存在于命名空间中的复制控制器的总数。

资源配额:可以存在于命名空间中的资源配额的总数。

服务:可以存在于命名空间中的服务的总数。

服务负载均衡器:可以存在于命名空间中的负载均衡器服务的总数。

服务节点端口:可以存在于命名空间中的节点端口服务的总数。

秘密:可以存在于命名空间中的秘密的总数。

一些资源,如Pod,可能处于不同的状态,为这些不同的状态设置不同的配额是有用的。例如,如果有许多正在终止的Pod(这在滚动更新期间经常发生),即使总数超过配额,也可以创建更多的Pod。这可以通过仅将pod对象计数配额应用于非终止的Pod来实现。以下是现有的范围:

终止:匹配spec.activeDeadlineSeconds>=0的Pod。

非终止:匹配spec.activeDeadlineSeconds为空的Pod。

最佳努力:匹配具有最佳努力的服务质量的Pod

非最佳努力:匹配没有最佳努力服务质量的Pod

虽然BestEffort范围仅适用于Pod,但Terminating,NotTerminating和NotBestEffort范围也适用于CPU和内存。这很有趣,因为资源配额限制可以阻止Pod终止。以下是支持的对象:

CPU

限制CPU

限制内存

内存

pods

requests.cpu

requests.memory

在资源配额的背景下,请求和限制的含义是它要求容器明确指定目标属性。这样,Kubernetes可以管理总配额,因为它确切地知道为每个容器分配了什么范围的资源。

首先让我们创建一个namespace:

>kubectlcreatenamespacensnamespace"ns"created使用特定于命名空间的上下文在与默认值不同的命名空间中工作时,我更喜欢使用context,这样我就不必为每个命令不断输入--namespace=ns:

>kubectldescribereplicasetnginx-8586cf59Name:nginx-8586cf59Namespace:nsSelector:pod-template-hash=41427915,run=nginxLabels:pod-template-hash=41427915run=nginxAnnotations:deployment.kubernetes.io/desired-replicas=1deployment.kubernetes.io/max-replicas=2deployment.kubernetes.io/revision=1ControlledBy:Deployment/nginxReplicas:0current/1desiredPodsStatus:0Running/0Waiting/0Succeeded/0FailedConditions:TypeStatusReason----------------ReplicaFailureTrueFailedCreateEvents:TypeReasonAgeFromMessage-------------------------WarningFailedCreate17m(x8over22m)replicaset-controller(combinedfromsimilarevents):Errorcreating:pods"nginx-8586cf59-sdwxj"isforbidden:failedquota:compute-quota:mustspecifylimits.cpu,limits.memory,requests.cpu,requests.memory输出非常宽,所以它跨越了几行,但是消息非常清晰。由于命名空间中有计算配额,因此每个容器必须指定其CPU、内存请求和限制。配额控制器必须考虑每个容器的计算资源使用情况,以确保总命名空间配额得到尊重。

好的。我们理解了问题,但如何解决呢?一种方法是为我们想要使用的每种pod类型创建一个专用的deployment对象,并仔细设置CPU和内存请求和限制。但如果我们不确定呢?如果有很多pod类型,我们不想管理一堆deployment配置文件呢?

另一个解决方案是在运行deployment时在命令行上指定限制:

>kubectlrunnginx\--image=nginx\--replicas=1\--requests=cpu=100m,memory=4Mi\--limits=cpu=200m,memory=8Mi\--namespace=ns这样做是有效的,但是通过大量参数动态创建部署是管理集群的一种非常脆弱的方式:

最简单的解决方案是选择一个已知数量的CPU、内存和本地存储的单一节点类型。但这通常不是最有效和成本效益的解决方案。这使得容量规划变得简单,因为唯一的问题是需要多少个节点。每当添加一个节点,就会向集群添加已知数量的CPU和内存,但大多数Kubernetes集群和集群内的组件处理不同的工作负载。我们可能有一个流处理管道,许多Pod在一个地方接收一些数据并对其进行处理。这种工作负载需要大量CPU,可能需要大量内存,也可能不需要。其他组件,如分布式内存缓存,需要大量内存,但几乎不需要CPU。其他组件,如Cassandra集群,需要每个节点连接多个SSD磁盘。

对于每种类型的节点,您应考虑适当的标记和确保Kubernetes调度设计为在该节点类型上运行的Pod。

存储是扩展集群的重要因素。有三种可扩展的存储解决方案:

自定义解决方案

使用您的云平台存储解决方案

使用集群外解决方案

当您使用自定义解决方案时,在Kubernetes集群中安装某种存储解决方案。优点是灵活性和完全控制,但您必须自行管理和扩展。

当您使用云平台存储解决方案时,您可以获得很多开箱即用的功能,但您失去了控制,通常需要支付更多费用,并且根据服务的不同,您可能会被锁定在该提供商那里。

当你使用集群外的解决方案时,数据传输的性能和成本可能会更大。通常情况下,如果你需要与现有系统集成,你会选择这个选项。

如果金钱不是问题,你可以过度配置你的集群。每个节点都将拥有最佳的硬件配置,你将拥有比处理工作负载所需更多的节点,以及大量可用的存储空间。猜猜?金钱总是一个问题!

好吧。所以,你仔细测量和优化,你得到了每个资源的99.99999%利用率。恭喜,你刚创造了一个系统,它无法处理额外的负载或单个节点的故障,而不会丢弃请求或延迟响应。

有时,如果你有严格的可用性和可靠性要求,你可以通过设计在系统中构建冗余来过度配置。例如,你希望能够在没有停机和没有明显影响的情况下热插拔失败的组件。也许你甚至不能失去一笔交易。在这种情况下,你将为所有关键组件提供实时备份,这种额外的容量可以用来缓解临时波动,而无需任何特殊操作。

大多数云提供商都可以让你自动扩展实例,这是对Kubernetes水平Pod自动缩放的完美补充。如果你使用云存储,它也会在你无需做任何事情的情况下神奇地增长。然而,有一些需要注意的地方。

所有大型云提供商都已经实现了实例自动缩放。虽然有一些差异,但基于CPU利用率的扩展和缩减始终可用,有时也可以使用自定义指标。有时也提供负载均衡。你可以看到,这里与Kubernetes有一些重叠。如果你的云提供商没有适当的自动缩放和适当的控制,相对容易自己实现,这样你就可以监控集群资源使用情况并调用云API来添加或删除实例。你可以从Kubernetes中提取指标。

这是一个图表,显示了基于CPU负载监视器添加了两个新实例的情况。

云平台按区域和可用性区域组织。某些服务和机器配置仅在某些区域可用。云配额也是在区域级别管理的。区域内数据传输的性能和成本要比跨区域低得多(通常是免费)。在规划您的集群时,您应该仔细考虑您的地理分布策略。如果您需要在多个区域运行您的集群,您可能需要做出一些关于冗余、可用性、性能和成本的艰难决定。

Hyper.sh是一个容器感知的托管服务。您只需启动容器。该服务负责分配硬件。容器在几秒钟内启动。您永远不需要等待几分钟来获取新的虚拟机。Hypernetes是在Hyper.sh上的Kubernetes,它完全消除了扩展节点的需要,因为在您看来根本没有节点。只有容器(或Pod)。

在下图中,您可以看到右侧的Hyper容器直接在多租户裸金属容器云上运行:

AWS最近发布了Fargate,类似地将底层实例抽象化,并允许您在云中安排容器。与EKS结合使用,可能成为部署Kubernetes的最流行方式。

在本节中,我们将看到Kubernetes团队如何将Kubernetes推向极限。这些数字相当说明问题,但一些工具和技术,如Kubemark,是巧妙的,您甚至可以使用它们来测试您的集群。在野外,有一些拥有3,000个节点的Kubernetes集群。在CERN,OpenStack团队实现了每秒2百万次请求:

Mirantis在其扩展实验室进行了性能和扩展测试,部署了5,000个Kubernetes节点(在虚拟机中)在500台物理服务器上。

OpenAI将其机器学习Kubernetes集群扩展到2,500个节点,并学到了一些宝贵的经验教训,比如注意日志代理的查询负载,并将事件存储在单独的etcd集群中:

在本节结束时,您将欣赏到改进大规模Kubernetes所需的努力和创造力,您将了解单个Kubernetes集群的极限以及预期的性能,您将深入了解一些工具和技术,可以帮助您评估自己的Kubernetes集群的性能。

Kubernetes团队在Kubernetes1.6中大力专注于性能和可扩展性。当Kubernetes1.2发布时,它支持Kubernetes服务水平目标内的最多1,000个节点的集群。Kubernetes1.3将该数字增加到2,000个节点,而Kubernetes1.6将其提高到惊人的5,000个节点每个集群。我们稍后会详细介绍这些数字,但首先让我们来看看Kubernetes是如何实现这些令人印象深刻的改进的。

Kubernetes将系统状态保存在etcd中,这是非常可靠的,尽管不是超级快速的(尽管etcd3专门提供了巨大的改进,以便实现更大的Kubernetes集群)。各种Kubernetes组件在该状态的快照上操作,并不依赖于实时更新。这一事实允许在一定程度上以一些延迟换取吞吐量。所有快照都曾由etcd监视更新。现在,API服务器具有用于更新状态快照的内存读取缓存。内存读取缓存由etcd监视更新。这些方案显著减少了etcd的负载,并增加了API服务器的整体吞吐量。

增加集群中节点的数量对于水平扩展至关重要,但Pod密度也至关重要。Pod密度是Kubelet在一个节点上能够有效管理的Pod数量。如果Pod密度低,那么你就不能在一个节点上运行太多的Pod。这意味着你可能无法从更强大的节点(每个节点的CPU和内存更多)中受益,因为Kubelet将无法管理更多的Pod。另一种选择是强迫开发人员妥协他们的设计,并创建粗粒度的Pod,每个Pod执行更多的工作。理想情况下,Kubernetes在Pod粒度方面不应该强迫你的决定。Kubernetes团队非常了解这一点,并投入了大量工作来改善Pod密度。

在Kubernetes1.1中,官方(经过测试和宣传)的数量是每个节点30个Pod。我实际上在Kubernetes1.1上每个节点运行了40个Pod,但我付出了过多的kubelet开销,这从工作Pod中窃取了CPU。在Kubernetes1.2中,这个数字跳升到每个节点100个Pod。

Kubelet以自己的go例程不断轮询容器运行时,以获取每个pod的状态。这给容器运行时带来了很大的压力,因此在性能高峰期会出现可靠性问题,特别是CPU利用率方面。解决方案是Pod生命周期事件生成器(PLEG)。PLEG的工作方式是列出所有pod和容器的状态,并将其与先前的状态进行比较。这只需要一次,就可以对所有的pod和容器进行比较。然后,通过将状态与先前的状态进行比较,PLEG知道哪些pod需要再次同步,并只调用这些pod。这一变化导致Kubelet和容器运行时的CPU使用率显著降低了四倍。它还减少了轮询周期,提高了响应性。

以下图表显示了Kubernetes1.1和Kubernetes1.2上120个pod的CPU利用率。您可以清楚地看到4倍的因素:

API服务器具有RESTAPI。RESTAPI通常使用JSON作为其序列化格式,KubernetesAPI服务器也不例外。然而,JSON序列化意味着将JSON编组和解组为本机数据结构。这是一个昂贵的操作。在大规模的Kubernetes集群中,许多组件需要频繁查询或更新API服务器。所有这些JSON解析和组合的成本很快就会累积起来。在Kubernetes1.3中,Kubernetes团队添加了一个高效的协议缓冲区序列化格式。JSON格式仍然存在,但Kubernetes组件之间的所有内部通信都使用协议缓冲区序列化格式。

etcd3的watch实现利用了GRPC双向流,并维护单个TCP连接以发送多个事件,这至少减少了一个数量级的内存占用。

使用etcd3,Kubernetes开始将所有状态存储为protobug,这消除了许多浪费的JSON序列化开销。

Kubernetes团队进行了许多其他优化:

优化调度程序(导致调度吞吐量提高了5-10倍)

优化API服务器中的单个操作(转换、深拷贝、补丁)

减少API服务器中的内存分配(这对API调用的延迟有显著影响)

为了提高性能和可伸缩性,您需要清楚地知道您想要改进什么,以及如何去衡量这些改进。您还必须确保在追求性能和可伸缩性的过程中不违反基本属性和保证。我喜欢性能改进的地方在于它们通常可以免费为您带来可伸缩性的改进。例如,如果一个pod需要节点的50%CPU来完成其工作,而您改进了性能,使得该pod只需要33%的CPU就能完成相同的工作,那么您可以在该节点上突然运行三个pod而不是两个,从而将集群的可伸缩性整体提高了50%(或者将成本降低了33%)。

使用足够的硬件来管理大量对象也很重要。Kubernetes团队在这次测试中使用了一个32核心、120GB的虚拟机作为主节点。

下图描述了Kubernetes1.3各种重要API调用延迟的50th、90th和99th百分位数。你可以看到,90th百分位数非常低,低于20毫秒。甚至对于DELETEpods操作,99th百分位数也低于125毫秒,对于其他所有操作也低于100毫秒:

这很好,但是看看Kubernetes1.6在一个5000个节点的集群上的API调用延迟:

Kubernetes1.6将其提升到了下一个级别,并在更大的集群上表现得更好:

拥有数千个节点的集群是昂贵的。即使像Kubernetes这样得到Google和其他行业巨头支持的项目,仍然需要找到合理的测试方法,而不会让自己破产。

Kubernetes团队每次发布都会在真实集群上运行全面的测试,以收集真实世界的性能和可伸缩性数据。然而,还需要一种轻量级和更便宜的方法来尝试潜在的改进,并检测回归。这就是Kubemark。

Kubemark是一个运行模拟节点(称为空心节点)的Kubernetes集群,用于针对大规模(空心)集群运行轻量级基准测试。一些在真实节点上可用的Kubernetes组件,如kubelet,被替换为空心kubelet。空心kubelet模拟了真实kubelet的许多功能。空心kubelet实际上不会启动任何容器,也不会挂载任何卷。但从Kubernetes集群的角度来看-存储在etcd中的状态-所有这些对象都存在,您可以查询API服务器。空心kubelet实际上是带有注入的模拟Docker客户端的真实kubelet,该客户端不执行任何操作。

另一个重要的空心组件是hollow-proxy,它模拟了Kubeproxy组件。它再次使用真实的Kubeproxy代码,具有一个不执行任何操作并避免触及iptables的模拟proxier接口。

Kubemark集群利用了Kubernetes的强大功能。要设置Kubemark集群,请执行以下步骤:

创建一个常规的Kubernetes集群,我们可以在其中运行Nhollow-nodes。

创建一个专用的VM来启动Kubemark集群的所有主要组件。

在基本Kubernetes集群上安排N个空节点pods。这些空节点被配置为与运行在专用VM上的KubemarkAPI服务器进行通信。

通过在基本集群上安排并配置它们与KubemarkAPI服务器进行通信来创建附加的pods。

Kubemark集群的性能与真实集群的性能非常相似。对于pod启动的端到端延迟,差异可以忽略不计。对于API的响应性,差异较大,尽管通常不到两倍。然而,趋势完全相同:真实集群中的改进/退化在Kubemark中表现为类似百分比的下降/增加。

到目前为止,您已经对Kubernetes集群面对动态和不断增长的工作负载时涉及的所有因素有了很好的理解。您有多种工具可供选择,用于规划和设计自己的扩展策略。

在下一章中,我们将深入探讨高级Kubernetes网络。Kubernetes具有基于通用网络接口(CNI)的网络模型,并支持多个提供程序。

在本章中,我们将研究网络这一重要主题。作为一个编排平台,Kubernetes管理在不同机器(物理或虚拟)上运行的容器/Pod,并需要一个明确的网络模型。我们将讨论以下主题:

Kubernetes网络模型

Kubernetes支持的标准接口,如EXEC、Kubenet,特别是CNI

满足Kubernetes网络要求的各种网络解决方案

网络策略和负载均衡选项

编写自定义CNI插件

在本章结束时,您将了解Kubernetes对网络的处理方式,并熟悉标准接口、网络实现和负载均衡等方面的解决方案空间。甚至可以自己编写自己的CNI插件。

Kubernetes网络模型基于一个扁平的地址空间。集群中的所有Pod都可以直接相互通信。每个Pod都有自己的IP地址。无需配置任何NAT。此外,同一Pod中的容器共享其Pod的IP地址,并且可以通过localhost相互通信。这个模型非常有见地,一旦设置好,就会极大地简化开发人员和管理员的生活。它特别容易将传统网络应用迁移到Kubernetes。一个Pod代表一个传统节点,每个容器代表一个传统进程。

运行中的Pod始终被调度到一个(物理或虚拟)节点上。这意味着所有的容器都在同一个节点上运行,并且可以以各种方式相互通信,比如本地文件系统、任何IPC机制,或者使用localhost和众所周知的端口。不同的Pod之间不会发生端口冲突,因为每个Pod都有自己的IP地址,当Pod中的容器使用localhost时,它只适用于Pod的IP地址。因此,如果Pod1中的容器1连接到Pod1上的端口1234,而Pod1上的容器2监听该端口,它不会与同一节点上运行的Pod2中的另一个容器监听的端口1234发生冲突。唯一需要注意的是,如果要将端口暴露给主机,那么应该注意Pod到节点的亲和性。这可以通过多种机制来处理,比如DaemonSet和Pod反亲和性。

在Kubernetes中,Pod被分配了一个网络可见的IP地址(不是私有的节点)。Pod可以直接通信,无需网络地址转换、隧道、代理或任何其他混淆层的帮助。可以使用众所周知的端口号来进行无需配置的通信方案。Pod的内部IP地址与其他Pod看到的外部IP地址相同(在集群网络内;不暴露给外部世界)。这意味着标准的命名和发现机制,如DNS,可以直接使用。

Pod可以使用它们的IP地址和众所周知的端口直接相互通信,但这需要Pod知道彼此的IP地址。在Kubernetes集群中,Pod可能会不断被销毁和创建。服务提供了一个非常有用的间接层,因为即使实际响应请求的Pod集合不断变化,服务也是稳定的。此外,您会获得自动的高可用负载均衡,因为每个节点上的Kube-proxy负责将流量重定向到正确的Pod:

最终,一些容器需要从外部世界访问。PodIP地址在外部不可见。服务是正确的载体,但外部访问通常需要两次重定向。例如,云服务提供商负载均衡器是Kubernetes感知的,因此它不能直接将流量定向到运行可以处理请求的Pod的节点。相反,公共负载均衡器只是将流量定向到集群中的任何节点,该节点上的Kube-proxy将再次重定向到适当的Pod,如果当前节点不运行必要的Pod。

下图显示了右侧的外部负载均衡器所做的一切只是将流量发送到达到代理的所有节点,代理负责进一步路由,如果需要的话。

Docker容器如何跨节点通信?容器必须将端口发布到主机。这显然需要端口协调,因为如果两个容器尝试发布相同的主机端口,它们将互相冲突。然后容器(或其他进程)连接到被通道化到容器中的主机端口。一个很大的缺点是,容器无法自我注册到外部服务,因为它们不知道它们所在主机的IP地址。您可以通过在运行容器时将主机的IP地址作为环境变量传递来解决这个问题,但这需要外部协调并且使过程复杂化。

以下图表显示了Docker的网络设置。每个容器都有自己的IP地址;Docker在每个节点上创建了docker0桥接:

为了使pod和容器能够相互通信,它们需要找到彼此。容器定位其他容器或宣布自己有几种方法。还有一些架构模式允许容器间间接交互。每种方法都有其优缺点。

我们已经多次提到自注册。让我们确切地理解它的含义。当一个容器运行时,它知道其pod的IP地址。每个希望对集群中的其他容器可访问的容器都可以连接到某个注册服务并注册其IP地址和端口。其他容器可以查询注册服务以获取所有已注册容器的IP地址和端口,并连接到它们。当一个容器被销毁(正常情况下),它将取消注册。如果一个容器非正常死亡,那么需要建立一些机制来检测。例如,注册服务可以定期ping所有已注册的容器,或者要求容器定期向注册服务发送保持活动的消息。

自注册的好处在于一旦通用注册服务就位(无需为不同目的定制),就无需担心跟踪容器。另一个巨大的好处是,容器可以采用复杂的策略,并决定在本地条件下暂时取消注册,比如如果一个容器很忙,不想在这一刻接收更多请求。这种智能和分散的动态负载平衡在全球范围内很难实现。缺点是注册服务是另一个非标准组件,容器需要了解它以便定位其他容器。

Kubernetes服务可以被视为注册服务。属于服务的pod会根据其标签自动注册。其他pod可以查找端点以找到所有服务pod,或者利用服务本身直接发送消息到服务,消息将被路由到其中一个后端pod。尽管大多数情况下,pod将消息直接发送到服务本身,由服务转发到其中一个后端pod。

如果容器可以相互通信,而不知道它们的IP地址和端口,甚至不知道服务IP地址或网络名称呢?如果大部分通信可以是异步和解耦的呢?在许多情况下,系统可以由松散耦合的组件组成,这些组件不仅不知道其他组件的身份,甚至不知道其他组件的存在。队列有助于这种松散耦合的系统。组件(容器)监听来自队列的消息,响应消息,执行它们的工作,并在队列中发布有关进度、完成状态和错误的消息。队列有许多好处:

无需协调即可添加处理能力;只需添加更多监听队列的容器

通过队列深度轻松跟踪整体负载

通过对消息和/或主题进行版本控制,轻松同时运行多个组件的不同版本

通过使多个消费者以不同模式处理请求,轻松实现负载均衡以及冗余

队列的缺点包括:

需要确保队列提供适当的耐用性和高可用性,以免成为关键的单点故障。

容器需要使用异步队列API(可以抽象化)

实现请求-响应需要在响应队列上进行有些繁琐的监听

总的来说,队列是大规模系统的一个很好的机制,可以在大型Kubernetes集群中使用,以简化协调工作。

另一种松散耦合的方法是使用数据存储(例如Redis)存储消息,然后其他容器可以读取它们。虽然可能,但这不是数据存储的设计目标,结果通常是繁琐、脆弱,并且性能不佳。数据存储针对数据存储进行了优化,而不是用于通信。也就是说,数据存储可以与队列一起使用,其中一个组件将一些数据存储在数据存储中,然后发送一条消息到队列,表示数据已准备好进行处理。多个组件监听该消息,并且都开始并行处理数据。

Kubernetes提供了一个入口资源和控制器,旨在将Kubernetes服务暴露给外部世界。当然,您也可以自己做,但定义入口所涉及的许多任务在特定类型的入口(如Web应用程序、CDN或DDoS保护器)的大多数应用程序中是常见的。您还可以编写自己的入口对象。

“入口”对象通常用于智能负载平衡和TLS终止。您可以从内置入口中受益,而不是配置和部署自己的NGINX服务器。如果您需要复习,请转到第六章,使用关键的Kubernetes资源,在那里我们讨论了带有示例的入口资源。

Kubernetes具有网络插件系统,因为网络如此多样化,不同的人希望以不同的方式实现它。Kubernetes足够灵活,可以支持任何场景。主要的网络插件是CNI,我们将深入讨论。但Kubernetes还配备了一个更简单的网络插件,称为Kubenet。在我们详细讨论之前,让我们就Linux网络的基础知识达成一致(只是冰山一角)。

网络实体通过其IP地址进行标识。服务器可以在多个端口上监听传入连接。客户端可以连接(TCP)或向其网络内的服务器发送数据(UDP)。

命名空间将一堆网络设备分组在一起,以便它们可以在同一命名空间中到达其他服务器,但即使它们在物理上位于同一网络上,也不能到达其他服务器。通过桥接、交换机、网关和路由可以连接网络或网络段。

在设计和维护网络时,网络段的细分非常有用。将网络划分为具有共同前缀的较小子网是一种常见做法。这些子网可以由表示子网大小(可以包含多少主机)的位掩码来定义。例如,255.255.255.0的子网掩码意味着前三个八位字节用于路由,只有256(实际上是254)个单独的主机可用。无类别域间路由(CIDR)表示法经常用于此目的,因为它更简洁,编码更多信息,并且还允许将来自多个传统类别(A、B、C、D、E)的主机组合在一起。例如,172.27.15.0/24表示前24位(三个八位字节)用于路由。

虚拟以太网(veth)设备代表物理网络设备。当您创建一个与物理设备连接的veth时,您可以将该veth(以及物理设备)分配到一个命名空间中,其他命名空间的设备无法直接访问它,即使它们在物理上位于同一个本地网络上。

桥接器将多个网络段连接到一个聚合网络,以便所有节点可以彼此通信。桥接是在OSI网络模型的L1(物理)和L2(数据链路)层进行的。

路由连接不同的网络,通常基于路由表,指示网络设备如何将数据包转发到其目的地。路由是通过各种网络设备进行的,如路由器、桥接器、网关、交换机和防火墙,包括常规的Linux框。

最大传输单元(MTU)确定数据包的大小限制。例如,在以太网网络上,MTU为1500字节。MTU越大,有效载荷和标头之间的比率就越好,这是一件好事。缺点是最小延迟减少,因为您必须等待整个数据包到达,而且如果出现故障,您必须重新传输整个数据包。

以下是一个描述通过veth0在网络层面上描述pod、主机和全局互联网之间关系的图表:

回到Kubernetes。Kubenet是一个网络插件;它非常基础,只是创建一个名为cbr0的Linux桥接和为每个pod创建一个veth。云服务提供商通常使用它来设置节点之间的通信路由规则,或者在单节点环境中使用。veth对将每个pod连接到其主机节点,使用来自主机IP地址范围的IP地址。

Kubenet插件有以下要求:

必须为节点分配一个子网,以为其pod分配IP地址

版本0.2.0或更高版本需要标准的CNI桥接、lo和host-local插件

Kubelet必须使用--network-plugin=kubenet参数运行

Kubelet必须使用--non-masquerade-cidr=参数运行

MTU对于网络性能至关重要。Kubernetes网络插件(如Kubenet)会尽最大努力推断最佳MTU,但有时它们需要帮助。如果现有的网络接口(例如Docker的docker0桥接)设置了较小的MTU,则Kubenet将重用它。另一个例子是IPSEC,由于IPSEC封装开销增加,需要降低MTU,但Kubenet网络插件没有考虑到这一点。解决方案是避免依赖MTU的自动计算,只需通过--network-plugin-mtu命令行开关告诉Kubelet应该为网络插件使用什么MTU,这个开关提供给所有网络插件。然而,目前只有Kubenet网络插件考虑了这个命令行开关。

CNI既是一个规范,也是一组用于编写网络插件以配置Linux容器中的网络接口的库(不仅仅是Docker)。该规范实际上是从rkt网络提案演变而来的。CNI背后有很多动力,正在快速成为行业标准。一些使用CNI的组织有:

Kubernetes

Kurma

云原生

Nuage

红帽

Mesos

CNI团队维护一些核心插件,但也有很多第三方插件对CNI的成功做出了贡献:

ProjectCalico:三层虚拟网络

Weave:多主机Docker网络

Contiv网络:基于策略的网络

Cilium:用于容器的BPF和XDP

Multus:一个多插件

CNI-Genie:通用CNI网络插件

Flannel:为Kubernetes设计的容器网络布局

Infoblox:企业级容器IP地址管理

CNI为网络应用容器定义了插件规范,但插件必须插入提供一些服务的容器运行时中。在CNI的上下文中,应用容器是一个可寻址的网络实体(具有自己的IP地址)。对于Docker,每个容器都有自己的IP地址。对于Kubernetes,每个pod都有自己的IP地址,而pod是CNI容器,而不是pod内的容器。

同样,rkt的应用容器类似于Kubernetes中的pod,因为它们可能包含多个Linux容器。如果有疑问,只需记住CNI容器必须有自己的IP地址。运行时的工作是配置网络,然后执行一个或多个CNI插件,以JSON格式将网络配置传递给它们。

以下图表显示了一个容器运行时使用CNI插件接口与多个CNI插件进行通信:

CNI插件的工作是将网络接口添加到容器网络命名空间,并通过veth对将容器桥接到主机。然后,它应通过IPAM(IP地址管理)插件分配IP地址并设置路由。

容器运行时(Docker,rkt或任何其他符合CRI标准的运行时)将CNI插件作为可执行文件调用。插件需要支持以下操作:

将容器添加到网络

从网络中删除容器

报告版本

插件使用简单的命令行界面,标准输入/输出和环境变量。以JSON格式的网络配置通过标准输入传递给插件。其他参数被定义为环境变量:

CNI_COMMAND:指示所需操作的命令;ADD,DEL或VERSION。

CNI_CONTAINERID:容器ID。

CNI_NETNS:网络命名空间文件的路径。

*CNI_IFNAME:要设置的接口名称;插件必须遵守此接口名称或返回一个error。

*CNI_ARGS:用户在调用时传入的额外参数。字母数字键值对由分号分隔,例如,FOO=BAR;ABC=123。

CNI_PATH:要搜索CNI插件可执行文件的路径列表。路径由操作系统特定的列表分隔符分隔,例如,在Linux上是:,在Windows上是;。

如果命令成功,插件将返回零退出代码,并且生成的接口(在ADD命令的情况下)将作为JSON流式传输到标准输出。这种低技术接口很聪明,因为它不需要任何特定的编程语言、组件技术或二进制API。CNI插件编写者也可以使用他们喜欢的编程语言。

使用ADD命令调用CNI插件的结果如下:

{"cniVersion":"0.3.0","interfaces":[(thiskeyomittedbyIPAMplugins){"name":"","mac":"",(requiredifL2addressesaremeaningful)"sandbox":""(requiredforcontainer/hypervisorinterfaces,empty/omittedforhostinterfaces)}],"ip":[{"version":"<4-or-6>","address":"","gateway":"",(optional)"interface":},...],"routes":[(optional){"dst":"","gw":""(optional)},...]"dns":{"nameservers":(optional)"domain":(optional)"search":(optional)"options":(optional)}}输入网络配置包含大量信息:cniVersion、名称、类型、args(可选)、ipMasq(可选)、ipam和dns。ipam和dns参数是具有自己指定键的字典。以下是网络配置的示例:

{"cniVersion":"0.3.0","name":"dbnet","type":"bridge",//type(plugin)specific"bridge":"cni0","ipam":{"type":"host-local",//ipamspecific"subnet":"10.1.0.0/16","gateway":"10.1.0.1"},"dns":{"nameservers":["10.1.0.1"]}}请注意,可以添加额外的特定于插件的元素。在这种情况下,bridge:cni0元素是特定的bridge插件理解的自定义元素。

CNI规范还支持网络配置列表,其中可以按顺序调用多个CNI插件。稍后,我们将深入研究一个完全成熟的CNI插件实现。

网络是一个广阔的话题。有许多设置网络和连接设备、pod和容器的方法。Kubernetes对此不能有意见。对于pod的高级网络模型是Kubernetes规定的。在这个空间内,有许多有效的解决方案是可能的,具有不同环境的各种功能和策略。在本节中,我们将研究一些可用的解决方案,并了解它们如何映射到Kubernetes网络模型。

Contiv是一个通用的容器网络插件,可以直接与Docker、Mesos、DockerSwarm以及当然Kubernetes一起使用,通过一个CNI插件。Contiv专注于与Kubernetes自身网络策略对象有些重叠的网络策略。以下是Contivnet插件的一些功能:

支持libnetwork的CNM和CNI规范

功能丰富的策略模型,提供安全、可预测的应用部署

用于容器工作负载的最佳吞吐量

多租户、隔离和重叠子网

集成IPAM和服务发现

各种物理拓扑:

Layer2(VLAN)

Layer3(BGP)

覆盖(VXLAN)

思科SDN解决方案(ACI)

IPv6支持

可扩展的策略和路由分发

与应用蓝图的集成,包括以下内容:

Docker-compose

Kubernetes部署管理器

服务负载平衡内置东西向微服务负载平衡

用于存储、控制(例如,etcd/consul)、网络和管理流量的流量隔离

Contiv具有许多功能和能力。由于其广泛的适用范围以及它适用于多个平台,我不确定它是否是Kubernetes的最佳选择。

OpenvSwitch可以连接裸机服务器、虚拟机和pod/容器,使用相同的逻辑网络。它实际上支持覆盖和底层模式。

以下是一些其关键特性:

标准的802.1QVLAN模型,带有干线和接入端口

上游交换机上带或不带LACP的NIC绑定

NetFlow、sFlow(R)和镜像,以增加可见性

QoS(服务质量)配置,以及流量控制

Geneve、GRE、VXLAN、STT和LISP隧道

802.1ag连接故障管理

OpenFlow1.0加上许多扩展

具有C和Python绑定的事务配置数据库

使用Linux内核模块进行高性能转发

Nuage网络的虚拟化云服务(VCS)产品提供了一个高度可扩展的基于策略的软件定义网络(SDN)平台。这是一个建立在开源OpenvSwitch数据平面之上的企业级产品,配备了基于开放标准构建的功能丰富的SDN控制器。

此外,所有VCS组件都可以安装在容器中。没有特殊的硬件要求。

Canal是两个开源项目的混合体:Calico和Flannel。Canal这个名字是这两个项目名称的混成词。由CoreOS开发的Flannel专注于容器网络,Calico专注于网络策略。最初,它们是独立开发的,但用户希望将它们一起使用。目前,开源的Canal项目是一个部署模式,可以将这两个项目作为独立的CNI插件进行安装。由Calico创始人组建的Tigera现在正在引导这两个项目,并计划更紧密地集成,但自从他们发布了用于Kubernetes的安全应用连接解决方案后,重点似乎转向了为Flannel和Calico做出贡献,以简化配置和集成,而不是提供统一的解决方案。以下图表展示了Canal的当前状态以及它与Kubernetes和Mesos等容器编排器的关系:

请注意,与Kubernetes集成时,Canal不再直接使用etcd,而是依赖于KubernetesAPI服务器。

Flannel是一个虚拟网络,为每个主机提供一个子网,用于容器运行时。它在每个主机上运行一个flaneld代理,该代理从存储在etcd中的保留地址空间中为节点分配子网。容器之间以及最终主机之间的数据包转发由多个后端之一完成。最常见的后端使用默认情况下通过端口8285进行的TUN设备上的UDP进行隧道传输(确保防火墙中已打开)。

以下图表详细描述了Flannel的各个组件、它创建的虚拟网络设备以及它们如何通过docker0桥与主机和pod进行交互。它还显示了数据包的UDP封装以及它们在主机之间的传输:

其他后端包括以下内容:

vxlan:使用内核VXLAN封装数据包。

host-gw:通过远程机器IP创建到子网的IP路由。请注意,这需要在运行Flannel的主机之间直接的二层连接。

aws-vpc:在AmazonVPC路由表中创建IP路由。

gce:在Google计算引擎网络中创建IP路由。

alloc:仅执行子网分配(不转发数据包)。

ali-vpc:在阿里云VPC路由表中创建IP路由。

Calico是一个多功能的容器虚拟网络和网络安全解决方案。Calico可以与所有主要的容器编排框架集成

和运行时:

Kubernetes(CNI插件)

Mesos(CNI插件)

Docker(libnework插件)

OpenStack(Neutron插件)

Calico还可以在本地部署或在公共云上部署,具有完整的功能集。Calico的网络策略执行可以针对每个工作负载进行专门化,并确保流量被精确控制,数据包始终从其源头到经过审查的目的地。Calico可以自动将编排平台的网络策略概念映射到自己的网络策略。Kubernetes网络策略的参考实现是Calico。

Romana是一个现代的云原生容器网络解决方案。它在第3层操作,利用标准IP地址管理技术。整个网络可以成为隔离单元,因为Romana使用Linux主机创建网关和网络的路由。在第3层操作意味着不需要封装。网络策略作为分布式防火墙在所有端点和服务上执行。跨云平台和本地部署的混合部署更容易,因为无需配置虚拟覆盖网络。

新的Romana虚拟IP允许本地用户通过外部IP和服务规范在第2层LAN上公开服务。

Kubernetes网络策略是关于管理流向选定的pod和命名空间的网络流量。在部署和编排了数百个微服务的世界中,通常情况下是Kubernetes,管理pod之间的网络和连接至关重要。重要的是要理解,它并不是主要的安全机制。如果攻击者可以访问内部网络,他们可能能够创建符合现有网络策略并与其他pod自由通信的自己的pod。在前一节中,我们看了不同的Kubernetes网络解决方案,并侧重于容器网络接口。在本节中,重点是网络策略,尽管网络解决方案与如何在其之上实现网络策略之间存在着紧密的联系。

网络策略是选择的pod之间以及其他网络端点之间如何通信的规范。NetworkPolicy资源使用标签选择pod,并定义白名单规则,允许流量到达选定的pod,除了给定命名空间的隔离策略允许的流量之外。

网络策略和CNI插件之间存在复杂的关系。一些CNI插件同时实现了网络连接和网络策略,而其他一些只实现了其中一个方面,但它们可以与另一个实现了另一个方面的CNI插件合作(例如,Calico和Flannel)。

网络策略是通过NetworkPolicy资源进行配置的。以下是一个示例网络策略:

apiVersion:networking.k8s.io/v1kind:NetworkPolicymetadata:name:test-network-policynamespace:defaultspec:podSelector:matchLabels:role:dbingress:-from:-namespaceSelector:matchLabels:project:awesome-project-podSelector:matchLabels:role:frontendports:-protocol:tcpport:6379实施网络策略虽然网络策略API本身是通用的,并且是KubernetesAPI的一部分,但实现与网络解决方案紧密耦合。这意味着在每个节点上都有一个特殊的代理或守门人,执行以下操作:

拦截进入节点的所有流量

验证其是否符合网络策略

转发或拒绝每个请求

Kubernetes提供了通过API定义和存储网络策略的功能。执行网络策略由网络解决方案或与特定网络解决方案紧密集成的专用网络策略解决方案来完成。Calico和Canal是这种方法的很好的例子。Calico有自己的网络解决方案和网络策略解决方案,它们可以一起工作,但也可以作为Canal的一部分在Flannel之上提供网络策略执行。在这两种情况下,这两个部分之间有紧密的集成。以下图表显示了Kubernetes策略控制器如何管理网络策略以及节点上的代理如何执行它:

负载均衡是动态系统中的关键能力,比如Kubernetes集群。节点、虚拟机和Pod会不断变化,但客户端无法跟踪哪个个体可以处理他们的请求。即使他们可以,也需要管理集群的动态映射,频繁刷新它,并处理断开连接、无响应或者慢速节点的复杂操作。负载均衡是一个经过验证和深入理解的机制,它增加了一层间接性,将内部动荡隐藏在集群外部的客户端或消费者之外。外部和内部负载均衡器都有选项。您也可以混合使用两者。混合方法有其特定的优缺点,比如性能与灵活性。

外部负载均衡器是在Kubernetes集群之外运行的负载均衡器。必须有一个外部负载均衡器提供商,Kubernetes可以与其交互,以配置外部负载均衡器的健康检查、防火墙规则,并获取负载均衡器的外部IP地址。

以下图表显示了负载均衡器(在云中)、KubernetesAPI服务器和集群节点之间的连接。外部负载均衡器有关于哪些Pod运行在哪些节点上的最新信息,并且可以将外部服务流量引导到正确的Pod。

通过服务配置文件或直接通过Kubectl配置外部负载均衡器。我们使用LoadBalancer服务类型,而不是使用ClusterIP服务类型,后者直接将Kubernetes节点公开为负载均衡器。这取决于外部负载均衡器提供程序在集群中是否已正确安装和配置。Google的GKE是最经过充分测试的提供程序,但其他云平台在其云负载均衡器之上提供了集成解决方案。

以下是一个实现此目标的示例服务配置文件:

{"kind":"Service","apiVersion":"v1","metadata":{"name":"example-service"},"spec":{"ports":[{"port":8765,"targetPort":9376}],"selector":{"app":"example"},"type":"LoadBalancer"}}通过Kubectl您还可以使用直接的kubectl命令来实现相同的结果:

负载均衡器将有两个感兴趣的IP地址。内部IP地址可在集群内部用于访问服务。集群外部的客户端将使用外部IP地址。为外部IP地址创建DNS条目是一个良好的做法。要获取这两个地址,请使用kubectldescribe命令。IP将表示内部IP地址。LoadBalanceringress将表示外部IP地址:

>kubectldescribeservicesexample-serviceName:example-serviceSelector:app=exampleType:LoadBalancerIP:10.67.252.103LoadBalancerIngress:123.45.678.9Port:80/TCPNodePort:32445/TCPEndpoints:10.64.0.4:80,10.64.1.5:80,10.64.2.4:80SessionAffinity:NoneNoevents.保留客户端IP地址有时,服务可能对客户端的源IP地址感兴趣。直到Kubernetes1.5版本,这些信息是不可用的。在Kubernetes1.5中,通过注释仅在GKE上可用的beta功能可以获取源IP地址。在Kubernetes1.7中,API添加了保留原始客户端IP的功能。

您需要配置服务规范的以下两个字段:

service.spec.externalTrafficPolicy:此字段确定服务是否应将外部流量路由到节点本地端点或集群范围的端点,这是默认设置。集群选项不会显示客户端源IP,并可能将跳转到不同节点,但会很好地分散负载。本地选项保留客户端源IP,并且只要服务类型为LoadBalancer或NodePort,就不会添加额外的跳转。其缺点是可能无法很好地平衡负载。

service.spec.healthCheckNodePort:此字段是可选的。如果使用,则服务健康检查将使用此端口号。默认值为分配节点端口。对于LoadBalancer类型的服务,如果其externalTrafficPolicy设置为Local,则会产生影响。

这是一个例子:

{"kind":"Service","apiVersion":"v1","metadata":{"name":"example-service"},"spec":{"ports":[{"port":8765,"targetPort":9376}],"selector":{"app":"example"},"type":"LoadBalancer""externalTrafficPolicy:"Local"}}即使在外部负载均衡中理解潜力外部负载均衡器在节点级别运行;虽然它们将流量引导到特定的pod,但负载分配是在节点级别完成的。这意味着如果您的服务有四个pod,其中三个在节点A上,最后一个在节点B上,那么外部负载均衡器很可能会在节点A和节点B之间均匀分配负载。这将使节点A上的三个pod处理一半的负载(每个1/6),而节点B上的单个pod将独自处理另一半的负载。未来可能会添加权重来解决这个问题。

服务负载平衡旨在在Kubernetes集群内部传输内部流量,而不是用于外部负载平衡。这是通过使用clusterIP服务类型来实现的。可以通过使用NodePort服务类型直接公开服务负载均衡器,并将其用作外部负载均衡器,但它并不是为此用例而设计的。例如,诸如SSL终止和HTTP缓存之类的理想功能将不会很容易地可用。

以下图显示了服务负载均衡器(黄色云)如何将流量路由到其管理的后端pod之一(通过标签,当然):

Kubernetes中的入口在其核心是一组规则,允许入站连接到达集群服务。此外,一些入口控制器支持以下功能:

连接算法

请求限制

URL重写和重定向

TCP/UDP负载平衡

SSL终止

我们讨论了使用云提供商外部负载均衡器,使用LoadBalancer服务类型以及在集群内部使用ClusterIP的内部服务负载均衡器。如果我们想要一个自定义的外部负载均衡器,我们可以创建一个自定义的外部负载均衡器提供程序,并使用LoadBalancer或使用第三种服务类型NodePort。高可用性(HA)代理是一个成熟且经过实战考验的负载均衡解决方案。它被认为是在本地集群中实现外部负载均衡的最佳选择。这可以通过几种方式实现:

利用NodePort并仔细管理端口分配

实现自定义负载均衡器提供程序接口

在集群内部运行HAProxy作为集群边缘前端服务器的唯一目标(无论是否经过负载平衡)

每个服务将从预定义范围中分配一个专用端口。通常这是一个较高的范围,例如30,000及以上,以避免与使用低已知端口的其他应用程序发生冲突。在这种情况下,HAProxy将在集群外部运行,并且将为每个服务配置正确的端口。然后它可以将任何流量转发到任何节点和Kubernetes通过内部服务,并且负载均衡器将其路由到适当的pod(双重负载均衡)。当然,这是次优的,因为它引入了另一个跳跃。规避它的方法是查询EndpointsAPI,并动态管理每个服务的后端pod列表,并直接将流量转发到pod。

这种方法稍微复杂一些,但好处是它与Kubernetes更好地集成,可以更容易地在本地和云端之间进行过渡。

在这种方法中,我们在集群内部使用HAProxy负载均衡器。可能有多个运行HAProxy的节点,它们将共享相同的配置来映射传入请求并在后端服务器(以下图表中的Apache服务器)之间进行负载均衡。

Trfic是一个现代的HTTP反向代理和负载均衡器。它旨在支持微服务。它可以与许多后端一起工作,包括Kubernetes,以自动和动态地管理其配置。与传统的负载均衡器相比,这是一个改变游戏规则的产品。它具有令人印象深刻的功能列表:

单个Go可执行文件

微型官方Docker镜像

RestAPI

热重新加载配置;无需重新启动进程

断路器,重试

轮询,重新平衡负载均衡器

指标(Rest,Prometheus,Datadog,Statsd,InfluxDB)

干净的AngularJSWebUI

Websocket,HTTP/2,GRPC准备就绪

访问日志(JSON,CLF)

支持Let'sEncrypt(自动HTTPS与更新)

具有集群模式的高可用性

CNI插件是可执行的

它负责将新容器连接到网络,为CNI容器分配唯一的IP地址,并负责路由

容器是一个网络命名空间(在Kubernetes中,一个pod是一个CNI容器)

网络定义以JSON文件的形式进行管理,但通过标准输入流传输到插件(插件不会读取任何文件)

辅助信息可以通过环境变量提供

环回插件只是添加环回接口。它非常简单,不需要任何网络配置信息。大多数CNI插件都是用Golang实现的,环回CNI插件也不例外。完整的源代码可在以下链接找到:

让我们先看一下导入。来自GitHub上的容器网络项目的多个软件包提供了实现CNI插件和netlink软件包所需的许多构建块,用于添加和删除接口,以及设置IP地址和路由。我们很快将看到skel软件包:

packagemainimport("github.com/containernetworking/cni/pkg/ns""github.com/containernetworking/cni/pkg/skel""github.com/containernetworking/cni/pkg/types/current""github.com/containernetworking/cni/pkg/version""github.com/vishvananda/netlink")然后,插件实现了两个命令,cmdAdd和cmdDel,当container被添加到或从网络中移除时调用。以下是cmdAdd命令:

funccmdAdd(args*skel.CmdArgs)error{args.IfName="lo"err:=ns.WithNetNSPath(args.Netns,func(_ns.NetNS)error{link,err:=netlink.LinkByName(args.IfName)iferr!=nil{returnerr//nottested}err=netlink.LinkSetUp(link)iferr!=nil{returnerr//nottested}returnnil})iferr!=nil{returnerr//nottested}result:=current.Result{}returnresult.Print()}该功能的核心是将接口名称设置为lo(用于环回),并将链接添加到容器的网络命名空间中。del命令则相反:

funccmdDel(args*skel.CmdArgs)error{args.IfName="lo"err:=ns.WithNetNSPath(args.Netns,func(ns.NetNS)error{link,err:=netlink.LinkByName(args.IfName)iferr!=nil{returnerr//nottested}err=netlink.LinkSetDown(link)iferr!=nil{returnerr//nottested}returnnil})iferr!=nil{returnerr//nottested}result:=current.Result{}returnresult.Print()}main函数只是简单地调用skel包,传递命令函数。skel包将负责运行CNI插件可执行文件,并在适当的时候调用addCmd和delCmd函数:

funcmain(){skel.PluginMain(cmdAdd,cmdDel,version.All)}构建CNI插件骨架让我们探索skel包,并了解其在内部的工作原理。从PluginMain()入口点开始,它负责调用PluginMainWithError(),捕获错误,将其打印到标准输出并退出:

funcPluginMain(cmdAdd,cmdDelfunc(_*CmdArgs)error,versionInfoversion.PluginInfo){ife:=PluginMainWithError(cmdAdd,cmdDel,versionInfo);e!=nil{iferr:=e.Print();err!=nil{log.Print("ErrorwritingerrorJSONtostdout:",err)}os.Exit(1)}}PluginErrorWithMain()实例化一个分发器,设置它与所有I/O流和环境,并调用其PluginMain()方法:

funcPluginMainWithError(cmdAdd,cmdDelfunc(_*CmdArgs)error,versionInfoversion.PluginInfo)*types.Error{return(dispatcher{Getenv:os.Getenv,Stdin:os.Stdin,Stdout:os.Stdout,Stderr:os.Stderr,}).pluginMain(cmdAdd,cmdDel,versionInfo)}最后,这是骨架的主要逻辑。它从环境中获取cmd参数(其中包括来自标准输入的配置),检测调用了哪个cmd,并调用适当的plugin函数(cmdAdd或cmdDel)。它还可以返回版本信息:

func(t*dispatcher)pluginMain(cmdAdd,cmdDelfunc(_*CmdArgs)error,versionInfoversion.PluginInfo)*types.Error{cmd,cmdArgs,err:=t.getCmdArgsFromEnv()iferr!=nil{returncreateTypedError(err.Error())}switchcmd{case"ADD":err=t.checkVersionAndCall(cmdArgs,versionInfo,cmdAdd)case"DEL":err=t.checkVersionAndCall(cmdArgs,versionInfo,cmdDel)case"VERSION":err=versionInfo.Encode(t.Stdout)default:returncreateTypedError("unknownCNI_COMMAND:%v",cmd)}iferr!=nil{ife,ok:=err.(*types.Error);ok{//don'twrapErrorinErrorreturne}returncreateTypedError(err.Error())}returnnil}审查桥接插件桥接插件更为重要。让我们看一下其实现的一些关键部分。完整的源代码可在以下链接找到:

它定义了一个网络配置struct,具有以下字段:

typeNetConfstruct{types.NetConfBrNamestring`json:"bridge"`IsGWbool`json:"isGateway"`IsDefaultGWbool`json:"isDefaultGateway"`ForceAddressbool`json:"forceAddress"`IPMasqbool`json:"ipMasq"`MTUint`json:"mtu"`HairpinModebool`json:"hairpinMode"`PromiscModebool`json:"promiscMode"`}由于空间限制,我们将不会涵盖每个参数的作用以及它如何与其他参数交互。目标是理解流程,并且如果您想要实现自己的CNI插件,这将是一个起点。配置通过loadNetConf()函数从JSON加载。它在cmdAdd()和cmdDel()函数的开头被调用:

n,cniVersion,err:=loadNetConf(args.StdinData)这是cmdAdd()函数的核心。它使用来自网络配置的信息,设置了一个veth,与IPAM插件交互以添加适当的IP地址,并返回结果:

hostInterface,containerInterface,err:=setupVeth(netns,br,args.IfName,n.MTU,n.HairpinMode)iferr!=nil{returnerr}//runtheIPAMpluginandgetbacktheconfigtoapplyr,err:=ipam.ExecAdd(n.IPAM.Type,args.StdinData)iferr!=nil{returnerr}//ConverttheIPAMresultwasintothecurrentResulttyperesult,err:=current.NewResultFromResult(r)iferr!=nil{returnerr}iflen(result.IPs)==0{returnerrors.New("IPAMreturnedmissingIPconfig")}result.Interfaces=[]*current.Interface{brInterface,hostInterface,containerInterface}这只是完整实现的一部分。还有路由设置和硬件IP分配。我鼓励您追求完整的源代码,这是相当广泛的,以获得全貌。

在本章中,我们涵盖了很多内容。网络是一个如此广泛的主题,有如此多的硬件、软件、操作环境和用户技能的组合,要想提出一个全面的网络解决方案,既稳健、安全、性能良好又易于维护,是一项非常复杂的工作。对于Kubernetes集群,云提供商大多解决了这些问题。但如果您在本地运行集群或需要定制解决方案,您有很多选择。Kubernetes是一个非常灵活的平台,设计用于扩展。特别是网络是完全可插拔的。我们讨论的主要主题是Kubernetes网络模型(平面地址空间,其中pod可以访问其他pod,并且在pod内部所有容器之间共享本地主机),查找和发现的工作原理,Kubernetes网络插件,不同抽象级别的各种网络解决方案(许多有趣的变体),有效使用网络策略来控制集群内部的流量,负载均衡解决方案的范围,最后我们看了如何通过剖析真实实现来编写CNI插件。

在这一点上,您可能会感到不知所措,特别是如果您不是专家。您应该对Kubernetes网络的内部有很好的理解,了解实现完整解决方案所需的所有相互关联的部分,并能够根据对系统有意义的权衡来制定自己的解决方案。

在第十一章中,在多个云和集群联合上运行Kubernetes,我们将更进一步,看看如何在多个集群、云提供商和联合上运行Kubernetes。这是Kubernetes故事中的一个重要部分,用于地理分布式部署和最终可扩展性。联合的Kubernetes集群可以超越本地限制,但它们也带来了一系列挑战。

在本章中,我们将进一步探讨在多个云上运行Kubernetes和集群联邦。Kubernetes集群是一个紧密结合的单元,其中所有组件都在相对接近的地方运行,并通过快速网络(物理数据中心或云提供商可用区)连接。这对许多用例来说非常好,但有一些重要的用例需要系统扩展到超出单个集群的范围。Kubernetes联邦是一种系统化的方法,可以将多个Kubernetes集群组合在一起,并将它们视为单个实体进行交互。我们将涵盖的主题包括以下内容:

深入了解集群联邦的全部内容

如何准备、配置和管理集群联邦

如何在多个集群上运行联合工作负载

集群联邦在概念上很简单。您可以聚合多个Kubernetes集群,并将它们视为单个逻辑集群。有一个联邦控制平面,向客户端呈现系统的单一统一视图。

以下图表展示了Kubernetes集群联邦的整体情况:

联邦控制平面由联邦API服务器和联邦控制器管理器共同协作。联邦API服务器将请求转发到联邦中的所有集群。此外,联邦控制器管理器通过将请求路由到各个联邦集群成员的更改来执行控制器管理器的职责。实际上,集群联邦并不是微不足道的,也不能完全抽象化。跨Pod通信和数据传输可能会突然产生大量的延迟和成本开销。让我们首先看一下集群联邦的用例,了解联合组件和资源的工作方式,然后再来研究难点:位置亲和性、跨集群调度和联邦数据访问。

有四类用例受益于集群联邦。

这种方法有时被称为云爆发。

这几乎是容量溢出的相反情况。也许您已经接受了云原生的生活方式,整个系统都在云上运行,但是一些数据或工作负载涉及敏感信息。监管合规性或您组织的安全政策可能要求数据和工作负载必须在完全由您控制的环境中运行。您的敏感数据和工作负载可能会受到外部审计。确保私有Kubernetes集群中的信息永远不会泄漏到基于云的Kubernetes集群可能至关重要。但是,希望能够查看公共集群并能够从私有集群启动非敏感工作负载可能是可取的。如果工作负载的性质可以动态地从非敏感变为敏感,那么就需要通过制定适当的策略和实施来解决。例如,您可以阻止工作负载改变其性质。或者,您可以迁移突然变得敏感的工作负载,并确保它不再在基于云的集群上运行。另一个重要的例子是国家合规性,根据法律要求,某些数据必须保留在指定的地理区域(通常是一个国家)内,并且只能从该地区访问。在这种情况下,必须在该地理区域创建一个集群。

大型组织通常更喜欢有选择,并不希望被绑定在单一供应商上。风险往往太大,因为供应商可能会关闭或无法提供相同级别的服务。拥有多个供应商通常也有利于谈判价格。Kubernetes旨在成为供应商无关的。您可以在不同的云平台、私有服务提供商和本地数据中心上运行它。

然而,这并不是微不足道的。如果您想确保能够快速切换供应商或将一些工作负载从一个供应商转移到另一个供应商,您应该已经在多个供应商上运行系统。您可以自己操作,或者有一些公司提供在多个供应商上透明运行Kubernetes的服务。由于不同的供应商运行不同的数据中心,您自动获得了一些冗余和对供应商范围内的故障的保护。

高可用性意味着即使系统的某些部分出现故障,服务仍将对用户保持可用。在联邦Kubernetes集群的背景下,故障的范围是整个集群,这通常是由于托管集群的物理数据中心出现问题,或者可能是平台提供商出现更广泛的问题。高可用性的关键是冗余。地理分布式冗余意味着在不同位置运行多个集群。这可能是同一云提供商的不同可用区,同一云提供商的不同地区,甚至完全不同的云提供商(参见“避免供应商锁定”部分)。在运行具有冗余的集群联邦时,有许多问题需要解决。我们稍后将讨论其中一些问题。假设技术和组织问题已经解决,高可用性将允许将流量从失败的集群切换到另一个集群。这对用户来说应该是透明的(切换期间的延迟,以及一些正在进行的请求或任务可能会消失或失败)。系统管理员可能需要采取额外步骤来支持切换和处理原始集群的故障。

联邦控制平面由两个组件组成,共同使得Kubernetes集群的联邦可以看作和作为一个统一的Kubernetes集群。

联邦API服务器正在管理组成联邦的Kubernetes集群。它在etcd数据库中管理联邦状态(即哪些集群是联邦的一部分),与常规Kubernetes集群一样,但它保持的状态只是哪些集群是联邦的成员。每个集群的状态存储在该集群的etcd数据库中。联邦API服务器的主要目的是与联邦控制器管理器进行交互,并将请求路由到联邦成员集群。联邦成员不需要知道它们是联邦的一部分:它们的工作方式完全相同。

以下图表展示了联邦API服务器、联邦复制控制器和联邦中的Kubernetes集群之间的关系:

以下图表展示了这个永久控制循环:

Kubernetes联邦仍在不断发展中。截至Kubernetes1.10,只有一些标准资源可以进行联邦。我们将在这里介绍它们。要创建联邦资源,您可以使用Kubectl的--context=federation-cluster命令行参数。当您使用--context=federation-cluster时,该命令将发送到联邦API服务器,该服务器负责将其发送到所有成员集群。

联邦ConfigMaps非常有用,因为它们帮助集中配置可能分布在多个集群中的应用程序。

以下是创建联邦ConfigMap的示例:

>kubectl--context=federation-clustercreate-fconfigmap.yaml正如您所看到的,创建单个Kubernetes集群中的ConfigMap时唯一的区别是上下文。创建联邦ConfigMap时,它存储在控制平面的etcd数据库中,但每个成员集群中也存储了一份副本。这样,每个集群可以独立运行,不需要访问控制平面。

您可以通过访问控制平面或访问成员集群来查看ConfigMap。要访问成员集群中的ConfigMap,请在上下文中指定联邦集群成员名称:

没错,你猜对了。你像往常一样删除,但指定上下文:

>kubectl--context=federation-clusterdeleteconfigmap只有一个小小的变化。从Kubernetes1.10开始,当你删除一个联邦ConfigMap时,每个集群中自动创建的单独的ConfigMap仍然存在。你必须在每个集群中分别删除它们。也就是说,如果你的联邦中有三个集群分别叫做cluster-1、cluster-2和cluster-3,你将不得不运行这额外的三个命令来摆脱联邦中的ConfigMap:

>kubectl--context=cluster-1deleteconfigmap>kubectl--context=cluster-2deleteconfigmap>kubectl--context=cluster-3deleteconfigmap这将在将来得到纠正。

联邦守护进程基本上与常规的Kubernetes守护进程相同。你通过控制平面创建它并与之交互(通过指定--context=federation-cluster),控制平面将其传播到所有成员集群。最终,你可以确保你的守护程序在联邦的每个集群的每个节点上运行。

联邦部署更加智能。当您创建一个具有X个副本的联邦部署,并且您有N个集群时,默认情况下副本将在集群之间均匀分布。如果您有3个集群,并且联邦部署有15个pod,那么每个集群将运行5个副本。与其他联邦资源一样,控制平面将存储具有15个副本的联邦部署,然后创建3个部署(每个集群一个),每个部署都有5个副本。您可以通过添加注释federation.kubernetes.io/deployment-preferences来控制每个集群的副本数量。截至Kubernetes1.10,联邦部署仍处于Alpha阶段。在将来,该注释将成为联邦部署配置中的一个正确字段。

联邦事件与其他联邦资源不同。它们仅存储在控制平面中,不会传播到底层Kubernetes成员集群。

您可以像往常一样使用--context=federation-cluster查询联邦事件:

>kubectl--context=federation-clustergetevents联邦水平Pod扩展最近在Kubernetes1.9中作为Alpha功能添加了联邦水平Pod扩展(HPA)。为了使用它,您必须在启动API服务器时提供以下标志:

--runtime-config=api/all=true这是一个重要的功能,因为集群联合的主要动机之一是在没有手动干预的情况下在多个集群之间流畅地转移工作负载。联邦HPA利用了集群内的HPA控制器。联邦HPA根据请求的最大和最小副本数量在成员集群之间均匀分配负载。在将来,用户将能够指定更高级的HPA策略。

例如,考虑一个具有4个集群的联邦;我们希望始终至少有6个pod和最多有16个pod在运行。以下清单将完成工作:

apiVersion:autoscaling/v1kind:HorizontalPodAutoscalermetadata:name:cool-appnamespace:defaultspec:scaleTargetRef:apiVersion:apps/v1beta1kind:Deploymentname:cool-appminReplicas:6maxReplicas:16targetCPUUtilizationPercentage:80使用以下命令启动联邦HPA:

>kubectl--context=federation-clustercreatefederated-hpa.yaml现在会发生什么?联邦控制平面将在4个集群中的每个集群中创建标准HPA,最多有4个副本和最少有2个副本。原因是这是最经济地满足联邦要求的设置。让我们了解一下为什么。如果每个集群最多有4个副本,那么我们最多会有4x4=16个副本,这符合我们的要求。至少2个副本的保证意味着我们至少会有4x2=8个副本。这满足了我们至少会有6个副本的要求。请注意,即使系统上没有负载,我们也将始终至少有8个副本,尽管我们指定6个也可以。鉴于跨集群的均匀分布的限制,没有其他办法。如果集群HPA的minReplicas=1,那么集群中的总副本数可能是4x1=4,这少于所需的联邦最小值6。未来,用户可能可以指定更复杂的分布方案。

可以使用集群选择器(在Kubernetes1.7中引入)来将联邦对象限制为成员的子集。因此,如果我们想要至少6个最多15个,可以将其均匀分布在3个集群中,而不是4个,每个集群将至少有2个最多5个。

联邦入口不仅在每个集群中创建匹配的入口对象。联邦入口的主要特点之一是,如果整个集群崩溃,它可以将流量引导到其他集群。从Kubernetes1.4开始,联邦入口在GoogleCloudPlatform上得到支持,包括GKE和GCE。未来,联邦入口将增加对混合云的支持。

联邦入口执行以下任务:

在联邦的每个集群成员中创建Kubernetes入口对象

为所有集群入口对象提供一个一站式逻辑L7负载均衡器,具有单个IP地址

监视每个集群中入口对象后面的服务后端pod的健康和容量

确保在各种故障情况下将客户端连接路由到健康的服务端点,例如pod、集群、可用区或整个区域的故障,只要联邦中有一个健康的集群

通过寻址联邦控制平面来创建联邦入口

>kubectl--context=federation-clustercreate-fingress.yaml联合控制平面将在每个集群中创建相应的入口。所有集群将共享相同的命名空间和ingress对象的名称:

>kubectl--context=cluster-1getingressmyingressNAMEHOSTSADDRESSPORTSAGEingress*157.231.15.3380,4431m使用联合入口进行请求路由联合入口控制器将请求路由到最近的集群。入口对象通过Status.Loadbalancer.Ingress字段公开一个或多个IP地址,这些IP地址在入口对象的生命周期内保持不变。当内部或外部客户端连接到特定集群入口对象的IP地址时,它将被路由到该集群中的一个pod。然而,当客户端连接到联合入口对象的IP地址时,它将自动通过最短的网络路径路由到请求源最近的集群中的一个健康pod。因此,例如,来自欧洲互联网用户的HTTP(S)请求将直接路由到具有可用容量的欧洲最近的集群。如果欧洲没有这样的集群,请求将被路由到下一个最近的集群(通常在美国)。

有两种广义的失败类别:

Pod故障

集群故障

Pod可能因多种原因而失败。在正确配置的Kubernetes集群(无论是集群联合成员还是不是),pod将由服务和ReplicaSets管理,可以自动处理pod故障。这不应影响联合入口进行的跨集群路由和负载均衡。整个集群可能由于数据中心或全球连接的问题而失败。在这种情况下,联合服务和联合ReplicaSets将确保联合中的其他集群运行足够的pod来处理工作负载,并且联合入口将负责将客户端请求从失败的集群中路由出去。为了从这种自动修复功能中受益,客户端必须始终连接到联合入口对象,而不是单个集群成员。

联合作业与集群内作业类似。联合控制平面在基础集群中创建作业,并根据任务的并行性均匀分配负载,并跟踪完成情况。例如,如果联合有4个集群,并且您创建了一个并行性为8和完成数为24的联合作业规范,那么将在每个集群中创建一个并行性为2和完成数为6的作业。

您可以使用相同的命令并添加--context=federation-cluster:

>kubectl--context=federation-clustercreate-fnamespace.yaml>kubectl--context=cluster-1getnamespacesnamespace>kubectl--context=federation-clustercreate-fnamespace.yaml联合复制ReplicaSet最好使用部署和联合部署来管理集群或联合中的副本。但是,如果出于某种原因您更喜欢直接使用ReplicaSets进行工作,那么Kubernetes支持联合ReplicaSet。没有联合复制控制器,因为ReplicaSets超越了复制控制器。

当您创建联合ReplicaSets时,控制平面的工作是确保整个集群中的副本数量与您的联合ReplicaSets配置相匹配。控制平面将在每个联合成员中创建一个常规ReplicaSet。每个集群将默认获得相等(或尽可能接近相等)数量的副本,以便总数将达到指定的副本数量。

您可以使用以下注释来控制每个集群的副本数量:federation.kubernetes.io/replica-set-preferences。

相应的数据结构如下:

typeFederatedReplicaSetPreferencesstruct{RebalanceboolClustersmap[string]ClusterReplicaSetPreferences}如果Rebalance为true,则正在运行的副本可能会根据需要在集群之间移动。集群映射确定每个集群的ReplicaSets偏好。如果将*指定为键,则所有未指定的集群将使用该偏好集。如果没有*条目,则副本将仅在映射中显示的集群上运行。属于联合但没有条目的集群将不会安排pod(对于该pod模板)。

每个集群的单独ReplicaSets偏好使用以下数据结构指定:

typeClusterReplicaSetPreferencesstruct{MinReplicasint64MaxReplicas*int64Weightint64}MinReplicas默认为0。MaxReplicas默认情况下是无限制的。权重表示向这个ReplicaSets添加额外副本的偏好,默认为0。

联合秘密很简单。当您通过控制平面像往常一样创建联合秘密时,它会传播到整个集群。就是这样。

到目前为止,联邦似乎几乎是直截了当的。将一堆集群组合在一起,通过控制平面访问它们,一切都会被复制到所有集群。但是有一些困难因素和基本概念使这种简化的观点变得复杂。Kubernetes的许多功能来自于其在幕后执行大量工作的能力。在一个完全部署在单个物理数据中心或可用性区域的单个集群中,所有组件都连接到快速网络,Kubernetes本身非常有效。在Kubernetes集群联邦中,情况就不同了。延迟、数据传输成本以及在集群之间移动Pods都有不同的权衡。根据用例,使联邦工作可能需要系统设计师和运营商额外的注意、规划和维护。此外,一些联合资源不如其本地对应物成熟,这增加了更多的不确定性。

Kubernetes集群中的工作单元是Pod。在Kubernetes中无法打破Pod。整个Pod将始终一起部署,并受到相同的生命周期处理。Pod是否应该保持集群联邦的工作单元?也许将更大的单元(如整个ReplicaSet、部署或服务)与特定集群关联起来会更有意义。如果集群失败,整个ReplicaSet、部署或服务将被调度到另一个集群。那么一组紧密耦合的ReplicaSets呢?这些问题的答案并不总是容易的,甚至可能随着系统的演变而动态改变。

严格耦合

松散耦合

优先耦合

严格解耦

均匀分布

在设计系统以及如何在联邦中分配和调度服务和Pods时,确保始终尊重位置亲和性要求非常重要。

严格耦合的要求适用于必须在同一集群中的应用程序。如果对pod进行分区,应用程序将失败(可能是由于实时要求无法在集群间进行网络传输),或者成本可能太高(pod可能正在访问大量本地数据)。将这种紧密耦合的应用程序移动到另一个集群的唯一方法是在另一个集群上启动完整的副本(包括数据),然后关闭当前集群上的应用程序。如果数据量太大,该应用程序可能实际上无法移动,并对灾难性故障敏感。这是最难处理的情况,如果可能的话,您应该设计系统以避免严格耦合的要求。

松耦合的应用程序在工作负载尴尬地并行时表现最佳,每个pod不需要了解其他pod或访问大量数据。在这些情况下,pod可以根据联邦中的容量和资源利用率安排到集群中。必要时,pod可以在不出问题的情况下从一个集群移动到另一个集群。例如,一个无状态的验证服务执行一些计算,并在请求本身中获取所有输入,不查询或写入任何联邦范围的数据。它只验证其输入并向调用者返回有效/无效的判断。

在所有pod都在同一集群中或pod和数据共同位于同一位置时,优先耦合的应用程序表现更好,但这不是硬性要求。例如,它可以与仅需要最终一致性的应用程序一起工作,其中一些联邦范围的应用程序定期在所有集群之间同步应用程序状态。在这些情况下,分配是明确地针对一个集群进行的,但在压力下留下了一个安全舱口,可以在其他集群中运行或迁移。

一些服务具有故障隔离或高可用性要求,这要求在集群之间进行分区。如果所有副本最终可能被安排到同一集群中,那么运行关键服务的三个副本就没有意义,因为该集群只成为一个临时的单点故障(SPOF)。

均匀分布是指服务、ReplicaSet或pod的实例必须在每个集群上运行。这类似于DaemonSet,但不是确保每个节点上有一个实例,而是每个集群一个实例。一个很好的例子是由一些外部持久存储支持的Redis缓存。每个集群中的pod应该有自己的集群本地Redis缓存,以避免访问可能更慢或成为瓶颈的中央存储。另一方面,每个集群不需要超过一个Redis服务(它可以分布在同一集群中的几个pod中)。

跨集群调度与位置亲和力相辅相成。当创建新的pod或现有的pod失败并且需要安排替代时,它应该去哪里?当前的集群联邦不能处理我们之前提到的所有场景和位置亲和力的选项。在这一点上,集群联邦很好地处理了松散耦合(包括加权分布)和严格耦合(通过确保副本的数量与集群的数量相匹配)的类别。其他任何情况都需要您不使用集群联邦。您将不得不添加自己的自定义联邦层,以考虑更多专门的问题,并且可以适应更复杂的调度用例。

这是一个棘手的问题。如果您有大量数据和在多个集群中运行的pod(可能在不同的大陆上),并且需要快速访问它,那么您有几个不愉快的选择:

将数据复制到每个集群(复制速度慢,传输昂贵,存储昂贵,同步和处理错误复杂)

远程访问数据(访问速度慢,每次访问昂贵,可能成为单点故障)

目前不支持联邦自动调用。可以利用两个维度的扩展,以及组合:

每个集群的扩展

将集群添加/移除联邦

混合方法

考虑一个相对简单的场景,即在三个集群上运行一个松散耦合的应用程序,每个集群有五个pod。在某个时候,15个pod无法再处理负载。我们需要增加更多的容量。我们可以增加每个集群中的pod数量,但如果我们在联邦级别这样做,那么每个集群将有六个pod在运行。我们通过三个pod增加了联邦的容量,而只需要一个pod。当然,如果您有更多的集群,问题会变得更糟。另一个选择是选择一个集群并只改变其容量。这是可能的,但现在我们明确地在整个联邦中管理容量。如果我们有许多集群运行数百个具有动态变化需求的服务,情况会很快变得复杂。

添加一个全新的集群更加复杂。我们应该在哪里添加新的集群?没有额外的可用性要求可以指导决策。这只是额外的容量问题。创建一个新的集群通常需要复杂的首次设置,并且可能需要几天来批准公共云平台上的各种配额。混合方法增加了联邦中现有集群的容量,直到达到某个阈值,然后开始添加新的集群。这种方法的好处是,当您接近每个集群的容量限制时,您开始准备新的集群,以便在必要时立即启动。除此之外,它需要大量的工作,并且您需要为灵活性和可伸缩性付出增加的复杂性。

管理Kubernetes集群联邦涉及许多超出管理单个集群的活动。有两种设置联邦的方式。然后,您需要考虑级联资源删除,跨集群负载平衡,跨集群故障转移,联邦服务发现和联邦发现。让我们详细讨论每一种。

注意:这种方法现在已经不推荐使用Kubefed。我在这里描述它是为了让使用较旧版本Kubernetes的读者受益。

建立Kubernetes集群联邦,我们需要运行控制平面的组件,如下所示:

etcdfederation-apiserverfederation-controller-manager其中一个最简单的方法是使用全能的hyperkube镜像:

联邦API服务器和联邦控制器管理器可以作为现有Kubernetes集群中的pod运行,但正如前面讨论的那样,最好从容错和高可用性的角度来看,将它们运行在自己的集群中。

首先,您必须运行Docker,并获取包含我们在本指南中将使用的脚本的Kubernetes版本。当前版本是1.5.3。您也可以下载最新可用版本:

>exportFEDERATION_OUTPUT_ROOT="${PWD}/output/federation">mkdir-p"${FEDERATION_OUTPUT_ROOT}"现在,我们可以初始化联邦:

>federation/deploy/deploy.shinit使用官方的Hyperkube镜像作为每个Kubernetes版本的一部分,官方发布的镜像都被推送到gcr.io/google_containers。要使用该存储库中的镜像,您可以将配置文件中的容器镜像字段设置为${FEDERATION_OUTPUT_ROOT}指向gcr.io/google_containers/hyperkube镜像,其中包括federation-apiserver和federation-controller-manager二进制文件。

我们准备通过运行以下命令部署联邦控制平面:

>federation/deploy/deploy.shdeploy_federation该命令将启动控制平面组件作为pod,并为联邦API服务器创建一个LoadBalancer类型的服务,并为etcd创建一个由动态持久卷支持的持久卷索赔。

要验证联邦命名空间中的所有内容是否正确创建,请输入以下内容:

>kubectlgetdeployments--namespace=federation你应该看到这个:

NAMEDESIREDCURRENTUP-TO-DATEfederation-controller-manager111federation-apiserver111您还可以使用Kubectlconfigview检查kubeconfig文件中的新条目。请注意,动态配置目前仅适用于AWS和GCE。

要向联邦注册集群,我们需要一个与集群通信的秘钥。

让我们在主机Kubernetes集群中创建秘钥。假设目标集群的kubeconfig位于|cluster-1|kubeconfig。您可以运行以下命令

创建secret:

>kubectlcreatesecretgenericcluster-1--namespace=federation--from-file=/cluster-1/kubeconfig集群的配置看起来和这个一样:

apiVersion:federation/v1beta1kind:Clustermetadata:name:cluster1spec:serverAddressByClientCIDRs:-clientCIDR:serverAddress:secretRef:name:我们需要设置。这里的是您刚刚创建的秘密的名称。serverAddressByClientCIDRs包含客户端可以根据其CIDR使用的各种服务器地址。我们可以使用CIDR0.0.0.0/0设置服务器的公共IP地址,所有客户端都将匹配。此外,如果要内部客户端使用服务器的clusterIP,可以将其设置为serverAddress。在这种情况下,客户端CIDR将是仅匹配在该集群中运行的pod的IP的CIDR。

让我们注册集群:

>kubectlcreate-f/cluster-1/cluster.yaml--context=federation-cluster让我们看看集群是否已正确注册:

>kubectlgetclusters--context=federation-clusterNAMESTATUSVERSIONAGEcluster-1Ready1m更新KubeDNS集群已注册到联邦。现在是时候更新kube-dns,以便您的集群可以路由联邦服务请求。从Kubernetes1.5或更高版本开始,通过kube-dnsConfigMap传递--federations标志来完成:

--federations=${FEDERATION_NAME}=${DNS_DOMAIN_NAME}ConfigMap的外观如下:

apiVersion:v1kind:ConfigMapmetadata:name:kube-dnsnamespace:kube-systemdata:federations:=将federation-name和federation-domain-name替换为正确的值。

如果要关闭联邦,只需运行以下命令:

federation/deploy/deploy.shdestroy_federation使用Kubefed设置集群联合Kubernetes1.5引入了一个名为Kubefed的新的Alpha命令行工具,帮助您管理联合集群。Kubefed的工作是使部署新的Kubernetes集群联合控制平面变得容易,并向现有联合控制平面添加或删除集群。自Kubernetes1.6以来一直处于beta阶段。

直到Kubernetes1.9,Kubefed是Kubernetes客户端二进制文件的一部分。您将获得Kubectl和Kubefed。以下是在Linux上下载和安装的说明:

要验证是否具有所需的kubeconfig条目,请键入以下内容:

>kubectlconfigget-contexts您应该看到类似于这样的东西:

CURRENTNAMECLUSTERAUTHINFONAMESPACEcluster-1cluster-1cluster-1在部署联邦控制平面时,将稍后提供上下文名称cluster-1。

是时候开始使用Kubefed了。kubefedinit命令需要三个参数:

联邦名称

主机集群上下文

用于您的联邦服务的域名后缀

以下示例命令部署了一个带有联邦控制平面的

名称联邦;一个主机集群上下文,cluster-1;一个corednsDNS提供程序(google-clouddns和aes-route53也是有效的);和域后缀,kubernetes-ftw.com:

>kubefedinitfederation--host-cluster-context=cluster-1--dns-providercoredns--dns-zone-name="kubernetes-ftw.com"DNS后缀应该是您管理的DNS域名。

kubefedinit在主机集群中设置联邦控制平面,并在本地kubeconfig中为联邦API服务器添加条目。由于错误,Kubernetes可能不会创建默认命名空间。在这种情况下,您将不得不自己执行。键入以下命令:

>kubectlcreatenamespacedefault--context=federation不要忘记将当前上下文设置为联邦,以便Kubectl将目标设置为联邦控制平面:

>kubectlconfiguse-contextfederation联邦服务发现联邦服务发现与联邦负载平衡紧密耦合。一个实用的设置包括一个全局L7负载均衡器,将请求分发到联邦集群中的联邦入口对象。

拥有专用的查找服务并让客户端直接连接到各个集群上的服务的替代方案会失去所有这些好处。

一旦控制平面成功部署,我们应该将一些Kubernetes集群添加到联邦中。Kubefed为此目的提供了join命令。kubefedjoin命令需要以下参数:

要添加的集群名称

例如,要将名为cluster-2的新集群添加到联邦中,请键入

以下:

kubefedjoincluster-2--host-cluster-context=cluster-1命名规则和自定义您提供给kubefedjoin的集群名称必须是有效的RFC1035标签。RFC1035只允许字母、数字和连字符,并且标签必须以字母开头。

此外,联邦控制平面需要加入集群的凭据才能对其进行操作。这些凭据是从本地的kubeconfig中获取的。Kubefedjoin命令使用指定为参数的集群名称来查找本地kubeconfig中的集群上下文。如果它找不到匹配的上下文,它将以错误退出。

这可能会导致问题,因为联邦中每个集群的上下文名称不遵循RFC1035标签命名规则。在这种情况下,您可以指定符合RFC1035标签命名规则的集群名称,并使用--cluster-context标志指定集群上下文。例如,如果您要加入的集群的上下文是cluster-3(不允许使用下划线),您可以通过运行此命令加入该集群:

kubefedjoincluster-3--host-cluster-context=cluster-1--cluster-context=cluster-3秘密名称联邦控制平面在上一节中描述的集群凭据作为主机集群中的一个秘密存储。秘密的名称也是从集群名称派生的。

但是,在Kubernetes中secret对象的名称应符合RFC1123中描述的DNS子域名规范。如果不是这种情况,您可以使用--secret-name标志将secretname传递给kubefedjoin。例如,如果集群名称是cluster-4,secretname是4secret(不允许以字母开头),您可以通过运行此命令加入该集群:

kubefedjoincluster-4--host-cluster-context=cluster-1--secret-name=4secretkubefedjoin命令会自动为您创建秘密。

要从联邦中删除一个集群,请使用集群名称和联邦主机集群上下文运行kubefedunjoin命令:

kubefedunjoincluster-2--host-cluster-context=cluster-1关闭联邦在Kubefed的beta版本中,联邦控制平面的适当清理尚未完全实现。但是,暂时删除联邦系统命名空间应该会删除除联邦控制平面的etcd动态配置的持久存储卷之外的所有资源。您可以通过运行以下命令delete联邦命名空间来删除联邦命名空间:

>kubectldeletensfederation-system资源的级联删除Kubernetes集群联邦通常在控制平面中管理联合对象,以及每个成员Kubernetes集群中的相应对象。级联删除联合对象意味着成员Kubernetes集群中的相应对象也将被删除。

这不会自动发生。默认情况下,只删除联合控制平面对象。要激活级联删除,您需要设置以下选项:

DeleteOptions.orphanDependents=false在Kuberentes1.5中,只有以下联合对象支持级联删除:

部署

守护进程集

入口管理

命名空间

副本集

秘密

对于其他对象,您必须进入每个集群并明确删除它们。幸运的是,从Kubernetes1.6开始,所有联合对象都支持级联删除。

跨集群的动态负载均衡并不是微不足道的。最简单的解决方案是说这不是Kubernetes的责任。负载均衡将在Kubernetes集群联合之外执行。但考虑到Kubernetes的动态特性,即使外部负载均衡器也必须收集关于每个集群上正在运行的服务和后端pod的大量信息。另一种解决方案是联合控制平面实现一个作为整个联合的流量导向器的L7负载均衡器。在较简单的用例中,每个服务在一个专用集群上运行,负载均衡器只是将所有流量路由到该集群。在集群故障的情况下,服务被迁移到另一个集群,负载均衡器现在将所有流量路由到新的集群。这提供了一个粗略的故障转移和集群级别的高可用性解决方案。

最佳解决方案将能够支持联合服务,并考虑其他因素,例如以下因素:

客户端的地理位置

每个集群的资源利用率

资源配额和自动扩展

以下图表显示了GCE上的L7负载均衡器如何将客户端请求分发到最近的集群:

联合故障转移很棘手。假设联合中的一个集群失败;一个选择是让其他集群接管工作。现在的问题是,如何在其他集群之间分配负载?

统一吗?

启动一个新的集群?

选择一个尽可能接近的现有集群(可能在同一地区)?

这些解决方案与联合负载平衡有微妙的相互作用,

地理分布的高可用性,跨不同集群的成本管理,

和安全。

现在,失败的集群再次上线。它应该逐渐重新接管其原始工作负载吗?如果它回来了,但容量减少或网络不稳定怎么办?有许多故障模式的组合可能使恢复变得复杂。

集群中的低容量事件(或集群故障)

调度策略的更改(我们不再使用云提供商X)

资源定价的更改(云提供商Y降低了价格,所以让我们迁移到那里)

联邦中添加或删除了一个新集群(让我们重新平衡应用程序的Pods)

严格耦合的应用程序可以轻松地一次移动一个Pod或整个Pod到一个或多个集群(在适用的策略约束条件下,例如“仅限私有云”)。

严格耦合的应用程序(除了被认为完全不可移动的应用程序)需要联邦系统执行以下操作:

在目标集群中启动整个副本应用程序

将持久数据复制到新的应用程序实例(可能在之前

启动Pods)

切换用户流量

拆除原始应用程序实例

Kubernetes提供KubeDNS作为内置核心组件。KubeDNS使用

cluster-localDNS服务器以及命名约定来组成合格的

(按命名空间)DNS名称约定。例如,the-service解析为默认namespace中的the-service服务,而the-service.the-namespace解析为the-namespacenamespace中名为the-service的服务,该服务与默认的the-service不同。Pod可以使用KubeDNS轻松找到和访问内部服务。Kubernetes集群联邦将该机制扩展到多个集群。基本概念是相同的,但增加了另一级联邦。现在服务的DNS名称由..组成。这样,仍然可以使用原始的.命名约定来访问内部服务。但是,想要访问联邦服务的客户端使用联邦名称,最终将被转发到联邦成员集群中的一个来处理请求。

这种联邦限定的命名约定还有助于防止内部集群流量错误地到达其他集群。

使用前面的NGINX示例服务和刚刚描述的联邦服务DNS名称形式,让我们考虑一个例子:位于cluster-1可用区的集群中的一个pod需要访问NGINX服务。它现在可以使用服务的联邦DNS名称,即nginx.the-namespace.the-federation,这将自动扩展并解析为NGINX服务的最近健康的分片,无论在世界的哪个地方。如果本地集群中存在健康的分片,该服务的集群本地(通常为10.x.y.z)IP地址将被返回(由集群本地的KubeDNS)。这几乎等同于非联邦服务解析(几乎因为KubeDNS实际上为本地联邦服务返回了CNAME和A记录,但应用程序对这种微小的技术差异是无感的)。

然而,如果服务在本地集群中不存在(或者没有健康的后端pod),DNS查询会自动扩展。

联合工作负载是在多个Kubernetes集群上同时处理的工作负载。这对于松散耦合和分布式应用程序来说相对容易。然而,如果大部分处理可以并行进行,通常在最后会有一个连接点,或者至少需要查询和更新一个中央持久存储。如果同一服务的多个pod需要在集群之间合作,或者一组服务(每个服务可能都是联合的)必须共同工作并同步以完成某些任务,情况就会变得更加复杂。

Kubernetes联合支持提供了联合工作负载的良好基础的联合服务。

联合服务的一些关键点是服务发现,跨集群

负载均衡和可用性区容错。

联合服务在联合成员集群中创建相应的服务。

例如,要创建一个联合NGINX服务(假设您在nginx.yaml中有服务配置),请输入以下内容:

>kubectl--context=federation-clustercreate-fnginx.yaml您可以验证每个集群中是否创建了一个服务(例如,在cluster-2中):

>kubectl--context=cluster-2getservicesnginxNAMECLUSTER-IPEXTERNAL-IPPORT(S)AGEnginx10.63.250.98104.199.136.8980/TCP9m所有集群中创建的服务将共享相同的命名空间和服务名称,这是有道理的,因为它们是一个单一的逻辑服务。

您的联合服务的状态将自动反映基础Kubernetes服务的实时状态:

run=nginx这是因为服务使用该标签来识别其pod。如果使用另一个标签,需要显式添加它:

forCincluster-1cluster-2cluster-3cluster-4cluster-5dokubectl--context=$Crunnginx--image=nginx:1.11.1-alpine--port=80done验证公共DNS记录一旦前面的pod成功启动并监听连接,Kubernetes将把它们报告为该集群中服务的健康端点(通过自动健康检查)。Kubernetes集群联邦将进一步考虑这些服务分片中的每一个为健康,并通过自动配置相应的公共DNS记录将它们放入服务中。你可以使用你配置的DNS提供商的首选界面来验证这一点。例如,你的联邦可能配置为使用GoogleCloudDNS和一个托管的DNS域,example.com:

>gclouddnsmanaged-zonesdescribeexample-dot-comcreationTime:'2017-03-08T18:18:39.229Z'description:ExampledomainforKubernetesClusterFederationdnsName:example.com.id:'7228832181334259121'kind:dns#managedZonename:example-dot-comnameServers:-ns-cloud-a1.googledomains.com.-ns-cloud-a2.googledomains.com.-ns-cloud-a3.googledomains.com.-ns-cloud-a4.googledomains.com.跟进以下命令以查看实际的DNS记录:

>gclouddnsrecord-setslist--zoneexample-dot-com如果你的联邦配置为使用awsroute53DNS服务,请使用以下命令:

>awsroute53list-hosted-zones然后使用这个命令:

>awsroute53list-resource-record-sets--hosted-zone-idK9PBY0X1QTOVBX当然,你可以使用标准的DNS工具,比如nslookup或dig来验证DNS记录是否被正确更新。你可能需要等一会儿才能使你的更改传播开来。或者,你可以直接指向你的DNS提供商:

>dig@ns-cloud-e1.googledomains.com...然而,我总是更喜欢在DNS更改在正确传播后观察它们的变化,这样我就可以通知用户一切都准备就绪。

如果服务在本地集群中不存在(或者存在但没有健康的后端pod),DNS查询会自动扩展,以找到最接近请求者可用区域的外部IP地址。KubeDNS会自动执行这个操作,并返回相应的CNAME。这将进一步解析为服务的一个后备pod的IP地址。

你不必依赖自动DNS扩展。你也可以直接提供特定集群中或特定区域中服务的CNAME。例如,在GCE/GKE上,你可以指定nginx.the-namespace.svc.europe-west1.example.com。这将被解析为欧洲某个集群中服务的一个后备pod的IP地址(假设那里有集群和健康的后备pod)。

外部客户端无法使用DNS扩展,但如果他们想要针对联邦的某个受限子集(比如特定区域),他们可以提供服务的完全限定的CNAME,就像例子一样。由于这些名称往往又长又笨重,一个好的做法是添加一些静态方便的CNAME记录:

eu.nginx.example.comCNAMEnginx.the-namespace.the-federation.svc.europe-west1.example.com.us.nginx.example.comCNAMEnginx.the-namespace.the-federation.svc.us-central1.example.com.nginx.example.comCNAMEnginx.the-namespace.the-federation.svc.example.com.下图显示了联邦查找在多个集群中是如何工作的:

当事情出现问题时,您需要能够找出问题所在以及如何解决。以下是一些常见问题以及如何诊断/解决它们。

请参考以下解决方案:

验证联邦API服务器正在运行

验证客户端(Kubectl)是否正确配置了适当的API端点和凭据

验证集群是否已注册到联邦

验证联邦API服务器能够连接并对所有集群进行身份验证

检查配额是否足够

检查日志是否有其他问题:

到目前为止,您应该对联邦的当前状态有清晰的了解,知道如何利用Kubernetes提供的现有功能,并了解您需要自己实现哪些部分来增强不完整或不成熟的功能。根据您的用例,您可能会决定现在还为时过早,或者您想要冒险尝试。致力于Kubernetes联邦的开发人员行动迅速,因此很可能在您需要做出决定时,它将更加成熟和经过实战检验。

在下一章中,我们将深入研究Kubernetes的内部结构以及如何自定义它。Kubernetes的一个显著的架构原则是,它可以通过一个完整的RESTAPI进行访问。Kubectl命令行工具是建立在KubernetesAPI之上的,并为Kubernetes的整个范围提供交互性。然而,编程API访问为您提供了许多灵活性,以增强和扩展Kubernetes。许多语言中都有客户端库,允许您从外部利用Kubernetes并将其集成到现有系统中。

除了其RESTAPI之外,Kubernetes在设计上是一个非常模块化的平台。它的核心操作的许多方面都可以定制和/或扩展。特别是,你可以添加用户定义的资源,并将它们与Kubernetes对象模型集成,并从Kubernetes的管理服务、etcd中的存储、通过API的暴露以及对内置和自定义对象的统一访问中受益。

我们已经看到了一些非常可扩展的方面,比如通过CNI插件和自定义存储类进行网络和访问控制。然而,Kubernetes甚至可以让你定制调度器本身,这个调度器控制着pod分配到节点上。

我们涵盖的主题如下:

使用KubernetesAPI

扩展KubernetesAPI

编写Kubernetes和Kubectl插件

编写webhooks

KubernetesAPI是全面的,涵盖了Kubernetes的全部功能。正如您所期望的那样,它是庞大的。但它采用了最佳实践进行了良好设计,并且是一致的。如果您了解基本原则,您可以发现您需要了解的一切。

在Kubernetes1.4中,为OpenAPI规范(在捐赠给OpenAPI倡议之前被称为Swagger2.0)添加了alpha支持,并更新了当前的模型和操作。在Kubernetes1.5中,通过直接从Kubernetes源自动生成规范来完成了对OpenAPI规范的支持,这使得规范和文档与操作/模型的未来变化完全同步。

新规范使API文档更好,并且我们将在以后探索自动生成的Python客户端。

该规范是模块化的,并按组版本划分。这是未来的保证。您可以运行支持不同版本的多个API服务器。应用程序可以逐渐过渡到更新的版本。

规范的结构在OpenAPI规范定义中有详细解释。Kubernetes团队使用操作标签来分隔每个组版本,并尽可能填写有关路径/操作和模型的信息。对于特定操作,所有参数、调用方法和响应都有文档记录。结果令人印象深刻。

为了简化访问,您可以使用Kubectl设置代理:

由于空间限制,这里是部分列表:

{"paths":["/api","/api/v1","/apis","/apis/apps","/apis/storage.k8s.io/v1",..."/healthz","/healthz/ping","/logs","/metrics","/swaggerapi/","/ui/","/version"]}您可以深入了解任何一个路径。例如,这是来自/api/v1/namespaces/default端点的响应:

{"apiVersion":"v1","kind":"Namespace","metadata":{"creationTimestamp":"2017-12-25T10:04:26Z","name":"default","resourceVersion":"4","selfLink":"/api/v1/namespaces/default","uid":"fd497868-e95a-11e7-adce-080027c94384"},"spec":{"finalizers":["kubernetes"]},"status":{"phase":"Active"}}我首先通过访问/api,然后发现了/api/v1,告诉我有/api/v1/namespaces,指引我到/api/v1/namespaces/default。

以下截图显示了批处理V1API组下可用的端点:

Postman有很多选项,并且以非常令人愉悦的方式组织信息。试试看吧。

API的输出有时可能太冗长。通常,您只对大量JSON响应中的一个值感兴趣。例如,如果您想获取所有运行服务的名称,可以访问/api/v1/services端点。然而,响应中包含许多无关的附加信息。这里是输出的一个非常部分子集:

检查完整的输出,您会看到服务名称在items数组中每个项目的metadata部分。将选择name的jq表达式如下:

.items[].metadata.name以下是完整的命令和输出:

{"kind":"Pod","apiVersion":"v1","metadata":{"name":"nginx","namespace":"default","labels":{"name":"nginx"}},"spec":{"containers":[{"name":"nginx","image":"nginx","ports":[{"containerPort":80}]}]}}以下命令将通过API创建pod:

/api/v1/namespaces/default/podsjq表达式如下:

items[].metadata.name,.items[].status.phase以下是完整的命令和输出:

首先确保已安装Python(2.7或3.5+)。然后安装Kubernetes包:

>pipinstallkubernetes要开始与Kubernetes集群通信,您需要连接到它。启动一个交互式的Python会话:

>pythonPython3.6.4(default,Mar12018,18:36:42)[GCC4.2.1CompatibleAppleLLVM9.0.0(clang-900.0.39.2)]ondarwinType"help","copyright","credits"or"license"formoreinformation.>>>Python客户端可以读取您的Kubectl配置:

>>>fromkubernetesimportclient,config>>>config.load_kube_config()>>>v1=client.CoreV1Api()或者它可以直接连接到已经运行的代理:

让我们深入了解CoreV1API组。Python对象有481个公共属性:

>>>attributes=[xforxindir(v1)ifnotx.startswith('__')]>>>len(attributes)481忽略以双下划线开头的属性,因为它们是与Kubernetes无关的特殊class/instance方法。

让我们随机挑选十个方法,看看它们是什么样子的:

>>>fromcollectionsimportCounter>>>verbs=[x.split('_')[0]forxinattributes]>>>pp(dict(Counter(verbs))){'api':1,'connect':96,'create':36,'delete':56,'get':2,'list':56,'patch':48,'proxy':84,'read':52,'replace':50}我们可以进一步深入,查看特定属性的交互式帮助:

>>>help(v1.create_node)Helponmethodcreate_nodeinmodulekuber-netes.client.apis.core_v1_api:create_node(body,**kwargs)methodofkuber-netes.client.apis.core_v1_api.CoreV1ApiinstancecreateaNodeThismethodmakesasynchronousHTTPrequestbydefault.TomakeanasynchronousHTTPrequest,pleasepassasync=True>>>thread=api.create_node(body,async=True)>>>result=thread.get():paramasyncbool:paramV1Nodebody:(required):paramstrpretty:If'true',thentheoutputisprettyprinted.:return:V1NodeIfthemethodiscalledasynchronously,returnstherequestthread.您可以自己查看并了解有关API的更多信息。让我们看一些常见操作,如列出、创建、观察和删除对象。

您可以列出不同类型的对象。方法名称以list_开头。以下是列出所有命名空间的示例:

>>>fornsinv1.list_namespace().items:...print(ns.metadata.name)...defaultkube-publickube-system创建对象要创建一个对象,您需要将一个body参数传递给create方法。body必须是一个等同于您在Kubectl中使用的YAML配置文件的Python字典。最简单的方法是实际使用YAML,然后使用PythonYAML模块(这不是标准库的一部分,必须单独安装)来读取YAML文件并将其加载到字典中。例如,要创建一个带有3个副本的nginx-deployment,我们可以使用这个YAML配置文件:

apiVersion:apps/v1kind:Deploymentmetadata:name:nginx-deploymentspec:replicas:3template:metadata:labels:app:nginxspec:containers:-name:nginximage:nginx:1.7.9ports:-containerPort:80要安装yamlPython模块,请输入以下命令:

>pipinstallyaml然后以下Python程序将创建部署:

fromosimportpathimportyamlfromkubernetesimportclient,configdefmain():#ConfigscanbesetinConfigurationclassdirectlyorusing#helperutility.Ifnoargumentprovided,theconfigwillbe#loadedfromdefaultlocation.config.load_kube_config()withopen(path.join(path.dirname(__file__),'nginx-deployment.yaml'))asf:dep=yaml.load(f)k8s=client.AppsV1Api()status=k8s_beta.create_namespaced_deployment(body=dep,namespace="default").statusprint("Deploymentcreated.status='{}'".format(status))if__name__=='__main__':main()观察对象观察对象是一种高级功能。它是使用单独的观察模块实现的。以下是一个示例,用于观察10个命名空间事件并将它们打印到屏幕上:

fromkubernetesimportclient,config,watch#ConfigscanbesetinConfigurationclassdirectlyorusinghelperutilityconfig.load_kube_config()v1=client.CoreV1Api()count=10w=watch.Watch()foreventinw.stream(v1.list_namespace,_request_timeout=60):print(f"Event:{event['type']}{event['object'].metadata.name}")count-=1ifcount==0:w.stop()print('Done.')以编程方式调用Kubectl如果您不是Python开发人员,也不想直接处理RESTAPI,那么您还有另一种选择。Kubectl主要用作交互式命令行工具,但没有任何阻止您自动化它并通过脚本和程序调用它。使用Kubectl作为KubernetesAPI客户端的一些好处包括:

易于找到任何用法的示例

在命令行上轻松实验,找到正确的命令和参数组合

Kubectl支持以JSON或YAML格式输出,以便快速解析。

身份验证是通过Kubectl配置内置的

我将再次使用Python,这样您可以比较使用官方Python客户端和自己编写的客户端。Python有一个名为subprocess的模块,可以运行Kubectl等外部进程并捕获输出。以下是一个Python3示例,独立运行Kubectl并显示用法输出的开头:

以下是一些初学者的基本命令:

create:使用文件名或stdin创建资源

expose:获取复制控制器、服务、部署或pod

check_checkout()函数将输出捕获为一个需要解码为utf-8以正确显示的字节数组。我们可以将其概括一点,并创建一个名为k的便利函数,它接受参数并将其传递给Kubectl,然后解码输出并返回它:

fromsubprocessimportcheck_outputdefk(*args):out=check_output(['kubectl']+list(args))returnout.decode('utf-8')Let'suseittolistalltherunningpodsinthedefaultnamespace:>>>print(k('get','po'))NAMEReadyStatusRestartsAgenginx-deployment-6c54bd5869-9mp2g1/1Running018mnginx-deployment-6c54bd5869-lgs841/1Running018mnginx-deployment-6c54bd5869-n74681/1Running0.18m这对于显示很好,但Kubectl已经做到了。当您使用带有-o标志的结构化输出选项时,真正的力量就会显现出来。然后结果可以自动转换为Python对象。这是k()函数的修改版本,它接受一个布尔值use_json关键字参数(默认为False);如果为True,则添加-ojson,然后将JSON输出解析为Python对象(字典):

fromsubprocessimportcheck_outputimportjsondefk(use_json=False,*args):cmd=['kubectl']cmd+=list(args)ifuse_json:cmd+=['-o','json']out=check_output(cmd)ifuse_json:out=json.loads(out)else:out=out.decode('utf-8')returnout返回一个完整的API对象,可以像直接访问RESTAPI或使用官方Python客户端一样进行导航和钻取:

result=k('get','po',use_json=True)forrinresult['items']:print(r['metadata']['name'])nginx-deployment-6c54bd5869-9mp2gnginx-deployment-6c54bd5869-lgs84nginx-deployment-6c54bd5869-n7468让我们看看如何删除deployment并等待所有pod消失。Kubectldelete命令不接受-ojson选项(尽管有-o名称),所以让我们不使用use_json:

k('delete','deployment','nginx-deployment')whilelen(k('get','po',use_json=True)['items'])>0:print('.')print('Done.')Done.扩展KubernetesAPIKubernetes是一个非常灵活的平台。它允许您通过称为自定义资源的新类型资源扩展自己的API。如果这还不够,您甚至可以提供与KubernetesAPI服务器集成的API聚合机制。您可以用自定义资源做什么?很多。您可以使用它们来管理Kubernetes集群外部的KubernetesAPI资源,您的pod与之通信。

通过将这些外部资源添加为自定义资源,您可以全面了解系统,并从许多KubernetesAPI功能中受益,例如以下功能:

自定义CRUDREST端点

观察

与通用Kubernetes工具的自动集成

自定义控制器和自动化程序的元数据

在Kubernetes1.7中引入的自定义资源是对现在已弃用的第三方资源的重大改进。让我们深入了解一下自定义资源的全部内容。

为了与KubernetesAPI服务器协作,第三方资源必须符合一些基本要求。与内置API对象类似,它们必须具有以下字段:

apiVersion:apiextensions.k8s.io/v1beta1

metadata:标准Kubernetes对象元数据

kind:CustomResourceDefinition

spec:描述资源在API和工具中的外观

status:指示CRD的当前状态

规范具有内部结构,包括组、名称、范围、验证和版本等字段。状态包括字段acceptedNames和Conditions。在下一节中,我将为您展示一个示例,以阐明这些字段的含义。

您可以使用自定义资源定义(也称为CRD)开发自定义资源。CRD的目的是与Kubernetes、其API和其工具平稳集成,因此您需要提供大量信息。这是一个名为Candy的自定义资源的示例:

apiVersion:apiextensions.k8s.io/v1beta1kind:CustomResourceDefinitionmetadata:#namemustmatchthespecfieldsbelow,andbeintheform:.name:candies.awesome.corp.comspec:#groupnametouseforRESTAPI:/apis//group:awesome.corp.com#versionnametouseforRESTAPI:/apis//version:v1#eitherNamespacedorClusterscope:Namespacednames:#pluralnametobeusedintheURL:/apis///plural:candies#singularnametobeusedasanaliasontheCLIandfordisplaysingular:candy#kindisnormallytheCamelCasedsingulartype.Yourresourcemanifestsusethis.kind:Candy#shortNamesallowshorterstringtomatchyourresourceontheCLIshortNames:-cn让我们创建它:

>kubectlcreate-fcrd.yamlcustomresourcedefinition"candies.awesome.corp.com"created注意,返回的元数据名称带有复数标记。现在,让我们验证一下我们是否可以访问它:

>kubectlgetcrdNAMEAGEcandies.awesome.corp.com17m还有一个新的API端点用于管理这种新资源:

/apis/awesome.corp.com/v1/namespaces//candies/让我们使用我们的Python代码来访问它:

>>>config.load_kube_config()>>>print(k('get','thirdpartyresources'))NAMEAGEcandies.awesome.corp.com24m集成自定义资源创建CustomResourceDefinition对象后,您可以特定地创建该资源类型的自定义资源,例如,在这种情况下是Candy(candy变为CamelCaseCandy)。Candy对象可以包含任意字段和任意JSON。在下面的示例中,flavor自定义字段设置在Candy对象上。apiVersion字段是从CRD规范的组和版本字段派生的:

apiVersion:"awesome.corp.com/v1"kind:Candymetadata:name:chocolatemspec:flavor:"sweeeeeeet"您可以向自定义资源添加任意字段。这些值可以是任何JSON值。请注意,这些字段未在CRD中定义。不同的对象可以具有不同的字段。让我们创建它:

>kubectlcreate-fcandy.yamlcandy"chocolate"created此时,kubectl可以像操作内置对象一样操作Candy对象。请注意,在使用kubectl时,资源名称不区分大小写:

$kubectlgetcandiesNAMEAGEchocolate2m我们还可以使用标准的-ojson标志查看原始JSON数据。这次我会使用简称cn:

>kubectlgetcn-ojson{"apiVersion":"v1","items":[{"apiVersion":"awesome.corp.com/v1","kind":"Candy","metadata":{"clusterName":"","creationTimestamp":"2018-03-07T18:18:42Z","name":"chocolate","namespace":"default","resourceVersion":"4791773","selfLink":"/apis/awesome.corp.com/v1/namespaces/default/candies/chocolate","uid":"f7a6fd80-2233-11e8-b432-080027c94384"},"spec":{"flavor":"sweeeeeeet"}}],"kind":"List","metadata":{"resourceVersion":"","selfLink":""}}完成自定义资源自定义资源支持与标准API对象一样的finalizers。finalizer是一种机制,对象不会立即被删除,而是必须等待后台运行并监视删除请求的特殊控制器。控制器可以执行任何必要的清理操作,然后从目标对象中删除其finalizer。对象上可能有多个finalizer。Kubenetes将等待直到所有finalizer都被删除,然后才删除对象。元数据中的finalizer只是它们对应的控制器可以识别的任意字符串。这里有一个示例,其中Candy对象有两个finalizer,eat-me和drink-me:

apiVersion:"awesome.corp.com/v1"kind:Candymetadata:name:chocolatefinalizers:-eat-me-drink-mespec:flavor:"sweeeeeeet"验证自定义资源您可以向CRD添加任何字段。这可能导致无效的定义。Kubernetes1.9引入了基于OpenAPIV3模式的CRD验证机制。它仍处于测试阶段,并且可以在启动API服务器时使用功能开关进行禁用:

--feature-gates=CustomResourceValidation=false在您的CRD中,您可以在规范中添加一个验证部分:

当您只需要对自己的类型进行一些CRUD操作时,CRDs非常好。您可以直接在KubernetesAPI服务器上运行,它将存储您的对象并提供API支持和与诸如Kubectl之类的工具集成。您可以运行控制器来监视您的对象,并在创建、更新或删除时执行一些操作。但CRDs有局限性。如果您需要更高级的功能和定制,您可以使用API服务器聚合并编写自己的API服务器,KubernetesAPI服务器将委托给它。

您的API服务器将使用与KubernetesAPI服务器本身相同的API机制。一些高级功能如下:

控制对象的存储

多版本

超出CRUD的自定义操作(如exec或scale)

使用协议缓冲区有效载荷

编写扩展API服务器是一项非常艰巨的工作。如果您决定需要所有这些功能,我建议使用API构建器项目:

这是一个年轻的项目,但它处理了许多必要的样板代码。API构建器提供以下功能:

引导完整的类型定义、控制器和测试,以及文档

您可以在Minikube内部本地运行扩展控制平面,也可以在实际的远程集群上运行

您生成的控制器将能够监视和更新API对象

添加资源(包括子资源)

如果需要,您可以覆盖默认值

Kubernetes服务目录项目允许您平滑且无痛地集成任何支持OpenServiceBrokerAPI规范的外部服务:

开放服务经纪人API的目的是通过支持文档和全面的测试套件,通过标准规范将外部服务暴露给任何云环境。这使提供商可以实现单一规范,并支持多个云环境。当前的环境包括Kubernetes和CloudFoundry。该项目致力于广泛的行业采用。

服务目录对于集成云平台提供商的服务特别有用。以下是一些此类服务的示例:

MicrosoftAzureCloudQueue

AmazonSimpleQueueService

GoogleCloudPub/Sub

这种能力对于致力于云计算的组织来说是一个福音。您可以在Kubernetes上构建系统,但不必自己部署、管理和维护集群中的每个服务。您可以将这些工作外包给您的云提供商,享受深度集成,并专注于您的应用程序。

服务目录有可能使您的Kubernetes集群完全自主,因为它允许您通过服务经纪人来配置云资源。我们还没有达到那一步,但这个方向非常有前途。

这结束了我们对从外部访问和扩展Kubernetes的讨论。在下一节中,我们将把目光投向内部,并研究通过插件自定义Kubernetes内部工作的方法。

在本节中,我们将深入研究Kubernetes的内部,并学习如何利用其著名的灵活性和可扩展性。我们将了解可以通过插件自定义的不同方面,以及如何实现这些插件并将其与Kubernetes集成。

Kubernetes将自己定义为容器调度和管理系统。因此,调度程序是Kubernetes最重要的组件。Kubernetes带有默认调度程序,但允许编写额外的调度程序。要编写自己的自定义调度程序,您需要了解调度程序的功能,它是如何打包的,如何部署您的自定义调度程序以及如何集成您的调度程序。调度程序源代码在这里可用:

在本节的其余部分,我们将深入研究源代码,并检查数据类型、算法和代码。

调度程序的工作是为新创建或重新启动的pod找到一个节点,并在API服务器中创建一个绑定并在那里运行它。如果调度程序找不到适合pod的节点,它将保持在挂起状态。

调度程序的大部分工作都是相当通用的——它会找出哪些pod需要被调度,更新它们的状态,并在选定的节点上运行它们。定制部分是如何将pod映射到节点。Kubernetes团队意识到了需要定制调度的需求,通用调度程序可以配置不同的调度算法。

主要数据类型是包含许多属性的调度程序struct,其中包含一个Configstruct(这很快将被configurator接口替换):

typeSchedulerstruct{config*Config}这是Configstruct:

调度程序具有算法提供程序和算法的概念。它们一起让您使用内置调度程序的重要功能,以替换核心调度算法。

算法提供程序允许您使用工厂注册新的算法提供程序。已经注册了一个名为ClusterAutoScalerProvider的自定义提供程序。稍后我们将看到调度程序如何知道使用哪个算法提供程序。关键文件如下:

init()函数调用registerAlgorithmProvider(),您应该扩展它以包括您的算法提供程序以及默认和autoscaler提供程序:

funcregisterAlgorithmProvider(predSet,priSetsets.String){//Registersalgorithmproviders.Bydefaultweuse'DefaultProvider'//butusercanspecifyonetobeusedbyspecifyingflag.factory.RegisterAlgorithmProvider(factory.DefaultProvider,predSet,priSet)//Clusterautoscalerfriendlyschedulingalgorithm.factory.RegisterAlgorithmProvider(ClusterAutoscalerProvider,predSet,copyAndReplace(priSet,"LeastRequestedPriority","MostRequestedPriority"))}除了注册提供程序,您还需要注册适合谓词和优先级函数,这些函数用于实际执行调度。

您可以使用工厂的RegisterFitPredicate()和RegisterPriorityFunction2()函数。

调度程序算法作为配置的一部分提供。自定义调度程序可以实现ScheduleAlgorithm接口:

typeScheduleAlgorithminterface{Schedule(*v1.Pod,NodeLister)(selectedMachinestring,errerror)Preempt(*v1.Pod,NodeLister,error)(selectedNode*v1.Node,preemptedPods[]*v1.Pod,cleanupNominatedPods[]*v1.Pod,errerror)Predicates()map[string]FitPredicatePrioritizers()[]PriorityConfig}当您运行调度程序时,您可以提供自定义调度程序的名称或自定义算法提供程序作为命令行参数。如果没有提供,则将使用默认的算法提供程序。调度程序的命令行参数是--algorithm-provider和--scheduler-name。

自定义调度程序作为一个pod在同一个Kubernetes集群中运行。它需要被打包为一个容器镜像。让我们使用标准Kubernetes调度程序的副本进行演示。我们可以从源代码构建Kubernetes以获取调度程序镜像:

FROMbusyboxADD./_output/bin/kube-scheduler/usr/local/bin/kube-scheduler使用它来构建一个Docker镜像类型:

>dockerlogin>dockerpushg1g1/custom-kube-scheduler请注意,我在本地构建了调度程序,并且在Dockerfile中,我只是将它从主机复制到镜像中。当您在与构建相同的操作系统上部署时,这种方法是有效的。如果不是这种情况,那么最好将构建命令插入Dockerfile中。你需要付出的代价是需要将所有Kubernetes内容都拉入镜像中。

现在调度程序镜像已构建并在注册表中可用,我们需要为其创建一个Kubernetes部署。调度程序当然是关键的,所以我们可以使用Kubernetes本身来确保它始终在运行。以下YAML文件定义了一个部署,其中包含一个单一副本和一些其他功能,如活跃和就绪探针:

运行另一个自定义调度程序就像创建部署一样简单。这就是这种封装方法的美妙之处。Kubernetes将运行第二个调度程序,这是一件大事,但Kubernetes并不知道发生了什么。它只是部署一个pod,就像部署任何其他pod一样,只是这个pod碰巧是一个自定义调度程序:

$kubectlcreate-fcustom-scheduler.yaml让我们验证调度程序pod是否在运行:

$kubectlgetpods--namespace=kube-systemNAMEREADYSTATUSRESTARTSAGE....custom-scheduler-7cfc49d749-lwzxj1/1Running02m...我们的自定义调度程序正在运行。

好的。自定义调度程序正在与默认调度程序一起运行。但是当pod需要调度时,Kubernetes如何选择要使用的调度程序呢?答案是pod决定而不是Kubernetes。pod规范具有一个可选的调度程序名称字段。如果缺少,将使用默认调度程序;否则,将使用指定的调度程序。这就是自定义调度程序名称必须是唯一的原因。默认调度程序的名称是default-scheduler,如果您想在pod规范中明确表示。以下是将使用默认调度程序安排的pod定义:

apiVersion:v1kind:Podmetadata:name:some-podlabels:name:some-podspec:containers:-name:some-containerimage:gcr.io/google_containers/pause:2.0要让custom-scheduler安排此pod,请将pod规范更改为以下内容:

apiVersion:v1kind:Podmetadata:name:some-podlabels:name:some-podspec:schedulerName:custom-schedulercontainers:-name:some-containerimage:gcr.io/google_containers/pause:2.0验证使用自定义调度程序安排了pod有两种主要方法可以验证pod是否由正确的调度程序安排。首先,您可以创建需要由自定义调度程序安排的pod,然后部署自定义调度程序。这些pod将保持在挂起状态。然后,部署自定义调度程序,挂起的pod将被安排并开始运行。

另一种方法是检查事件日志,并使用以下命令查找已安排的事件:

要提供这些信息并启用认证webhooks,请使用以下命令行参数启动API服务器:

--runtime-config=authentication.k8s.io/v1beta1=true

--authentication-token-webhook-config-file

--authentication-token-webhook-cache-ttl

配置文件使用kubeconfig文件格式。以下是一个例子:

缓存TTL很有用,因为通常用户会对Kubernetes进行多次连续请求。缓存认证决策可以节省大量与远程认证服务的往返。

当APIHTTP请求到来时,Kubernetes会从其标头中提取持有者令牌,并通过Webhook将TokenReviewJSON请求发送到远程认证服务:

{"apiVersion":"authentication.k8s.io/v1beta1","kind":"TokenReview","spec":{"token":""}}远程认证服务将做出响应。认证状态将是true或false。以下是成功认证的示例:

{"apiVersion":"authentication.k8s.io/v1beta1","kind":"TokenReview","status":{"authenticated":true,"user":{"username":"gigi@gg.com","uid":"42","groups":["developers",],"extra":{"extrafield1":["extravalue1","extravalue2"]}}}}拒绝的响应要简洁得多:

您可以通过向API服务器传递以下命令行参数来配置Webhook:

--runtime-config=authorization.k8s.io/v1beta1=true

--authorization-webhook-config-file=

{"apiVersion":"authorization.k8s.io/v1beta1","kind":"SubjectAccessReview","spec":{"resourceAttributes":{"namespace":"awesome-namespace","verb":"get","group":"awesome.example.org","resource":"pods"},"user":"gigi@gg.com","group":["group1","group2"]}}请求将被允许:

{"apiVersion":"authorization.k8s.io/v1beta1","kind":"SubjectAccessReview","status":{"allowed":true}}否则将被拒绝(并附带原因):

以下是如何请求访问日志:

{"apiVersion":"authorization.k8s.io/v1beta1","kind":"SubjectAccessReview","spec":{"nonResourceAttributes":{"path":"/logs","verb":"get"},"user":"gigi@gg.com","group":["group1","group2"]}}使用准入控制Webhook动态准入控制也支持Webhook。它仍处于alpha阶段。您需要通过向API服务器传递以下命令行参数来启用通用准入Webhook:

--admission-control=GenericAdmissionWebhook

--runtime-config=admissionregistration.k8s.io/v1alpha1

apiVersion:admissionregistration.k8s.io/v1alpha1kind:ExternalAdmissionHookConfigurationmetadata:name:example-configexternalAdmissionHooks:-name:pod-image.k8s.iorules:-apiGroups:-""apiVersions:-v1operations:-CREATEresources:-podsfailurePolicy:IgnoreclientConfig:caBundle:service:name:namespace:为水平Pod自动缩放提供自定义指标在Kubernetes1.6之前,自定义指标是作为Heapster模型实现的。在Kubernetes1.6中,引入了一个新的自定义指标API,并逐渐成熟。截至Kubernetes1.9,它们已默认启用。自定义指标依赖于API聚合。推荐的路径是从这里开始使用自定义指标API服务器样板:

然后,您实现CustomMetricsProvider接口:

typeCustomMetricsProviderinterface{GetRootScopedMetricByName(groupResourceschema.GroupResource,namestring,metricNamestring)(*custom_metrics.MetricValue,error)GetRootScopedMetricBySelector(groupResourceschema.GroupResource,selectorlabels.Selector,metricNamestring)(*custom_metrics.MetricValueList,error)GetNamespacedMetricByName(groupResourceschema.GroupResource,namespacestring,namestring,metricNamestring)(*custom_metrics.MetricValue,error)GetNamespacedMetricBySelector(groupResourceschema.GroupResource,namespacestring,selectorlabels.Selector,metricNamestring)(*MetricValueList,error)ListAllMetrics()[]MetricInfo}使用自定义存储扩展Kubernetes卷插件是另一种类型的插件。在Kubernetes1.8之前,您必须编写一个需要实现、注册到Kubernetes并与Kubelet链接的Kublet插件。Kubernetes1.8引入了FlexVolume,这是更加灵活的。Kubernetes1.9通过容器存储接口(CSI)将其提升到了下一个级别。

Kubernetes卷插件旨在支持特定类型的存储或存储提供程序。有许多卷插件,我们在第七章中介绍了这些内容,处理Kubernetes存储。现有的卷插件对于大多数用户来说已经足够了,但是如果您需要集成一个不受支持的存储解决方案,您必须实现自己的卷插件,这并不是微不足道的。如果您希望它被接受为官方的Kubernetes插件,那么您必须经过严格的批准流程。但是FlexVolume提供了另一条路径。它是一个通用插件,允许您连接不受支持的存储后端,而无需与Kubernetes本身进行深度集成。

FlexVolume允许您向规范添加任意属性,并通过调用接口与您的后端通信,该接口包括以下操作:

附加:将卷附加到KubernetesKubelet节点

分离:从KubernetesKubelet节点分离卷

挂载:挂载附加的卷

卸载:卸载附加的卷

FlexVolume提供了超出树插件的能力,但仍需要FlexVolume插件本身和一种相当繁琐的安装和调用模型。CSI将通过让供应商直接实现它来显著改进。最好的是,作为开发人员,你不必创建和维护这些插件。实现和维护CSI是存储解决方案提供商的责任,他们有兴趣尽可能地使其稳健,以便人们不选择在Kubernetes(以及与CSI集成的其他平台)上直接使用的不同存储解决方案。

在本章中,我们涵盖了三个主要主题:使用KubernetesAPI、扩展KubernetesAPI和编写Kubernetes插件。KubernetesAPI支持OpenAPI规范,是RESTAPI设计的一个很好的例子,遵循了所有当前的最佳实践。它非常一致、组织良好、文档完善,但它是一个庞大的API,不容易理解。你可以通过RESToverHTTP直接访问API,使用包括官方Python客户端在内的客户端库,甚至通过调用Kubectl。

扩展KubernetesAPI涉及定义自己的自定义资源,并通过API聚合可选地扩展API服务器本身。当你外部查询和更新它们时,自定义资源在与额外的自定义插件或控制器相结合时最有效。

插件和webhooks是Kubernetes设计的基础。Kubernetes始终旨在由用户扩展以适应任何需求。我们看了一些你可以编写的插件和webhooks,以及如何注册和无缝集成它们到Kubernetes中。

我们还研究了自定义指标,甚至如何通过自定义存储选项扩展Kubernetes。

在这一点上,你应该很清楚通过API访问、自定义资源和自定义插件来扩展、定制和控制Kubernetes的所有主要机制。你处于一个很好的位置,可以利用这些能力来增强Kubernetes的现有功能,并使其适应你的需求和系统。

在下一章中,我们将看一下Helm,Kubernetes包管理器,以及它的图表。正如你可能已经意识到的那样,在Kubernetes上部署和配置复杂系统远非简单。Helm允许将一堆清单组合成一个图表,可以作为单个单元安装。

在本章中,我们将深入了解Helm,即Kubernetes软件包管理器。每个成功和重要的平台都必须有一个良好的打包系统。Helm由Deis开发(于2017年4月被微软收购),后来直接贡献给了Kubernetes项目。我们将从理解Helm的动机、架构和组件开始。然后,我们将亲身体验并了解如何在Kubernetes中使用Helm及其图表。这包括查找、安装、自定义、删除和管理图表。最后但同样重要的是,我们将介绍如何创建自己的图表,并处理版本控制、依赖关系和模板化。

将涵盖以下主题:

理解Helm

使用Helm

创建自己的图表

Kubernetes提供了许多在运行时组织和编排容器的方式,但缺乏将一组图像进行更高级别组织的方式。这就是Helm的用武之地。在本节中,我们将讨论Helm的动机、其架构和组件,并讨论从HelmClassic过渡到Helm时发生了什么变化。

Helm支持几个重要的用例:

管理复杂性

轻松升级

简单共享

安全回滚

图表可以描述甚至最复杂的应用程序,提供可重复的应用程序安装,并作为单一的权威点。原地升级和自定义钩子允许轻松更新。共享图表很简单,可以在公共或私有服务器上进行版本控制和托管。当您需要回滚最近的升级时,Helm提供了一个命令来回滚基础设施的一致变化集。

Helm旨在执行以下操作:

从头开始创建新图表

将图表打包成图表存档(tgz)文件

与存储图表的图表存储库进行交互

将图表安装和卸载到现有的Kubernetes集群中

管理使用Helm安装的图表的发布周期

Helm使用客户端-服务器架构来实现这些目标。

Helm有一个在Kubernetes集群上运行的服务器组件和一个在本地机器上运行的客户端组件。

服务器负责管理发布。它与Helm客户端以及KubernetesAPI服务器进行交互。其主要功能如下:

监听来自Helm客户端的传入请求

结合图表和配置以构建发布

将图表安装到Kubernetes中

跟踪后续发布

通过与Kubernetes交互来升级和卸载图表

您在您的机器上安装Helm客户端。它负责以下工作:

本地图表开发

管理存储库

与Tiller服务器交互

发送图表以安装

询问有关发布的信息

请求升级或卸载现有版本

Helm是一个丰富的软件包管理系统,可以让您执行管理集群上安装的应用程序所需的所有必要步骤。让我们卷起袖子,开始吧。

安装Helm涉及安装客户端和服务器。Helm是用Go实现的,同一个二进制可用作客户端或服务器。

您必须正确配置Kubectl以与您的Kubernetes集群通信,因为Helm客户端使用Kubectl配置与Helm服务器(Tiller)通信。

对于macOS和Linux,您可以从脚本安装客户端:

brewinstallkubernetes-helm安装Tiller服务器Tiller通常在集群内运行。对于开发来说,有时在本地运行Tiller会更容易一些。

安装Tiller的最简单方法是从安装了Helm客户端的机器上进行。运行以下命令:

helminit这将在远程Kubernetes集群上初始化客户端和Tiller服务器。安装完成后,您将在集群的kube-system命名空间中有一个正在运行的Tillerpod:

>kubectlgetpo--namespace=kube-system-lname=tillerNAMEREADYSTATUSRESTARTSAGEtiller-deploy-3210613906-2j5sh1/1Running01m您还可以运行helmversion来查看客户端和服务器的版本:

>helmversionClient:&version.Version{SemVer:"v2.2.3",GitCommit:"1402a4d6ec9fb349e17b912e32fe259ca21181e3",GitTreeState:"clean"}Server:&version.Version{SemVer:"v2.2.3",GitCommit:"1402a4d6ec9fb349e17b912e32fe259ca21181e3",GitTreeState:"clean"}在本地安装Tiller如果要在本地运行Tiller,首先需要构建它。这在Linux和macOS上都受支持:

构建目标将编译Helm并将其放置在bin/helm中。Tiller也被编译并放置在bin/tiller中。

现在您可以运行bin/tiller。Tiller将通过您的Kubectl配置连接到Kubernetes集群。

需要告诉Helm客户端连接到本地的Tiller服务器。您可以通过设置环境变量来实现:

>exportHELM_HOST=localhost:44134否则,您可以将其作为命令行参数传递:--hostlocalhost:44134。

Helm2.7.0添加了将发布信息存储为secrets的选项。早期版本总是将发布信息存储在ConfigMaps中。secrets后端增加了图表的安全性。它是一种通用Kubernetes加密的补充。要使用Secrets后端,您需要使用以下命令行运行Helm:

>helminit--override'spec.template.spec.containers[0].command'='{/tiller,--storage=secret}'查找图表为了使用Helm安装有用的应用程序和软件,您需要先找到它们的图表。这就是helmsearch命令发挥作用的地方。默认情况下,Helm搜索官方的Kuberneteschart仓库,名为stable:

>helmsearchNAMEVERSIONDESCRIPTIONstable/acs-engine-autoscaler2.1.1Scalesworkernodeswithinagentpoolsstable/aerospike0.1.5AHelmchartforAerospikeinKubernetesstable/artifactory6.2.4UniversalRepositoryManagersupportingallmaj...stable/aws-cluster-autoscaler0.3.2Scalesworkernodeswithinautoscalinggroups.stable/buildkite0.2.0AgentforBuildkitestable/centrifugo2.0.0Centrifugoisareal-timemessagingserver.stable/chaoskube0.6.1Chaoskubeperiodicallykillsrandompodsinyou...stable/chronograf0.4.0Open-sourcewebapplicationwritteninGoandR..stable/cluster-autoscaler0.3.1Scalesworkernodeswithinautoscalinggroups.官方仓库拥有丰富的图表库,代表了所有现代开源数据库、监控系统、特定于Kubernetes的辅助工具,以及一系列其他提供,比如Minecraft服务器。您可以搜索特定的图表,例如,让我们搜索包含kube在其名称或描述中的图表:

>helmsearchmysqlNAMEVERSIONDESCRIPTIONstable/mysql0.3.4Fast,reliable,scalable,andeasytouseopen-...stable/percona0.3.0free,fullycompatible,enhanced,opensourced...stable/gcloud-sqlproxy0.2.2GoogleCloudSQLProxystable/mariadb2.1.3Fast,reliable,scalable,andeasytouseopen-...发生了什么?为什么mariadb出现在结果中?原因是mariadb(它是MySQL的一个分支)在其描述中提到了MySQL,即使在截断的输出中看不到。要获取完整的描述,请使用helminspect命令:

>helminstallstable/mariadbNAME:cranky-whippetLASTDEPLOYED:SatMar1710:21:212018NAMESPACE:defaultSTATUS:DEPLOYED输出的第二部分列出了此图表创建的所有资源。请注意,资源名称都是根据发布名称派生的:

RESOURCES:==>v1/ServiceNAMETYPECLUSTER-IPEXTERNAL-IPPORT(S)AGEcranky-whippet-mariadbClusterIP10.106.206.1083306/TCP1s==>v1beta1/DeploymentNAMEDESIREDCURRENTUP-TO-DATEAVAILABLEAGEcranky-whippet-mariadb11101s==>v1/Pod(related)NAMEREADYSTATUSRESTARTSAGEcranky-whippet-mariadb-6c85fb4796-mttf70/1Init:0/101s==>v1/SecretNAMETYPEDATAAGEcranky-whippet-mariadbOpaque21s==>v1/ConfigMapNAMEDATAAGEcranky-whippet-mariadb11scranky-whippet-mariadb-tests11s==>v1/PersistentVolumeClaimNAMESTATUSVOLUMECAPACITYACCESSMODESSTORAGECLASSAGEcranky-whippet-mariadbBoundpvc-9cb7e176-2a07-11e8-9bd6-080027c943848GiRWOstandard1s最后一部分是提供如何在Kubernetes集群中使用MariaDB的易于理解的说明:

>helmstatuscranky-whippet|grepPersist-A3==>v1/PersistentVolumeClaimNAMESTATUSVOLUMECAPACITYACCESSMODESSTORAGECLASSAGEcranky-whippet-mariadbBoundpvc-9cb7e176-2a07-11e8-9bd6-080027c943848GiRWOstandard5m万岁!它现在已绑定,并且附加了一个容量为8GB的卷。

让我们尝试连接并验证mariadb是否确实可访问。让我们稍微修改一下注释中建议的命令以进行连接。我们可以直接在容器上运行mysql命令,而不是运行bash然后再运行mysql:

>kubectlruncranky-whippet-mariadb-client--rm--tty-i--imagebitnami/mariadb--command--mysql-hcranky-whippet-mariadb如果您看不到命令提示符,请尝试按Enter键。

MariaDB[(none)]>showdatabases;+--------------------+|Database|+--------------------+|information_schema||mysql||performance_schema|+--------------------+3rowsinset(0.00sec)自定义图表作为用户,您经常希望自定义或配置您安装的图表。Helm完全支持通过config文件进行自定义。要了解可能的自定义,您可以再次使用helminspect命令,但这次要专注于值。以下是部分输出:

mariadbRootPassword:supersecretmariadbDatabase:awesome_stuff然后,运行helm并传递yaml文件:

>helminstall-fconfig.yamlstable/mariadb您还可以使用--set在命令行上设置单个值。如果--f和--set都尝试设置相同的值,则--set优先。例如,在这种情况下,根密码将是evenbettersecret:

helminstall-fconfig.yaml--setmariadbRootPassword=evenbettersecretstable/mariadb您可以使用逗号分隔的列表指定多个值:--seta=1,b=2。

一个chartrepository(正如我们所见)

本地图表存档(helminstallfoo-0.1.1.tgz)

一个解压的chart目录(helminstallpath/to/foo)

您可能希望将安装的软件包升级到最新版本。Helm提供了upgrade命令,它可以智能地操作,并且只更新已更改的内容。例如,让我们检查我们mariadb安装的当前值:

>helmgetvaluescranky-whippetmariadbDatabase:awesome_stuffmariadbRootPassword:evenbettersecret现在,让我们运行、升级并更改数据库的名称:

>helmupgradecranky-whippet--setmariadbDatabase=awesome_saucestable/mariadb$helmgetvaluescranky-whippetmariadbDatabase:awesome_sauce请注意,我们已经丢失了root密码。当您升级时,所有现有值都将被替换。好的,让我们回滚。helmhistory命令显示了我们可以回滚到的所有可用修订版本:

>helmhistorycranky-whippetREVISIONSTATUSCHARTDESCRIPTION1SUPERSEDEDmariadb-2.1.3Installcomplete2SUPERSEDEDmariadb-2.1.3Upgradecomplete3SUPERSEDEDmariadb-2.1.3Upgradecomplete4DEPLOYEDmariadb-2.1.3Upgradecomplete让我们回滚到修订版本3:

>helmrollbackcranky-whippet3Rollbackwasasuccess!HappyHelming!>helmhistorycranky-whippetREVISIONSTATUSCHARTDESCRIPTION1SUPERSEDEDmariadb-2.1.3Installcomplete2SUPERSEDEDmariadb-2.1.3Upgradecomplete3SUPERSEDEDmariadb-2.1.3Upgradecomplete4SUPERSEDEDmariadb-2.1.3Upgradecomplete5DEPLOYEDmariadb-2.1.3Rollbackto3让我们验证一下我们的更改是否已回滚:

>helmgetvaluescranky-whippetmariadbDatabase:awesome_stuffmariadbRootPassword:evenbettersecret删除发布当然,您也可以使用helmdelete命令删除一个发布。

首先,让我们检查发布的列表。我们只有cranky-whippet:

>helmlistNAMEREVISIONSTATUSCHARTNAMESPACEcranky-whippet5DEPLOYEDmariadb-2.1.3default现在,让我们删除它:

>helmdeletecranky-whippetrelease"cranky-whippet"deleted所以,没有更多的发布了:

>helmlist但是,Helm也会跟踪已删除的发布。您可以使用--all标志查看它们:

>helmlist--allNAMEREVISIONSTATUSCHARTNAMESPACEcranky-whippet5DELETEDmariadb-2.1.3default要完全删除一个发布,添加--purge标志:

>helmdelete--purgecranky-whippet使用存储库Helm将图表存储在简单的HTTP服务器存储库中。任何标准的HTTP服务器都可以托管Helm存储库。在云中,Helm团队验证了AWSS3和GoogleCloud存储都可以在Web启用模式下作为Helm存储库。Helm还附带了一个用于开发人员测试的本地包服务器。它在客户端机器上运行,因此不适合共享。在一个小团队中,您可以在本地网络上的共享机器上运行Helm包服务器,所有团队成员都可以访问。

要使用本地包服务器,请键入helmserve。请在单独的终端窗口中执行此操作,因为它会阻塞。Helm将默认从~/.helm/repository/local开始提供图表服务。您可以将您的图表放在那里,并使用helmindex生成索引文件。

生成的index.yaml文件列出了所有的图表。

请注意,Helm不提供将图表上传到远程存储库的工具,因为这将需要远程服务器了解Helm,知道在哪里放置图表,以及如何更新index.yaml文件。

在客户端方面,helmrepo命令允许您list,add,remove,index和update:

>helmrepo该命令由多个子命令组成,用于与chart存储库交互。

它可以用来add,remove,list和index图表存储库:

$helmrepoadd[NAME][REPO_URL]用法:helmrepo[command]可用命令:addaddachartrepositoryindexgenerateanindexfileforagivenadirectorylistlistchartrepositoriesremoveremoveachartrepositoryupdateupdateinformationonavailablecharts使用Helm管理图表Helm提供了几个命令来管理图表。它可以为您创建一个新的图表:

>helmcreatecool-chartCreatingcool-chartHelm将在cool-chart下创建以下文件和目录:

-rw-r--r--1gigi.sayfangigi.sayfan333BMar1713:36.helmignore-rw-r--r--1gigi.sayfangigi.sayfan88BMar1713:36Chart.yamldrwxr-xr-x2gigi.sayfangigi.sayfan68BMar1713:36chartsdrwxr-xr-x7gigi.sayfangigi.sayfan238BMar1713:36templates-rw-r--r--1gigi.sayfangigi.sayfan1.1KMar1713:36values.yaml编辑图表后,您可以将其打包成一个targzipped存档:

>helmpackagecool-chartHelm将创建一个名为cool-chart-0.1.0.tgz的存档,并将两者存储在local目录和localrepository中。

您还可以使用helm来帮助您找到图表格式或信息的问题:

>helmlintcool-chart==>Lintingcool-chart[INFO]Chart.yaml:iconisrecommended1chart(s)linted,nofailures利用入门包helmcreate命令带有一个可选的--starter标志,让您指定一个入门图表。

启动器是位于$HELM_HOME/starters中的常规图表。作为图表开发者,您可以编写专门用作启动器的图表。这样的图表应该考虑以下几点:

Chart.yaml将被生成器覆盖

用户希望修改这样一个图表的内容,因此文档应该说明用户如何做到这一点

目前,没有办法将图表安装到$HELM_HOME/starters,用户必须手动复制。如果您开发启动包图表,请确保在您的图表文档中提到这一点。

图表是以特定目录树布局的文件创建的。然后,它们可以被打包成版本化的存档进行部署。关键文件是Chart.yaml。

Chart.yaml文件是Helm图表的主文件。它需要一个名称和版本字段:

name:图表的名称(与目录名称相同)

version:SemVer2版本

它还可以包含各种可选字段:

kubeVersion:兼容的Kubernetes版本的SemVer范围

description:该项目的单句描述

keywords:关于这个项目的关键字列表

home:该项目主页的URL

sources:该项目源代码的URL列表

maintainers:

name:维护者的名称(每个维护者都需要)

email:维护者的电子邮件(可选)

url:维护者的URL(可选)

engine:模板引擎的名称(默认为gotpl)

icon:用作图标的SVG或PNG图像的URL

appVersion:包含的应用程序版本

deprecated:这个图表是否已被弃用?(布尔值)

tillerVersion:该图表所需的Tiller版本

Chart.yaml中的版本字段由CLI和Tiller服务器使用。helmpackage命令将使用在Chart.yaml中找到的版本来构建包名。图表包名中的版本号必须与Chart.yaml中的版本号匹配。

appVersion字段与版本字段无关。Helm不使用它,它作为用户的元数据或文档,用于了解他们正在部署的内容。Helm不强制正确性。

有时,您可能希望弃用一个图表。您可以通过将Chart.yaml中的弃用字段设置为true来标记图表为弃用状态。弃用最新版本的图表就足够了。稍后您可以重用图表名称并发布一个未弃用的新版本。kubernetes/charts项目使用的工作流程是:

更新图表的Chart.yaml以标记图表为弃用状态并提升版本

发布图表的新版本

从源代码库中删除图表

图表可能包含各种元数据文件,例如README.md、LICENSE和NOTES.txt,用于描述图表的安装、配置、使用和许可。README.md文件应格式化为markdown。它应提供以下信息:

图表提供的应用程序或服务的描述

运行图表的任何先决条件或要求

values.yaml中选项的描述和默认值

安装或配置图表的任何其他信息

templates/NOTES.txt文件将在安装后或查看发布状态时显示。您应该保持NOTES简洁,并指向README.md以获取详细说明。通常会放置使用说明和下一步操作,例如有关连接到数据库或访问WebUI的信息。

在Helm中,一个图表可以依赖任意数量的其他图表。通过在requirements.yaml文件中列出它们或在安装期间将依赖图表复制到charts/子目录中来明确表示这些依赖关系。

依赖可以是图表存档(foo-1.2.3.tgz)或未解压的图表目录。但是,其名称不能以_或.开头。图表加载程序会忽略这样的文件。

requirements.yaml文件是一个简单的文件,用于列出图表的依赖关系:

version字段是您想要的图表版本。

repository字段是指向图表存储库的完整URL。请注意,您还必须使用helmrepo将该存储库添加到本地。

一旦您有了一个依赖文件,您可以运行helmdepup,它将使用您的依赖文件将所有指定的图表下载到charts子目录中:

charts/foo-1.2.3.tgzbar-4.5.6.tgz使用requirements.yaml管理图表及其依赖项是最佳实践,既可以明确记录依赖关系,也可以在团队之间共享,并支持自动化流程。

requirements.yaml文件中的每个条目还可以包含可选的fields标签和条件。

这些字段可用于动态控制图表的加载(默认情况下,所有图表都会加载)。当存在标签或条件时,Helm将评估它们并确定是否应加载目标图表:

condition:condition字段包含一个或多个YAML路径(用逗号分隔)。如果此路径存在于顶级父级的值中并解析为布尔值,则图表将根据该布尔值启用或禁用。仅评估列表中找到的第一个有效路径,如果没有路径存在,则条件不起作用。

tags:tags字段是一个YAML标签列表,用于与该图表关联。在顶级父级的值中,可以通过指定标签和布尔值来启用或禁用具有标签的所有图表。

以下是一个很好地利用条件和标签来启用和禁用依赖项安装的requirements.yaml和values.yaml示例。requirements.yaml文件根据globalenabled字段的值和特定的sub-chartsenabled字段定义了安装其依赖项的两个条件:

#parentchart/values.yamlsubchart1:enabled:truetags:front-end:falseback-end:true在安装图表时,也可以从命令行设置标签和条件值,并且它们将优先于values.yaml文件:

helminstall--setsubchart2.enabled=false标签和条件的解析如下:

条件(在值中设置)始终会覆盖标签。存在的第一个条件路径获胜,该图表的后续条件将被忽略。

如果图表的任何标签为true,则启用该图表。

标签和条件值必须在顶级父值中设置。

值中的标签:键必须是顶级键。不支持全局和嵌套标签。

任何重要的应用程序都需要配置和适应特定的用例。Helm图表是使用Go模板语言填充占位符的模板。Helm支持来自Sprig库和其他一些专门函数的附加功能。模板文件存储在图表的templates/子目录中。Helm将使用模板引擎渲染此目录中的所有文件,并应用提供的值文件。

模板文件只是遵循Go模板语言规则的文本文件。它们可以生成Kubernetes配置文件。以下是artifactory图表中的服务模板文件:

kind:ServiceapiVersion:v1kind:Servicemetadata:name:{{template"artifactory.fullname".}}labels:app:{{template"artifactory.name".}}chart:{{.Chart.Name}}-{{.Chart.Version}}component:"{{.Values.artifactory.name}}"heritage:{{.Release.Service}}release:{{.Release.Name}}{{-if.Values.artifactory.service.annotations}}annotations:{{toYaml.Values.artifactory.service.annotations|indent4}}{{-end}}spec:type:{{.Values.artifactory.service.type}}ports:-port:{{.Values.artifactory.externalPort}}targetPort:{{.Values.artifactory.internalPort}}protocol:TCPname:{{.Release.Name}}selector:app:{{template"artifactory.name".}}component:"{{.Values.artifactory.name}}"release:{{.Release.Name}}使用管道和函数Helm允许在模板文件中使用内置的Go模板函数、sprig函数和管道的丰富和复杂的语法。以下是一个利用这些功能的示例模板。它使用repeat、quote和upper函数来处理food和drink键,并使用管道将多个函数链接在一起:

apiVersion:v1kind:ConfigMapmetadata:name:{{.Release.Name}}-configmapdata:greeting:"HelloWorld"drink:{{.Values.favorite.drink|repeat3|quote}}food:{{.Values.favorite.food|upper|quote}}查看值文件是否具有以下部分:

favorite:drink:coffeefood:pizza如果是,则生成的图表将如下所示:

apiVersion:v1kind:ConfigMapmetadata:name:cool-app-configmapdata:greeting:"HelloWorld"drink:"coffeecoffeecoffee"food:"PIZZA"嵌入预定义值Helm提供了一些预定义的值,您可以在模板中使用。在先前的artifactory图表模板中,Release.Name,Release.Service,Chart.Name和Chart.Version是Helm预定义值的示例。其他预定义值如下:

Release.Time

Release.Namespace

Release.IsUpgrade

Release.IsInstall

Release.Revision

Chart

Files

Capabilities

图表是Chart.yaml的内容。文件和功能预定义值是类似于映射的对象,允许通过各种函数进行访问。请注意,模板引擎会忽略Chart.yaml中的未知字段,并且无法用于传递任意结构化数据到模板中。

这是artifactory默认值文件的一部分。该文件中的值用于填充多个模板。例如,先前的服务模板中使用了artifactoryname和internalPort的值:

artifactory:name:artifactoryreplicaCount:1image:#repository:"docker.bintray.io/jfrog/artifactory-oss"repository:"docker.bintray.io/jfrog/artifactory-pro"version:5.9.1pullPolicy:IfNotPresentservice:name:artifactorytype:ClusterIPannotations:{}externalPort:8081internalPort:8081persistence:mountPath:"/var/opt/jfrog/artifactory"enabled:trueaccessMode:ReadWriteOncesize:20Gi您可以提供自己的YAML值文件来在安装命令期间覆盖默认值:

global:app:cool-app当全局存在时,它将被复制到每个依赖图表的值中,如下所示:

global:app:cool-apppostgresql:global:app:cool-app...总结在本章中,我们看了一下Helm,Kubernetes的包管理器。Helm使Kubernetes能够管理由许多Kubernetes资源组成的复杂软件,这些资源之间存在相互依赖。它的作用与操作系统的包管理器相同。它组织软件包,让您搜索图表,安装和升级图表,并与合作者共享图表。您可以开发自己的图表并将它们存储在存储库中。

在下一章中,我们将展望Kubernetes的未来,审查其路线图以及我愿望清单中的一些个人项目。

在这一章中,我们将从多个角度探讨Kubernetes的未来。我们将从路线图和即将推出的产品功能开始,包括深入探讨Kubernetes的设计过程。然后,我们将涵盖Kubernetes自诞生以来的动力,包括社区、生态系统和知名度等方面。Kubernetes未来的一个重要部分将取决于它在竞争中的表现。教育也将发挥重要作用,因为容器编排是一个新的、快速发展的、并且不是一个被充分理解的领域。然后,我们将讨论我心中最希望的一个功能——动态插件。

涵盖的主题如下:

前方的道路

竞争

Kubernetes的动力

教育和培训

模块化和树外插件

服务网格和无服务器框架

Kubernetes是一个庞大的开源项目。让我们来看看一些计划中的功能和即将推出的版本,以及专注于特定领域的各种特别兴趣小组。

Kubernetes有相当规律的发布。截至2018年4月,当前版本是1.10。下一个版本1.11目前已经完成33%。以下是1.11版本的一些问题,让你了解正在进行的工作:

更新到Go1.10.1并将默认的etcd服务器更新到3.2

支持树外认证提供者

将kublet标志迁移到kublet.config.k8s.io

添加对Azure标准负载均衡器和公共IP的支持

添加kubectlapi-resources命令

次要版本每3个月发布一次,补丁版本填补漏洞和问题,直到下一个次要版本。以下是最近三个版本的发布日期:

10.0版本于2018年3月26日发布,1.9.6版本于2018年3月21日发布

9.0版本于2017年12月15日发布,1.8.5版本于2017年12月7日发布

8.0和1.7.7版本于2017年9月28日发布(我的生日!)

以下是1.10版本的一些主要主题:

节点

存储

Windows

OpenStack

API机械

认证

Azure

CLI

作为一个大型的开源社区项目,Kubernetes的大部分开发工作都是在多个工作组中进行的。完整的列表在这里:

未来版本的规划主要在这些SIG和工作组内进行,因为Kubernetes太大了,无法集中处理。SIG定期会面并讨论。

《精通Kubernetes》的第一版于2017年5月出版。当时,Kubernetes的竞争格局完全不同。以下是我当时写的内容:

容器编排平台如Kubernetes直接和间接地与更大和更小的范围竞争。例如,Kubernetes可能在特定的云平台上可用,比如AWS,但可能不是默认/首选解决方案。另一方面,Kubernetes是Google云平台上GKE的核心。选择更高级抽象级别的开发人员,比如云平台或甚至PaaS,往往会选择默认解决方案。但一些开发人员或组织担心供应商锁定或需要在多个云平台或混合公共/私有上运行。Kubernetes在这里有很大的优势。捆绑曾经是Kubernetes采用的一个潜在严重威胁,但势头太大了,现在每个主要参与者都直接在他们的平台或解决方案上提供Kubernetes。

Docker目前是容器的事实标准(尽管CoreOSrkt正在崭露头角),人们经常在说容器时指的是Docker。Docker希望在编排领域分一杯羹,并发布了DockerSwarm产品。DockerSwarm的主要优点是它作为Docker安装的一部分,并使用标准的DockerAPI。因此,学习曲线不会那么陡峭,更容易上手。然而,DockerSwarm在功能和成熟度方面远远落后于Kubernetes。此外,当涉及到高质量工程和安全性时,Docker的声誉并不好。关心系统稳定性的组织和开发人员可能会远离DockerSwarm。Docker意识到了这个问题,并正在采取措施加以解决。它发布了企业版,并通过Moby项目重塑了Docker的内部作为一组独立的组件。但是,最近Docker承认了Kubernetes作为容器编排平台的重要地位。Docker现在直接支持Kubernetes和DockerSwarm并存。我猜想DockerSwarm会逐渐消失,只会用于非常小的原型。

Mesosphere是开源ApacheMesos背后的公司,DC/OS产品是在云中运行容器和大数据的现有产品。该技术已经成熟,Mesosphere在不断发展,但他们没有Kubernetes拥有的资源和动力。我相信Mesosphere会做得很好,因为这是一个大市场,但它不会威胁Kubernetes作为头号容器编排解决方案。此外,Mesosphere也意识到他们无法击败Kubernetes,并选择加入它。在DC/OS1.11中,您可以获得Kubernetes即服务。DC/OS提供的是一个高可用、易于设置和默认安全的Kubernetes部署,经过了在Google、AWS和Azure上的测试。

许多组织和开发人员涌向公共云平台,以避免基础设施的低级管理带来的麻烦。这些公司的主要动机通常是快速前进,专注于他们的核心竞争力。因此,他们通常会选择云提供商提供的默认部署解决方案,因为集成是最无缝和流畅的。

Kops的一些特性如下:

自动化在AWS中部署Kubernetes集群

部署高可用的Kubernetes主节点

生成Terraform配置的能力

然而,Kops不是官方的AWS解决方案。如果您通过AWS控制台和API管理基础设施,最简单的方法过去是AWS弹性容器服务(ECS)——这是一个不基于Kubernetes的内置容器编排解决方案。

现在,AWS完全致力于Kubernetes,并正在发布弹性Kubernetes服务(EKS),这是一个完全托管且高可用的上游Kubernetes集群,没有修改,但通过附加组件和插件与AWS服务紧密集成。

Azure提供了Azure容器服务,他们不偏袒。您可以选择是否要使用Kubernetes、DockerSwarm或DC/OS。这很有趣,因为最初,Azure是基于MesosphereDC/OS的,后来他们添加了Kubernetes和DockerSwarm作为编排选项。随着Kubernetes在功能、成熟度和知名度上的提升,我相信它也将成为Azure上的头号编排选项。

在2017年下半年,Azure正式发布了AzureKubernetes服务(AKS),微软完全支持Kubernetes作为容器编排解决方案。它在Kubernetes社区非常活跃,收购了Deis(Helm的开发者),并贡献了许多工具、代码修复和集成。对Kubernetes的Windows支持也在不断改进,与Azure的集成也在不断加强。

阿里云在很多方面都是中国的AWS。他们的API故意设计得非常像AWS的API。阿里云曾经提供基于DockerSwarm的容器管理服务。我在阿里云上部署了一些小规模的应用,他们似乎能够跟上领域的变化并迅速跟随大公司。在过去的一年里,阿里云加入了Kubernetes支持者的行列。在阿里云上部署和管理Kubernetes集群的资源有几个,包括云提供商接口的GitHub实现。

Kubernetes背后有巨大的势头;社区非常强大。随着Kubernetes的知名度增加,用户纷纷涌向Kubernetes,技术媒体承认其领导地位,生态系统火热,许多大公司(除了谷歌)积极支持它,还有许多公司在评估并在生产中运行它。

Kubernetes社区是其最大的资产之一。Kubernetes最近成为第一个从云原生计算基金会(CNCF)毕业的项目。

Kubernetes在GitHub上开发,是GitHub上排名靠前的项目之一。它在星星数量上排在前0.01%,在活跃度上排名第一。请注意,过去一年,Kubernetes变得更加模块化,现在许多部分都是分开开发的。

更多专业人士在他们的LinkedIn档案中列出Kubernetes,比其他类似产品多得多。

一年前,Kubernetes有大约1,100名贡献者和大约34,000次提交。现在,这个数字激增到了超过1,600名贡献者和超过63,000次提交。

Kubernetes势头的另一个指标是会议、聚会和参与者的数量。KubeCon迅速增长,新的Kubernetes聚会每天都在开展。

Kubernetes生态系统非常令人印象深刻,从云提供商到PaaS平台和提供简化环境的初创公司。

OpenShift是RedHat的容器应用产品,构建在开源的OpenShiftorigin之上,后者基于Kubernetes。OpenShift在Kubernetes之上增加了应用程序生命周期管理和DevOps工具,并为Kubernetes做出了很多贡献(如自动扩展)。这种互动非常健康和鼓舞人心。RedHat最近收购了CoreOS,CoreOSTectonic与OpenShift的合并可能会产生很大的协同效应。

OpenStack是开源的私有云平台,最近决定将Kubernetes作为底层编排平台进行标准化。这是一件大事,因为希望在公有云和私有云混合部署的大型企业将会更好地与Kubernetes云联邦和以Kubernetes为私有云平台的OpenStack进行集成。

2017年11月的最新OpenStack调查显示,Kubernetes是迄今为止最受欢迎的容器编排解决方案:

还有许多其他公司将Kubernetes作为基础,比如Rancher和Apprenda。大量初创公司开发了运行在Kubernetes集群内部的附加组件和服务。未来是光明的。

教育将至关重要。随着Kubernetes的早期采用者向大多数人过渡,为组织和开发人员提供正确的资源快速掌握Kubernetes并提高生产力非常重要。已经有一些非常好的资源,未来我预计数量和质量将会增加。当然,你现在正在阅读的这本书也是这一努力的一部分。

官方的Kubernetes文档越来越好。在线教程非常适合入门:

此外,还有许多付费的Kubernetes培训选项。随着Kubernetes的流行程度进一步增长,将会有越来越多的选择。

自第一版以来,Kubernetes在模块化方面取得了巨大进步。Kubernetes一直是灵活和可扩展的典范。然而,最初您必须将代码构建并链接到KubernetesAPI服务器或Kublet(除了CNI插件)。您还必须获得代码的审查并将其集成到主要的Kubernetes代码库中,以使其对其他开发人员可用。当时,我对Go1.8动态插件以及它们如何以更加灵活的方式扩展Kubernetes感到非常兴奋。Kubernetes的开发人员和社区选择了不同的路径,并决定使Kubernetes成为一个通用和多功能的引擎,几乎可以通过标准接口从外部定制或扩展每个方面。您在第十二章中看到了许多示例,自定义Kubernetes-API和插件。外部插件的方法意味着您将插件或扩展集成到GitHub上Kubernetes代码树之外的Kubernetes中。有几种正在使用的机制:

CNI插件使用标准输入和输出通过单独的可执行文件

CSI插件使用PodsgRPC

Kubectl插件使用YAML描述符和二进制命令

API聚合器使用自定义API服务器

Webhooks使用远程HTTP接口

各种其他插件可以部署为Pod

外部凭证提供者

Kubernetes在容器编排和成本降低方面帮助了很多繁重的工作。但是,在云原生世界中有两个趋势正在蓬勃发展。服务网格与Kubernetes完美契合,运行无服务器框架也发挥了Kubernetes的优势。

服务网格在容器编排的层次上操作。服务网格管理服务。服务网格在运行具有数百或数千个不同服务的系统时提供了各种必要的能力。

动态路由

感知延迟的东西向负载平衡(集群内部)

自动重试幂等请求

运营指标

过去,应用程序必须在核心功能之上解决这些责任。现在,服务网格减轻了负担,并提供了一个基础架构层,使应用程序可以专注于它们的主要目标。

最著名的服务网格是Buoyant的Linkered。Linkered支持Kubernetes以及其他编排器。但是,鉴于Kubernetes的势头。

Buoyant决定开发一个名为Conduit(用Rust编写)的新的仅限于Kubernetes的服务网格。这是对Kubernetes受欢迎程度的又一个证明,所有的创新都在这里发生。另一个Kubernetes服务网格是Istio。Istio由来自Google、IBM和Lyft的团队创建。它是建立在Lyft的Envoy之上,并且发展迅速。

无服务器计算是云原生领域的一个令人兴奋的新趋势。AWSLambda函数是最受欢迎的,但现在所有云平台都提供它们。其思想是你不必预留硬件、实例和存储。相反,你只需编写你的代码,打包它(通常在一个容器中),并在需要时调用它。云平台在调用时负责分配资源来运行你的代码,并在代码运行结束时释放资源。这可以节省大量成本(你只支付你使用的资源)并消除了预留和管理基础设施的需求。然而,云提供商提供的无服务器能力通常带有附加条件(运行时和内存限制),或者它们不够灵活(无法控制你的代码将在哪种硬件上运行)。一旦你的集群被预留,Kubernetes也可以提供无服务器能力。有多个不同成熟度水平的框架可用,如下所示:

Fast-netes

Nuclio.io

ApacheOpenWhisk

Platform9Fission

Kubless.io

对于在裸金属上运行Kubernetes或需要比云平台提供的更灵活性的人来说,这是个好消息。

在本章中,我们展望了Kubernetes的未来,前景看好!技术基础、社区、广泛支持和势头都非常令人印象深刻。Kubernetes仍然年轻,但创新和稳定化的速度非常令人鼓舞。Kubernetes的模块化和可扩展性原则使其成为现代云原生应用程序的通用基础。

到目前为止,您应该清楚地了解Kubernetes目前的状况以及未来的发展方向。您应该相信Kubernetes不仅会留下来,而且将成为未来许多年的主要容器编排平台,并将与更大的产品和环境集成。

现在,轮到您利用所学知识,用Kubernetes构建令人惊奇的东西了!

THE END
1.你会在自己的宠物死后,选择再克隆一个它吗克隆宠物陪伴男子叫宠物,怎料压根看不出头,抬头后知道了! 爆笑敦敦 344跟贴 猫妈妈紧紧抱住路人小哥的腿,向好心人类求救,母爱真的伟大 可乐爱动物 87跟贴 无毛猫发疯攻击主人,一家人每天全副武装,一不小心就会被猫抓伤 大马猴趣奇 909跟贴 这只猫真聪明,只跳自家的车,傻猫的日常 爆笑论 1229跟贴 免费领养品种猫的真...https://m.163.com/v/video/VOFIQI56S.html
2.动物医学研究生院校排行研究生资讯–加倍考研网副标题:动物医学专业非官方指南,以一个“过来人”的经历解析个中利弊最近高考成绩陆续出炉,填志愿选专业成了当下热门话题,选专业需要参考很多情况,但如果你平时对小动物比较感兴趣,可以考虑一下“动物医学”,未来或许可以做一名宠物医生,今天,新手养狗大全就来结合自身经历和经验,给大家讲讲动物医学那些事儿!一、到底...https://www.kaoyankaoyan.com/zixun/dongwuyixueyanjiushengyuanxiao.html
3.要爱因斯坦还是希特勒?克隆要遵循造福人类原则有理有据:克隆人是人类在自掘坟墓 随着科学技术飞速发展,人类已不仅仅满足于对于尖端科技的研究,而转向对于自身基因的研究。随之便出现了克隆技术。多利羊、克隆宠物、乃至克隆人的出现,将人类的命运彻底改变了,而对于克隆人的出现,许多专家的意见并非一致,多利羊虽然有许多优点,可是其弊端却是致命的,就是很难适应周...https://news.sina.com.cn/w/2003-01-30/0057892758.shtml
4.2023年12月浙江省文物鉴定站选聘人员笔试近6年高频考题难易错点...2023年12月浙江省文物鉴定站选聘人员笔试近6年高频考题难、易错点荟萃答案带详解附后.docx,2023年12月浙江省文物鉴定站选聘人员笔试近6年高频考题难、易错点荟萃答案带详解附后 (图片大小可任意调节) 第1卷 一.参考题库(共420题) 1.两个人带着宠物狗玩游戏,两人相距200米https://max.book118.com/html/2024/0320/7131135064006054.shtm
5.鱿鱼科普宠物界的4个”奇葩“,主人奉为宝贝粉丝数1596974简介:鱿鱼科普上传的生活视频:宠物界的4个”奇葩“,主人奉为宝贝,粉丝数1596974,作品数3365,点赞数152,评论数1,免费在线观看,视频简介:盘点世界各地的奇闻怪事 UP主简介 鱿鱼科普 粉丝数:1596974 作品数:3365 泡泡活动 都是你不知道的“怪事情”生活在线观看 都是你不知道的“怪事情”生活完整版在线观看 都是你...http://www.qiyi.com/v_19rux3eb8g.html
6.宠物克隆技术遭热议,但其中的隐患不能不提...24小时宠物克隆技术遭热议,但其中的隐患不能不提...8月21日,国内第一只克隆猫“大蒜”迎来“满月”,8月22日,我国首只警用工作克隆犬“昆勋”正式入警。其实美国早在2002年就有克隆猫问世,韩国2005年开始克隆狗就进入商用,十多年来关于克隆宠物的争议一直没有停止过……(via 界面Vnews) Play % buffered00:00 00...https://www.huxiu.com/moment/65900.html
7.警惕以克隆技术“复活”宠物为名实施的诈骗安全防范案例2:2019年3月12日,南京市民秦女士想要复活自家的爱狗“球球”,看到有克隆宠物的新闻后,便在网络上找到一家生物科技公司称可以进行该项服务,秦女士向对方寄送了自己狗狗的毛发和3000元预付款,之后对方告知复活成功寄回一只狗狗,秦女士又向其打款2万元,之后,秦女士觉得该狗狗与“球球”并不一样要求对方退款,遭对...https://gaj.nanjing.gov.cn/aqff/201904/t20190408_1501603.html
8.克隆去世宠物不是真爱对于某些养宠人士来说,只要自己的精神有所寄托,高昂的克隆价格不算什么。被克隆宠物虽然外观上与克隆宠物基本相同,性格上也比较相似,但是毕竟这个世界上没有完全一样的东西,被克隆宠物其实与其他的宠物并没有什么区别。养宠人士以为被克隆宠物可以完全代替克隆宠物,但是克隆宠物具有唯一性和不可替代性,所以被克隆宠物不...https://m.dezhoudaily.com/pinglun/p/1606574.html
1.不少年轻人开始养它当宠物!对身体有害吗……澎湃号·政务也成为了年轻群体中的宠物 但最近 不少的年轻人流行起养黏菌 在社交平台上晒出自己养的黏菌 但也有网友发出了疑问 黏菌是什么 黏菌会对健康造成危害吗 “养”黏菌为什么会流行起来? 养黏菌之所以流行开来,是因为相对于养宠物来说,养黏菌上手简单,又能满足年轻人的探索欲。日常饲养只需准备一个培养皿,可投喂淀粉...https://www.thepaper.cn/newsDetail_forward_29276894
2.单克鹿体药物市场前景预测与国际化发展战略研究报告...单克隆抗体药物企业国际化战略研究报告 1 内容目录 一、前言 ...6 二、2023-2028 年单克隆抗体药物市场前景及趋势预测 ...https://doc.mbalib.com/view/b008b6de5ab4f0abf2fb47b9aa8921f8.html
3.RealPython中文系列教程(三)相对进口的利弊 相对导入的一个明显优势是它们非常简洁。根据当前的位置,他们可以将您之前看到的长得离谱的导入语句变成像这样简单的语句: from..subpackage4.module5importfunction6 1 不幸的是,相对导入可能会很混乱,特别是对于目录结构可能会改变的共享项目。相对导入也不像绝对导入那样可读,并且不容易判断导入资源的...https://blog.csdn.net/wizardforcel/article/details/143669984
4.克隆宠物犯法吗?喵百科克隆宠物不犯法。不少人担心克隆项目是否合法,事实上,目前国内无法律规定说明克隆违法,更多是人们对于...https://moecats.com/question/33320.html
5.世界首例宠物狗引关注韩国的宠物克隆大战实验动物尽管韩国的实验室是当今世界上唯一将克隆宠物用于商业用途的,他们可能会发现这个羽翼未丰的行业并不完全呈增长态势。 很多人对于克隆宠物伦理道德问题的关注超过对商业价值的关注,并且克隆一只宠物狗的费用高达大约150,000美元,服务过于昂贵,目前大多数爱犬者无法接受,价格下降到 50000美元可能更有利于技术开发。但是现在克...https://www.lascn.com/Item/975.aspx