本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。
使用 Kubernetes 现代化 Skytap 云微服务架构
Skytap 是一个全球性的公共云,它为我们的客户提供在任何给定状态下保存和克隆复杂虚拟化环境的能力。我们的客户包括在混合云中运行应用程序的企业组织、提供虚拟培训实验室的教育组织、需要易于维护的开发和测试实验室的用户以及各种具有不同 DevOps 工作流的组织。
不久前,我们开始以加速的步伐发展业务——我们的用户群和工程组织同时持续增长。这些是令人兴奋且富有挑战性的挑战!然而,平稳地扩展应用程序和组织是困难的,我们正在谨慎地处理这项任务。当我们第一次开始寻找改进以扩展我们的工具集时,很明显传统的操作系统虚拟化不会是实现我们扩展目标的有效方法。我们发现虚拟机持久性的特性鼓励工程师构建和维护定制的“宠物”虚拟机;这与我们构建具有稳定、可预测状态的可重用运行时环境的愿望不符。幸运的是,Docker 和 Kubernetes 社区的增长与我们的增长相吻合,社区参与度的同步爆发(从我们的角度来看)帮助这些工具成熟。
在本文中,我们将探讨 Skytap 如何将 Kubernetes 作为处理不断增长的 Skytap 云生产工作负载服务的关键组件。
随着我们工程师数量的增加,我们希望保持敏捷性,并继续在软件开发生命周期的关键方面实现组件的所有权。这需要我们流程中关键方面的大量模块化和一致性。以前,我们通过虚拟机和环境模板使用系统级打包来推动重用,但随着我们规模的扩大,容器作为一种打包机制变得越来越重要,因为它们对运行时环境的控制相对轻量且精确。
除了这种打包灵活性之外,容器还帮助我们建立更高效的资源利用,并避免了团队将资源混合到大型、高度专业化的虚拟机中这种自然倾向所带来的日益增长的复杂性。例如,我们的运营团队会安装用于监控健康和资源利用率的工具,开发团队会部署服务,安全团队可能会安装流量监控;将所有这些结合到一个虚拟机中会大大增加测试负担,并且经常导致意外情况——哎呀,您引入了一个新的系统级 Ruby gem!
使用 Docker 将服务中的单个组件容器化非常简单。入门很容易,但任何构建过具有少量以上组件的分布式系统的人都知道,真正的困难在于部署、扩展、可用性、一致性以及集群中每个单元之间的通信。
让我们容器化吧!
我们已经开始将许多备受喜爱的宠物虚拟机换成了,正如俗话所说,牛。
_____
/ Moo \
\---- /
\ ^__^
\ (oo)\_______
(__)\ )\/\
||-----w |
|| ||
然而,创建一大群散养容器并不能简化分布式系统的挑战。当我们开始使用容器时,我们认识到需要一个容器管理框架。我们评估了 Docker Swarm、Mesosphere 和 Kubernetes,但我们发现 Mesosphere 的使用模型不符合我们的需求——我们需要管理离散虚拟机的能力;这与 Mesosphere 的“分布式操作系统”模型不符——而且 Docker Swarm 还不成熟。所以,我们选择了 Kubernetes。
启动 Kubernetes 并构建一个新的分布式服务相对容易(就这种服务而言:您无法击败CAP 定理)。但是,我们需要将容器管理与我们现有的平台和基础设施集成。平台的一些组件通过虚拟机更好地服务,我们需要能够迭代地容器化服务。
我们将这个集成问题分解为四个类别:
- 1. 服务控制和部署
- 2. 服务间通信
- 3. 基础设施集成
- 4. 工程支持和教育
服务控制和部署
我们使用 Capistrano 的自定义扩展(我们称之为“Skycap”)来部署服务并在运行时管理这些服务。对我们来说,通过一个单一的、完善的框架来管理容器化服务和传统服务非常重要。我们还需要将 Skycap 与 Kubernetes 这种活跃开发的工具中固有的不可避免的破坏性变更隔离开来。
为了解决这个问题,我们在服务控制框架中使用包装器,将 kubectl 隔离在 Skycap 之后,并处理诸如忽略虚假日志消息之类的问题。
部署为我们增加了一层复杂性。Docker 镜像是一种很好的打包软件的方式,但从历史上看,我们是从源代码而不是软件包部署的。我们的工程团队期望修改源代码足以发布他们的工作;开发人员不期望处理额外的打包步骤。我们没有为容器化而重建我们整个部署和编排框架,而是为我们的容器化服务使用持续集成管道。我们为项目的每次提交自动构建新的 Docker 镜像,然后使用该提交的 Mercurial (Hg) 变更集号对其进行标记。在 Skycap 端,来自特定 Hg 修订版的部署将拉取标记有相同修订版号的 Docker 镜像。
我们在多个环境中重用容器镜像。这需要将环境特定的配置注入到每个容器实例中。直到最近,我们还使用类似的基于源代码的原则来注入这些配置值:每个容器会在运行时通过 cURL 从仓库中复制相关的 Hg 原始配置文件。然而,网络可用性和可变性是最好避免的挑战,所以我们现在将配置加载到 Kubernetes 的 ConfigMap 功能中。这不仅简化了我们的 Docker 镜像,还使 Pod 启动更快、更可预测(因为容器不必从 Hg 下载文件)。
服务间通信
我们的服务使用两种主要方法进行通信。第一种是消息代理,这在 Skytap 平台内的进程间通信中很典型。第二种是通过直接的点对点 TCP 连接,这在与外部世界通信的服务(如 Web 服务)中很典型。我们将在下一节中讨论 TCP 方法,作为基础设施集成的一个组件。
以服务可以理解的方式管理 Pod 之间的直接连接是复杂的。此外,我们的容器化服务需要与基于传统虚拟机的服务进行通信。为了减轻这种复杂性,我们主要使用我们现有的消息队列系统。这帮助我们避免了编写基于 TCP 的服务发现和负载均衡系统来处理 Pod 和非 Kubernetes 服务之间的流量。
这减少了我们的配置负担——服务只需要知道如何与消息队列通信,而不是与它们需要交互的每个其他服务通信。我们在管理 Pod 运行状态等方面拥有额外的灵活性;当节点重启时,消息会在队列中缓冲,并且我们避免了每次从集群中添加或移除 Pod 时重新配置 TCP 端点的开销。此外,MQ 模型允许我们使用更准确的“拉取”方法来管理负载均衡,其中接收方决定何时准备好处理新消息,而不是使用像“最少连接”这样简单地计算开放套接字数量来估计负载的启发式方法。
与迁移使用复杂的基于 TCP 的直接或负载均衡连接的服务相比,将启用 MQ 的服务迁移到 Kubernetes 相对简单。此外,消息代理提供的隔离意味着从传统服务到基于容器的服务的切换对于任何其他启用 MQ 的服务来说基本上是透明的。
基础设施集成
作为基础设施提供商,我们在配置 Kubernetes 以与我们的平台一起使用时面临一些独特的挑战。AWS 和 GCP 提供了简化 Kubernetes 供应的开箱即用解决方案,但它们对底层基础设施的假设与我们的现实不符。有些组织拥有专门的数据中心。这个选项将要求我们放弃我们现有的负载均衡基础设施、基于 Puppet 的供应系统以及我们围绕这些工具建立起来的专业知识。我们对放弃这些工具或我们既有的经验不感兴趣,所以我们需要一种能够与我们的世界集成而不是重建它的 Kubernetes 管理方式。
因此,我们使用 Puppet 来供应和配置虚拟机,这些虚拟机反过来运行 Skytap 平台。我们编写了自定义部署脚本来在这些虚拟机上安装 Kubernetes,并与我们的运营团队协调进行 Kube-master 和 Kube-node 主机的容量规划。
在上一节中,我们提到了基于点对点 TCP 的通信。对于面向客户的服务,Pod 需要一种方式来与 Skytap 的第 3 层网络基础设施接口。Skytap 的示例包括我们的 Web 应用程序和通过 HTTPS 的 API、通过 Web Sockets 的远程桌面、FTP、TCP/UDP 端口转发服务、完整的公共 IP 等。我们需要对这种外部流量的网络入口和出口进行仔细管理,并且历史上一直使用 F5 负载均衡器。用于内部服务的 MQ 基础设施不足以处理此工作负载,因为各种客户端(如 Web 浏览器)使用的协议非常具体,而 TCP 是最低的公分母。
为了让我们的负载均衡器与 Kubernetes Pod 通信,我们在每个节点上运行 kube-proxy。负载均衡器将流量路由到节点,kube-proxy 负责最终将流量移交给相应的 Pod。
我们不能忘记 Kubernetes 需要在 Pod 之间路由流量(包括基于 TCP 和基于 MQ 的消息传递)。我们使用 Calico 插件进行 Kubernetes 网络,并使用专门的服务在 Kubernetes 启动或回收 Pod 时重新配置 F5。Calico 使用 BGP 处理路由通告,这简化了与 F5 的集成。
当 Pod 进入或离开集群时,F5 也需要重新配置其负载均衡池。F5 设备维护一个负载均衡后端池;对容器化服务的入口通过此池定向到托管服务 Pod 的某个节点。这对于静态网络配置来说很简单——但由于我们使用 Kubernetes 来管理 Pod 复制和可用性,我们的网络情况变得动态。为了处理这些变化,我们有一个“负载均衡器”Pod,它监控 Kubernetes svc 对象的更改;如果一个 Pod 被移除或添加,“负载均衡器”Pod 将通过 svc 对象检测到此更改,然后通过设备的 Web API 更新 F5 配置。这样,Kubernetes 透明地处理复制和故障转移/恢复,动态负载均衡器配置使此过程对发起请求的服务或用户保持不可见。类似地,Calico 虚拟网络加上 F5 负载均衡器的组合意味着 TCP 连接对于在传统虚拟机基础设施上运行的服务或已迁移到容器的服务来说,行为应该保持一致。
通过网络的动态重新配置,Kubernetes 的复制机制使得横向扩展和(大多数)故障转移/恢复变得非常简单。我们尚未达到反应式扩展的里程碑,但我们已经为 Kubernetes 和 Calico 基础设施奠定了基础,使其成为实现它的直接途径。
- 配置服务复制的上限和下限
- 构建负载分析和扩展服务(简单,对吗?)
- 如果负载模式与扩展服务中配置的触发器匹配(例如,请求速率或流量超过特定限制),则执行:kubectl scale --replicas=COUNT rc NAME
这将允许我们在平台层面进行细粒度的自动扩缩控制,而不是从应用程序本身进行控制——但我们也将评估 Kubernetes 中的 Horizontal Pod Autoscaling;这可能无需自定义服务就能满足我们的需求。
请关注我们的 GitHub 帐户和Skytap 博客;随着我们解决这些问题的方案成熟,我们希望能与开源社区分享我们的成果。
工程支持
像我们的容器化项目这样的转型需要参与维护和贡献平台的工程师改变他们的工作流程,并学习创建和排除服务故障的新方法。
由于各种学习风格需要多方面的方法,我们通过三种方式处理:文档、直接与工程师接触(即午餐会或辅导团队),以及提供易于访问的、即时支持。
我们持续整理一系列文档,提供关于将传统服务过渡到 Kubernetes、创建新服务以及操作容器化服务的指导。文档并非适用于所有人,有时尽管我们尽了最大的努力,它仍然缺失或不完整,因此我们还运营一个内部的 #kube-help Slack 频道,任何人都可以在其中寻求帮助或安排更深入的面对面讨论。
我们还有一个强大的支持工具:我们自动构建并测试包含 Kubernetes 基础设施的类生产环境,这让工程师有很大的自由度来亲身体验和使用 Kubernetes。我们在这篇文章中详细探讨了自动化环境交付的细节。
最终思考
我们通过 Kubernetes 和容器化总体上取得了巨大成功,但我们确实发现与现有全栈环境集成带来了许多挑战。尽管从企业生命周期的角度来看并非即插即用,但 Kubernetes 的灵活性和可配置性仍然是构建我们模块化服务生态系统的强大工具。
我们热爱应用程序现代化带来的挑战。Skytap 平台非常适合这类迁移工作——我们当然在 Skytap 中运行 Skytap,这在我们的 Kubernetes 集成项目中为我们提供了巨大的帮助。如果您正在规划自己的现代化工作,请联系我们,我们很乐意提供帮助。
- 下载 Kubernetes
- 在 GitHub 上参与 Kubernetes 项目
- 在 Stack Overflow 上提问(或回答问题)
- 在 Slack 上与社区联系
- 在 Twitter 上关注我们 @Kubernetesio 获取最新更新