蓝绿部署与金丝雀部署:差异、权衡以及如何选择

  1. 首页
  2. 博客
  3. 开发运营

开发运营

蓝绿部署与金丝雀部署:差异、权衡以及如何选择

这两种策略都可以让您在不关闭网站的情况下发布内容,但它们以相反的方式管理风险。了解每种方法的工作原理、实际成本、如何处理数据库以及哪种方法适合您的团队。

阅读约 9 分钟
蓝绿部署与金丝雀部署:差异、权衡以及如何选择

要点速览

  • 蓝绿保留两个完整的环境并立即切换所有流量。
  • Canary 首先将新版本发送给一小部分用户,然后逐渐扩大。
  • 蓝绿提供非常快的回滚。 Canary 限制了不良版本可能影响的用户数量。
  • 两者都依赖于向后兼容的数据库更改和良好的监控。

发布软件过去意味着维护窗口和祈祷。现代团队使用使发布例行化的策略,蓝绿部署与金丝雀部署选择是您首先面临的选择之一。两者都降低了风险,但它们的方式相反:一个准备完整的替代品,而另一个则一次对真实用户进行一点测试。

本指南解释了每种方法的工作原理以及实际成本。我们在 Python 3.13 中运行了小型用户路由和回滚示例,并且输出来自该运行。 Kubernetes 示例是说明性的,我们没有运行这些示例。将百分比和阈值视为适应的起始示例。

简短的回答

通过蓝绿部署,您可以运行两个相同的生产环境。一个为用户提供服务,而您在另一个上部署和测试新版本,然后一次性切换所有流量。

通过 canary 部署,新版本将在旧版本旁边运行。您向其发送一小部分实际流量,比较结果,然后逐步增加份额,直到为所有人服务。这个名字来源于金丝雀矿工曾经用作危险的预警。

蓝绿与金丝雀并排

方面 蓝绿色 金丝雀
交通转移 一下子全部完成 循序渐进
用户暴露于不良版本 全部,直到你切换回来 只有金丝雀分享
回滚 将流量切换回来 将金丝雀份额设置为零
释放速度 快速割接 较慢,分阶段
基础设施成本 发布期间最多翻倍 一小部分额外份额
需要流量路由 一个简单的开关 基于百分比的路由
需要监控 好 非常好,具有每个版本的指标
版本共存 简而言之,如果有的话 对于整个推出
完整发布前的真实用户反馈 否 是的

蓝绿部署的工作原理

  1. Blue 已上线并为所有流量提供服务。
  2. 将新版本部署到 green,该版本不接收任何用户流量。
  3. 在真实的生产环境中,使用冒烟测试和内部用户来彻底测试绿色。
  4. 将路由器、负载均衡器或 DNS 切换为绿色以发送流量。
  5. 仔细观察,蓝色仍在运行并准备就绪。
  6. 如果出现问题,请切换回来。如果一切顺利,蓝色将成为下一个空闲环境。

在 Kubernetes 中,您可以通过更改服务的选择器来进行切换,以便流量转移到不同标记的 Pod:

apiVersion: v1
kind: Service
metadata:
  name: shop
spec:
  selector:
    app: shop
    version: green   # was: blue
  ports:
    - port: 80
      targetPort: 8000

优点:它在概念上很简单,您可以在用户看到它之前测试确切的生产设置,并且回滚几乎是即时的。 弱点:重复的基础设施可能会很昂贵,通过测试的错误会立即影响到每个人,并且在切换期间需要小心开放会话和正在进行的工作。如果您对交换机使用 DNS,缓存的记录可能会延迟它。对这些概念不熟悉?请参阅我们的 Kubernetes 初学者教程。

金丝雀部署的工作原理

  1. 将新版本与稳定版本一起部署。
  2. 将一小部分流量(例如 5%)路由到金丝雀。
  3. 将错误率、延迟和业务指标与稳定版本进行比较。
  4. 如果健康,增加到 25%,然后 50%,然后 100%。
  5. 如果出现任何性能下降,请将所有流量发送回稳定版本。

许多团队先从内部员工开始金丝雀,然后是低风险区域,最后才是更广泛的公众。

优点: 糟糕的版本只会影响一小部分人,并且您可以从真实流量中了解到没有测试环境可以完全重现。 弱点:您需要基于百分比的路由和每个版本的指标,部署需要更长的时间,并且两个版本一起运行一段时间。小金丝雀也可能太安静而无法发出有意义的信号,尤其是在交通流量较少的时候。

稳定的用户路由

