测试策略

将测试与 Ansible 剧本集成

人们经常问:“如何将测试最佳地与 Ansible 剧本集成?”有很多选项。Ansible 实际上被设计为一个“快速失败”且有序的系统,因此它使得将测试直接嵌入 Ansible 剧本中变得很容易。在本章中,我们将深入探讨将基础设施测试集成的一些模式,并讨论可能合适的测试级别。

注意

本章是关于测试您正在部署的应用程序,而不是关于如何测试开发期间的 Ansible 模块。有关该内容,请转到“开发”部分。

通过在您的部署工作流程中加入一定程度的测试,当代码进入生产环境时,就会有更少的意外情况,而且在很多情况下,测试可以用于生产环境,以防止失败的更新迁移到整个安装中。由于它是基于推送的,因此它也很容易在 localhost 或测试服务器上运行这些步骤。Ansible 允许您在升级工作流程中插入任意多的检查和平衡,以满足您的需要。

合适的测试级别

Ansible 资源是理想状态的模型。因此,不需要测试服务是否已启动、包是否已安装或其他类似的事情。Ansible 是确保这些事情在声明上为真的系统。相反,在您的剧本中断言这些事情。

tasks:
  - ansible.builtin.service:
      name: foo
      state: started
      enabled: true

如果您认为服务可能没有启动,最好的做法是请求它启动。如果服务无法启动,Ansible 会发出相应的警报。(不要将其与服务是否正在执行某种功能相混淆,我们将在后面更详细地介绍如何做到这一点)。

将检查模式用作漂移测试

在上面的设置中,Ansible 中的 --check 模式也可以用作一层测试。如果将部署剧本运行到现有系统上,使用 --check 标志对 ansible 命令进行操作,将报告 Ansible 是否认为它需要进行任何更改才能将系统置于期望状态。

这可以提前让您知道是否有必要将系统部署到给定系统上。通常情况下,脚本和命令在检查模式下不会运行,因此如果您希望某些步骤即使在使用 --check 标志时也以正常模式执行,例如对 script 模块的调用,请为这些任务禁用检查模式。

roles:
  - webserver

tasks:
  - ansible.builtin.script: verify.sh
    check_mode: false

适用于测试的模块

某些剧本模块特别适合进行测试。下面是一个确保端口打开的示例。

tasks:

  - ansible.builtin.wait_for:
      host: "{{ inventory_hostname }}"
      port: 22
    delegate_to: localhost

这是一个使用 URI 模块来确保 Web 服务返回的示例。

tasks:

  - action: uri url=https://www.example.com return_content=yes
    register: webpage

  - fail:
      msg: 'service is not happy'
    when: "'AWESOME' not in webpage.content"

很容易在远程主机上推送任意脚本(使用任何语言),如果脚本具有非零返回代码,则脚本会自动失败。

tasks:

  - ansible.builtin.script: test_script1
  - ansible.builtin.script: test_script2 --parameter value --parameter2 value

如果使用角色(您应该使用,角色很棒!),script 模块推送的脚本可以放在角色的‘files/’ 目录中。

assert 模块使得验证各种真值变得非常容易。

tasks:

   - ansible.builtin.shell: /usr/bin/some-command --parameter value
     register: cmd_result

   - ansible.builtin.assert:
       that:
         - "'not ready' not in cmd_result.stderr"
         - "'gizmo enabled' in cmd_result.stdout"

如果您需要测试非 Ansible 配置声明性设置的文件是否存在,‘stat’ 模块是一个不错的选择。

tasks:

   - ansible.builtin.stat:
       path: /path/to/something
     register: p

   - ansible.builtin.assert:
       that:
         - p.stat.exists and p.stat.isdir

如上所述,不需要检查命令的返回值。Ansible 会自动检查它们。与其检查用户是否存在,不如考虑使用 user 模块来使其存在。

Ansible 是一个快速失败的系统,因此当创建该用户时出现错误时,它会停止剧本运行。您无需在后面进行检查。

测试生命周期

如果您在剧本中写入了一些基本的应用程序验证代码,它们将在每次部署时运行。

因此,将系统部署到本地开发 VM 和暂存环境中都将验证事情是否按计划进行,然后进行生产部署。

您的工作流程可能类似于以下步骤。

- Use the same playbook all the time with embedded tests in development
- Use the playbook to deploy to a staging environment (with the same playbooks) that simulates production
- Run an integration test battery written by your QA team against staging
- Deploy to production, with the same integrated tests.

如果您的系统是生产 Web 服务,那么您的 QA 团队应该编写类似于集成测试套件的内容。这将包括诸如 Selenium 测试或自动 API 测试之类的内容,通常不会嵌入到您的 Ansible 剧本中。

但是,将一些基本健康检查包含在您的剧本中是很有意义的,在某些情况下,甚至可以对远程节点运行 QA 套件的子集。下一部分将介绍这些内容。

将测试与滚动更新集成

如果您已阅读了有关 控制任务运行的位置:委派和本地操作 的内容,您可能会很快发现滚动更新模式可以扩展,并且您可以使用剧本运行的成功或失败来决定是否将机器添加到负载均衡器中。

