RHCA - DO374 | Day08:配置滚动更新

一、委派任务及系统指标

1.   委派task

Ansible在运行一个play时,有时候会需要代表受管主机在另一台主机上执行任务,而不是直接在受管主机上执行任务。例如,你可能需要登录到网络设备以更改DHCP配置,确保Active Directory域中存在某些组,或者使用未能由受管主机提供的工具与服务的API通信。在这种情况下,可以委派(delegate)任务在另一台主机上运行,而不是在当前受管主机上运行

通过使用delegate_to指令将任务委派给指定的主机,被委派的主机可以是主机清单内的其他主机,也可以是清单之外的其他主机

1)任务委派(Delegation)至执行环境或本地环境

在 Ansible 中,任务委派(Delegation) 允许将某个任务的执行从默认的受管主机(Managed Nodes)转移到指定的其他环境,例如:

  • 执行环境(Execution Environment, EE)(容器化环境)

  • 控制节点(localhost)

  • 其他特定主机

最常见的委派任务的地方是自动化执行环境localhost环境

委派目标:

  • delegate_to: localhost 任务在 Ansible 控制节点 上执行

  • delegate_to: "{{ ansible_ee_host }}" 任务在 执行环境容器 内执行(需确保容器具备所需依赖)


示例1:先在受管机上运行uname -a命令,然后代表受管机在host.lab.example.com上运行uname -a命令。其中host.lab.example.com是被委派到的主机。

---
- name: Delegation Example
  hosts: demo.lab.example.com
  become: false
 
  tasks:
    - name: Get system information    # 受管主机 执行uname -a
      ansible.builtin.command: uname -a
      register: managed_host
 
    - name: Display demo system information
      ansible.builtin.debug:
      var: managed_host
 
    - name: Get system information    # 委派给 host.lab.example.com 执行uname -a
      ansible.builtin.command: uname -a
      delegate_to: host.lab.example.com
      register: delegated
 
    - name: Display localhost system information
      ansible.builtin.debug:
        var: delegated

示例2:第1个任务依次委派给lbservers组中的每个负载均衡器,用来从所有的负载均衡器上删除受管的成员主机。第2个任务未被委派,用来停止受管主机上的web服务器。这两个任务都针对play的每个主机运行。

- name: Remove the server from HAProxy
  community.general.haproxy:
    state: disabled    # 删除负载均衡器上受管的后端成员主机
    host: "{{ ansible_facts['fqdn'] }}"
    socket: /var/lib/haproxy/stats
  delegate_to: "{{ item }}"
  loop: "{{ groups['lbservers'] }}"
 
- name: Make sure Apache HTTPD is stopped
  ansible.builtin.service:
    name: httpd
    state: stopped

示例3:如果想验证对一台受管主机上的服务的访问权限,也可以将任务委派给另一台主机。例如,检查从test-client.example.com客户机访问受管机是否能顺利通过防火墙。

- name: Access the web service
  uri:
   url: http://{{ ansible_facts['fqdn'] }}
   timeout: 5
  delegate_to: test-client.example.com

示例4:如果某个任务需要访问仅限控制节点(或执行环境)可用的 API(如云平台接口、内部服务),但受管主机无法直接连接该 API,此时可以将任务委派给控制节点或执行环境执行。

- name: Get information about controller instance
  ansible.builtin.uri:
    url: https://{{ ansible_facts['fqdn'] }}/api/v2/ping/
    method: GET
    validate_certs: no
    return_content: yes
  delegate_to: localhost    # 强制任务在控制节点上运行(而非受管主机)。
  register: controller_ping

2)为什么需要委派?

场景

问题

解决方案

受管主机无法访问外部服务

如调用内部 API、下载受限资源

委派至可访问该服务的控制节点

依赖控制节点本地工具

如需使用docker、kubectl 等仅安装在控制机的命令

委派至localhost

集中化操作

如批量注册主机、统一生成密钥

在单一节点执行后分发结果

2.   委派fact

在前面的示例2中,将系统指标 ansible_facts['fqdn'] 委派给了每个负载平衡器的任务,任务中使用的是受管主机的完全限定域名(FQDN),而不是当前负载均衡器的域名。

1)任务委派(Delegation)中的 Facts 传递问题与解决方案

在 Ansible 中,默认情况下,即使任务被 委派(delegate_to) 到其他主机(如 localhost 或执行环境),任务仍会使用原始受管主机(inventory_hostname)的变量和 Facts,而非被委派主机的 Facts。

假设以下场景:

  • 受管主机:web1.example.com(FQDN: web1.example.com)

  • 委派目标:localhost(FQDN: control-node.example.com)

- name: 打印主机名(委派至 localhost)
  debug:
    msg: "FQDN: {{ ansible_facts['fqdn'] }}"
  delegate_to: localhost

输出结果:msg: "FQDN: web1.example.com" # 使用的是受管主机的 FQDN,而非 localhost 的!

即使任务在 localhost 上运行,仍会错误地使用 web1.example.com 的 Facts。

在有些情况下,可能希望委派任务使用的系统指标也是被委派的主机的(而不是当前的受管主机的)。若要满足这个要求,请将delegate_facts指令设置为true,表示在委派任务的同时也同时委派系统指标(facts)。

- name: Delegate Fact Example
  hosts: localhost
  gather_facts: false
  tasks:
    - name: Set a fact in delegated task on demo
      ansible.builtin.set_fact:
        myfact: Where am I set?
      delegate_to: demo.lab.example.com
      delegate_facts: true
 
    - name: Display the facts from demo.lab.example.com
      ansible.builtin.debug:
        msg: "{{ hostvars['demo.lab.example.com']['myfact'] }}"

上面这个例子中,整个play是为受管机localhost运行,但是其中第1个任务被委派给主机demo.lab.example.com。任务中的delegate_facts指令,用来告诉Ansible将收集的系统指标保存到被委派的主机的 hostvars['demo.lab.example.com'] 命名空间,而不是受管主机上的 hostvars['localhost'] 命名空间。

2)何时需要 delegate_facts: true?

场景

是否需要delegate_facts: true

原因

任务依赖被委派主机的系统状态

需要

如检查localhost的磁盘空间、网络接口

任务仅使用受管主机的变量

不需要

如调用 API 时仍需受管主机的 IP 地址

混合使用双方变量

需谨慎

可能需手动组合变量(如hostvars[inventory_hostname])

注意事项:

  • 性能影响:设置 delegate_facts: true 会强制 Ansible 收集被委派主机的 Facts,可能增加任务时间。

  • 变量作用域:委派任务的 register 变量仍属于原始受管主机的上下文,后续任务可直接使用。

3.   课堂练习:委派task任务及fact系统指标

开始练习(部署环境):

以用户student登入workstation虚拟机,使用lab命令来构建案例环境。

[student@workstation ~]$ lab start update-delegation

步骤说明:

1)从https://git.lab.example.com/student/update-delegation.git仓库克隆项目到/home/student/git-repos目录,并创建exercise分支

创建/home/student/git-repos目录,并进入此目录:

[student@workstation ~]$ mkdir -p ~/git-repos/
[student@workstation ~]$ cd ~/git-repos/

从Git仓库克隆项目,并进入项目目录:

[student@workstation git-repos]$ git clone https://git.lab.example.com/student/update-delegation.git
[student@workstation git-repos]$ cd update-delegation

检出exercise分支:

[student@workstation update-delegation]$ git checkout -b exercise

2)检查清单文件inventory.yml,确认web_servers组包括A-F这6个主机

[student@workstation update-delegation]$ cat inventory.yml
web_servers:
  hosts:
    server[a:f].lab.example.com:

3)为剧本query_times.yml添加一个任务,用来完成以下任务:查询web_servers组中每个主机的时间;将结果保存到workstation主机的/tmp/times.txt文件中

任务用来在workstation主机上生成/tmp/times.txt文件,文件中为每个服务器添加两行文本:第1行是受管主机的名称,第2行是受管主机上的时间。

这里需要将每个受管主机的任务委派给workstation,任务应包括以下内容:

[student@workstation update-delegation]$ vim query_times.yml
---
- name: Query server times and store them locally
  hosts: web_servers
  gather_facts: true
  become: true

  tasks:   # 新增task任务
    - name: Save server times to workstation
      ansible.builtin.lineinfile:
        path: /tmp/times.txt
        state: present
        mode: '0644'
        create: true    # 如果文件不存在则创建文件
        insertafter: EOF    # 插入新内容在文件末尾
        line: |
          {{ ansible_facts['fqdn'] }}
          {{ '%c' | strftime(ansible_facts['date_time']['epoch']) }}
      delegate_to: workstation

4)使用ansible-navigator命令运行剧本,测试任务委派

[student@workstation update-delegation]$ ansible-navigator run -m stdout query_times.yml
PLAY [Query server times and store them locally] *******************************
TASK [Gathering Facts] *********************************************************
ok: [serverb.lab.example.com]
ok: [serverc.lab.example.com]
ok: [servere.lab.example.com]
ok: [servera.lab.example.com]
ok: [serverd.lab.example.com]
ok: [serverf.lab.example.com]
TASK [Save server times to workstation] ****************************************
changed: [serverc.lab.example.com -> workstation]
changed: [servera.lab.example.com -> workstation]
changed: [serverd.lab.example.com -> workstation]
changed: [serverb.lab.example.com -> workstation]
changed: [servere.lab.example.com -> workstation]
changed: [serverf.lab.example.com -> workstation]
PLAY RECAP *********************************************************************
servera.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverb.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverc.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverd.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
servere.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverf.lab.example.com    : ok=2    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

5)确认剧本已正确收集到受管机的时间并将结果保存到workstation主机的/tmp/times.txt文件中

[student@workstation update-delegation]$ cat /tmp/times.txt
serverb.lab.example.com Thu Dec 1 14:49:24 2022 
servera.lab.example.com Thu Dec 1 14:49:24 2022
serverd.lab.example.com Thu Dec 1 14:49:24 2022
serverc.lab.example.com Thu Dec 1 14:49:24 2022
servere.lab.example.com Thu Dec 1 14:49:24 2022
serverf.lab.example.com Thu Dec 1 14:49:25 2022

注意,在你的文件中主机的实际顺序可能不一样;如果多次运行剧本,每个主机可能也会有多个条目。

结束练习(清理环境):

在workstation虚拟机上,切换到student用户主目录,使用lab命令来清理案例环境,确保先前练习的资源不会影响后续的练习。

[student@workstation ~]$ lab finish update-delegation

二、  并发和分批配置

1.   并发配置及forks的使用

Ansible在处理一个剧本时,会按照顺序来运行每一个play。在确定了这个play针对的目标受管主机后,Ansible按顺序运行每一个任务。通常情况下,所有的受管主机都必须成功完成一项任务,然后这些主机才能开始play中的下一项任务。

理论上,Ansible可同时连接到所有的目标受管主机来完成每一项任务。这种方法适用于少量主机,如果针对的是数百台主机,则会给控制节点和自动化执行环境带来沉重的压力。

Ansible同时建立的最大连接数(同时在多少个受管主机执行),由Ansible配置中的 forks参数 控制。默认情况下,这个参数设置为5,以下方法可以用来验证:

[user@host demo]$ ansible-navigator config dump -m stdout
...output omitted...
DEFAULT_FORCE_HANDLERS(default) = false
DEFAULT_FORKS(default) = 5
DEFAULT_GATHERING(default) = implicit
...output omitted...

或者

[user@host demo]$ ansible-navigator config list -m stdout
...output omitted...
DEFAULT_FORKS:
default: 5
description: Maximum number of forks Ansible will use to execute tasks on target hosts.
env:
name: ANSIBLE_FORKS
ini:
key: forks section: defaults
name: Number of task forks type: integer
...output omitted...

针对前面的命令输出结果,可以执行关键词检索。例如要查找默认的forks参数,可以输入/DEFAULT_FORKS后按Enter键。

例如,假设Ansible配置为默认5个fork,play任务针对的有10台受管主机。则Ansible按每5个主机分组,先在前5个主机上运行play中的第1个任务,再在后5个主机上执行第1个任务。依次对每个任务执行此操作,直到play结束。

默认forks设置是比较保守的。如果play在执行环境中运行并用来管理Linux主机,则大多数任务都在受管主机上运行,执行环境的负载会较小。在这种情况下,通常可以将forks参数设置为更高的值,可能更接近100,从而增强性能。如果play在执行环境中运行了大量代码,也建议提高fork限制。

如果使用Ansible来管理路由器和交换机,那么这些模块大多在执行环境中运行,而不是在被管理的网络设备上运行,因此建议将fork的数量设少一些以减轻负担。

  • 通过修改Ansible配置文件,可以修改默认的forks设置。

  • 使用ansible-navigator run命令时,也可以提供-f--forks选项来指定fork的数量。

2.   分批次运行整个play

通常情况下,Ansible在运行play时按任务顺序执行 —— 即确保所有的受管主机先完成一个任务,然后再开始下一个任务。当所有的受管主机完成所有的任务后再运行所有通知的hander处理程序

  • 第1环节:5个主机,运行第1个任务

  • 第2环节:另5个主机,运行第1个任务

  • .. ..

  • 第3环节:5个主机,运行第2个任务

  • 第4环节:另5个主机,运行第2个任务

  • .. ..

但是,要求所有主机运行所有任务可能会导致不好的结果。例如,针对一个更新web服务器集群的play,更新过程中可能要停止每个web服务器的服务。如果所有的服务器都在同一个play中更新,它们就会同时停止服务,从而导致整个集群中止服务。


要避免这个问题,一种方法是按主机分批执行 —— 即使用serial关键字实现play在这些主机上的分批次运行,这样的话每一批主机会执行整个play,然后下一批主机再执行这个play。

  • 第1批:5个主机,运行第1个任务、第2个任务、……、最后一个任务;

  • 第2批:另5个主机,运行第1个任务、第2个任务、……、最后一个任务;

  •  .. ..

在下面的示例中,Ansible按每2个主机一批来运行play。Ansible先在前2个主机上运行play中的所有任务,如果有通知了handler处理程序再运行该处理程序;当这2个主机上的所有任务完成后,接下来再在另两个主机上重复上面的过程。以此类推,直到所有受管主机都更新为止。

---
- name: Rolling update
  hosts: webservers
  serial: 2
  tasks:
    - name: Latest apache httpd package is installed
      ansible.builtin.yum:
        name: httpd
        state: latest
      notify: restart apache
 
  handlers:
    - name: Restart apache
      ansible.builtin.service:
        name: httpd
        state: restarted

假设上一个示例中的webservers组在负载均衡器后端包含五个web服务器。当serial参数设置为2时,这个play最多同时在2台服务器上运行(也就是只有2台服务器在更新过程中可能会中断)。因此,五个web服务器中的大多数始终是可用的。相反,在不配置serial关键字的情况下,play和结果处理程序将同时在所有的五个web服务器上运行。这种方法可能会导致集群服务中断,因为所有web服务器上的web服务都将同时重新启动。