根据每个请求随机选择一个版本会给用户带来混乱的体验,因为他们可能会在新旧版本之间来回切换。一个常见的解决方案是将稳定的用户 ID 散列到一个存储桶中,因此每个用户总是得到相同的答案:

import hashlib

def bucket(user_id):
    digest = hashlib.sha256(user_id.encode()).hexdigest()
    return int(digest, 16) % 100

def version_for(user_id, canary_percent):
    return "v2" if bucket(user_id) < canary_percent else "v1"

users = [f"user-{i}" for i in range(10000)]
for percent in (5, 25, 50, 100):
    share = sum(version_for(u, percent) == "v2" for u in users) / len(users)
    print(percent, round(share * 100, 1))

# 5 5.0
# 25 24.5
# 50 49.9
# 100 100.0

实际份额紧随目标。该方法有两个更有用的属性,我们的运行证实了这两个属性。同一用户始终收到相同的版本。 5% 组中的每个人仍包含在 25% 中,因此随着部署的扩展,已经拥有新版本的人都不会被退回。

在实际系统中,服务网格、负载均衡器或 Argo Rollouts 或 Flagger 等工具通常会处理此路由,因此您不需要自己编写。了解他们在做什么仍然很有帮助。

自动化决策

金丝雀仅在有人实际比较版本时才起作用。自动检查比人看图表更可靠。这是核心思想,将金丝雀的错误率与稳定版本的错误率进行比较:

def should_rollback(canary_errors, canary_total, base_errors, base_total,
                    tolerance=1.5, min_requests=500):
    if canary_total < min_requests:
        return False   # not enough data to decide
    return (canary_errors / canary_total) > (base_errors / base_total) * tolerance

should_rollback(3, 100, 10, 5000)        # False: too few requests
should_rollback(30, 1000, 100, 10000)    # True:  3% against 1%
should_rollback(12, 1000, 100, 10000)    # False: 1.2% is within tolerance

注意第一种情况。它的错误率看起来很高,但只有 100 个请求,还没有足够的证据。真正的工具会应用更仔细的统计数据,并且它们会一起查看多个信号:

信号 示例
错误 5xx 回复率
延迟 p95 和 p99,如 负载测试与压力测试中所述
饱和度 CPU、内存、连接池
商务 注册、结账、支付成功

提示: 将金丝雀与同一时间窗口内的稳定版本进行比较,而不是与上周的数字进行比较。交通和行为每小时都会发生变化,因此并排比较可以为您提供更公平的信号。

数据库问题

应用程序代码可以在几秒钟内交换,但数据不能。两种策略通常共享一个数据库,因此在部署期间和回滚之后,新旧版本必须都使用相同的架构。

标准答案是扩展和收缩模式。假设您要将 name 重命名为 full_name:

  1. 展开:添加新列。旧代码不受影响。
  2. 迁移:发布写入两列的代码,并回填旧行。
  3. 开关:释放从新列读取的代码。
  4. 契约:一旦没有任何东西使用旧列,就在以后的版本中将其删除。

这需要多次发布,但每一步都可以安全地回滚。一次性运行 RENAME COLUMN 会破坏任何需要另一个名称的版本。还要小心长时间运行的架构更改,因为它们可以持有锁。有关这方面的更多信息,请阅读 SQL 事务。

滚动策略和其他策略

策略 它是如何运作的 适合
重新创造 停止旧的,然后开始新的,有停机时间 内部工具、批处理系统
滚动 一次替换几个实例 常见的默认值,包括在 Kubernetes 中
蓝绿色 两种环境切换 快速切换和回滚
金丝雀 逐步扩大小份额 高流量,高风险
影子 将流量复制到新版本,并丢弃其响应 使用真实负载进行测试,对用户没有影响

Kubernetes 部署默认进行滚动更新,这适合许多服务。滚动更新也会逐渐变化,但其份额与实例数量相关,并且它本身不会分析结果或因不良指标而暂停。

功能标志怎么样?

部署将代码放在服务器上,发布 使功能可供用户使用。功能标志将这些功能分开,让您无需部署任何内容即可为选定的用户打开功能。它们对于这两种策略都非常有效:在关闭标志的情况下进行部署,然后逐渐启用它。它们不会取代这些策略,因为标志无法保护您免受启动时崩溃的版本的影响。请记住删除旧标志,因为它们会累积为技术债务。

如何选择

你的情况 合理选择
应用程序简单,用户很少 滚动更新
需要非常快速的切换和回滚 蓝绿色
想要在切换之前在生产中进行测试 蓝绿色
高流量和高失败成本 金丝雀
强大的监控和每个版本的指标 金丝雀
流量低 蓝绿色,因为金丝雀信号太弱
基础设施预算紧张 金丝雀或滚动

