本文发布已超过一年。较旧的文章可能包含过时的内容。请检查页面中的信息自发布以来是否已变得不正确。
Bitmovin 如何在云端和本地使用 Kubernetes 进行多阶段 Canary 部署
在多个公有云上运行大规模视频编码基础设施非常困难。在 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.为我们的客户提供零停机部署
- 2.在每次 git 主干推送时持续部署到生产环境
- 3.为客户提供已部署服务的高稳定性
显然,#2 和 #3 是相互矛盾的,如果每个合并的功能都立即部署到生产环境中 - 我们如何确保这些版本没有错误,并且不会对我们的客户产生不利的副作用?
为了克服这个矛盾,我们为每个微服务提出了一个四阶段金丝雀管道,我们在其中同时部署到生产环境,并使更改远离客户,直到新的构建证明在生产环境中可靠且正确地工作。
一旦推送新的构建,我们将其部署到内部阶段,该阶段仅供我们的内部测试和集成测试套件访问。一旦内部测试套件通过,质量保证报告没有问题,并且我们没有检测到任何异常行为,我们将新的构建推送到我们的免费阶段。这意味着我们 5% 的免费用户将被随机分配到这个新版本。在此阶段经过一段时间后,构建将被提升到下一个阶段,该阶段会将 5% 的付费用户路由到它。只有当构建成功通过所有这 3 个障碍时,它才会部署到生产层,在那里它将接收我们剩余用户的所有流量以及我们的企业客户的流量,这些企业客户不属于付费存储桶,并且永远不会看到他们的流量被路由到金丝雀轨道。
默认情况下,此设置使我们成为一个相当大的 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) | 部署 | #Pods | | 帐户服务 | 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,这些 pod 从 Kubernetes ConfigMaps 获取它们的 haproxy.conf,如下所示
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 个副本运行。它还会检查 HAProxy 后面的 4 个服务(内部、免费、其他和生产)是否至少运行 2 个端点。如果未满足任何这些条件,我们会在 Slack 和电子邮件中立即收到通知。
事实证明,使用我们之前的编排器管理这么多 pod 非常不可靠,并且覆盖网络经常导致问题。Kubernetes 则不然 - 即使将我们当前的工作负载增加一倍以进行测试也完美无瑕,总的来说,自从我们安装它以来,集群一直像时钟一样运行。
切换到 Kubernetes 的另一个优势是除了 API(我们使用它来编写一些内部部署工具)之外,还可以使用 Kubernetes 资源规范。这使我们能够拥有一个包含所有 Kubernetes 规范的 Git 存储库,其中每个轨道都是根据一个通用模板生成的,并且只包含诸如金丝雀轨道和名称之类的可变事物的占位符。
对集群的所有更改都必须通过修改这些资源规范的工具并自动签入 git,因此,每当我们看到问题时,我们都可以调试基础架构随时间推移所经历的更改!
总结这篇文章 - 通过将我们的基础设施迁移到 Kubernetes,Bitmovin 能够拥有
- 零停机部署,使我们的客户能够 24/7 全天候进行编码而不会中断
- 快速的开发到生产周期,使我们能够更快地交付新功能
- 多个级别的质量保证和对生产部署的高度信心
- 跨云架构和本地部署的通用抽象
- 稳定可靠的服务运行状况检查和调度
- 围绕我们的基础设施的自定义工具,以检查和验证系统
- 部署历史记录(git 中的资源规范 + 自定义工具)
我们要感谢 Kubernetes 社区为该项目所做的杰出工作。该项目的发展速度令人叹为观止!在如此多样化的环境中保持如此高的质量和稳健性真是令人惊讶。
- 在 Stack Overflow 上发布问题(或回答问题)
- 在 K8sPort 上加入倡导者社区门户
- 在 GitHub 上参与 Kubernetes 项目
- 在 Twitter 上关注我们 @Kubernetesio,获取最新更新
- 在 Slack 上与社区交流
- 下载 Kubernetes