1
2
3
4
5
作者:李晓辉

微信联系:Lxh_Chat

联系邮箱: 939958092@qq.com

Ansible 提供了滚动更新的功能,这种策略通过将服务器部署分批进行,帮助实现不中断服务的基础设施更新。

如果在更新过程中发生了意外问题,Ansible 可以暂停部署并将故障局限于某个特定批次的服务器。通过结合监控和测试,你可以设置 Playbook 执行以下操作:

  • 对受影响的服务器批次进行配置回滚。
  • 将出问题的服务器隔离,进行问题分析。
  • 向相关方发送部署进度和状态通知。

控制批次⼤⼩

默认情况下,Ansible 会在所有主机上顺序执行任务。如果某个任务因错误无法成功,且 play 在所有主机上并行运行,那么当 Ansible 到达这个任务时,所有主机都可能失败并导致 play 中止。这样一来,所有主机都会受到影响,可能导致服务中断。

为了避免这种情况,你可以选择先在部分主机上执行 play,再逐步处理下一批主机。如果某一批主机出现问题,只有该批次的主机会受到影响,而不是所有主机都出问题。同时,你也可以设置 Ansible 在所有主机执行任务之前,如果出现过多失败的情况,提前停止整个 play,从而避免不必要的影响。

固定批次⼤⼩

如果你想分批处理主机,可以在 play 里用 serial 关键字来设置每批次处理的主机数量。Ansible 会先处理完当前批次的主机任务,然后才会开始下一个批次。如果当前批次的主机全都出问题,整个 play 就会停下来,不会继续执行后面的批次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
---
- name: Update web servers in batches
hosts: web_servers
serial: 2 # 每次处理2台主机
tasks:
- name: Update httpd package
yum:
name: httpd
state: latest

- name: Restart the web server
service:
name: httpd
state: restarted

handlers:
- name: Restart nginx if needed
service:
name: nginx
state: restarted

解释:

  • serial: 2 表示每次只会处理 2 台 web_servers 上的任务。
  • tasks 部分更新 httpd 包并重启 Web 服务器。
  • handlers 部分定义了如果需要重启 Nginx 服务的处理程序。

执行流程:

  1. Ansible 会先在前两台 web_servers 上执行更新任务。
  2. 如果这两台主机触发了 nginx 重启的通知,Ansible 会立即执行处理程序。
  3. 完成前两台主机的任务后,Ansible 会继续处理下一个批次的主机,直到所有主机都更新完毕。

按百分⽐划分批次

如果你想按百分比来分批处理主机,也就是每次处理一部分主机,你可以在 serial 里设置百分比。比如,你有 10 台主机,设置 serial: 20% 就会每次处理 2 台主机(10 台主机的 20%)。这种方法非常适合大规模更新,能有效避免一次性更新所有主机时出现问题,出错的影响也能更小。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
---
- name: Update web servers in batches by percentage
hosts: web_servers
serial: 20% # 每次处理20%的主机
tasks:
- name: Update httpd package
yum:
name: httpd
state: latest

- name: Restart the web server
service:
name: httpd
state: restarted

handlers:
- name: Restart nginx if needed
service:
name: nginx
state: restarted

解释:

  • serial: 20% 表示每次处理 web_servers 列表中的 20% 主机。如果有 10 台主机,那么每次会处理 2 台。
  • tasks 部分,我们更新 httpd 包并重启 Web 服务器。
  • handlers 部分定义了,如果有主机触发重启 nginx 服务的通知,Ansible 会自动执行处理程序。

执行流程:

  1. Ansible 会先处理前 20%(2 台)主机,执行更新和重启操作。
  2. 如果需要,处理程序会立即执行,比如重启 nginx
  3. 完成当前批次后,Ansible 会继续处理接下来的批次,直到所有主机都完成更新。

动态设置批次大小

你可以在 play 执行过程中灵活调整批次大小!比如,开始时用 1 台主机进行测试,如果成功了,你可以把批次大小增加到 10%,然后再增加到 50%,最后再处理剩余的主机。这种逐步增加批次大小的策略特别适合在进行大规模更新时,能有效地控制风险,逐步验证每个阶段的变化。

你可以通过将 serial 设置为一个值列表来实现这个目标。列表中的每个值代表一个阶段的批次大小,可以是整数也可以是百分比,并且它们会按顺序逐个应用。如果你使用了百分比,Ansible 会根据当前剩余的主机数量来计算批次大小,而不是基于整个主机组的总数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
---
- name: Gradually update web servers in batches
hosts: web_servers
serial:
- 1 # 第一个阶段处理1台主机
- 10% # 第二个阶段处理10%的主机
- 50% # 第三个阶段处理50%的主机
- 100% # 最后阶段处理剩余的所有主机
tasks:
- name: Update httpd package
yum:
name: httpd
state: latest

