Kubernetes 在 GitHub

  • Jesse Newland
  • 张斌

2017 年 8 月 21 日

话题:GitHub语言 & 开发架构Kubernetes

为增加本文的代入感,本译文将以原作者第一称视角阐述 Jesse Newland 的经历,请读者知悉。

去年,GitHub 已经改进了 Ruby on Rails 应用的基础设施,该应用负责运行 github.com 和 api.github.com。最近我们实现了一个重要里程碑,即:所有 Web 和 API 请求都由Kubernetes集群中运行的容器来处理,这些集群都部署在了我们的metal 云上。将一个重要的应用迁移到 Kubernetes 是一个非常有趣的挑战,所以我们非常激动可以向你分享我们学到的东西。

为什么要改变?

在做出这次迁移之前,我们主要的 Ruby on Rails 应用(我们称之为 github/github)的配置和它 8 年前没什么两样:即由一个名叫God的 Ruby 进程管理器管理着Unicorn(独角兽)进程,而该 Ruby 进程管理器则运行在受 Puppet 管理的许多服务器上。类似地,chatops 部署的工作方式和它刚被引入时差不多:即 Capistrano 与每个前端服务器建立 SSH 连接,然后更新代码并重启应用进程。当峰值请求负载超过可用的前端 CPU 能力时,GitHub 网站可靠性工程师会分配额外的能力并添加到有效的前端服务器池中。

虽然我们基本的生产方式这些年并未改变多少,但 GitHub 本身却发生了巨大的变化,包括:新特性、更大的软件社区、更多的 GitHub 用户以及更高的每秒请求数。随着我们不断地发展,这一方式开始出现问题。许多团队希望将其负责的功能从这个庞大的应用中提取到一个更小的服务中,以便该服务可以单独运行或部署。随着运行的服务量增大,网站可靠性工程师团队开始让几十个其它的应用支持与前面提到的配置相类似的配置。这增加了我们在服务器维护、分配和其它工作上花费的时间,而这些工作与改善 GitHub 整体的体验并无直接关系。新服务往往需要几天、几周甚至几个月的时间来部署,这取决于其复杂程度和网站可靠性工程师团队是不是有空。随着时间的推移,我们发现,这一方式不能给工程师带来足够的灵活性,以使他们继续打造世界级的服务。工程师们需要一个可以让他们实验、部署和扩展新服务的自助服务平台。同时我们也需要这样的平台来满足核心 Ruby on Rails 应用的需求,从而使工程师或机器人能在几秒钟之内(而不是几小时、几周甚至更长时间)分配额外的计算资源,从而应对需求上的变化。

为了应对这些需求,网站可靠性工程师、平台和开发者体验团队启动了一个共同的项目,在这个项目中,我们最开始只是对容器编排平台进行评估,而到今天,我们已取得这样的成就,即:每天能将支撑 github.com 和 api.github.com 运行的代码往 Kubernetes 集群部署几十次。本文旨在概述一下这个过程中涉及的工作。

为什么是 Kubernetes?

作为对“平台即服务”工具当前局势评估的一部分,我们对 Kubernetes 做了近距离考查。Kubernetes 是一个谷歌的项目,它自称是一个开源的系统,用来自动部署、扩展和管理容器化的应用。Kubernetes 一些优点使它从众多平台中脱颖而出,例如:支撑该项目的活跃的开源社区、首次运行的体验(这使我们能够在最初实验的最初几个小时内部署小型的集群和应用)、以及大量与刺激其设计的体验有关的信息。

这些实验的范围迅速扩大:我们组建了一个小项目,来构建 Kubernetes 集群和部署工具,以便在接下来的黑客(hack)周获得一些有关该平台实际的体验。我们在这个项目的体验以及使用过它的工程师的反馈都极其正面。是时候将实验扩大了,所以我们开始计划一个更大的上线活动。

为什么要从github/github开始?

