本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。

Bitmovin 如何在云端和本地使用 Kubernetes 进行多阶段金丝雀部署

在多个公共云上运行大规模视频编码基础设施是一项艰巨的任务。在 Bitmovin,我们过去几年一直成功地做着这件事,但从工程角度来看,这既不愉快,也谈不上特别有趣。

所以很明显,真正促使我们使用 Kubernetes 的主要原因之一是它对不同支持的云提供商的通用抽象以及它提供的精心设计的编程接口。更重要的是,Kubernetes 项目并没有满足于最低公分母的方法。相反,他们添加了在云中运行容器化工作负载所需且有用的抽象概念,然后完成了所有艰苦的工作,将这些概念映射到不同的云提供商及其产品。

我们在 2016 年年中进行的早期测试中看到了极高的稳定性、速度和操作可靠性,这使得迁移到 Kubernetes 变得理所当然。

而且,Kubernetes 项目所追求的规模愿景与我们公司自身的目标紧密契合,这一点也无伤大雅。旨在实现超过 1,000 个节点的集群可能是一个崇高的目标,但对于像我们这样快速发展的视频公司来说,让基础设施旨在支持未来的增长至关重要。此外,在我们对新基础设施进行初步头脑风暴之后,我们立即知道我们将运行大量容器,并且拥有一个明确目标是在全球范围内运行的系统,这非常适合我们。现在,随着最近发布的 Kubernetes 1.6 及其对 5,000 个节点集群的支持,我们对选择容器编排系统更加坚定了信心。

在将我们的基础设施运行在 Kubernetes 上的测试和迁移阶段,我们对 Kubernetes API 及其整个生态系统非常熟悉。因此,当我们考虑扩展我们的云视频编码产品,以便客户在自己的数据中心或云环境中进行使用时,我们很快决定利用 Kubernetes 作为我们无处不在的云操作系统来构建解决方案。

仅仅几个月后,这项努力就成为了我们最新的服务产品:Bitmovin 托管本地编码。由于所有 Kubernetes 集群都共享相同的 API,因此我们的云编码服务也能在 Kubernetes 上运行,这使我们能够部署到客户的数据中心,而无论底层运行的硬件基础设施如何。借助社区提供的优秀工具(如 kube-up)和交钥匙解决方案(如 Google Container Engine),任何人都可以轻松地在自己的基础设施或自己的云账户中配置新的 Kubernetes 集群。

为了给部署到裸机且可能还没有任何自定义 Kubernetes 云集成的客户提供最大的灵活性,我们决定将我们的解决方案完全基于任何 Kubernetes 安装中可用的设施,并且不需要与周边基础设施进行任何集成(它甚至可以在 Minikube 中运行!)。我们不依赖类型为 LoadBalancer 的服务,主要是因为企业 IT 通常不愿意向公共互联网开放端口,而且并非每个裸机 Kubernetes 安装都开箱即用地支持外部预置的负载均衡器。为了避免这些问题,我们部署了一个 BitmovinAgent,它在集群内部运行,并轮询我们的 API 以获取新的编码作业,而无需任何网络设置。然后,该代理使用本地可用的 Kubernetes 凭据启动新的部署,这些部署通过 Kubernetes API 在可用硬件上运行编码器。

即使没有完整的云集成,我们从使用 Kubernetes API 中获得的持续调度、健康检查和监控也确实使我们能够专注于让编码器在容器中工作,而不是将宝贵的工程资源花费在集成一系列不同的虚拟机管理器、机器提供者和监控系统上。

多阶段金丝雀部署

我们与 Kubernetes API 的首次接触并非为了本地编码产品。将我们的容器化编码工作流构建在 Kubernetes 上,是因为我们在 Bitmovin API 基础设施的开发和部署过程中,看到了 Kubernetes 平台令人难以置信的简便性和强大功能后,才做出的决定。大约四个月前,我们迁移到了 Kubernetes,它使我们能够快速迭代我们的服务,同时满足我们零停机部署和稳定开发到生产流水线的需求。为了实现这一目标,我们提出了一种架构,该架构运行近千个容器,并满足我们在第一天就设定的以下要求

  1. 1. 为客户提供零停机部署
  2. 2. 在每次 git 主线推送时持续部署到生产环境
  3. 3. 为客户提供高稳定性的已部署服务

显然,如果每个合并的功能都立即部署到生产环境,那么 #2 和 #3 之间就存在冲突——我们如何确保这些发布没有错误,并且不会对我们的客户产生负面影响?

为了克服这个矛盾,我们为每个微服务设计了一个四阶段的金丝雀管道,同时部署到生产环境,并隔离对客户的更改,直到新构建在生产环境中被证明可以可靠且正确地运行。

一旦新的构建被推送,我们将其部署到一个只有内部测试和集成测试套件才能访问的内部阶段。一旦内部测试套件通过,QA 报告没有问题,并且我们没有检测到任何异常行为,我们就会将新的构建推送到我们的免费阶段。这意味着 5% 的免费用户将被随机分配到这个新构建。在这个阶段运行一段时间后,构建会被提升到下一个阶段,该阶段将有 5% 的付费用户路由到它。只有当构建成功通过这三个障碍后,它才会被部署到生产层,在那里它将接收来自我们剩余用户以及我们的企业客户的所有流量,这些客户不属于付费用户组,并且他们的流量永远不会被路由到金丝雀轨道。

