掌握使用顺序Ansible剧本的多阶段部署

学习如何使用Ansible设计和执行复杂的多阶段应用程序部署。本指南涵盖为不同部署阶段创建顺序剧本、实施有效的错误处理以及制定回滚策略。通过实际示例和最佳实践,掌握稳健的自动化应用程序交付艺术。

掌握使用顺序Ansible剧本的多阶段部署

当“复制文件并重启服务”不再可靠时,多阶段Ansible部署就变得必要。真正的部署可能需要数据库迁移、功能标志更改、软件包推出、服务重新加载、健康检查以及在最新版本失败时的回滚路径。如果所有这些都存在于一个边界不清晰的大型剧本中,那么每次失败的部署都会变成一场阅读练习。

顺序剧本为每个阶段分配了明确的任务。您可以从CI/CD管道、AWX、Ansible自动化平台或简单的Shell脚本中运行它们。重要的不是按下按钮的工具,而是部署具有顺序、每个阶段可以安全重试以及失败处理是明确的事实。

为什么为多阶段部署使用顺序剧本?

部署应用程序通常不仅仅是复制文件。您可能需要:

  • 准备环境: 创建目录、设置权限、安装依赖项。
  • 更新数据库: 运行模式迁移、填充初始数据。
  • 部署应用程序代码: 传输新代码版本、重启服务。
  • 配置服务: 更新应用程序配置、重新加载守护进程。
  • 执行部署后检查: 运行冒烟测试、验证服务可用性。

顺序很重要,因为有些操作容易回滚,而有些则不然。将符号链接恢复到上一个版本通常很简单。恢复破坏性的数据库迁移可能就不那么容易了。这种差异应该在任何人编写YAML之前就塑造部署计划。

将这些分解为不同的、顺序的剧本提供了几个优势:

  • 模块化: 每个剧本专注于一个阶段,使其更容易理解、维护和重用。
  • 可读性: 复杂的逻辑被分解为可管理的块。
  • 控制: 您可以独立执行特定阶段,或作为更大工作流的一部分。
  • 错误隔离: 如果某个阶段发生故障,更容易查明原因并回滚特定更改,而不会影响部署的其他部分。
  • 幂等性: 编写良好的剧本本质上是幂等的,这意味着多次运行它们与运行一次的效果相同。这对于安全重试至关重要。

这里有一个权衡。单独的剧本增加了编排工作。变量、工件和状态可能需要从一个阶段移动到另一个阶段。对于小型内部服务,一个带有标记块的剧本可能就足够了。对于具有迁移和回滚要求的面向客户的应用程序,额外的结构通常会带来回报。

设计您的多阶段部署工作流

在编写任何Ansible代码之前,规划您的部署阶段。确定逻辑步骤、它们的依赖关系以及执行顺序。一个常见的工作流可能如下所示:

  1. 部署前检查: 确保目标环境已准备就绪。
  2. 数据库迁移: 应用必要的数据库模式更改。
  3. 应用程序部署: 部署新版本的应用程序代码。
  4. 服务重启/重新加载: 使用新代码使应用程序服务上线。
  5. 部署后验证: 运行测试以确认部署成功。

对于每个阶段,考虑需要哪些Ansible任务以及哪个剧本将包含它们。

还要决定哪些阶段允许更改生产状态。冒烟测试剧本不应悄悄修复配置。预检剧本不应安装缺失的软件包,除非这是部署契约的明确部分。将只读检查与可变步骤分开,使工作流更易于信任。

这是一个实用的目录布局:

deploy/
  inventories/
    staging.ini
    production.ini
  group_vars/
    all.yml
    production.yml
  playbooks/
    00-preflight.yml
    01-migrate-db.yml
    02-deploy-app.yml
    03-reload-services.yml
    04-smoke-test.yml
    rollback-app.yml

这些数字不是魔法。它们只是使顺序在文件列表和CI日志中可见。

顺序执行剧本

Ansible提供了一种直接的方法,使用--playbook-diransible-playbook命令一个接一个地运行剧本。最简单的方法是在CI/CD管道或命令行中链接命令。

假设您有以下剧本文件:

  • 01-database-migration.yml
  • 02-deploy-application.yml
  • 03-restart-services.yml
  • 04-smoke-tests.yml

您可以像这样顺序执行它们:

ansible-playbook -i inventory.ini 01-database-migration.yml
ansible-playbook -i inventory.ini 02-deploy-application.yml
ansible-playbook -i inventory.ini 03-restart-services.yml
ansible-playbook -i inventory.ini 04-smoke-tests.yml

在实践中,包装该序列,以便失败的阶段停止管道:

set -euo pipefail

ansible-playbook -i inventories/production.ini playbooks/00-preflight.yml
ansible-playbook -i inventories/production.ini playbooks/01-migrate-db.yml
ansible-playbook -i inventories/production.ini playbooks/02-deploy-app.yml
ansible-playbook -i inventories/production.ini playbooks/03-reload-services.yml
ansible-playbook -i inventories/production.ini playbooks/04-smoke-test.yml