这是嵌入式测试的巨大成果。

---

- hosts: webservers
  serial: 5

  pre_tasks:

    - name: take out of load balancer pool
      ansible.builtin.command: /usr/bin/take_out_of_pool {{ inventory_hostname }}
      delegate_to: 127.0.0.1

  tasks:

    - ansible.builtin.include_role:
        name: "{{ item }}"
      loop:
        - common
        - webserver

    - name: run any notified handlers
      ansible.builtin.meta: flush_handlers

    - name: test the configuration
      ansible.builtin.include_role:
        name: apply_testing_checks

  post_tasks:

    - name: add back to load balancer pool
      ansible.builtin.command: /usr/bin/add_back_to_pool {{ inventory_hostname }}
      delegate_to: 127.0.0.1

当然,在上面的示例中,"从池中取出"和"添加回来"步骤将被替换为对 Ansible 负载均衡器模块或适当的 shell 命令的调用。您还可以添加一些步骤,使用监控模块来启动和结束机器的中断窗口。

但是,您可以从上面的示例中看到,测试用作门控机制 - 如果未执行“apply_testing_checks”步骤,则该机器将不会返回池中。

阅读有关“max_fail_percentage”的委派章节,您还可以控制多少次测试失败会阻止滚动更新继续进行。

上面的方法也可以修改为从测试机器远程运行步骤以针对机器执行操作。

---

- hosts: webservers
  serial: 5

  pre_tasks:

    - name: take out of load balancer pool
      ansible.builtin.command: /usr/bin/take_out_of_pool {{ inventory_hostname }}
      delegate_to: 127.0.0.1

  roles:

     - common
     - webserver

  tasks:
     - ansible.builtin.script: /srv/qa_team/app_testing_script.sh --server {{ inventory_hostname }}
       delegate_to: testing_server

  post_tasks:

    - name: add back to load balancer pool
      ansible.builtin.command: /usr/bin/add_back_to_pool {{ inventory_hostname }}
      delegate_to: 127.0.0.1

在上面的示例中,从测试服务器运行脚本以针对远程节点执行操作,然后再将其放回池中。

如果出现问题,请使用 Ansible 自动生成的重试文件修复少数出现故障的服务器,以仅对这些服务器重复部署。

实现持续部署

如果需要,上述技术可以扩展以实现持续部署实践。

工作流程可能如下所示。

- Write and use automation to deploy local development VMs
- Have a CI system like Jenkins deploy to a staging environment on every code change
- The deploy job calls testing scripts to pass/fail a build on every deploy
- If the deploy job succeeds, it runs the same deploy playbook against production inventory

一些 Ansible 用户使用上述方法每小时部署六到十二次,而无需使所有基础设施离线。如果您希望达到这种水平,自动化 QA 的文化至关重要。

如果您仍然需要进行大量的手动 QA,那么您仍然应该手动决定是否部署,但它仍然有助于利用上一节中的滚动更新模式,并使用诸如‘script’、‘stat’、‘uri’和‘assert’之类的模块来加入一些基本健康检查。

结论

Ansible 认为您不需要其他框架来验证基础设施是否确实为真。这是因为 Ansible 是一个基于顺序的系统,它会立即对主机上的未处理错误失败,并阻止对该主机的进一步配置。这会将错误传播到顶部,并在 Ansible 运行结束时显示在摘要中。

但是,由于 Ansible 被设计为一个多层编排系统,因此将测试加入剧本运行的末尾变得非常容易,可以使用松散的任务或角色来完成。当与滚动更新一起使用时,测试步骤可以决定是否将机器放回负载均衡池中。

最后,由于 Ansible 错误会一直传播到 Ansible 程序本身的返回代码,而且 Ansible 默认情况下以一种简单的基于推送的模式运行,因此如果您希望在构建环境中使用它来作为持续集成/持续交付管道的一部分推出系统,则 Ansible 是一个很好的选择,如上文所述。

重点不应放在基础设施测试上,而应放在应用程序测试上,因此我们强烈建议您与您的 QA 团队协商,询问每次部署开发 VM 时,哪些测试值得运行,以及他们希望在每次部署时对暂存环境运行哪些测试。显然,在开发阶段,单元测试也很棒。但不要对您的剧本进行单元测试。Ansible 声明性地描述了资源的状态,因此您无需进行单元测试。但是,如果您想确定某些事情,这是很棒的,并且诸如 stat/assert 之类的模块非常适合这种情况。

总的来说,测试是一件非常组织性和站点特定的事情。每个人都应该进行测试,但对于您的环境来说,最合理的方法会根据您正在部署的内容以及谁在使用它而有所不同 - 但每个人都将从更强大、更可靠的部署系统中受益。

另请参见

集合索引

浏览现有的集合、模块和插件

使用剧本

剧本介绍

控制任务运行的位置:委派和本地操作

委派,适用于处理负载均衡器、云和本地执行的步骤。

用户邮件列表

有问题?欢迎加入 Google 论坛!

实时聊天

如何加入 Ansible 聊天频道