出于某些目的,每一批主机被视为需要运行完整个play的一个主机子集。这意味着,如果这一批主机运行失败了,那么play就会失败,从而导致整个剧本运行失败。在前面的场景中,serial参数设置为2,如果前2个主机上play运行失败,则整个剧本将会中止,并且play也不会在其余的3个主机上运行。这是一个有用的功能,因为当服务器的一个子集不可用时,会使服务降级而不是停机。

serial关键字既可以指定整数数量,也可以指定为百分比。这个百分比相对于play作用的主机总数,以便计算滚动更新的批次大小。无论百分比如何,每次传递的主机数总是一个或者多个。

3.   课堂练习:并发和分批配置

开始练习(部署环境):

以用户student登入workstation虚拟机,使用lab命令来构建案例环境。

[student@workstation ~]$ lab start update-parallelism

步骤说明:

1)从https://git.lab.example.com/student/update-parallelism.git仓库克隆项目到/home/student/git-repos目录,并创建exercise分支

创建/home/student/git-repos目录,并进入此目录:

[student@workstation ~]$ mkdir -p ~/git-repos
[student@workstation ~]$ cd ~/git-repos

从Git仓库克隆项目,并进入项目目录:

[student@workstation git-repos]$ git clone https://git.lab.example.com/student/update-parallelism.git
[student@workstation git-repos]$ cd update-parallelism

检出exercise分支:

[student@workstation update-parallelism]$ git checkout -b exercise

2)检查项目目录以熟悉项目文件

检查ansible.cfg配置文件,可看到清单文件已设置为inventory、并发forks参数设置为4,回调插件ansible.posix.timer和ansible.posix.profile_tasks已经启用:

[student@workstation update-parallelism]$ cat ansible.cfg
[defaults]
inventory=inventory
remote_user=devops
forks=4
callbacks_enabled=ansible.posix.timer,ansible.posix.profile_tasks

检查inventory清单内容,可看到定义了一个包含4个主机的webservers组:

[student@workstation update-parallelism]$ cat inventory
[webservers]
servera.lab.example.com
serverb.lab.example.com
serverc.lab.example.com
serverd.lab.example.com

检查update_httpd.yml剧本,这个剧本在webservers组的主机上运行,以确保安装最新版的httpd软件包,并且启用/启动httpd服务:

[student@workstation update-parallelism]$ cat update_httpd.yml
---
- name: Update the web server
  hosts: webservers
  become: true
  gather_facts: false

  tasks:
    - name: Latest httpd package installed
      ansible.builtin.yum:
        name: httpd
        state: latest
      notify:
        - Restart httpd

  handlers:
    - name: Restart httpd
      ansible.builtin.service:
        name: httpd
        enabled: true
        state: restarted

检查remove_httpd.yml剧本,这个剧本在webservers组的主机上运行,以确保禁用/停止httpd服务,并且卸载已安装的httpd软件包:

[student@workstation update-parallelism]$ cat remove_httpd.yml
---
- name: Remove the web server
  hosts: webservers
  become: true
  gather_facts: false

  tasks:
    - name: Query service facts
      ansible.builtin.service_facts:

    - name: Disable httpd service
      ansible.builtin.service:
        name: httpd
        enabled: false
        state: stopped
      when: ansible_facts['services']['httpd.service'] is defined

    - name: Remove httpd package
      ansible.builtin.yum:
        name: httpd
        state: absent
        autoremove: true

3)使用ansible-navigator命令运行update_httpd.yml剧本

观察剧本执行,注意看Ansible是如何同时为4个主机执行每一个任务的;同时也注意通过回调插件的输出来了解剧本执行时间。

[student@workstation update-parallelism]$ ansible-navigator run -m stdout update_httpd.yml
PLAY [Update the web server] ***************************************************

TASK [Latest httpd package installed] ******************************************
Tuesday 20 May 2025  12:54:30 +0000 (0:00:00.068)       0:00:00.069 ***********
changed: [serverc.lab.example.com]
changed: [serverb.lab.example.com]
changed: [servera.lab.example.com]
changed: [serverd.lab.example.com]

RUNNING HANDLER [Restart httpd] ************************************************
Tuesday 20 May 2025  12:54:50 +0000 (0:00:20.211)       0:00:20.280 ***********
changed: [servera.lab.example.com]
changed: [serverc.lab.example.com]
changed: [serverb.lab.example.com]
changed: [serverd.lab.example.com]

PLAY RECAP *********************************************************************
servera.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverb.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverc.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverd.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Playbook run took 0 days, 0 hours, 0 minutes, 25 seconds
Tuesday 20 May 2025  12:54:55 +0000 (0:00:04.727)       0:00:25.007 ***********
===============================================================================
Latest httpd package installed ----------------------------------------- 20.21s
Restart httpd ----------------------------------------------------------- 4.73s

运行remove_httpd.yml剧本来停用/停止httpd服务并卸载httpd软件包:

[student@workstation update-parallelism]$ ansible-navigator run -m stdout remove_httpd.yml

4)将ansible.cfg配置中的forks参数设为1,然后再次运行update_httpd.yml剧本

修改ansible.cfg配置文件,将forks参数的值改为1:

[student@workstation update-parallelism]$ vim ansible.cfg
[defaults]
inventory=inventory
remote_user=devops
forks=1
callbacks_enabled=ansible.posix.timer,ansible.posix.profile_tasks

再次运行update_httpd.yml剧本,观察剧本运行情况。这一次Ansible同时只在1个主机上运行任务;请注意,减少fork的数量会导致剧本执行花费更长的时间。

[student@workstation update-parallelism]$ ansible-navigator run -m stdout update_httpd.yml
PLAY [Update the web server] ***************************************************

TASK [Latest httpd package installed] ******************************************
Tuesday 20 May 2025  12:56:31 +0000 (0:00:00.069)       0:00:00.069 ***********
changed: [servera.lab.example.com]
changed: [serverb.lab.example.com]
changed: [serverc.lab.example.com]
changed: [serverd.lab.example.com]

RUNNING HANDLER [Restart httpd] ************************************************
Tuesday 20 May 2025  12:57:18 +0000 (0:00:47.077)       0:00:47.147 ***********
changed: [servera.lab.example.com]
changed: [serverb.lab.example.com]
changed: [serverc.lab.example.com]
changed: [serverd.lab.example.com]

PLAY RECAP *********************************************************************
servera.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverb.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverc.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverd.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Playbook run took 0 days, 0 hours, 0 minutes, 56 seconds
Tuesday 20 May 2025  12:57:28 +0000 (0:00:09.507)       0:00:56.654 ***********
===============================================================================
Latest httpd package installed ----------------------------------------- 47.08s
Restart httpd ----------------------------------------------------------- 9.51s

运行remove_httpd.yml剧本来停用/停止httpd服务并卸载httpd软件包:

[student@workstation update-parallelism]$ ansible-navigator run -m stdout remove_httpd.yml

5)为update_httpd.yml剧本中的play设置serial参数,每一批次只处理2台主机

[student@workstation update-parallelism]$ vim update_httpd.yml
---
- name: Update the web server
  hosts: webservers
  become: true
  gather_facts: false
  serial: 2

6)将ansible.cfg配置中的forks参数设为2,forks参数的值至少要等于serials的值,否则会降低剧本执行速度

[student@workstation update-parallelism]$ vim ansible.cfg
[defaults]
inventory=inventory
remote_user=devops
forks=2
callbacks_enabled=ansible.posix.timer,ansible.posix.profile_tasks

7)再次运行update_httpd.yml剧本, 观察剧本运行情况

可以看到Ansible在开始的2台主机上运行了整个play,然后再去剩下的2台主机上运行这个play:

[student@workstation update-parallelism]$ ansible-navigator run -m stdout update_httpd.yml
PLAY [Update the web server] ***************************************************