在本项目的初期,我们做了一个慎重的决定,那就是将迁移的目标设定为某个关键的负载:即 github/github。许多因素促使我们做出这个决定,但其中尤为突出的是:

  • 我们知道,贯穿 GitHub 深入了解这个应用会对整个迁移过程有帮助。 
  • 我们需要自助服务容量扩展工具来应对持续性的增长。
  • 我们需要确保我们养成的习惯和模式不仅适合大型应用,还同样适合小规模服务。
  • 我们希望更好地让应用不受开发、过渡、生产、企业和其它环境的差异所影响。
  • 我们知道,成功迁移一个关键且高度引人注目的负载可以鼓励在 GitHub 更大范围地采用 Kubernetes。

考虑到选择迁移的负载的关键性,我们需要在引入任何生产流量之前,树立高度的运营信心。

通过审查实验室快速迭代和树立信心

作为此次迁移的一部分,我们使用了如 Pods、Deployments 和 Services 之类的 Kubernetes 基本单元设计了目前前端服务器所提供的服务的替代品,制作了其原型并进行了验证。这个新设计的某些验证可以通过在容器内运行 github/github 现有的测试套件完成,而不是在同前端服务器配置相似的服务器上完成。然而,我们仍然需要观察这个容器作为大量 Kubernetes 资源的一部分有怎样的表现。很快我们就清楚发现,我们必须得有一个环境,能够支持对 Kubernetes 和我们要运行的服务进行探索性测试。

大约在同一时间,我们发现,现有的 github/github pull request 的探索性测试模式已经开始出现痛点增长的迹象。随着部署速度以及项目工作的工程师的数量的增加,使用几个额外的部署环境来验证向 github/github 发出 pull request 的频率也在增加。通常,在峰值工作时间内,少量功能完备的部署环境已经被预定了,这就减缓了部署 pull request 的过程。工程师经常要求能够在“分支实验室”上测试更多的生产子系统。虽然分支实验室允许许多工程师进行并发部署,但它只给每个部署单独启动了一个 Unicorn 进程,这意味着“分支实验室”仅在测试 API 和 UI 变化时才有用。这些需求重叠的很多,所以我们将这些项目结合起来,并采用了一个由 Kubernetes 专为 github/github 开发的新部署环境,名为“审查实验室”。

在构建审查实验室的过程中,我们交付了不少子项目,每个子项目都可以在各自单独的博客文章中进行介绍。前前后后,我们共交付了:

  • 一个在 AWS VPC 中运行的 Kubernetes 集群,该 VPC 由Terraformkops共同管理。
  • 一系列短暂运行 Kubernetes 集群的 Bash 集成测试。在项目初期我们大量使用这些测试,以树立对 Kubernetes 的信心。
  • github/github 的 Dockerfile 文件。
  • 增强内部 CI 平台,以支持将容器构建和发布到容器注册表。
  • 50 个 Kubernetes 资源的 YAML 陈述,它们检查后被加入到 github/github。
  • 增强内部部署应用,以支持将 Kubernetes 资源从存储库部署到 Kubernetes 命名空间,以及从内部 secret 存储库创建 Kubernetes secrets。
  • 一个将 haproxy 和 consul-template 组合在一起的服务,以便将 Unicorn pod 的流量路由到现有服务,并在那里发布服务信息。
  • 一个读取 Kubernetes 事件并将异常事件发送到内部错误跟踪系统的服务。
  • 一个名为 kube-me 的兼容chatops-rpc的服务,它会通过聊天向用户公开一组有限的 kubectl 命令。

最终成果是一个基于聊天的界面,它可以为任何 pull request 创建 GitHub 的单独部署。一旦 pull request 通过了所有必需的 CI 作业,用户就可以将该 pull request 部署到审查实验室,如下所示:

jnewland

.deploy https://github.com/github/github/pull/4815162342 to review-lab

用户说:部署https://github.com/github/github/pull/4815162342 到审查实验室

Hubot

@jnewland's review-lab deployment of github/add-pre-stop-hook (00cafefe) is done! (12 ConfigMaps, 17 Deployments, 1 Ingress, 1 Namespace, 6 Secrets, and 23 Services)(77.62s) your lab is available at https://jnewland.review-lab.github.com

答复用户:@ jnewland的审查实验室部署 github/add-pre-stop-hook(00cafefe)已经完成! (共 12 个 ConfigMap、17 个 Deployment、1 个 Ingress、1 个 Namespace、6 个 Secrets 和 23 个 Service)(用时 77.62 秒),点击https://jnewland.review-lab.github.com 即可使用。

