前几周,我被迫拒绝“批准”了 GitLab 项目的合并请求。我不喜欢他们提出的解决方案,即,对我们的应用程序代码库进行特定的更改,以支持蓝绿发布。它向我发出了一个代码更改的警告:将部署与代码绑定了;在环境应该是不可见和可互换的情况下,以编写代码来支持环境。创建这些类型的依赖将我们与特定的平台和发布方法绑定了,而额外的代码会导致各种可能的缺陷和错误,这些缺陷和错误可能会因环境而异,因此极难测试。
这是怎么发生的呢?它有一个非常有趣的背景,并且这样的事情非常普遍。这一切都是从一个愿望开始的,即改进发布,从而更频繁地将变更引入到生产环境中。
我们团队的应用程序相对现代化且灵活:托管在 Docker 容器中,并能自动部署到云上,单元和组件测试根据更改运行,一旦通过了全套的自动化测试并满足了代码质量标准,部署就可以自动继续。我们有一个“发布”的概念,即部署到云环境中的多个服务的构建构件的标签集合。
然而,将这些构件移动到“更高的环境”中(例如预发布环境、生产环境)需要停机时间来重新启动所有服务,并且必须安排在非工作时间,而且发布要由单独的团队来执行。如果我们希望运行某些类型的更新(例如,对 Liquibase 来说过于复杂或缓慢的数据库更改),则需要手动执行步骤,因此,这些发布窗口虽并不频繁,但对团队来说却很痛苦。更不用说那些令人筋疲力尽的反社会工时制了。总的来说,一个好的改进候选项和蓝绿发布应该要能有助于消除其所需的加班和停机时间。
简而言之,蓝绿部署的概念是同时运行(至少)两个应用程序实例。当发布新版本时,它只能发布到一个(或一些)实例上,而让其他实例仍在旧版本上运行。一开始可以完全限制对这个新版本的访问,然后可能会发布给一部分消费者,直到对新版本产生信任为止。此时,可以逐渐限制对运行的旧版本实例的访问,当然也可以升级这些实例。这为用户创建了一个零停机时间的发布。
当然,也有需要注意的地方。对数据源或 API 的任何破坏性更改都意味着旧版本的请求不能被新版本处理,这就排除了蓝绿发布的可能性。这是我最喜欢问的面试问题之一,问一个人如何在蓝绿环境中处理破坏性更改,以避免有人提出了一个很好的解决方案,但它可能会涉及一些定制的路由层来丰富或调整“旧”请求以适应“新”系统。在这一点上,你必须考虑一下,保留一些旧版本的停机时间是不是更好。虽然大多数软件团队都在尽最大努力避免破坏性更改,但破坏性更改通常是不可避免的。
所以,让我们假设一下最好的情况,我们没有任何的破坏性更改。我们还假设,就像我的项目一样,我们正在将 Docker 容器直接部署到云服务上——一个Azure应用服务,而不是Kubernetes或另一个支持自动扩缩和路由的 PaaS 层。那么,我们该怎么做呢?
我们的架构由许多微服务组成的,这些微服务通过 REST API 进行通信,并作为单独的构件进行部署。但是当前所有的构件都在一个 Git 存储库中,并在单个版本中同时部署。假设我们有两个运行 1.0 版本的微服务 A 和微服务 B,以及一个包含 A 接口的新版本(2.0 版),该接口将由 B 中的新方法调用。假设我们在生产环境中部署了负载均衡的 2 个 A 实例和 2 个 B 实例;对于蓝绿来说,每个实例都将迁移到新版本上。
你可以立即看到问题所在:2.0 版本的 B 实例只能调用 2.0 版本的 A 实例。如果它被定向到 1.0 的端点,则无法找到所需的新功能。由于这种特定的路由要求,服务 B 不能使用它从服务发现中所获取的负载平衡端点来调用服务 A,而是需要特定的“绿”实例地址。
我们团队也面临着同样的情景。来看看我们可以用的解决方案。
按依赖顺序发布
在调用 API 的功能之前发布 API。在上面的例子中,如果我们为微服务 B 做了一次蓝绿发布,检查它是否正常,然后确保微服务 B 的两个实例都迁移到了 2.0 版本,那么之后我们就可以安全地对微服务 A 做蓝绿发布。
这种模型是一种适应增量式、非破坏性 API 更改的良好且简单的方式,尽管它必然会导致更多的发布,因为在发布下一个服务之前,每个依赖项都需要就位。这确实让回答“我们线上有什么版本?”这个问题变得更加困难。你的标签版本跨越了多个微服务版本。但这确实是微服务、部署复杂性和计算效率之间的权衡。微服务架构意味着,如果系统的某个特定部分需要用更多的资源,你可以只水平扩展该部分,而不必扩展整个系统,但随后你必须要单独管理所有部分的生命周期。
API 调用中的版本控制
有几种方法可以将版本控制引入到 API 调用中。例如,一种直接的方式是在 RESTful 端点的实际 URL 中放入一个版本。另一种方式是尝试使用 HTTP 头等元数据来表示版本控制;然而,这只适用于你能控制所有服务的服务内通信时。否则,你不能指定服务请求必须包含的版本控制信息。
如果我们的 API 端点是版本化的,这对我们的发布有何帮助呢?它将允许我们的服务 B 的 2.0 版本管理任何 HTTP 404“URL 未找到”响应,如果它碰巧向服务 B 的 1.0 版本实例发送了一个 V2 请求,并且它将允许服务 A 托管端点的 V1 和 V2,那么它就可以在前一个版本仍然存在时继续服务。一旦每个服务都迁移了,这将会导致一些工作,如管理和清理服务 B 中的 V1-mitigation 代码。
依赖基础设施
云原生选项。我们的团队将应用程序部署到 Azure。如果你要问 Azure 是如何做蓝绿发布的,他们会向你介绍他们的Azure Traffic Manager产品。这是一种基于 DNS 负载均衡的解决方案,提供了一种加权轮询路由方法。权重可以用于逐渐向新迁移的服务器上引入流量,你还可以添加规则,以确保“蓝”服务器只路由到其他“蓝”的服务器,从而将你的蓝环境和绿环境分开。这确实是有成本的,尽管成本不是很高。
回到我们的具体问题。我们还没有构建版本化的 API,正如我前面提到的,我们目前在一个版本中部署所有的微服务。我会将我们的服务归类为微服务,因为它们可以单独部署和扩展,但我们的发布过程有效地将它们合并到一个 BBOM(Big Ball Of Mud,大泥球)了。
对于选项三,如果没有 Azure Traffic Manager(这被认为过于昂贵),当“蓝”前端向后端微服务发送请求时,我们的团队无法检查或强制执行,它将调用“蓝”后端。这意味着,除非我们首先从后端传播更改(这并不总是可行,特别是当蓝和绿共享同一个数据库时),否则我们将面临路由无法处理请求的风险。让我非常畏缩的一个解决方法是:包含一个可以设置为蓝或绿的配置变量,然后在来自前端的请求中设置一个 HTTP 头,通过指定该变量来在应用程序代码库中有效地重新创建 Azure Traffic Manager 功能。哎唷。
代码可以在生成路由 URL 时使用这个 HTTP 头/配置变量作为标志,以决定是通过绿服务器还是蓝服务器来生成路径。因此,例如,“注销”链接将在前端配置中指定 2 个配置变量:一个用于绿,一个用于蓝,允许根据服务器“颜色”生成不同的注销链接......吃饱撑的吗?
我们团队知道这是一种创建蓝/绿发布流程的糟糕方式,它们是被预算和时间压力这两个常见的恶魔所逼迫的产物。我们的要求是在一个月内创建一个蓝绿部署流程,并且不使用 Azure 云原生服务,考虑到我们的起点,我们的选择非常有限。但是我们应该早点看到它的到来,例如,当我们一开始知道我们要构建 API 时,就应该考虑到 API 版本控制。
我们陷入了“DevOps 鸿沟”,因为我们有两个优先级不同的团队,一个开发团队的首要任务是尽快将更改引入到发布管道,而另一个 WebOps 团队的首要任务则是确保云平台的可重复性和安全性。当有人提出构建微服务的请求时,开发团队认为 WebOps 团队会管理蓝绿发布之类的事情,并没有停下来考虑他们应该如何构建解决方案来帮助他们。由于这样的疏忽,它最终会反噬,从而伤害我们。
那么我们现是做到什么地步了呢?目前,我们还没有使用硬编码版本的蓝绿发布;正如我所预测的那样,当我们尝试使用我们构建的流程时,我们会发现一些非常严重的路由缺陷。我期待的是,我们最终能改用 Azure Traffic Manager。到那时,我们就会开始将我们的“微服务大球”分解为多个部署管道,这样我们就可以计划一个自下而上的新变更发布了。在我们最初的示例中,我们的第一个版本将服务 A 升级到 2.0,以在 API 和数据库中可以使用新的端点字段,然后第二个版本则是更新服务 B,以调用服务 A 的新端点。
对我们来说,这是一个非常有价值的学习过程:让开发人员和 WebOps 团队更紧密地联系在一起,并与发布团队更密切地合作,以了解我们是如何帮助他们的。当技能组合不同时,人们很自然地会将他们认为属于其他人的任务委派给其他人(例如,负载均衡应用程序实例将委托给理解 Azure 云概念和各种模板语言的人来编写基础架构代码),但我们已经学会了分解这些任务,以便双方都能理解对方在做什么,从而帮助发现整个流程中的问题。
经验教训
总之,我们从早期的蓝绿设置尝试中学到了很多东西。
变革架构
我非常反对“面向未来”的应用程序。如果没有性能问题,请不要构建缓存。如果你没有删除内容的要求,那么就不要执行删除。你对需求的猜测很有可能是错误的。
然而,你应该从一开始就让这些未来的变更变得可行且容易。这意味着在构建整体应用程序设计时,你应该考虑如何在数据库级别实现更改,以及如何向 API 中添加版本等。
不要为了微服务而微服务
微服务不必是设计的默认设置。如果你的架构中没有契合点,也没有比其他架构更容易被大流量冲击的点,并且如果你的组件只是彼此通信,而且部署在相同的近似位置(例如,相同的云或相同的数据中心),那么你可能无法从微服务架构中获得很大的收益。
通过减少移动部件的数量以及减少组件调用之间的网络延迟,你可能能从简化部署中获得更多的好处。不要只是随大流,要好好思考你想要实现的目标。
注意团队边界
对于任何需要一起协作的团队,无论是用户体验设计师和开发人员、业务分析师和 QA,还是开发人员和运营团队,我们都需要意识到项目中风险最大的领域是团队之间的边界。
每个团队都会一直在做假设,例如,开发人员会假设用户体验设计师正在提供有效的 HTML 原型;业务分析师会假设 QA 团队已经根据文档化的需求进行了自动化测试;运营团队会假设他们已经收到了应用程序依赖项的通知。每当两个团队开始协作时,最好使用一些技术来消除这些假设,例如,你可以从领域驱动设计中获取一些工具,并运行事件风暴事件研讨会。
在一个项目中,越早将这些假设作为风险项提出,事情就会越好,也就越安全!
原文链接:
https://www.infoq.com/articles/blue-green-deployments/
相关阅读:
评论