- name: Restart the web server
service:
name: httpd
state: restarted

handlers:
- name: Restart nginx if needed
service:
name: nginx
state: restarted

解释:

  • serial 关键字设置为一个列表,逐步增加批次大小。首先处理 1 台主机,接着 10%(例如,10 台主机中的 1 台),然后是 50%,最后处理剩余的所有主机。
  • tasks 部分,我们更新 httpd 包并重启 Web 服务器。
  • handlers 部分定义了如果某台主机触发重启 nginx 服务的通知,Ansible 会自动执行处理程序。

运行逻辑:

  1. 第一批: 先用 1 台服务器试水,如果有问题,就不会影响其他服务器。
  2. 第二批: 如果第一台服务器成功,下一批增加到 10%(假设 100 台服务器,那就是 10 台)。
  3. 第三批: 继续增加到 50%,加快更新速度。
  4. 第四批: 剩下的服务器一次性更新完。

这个方法特别适合 高可用集群的滚动更新,避免一次性把所有服务器都搞崩溃。

错误处理

默认情况下,Ansible 会尽可能多地调度主机来执行 play。如果某台主机在某个任务上失败,Ansible 会把它从 play 中移除,但不会影响其他主机的执行,除非所有主机都失败,play 才会终止。

不过,如果你使用了 serial 指令,把主机划分成不同的批次,那么失败的处理方式就不太一样了。如果某个批次里的所有主机都失败,Ansible 不会 继续执行下一个批次,而是会直接停止整个 play。这样可以防止继续尝试对可能存在相同问题的主机执行任务,避免影响更大范围的系统。

在 Ansible 里,每个批次的活跃主机列表都会存储在 ansible_play_batch 变量中。这个列表会在任务执行过程中动态更新,如果某台主机失败,它会被从 ansible_play_batch 里移除,确保后续任务不会再尝试对它执行。这样,Ansible 可以更灵活地管理批次中的主机状态,保证任务执行的稳定性。

避免全军覆没

你有没有遇到过这样的情况:用 Ansible 批量更新服务器,结果一不小心全军覆没?如果一次性对 100 台服务器执行任务,其中 99 台失败,只有 1 台成功,Ansible 还是会继续执行剩下的任务,但这个时候其实已经没啥意义了。

别担心,咱们可以用 serial 关键字,让 Ansible 按批次执行任务,这样就能更好地控制风险,防止所有服务器一起挂掉。

原始写法(所有服务器一起跑,风险大)

这个写法的问题是:如果 Step One 失败了 99 台,只有 1 台成功,那 Ansible 还是会继续跑 Step Two,这就没什么意义了。

1
2
3
4
5
6
7
8
9
---
- name: Update Webservers
hosts: web_servers
tasks:
- name: Step One
ansible.builtin.shell: /usr/bin/some_command

- name: Step Two
ansible.builtin.shell: /usr/bin/some_other_command

改进版(每次 2 台服务器批量执行)

运行逻辑:

  • 先执行 2 台 服务器,如果成功,继续下一批。
  • 如果某一批 2 台服务器全部失败,Ansible 停止 Playbook,不再执行剩下的服务器。
  • 这样做能减少故障范围,及时发现问题。
1
2
3
4
5
6
7
8
9
10
---
- name: Update Webservers in Batches
hosts: web_servers
serial: 2
tasks:
- name: Step One
ansible.builtin.shell: /usr/bin/some_command

- name: Step Two
ansible.builtin.shell: /usr/bin/some_other_command

指定容错率

默认情况下,Ansible 只有在 当前批次中的所有主机 都失败时才会停止 Playbook 运行。但在实际场景中,你可能更希望它更早终止,而不是等到整批失败才行动。

比如,如果 整个清单中的主机 超过一定比例失败,就应该立刻停止执行,而不是继续尝试剩下的服务器。甚至,你可能希望 只要有任何任务失败,就立即终止 Playbook,避免让问题进一步扩大。

这时候,max_fail_percentage 就派上用场了!

在 Playbook 里添加 max_fail_percentage,可以让 Ansible 在 失败率超过设定值 时,自动中止执行,防止更多服务器受到影响。

1
2
3
4
5
6
7
8
---
- name: Update Web Servers Safely
hosts: web_servers
serial: 5 # 每次处理 5 台服务器
max_fail_percentage: 20 # 如果 20% 以上的服务器失败,停止 Playbook 执行
tasks:
- name: Step One - Run some command
ansible.builtin.shell: /usr/bin/some_command

max_fail_percentage: 20:如果 超过 20% 的服务器失败,Ansible 就会 停止 Playbook,不再继续执行。

  • 这样可以防止大规模宕机,给管理员留出修复问题的时间。
  • 例如:
    • 如果有 10 台服务器,最多 允许 2 台失败
    • 如果有 100 台服务器,最多 允许 20 台失败
    • 超过这个失败比例,Playbook 直接终止,不会继续执行。