此设置默认使我们的 Kubernetes 安装规模相当大,因为我们所有的金丝雀层都以最少 2 个副本可用。由于我们目前正在集群中部署大约 30 个微服务(并且还在增长),因此每个服务至少有 10 个 Pod(8 个应用程序 Pod + 最少 2 个执行金丝雀路由的 HAProxy Pod)。尽管在现实中,我们首选的标准配置通常是运行 2 个内部 Pod、4 个免费 Pod、4 个其他 Pod 和 10 个生产 Pod,以及 4 个 HAProxy Pod——总共大约 700 个 Pod。这也意味着我们正在运行至少 150 个服务,这些服务为其底层的微服务金丝雀层提供静态 ClusterIP。

一个典型的部署看起来是这样的:

| 服务 (ClusterIP) | 部署 | #Pod | | account-service | account-service-haproxy | 4 | | account-service-internal | account-service-internal-v1.18.0 | 2 | | account-service-canary | account-service-canary-v1.17.0 | 4 | | account-service-paid | account-service-paid-v1.15.0 | 4 | | account-service-production | account-service-production-v1.15.0 | 10 |

生产轨道的示例服务定义将具有以下标签选择器:

apiVersion: v1

kind: Service

metadata:

 name: account-service-production

 labels:

 app: account-service-production

 tier: service

 lb: private

spec:

 ports:

 - port: 8080

 name: http

 targetPort: 8080

 protocol: TCP

 selector:

 app: account-service

 tier: service

 track: production

在 Kubernetes 服务之前,负责负载均衡不同金丝雀版本服务的,是一个小型 HAProxy Pod 集群,它们的 haproxy.conf 来自 Kubernetes ConfigMaps,看起来像这样:

frontend http-in

 bind \*:80

 log 127.0.0.1 local2 debug


 acl traffic\_internal hdr(X-Traffic-Group) -m str -i INTERNAL

 acl traffic\_free  hdr(X-Traffic-Group) -m str -i FREE

 acl traffic\_enterprise hdr(X-Traffic-Group) -m str -i ENTERPRISE


 use\_backend internal if traffic\_internal

 use\_backend canary if traffic\_free

 use\_backend enterprise if traffic\_enterprise


 default\_backend paid


backend internal

 balance roundrobin

 server internal-lb  user-resource-service-internal:8080 resolvers dns check inter 2000

backend canary

 balance roundrobin

 server canary-lb    user-resource-service-canary:8080 resolvers dns check inter 2000 weight 5

 server production-lb user-resource-service-production:8080 resolvers dns check inter 2000 weight 95

backend paid

 balance roundrobin

 server canary-paid-lb user-resource-service-paid:8080 resolvers dns check inter 2000 weight 5

 server production-lb user-resource-service-production:8080 resolvers dns check inter 2000 weight 95

backend enterprise

 balance roundrobin

 server production-lb user-resource-service-production:8080 resolvers dns check inter 2000 weight 100

每个 HAProxy 都会检查由我们的 API 网关分配的名为 X-Traffic-Group 的标头,该标头决定此请求属于哪个客户桶。基于此,决定是命中金丝雀部署还是生产部署。

显然,在这种规模下,kubectl(尽管仍然是我们日常在集群上工作的主要工具)并不能很好地概述一切是否都按预期运行,以及哪些可能复制过多或过少。

由于我们进行蓝绿部署,有时在旧版本启动后,我们会忘记关闭旧版本,因此有些服务可能会过度复制,而在 kubectl 中列出的 25 个部署中找到这些问题并非易事,至少可以说。

因此,拥有像 Kubernetes 这样以 API 驱动的容器编排器,对我们来说简直是天赐之物,因为它使我们能够编写处理这些问题的工具。

我们构建的工具可以直接在 kubectl(例如 bash 脚本)上运行,或者直接与 API 交互并理解我们的特殊架构,从而为我们提供系统概览。这些工具主要是使用 client-go 库用 Go 语言构建的。

其中一个工具值得特别强调,因为它基本上是我们一眼就能真正看到服务健康状况的唯一方式。它会遍历所有带有 tier: service 选择器的 Kubernetes 服务,并检查伴随的 HAProxy 部署是否可用,以及所有 Pod 是否以 4 个副本运行。它还会检查 HAProxys 后面的 4 个服务(内部、免费、其他和生产)是否至少有 2 个端点正在运行。如果这些条件中的任何一个不满足,我们就会立即在 Slack 和通过电子邮件收到通知。

用我们之前的编排器管理如此多的 Pod 证明非常不可靠,并且覆盖网络经常导致问题。但 Kubernetes 则不然——即使将我们当前的测试工作量翻倍也能完美无缺地运行,而且总的来说,自从我们安装它以来,集群一直运行得像发条一样。

切换到 Kubernetes 的另一个优点是除了 API(我们用来编写一些内部部署工具)之外,还可以使用 Kubernetes 资源规范。这使我们能够拥有一个包含所有 Kubernetes 规范的 Git 存储库,其中每个轨道都是从一个通用模板生成的,并且只包含金丝雀轨道和名称等可变事物的占位符。

所有对集群的更改都必须通过修改这些资源规范的工具进行,并自动提交到 Git,因此,无论何时出现问题,我们都可以调试基础设施随时间发生的变化!

总结一下这篇文章——通过将我们的基础设施迁移到 Kubernetes,Bitmovin 能够实现:

  • 零停机部署,让我们的客户可以 24/7 不间断地编码
  • 快速的开发到生产周期,使我们能够更快地推出新功能
  • 多层质量保证和对生产部署的高度信心
  • 跨云架构和本地部署的普遍抽象
  • 稳定可靠的服务健康检查和调度
  • 围绕我们的基础设施定制工具以检查和验证系统
  • 部署历史(Git 中的资源规范 + 自定义工具)

我们感谢 Kubernetes 社区在项目中做出的杰出贡献。项目推进的速度令人惊叹!在如此多样化的环境中保持如此高水平的质量和健壮性,着实令人震惊。