set -e本身不是一种部署策略,但它可以防止最严重的错误:在失败的阶段后继续,好像什么都没发生。CI系统通常提供自己的失败行为,但同样的想法适用。

使用 ansible-playbook --skip-tags--limit

在更高级的场景中,您可能会将多个逻辑步骤组合到一个剧本中,但使用标签来控制执行。然而,对于真正的多阶段分离,通常首选不同的剧本。如果您想运行剧本的子集或跳过某些剧本,可以使用命令行参数。

跳过剧本: 如果03-restart-services.yml由于临时服务问题而失败,您可以在修复原因后仅重新运行该阶段。当早期阶段产生后续阶段依赖的工件或状态时,不要盲目跳过阶段。

限制到特定阶段: 您还可以使用--limit标志将执行限制到特定主机或组,这对于测试很有用。

对于滚动部署,--limit还可以减少爆炸半径:

ansible-playbook -i inventories/production.ini playbooks/02-deploy-app.yml --limit web_canary

针对一台主机或一个小型组运行部署,验证它,然后继续到其余机器。当您的负载均衡器支持在重新加载或重启之前排空主机时,这尤其有用。

结合错误处理和回滚策略

稳健的部署需要为出现问题时制定计划。

ignore_errorsfailed_when

默认情况下,如果任务失败,Ansible会停止执行。您可以控制此行为:

  • ignore_errors: true:即使任务失败,也允许剧本继续。谨慎使用此选项,通常用于非关键任务,或者当您有后续任务要清理或补偿时。
  • failed_when::定义任务应被视为失败的自定义条件。这对于处理预期的非致命错误或验证特定结果非常有用。
- name: 检查服务状态(可能非致命)
  command: systemctl status myapp
  register: service_status
  ignore_errors: true

- name: 如果服务未激活则失败
  fail:
    msg: "服务 myapp 未运行!"
  when: "service_status.rc != 0"

谨慎使用ignore_errors。通常最好注册结果并做出明确的决定。一个充满被忽略失败的部署日志会教会人们停止阅读失败信息。

对于命令,当存在专用模块时,优先使用它们。例如,使用ansible.builtin.serviceansible.builtin.systemdansible.builtin.copyansible.builtin.template和软件包模块,而不是使用shell。模块通常提供更好的幂等性和更清晰的更改和失败状态。

回滚剧本

对于关键部署,要有专用的回滚剧本。这些剧本应设计为撤销其对应部署剧本所做的更改。

  • 01-database-migration-rollback.yml:撤销模式更改。
  • 02-deploy-application-rollback.yml:部署以前的应用程序版本或恢复备份。
  • 03-restart-services-rollback.yml:以以前的状态重启服务。

数据库回滚需要特别小心。在开始使用新模式的写入后,某些迁移无法安全地逆转。一种更安全的模式通常是扩展和收缩:添加向后兼容的模式更改,部署可以同时处理新旧形状的应用程序代码,如果需要则回填数据,然后在以后的部署中删除旧列或字段。

使用该模型,回滚通常意味着恢复应用程序代码并保留兼容的模式,而不是试图在压力下撤销有风险的数据库更改。

示例回滚触发器: 在您的CI/CD管道中,如果04-smoke-tests.yml剧本失败,您将触发按相反顺序执行回滚剧本。

# 如果 04-smoke-tests.yml 失败:
ansible-playbook -i inventory.ini 03-restart-services-rollback.yml
ansible-playbook -i inventory.ini 02-deploy-application-rollback.yml
ansible-playbook -i inventory.ini 01-database-migration-rollback.yml

使用 blockrescuealways

Ansible的blockrescuealways构造提供了一种更结构化的方式来处理单个剧本中的错误。虽然不适用于剧本的顺序,但它们非常适合封装一系列可能失败的任务,并定义在失败情况下该怎么做。

- block:
    - name: 部署新的应用程序代码
      copy:
        src: /path/to/new/app/
        dest: /var/www/myapp/

    - name: 重启应用程序服务
      service:
        name: myapp
        state: restarted

  rescue:
    - name: 尝试恢复到以前的版本
      copy:
        src: /path/to/old/app/
        dest: /var/www/myapp/

    - name: 回滚后重启应用程序服务
      service:
        name: myapp
        state: restarted

  always:
    - name: 记录部署尝试
      debug:
        msg: "部署尝试完成。"

这种方法对于在单个部署阶段剧本中对相关任务进行分组很有用。

对于跨剧本回滚,让编排器做出决定。CI管道可以仅在后续阶段失败时运行回滚剧本。AWX作业工作流可以直观地建模相同的成功和失败分支。保持回滚命令简单且经过演练。

在阶段之间传递发布状态

顺序剧本通常需要一个共享的发布标识符。例如,部署阶段需要知道要安装哪个工件,冒烟测试需要知道期望哪个版本,回滚需要知道以前的版本。

显式传递该状态:

ansible-playbook -i inventories/production.ini playbooks/02-deploy-app.yml \
  -e release_version=2026.05.24.3 \
  -e artifact_url=https://artifacts.example.com/myapp/2026.05.24.3.tar.gz

