Kubernetes实战:构建生产级应用平台
上QQ阅读APP看书,第一时间看更新

1.3 基于Kubernetes构建应用平台

现在我们已经知道了Kubernetes是构建生产应用平台的一块拼图。这时,我们可能会思考:Kubernetes是不是缺少了一些东西?由于我们对Kubernetes项目有一个美好的愿景,就像UNIX哲学所述,“让每个程序只做好一件事”,所以我们相信Kubernetes最好的功能是它所没有的,特别是在被那些试图解决世界所有问题的“银弹”伤害过之后。Kubernetes专注于成为一个完美的调度者,同时为如何基于它之上构建应用平台定义了清晰的蓝图,就像房屋的地基一样。

一个好的地基应该具有结构良好、可建设的特点,并提供适当的预留位用于后期接入公用设施。虽然地基很重要,但仅有它无法成为我们的家。通常情况下,我们需要基于这个地基来建设属于自己的家。接下来,在讨论如何基于Kubernetes进行建设之前,让我们先看一下图1-6所示的公寓。

图1-6:一套拎包即住的公寓。这类似于Heroku等平台(插图:Jessica Appelbaum)

如图1-6所示,Heroku等相当于这种拎包即住的公寓,我们不需要额外的建设就可以居住,也有机会定制里面的功能。这样一来,我们的许多担忧就解决了。只要我们对租金感到满意,并愿意严格遵守内部规定。

回到Kubernetes。之前我们把它比作一个地基,现在我们可以基于它建造一个适合居住的家,如图1-7所示。

图1-7:搭建一个房子。这类似于建立一个应用平台,Kubernetes就是这个平台的地基(插图:Jessica Appelbaum)

通过设计、开发和维护,我们可以建立一个非常棒的应用平台来运行整个组织的工作负载。这意味着我们可以完全控制输出中的每个元素。这个“房子”可以根据未来“租户”(应用程序)的需求来定制。现在让我们来分析一下所需要的各种层面和因素。

1.3.1 从基础开始

首先,我们必须从基础开始,包括Kubernetes的运行环境。通常该环境是一个数据中心或云服务商,它提供计算、存储和网络。一旦建立,Kubernetes就可以在上面运行。几分钟之内,你就可以让集群运行在底层基础设施之上。有几种Kubernetes启动的方法,我们将在第2章中深入介绍。

从Kubernetes集群的角度来看,我们接下来需要介绍一个概念性的流程以确定应该在上面建立什么。图1-8展示了关键的节点。

从Kubernetes的角度来看,你很快就会发现这样的问题:

•如何确保工作负载之间的流量通信是完全加密的?

•如何确保出口流量通过一个网关,保证源CIDR的一致性?

•如何向应用程序提供监控服务和管理面板?

•如何让开发人员不担心他们需要成为Kubernetes专家才能上岗?

图1-8:我们的团队在使用Kubernetes进行生产构建过程中可能经历的流程

这个清单可能是无穷无尽的。我们往往需要判断哪些需求需要在平台层解决,哪些需要在应用层解决。其中最重要的是需要对现有的工作流程有深刻的了解,来确保我们建立的东西与期望保持一致,并且了解如果我们不能满足这些功能,会对开发团队产生什么影响。接下来,我们才可以开始在Kubernetes之上构建一个平台。在平台构建的初始阶段,我们需要尽早与开发团队建立联系,随时了解出现的情况,并根据反馈做出明智的决策。在进入生产阶段后,这一流程也不应该停止。平台团队不应期望所交付给开发团队使用十几年的东西是一个静态环境。为了获得成功,我们必须不断地与我们的开发小组保持信息同步,以随时了解哪里有问题或潜在的功能缺失,从而提高开发速度。我们应该根据所期望的开发人员与Kubernetes的交互程度来确定应该对哪些部分抽象化。

1.3.2 抽象频谱

在过去几年里,我们听到过类似于“如果你的应用程序开发人员知道他们在使用Kubernetes,那么你就很失败”的评论。这可能是对Kubernetes交互理念的一种合理看法,特别是当你正在构建产品或服务时,底层的调度技术对终端用户来说毫无意义,或者你正在构建一个支持多种数据库技术的数据库管理系统(DBMS),而数据库的分片或实例是否通过Kubernetes、Bosh或Mesos运行对你的开发人员来说并不重要。然而,把这种哲学思想从推特上全盘搬到你的团队,并使其成为团队成功的标准是一件非常危险的事情。当我们在Kubernetes上面分层并建立应用平台用以更好地服务客户时,我们将面对许多决策点,以确定适当的抽象是什么样子。图1-9提供了一个可视化的抽象频谱。

图1-9:该频谱的两端从给每个团队提供自己的Kubernetes集群开始,到通过平台即服务(PaaS)产品将Kubernetes完全从用户那里抽象出来