它们也可以组合起来。有的团队部署了绿化环境,然后逐步将流量转移到绿化环境中,获得了干净的环境和有限的爆炸半径。无论您选择什么,都可以通过您的 CI/CD 管道 实现自动化,正如我们的 GitHub Actions 指南 所解释的那样。

常见错误

  • 进行重大架构更改。 如果旧代码无法读取数据,则回滚没有帮助。
  • 没有回滚标准。 在发布开始之前而不是在事件期间确定“坏”的含义。
  • 让环境发生变化。如果绿色与蓝色不同,则您的测试证明的结果较少。使用基础设施作为代码定义两者。
  • 仅测量技术指标。 发布可以没有错误,但仍会中断签出。
  • 永远不要练习回滚。排练它,以便它在你面临压力时发挥作用。
  • 忘记后台作业。工作人员和计划任务在切换期间也需要一个计划。

最后的话

蓝绿色为您提供干净、快速的切换。金丝雀给你一个小而谨慎的曝光。如果您的监控仍然是基础的,请从滚动更新或蓝绿开始,并在尝试金丝雀之前投资每个版本的指标。然后演练下一个版本的回滚,包括数据库。这个单一的练习往往会揭示出真正需要解决的问题。

常见问题

蓝绿部署和金丝雀部署有什么区别?

蓝绿同时将所有流量从一个完整环境切换到另一个完整环境。 Canary 向新版本发送一小部分流量,并逐渐扩大。

蓝绿和金丝雀哪个更安全?

金丝雀限制了不良版本影响的用户数量,蓝绿提供了非常快速的回滚。更安全的选择取决于您的流量、工具和监控。

蓝绿部署成本高吗?

可以,因为您可能在发布期间运行两个完整的环境。云自动扩展和关闭空闲环境可以降低成本。

金丝雀应该从多少百分比开始?

团队通常从大约 1% 到 5% 开始,只要这能为有意义的信号提供足够的流量。低流量系统可能需要更大的份额。

金丝雀和蓝绿如何处理数据库更改?

通常具有向后兼容的更改,例如扩展和收缩,因为两个版本可能共享一个数据库并且必须在部署期间都工作。

继续学习

与Stack Quora合作

需要测试、WordPress 或 SEO 方面的帮助?

我们帮助团队交付更好的软件并发展他们的网站。告诉我们您正在做什么,我们将建议明确的下一步。

继续阅读

更多指南 测试、人工智能和现代开发

喜欢这篇文章吗?选择一个主题并直接跳至我们最新的教程。

人工智能

人工智能幻觉解释:语言模型为何会产生幻觉以及如何减少它

人工智能幻觉解释:它们是什么,为什么语言模型产生它们,常见类型,如何通过基础和提示来减少它们,以及我们进行的简单检查。

阅读文章 →
DevOps

环境变量解释:.env 文件、秘密和配置正确完成

使用我们运行的 Python 示例解释环境变量:读取和设置它们、为什么每个值都是字符串、.env 文件、进程继承、秘密和验证。

阅读文章 →
软件开发

API 速率限制解释:算法、标头和处理 429 错误

API 速率限制通过我们运行的模拟进行了解释:令牌桶、固定窗口、滑动窗口、429 响应、速率限制标头、带回退的重试和测试限制。

阅读文章 →
测试自动化

Python 中的模拟:unittest.mock、补丁以及导致测试谎言的错误

使用unittest.mock 在Python 中进行模拟,经过测试:模拟和return_value、side_effect、修补使用名称的位置、spec 和autospec、模拟日期以及常见错误。

阅读文章 →
软件测试

代码覆盖率解释:行覆盖率与分支覆盖率以及 100% 的真正含义

用真实的coverage.py运行来解释代码覆盖率:行、分支和条件覆盖率,阅读报告,为什么100%覆盖率仍然会错过错误,以及合理的目标。

阅读文章 →
数据库与 SQL

SQL 中的 DELETE、TRUNCATE 和 DROP:差异、回滚以及何时使用它们

DELETE、TRUNCATE 和 DROP 通过经过测试的 SQL 进行了解释:各自删除什么、回滚、触发器、身份重置、速度,以及 PostgreSQL、MySQL、SQL Server 和 SQLite 有何不同。

阅读文章 →
数据库与 SQL

SQL 视图解释:创建、使用和避免陷阱(附示例)