错误总结

Ansible 失败行为归纳

  1. 默认情况下(无 serialmax_fail_percentage

    • 所有主机同一批次 执行 play。
    • 只有当所有主机都失败 时,play 才会失败。
    • 否则,Ansible 继续执行剩余任务。
  2. 使用 serial(分批执行)

    • 主机按 serial 指定的数量 分批 运行 play。
    • 如果某个批次中所有主机都失败,则 play 立即失败,不会执行下一个批次。
    • 但之前 成功的批次不会受影响,只影响当前批次及后续未执行的批次。
  3. 使用 max_fail_percentage(失败率控制)

    • 允许设置 最大可接受的失败比例(如 20%)。
    • 如果某个批次中失败的主机超过该比例,则 play 立即失败,不继续执行。
    • 避免等所有主机都失败后才终止 play,提高容错性。
  4. 如果某个 play 失败,Ansible 的行为

    • playbook 立即停止,不再执行后续的 play。
    • 避免继续执行可能导致更严重的错误。
策略失败行为
默认(无 serial & max_fail_percentage只有所有主机都失败时,play 才会失败
使用 serial如果某个批次所有主机都失败,play 立即失败
使用 max_fail_percentage若某个批次失败主机超设定比例(如 20%),play 立即失败
play 失败时停止执行 playbook 中的剩余 play

只运行一次的任务

有时候,你可能不想让某个任务在所有服务器上都跑一遍,而是只执行一次,比如生成个全局配置文件、触发数据库迁移、发个通知啥的。这时候就可以用 run_once: true,让 Ansible 只在一台机器上跑这个任务,而不是整个批次的每台都跑一遍。

比如,你有 100 台服务器要更新,每批 10 台在滚动更新,但你只想生成 一次 配置文件,而不是 10 次、20 次地重复搞,那就加上 run_once: true,Ansible 就会乖乖只在第一批的某台机器上跑一次,剩下的服务器就直接用这个文件了。

简单说,run_once 适合那些 一次搞定,大家都能用 的任务,比如 创建配置、数据库变更、触发 CI/CD、发通知 这种。

1
2
3
4
5
6
7
8
9
10
11
---
- name: Example Playbook with run_once
hosts: all
tasks:
- name: Gather host information (only once)
ansible.builtin.setup:
run_once: true # 这条任务只会在其中一个主机上执行一次

- name: Print successful hosts
ansible.builtin.debug:
msg: "This host is part of the group."
  1. hosts: all:这个 Playbook 会在所有的主机上执行。
  2. run_once: true:确保 “Gather host information” 这个任务只会在整个 Playbook 执行中运行一次。即使你有多个主机,这个任务也只会在第一个主机上执行一次,而不会在其他主机上重复执行。
  3. ansible.builtin.setup:这是一个内建模块,用来收集目标主机的系统信息。
  4. ansible.builtin.debug:这个任务会在每个主机上执行,显示一条调试信息,表示该主机是参与的主机之一。

结果:

在执行此 Playbook 时,setup 任务只会在一个主机上执行一次,其他主机会跳过这个任务。debug 任务则会在所有主机上运行。

如果你想在每个批次里只给所有主机执行一次任务,而你的 Playbook 有多个批次,那就用 run_once: true。不过,如果你希望只在批次里的第一台主机上执行这个任务,可以加个条件 when: inventory_hostname == ansible_play_hosts[0]。这样,任务就只会在每批的第一台主机上跑,其他的主机就跳过了。

如果你把任务设置成每批次执行一次(用 run_once: true),那么每个批次都会运行这项任务一次。也就是说,如果你把主机分成了多个批次,每个批次的第一台主机会执行这项任务,然后跳过其他的主机。所以,任务会在每个批次的第一台主机上跑一次,而不是对每个主机都执行一次。

假设你有10台主机,分成5个批次,如果你在任务中使用了 run_once: true,这意味着任务只会在每个批次的第一台主机上执行一次,而其他主机都不会执行该任务。

所以,如果你将 10 台主机分成 5 个批次,每批次有 2 台主机,那么任务就会在每个批次的第一台主机上执行 5 次。这样就确保了任务仅在每批次的第一台主机上执行,而不是每台主机都执行一次。

举个例子,假设有 10 台主机,任务是“执行一些命令”,如果使用 run_once: true,那么:

  • 第1批次:任务会在第 1 台主机上执行(批次中的第一台主机)
  • 第2批次:任务会在第 3 台主机上执行(批次中的第一台主机)
  • 第3批次:任务会在第 5 台主机上执行
  • 第4批次:任务会在第 7 台主机上执行
  • 第5批次:任务会在第 9 台主机上执行