这可能是一个让平台构建团队夜不能寐的问题。抽象化有很多优点。像Cloud Foundry这样的项目提供了一个完整版的平台,例如在一个cf push的背景下,我们可以对一个应用程序进行构建、部署并让它为现实用户服务。以这个目标和体验为主要焦点,随着Cloud Foundry将要支持Kubernetes,我们更希望看到这种转变是丰富其细节,而不是功能集的变化。另一种模式是公司希望提供比Kubernetes更多的技术,但不希望让开发人员在技术之间进行选择。例如,一些公司在Kubernetes的基础上又有了Mesos的足迹。然后,它们进行了抽象,让平台能够自动选择工作负载的落脚点,而不把这个责任放在应用开发人员身上。这也防止了它们局限于某种技术。这种方法在两个运行方式不同的系统之上建立抽象,需要相当大的工程量和技术成熟度。此外,虽然这减轻了开发人员了解如何与Kubernetes或Mesos交互方面的负担,但他们需要了解如何使用一个被公司抽象化的系统。在开源时代,开发人员对学习那些不能适用于各个公司的系统并不那么感兴趣。如果团队过度沉浸于抽象化平台,将会导致无法暴露Kubernetes关键功能的问题。随着时间的推移和项目的不断发展,这种不断的抽象化可能会使平台变成“猫抓老鼠的游戏”,变得越来越复杂。

另一种解决方案是平台构建团队给开发团队提供一个自助的集群。这也是一个很好的模式。它确实将Kubernetes的责任放在了开发人员身上。他们是否了解Deployment、ReplicaSet、Pod、Service和Ingress API是如何工作的?他们是否知道如何设置CPU资源(millicpus),以及如何超量分配资源?他们是否知道如何确保配置了多个副本的工作负载总是被安排在不同的节点上?如果是的话,这是一个避免过度设计应用平台的绝佳机会,从而让应用团队来接手Kubernetes层。

这种拥有自助的集群的开发团队不太常见。即使有一个具有Kubernetes背景的团队,团队也不太可能想从功能开发中抽出时间来管理Kubernetes集群的生命周期。Kubernetes的所有功能都很强大,但对于许多开发团队来说,期望团队成员在开发功能的基础上成为Kubernetes专家是不现实的。正如你在接下来的章节中将看到的,抽象不一定是一个二选一的决定。我们将会根据合理的判断来对不同的部分进行抽象化,并且确定哪里可以在为开发者提供适量灵活性的同时还可以简化他们完成工作的流程。

1.3.3 选择平台服务

当基于Kubernetes构建平台时,我们需要考虑的重点是:相比在应用层面解决问题,哪些功能可以被直接集成到应用平台上。一般来说,这些事情应该被逐步评估。例如假设每个Java微服务都实现了一个库,用来使得每个服务变成平台内的一个工作负载,并利用mTLS(相互传输层安全)协议进行加密通信。作为一个平台团队,我们需要深入了解这种用法,以确定它是否是我们应该在应用平台层面提供或实现的东西。许多团队希望通过在集群部署一种叫作服务网格(service mesh)的技术来解决这个问题。在部署之前,我们需要考虑如下几个问题。

引入服务网格的优点:

•Java应用不再需要打包库来使用mTLS。

•非Java应用可以参与到同一个mTLS或加密系统中。

•减少了应用团队开发的复杂性。

引入服务网格的缺点:

•运行一个服务网格不是一件简单的事情,因为它是另一套具有操作复杂性的分布式系统。

•服务网格经常引入一些除身份和加密之外的功能。

•网格的身份验证API可能出现与现有应用所使用的后台系统集成不兼容的问题。

权衡这些利弊,我们就可以得知在平台层解决这个问题是否值得。关键是我们不需要,也不应该努力地在平台上解决每一个应用问题。这是你在阅读本书时需要做的一种权衡。我们将分享一些建议、最佳实践和指导,但就像我们说的一样,你应该根据你的业务需求的优先次序来评估每一项功能。

1.3.4 构建模块

在本章的最后,让我们来选择在构建平台时可以使用的核心组件,包括从基础组件到你可能希望实现其他功能的扩展组件。

图1-10中的组件对不同的受众有不同的重要性。

有些组件(如容器网络和容器运行时)是每个集群都需要的,因为一个不能运行工作负载或相互通信的Kubernetes集群是无效的。此外,你可能会发现有些组件可能不需要使用。例如,应用程序已经从外部秘密管理解决方案中获得秘密,那么秘密管理可能是你不需要的平台服务。

在图1-10中明显缺失了一些领域,如安全。这是因为安全不是一项功能,而是你如何基于IaaS层向上实现的结果。我们将在探讨高层次时更加深入地探讨这些领域。

图1-10:建立一个应用平台所涉及的核心组件

IaaS/数据中心和Kubernetes

IaaS/数据中心和Kubernetes构成了我们在本章中多次提到的基础层。虽然在现代环境中,我们花在决定各种部署选型和顶层逻辑上的时间要远超花在Kubernetes架构上的时间。但是我们无意忽略这一层,因为它的稳定性将直接关系到我们平台的稳定性。我们需要评估如何配置和提供可用的Kubernetes集群。