SQL 视图通过我们运行的查询进行解释:创建视图、实时结果、隐藏列、性能、可更新和物化视图、破坏的依赖关系和最佳实践。

阅读文章 →
软件开发

TypeScript 接口与类型:真正的差异,使用编译器进行测试

TypeScript 接口与类型,使用真实编译器进行检查:两者都可以做什么,只有一个可以做什么,声明合并,扩展与交集以及要遵循的简单规则。

阅读文章 →
软件开发

Python *args 和 **kwargs 解释:灵活的函数和清晰的示例

Python *args 和 **kwargs 用我们运行的代码进行了解释:打包和解包、参数顺序、仅关键字和仅位置参数、装饰器和常见错误。

阅读文章 →
软件开发

Python Lambda 函数:语法、实际用途以及何时不使用它们

Python lambda 函数用我们运行的代码进行了解释:语法、使用键排序、映射和过滤器、闭包、后期绑定陷阱,以及何时普通 def 是更好的选择。

阅读文章 →
人工智能

什么是人工智能代理?它们是如何工作的,只需运行一个简单的代理循环

什么是人工智能代理?关于代理循环、工具、内存和护栏的简单指南,以及我们运行的一个小型 Python 循环、真正的风险以及何时简单的聊天机器人是更好的选择。

阅读文章 →
DevOps

监控与可观察性:真正的区别,包括日志、指标和跟踪

监控与可观察性解释:已知和未知的故障、日志、指标和跟踪、检查事件演练、黄金信号、OpenTelemetry 和有用的警报。

阅读文章 →
DevOps

Docker Volumes 与 Bind Mounts:以正确的方式保留容器数据

Docker 卷与绑定挂载解释:为什么容器数据消失、命名卷、绑定挂载、tmpfs、Compose 示例、备份、权限以及何时使用它们。

阅读文章 →
测试自动化

TDD 与 BDD:主要区别、以两种方式构建的一个功能以及何时使用每种方式

TDD 与 BDD 的解释是通过一种双向构建的折扣功能:使用我们运行的代码进行红绿重构、Given-When-Then 场景、工具、权衡以及何时使用每种方案。

阅读文章 →
软件测试

用户验收测试 (UAT):流程、清单和真实示例

用户验收测试解释:谁运行它、它与 QA 有何不同、七步流程、UAT 测试用例模板、清单、签核标准和常见错误。

阅读文章 →
数据库与 SQL

SQL NULL 值解释:IS NULL、COALESCE、NULLIF 和要避免的陷阱

通过我们运行的查询解释了 SQL NULL 值:三值逻辑、IS NULL、COALESCE、NULLIF,聚合、连接和排序如何处理 NULL,以及它悄悄引起的错误。

阅读文章 →
数据库与 SQL

SQL CASE WHEN 语句:语法、实例和常见错误

SQL CASE WHEN 解释了我们运行的查询:简单和搜索表单、条件顺序、SUM 透视、COUNT 技巧、自定义排序、更新和要避免的 NULL 陷阱。

阅读文章 →
软件开发

什么是智威汤逊?通过工作示例解释 JSON Web 令牌

什么是智威汤逊? JSON Web Tokens 解释:这三个部分,在我们运行的代码中构建和篡改的令牌,JWT 与会话,存储,刷新令牌和安全错误。

阅读文章 →
软件开发

JavaScript 事件循环解释:调用堆栈、微任务和计时器以及示例

JavaScript 事件循环用我们运行的代码进行了解释:调用堆栈、任务和微任务队列、为什么 setTimeout 0 不是立即执行、阻塞、饥饿和面试测验。

阅读文章 →
软件开发

Python 生成器和 Yield 解释:通过真实示例节省内存

Python 生成器和yield 用我们运行的代码进行了解释:它们如何暂停和恢复、测量内存节省、管道、无限序列、yield from、发送和常见陷阱。

阅读文章 →
人工智能

什么是嵌入?向量和余弦相似度简单解释

什么是嵌入?关于向量、余弦相似度和语义搜索的简单语言指南,其中包含我们运行的一个小示例、常见用途、选择模型和已知限制。

阅读文章 →
DevOps

蓝绿部署与金丝雀部署:差异、权衡以及如何选择

蓝绿部署与金丝雀部署解释:每种部署的工作原理、成本、风险和回滚、我们测试的稳定用户分桶、数据库迁移、滚动更新以及如何选择。

阅读文章 →
测试自动化

不稳定的测试:造成这些问题的原因以及如何彻底解决它们

我们运行的演示解释了不稳定的测试:主要原因、如何确认和诊断、正确的时间修复、共享状态和日期以及隔离过程。