TASK [Latest httpd package installed] ******************************************
Tuesday 20 May 2025  13:02:18 +0000 (0:00:00.048)       0:00:00.048 ***********
changed: [serverb.lab.example.com]
changed: [servera.lab.example.com]

RUNNING HANDLER [Restart httpd] ************************************************
Tuesday 20 May 2025  13:02:32 +0000 (0:00:13.587)       0:00:13.636 ***********
changed: [serverb.lab.example.com]
changed: [servera.lab.example.com]

PLAY [Update the web server] ***************************************************

TASK [Latest httpd package installed] ******************************************
Tuesday 20 May 2025  13:02:35 +0000 (0:00:03.047)       0:00:16.684 ***********
changed: [serverc.lab.example.com]
changed: [serverd.lab.example.com]

RUNNING HANDLER [Restart httpd] ************************************************
Tuesday 20 May 2025  13:02:47 +0000 (0:00:12.706)       0:00:29.390 ***********
changed: [serverd.lab.example.com]
changed: [serverc.lab.example.com]

PLAY RECAP *********************************************************************
servera.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverb.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverc.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverd.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Playbook run took 0 days, 0 hours, 0 minutes, 32 seconds
Tuesday 20 May 2025  13:02:50 +0000 (0:00:02.657)       0:00:32.048 ***********
===============================================================================
Latest httpd package installed ----------------------------------------- 12.71s
Restart httpd ----------------------------------------------------------- 2.66s

运行remove_httpd.yml剧本来停用/停止httpd服务并卸载httpd软件包:

[student@workstation update-parallelism]$ ansible-navigator run -m stdout remove_httpd.yml

8)将ansible.cfg配置中的forks参数重新设为4

[student@workstation update-parallelism]$ vim ansible.cfg
[defaults]
inventory=inventory
remote_user=devops
forks=4
callbacks_enabled=ansible.posix.timer,ansible.posix.profile_tasks

9)将update_httpd.yml剧本中的serial参数改成3

[student@workstation update-parallelism]$ vim update_httpd.yml
---
- name: Update the web server
  hosts: webservers
  become: true
  gather_facts: false
  serial: 3

10)再次运行update_httpd.yml剧本, 观察剧本运行情况

可以看到Ansible在开始的3台主机上运行了整个play,然后再去剩下的1台主机上运行这个play:

[student@workstation update-parallelism]$ ansible-navigator run -m stdout update_httpd.yml
PLAY [Update the web server] ***************************************************

TASK [Latest httpd package installed] ******************************************
Tuesday 20 May 2025  13:06:41 +0000 (0:00:00.042)       0:00:00.042 ***********
changed: [serverb.lab.example.com]
changed: [serverc.lab.example.com]
changed: [servera.lab.example.com]

RUNNING HANDLER [Restart httpd] ************************************************
Tuesday 20 May 2025  13:06:53 +0000 (0:00:11.983)       0:00:12.026 ***********
changed: [servera.lab.example.com]
changed: [serverc.lab.example.com]
changed: [serverb.lab.example.com]

PLAY [Update the web server] ***************************************************

TASK [Latest httpd package installed] ******************************************
Tuesday 20 May 2025  13:06:56 +0000 (0:00:03.193)       0:00:15.220 ***********
changed: [serverd.lab.example.com]

RUNNING HANDLER [Restart httpd] ************************************************
Tuesday 20 May 2025  13:07:06 +0000 (0:00:09.464)       0:00:24.684 ***********
changed: [serverd.lab.example.com]

PLAY RECAP *********************************************************************
servera.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverb.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverc.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
serverd.lab.example.com    : ok=2    changed=2    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
Playbook run took 0 days, 0 hours, 0 minutes, 27 seconds
Tuesday 20 May 2025  13:07:08 +0000 (0:00:02.451)       0:00:27.135 ***********
===============================================================================
Latest httpd package installed ------------------------------------------ 9.46s
Restart httpd ----------------------------------------------------------- 2.45s

结束练习(清理环境):

在workstation虚拟机上,切换到student用户主目录,使用lab命令来清理案例环境,确保先前练习的资源不会影响后续的练习。

[student@workstation ~]$ lab finish update-parallelism

三、  管理滚动更新

1.   控制批量执行的规模

Ansible支持配置滚动更新(rolling updates),这是一种将部署批量交付到服务器的策略。通过滚动更新,针对基础架构的部署可以在零宕机的情况下完成。当出现问题时,Ansible可以及时停止部署,并将错误限制在特定批次中的服务器上。结合测试和监控,你可以配置剧本任务来完成以下任务:

  • 针对受影响批次中主机的配置回滚

  • 将受影响的主机隔离,以便分析部署失败的原因

  • 向干系人发送部署通知

默认情况下,Ansible在运行play中的第2个任务之前,会先为所有主机运行第1个任务。假设有一项任务由于某些错误而无法成功,那么你的play在所有主机上并发执行时就都会失败,从而中止play的运行,没有一个主机能完成整个play,导致业务停机。

为了避免这种情况,你可以将大量主机分批次来处理当前面的批次已经发现有很多主机失败了,可以提前中止play的运行,而不是直接在所有主机上运行play。

下面介绍如何将play配置为分批执行,遇到太多失败时提前中止运行。

1)选择合适的批次大小(整数)

通过为play使用serials指令,可以指定分批的规模即批次大小(即每个批次为多少台主机运行play)。Ansible在运行play时,会先处理一批主机,之后再开始处理下一批。如果当前批次中的所有主机都失败,则整个play会中止运行,并且Ansible也不会再启动下一批。

看看这个play:

---
- name: Update Webservers
  hosts: web_servers
  serial: 2

这个例子中,serials分批规模设为2,也就是每次只处理web_servers组中的2台主机。如果当前批次的主机都能正常运行play,然后再处理下一批主机,直到全部主机都处理完毕。注意,最后一批的主机数量可能会少于serials指令的值(比如一共5台主机,每批次2台,则最后一批只包括1台主机)。

请记住,如果serials指令使用整数,那么随主机数量的增加,完成play所需要的批数也会增加。当serials设为2时,200台主机需要100个批次才能完成,而20台主机就只需要10个批次。

2)选择合适的批次大小(百分比)

可以使用百分比值来作为serial参数的值,每个批次会在指定百分比的主机上来运行play:

---
- name: Update Webservers
  hosts: web_servers
  serial: 25%

Ansible根据主机总数按serials指定的百分比来确定数量,如果得到的值不是整数,则向下四舍五入到最接近的整数(但是不能为0,如果截断的值为零,则自动改为1)。

下表说明了百分比的使用以及由此产生的批次大小和数量(如表所示)。

描述

主机组1

主机组2

主机组3

总的主机数

3

13

19

serial分批规模

25%

25%

25%

计算的主机数值

0.75

3.25

4.75

四舍五入结果

0

3

4

实际分批规模/大小

1

3

4

分几批

3

5

5

最后一批的主机数量

1

1

3

3)将批次大小设置为在运行中更改

可以在play的执行过程中按照特定条件来变更批次大小。例如,使用只有1个主机的一批主机来测试一个play;如果这一个主机失败,则整个play中止。但是,如果这一个主机上运行成功了,可以将批量大小增加到主机的10%,然后下一批是50%,然后是其余主机。

只要将serials设置为值的列表,就可以实现批次大小的逐渐变更。这个列表可以包含整数和百分比的任意组合,并按照列表值的顺序重新设置每个批次的大小。对于给定的一个百分比的值,Ansible将根据主机的总数量(而不是未处理主机的数量)来计算批次大小。

看下面这个示例:

---
- name: Update Webservers
  hosts: web_servers
  serial:
    - 1
    - 10%
    - 100%

按照这个设置,批次安排如下:

  • 第一批包含1个主机

  • 第二批包含web_servers组中主机总数的10%

  • 第三批包含所有剩余的未处理主机

⚠️注意:如果处理了serials条目指定的最后一批是25%,处理列表的最后一个条目,还仍然存在未处理的主机,则重复最后一批,直到处理完所有主机。

再看下面这个示例,针对具有100个主机的web_servers主机组运行:

---
- name: Update Webservers
  hosts: web_servers
  serial:
    - 1
    - 10%
    - 25%

按照这个设置,批次安排如下:

  • 第一批包含1个主机

  • 第二批包含10台主机(即100的10%)

  • 第三批包含25台主机(即100的25%),还剩下64台未处理的主机

  • 继续处理,第四批包含25台主机(即100的25%),还剩下39台未处理的主机

  • 继续处理,第五批包含25台主机(即100的25%),还剩下14台未处理的主机

  • 继续处理,第六批包含14台主机(剩下的不够25%了,所以是全部)

2.   play的中止

默认情况下,Ansible会让尽可能多的受管机来完成play。如果某一个主机的任务失败,这个主机会中止play的运行,但是其他主机会继续运行剩下的任务。只有当所有受管机都失败时,整个play才会中止。

但是,如果使用serials指令将主机分批处理,那么一旦当前批次中的所有主机都失败,则Ansible还将停止在剩余批次主机上运行play,即下一个批次不会开始。

Ansible使用ansible_play_batch变量保留每个批次的活动服务器列表,任务失败的主机会从这个列表中删除,每次任务后Ansible会更新这个列表。

假设有以下这个剧本,针对具有100个主机的web_servers组运行:

---
- 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
  1. 对于第1个任务,若有99个主机失败、1个主机成功,Ansible将继续执行第2个任务

  2. 当运行第2个任务时,Ansible只为之前成功的这一个主机运行此任务(其他主机因为任务1失败就放弃后续任务了)。

如果使用serials指令,只要主机保持在当前这个批次中没有故障,则剧本执行将继续。对剧本做如下修改:

---
- name: Update Webservers
  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
  1. 如果第一批的2个主机其中1个成功、另一个失败,则该批次完成,Ansible将转到第二批的2个主机。

  2. 如果第二批中的2个主机都失败了,则中止整个play,后续的主机将不再有新批次执行任务

在这种情况下,运行剧本的结果状态如下:

  • 一台受管机完成play

  • 3台受管机处于错误状态

  • 其余96台主机保持不变(未运行任务1)

指定故障容限

默认情况下,只有在当前批次处理中的所有主机都失败时,Ansible才会停止运行play。但是,如果失败的主机数量超过一定百分比,即使不是整个批次失败,你也可能希望放弃运行play。另外也有一种情况,如果出现任何任务失败,可以认为“fail fast”并立即停止运行play。

可以为play设置 max_fail_percentage 指令,来更改Ansible针对失败的默认行为。当一批主机中失败的主机数量超过此百分比时,Ansible将停止执行剧本

看看下面这个剧本,针对web_servers组的100个主机运行:

---
- name: Update Webservers
  hosts: web_servers
  max_fail_percentage: 30%
  serial:
    - 2
    - 10%
    - 100%
  tasks:
    - name: Step One
      ansible.builtin.shell: /usr/bin/some_command
    - name: Step Two
      ansible.builtin.shell: /usr/bin/some_other_command
  • 第一批包含2个主机。因为2的30%是0.6,所以第一批只要有1个主机故障就会导致执行停止;如果两个主机都成功了,则进入下一批次。

  • 第二批包含10个主机。因为10的30%是3,所以这个批次必须发生三次以上的主机故障才能停止执行剧本;如果故障主机数量为3个或不到3个,则进入下一批次。

如果想实现“fail fast”策略(遇故障立即中止),请将max_fail_percentage设置为零

Ansible的故障行为处理:

  • 如果未定义serials指令max_fail_percentage值,则所有主机都将在一个批次中运行。如果所有主机都失败了,那么这个play就失败

  • 如果定义了serials指令,则主机将分为多个批次运行。任何一个批次中的所有主机失败,那么这个play将失败

  • 如果定义了max_fail_percentage指令,那么任何一个批次中失败的主机数超过这个百分比,则这个play将失败

如果一个play失败,Ansible会中止剧本中所有剩余的play。

3.   运行一次性task

在某些情况下,可能某个任务在一整批主机中只需要有一次成功的运行,而不是这一批主机中的每一个主机都要运行这个任务。这种情况下,请在相应的任务下设置run_once: true

例如:

- name: Reactivate Hosts
  ansible.builtin.shell: /sbin/activate.sh {{ active_hosts_string }}
  run_once: true
  delegate_to: monitor.example.com
  vars:
    active_hosts_string: "{{ ansible_play_batch | join(' ')}}"

这个任务被委派到monitor.example.com主机上运行,只运行一次。这个任务将活动主机变量active_hosts_string的值(包含当前批次中已成功完成所有先前任务的主机)传递给激活脚本。

4.   课堂练习:管理滚动更新

开始练习(部署环境):

以用户student登入workstation虚拟机,使用lab命令来构建案例环境。

[student@workstation ~]$ lab start update-management

步骤说明:

1)从https://git.lab.example.com/student/update-management.git仓库克隆项目到/home/student/git-repos目录,并创建exercise分支

创建/home/student/git-repos目录,并进入此目录:

[student@workstation ~]$ mkdir -p ~/git-repos/
[student@workstation ~]$ cd ~/git-repos/

从Git仓库克隆项目,并进入项目目录:

[student@workstation git-repos]$ git clone https://git.lab.example.com/student/update-management.git
[student@workstation git-repos]$ cd update-management

检出exercise分支:

[student@workstation update-management]$ git checkout -b exercise

2)根据collections/requirements.yml文件设置,安装内容集到collections/目录

登录位于https://hub.lab.example.com的私有自动化中心,验证用户名student、密码redhat123。

导航到Collections > API token management,然后单击load token,复制API令牌字串。

使用复制的令牌更新ansible.cfg文件中的两个token行(你的令牌可能与这里不一样):

[student@workstation update-management]$ vim ansible.cfg
[galaxy_server.rh-certified_repo]
url=https://hub.lab.example.com/api/galaxy/content/rh-certified
token=11a7e652f253870377c7ddaba61e1d08141c0232
 
[galaxy_server.community_repo]
url=https://hub.lab.example.com/api/galaxy/content/community/
token=11a7e652f253870377c7ddaba61e1d08141c0232

使用ansible-galaxy命令将community.generalredhat.rhel_system_roles内容集安装目录collections/下。

后面练习中,update_webapp.yml剧本会用到community.general.haproxy模块来与HAProxy服务器交互,角色apache需要用到redhat.rhel_system_roles模块。

[student@workstation update-management]$ cat collections/requirements.yml
---
collections:
  - name: community.general
  - name: redhat.rhel_system_roles
[student@workstation update-management]$ ansible-galaxy collection install -r collections/requirements.yml -p collections/

3)使用ee-supported-rhel8:latest自动化环境来运行site.yml剧本,这个剧本用来部署前端的负载均衡器以及后端的Web服务器

[student@workstation update-management]$ ansible-navigator run site.yml 
Play name  Ok Changed ... Failed ... Task count Progress
0|Gather web_server facts  5 0 ...  0 ...  5   Complete
1|Ensure HAProxy is deployed  6  5 ...  0 ...  6   Complete
2|Set load Balancer facts 1   0 ...  0 ...  1   Complete
3|Ensure Apache is deployed  35  25 ... 0 ...  35  Complete
4|Ensure Web App is deployed 15  5 ...  0 ...  15  Complete
 