像分支实验室一样,实验室在上次部署之后的一天内即被清理干净。由于每个实验室都是在自己的 Kubernetes 命名空间中创建的,因此清理命名空间与删除命名空间一样简单,部署系统在必要时会自动执行这些动作。

审查实验室是一个成功的项目,产生了一些积极的结果。在将该环境普遍开放给工程师之前,它充当 Kubernetes 集群设计的基本试验场和原型环境,以及 Kubernetes 资源设计和配置的基本试验场和原型环境,现在 github/github Unicorn 工作负载由 Kubernetes 资源来描述。发布后,大量工程师被迫适应新的部署风格,一些感兴趣的工程师提供了反馈,而一些没有注意到任何变化的工程师仍在正常使用,这使我们逐步建立了信心。就在最近,我们观察了高可用性团队中一些工程师使用审查实验室的情况,他们使用审查实验室来实验与 Unicorn 的互动,并通过将某个新实验子系统部署到共享实验室来实验该子系统的表现。这种环境能使工程师以自助服务的方式实验和解决问题,我们对此感到非常高兴。

每周向分支实验室和审查实验室的部署量

Metal 上的 Kubernetes

随着审查实验室的交付,我们的注意力转移到了 github.com。为了满足旗舰服务的性能和可靠性要求(旗舰服务依赖于低延迟来访问其它数据服务),我们需要构建 Kubernetes 基础设施来支持在物理数据中心和 POP 中运行的metal 云。这次的工作又有近十几个子项目:

  • 这篇关于容器联网的及时而全面的帖子帮助我们选择了Calico网络提供商,它提供了我们在 ipip 模式下快速交付集群所需的开箱即用功能,同时让我们之后能够灵活地与网络基础设施进行对等互连。
  • 经过对@kelseyhightower的“艰难但必不可少的 Kubernetes”的不少于十几次的拜读,我们将一些手动分配的服务器组装到了某临时的 Kubernetes 集群中,该集群通过了与我们用来锻炼 AWS 集群相同的一套集成测试。
  • 我们构建了一个小工具,来为每个集群生成 CA 和配置,它们的格式可被内部 Puppet 和 secret 系统使用。
  • 我们 Puppet 化了两个实例角色(Kubernetes 节点和 Kubernetes apiservers)的配置,并允许用户提供已配置的集群的名称于分配时加入。
  • 我们构建了一个小型 Go 服务来使用容器日志,并将元数据按键 / 值的格式附加 (append) 到每一行,并将它们发送到主机的本地 syslog 端点。
  • 我们加强了GLB,即内部负载均衡服务,以支持 Kubernetes NodePort 服务。

所有这些努力产生了一个集群,并通过了我们的内部验收测试。鉴于此,我们相信,同一套输入(即由审查实验室使用的 Kubernetes 资源)、同一组数据(即通过 VPN 与审查实验室连接的网络服务)以及同样的工具会产生类似的结果。在不到一周的时间内,即便其中大部分时间用于内部沟通和对那些对迁移有重大影响的事件进行排序,我们仍能将整个工作负载从运行在 AWS 上的 Kubernetes 集群迁移到数据中心内的 Kubernetes 集群。

提高信心

凭借在 metal 云上搭建 Kubernetes 集群的成功和可重复的模式,现在我们可以对用 Unicorn 部署替代当前前端服务器池的能力充满信心了。在 GitHub,工程师及其团队通常会通过创建Flipper特性来验证新功能,然后在其可行后立即选择该功能。在加强部署系统后,我们将一套新的 Kubernetes 资源部署到与现有的生产服务器并行的 github-production 命名空间,并加强 GLB,以支持通过受 Flipper 影响的 cookie 将员工请求路由到不同的后端。因此员工可以通过在任务控制栏中选择某按钮进入 Kubernetes 实验后端:

内部用户的负载帮助我们找到问题、修复错误,并在生产中开始习惯 Kubernetes 了。在此期间,我们通过模拟将来要执行的程序、编写运行手册和执行故障测试来努力提高信心。我们还将少量生产流量路由到此集群,以确认我们对负载下的性能和可靠性的假设。我们从每秒 100 个请求开始,并将其扩大到 github.com 和 api.github.com10%的请求。经过这些模拟后,我们暂停了一下,重新评估了全面迁移的风险。

集群组

几个故障测试产生了始料未及的结果。尤其是,一个模拟单个 apiserver 节点的故障的测试对运行的工作负载的可用性产生了负面影响,从而中断了集群。对这些测试结果的调查并没有产生确凿的结果,但是帮助我们确定中断可能与连接到 Kubernetes apiserver 的各种客户端之间的交互有关(如 calico-agent,kubelet,kube-proxy 和 kube-controller-manager)和确定内部负载均衡器在 apiserver 节点故障期间的行为。鉴于观察到 Kubernetes 集群降级可能会中断服务,我们开始考虑在每个站点的多个集群上运行旗舰应用,并自动将请求从不正常的集群转移到其他正常集群。

我们的路线图已包含类似的工作,以支持将此应用部署到多个独立运行的站点。同时,这种方法其它积极的折衷 - 包括为低中断集群升级提供可行的故事、将集群与现有故障域(如共享网络和电源设备)相关联 - 支持我们继续走这条路线。我们最终决定使用某个设计,它利用了部署系统支持部署到多个“分区”的功能,并且通过自定义的 Kubernetes 资源注释来增强该设计,以支持集群特定的配置。所以我们放弃了现有的联合解决方案,转而使用另一个方法,因为该方法能够利用已经存在于部署系统中的业务逻辑。

从 10%到 100%

随着集群组的实施,我们逐渐将前端服务器转换为 Kubernetes 节点,并增加路由到 Kubernetes 的流量百分比。除了一些其他负责的设计​​团队,我们在短短一个多月内完成了前端转型,同时将性能和错误率保持在目标范围内。

Web 请求百分比:Kubernetes/github-fe (%)

在这次迁移期间,有个问题一直延续到今天:在高负载和 / 或高比率容器抖动的时候,一些 Kubernetes 节点会引起内核崩溃和重启。虽然我们对此不满意,并且正在继续高度重视和调查该问题,但让我们高兴的是,Kubernetes 能够自动绕过这些故障,并继续在目标错误率范围内提供流量。我们已经通过 echo c> / proc / sysrq-trigger 执行了一些模拟内核崩溃的故障测试,而且发现这可以作为我们故障测试模式的有用补充。

接下来该做什么?

将此应用迁移到 Kubernetes,我们深受鼓舞,并期待后续做更多的迁移。虽然我们故意将首次迁移的范围限定于于无状态的工作负载,但能够在 Kubernetes 上尝试运行有状态的服务仍令人激动不已。

在本项目的最后阶段,我们还交付了一个工作流程,用于将新的应用和服务部署到类似的 Kubernetes 集群组中。在过去几个月中,工程师已经将数十个应用部署到了这个集群。以前,这些应用中每一个都需要网站可靠性工程师进行配置管理和分配支持。通过自助服务应用分配工作流程,网站可靠性工程师可以将更多的时间投入到为组织其它部门提供基础设施产品,以支持我们的最佳实践,以及为每个人提供更快、更有弹性的 GitHub 体验。

鸣谢

我们衷心感谢整个 Kubernetes 团队提供的软件、文字和指导。我也要感谢以下 GitHub 用户在本项目上所做出杰出贡献:@samlambert, @jssjr, @keithduncan, @jbarnette, @sophaskins, @aaronbbrown, @rhettg, @bbasata, 和@gamefiend

和我们一起工作!

想帮助 GitHub 网站可靠性工程师团队解决像这样有趣的问题吗?  欢迎你加入我们。请通过这里申请!

查看英文原文https://githubengineering.com/kubernetes-at-github/


感谢冬雨对本文的审校。

给 InfoQ 中文站投稿或者参与内容翻译工作,请邮件至editors@cn.infoq.com。也欢迎大家通过新浪微博(@InfoQ@丁晓昀),微信(微信号:InfoQChina)关注我们。

GitHub语言 & 开发架构Kubernetes