共计 5602 个字符,预计需要花费 15 分钟才能阅读完成。
导读 | 在过去一年中,GitHub 网站的基础架构渐次更新,来在 github.com 和 api.github.com 运行 Ruby on Rails 应用。而最近的改变是里程碑式的:所有的 Web 和 API 服务都是由在 metal cloud 上部署的 Kubernetes 集群中运行的容器提供的。将关键的应用移交给 Kubernetes 的挑战非常有意思。今天,我们很高兴与您分享我们的所学所得。 |
在此之前,我们主要的 Ruby on Rails 应用程序(我们称之为 github/github)的配置和八年前一样:由 God(Ruby 进程管理器管理的 Unicorn 进程)运行在 Puppet 管理的服务器上。与之相类似地,我们的 chatops 部署的工作机制基本没什么变化:Capistrano 与每个服务器建立 SSH 连接,在线更新相应代码、并重新启动应用进程。当峰值请求负载超过可用的处理能力时,GitHub 的 SRE 们(站点可靠性工程师)将提供额外的处理能力,并将其添加到生产环境中。(译者注:从这段描述来看,之前还是比较传统的运维模式)
我们的基本产品模式在这些年没有发生太大变化,但是 GitHub 社区变化很大:新功能,更大的软件社区,更多的在职员工(GitHubbers)以及每秒更多的请求。随着我们的成长,以前的模式开始出现新的问题。
许多团队希望从这个大型应用程序中抽离出来,将他们负责的功能放到可以独立运行、部署的较小服务中去。随着我们运行的服务数量增加,SRE 团队开始为数十种应用程序提供类似的配置工作,从而增加了我们花在服务器维护、配置和其他(与改善整体 GitHub 体验无关的工作)的时间比例。新服务的部署需要几天,几周或几个月,而这又取决于其复杂度和 SRE 团队的能力。随着时间的推移,这种方法很明显没有为我们的工程师提供继续建立世界级服务所需的灵活性。我们的工程师需要一个可以用来试验、部署和扩展新服务的自我管理平台。我们同时需要这个平台来满足我们的核心 Ruby on Rails 应用程序的需求,以便工程师(或者自动化脚本)可以通过在几秒钟内(而不是几小时、几天或更长时间)分配额外的计算资源来响应需求变化。
为了满足这些需求,SRE、平台和开发者体验团队开始了一个联合项目,从对容器编排平台的初步评估开始一直到今天:每天几十次的部署 github.com 和 api.github.com 代码到 Kubernetes 群集。其目的是为我们当下工作提供一个高层次的概貌。
为了评估现有的“PaaS”平台,我们仔细了解了一下这个来自 Google 的项目:Kubernetes,这个开源项目号称可以自动部署、扩展和管理容器应用。Kubernetes 的几个优势令它从其他平台中脱颖而出:有充满活力的社区支持,很容易上手(我们可以在几个小时内部署一个初始的实验集群、并运行我们的应用程序),以及丰富的落地经验。
随之实验范围很快扩大:一个小型项目组建起来,构建了一个 Kubernetes 集群和部署工具,支持即将到来的黑客竞赛周,以获得该平台的实操经验。我们对此的反馈和其他工程师一样,非常棒!时机已成熟,所以我们开始规划一个较大的实验。
在此项目的最初阶段,我们经过商榷并作出了决定,开始迁移核心模块:github/github。许多因素促成了这一决定,特别是以下几点:
- 我们知道在迁移过程中,GitHub 对此应用的深入了解将会很有用。
- 我们需要平台具有自我管理能力,来应对业务的持续增长。
- 我们希望保留开发习惯和模式,适合大型应用和较小的服务。
- 我们希望更好地环境隔离(开发、测试、生产、企业和其他环境之间的隔离)。
- 我们知道,迁移这种核心的、高负载的模块,将有利于 Kubernetes 进一步在 GitHub 内部的推广。
鉴于我们选择迁移的工作量非常大,我们需要在提供生产环境流量之前高度地建立运营信心。
作为迁移的一部分,我们设计、做原型并验证了使用诸如 Pods,Deployments 和 Services 之类的 Kubernetes 原语替代了我们前端服务器提供的服务。新设计的某些验证可以通过在容器中运行 github/github 的现有测试套件,而不是在类似于前端服务器配置的服务器上运行这种新设计,但是我们还需要观察这个容器是如何作为 Kubernetes 资源一部分的。我们很快验证了在验证阶段结合 Kubernetes 和服务来支持探索性测试的必要性。
大约在同一时间,我们发现现有的 github/github 的 PR 测试已经开始出现增长的迹象。随着部署速度的增加以及加入此项目工程师数量的增加,我们使用了几个 额外的部署环境 作为向 github/github 提交 PR 进行验证的一部分。通常,在峰值工作时间内,少量功能完备的部署环境已经保持稳定,从而减缓部署 PR 的过程。工程师经常要求能够在“branch lab”上测试更多的各种各样的生产子系统。虽然 branch lab 允许工程师们进行并发部署,但它只为每个单独启动一个 Unicorn 进程,这意味着其仅在测试 API 和 UI 时才有用。这些重要的需求重叠在一起,促使我们将项目结合起来,并开始为 github/github 开发一个名为“review lab”的新的 Kubernetes 部署环境。
在搭建“review lab”的过程中,我们交付了少数子项目,每个子项目都会在他们自己的博文中提到。在这个过程中,我们交付了下面的几个内容:
- 一个 Kubernetes 集群(基于 AWS VPC)。
- 一系列的测试脚本(在项目开始时,大量使用的集成测试来试验 Kubernetes 集群,以获得对 Kubernetes 的信心)。
- 构建 github/github 服务的 Dockerfile。
- 内部 CI 平台的增强(支持容器构建、并发布到镜像仓库)。
- 50 多个 Kubernetes 的 YAML 文件被提交进 github/github 项目。
- 内部部署应用程序的增强(支持从代码库部署到 Kubernetes 集群,以及从内部的秘钥库库创建 Kubernetes 的 secret 文件。
- 一种将 haproxy 和 consul-template 组合在一起的服务,可将 单个的 pod 的流量分发到不同的服务中。
- 读取 Kubernetes 事件的服务(将系统异常发送到我们的内部错误跟踪系统)。
- 一个名为 kube-me 的兼容 chatops-rpc 的服务(通过对话向用户公开了一组有限的 kubectl 命令)。
最终结果是一个基于聊天的界面,用于为任何 PR 创建 GitHub 单独部署。一旦 PR 通过了所有必需的 CI 作业,用户就可以将其 PR 部署到 review lab,如下所示:
像之前的 branch lab 一样,实验室在上次部署后一天内被清理干净。由于每个实验室都是在自己的 Kubernetes 名字空间创建的,因此清理就像删除名字空间一样简单,我们的部署系统在必要时可以自动执行。
Review lab 是一个成功的项目,结果非常好。在将此环境广泛提供给工程师之前,它正作为 Kubernetes 集群设计的基本实验基地和原型环境,以及该设计和 Kubernetes 资源的配置现在用于描述 github/github Unicorn。发布后,它给大量工程师可以使用新的部署方式,通过对此感兴趣及还从未使用的工程师们提供的反馈信息,帮助我们建立了信心。就在最近,我们发现一些来自高可用团队的使用 review lab 的工程师,通过将其部署到 shared lab 来试验 Unicorn 与新实验子系统之间的相互作用。我们对工程师们在这种环境下,以自助服务方式实验和解决问题的方式感到非常高兴。
随着 review lab 的迁移,我们将注意力转移到 github.com 上。为了满足我们关键服务的性能和可靠性要求(这取决于低延迟访问其他数据服务),我们需要搭建 Kubernetes 基础架构,以支持我们在物理数据中心和 POP 中运行 metal cloud。同时,有近十几个子项目参与了这项工作:
- container networking 及时而全面地帮助我们选择了 Calico 网络提供商,它提供了我们在 ipip 模式下快速迁移集群所需的开箱即用功能,同时可提供给我们探索网络基础架构的灵活特性。
- 通过阅读十几篇来自 @kelseyhightower 的“Kubernetes the hard way”,我们将一些手动配置的服务器组装到了一个临时的 Kubernetes 集群中,该集群通过了一系列集成测试(这些测试用来完善我们的 AWS 集群)。
- 我们搭建了一个小工具以可由 Puppet 和秘钥系统使用的格式生成每个集群所需的 CA 和配置。
- 我们为 Puppet 生成了两个实例角色的配置:Kubernetes node 和 Kubernetes apiservers,允许用户提供已配置集群的名称,并在规定的时间加入。
- 我们搭建了一个小型 Go service 来处理容器日志,并将键值对格式的元数据附加到每一行,并将它们发送到主机的本地 syslog 端点。
- 我们加强了 GLB 和我们的内部负载平衡服务,以支持 Kubernetes NodePort Service。
所有这些努力并没有白费,都通过了内部验收测试的集群。鉴于此,我们相信,同一套输入(正由 review lab 使用的 Kubernetes 资源),同一组数据(通过 VPN 连接到网络服务 review lab)以及相同的工具将会产生类似的结果。在不到一个星期的时间内(大部分时间用于针对重大迁移的内部沟通和排序),我们可以将整个 工作负载从运行在 AWS 上的 Kubernetes 集群迁移到一个运行在 GitHub 数据中的集群上。
凭借在我们的 metal cloud 上组装 Kubernetes 集群的成功且可重复模式,是时候建立我们对 Unicorn 部署替代当前前端服务器池的能力的信心了。在 GitHub,工程师及其团队通常会通过创建 Flipper 功能来验证新功能,一旦可行就可以选择该功能。在加强我们的部署系统后,我们将与现有的生产服务器并行地部署到 github-production 名字空间部署一套新的 Kubernetes 资源,并加强 GLB,以支持基于 Flipper 影响下的 cookie 请求,我们允许员工在我们的任务控制栏中选择一个按钮对 Kubernetes 后端进行试验。
内部用户的负载可以帮助我们找到问题、修复错误,并开始在生产中顺利地使用 Kubernetes。在此期间,我们通过模拟未来将要执行的程序,编写运行手册和执行 failure test 来提高我们的信心。我们还将少量生产流量路由到此集群,以证明我们对负载下的性能表现和可靠性的假设。从每秒 100 个请求开始,随即扩展到生产环境 10% 的流量。在我们的一些模拟中,我们暂停了一下,重新评估了全面迁移的风险。
有几个 failure test 的结果是我们没有想到的。特别是模拟单个 apiserver 节点故障的测试中,可用性的降低使我们中断了测试。这些测试并没有产生确凿的结果,但是帮助我们确定中断可能与连接到 Kubernetes apiserver 的各种客户端之间的交互(如 calico-agent、kubelet、kube-proxy 和 kube-controller-manager)和内部负载均衡器在 apiserver 节点故障的行为有关。鉴于我们发现 Kubernetes 集群降级可能会破坏服务,我们开始考虑在每个站点的多个集群上运行我们的关键应用程序,并自动将请求从不健康的集群转移到其他健康集群。
类似的工作已经在我们的 GitHub 流程图上,以支持将此应用程序部署到多个独立运营的站点,以及其他积极方法的权衡——包括为低中断集群升级提供可行性方案,并利用现有的故障(如共享网络、电源设备等会起到影响)关联集群。我们最终决定使用支持部署支持多个“分区”,并通过自定义的 Kubernetes 资源注释来增强其支持集群特定的配置,从而实现现有的联合解决方案,使我们能够使用现有的业务逻辑在我们的部署系统中呈现。
随着集群组的到位,我们逐步将前端服务器转换为 Kubernetes 节点,并增加路由到 Kubernetes 的流量百分比。除了负责其他项目的工程团队,我们在短短一个多月内完成了前端转换,同时将性能和错误率保持在我们可接受的范围内。
在迁移过程中,我们遇到了一个至今仍存在的问题:在高负载和(或)高容量流失的时候,我们的一些 Kubernetes 节点会引起内核错误并重启。虽然我们对这种情况感到不满,且进行最高优先级检查,但我们很高兴 Kubernetes 能够自动绕过这些故障,并继续在错误范围内提供流量。我们已经执行了一些使用
echo c > /proc/sysrq-trigger
模拟内核错误的故障测试,并发现对我们故障测试模式是很有用的补充。
我们对将应用程序迁移到 Kubernetes 感到高兴,并期待更快的迁移。虽然我们首次迁移的范围有意限于无状态工作负载,但我们很高兴能够尝试在 Kubernetes 上运行有状态服务。
在本项目的最后阶段,我们还发布了一个工作流,用于将新的应用程序和服务部署到类似的 Kubernetes 群集中。在过去几个月中,工程师已经将数十个应用程序部署到了这个集群。在此之前,这些应用程序中都需要 SRE 的配置管理和配置支持。通过自助服务应用程序配置工作流程,SRE 可以将更多时间投入到为其他部门提供基础设施产品的工作中去,以支持我们的达到最佳实践,为每个人提供更快,更富弹性的 GitHub 体验。