^f/PgUp page up   ^b/PgDn page down T† scroll  esc back ... Successful

按Esc键退出ansible-navigator命令。

4)使用curl命令向负载均衡器发5次访问请求

[student@workstation update-management]$ for x in {1..5}; do curl servera; done
serverb: /var/www/html/index.html
serverc: /var/www/html/index.html
serverd: /var/www/html/index.html
servere: /var/www/html/index.html
serverf: /var/www/html/index.html

负载均衡器会将这5次请求轮流分发给后端的5个Web服务器(通过/var/www/html目录交付网页内容)。

5)剧本update_webapp.yml用来为后端web服务器上的web程序做滚动更新,可以查看此剧本内容,并了解serial关键字的使用情况

① 看看update_webapp.yml剧本的开头部分:

[student@workstation update-management]$ cat update_webapp.yml
---
- name: Update web servers to use a new document root
  hosts: web_servers
  become: true
  force_handlers: true
  vars:
    webapp_content_dir: /srv/web/app/html
 
  serial:
    - 1
    - 25%
    - 100%
...output omitted...

这个剧本会在web_servers组的所有主机上运行。变量webapp_content_dir指定了网站文档的根目录(如果不指定,此变量默认为/var/www/html/)。

serial关键字指定分三批处理play中的任务:

  • 第一批包含1台主机,这一批处理完成后,仍有4个主机需要处理

  • 第二批包含1个主机,因为25%的主机组大小为1.25(四舍五入为1个)

  • 第三批包含3个主机,因为100%的主机组大小是5个,但只有剩下的3个主机

② 观察剧本中的pre_tasks前置任务部分:

pre_tasks:
  - name: Remove web server from service during the update
    community.general.haproxy:
      state: disabled        ## 移除对应的Web服务器主机
      backend: app
      host: "{{ inventory_hostname }}"    ## Web服务器主机
    delegate_to: "{{ item }}"       ## 委派给负载均衡主机
    with_items: "{{ groups['lb_servers'] }}"

剧本在为每个Web服务器更新web程序之前,haproxy模块会从负载均衡器上移除对应的Web服务器主机,避免当Web程序部署错误时客户端访问出现异常。

③ 观察剧本中的roles角色部分:

roles:
  - role: apache
  - role: webapp

apache角色修改/etc/httpd/conf/httpd.conf配置文件,以使用webapp_content_dir变量指定的网页目录,而webapp角色将Web内容部署到这个网页目录中。

④ 观察剧本中的post_tasks后置任务部分:

post_tasks:
  # Firewall rules dictate that requests to backend web
  # servers must originate from a load balancer.
  - name: Smoke Test - Ensure HTTP 200 OK
    ansible.builtin.uri:
      url: "http://{{ inventory_hostname }}:{{ apache_port }}"
      status_code: 200
    delegate_to: "{{ groups['lb_servers'][0] }}"
    become: false
 
    # If the test fails, servers are not re-enabled
    # in the load balancers, and the update process halts.
    - name: Enable healthy server in load balancers
      community.general.haproxy:
        state: enabled backend: app
        host: "{{ inventory_hostname }}"
      delegate_to: "{{ item }}"
      with_items: "{{ groups['lb_servers'] }}"
  • 在剧本部署好web程序之后,通过冒烟测试来确保每个后端web服务器都能为客户端正常提供服务(HTTP状态码200)。由于每个web服务器上的防火墙只允许来自负载平衡器的web访问请求,因此所有的冒烟测试应委托给负载平衡器。
  • 当针对一个Web服务器的冒烟测试失败时,将会停止对这个web服务器的进一步处理,并且不会在负载均衡器中重新启用这个web服务器。
  • 第二个任务用来在负载平衡器中启用通过冒烟测试的web服务器。

6)使用update_webapp.yml剧本将web程序部署到/srv/web/app/html/目录。再使用curl命令向负载均衡器发送五个请求。请注意,发生故障的后端web服务器不会包含在负载均衡池中

使用ee-supported-rhel8:latest环境来运行update_webapp.yml剧本。剧本会失败了,请保持此终端在交互模式下运行(如图-1所示),以便排除问题:

[student@workstation update-management]$ ansible-navigator run update_webapp.yml

图-1

新打开一个终端,向负载均衡器发送5个web访问请求。注意观察,负载均衡器如何将这些请求重定向到原来5个web服务器中的4个(有一个为失败状态,被隔离出去了):

[student@workstation update-management]$ for x in {1..5}; do curl servera; done
serverc: /var/www/html/index.html
serverd: /var/www/html/index.html
servere: /var/www/html/index.html
serverf: /var/www/html/index.html
serverc: /var/www/html/index.html

update_webapp.yml 剧本首先从负载均衡池中删除了其中一个服务器(本例中为serverb)。在将更改应用到web服务器之后,剧本运行一个任务来验证web服务器是否仍然能够成功响应web请求。当web服务器没有成功响应时,剧本不再将服务器加回负载均衡池。

本例中每个web服务器都显示了一条不一样的自定义消息。在实际应用中,会为每个web服务器部署相同的内容,因此即使有一个或多个web服务器被隔离出去了,访问负载均衡器的用户也并不知道。

返回到ansible-navigator命令的交互式会话界面,按0查看有关Update web servers to usea new document root这个play的详情:

...ouptut omitted...
0|Update web servers ...  10  5  ...   1   ...   11   Complete
...ouptut omitted...

输出结果显示,有一个任务失败了(如图-2所示)。

图-2

继续在交互模式来发现有关问题的更多细节。输入 :10 查看任务Smoke Test – Ensure HTTP 200 OK这个任务的详情。

...output omitted...
10|Failed serverb.lab.example.com ... Smoke Test - Ensure HTTP 200 OK   ...
...output omitted...

详情页表明serverb.lab.example.com返回了403状态码,而不是预期的200状态码。在输出的msg行上也显示了同样的信息。

Play name: Update web servers to use a new document root:10
Task name: Smoke Test - Ensure HTTP 200 OK
Failed: serverb.lab.example.com Status code was 403 and not [200]: HTTP Error 403: Forbidden
...output omitted...

按Esc键退出ansible-navigator命令。

7)因文件权限问题或SELinux访问问题,Web服务器经常会返回403的状态码。请确定具体问题和潜在的解决方案

以student用户通过SSH连入serverb.lab.example.com主机:

[student@workstation update-management]$ ssh student@serverb.lab.example.com

使用 ls命令 检查/srv/web/ app/html/index.html文件的权限和归属,结果表明index.html文件是所有人可读的,说明不是这个文件权限的问题。

[student@serverb ~]$ ls -l /srv/web/app/html/index.html
-rw-r--r--. 1 root root ... /srv/web/app/html/index.html

使用 ls命令 检查/srv/web/ app/html/index.html文件的SELinux安全上下文,发现var_t这个类型对于web目录是不合适的,所以是SELinux阻止了httpd进程访问这个目录

[student@serverb ~]$ ls -Z /srv/web/app/html/index.html
system_u:object_r:var_t:s0 /srv/web/app/html/index.html

默认情况下,/var/www/html/目录的SELinux安全上下文是允许httpd进程访问的,可以查看这个目录的安全上下文类型:(本练习中使用这里找到的httpd_sys_content_t这个类型。)

[student@serverb ~]$ ls -Zd /var/www/html
system_u:object_r:httpd_sys_content_t:s0 /var/www/html
[student@serverb ~]$ logout