在剧本内部,记录更改的内容:

- name: 写入当前发布标记
  ansible.builtin.copy:
    dest: /opt/myapp/current-release.txt
    content: "{{ release_version }}\n"
    owner: root
    group: root
    mode: "0644"

该标记在事件期间很有帮助。当有人SSH到主机时,他们可以看到主机认为它正在运行的版本。您还可以让冒烟测试剧本读取该标记并将其与预期的发布版本进行比较。

高级考虑

在剧本之间管理状态

有时,一个剧本中的任务需要通知另一个剧本其结果。您可以通过以下方式实现:

  • 事实缓存: 如果启用了事实缓存,一个剧本收集的事实可以供同一Ansible会话中运行的后续剧本使用。
  • 临时文件/数据库: 将关键状态信息或输出写入临时文件或专用状态表,后续剧本可以读取。

优先使用显式状态而不是隐藏状态。事实缓存可能很有用,但当值过时或一个运行器启用了缓存而另一个没有时,它也可能让人困惑。发布文件、工件元数据、CI变量和部署记录更容易检查。

版本控制和编排工具

对于复杂的编排,考虑将您的顺序Ansible剧本集成到更高级别的工具中:

  • CI/CD管道: 像Jenkins、GitLab CI、GitHub Actions或CircleCI这样的工具非常适合定义和触发多阶段部署。您在管道配置中定义ansible-playbook命令的顺序。
  • Ansible Tower/AWX: 对于企业级编排,Ansible Tower(现在是自动化平台)或其开源对应物AWX提供了一个强大的UI,用于调度、监控和管理可以链接多个剧本的复杂作业模板。

如果多个人部署同一个系统,集中编排就不仅仅是方便,更是控制。它为您提供一致的清单、凭据、审计日志、批准以及哪个阶段失败的可见历史。这些细节在生产事件期间很重要。

使用标签进行精细控制

虽然我们主张为不同的阶段使用单独的剧本,但您也可以在剧本中使用标签。如果您有一个非常大的单个阶段剧本(例如,数据库迁移),您可以标记特定任务,并使用ansible-playbook --tags <tag_name>仅运行这些任务。

这更多是关于阶段内的精细控制,而不是阶段之间的顺序。

多阶段部署的最佳实践

  • 保持剧本专注: 每个剧本应做好一件事(例如,数据库迁移、应用程序部署)。
  • 清晰命名剧本: 使用反映阶段和顺序的命名约定(例如,01-02-)。
  • 实现幂等性: 确保所有任务都是幂等的,以允许安全重试。
  • 测试回滚: 定期测试您的回滚程序,以确保它们按预期工作。
  • 使用版本控制: 将所有剧本和清单文件存储在版本控制系统(如Git)中。
  • 自动化编排: 使用CI/CD管道或Ansible Tower/AWX等工具来自动化执行您的顺序剧本。
  • 记录您的工作流: 清晰记录阶段、其目的、依赖关系和回滚程序。
  • 使冒烟测试真实: 检查实际的端点、登录路径、队列工作器或重要的后台作业。简单的进程检查是不够的。
  • 保护生产清单: 为暂存和生产使用单独的清单和凭据。--limit中的拼写错误不应部署到错误的位置。
  • 尽可能使用串行推出: serial允许您一次更新少数主机,并在影响整个机器群之前停止。
- name: 逐步部署应用程序
  hosts: web
  serial: 2
  tasks:
    - name: 安装发布
      ansible.builtin.unarchive:
        src: "{{ artifact_path }}"
        dest: /opt/myapp/releases/{{ release_version }}
        remote_src: true

使用serial,Ansible分批处理主机。如果您的应用程序无法在不丢弃活动请求的情况下重启,请将其与负载均衡器排空结合使用。

一个具体的部署流程

一个安全的Web应用程序Ansible部署可能如下所示:

00-preflight.yml检查磁盘空间,确认目标发布存在,验证数据库连接,并确保主机处于预期环境中。它不会更改系统。

01-migrate-db.yml仅运行向后兼容的迁移。它记录迁移版本,如果数据库已经领先于请求的发布,则失败。

02-deploy-app.yml下载工件,将其解压到版本化的发布目录中,模板化配置,并更新current符号链接。它尚未重启服务。

03-reload-services.yml从负载均衡器中排空每个主机,重新加载或重启服务,等待本地健康端点,然后将主机恢复服务。

04-smoke-test.yml通过用户使用的相同路径调用公共端点。它检查响应体或版本端点,而不仅仅是来自负载均衡器默认页面的200

这个流程比一键重启慢。当部署中途失败时,它也更容易推理。

使这行之有效的习惯

顺序Ansible剧本在每一个都有狭窄的契约时效果最好:它期望什么,它更改什么,它如何证明成功,以及如果失败该怎么做。这个契约比YAML文件的数量更重要。

从反映您真实风险的阶段开始:预检、迁移、部署、重新加载、冒烟测试、回滚。保持命令简单。在需要之前测试回滚。当部署中断时,您应该能够指向确切的失败阶段,并决定下一步,而无需重新阅读整个自动化树。