容器运行时

容器运行时将有助于我们在每台主机上对工作负载进行生命周期管理。这通常是使用一种能够管理容器的技术来实现的,例如CRI-O、containerd和Docker。要感谢容器运行时接口使得我们能够在这些不同的实现方式之间进行选择。除了这些常见的实现,还有一些专门的运行时支持一些特别的需求,例如希望在微型虚拟机中运行一个工作负载。

容器网络

我们选择容器网络通常是为了解决工作负载的IP地址管理(IPAM)和路由协议问题,并基于该网络实现相互通信。常见的技术选择包括Calico或Cilium。这要感谢容器网络接口,通过将容器网络插件插入集群,kubelet可以为其启动的工作负载请求IP地址。有些插件甚至在Pod网络的基础上实现服务抽象。

存储集成

存储集成涵盖了当主机磁盘存储不能满足需要时我们所做的事情。在现代Kubernetes中,越来越多的组织正在向其集群部署有状态的工作负载。这些工作负载需要在服务出现问题或被重新调度时也可以保持一致的状态和稳定性,存储可以由vSAN、EBS、Ceph等常用系统提供。容器存储接口增加了在各种后端服务之间的可选性。与CNI和CRI类似,我们能够在我们的集群中部署一个插件,该插件能够满足应用程序所要求的存储需求。

服务路由

服务路由将对我们Kubernetes中工作负载之间的流量进行转发。Kubernetes提供了一个支持丰富路由功能的Service API。服务路由建立在容器网络的基础上,创造了更高层次的功能,如第7层路由、流量模式等。很多时候,这些都是通过一种叫作Ingress控制器的技术实现的。在服务路由的深层,有各种服务网格。这项技术具有完整的功能,如服务到服务的mTLS、可观测性,以及对应用层面(如熔断器等)的支持。

秘密管理

秘密管理涵盖了工作负载所需敏感数据的管理和分配。Kubernetes提供了一个开箱即用的Secrets API,在这里可以与敏感数据进行交互。但许多集群没有一些企业所要求的强大的秘密管理和加密能力,这在很大程度上是一个深度防御的问题。在简单的层面上,我们可以确保数据在被存储之前就被加密(静态加密)。在更高级的层面上,我们可以提供与各种专注于秘密管理的技术(如Vault或Cyberark)的整合。

身份认证

身份认证包括用户和工作负载的认证。集群管理员最初的一个共同问题是如何根据系统(如LDAP或云提供商的IAM系统)认证用户。除了用户之外,工作负载可能希望识别自己,以支持零信任(zero-trust)网络模型,在这种模型中,可以通过整合一个验权服务和使用诸如mTLS等机制来验证工作负载,从而使得工作负载被欺骗的可能性大大减小。

权限控制

权限控制是我们对用户或工作负载身份验证通过后的下一步。当用户或工作负载与API服务器交互时,我们如何同意或拒绝他们访问资源?Kubernetes提供了RBAC功能,包括资源与动词级别的控制。但是在我们的组织内部,针对授权的自定义逻辑是什么呢?权限控制可以通过建立验证逻辑来实现,这个逻辑可以是查看静态的规则列表,也可以是动态地调用其他系统来确定正确的授权响应。

软件供应链

软件供应链涵盖了从源代码到运行时的整个软件生命周期。这涉及持续集成(CI)和持续交付(CD)。很多时候,开发人员的主要互动点是他们在这些系统中建立的管道。让CI/CD系统与Kubernetes很好地协同工作,对平台的成功至关重要。除了CI/CD之外,还需要关注项目文件包的存储、安全性以及确保集群中所运行的镜像的完整性。

可观测性

可观测性是所有帮助我们理解集群状态的特征的总称。它涉及系统层和应用层。通常情况下,我们认为可观测性包括三个关键领域:日志、指标和追踪。日志记录通常涉及将主机上工作负载的日志数据转发到目标后端系统。从这个系统中,我们以一种可总结的方式来收集和分析日志。指标涉及抓取某个时间点上的某种状态的数据。我们经常将这些数据汇总,或传输到一些系统中进行分析。追踪的流行在很大程度上是因为需要了解构成应用堆栈的各种服务之间的相互作用。随着追踪数据的收集,它可以被带到一个聚合系统中,通过某种形式的上下文或相关ID来显示一个请求或响应的生命周期。

开发者抽象

开发者抽象是我们为简化开发者在我们的平台上的学习成本而设置的工具和平台服务。正如前面所讨论的,抽象的程度需要我们依靠情况来抉择。一些组织将Kubernetes的使用对开发团队完全隔离。也有一些组织会选择公开Kubernetes提供的许多强大功能,并赋予每个开发团队极大的灵活性。其解决方案也更倾向于关注开发人员的经验,确保他们有能力对平台环境进行访问和安全控制。