8)修改apache角色的roles/apache/tasks/main.yml任务文件,在Start and enable httpd任务之后添加一个block块,将现有的Customize Apache HTTPD Configuration任务和Ensure that {{ webapp_content_dir }} exists任务放到block块下

修改后的main.yml文件应包括以下内容:

[student@workstation update-management]$ vim roles/apache/tasks/main.yml
---
# tasks file for apache
 
- name: Apache Port Check
  ansible.builtin.assert:
    that:
      - apache_port in apache_standard_ports_list
    fail_msg: "{{ tmp_msg}}: {{ apache_standard_ports_list }}"
    success_msg: The specified apache port ({{ apache_port }}) is allowed.
  vars:
    tmp_msg: "'apache_port' value ({{ apache_port }}) is not in the list"
 
- name: Install httpd
  ansible.builtin.yum:
    name:
      - httpd
    state: present
 
- name: Start and enable httpd
  ansible.builtin.service:
    name: httpd
    state: started
    enabled: true
 
- name: Customize SELinux for web_content_dir
  block:
    - name: Set webapp_base fact
      ansible.builtin.set_fact:
        webapp_base: "{{ webapp_content_dir | split('/') }}"
 
    - name: Web directory is a subdirectory of /srv
      ansible.builtin.assert:
        that:
          - webapp_base[0] == ''
          - webapp_base[1] == 'srv'
          - webapp_base[2] is defined
        fail_msg: '"{{ webapp_content_dir }}" is not a subdirectory of /srv.'
        success_msg: '"{{ webapp_content_dir }}" is a subdirectory of /srv.'
 
    - name: Customize Apache HTTPD Configuration
      ansible.builtin.template:
        src: templates/httpd.conf.j2
        dest: /etc/httpd/conf/httpd.conf
      notify: restart httpd
 
    - name: Ensure that {{ webapp_content_dir }} exists
      ansible.builtin.file:
        path: "{{ webapp_content_dir }}"
        state: directory
        owner: root
        group: root
        mode: '0755'
 
    - name: Create SELinux file context for the directory
      ansible.builtin.include_role:
        name: redhat.rhel_system_roles.selinux
      vars:
        selinux_fcontexts:
          - target: "/{{ webapp_base[1] }}/{{ webapp_base[2] }}(/.*)?"
            setype: "httpd_sys_content_t"
            state: present
        selinux_restore_dirs:
          - /{{ webapp_base[1] }}/{{ webapp_base[2] }}
  when: "webapp_content_dir != '/var/www/html'"

针对block代码块,其中,

  • 第一个任务使用正斜杠作为分隔符,将webapp_content_dir变量拆分为一个数组。

  • 第二个任务检查webapp_content_dir变量指定的目录是否是/srv/目录的子目录。此任务检查数组的第一个项webapp_base[0]是否为空,因为这是第一个正斜杠左侧的值;而数组的第二个项webapp_base[1]必须与srv值匹配。必须定义数组的第三项weapp_base[2]。如果任务失败,剧本将退出并显示一条错误消息

  • 第三个和第四个任务来自现有的任务文件,并且已经移动到块中。运行restorecon命令之前,webapp_content_dir变量指定的目录必须存在

  • 第五个任务创建一个新的文件上下文规则,该规则使用webapp_content_dir变量指定的前两个目录,然后应用新规则

  • 只有当webapp_content_dir变量与/var/www/html的默认值不匹配时,block块才适用

~/git-repos/update-management/solutions/main.yml 提供一份正确的任务文件配置,必要时可作参考

cp -f ~/git-repos/update-management/solutions/main.yml roles/apache/tasks/main.yml

9)再次运行update_webapp.yml剧本,在另一个终端跟踪issue_requests.sh脚本的输出,可以观察到后端服务器逐渐切换了新的web文档根目录

在另一个终端运行issue_requests.sh脚本,跟踪update_webapp.yml剧本的执行输出。这个脚本每2秒向负载均衡器servera.lab.example.com发送一个web请求,输出结果同时还会记录到curl_output.log文件中:

[student@workstation update-management]$ ./issue_requests.sh
serverc: /var/www/html/index.html
serverd: /var/www/html/index.html
servere: /var/www/html/index.html
serverf: /var/www/html/index.html
...output omitted...

一开始的时候,web请求由后端的4台web服务器正常提供响应。

使用ee-supported-rhel8:latest环境运行update_webapp.yml剧本:

[student@workstation update-management]$ ansible-navigator run update_webapp.yml

当剧本运行时,切换到正在运行issue_requests.sh脚本的终端,观察输出结果。等新Web程序的冒烟测试通过之后,serverb服务器将提供新的web文档内容:

...output omitted...
serverc: /var/www/html/index.html
serverd: /var/www/html/index.html
servere: /var/www/html/index.html
serverf: /var/www/html/index.html
...output omitted...
serverb: /srv/web/app/html/index.html
serverc: /var/www/html/index.html
serverd: /var/www/html/index.html
servere: /var/www/html/index.html
serverf: /var/www/html/index.html
...output omitted...

当剧本处理下一批主机的时候,其中只包含serverc,该剧本将serverc从服务中删除:

...output omitted...
serverb: /srv/web/app/html/index.html
serverc: /var/www/html/index.html
serverd: /var/www/html/index.html
servere: /var/www/html/index.html
serverf: /var/www/html/index.html
...output omitted...
serverb: /srv/web/app/html/index.html
serverd: /var/www/html/index.html
servere: /var/www/html/index.html
serverf: /var/www/html/index.html
...output omitted...

再往后,等serverc主机的冒烟测试通过之后,又会重新投入使用:

...output omitted...
serverb: /srv/web/app/html/index.html
serverd: /var/www/html/index.html
servere: /var/www/html/index.html
serverf: /var/www/html/index.html
...output omitted...
serverb: /srv/web/app/html/index.html
serverc: /srv/web/app/html/index.html
serverd: /var/www/html/index.html
servere: /var/www/html/index.html
serverf: /var/www/html/index.html
...output omitted...

剧本最后一批处理的是剩余的所有web服务器。运行时会先禁用这三个服务器(开始能看到,然后就看不到了),只留下serverb和serverc服务器来处理请求:

...output omitted...
serverb: /srv/web/app/html/index.html
serverc: /srv/web/app/html/index.html
serverd: /var/www/html/index.html
servere: /var/www/html/index.html
serverf: /var/www/html/index.html
...output omitted...
serverb: /srv/web/app/html/index.html
serverc: /srv/web/app/html/index.html
...output omitted...

最终,每台服务器都通过冒烟测试,并重新投入使用:

...output omitted...
serverb: /srv/web/app/html/index.html
serverc: /srv/web/app/html/index.html
...output omitted...
serverb: /srv/web/app/html/index.html
serverc: /srv/web/app/html/index.html
serverd: /srv/web/app/html/index.html
servere: /srv/web/app/html/index.html
serverf: /srv/web/app/html/index.html
...output omitted...

按Ctrl+C停止issue_requests.sh脚本。

...output omitted...
serverb: /srv/web/app/html/index.html
serverc: /srv/web/app/html/index.html
serverd: /srv/web/app/html/index.html
servere: /srv/web/app/html/index.html
serverf: /srv/web/app/html/index.html
^C

[student@workstation update-management]$

10)回到运行ansible-navigator的界面,剧本已成功运行完毕(如图-3所示)

图-3

结束练习(清理环境):

在workstation虚拟机上,切换到student用户主目录,使用lab命令来清理案例环境,确保先前练习的资源不会影响后续的练习。

[student@workstation ~]$ lab finish update-management

四、  综合实验:部署滚动更新

开始实验(部署环境):

以用户student登入workstation虚拟机,使用lab命令来构建案例环境。

