作者:Jon Friesen、Nick Tate 和 Cody Baker,DigitalOcean
我们喜欢 Kubernetes 和它的所有功能。我们希望所有的开发人员都能从中受益,因此我们决定在它的基础上构建更高层次的抽象。作为一个全功能的平台即服务(PaaS), App Platform 解决了从开发到 Kubernetes 支持的高度可扩展和弹性的云原生部署的操作方面的问题,同时保持了尽可能简单的用户体验。
提供这种服务意味着在世界各地的许多数据中心上运行许多集群。可伸缩性、冗余和性能是关键,这使得我们在架构中使用了几个 CNCF 项目:
我们非常依赖 CNCF 生态系统,因为这些工具允许真正可伸缩的基础设施,可以处理任何规模的用户应用程序的需求。App Platform 为开发者带来了这些工具的强大功能,而无需进行设置和维护。
在 Kubernetes 集群中,节点应该像牛(cattle)一样被对待,而不是宠物(pet)。当你没有紧密地与底层基础设施绑定时,这使得系统具有弹性。如果某个节点变得不正常,或者你想在空闲时间缩小池以节省成本,只需破坏该节点。
我们借鉴了 Kubernetes 的核心原则,并将其带到应用平台的集群层面。为了跨区域和硬件拓扑管理集群,我们构建了一个集群协调器(reconciler),它在集群级别上的功能类似于 Kubernetes 在单个 Kubernetes 集群节点级别上的功能。
使用单个命令,我们可以编排一个要创建的全新 Kubernetes 集群,考虑到不同的节点池类型,设置 Cloudflare 入口,确保所有定制管理工作负载(如 Istio 和 Fluent Bit)启动并运行等等。这个过程使我们在系统的大小和规模上具有很大的灵活性。
这对于某些类型的升级也是非常重要的。有各种关键的控制平面组件,如 Istio 用于入口网络,我们更倾向于创建一个全新的集群进行升级,而不是在一个有活动流量通过的集群上进行实时升级。一旦新的集群准备就绪,我们就可以指示应用平台协调器开始安全地将应用迁移到它们。
应用平台与开发者相遇。对于有应用程序源代码的开发人员,我们利用 Cloud Native Buildpacks 来检测和构建 OCI 格式的镜像。对于带有 Dockerfile 的开发者,我们利用了 Kaniko。使用现有 CI 工作流的开发人员也可以部署预构建的镜像。我们有两个方案:Cloud Native Buildpacks 和 Dockerfile(使用 Kaniko 构建)。
Cloud Native Buildpacks 致力于标准化构建应用的抽象生命周期和契约。在高层次上,它将这个过程分为 4 个阶段:
应用平台实现了 Cloud Native Buildpacks 功能,用于应用检测,当用户第一次设置他们的应用和在应用构建过程中发生。最初的检测包括将应用程序代码克隆到预先预热的环境中,并运行 CNB 构建包的检测部分,以确定应用的构建包组。我们还用额外的元数据来扩展 buildpack 结果,比如检测到的语言/框架、我们认为支持的应用平台组件类型、推荐的构建/运行命令等。这些信息有助于用户理解如何构建他们的应用程序,并使我们能够为他们的应用程序提供一个简单的创建过程。
构建利用了整个 CNB 生命周期,从检测到编译,最后将其打包成一个 OCI 镜像并将其存储在 DigitalOcean 容器注册中心。所有这些都发生在一个保持代码和配置安全的沙盒 Kubernetes Job 之中。
有一个很棒的开源社区,提供了许多语言用户经常需要的构建包。支持 Cloud Native Buildpacks 并在其上构建我们的应用检测和应用构建过程是很自然的。
Dockerfile
第二种方案通过将 Dockerfile 定义为创建容器的指令集,提供了更多的深度和可定制性。我们使用这个 Dockerfile 来为你的应用程序创建构建。传统上,这涉及到与 Docker 守护进程的交互,但出于安全原因,这是我们不能轻易提供给最终用户的构建容器。这就是 Kaniko 发挥作用的地方:它完全在一个非特权容器中运行 Dockerfile 的指令,抓取文件系统的快照并在每条指令之后上传,而不需要访问 Docker 守护进程。
用户应用程序部署在 Kubernetes 集群中,应用程序部署由 Kubernetes 组件组成(例如,部署、服务)。Kubernetes 帮助我们轻松地垂直和水平地伸缩用户应用程序,并添加资源限制以适应适当的计划。
我们从 Kubernetes 借鉴的另一个核心概念是在代码中描述配置。所有应用程序都由一个声明式的 YAML 配置定义[1],我们称之为“应用程序规范”。用户可以手动编辑他们的应用规范,并使用我们的 doctl 命令行工具推送它,或者在应用平台的 web 控制面板中进行更改。这两种操作都将应用规范传递给 app reconciler,后者负责验证、构建和部署它。如果应用的源代码是相同的,或者应用规范的改变不会影响构建,我们可以完全跳过它,重用现有的 OCI 镜像,并在几秒钟内使用新的配置部署应用。
App Platform 的容器化运行时将客户推向高度可伸缩性和高可用性的模式。容器的水平扩展在运行时端做了大量的工作来实现这一点。在网络方面,我们面临的挑战是提供一种解决方案,既能满足最大客户的需求,又能在小型应用程序方面保持成本效益。从技术上讲,我们需要一个负载均衡器,它的开销较低,可以将流量分配给小型应用实例,但也可以处理超出单个虚拟机最大容量限制的流量。同样重要的是,一个应用平台用户的流量峰值不会影响其他用户。
我们选择使用 Cloudflare 作为我们所有服务的前端,它为我们提供了强大的内容分发网络(CDN)、负载平衡和世界级的 DDoS 保护。CDN 由分布在全球各地的边缘服务器组成,这些服务器缓存内容,极大地减少了资产(如静态站点)的加载时间。DDoS 保护会吸收大量匹配恶意模式的流量。
在它的背后,我们有一个可扩展的节点池,运行 Istio 并充当入口网关。Istio 从 Cloudflare 接收流量,并通过 Cilium 覆盖网络将其路由到客户的应用程序 pod。对于静态站点,Istio 对请求进行一些小的转换,然后将其路由到 DigitalOcean Spaces 后端。
在 Kubernetes 的术语中,每个应用程序都分配了自己的命名空间,我们使用 NetworkPolicies 将服务通信锁定在该命名空间内的资源上。
不过,这只是隔离的一部分;我们还需要保护容器的实际运行时环境。否则,恶意用户可能试图突破其容器,访问或接管其他客户工作负载,甚至主机系统。
我们探索了这个领域的一些技术,并决定利用 gVisor 进行隔离。你可以将 gVisor 看作定义在用户空间中的“迷你”内核。它实现了所支持的所有系统调用、/proc 文件等的子集,并拦截应用程序创建的实际系统调用。它在这个沙箱环境中运行这些调用,只将它认为安全的调用的非常有限的子集转发给实际的主机内核。
我们考虑的另一个运行时解决方案是 Kata Containers。这些可能更接近你对云虚拟机的心理模型。使用 Kata Containers,每个容器都包装了一个轻量级虚拟机和它自己的内核。在我们的基准测试中,我们认为 gVisor 的性能更适合应用程序平台的需求。
这是一个我们不断重新评估和尝试新技术和想法的领域。这些运行时的一大优点是它们都符合 OCI,并且使用 Kubernetes 编排器,我们可以为单个集群注册多个运行时。我们可以准确地选择哪个 pod 在哪个运行时下运行,这使得测试新的运行时技术变得非常容易。
应用平台将所有这些技术结合在一起,消除了大多数应用程序无法达到的复杂性和运营投资,以最小的用户努力提供了一流的云原生平台。应用平台是建立在巨人的肩膀上。我们永远感谢所有投入时间和精力创建这些神奇工具的个人和组织。
[1]
声明式的 YAML 配置定义: https://www.digitalocean.com/docs/app-platform/references/app-specification-reference/