阅读文章 →
软件测试

负载测试与压力测试:差异、指标和 k6 示例

负载测试与压力测试的解释:各自证明什么、峰值和浸泡测试、重要的指标、为什么平均值会误导、k6 个示例以及如何计划测试。

阅读文章 →
软件测试

测试用例与测试场景:主要区别、示例以及何时使用它们

测试用例与测试场景的解释:清晰的定义、并排的表格、从场景到案例的密码重置功能、模板以及何时使用每个功能。

阅读文章 →
数据库与 SQL

SQL 查询优化:读取执行计划并修复慢速查询(带计时)

对一百万行进行 SQL 查询优化测试:读取执行计划、添加正确的索引、修复不可控制的过滤器、使用复合索引和键集分页。

阅读文章 →
数据库与 SQL

SQL UNION 与 UNION ALL:差异、示例和性能测试

SQL UNION 与 UNION ALL 通过我们运行的查询进行了解释:重复、都遵循的规则、真实的数据错误、测量的性能、INTERSECT 和 EXCEPT 以及何时使用它们。

阅读文章 →
软件开发

JavaScript Fetch API 教程:GET、POST、错误处理和超时

针对本地服务器测试的 JavaScript Fetch API 教程:GET 和 POST、为什么不抛出 404、可重用的帮助程序、查询字符串、超时、CORS 和常见错误。

阅读文章 →
软件开发

Python 文件处理:读取、写入、CSV、JSON 和 pathlib 以及示例

使用我们运行的代码解释了 Python 文件处理:打开模式、使用、读取大文件、CSV、JSON、pathlib、二进制文件、安全写入、错误处理和常见错误。

阅读文章 →
软件开发

Python 类和对象:带有实际示例的 OOP 教程

用我们运行的代码解释 Python 类和对象:__init__、方法、类与实例属性、继承、属性、dunder 方法、数据类和常见错误。

阅读文章 →
人工智能

什么是大语言模型?法学硕士如何运作,在没有炒作的情况下进行解释

什么是大语言模型?法学硕士用简单的语言解释:标记、嵌入、注意力、训练、温度和上下文窗口、一个微小的下一个单词演示和真正的限制。

阅读文章 →
DevOps

适合初学者的 Linux 命令:带有示例的基本终端指南

适合初学者的 Linux 命令,按任务分组:导航、文件、查看日志、grep、管道、权限、进程和档案,并附有实用的日志分析示例。

阅读文章 →
测试自动化

Selenium 与 Playwright:主要区别、代码比较以及选择

Selenium 与 Playwright 比较:架构、等待、浏览器和语言支持、调试、两种工具中的相同测试、迁移建议以及如何选择。

阅读文章 →
软件测试

等价划分:通过实例解释测试设计技术

等效分区解释:如何找到有效和无效分区,年龄、密码和优惠券的工作示例,我们运行的测试,以及为什么将其与边界配对。

阅读文章 →
软件测试

软件测试中的严重性与优先级:差异、矩阵和实例

软件测试中的严重性与优先级解释:清晰的定义、带有示例的四向矩阵、级别尺度、谁设置每个级别以及如何运行错误分类。

阅读文章 →
数据库与 SQL

SQL 子查询教程:类型、相关子查询和 EXISTS 以及示例

SQL 子查询教程,其中包含我们运行的查询:标量、IN、FROM 和 SELECT 子查询、相关子查询、EXISTS、NOT IN NULL 陷阱以及子查询与 JOIN 与 CTE。

阅读文章 →
数据库与 SQL

SQL 事务和 ACID 解释:COMMIT、ROLLBACK 和隔离级别

SQL 事务和 ACID 通过我们运行的银行转账进行了解释:COMMIT、ROLLBACK、保存点、锁、隔离级别、脏读、死锁和最佳实践。

阅读文章 →
软件开发

Git merge 与 rebase 的区别:示例及如何选择

用真实的 log 输出、对比表、rebase 黄金法则和安全的团队工作流,讲清 Git merge 与 rebase 的区别。

阅读文章 →
软件开发

Python 列表推导式:语法、示例与何时避免使用

Python 列表推导式详解:语法、过滤、if-else、嵌套循环、实测耗时,以及何时普通 for 循环更合适。

阅读文章 →
数据库与 SQL

SQL JOIN 详解:附示例(INNER、LEFT、RIGHT、FULL)

通过示例表和查询,一步步讲解 SQL JOIN:INNER、LEFT、RIGHT、FULL、CROSS 和 SELF,并介绍常见错误与性能技巧。

阅读文章 →