[student@workstation ~]$ lab start update-review

解决方案:

1.   从https://git.lab.example.com/student/update-review.git仓库克隆项目到/home/student/git repos/update-review目录,并创建exercise分支。运行site.yml剧本来部署web应用程序

1)创建/home/student/git-repos目录,并进入此目录

[student@workstation ~]$ mkdir -p ~/git-repos/
[student@workstation ~]$ cd ~/git-repos/

2)从Git仓库克隆项目,并进入项目目录

[student@workstation git-repos]$ git clone https://git.lab.example.com/student/update-review.git
[student@workstation git-repos]$ cd update-review

3)检出exercise分支

[student@workstation update-review]$ git checkout -b exercise

4)使用ansible-navigator run运行site.yml剧本

[student@workstation update-review]$ ansible-navigator run -m stdout site.yml

2.   根据collections/requirements.yml文件的定义来安装集合,以方便后续的update_webapp.yml剧本使用

1)登录位于https://hub.lab.example.com的私有自动化中心,用户名student、密码redhat123

2)导航到Collections > API token management,然后单击load token,复制令牌字串

3)使用复制的令牌更新ansible.cfg文件中的两个token行

[galaxy_server.rh-certified_repo]
url=https://hub.lab.example.com/api/galaxy/content/rh-certified
token=11a7e652f253870377c7ddaba61e1d08141c0232
 
[galaxy_server.community_repo]
url=https://hub.lab.example.com/api/galaxy/content/community/
token=11a7e652f253870377c7ddaba61e1d08141c0232

4)将集合安装到/home/student/git-repos/update-review/collections/目录中

[student@workstation update-review]$ ansible-galaxy collection install -r collections/requirements.yml -p collections/
Starting galaxy collection install process
...output omitted...
Installing 'community.general:6.1.0' to '/home/student/git-repos/update-review/ collections/ansible_collections/community/general'
community.general:6.1.0 was installed successfully

3.   为update_webapp.yml剧本添加pre_task前置任务,用来禁用HAProxy负载均衡器指向的web服务器(防止外部客户端在部署新Web的过程中访问集群出现异常)

按如下方式配置新任务:

  • 使用community.general.hapxy模块

  • 使用inventory_hostname变量禁用应用程序后端中的主机

  • 将每个操作委托给负载均衡器。使用Jinja2表达式 {{ groups['lb_servers'][0] }} 获取此负载平衡器的名称

修改update_webapp.yml文件,设置好pre_task前置任务

pre_tasks:
- name: Remove web server from service during the update
  community.general.haproxy:
    state: disabled
    backend: app
    host: "{{ inventory_hostname }}"
  delegate_to: "{{ groups['lb_servers'][0] }}"

4.   update_webapp.yml剧本中post_tasks部分的第一个任务是“smoke test”冒烟测试,修改这个任务用来验证每个web服务器上的web程序是否能正常响应请求

将访问web的测试操作委派给负载均衡器(相当于从负载均衡器访问后端的Web程序),使用inventory_hostname变量连接每个web服务器。

为冒烟测试任务设置delegate_to指令,将测试URL指向inventory_hostname变量

- name: Smoke Test - Ensure HTTP 200 OK
  ansible.builtin.uri:
    url: "http://{{ inventory_hostname }}:{{ apache_port }}"
    status_code: 200
  become: false
  delegate_to: "{{ groups['lb_servers'][0] }}"

5.   修改update_webapp.yml剧本中post_tasks部分,在冒烟测试任务之后再添加一个任务,用来在HAProxy负载均衡器上重新激活相应的后端web服务器。类似于pre_tasks部分的community.general.haproxy任务,只是它将状态设置为enabled而不是disabled

- name: Enable heaNthy server in load balancers
  community.general.haproxy:
    state: enabled
    backend: app
    host: "{{ inventory_hostname }}"
  delegate_to: "{{ groups['lb_servers'][0] }}"

6.   在update_webapp.yml剧本中配置play分批次运行,确保剧本使用不超过3个批次来完成所有web服务器主机的升级(第一批5%、第二批35%、最后一批包括所有剩余主机

确保在升级过程中,如果任何主机的任务失败,则停止执行剧本。

1)添加max_fail_percentage: 0的设置,一有失败即停止play的执行。添加serials指令并将值设置为三个元素的列表:5%、35%和100%,确保所有服务器都在最后一批中更新

- name: Upgrade Web Application
  hosts: web_servers
  become: true
  vars:
    webapp_version: v1.1
  max_fail_percentage: 0
  serial:
    - "5%"
    - "35%"
    - "100%"

2)改好的update_webapp.yml剧本参考下列内容

[student@workstation update-review]$ vim update_webapp.yml
---
- name: Upgrade Web Application
  hosts: web_servers
  become: true
  vars:
    webapp_version: v1.1
  max_fail_percentage: 0
  serial:
    - "5%"
    - "35%"
    - "100%"
 
  pre_tasks:
    - name: Remove web server from service during the update
      community.general.haproxy:
        state: disabled
        backend: app
        host: "{{ inventory_hostname }}"
      delegate_to: "{{ groups['lb_servers'][0] }}"
 
  roles:
    - role: webapp
 
  post_tasks:
    - name: Smoke Test - Ensure HTTP 200 OK
      ansible.builtin.uri:
        url: "http://{{ inventory_hostname }}:{{ apache_port }}"
        status_code: 200
      become: false
      delegate_to: "{{ groups['lb_servers'][0] }}"
 
    - name: Enable healthy server in load balancers
      community.general.haproxy:
        state: enabled
        backend: app
        host: "{{ inventory_hostname }}"
      delegate_to: "{{ groups['lb_servers'][0] }}"

7.   运行update_webapp.yml剧本来执行滚动更新

使用ansible-navigator命令来运行update_webapp.yml剧本。

[student@workstation update-review]$ ansible-navigator run -m stdout update_webapp.yml

8.   提交更改并推送到远程Git存储库

执行Git的add、commit、push操作,密码是Student@123

[student@workstation update-review]$ git status
On branch exercise
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified:   ansible.cfg
modified:   update_webapp.yml
no changes added to commit (use "git add" and/or "git commit -a")
[student@workstation update-review]$ git add .
[student@workstation update-review]$ git commit -m "Rolling updates"
[exercise 8d38803] Rolling updates
1 files changed, 26 insertions(+), 12 deletions(-)
 
[student@workstation update-review]$ git push -u origin exercise
Password for 'https://student@git.lab.example.com': Student@123
Enumerating objects: 13, done.
Counting objects: 100% (13/13), done. DeNta compression using up to 4 threads.
Compressing objects: 100% (6/6), done.
Writing objects: 100% (7/7), 931 bytes | 931.00 KiB/s, done. Total 7 (delta 3), reused 0 (delta 0)
To http://git.lab.example.com:8081/git/update-review.git
a36da15..5feb08e exercise -> exercise

结束实验(清理环境):

在workstation虚拟机上,切换到student用户主目录,使用lab命令来清理案例环境,确保先前练习的资源不会影响后续的练习。

[student@workstation ~]$ lab finish update-review

思维导图:

小结:

本篇为 【RHCA认证 - DO374 | Day08:配置滚动更新】的学习笔记,希望这篇笔记可以让您初步了解如何委派任务及系统指标、并发和分批配置、管理滚动更新、综合实验:部署滚动更新等,不妨跟着我的笔记步伐亲自实践一下吧


Tip:毕竟两个人的智慧大于一个人的智慧,如果你不理解本章节的内容或需要相关环境、视频,可评论666并私信小安,请放下你的羞涩,花点时间直到你真正的理解。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小安运维日记

Hey~ 感谢您的充电支持!!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值