本文最初发表于eficode的博客站点,经原作者Michael Vittrup Larsen和 eficode 官方授权,由 InfoQ 中文站翻译分享。
Kubernetes 在容器编排中无处不在,其受欢迎的程度依然没有减弱。但是,这并不意味着容器编排领域的演进处于停滞状态。本文将会提出一些观点,那就是为什么 Kubernetes 的用户,尤其是开发人员,应该超越我们过去几年里学习的传统 Kubernetes,转而采用更适合云原生应用的范式。
Kubernetes变得如此流行的原因之一就是它构建在 Docker 之上。在 Linux 和 BSD 变种中,容器有着很悠久的历史,然而,Docker 通过专注用户体验,使容器的构建和运行变得非常容易,从而使容器变得流行了起来。Kubernetes 建立在容器流行的基础之上,使得在计算机节点组成的集群上运行(又叫编排)容器变得非常容易。
Kubernetes 流行和广泛采用的另外一个原因是它并没有过多改变软件运行的模式。从 Kubernetes 出现之前运行软件的方式到基于 Kubernetes 运行软件之间,设想一条发展路径是非常容易的。
构建容器镜像以冻结依赖,提供“到处可运行”的体验,再结合 Kubernetes Deployment资源规范来管理容器副本的编排,这一套实践的功能是非常强大的。但是,它与我们在 Docker 和 Kubernetes 出现之前,操作虚拟机的方式并没有根本性的差异。这个很小的思维转变使我们很容易就能使用 Kubernetes,这也是为何我们应该超越目前“传统”Kubernetes 的原因。
本文将会从开发人员的角度展望 Kubernetes 的未来。概括来讲,我们现在熟知的 Kubernetes 将会消失,而开发人员并不会在意。这并不是说,Kubernetes 不会出现在我们的技术栈中,而是我们会使用新的抽象来改善构建和运维应用的方式,这些新的抽象本身就是构建在 Kubernetes 之上的。应用会使用平台来构建,平台则基于 Kubernetes 来构建:
有意思的是,在十多年以前,Linux 是我们构建一切的平台。Linux 依然无处不在,是我们技术栈的一部分,但是很少有开发人员关注它,因为我们基于它添加了一些抽象。我们今天所熟知的传统 Kubernetes 也会面临这样的情况。
Kubernetes 提供了Secret资源来声明静态的 secret,比如 API 秘钥、密码等。开发人员不应该再使用 Secret 资源了。
在 Secret 资源中,简单编码的 secret 可能会被泄露,而且密码的轮换和撤销也很困难。在 GitOps 工作流中,secret 也需要特别注意,避免明文存储。应用应该使用基于角色的方式来进行认证和授权。这意味着,应用程序的认证和授权应该基于“我们是谁”,而不是“你知道什么(密码、API 秘钥)”来进行。
强大的身份标识是所有安全性的基础。如果你不确定与你通信的服务器的身份,那么对网络流量进行加密是没有意义的。这就是证书和证书授权机构对 HTTPS 流量所发挥的作用,它保证了互联网的安全。
Kubernetes 有一个强大的工作负载身份系统。所有工作负载都与服务账户(service account)相关联,它们拥有Kubernetes颁发的短暂有效的OpenID-Connect(OIDC)身份token。Kubernetes API 服务器签发这些 OIDC token,而其他工作负载可以通过 Kubernetes API 服务器验证 token。这为在 Kubernetes 上运行的工作负载提供了强大的身份识别功能,可以作为基于角色的认证和授权的基础。
开发人员不应再使用 Kubernetes Secret,而应基于 OIDC token 构建认证和授权。这意味着,我们不应在 Secret 资源中存储数据库密码,而应该确保我们的数据库只在收到有效的、未过期的 token 时才接受请求。
使用 OIDC token 与外部系统集成的例子是AWS IAM用于服务账户的角色和Hashicorp Vault Kubernetes auth。
Kubernetes 提供了一个Ingress资源,以指定如何将 HTTP 流量路由到工作负载中。正如 Tim Hockin(Kubernetes 联合创始人)所承认的那样,Ingress资源有很多问题。主要的问题是,它只允许我们管理 HTTP 流量路由中最基本的东西。对于基础设施和网站可靠性工程(SRE)团队来说,允许开发人员使用 Ingress 资源将是一个令人头疼的问题,他们需要将大量的基础设施互连,并确保它能可靠地运行。Ingress 资源太简单了,开发人员不应该用它来配置网络。
从服务网格的兴起中,我们可以看到业界对 Kubernetes 网络有着更多控制和可编程性的需求。它们将 Ingress 资源划分为多个资源,以便于更好地分离职责,并在路由、可观测性、安全性和容错方面提供额外的功能。
越来越多建立在 Kubernetes 之上的抽象都假设有一个可编程的网络,这超出了 Ingress 所能提供的可能性(如 Knative、Kubeflow,以及像 Argo Rollouts 这样的持续部署工具)。这凸显了在 Kubernetes 中更强大的网络模型已经是一个事实标准。
Kubernetes 已经演进出了“Ingress v2” 网关-API。虽然这解决了 Ingress 的一些问题,但是它只涵盖了大部分服务网格所能支持的一小部分特性。
Kubernetes 支持 ACL,用于限制哪些工作负载可以通过NetworkPolicy资源进行通信。该资源在 Kubernetes 的网络插件中实现,通常会转化为 Linux 的 iptables 过滤规则,即基于 IP 地址的解决方案,很像防火墙,这也是一种古老的范式。一些服务网格扩展了 Kubernetes 强大的基于 OIDC 的工作负载身份标识,以实现工作负载之间的双向 TLS。这基于比 IP 地址更强大的原则,为 Kubernetes 网络通信带来了保密性和真实性。
在 Kubernetes 应用打包时,在如何包含网络配置方面存在一些分歧。许多 Helm charts 都带有 Ingress 资源模板。然而,随着我们转向更高级的网络模型,这些定义将不能再使用。展望未来,像 Helm charts 这样的应用部署应该把网络配置看作是一个正交性的问题,不应该包含在应用部署制品(artifact)中。关于应用的网络配置,可能没有一个放之四海而皆准的解决方案,组织很可能希望开发自己的“应用路由”部署制品。
Kubernetes 通过在集群中的所有节点上创建一个同质(homogeneous)的网络,使网络变得更加简单。如果你的应用是多集群或多云部署的,它可能同样受益于跨集群或云的同质网络。Kubernetes 的网络模型并不能做到这一点,我们需要一些能力更强的方案,比如服务网格。
因此,从组织和架构的角度来看,有多个原因可以证明开发者不应该用 Ingress 资源对网络进行编程。必须以整体的组织视角来考虑这些方案,以确保以可管理和长期可行的方式进行网络配置和管理。
实际上,所有 Kubernetes 应用的核心都是Deployment资源。Deployment 资源定义了我们的工作负载应该如何以 Pod 内容器的形式来执行。
Deployment 的扩展可以通过HorizontalPodAutoscaler(HPA)资源来控制,以适应不同的容量需求。HPA 通常使用容器的 CPU 负载作为增加或删除 Pod 的标准。由于 HPA 算法通常的目标利用率在 70%左右,这意味着我们在设计时要浪费 30%的资源。使用保守的目标利用率的另一个原因是,HPA 经常需要一分钟或更长的响应时间才能开始工作。为了处理不同的容量需求,我们需要一些备用容量,与此同时 HPA 会增加更多的 Pod。
如果我们的应用会经历缓慢变化的容量需求,用 Deployments 和 HPA 管理工作负载效果很好。然而,随着向微服务、事件驱动架构和函数(处理一个或多个事件/请求,然后终止)转变,这种形式的工作负载管理就不够理想了。
Kubernetes Event-Driven Autocaler(KEDA)可以改善微服务和快速变化的工作负载(如函数)的扩展行为。KEDA 定义了一套自己的 Kubernetes 资源来定义扩展行为,可以视为“HPA v3”(因为 HPA 资源已经是“v2”版本了)。
有一个结合了 Kubernetes Deployment 模型、扩展以及事件和网络路由的框架,即Knative。 Knative 是一个建立在 Kubernetes 之上的平台,通过Knative-Service资源对工作负载进行有针对性的管理。Knative 的核心是CloudEvents,Knative 服务基本上是由 CloudEvents 或普通 HTTP 请求等事件触发和扩展的函数。Knative 使用 Pod sidecar 来监控事件发生率,因此在事件发生率变化时可以快速扩展。Knative 还支持扩展到零(scaling to zero),因此允许更细粒度的工作负载扩展,更适合于微服务和函数。
Knative 服务使用了传统的 Kubernetes Deployment/Service,Knative 服务的更新(例如,一个新的容器镜像)会创建并行的 Kubernetes Deployment/Service 资源。Knative 利用这一点来实现蓝/绿和金丝雀部署模式,HTTP 流量的路由是 Knative 服务资源定义的一部分。
因此,Knative 服务资源及其定义事件路由的相关资源将成为开发者在 Kubernetes 上定义应用部署时所使用的主要资源。 就像我们今天经常通过 Deployment 资源与 Kubernetes 互动,让 Kubernetes 处理 Pod 一样,使用 Knative 意味着开发人员将主要关注 Knative 服务,而 Deployment 则由 Knative 平台处理。
虽然我希望 Knative 模型能适合大多数的使用场景,但你的场景可能会有所不同。如果你是做机器学习的,那么Kubeflow可能是更好的抽象。如果你更专注于 DevOps 和交付流水线,那么kpack、Tekton或Cartographer可能是适合你的抽象形式。无论你在 Kubernetes 上做什么,都有相应的抽象。
Kubernetes 提供了PersistentVolume和PersistentVolumeClaim资源来管理工作负载的存储问题。这可能是我最不喜欢的资源了,除了短暂的缓存数据外,它允许开发人员将其用于任何目的。
从高层次的角度来看,PersistentVolume(PV)的问题在于,它将应用程序的主要关注点与存储问题结合在了一起,这不是一个理想的云原生设计模式。12-factors应用方法论告诉我们要将所有支撑服务视为网络附加资源。这要归因于我们在 Kubernetes 中水平扩展工作负载和管理数据的方式(请想一下CAP理论)。
PV 代表了文件和目录的文件系统,我们用 POSIX 文件系统接口对数据进行操作。访问权限也是基于 POSIX 模型的,允许通过用户和组进行读/写访问控制。这种模式不仅与云原生应用的设计不匹配,而且在实际使用中也有很多的问题,这意味着大多数情况下,PV 是以“容器可以访问所有数据”的模式 mount 的。
开发人员构建的有状态应用其实应该是无状态的。这意味着数据应该在应用外部进行处理,使用除文件系统外的其他抽象形式,如数据库或对象存储。数据库和对象存储应用可以使用 PV 来满足其存储需求,但这些系统应该由基础设施/SRE 团队来管理,并由开发人员以服务的形式进行消费。
当我们将存储视为网络附加资源时,数据安全问题就可能得到极大的改善,例如,我们可以考虑通过 REST API 进行对象存储。借助 REST API 的形式,我们就可以通过上述基于 Kubernetes 工作负载身份标识的短期访问 token 实现认证和授权。
随着 Serverless 工作负载模式的不断采用,我们应该预期出现更多的动态和更短生命周期的工作负载(例如,Serverless 函数处理每个 Pod 的一个事件)。在这种情况下,工作负载和“老式磁盘”之间的不匹配变得更加明显。
在 Kubernetes 中,容器存储接口(container storage interface,CSI)一直是通过 PV 向工作负载添加文件系统和块存储的接口。Kubernetes 对象存储特别兴趣小组正在研究容器对象存储接口(COSI),这可能会使对象存储成为 Kubernetes 中的一等公民。
在本文中,我认为在定义 Kubernetes 应用时,有充分的理由超越“传统”的 Kubernetes 资源。这并不是说,我们永远都不会使用传统的资源类型。仍然会有一些我们无法轻易转换的遗留应用,SRE 团队可能仍然需要运行有状态的服务,这些服务会被开发人员构建的应用所消费。对于私有云基础设施来说,情况更是如此。
Kubernetes 的未来在于自定义资源定义(custom resource definition,CRD)和抽象,我们会在 Kubernetes 之上构建它们,并通过 CRD 提供给用户。Kubernetes 会成为抽象的控制平面,而开发人员应该关注的正是这些抽象的 CRD。Kubernetes 控制平面可以管理 Kubernetes 内部的资源,甚至是 Kubernetes 外部的资源,例如Crossplane管理云基础设施。
正如上面所总结的,大多数传统的 Kubernetes 资源对开发人员来说可能有更好的替代方案。使用这些替代方案将改善我们在未来几年内开发和运维云原生应用程序的方式。毕竟,Kubernetes 是一个构建平台的平台。它并不是终点!
领取专属 10元无门槛券
私享最新 技术干货