本文发布已超过一年。较旧的文章可能包含过时内容。请检查页面中的信息自发布以来是否仍然正确。

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

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

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

我们i2016 年年中进行的早期测试中看到的出色稳定性、速度和运行可靠性,使得迁移到 Kubernetes 成为理所当然的选择。

而且,Kubernetes 项目一直追求的规模愿景与我们公司自身的目标密切一致,这也有益无害。目标是 >1000 个节点的集群可能是一个崇高的目标,但对于像我们这样快速发展的视频公司来说,让基础设施旨在支持未来的增长至关重要。此外,在我们对新基础设施进行初步头脑风暴后,我们立刻就知道我们将运行大量的容器,而拥有一个明确目标是在全球范围内工作的系统,对我们来说是完美的选择。现在,随着最近发布的 Kubernetes 1.6 及其对 5000 节点集群的支持,我们更加坚信选择 Kubernetes 作为容器编排系统的决策是正确的。

在将我们的基础设施迁移到 Kubernetes 并进行测试的过程中,我们对 Kubernetes API 及其整个生态系统变得非常熟悉。因此,当我们考虑扩展我们的云视频编码产品,供客户在其自己的数据中心或云环境中部署时,我们迅速决定利用 Kubernetes 作为我们无处不在的云操作系统来构建解决方案。

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

为了给部署到裸金属环境且可能尚未针对 Kubernetes 进行任何自定义云集成的客户提供最大的灵活性,我们决定完全基于任何 Kubernetes 安装都可用的功能构建解决方案,这些功能无需与周围基础设施进行任何集成(甚至可以在 Minikube 中运行!)。我们不依赖类型为 LoadBalancer 的 Service,主要是因为企业 IT 通常不愿向公共互联网开放端口——而且并非所有裸金属 Kubernetes 安装都原生支持外部配置的负载均衡器。为了避免这些问题,我们部署了一个 BitmovinAgent,它在集群内部运行,并轮询我们的 API 获取新的编码作业,无需进行任何网络设置。然后,该 Agent 使用本地可用的 Kubernetes 凭证通过 Kubernetes API 在可用硬件上启动运行编码器的新 Deployment。

即使没有完整的云集成,我们从使用 Kubernetes API 获得的稳定调度、健康检查和监控功能,也使我们能够专注于让编码器在容器内部工作,而不是将宝贵的工程资源花费在集成各种不同的虚拟机管理器、机器配置工具和监控系统上。

多阶段金丝雀部署

我们首次接触 Kubernetes API 并不是为了本地编码产品。将我们的容器化编码工作流构建在 Kubernetes 上,是在看到 Kubernetes 平台在我们 Bitmovin API 基础设施开发和推广过程中表现出的惊人易用性和强大能力后做出的决定。大约四个月前,我们迁移到了 Kubernetes,它使我们能够为服务提供快速的开发迭代,同时满足了零停机部署和稳定从开发到生产流水线的要求。

  1. 为了实现这一目标,我们设计了一种架构,该架构运行近千个容器,并满足我们在第一天就提出的以下要求:
  2. 1. 为客户提供零停机部署
  3. 2. 每次 git 主线推送时持续部署到生产环境

3. 为客户提供高稳定性的已部署服务

显然,#2 和 #3 是相互矛盾的,如果每次合并的特性都立即部署到生产环境——我们如何确保这些发布没有 Bug 并且不会对客户产生不良副作用?

为了克服这种矛盾,我们为每个微服务设计了一个四阶段的金丝雀流水线,在该流水线中,我们同时部署到生产环境,并将更改隔离于客户之外,直到新构建在生产环境中被证明可靠且正确地工作。一旦推送新构建,我们会将其部署到内部环境,该环境仅供内部测试和集成测试套件访问。一旦内部测试套件通过、质量保证部门没有报告问题且我们没有检测到任何异常行为,我们就会将新构建推送到免费环境。这意味着我们 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 个 Service,它们为其底层微服务金丝雀层提供一个静态的 ClusterIP。

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

| Service (ClusterIP) | Deployment | 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 |

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

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 Service 前面,是一个由 HAProxy Pod 组成的小集群,它们的 haproxy.conf 来自 Kubernetes ConfigMap,配置大致如下:

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(虽然仍然是我们日常操作集群的主要工具)并不能很好地概述所有东西是否按预期运行,以及是否有 Pod 过度或不足副本的情况。

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

我们构建了一些工具,这些工具要么直接使用 kubectl 运行(例如 bash 脚本),要么直接与 API 交互,并理解我们的特殊架构,从而快速了解系统概况。这些工具大多使用 client-go 库用 Go 语言构建。

其中一个工具值得重点介绍,因为它基本上是我们唯一能够快速查看服务健康状况的方式。它遍历所有带有 tier: service 选择器的 Kubernetes Service,检查配套的 HAProxy Deployment 是否可用以及所有 Pod 是否以 4 个副本运行。它还检查 HAProxy 后面的 4 个 Service(internal, free, others 和 production)是否至少有两个 Endpoints 正在运行。

如果任何一个条件不满足,我们立即会在 Slack 和电子邮件中收到通知。

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

切换到 Kubernetes 的另一个优势是除了 API(我们用它编写了一些内部部署工具)之外,还可以获取 Kubernetes 资源规范。这使我们能够拥有一个包含所有 Kubernetes 规范的 Git 仓库,其中每个跟踪都是从通用模板生成的,并且只包含可变内容的占位符,例如 Canary 跟踪和名称。

对集群的所有更改都必须通过修改这些资源规范并将它们自动提交到 Git 的工具进行。因此,每当我们发现问题时,我们都可以调试基础设施随着时间经历了哪些变化!

总之,通过将我们的基础设施迁移到 Kubernetes,Bitmovin 能够实现以下目标:

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

我们感谢 Kubernetes 社区在该项目上所做的出色工作。项目推进的速度令人惊叹!在如此多样化的环境中保持如此高的质量和健壮性水平确实令人震惊。