【运维进阶】管理大项目
管理大项目
实验环境
[lth@controller ~ 19:14:24]$ mkdir web && cd web[lth@controller ~ 19:14:30]$ cat > ansible.cfg <<'EOF'
[defaults]
remote_user = lth
inventory = ./inventory[privilege_escalation]
become = True
become_user = root
become_method = sudo
become_ask_pass = False
EOF[lth@controller ~ 19:14:36]$ cat > inventory <<'EOF'
controller
node1
node2
node3
node4
EOF
配置并行
playbook 执行顺序
Ansible 处理 playbook 时的执行机制如下:
-
整体执行顺序:按照 playbook 中定义的顺序依次运行各个 play。
-
单 play 内的任务执行方式:
-
所有主机分批次执行第一个任务,待所有批次主机均完成该任务后,再进入下一个任务
-
按上述方式依次处理后续任务,直至该 play 中所有任务在所有主机上执行完毕
-
-
资源释放时机:当所有主机完成所有任务后,Ansible 才会释放 shell
-
并行能力与限制:
- 理论上可同时连接 play 中所有主机执行任务,适合小型主机列表
- 若目标主机达数百台,会给控制主机带来较大负载
配置 forks
Ansible 所进行的最大同时连接数由Ansible配置文件中的forks参数控制。
默认值为 5。
[lth@controller web 19:21:08]$ ansible-config dump|grep FORKS
DEFAULT_FORKS(default) = 5[lth@controller web 19:21:14]$ grep forks /etc/ansible/ansible.cfg
#forks = 5
示例:
# 清单内容如下
[lth@controller web 19:21:26]$ cat inventory
controller[webs]
node1
node3[dbs]
node2
node4# playbook.yaml剧本内容如下
[lth@controller web 19:28:18]$ cat playbook.yaml
---
- name: connectionhosts: alltasks:- name: conneciton 1shell: sleep 5- name: conneciton 2debug: msg: connection 2# 验证:每一批2个主机执行同一个任务。
[lth@controller web 19:28:49]$ ansible-playbook playbook.yaml -f 2PLAY [connection] *******************************************************************************TASK [Gathering Facts] **************************************************************************
ok: [node1]
ok: [node2]
ok: [node3]
ok: [node4]
ok: [controller]TASK [conneciton 1] *****************************************************************************
changed: [node1]
changed: [node2]
changed: [node3]
changed: [node4]
changed: [controller]TASK [conneciton 2] *****************************************************************************
ok: [node1] => {"msg": "connection 2"
}
ok: [node2] => {"msg": "connection 2"
}
ok: [node3] => {"msg": "connection 2"
}
ok: [node4] => {"msg": "connection 2"
}
ok: [controller] => {"msg": "connection 2"
}PLAY RECAP **************************************************************************************
controller : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node2 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node3 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node4 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# 结果:
# 第一个任务 node1、node2同时完成,然后node3、node4同时完成,最后controller完成。
# 第二个任务 似乎 5台主机同时完成。
# 解释:第一个任务执行需要5秒,所以看起来比较明显。第二个任务执行速度非常块,所以感知不到先后顺序。
forks 的默认值设置得非常保守:
- 如果受管主机是Linux主机,则大多数任务将在受管主机上运行,并且控制主机的负载较少。在这种情況下,您通常可以将forks的值设置得更高,可能接近100 ,然后性能就会提高。
- 如果控制主机管理网络设备,路由器和交换机,则大多数模块在控制主机上运行而不在网络设备上运行,这会增加控制主机上的负载,因此其支持forks数量增加的能力将显著低于仅管理Linux主机的控制主机。
配置 serial
Ansible 跑一个 play 的时候,是让所有被管的机器先把所有任务都执行完,最后才去运行那些被通知到的处理程序。
但这么做可能会出岔子。比如更新负载均衡的 Web 服务器集群时,可能需要每台服务器更新时暂时停服。如果所有服务器在同一个 play 里一起操作,就可能全停了;而且要是某个步骤失败了,整个剧本就都跑不下去了。
想避免这问题,就可以用 serial 这个设置。它能让机器分批来:先让一批机器把这个 play 里的所有任务都做完,再让下一批做,一批批来,直到所有机器都完成这个 play。
示例:
# playbook.yaml剧本内容如下
[lth@controller web 19:41:25]$ cat playbook.yaml
---
- name: Rolling updatehosts: allserial: 2tasks:- name: latest apache httpd package is installedyum:name: httpdstate: latestnotify: restart apachehandlers:- name: restart apacheservice:name: httpdstate: restarted# 验证
[lth@controller web 19:41:38]$ ansible-playbook playbook.yaml PLAY [Rolling update] ***************************************************************************TASK [Gathering Facts] **************************************************************************
ok: [node1]
ok: [node2]TASK [latest apache httpd package is installed] *************************************************
ok: [node1]
ok: [node2]PLAY [Rolling update] ***************************************************************************TASK [Gathering Facts] **************************************************************************
ok: [node4]
ok: [node3]TASK [latest apache httpd package is installed] *************************************************
ok: [node4]
ok: [node3]PLAY [Rolling update] ***************************************************************************TASK [Gathering Facts] **************************************************************************
ok: [controller]TASK [latest apache httpd package is installed] *************************************************
ok: [controller]PLAY RECAP **************************************************************************************
controller : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node3 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
node4 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
# serial关键字也可以指定为百分比。此百分比应用于play中的主机总数,以确定滚动更新批处理大小。主机数不能小于1。
配置 async
Ansible 默认的工作方式是:一个任务做完了,才能开始做下一个任务,得按顺序来。
但有些操作特别费时间,比如下很大的文件、重启服务器这些。这时候如果用了异步并行模式,Ansible 就能很快在被控机器上启动这些操作,不过还是得等这些操作自己跑完,才能让机器进入该有的状态。
Ansible 里用 async 来开启这种异步并行的任务模式,主要有两个参数要了解:
- async:就是给任务设个最大的等待时间。要是超过这个时间任务还没做完,Ansible 就会强行把它停掉,算这个任务失败了。
- poll:就是 Ansible 隔多久去看看任务做完了没,默认是 15 秒看一次。
示例1: 任务执行失败,在规定时间内容任务没有执行完成。
# 编写playbook.yaml
[lth@controller web 19:42:46]$ cat playbook.yaml
---
- name: connectionhosts: node1tasks:- name: conneciton shell: sleep 10async: 5poll: 2# 验证
[lth@controller web 19:46:44]$ ansible-playbook playbook.yaml PLAY [connection] *******************************************************************************TASK [Gathering Facts] **************************************************************************
ok: [node1]TASK [conneciton] *******************************************************************************
fatal: [node1]: FAILED! => {"changed": false, "msg": "async task did not complete within the requested time - 5s"}PLAY RECAP **************************************************************************************
node1 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
示例2: 放入后台下载,立刻执行下一个任务。
# 编写playbook.yaml
[lth@controller web 19:46:55]$ cat playbook.yaml
---
- name: connectionhosts: node1tasks:- name: downloadget_url: url: http://192.168.48.100/ISOS/openEuler-24.03-LTS-x86_64-dvd.isodest: /home/laomaasync: 100poll: 0# 验证
[lth@controller web 19:51:27]$ ansible-playbook playbook.yaml PLAY [connection] *******************************************************************************TASK [Gathering Facts] **************************************************************************
ok: [node1]TASK [download] *********************************************************************************
changed: [node1]PLAY RECAP **************************************************************************************
node1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
示例3: ansible 默认行为,等该任务执行完,再执行下一个任务。
# 编写playbook.yaml
[lth@controller web 19:51:32]$ cat playbook.yaml
---
- name: connectionhosts: node1tasks:- name: conneciton shell: sleep 10async: 0poll: 2# 验证
[lth@controller web 19:54:04]$ ansible-playbook playbook.yaml PLAY [connection] *******************************************************************************TASK [Gathering Facts] **************************************************************************
ok: [node1]TASK [conneciton] *******************************************************************************
changed: [node1]PLAY RECAP **************************************************************************************
node1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
wait_for 模块
使用 wait_for 模块检查之前的任务是否达到预期状态。
示例1: 测试文件是否存在
# 编写playbook.yaml
[lth@controller web 19:54:18]$ cat playbook.yaml
---
- name: test wait forhosts: node1tasks:- shell: sleep 10 && touch /tmp/hello# async时间要大于sleep的时间async: 20poll: 0register: out- name: wait for create /tmp/hellowait_for:path: /tmp/hellostate: presentdelay: 5 # delay,设置检测前延迟时间。timeout: 30 # timeout,设置检测超时时间。sleep: 2 # sleep,设置检测时间间隔。# 验证
[lth@controller web 19:56:18]$ ansible-playbook playbook.yaml PLAY [test wait for] ****************************************************************************TASK [Gathering Facts] **************************************************************************
ok: [node1]TASK [shell] ************************************************************************************
changed: [node1]TASK [wait for create /tmp/hello] ***************************************************************
ok: [node1]PLAY RECAP **************************************************************************************
node1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
示例2: 测试主机端口是否打开
# 编写playbook.yaml
[lth@controller web 19:56:27]$ cat playbook.yaml
---
- name: test wait_forhosts: node1,node2tasks:- name: reboot node1shell: shutdown -r now "Ansible updates triggered"async: 1poll: 0when: inventory_hostname == "node1"- name: wait for node1 come backwait_for:host: node1port: 22state: starteddelay: 10sleep: 2timeout: 300when: inventory_hostname == "node2"# 验证
[lth@controller web 19:59:03]$ ansible-playbook playbook.yaml PLAY [test wait_for] ****************************************************************************TASK [Gathering Facts] **************************************************************************
ok: [node1]
ok: [node2]TASK [reboot node1] *****************************************************************************
skipping: [node2]
changed: [node1]TASK [wait for node1 come back] *****************************************************************
skipping: [node1]
ok: [node2]PLAY RECAP **************************************************************************************
node1 : ok=2 changed=1 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
node2 : ok=2 changed=0 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
async_status 模块
使用 async_status 模块检查之前的任务是否运行完成。
示例:
# 编写playbook.yaml
[lth@controller web 19:59:21]$ cat playbook.yaml
---
- name: test async_statushosts: node1tasks:- shell: sleep 10 async: 20poll: 0register: out- name: wait forasync_status:# 通过任务的 ansible_job_id 属性跟踪任务jid: "{{ out.ansible_job_id }}"register: job_result# 根据当前任务执行结果的finished值,判断跟踪任务是否执行完成until: job_result.finishedretries: 30 # retries,设置重试次数,默认值为3。delay: 2 # delay,设置检测时间间隔,默认5秒检测一次。# 验证
[lth@controller web 20:00:48]$ ansible-playbook playbook.yaml PLAY [test async_status] ************************************************************************TASK [Gathering Facts] **************************************************************************
ok: [node1]TASK [shell] ************************************************************************************
changed: [node1]TASK [wait for] *********************************************************************************
FAILED - RETRYING: wait for (30 retries left).
FAILED - RETRYING: wait for (29 retries left).
FAILED - RETRYING: wait for (28 retries left).
FAILED - RETRYING: wait for (27 retries left).
FAILED - RETRYING: wait for (26 retries left).
changed: [node1]PLAY RECAP **************************************************************************************
node1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Including 和 importing 文件
如果playbook很长或很复杂,可以将其分成较小的文件以便于管理。
采用模块化方式将多个playbook组合为一个main playbook或者将文件中的任务列表插入play。这样可以更轻松地在不同项目中重用play或任务。
ansible重用内容主要有两种方式:
- 使用任何 include 关键字任务(include_tasks、include_role 等),它将是动态的。
- 使用任何 import 关键字任务(import_playbook、import_tasks、import_role等),它将是静态的。
- 只使用include的任务(用于 task 级别和 Playbook 级别)仍然可用,此功能将在 2.12 版中删除。
[lth@controller web 21:14:43]$ ansible-doc -l|grep -e ^import -e ^include
import_playbook Import a playbook
include_vars Load variables from files, dyn...
import_role Import a role into a play
include_role Load and execute a role
include_tasks Dynamically include a task lis...
include Include a play or task list
import_tasks Import a task list
playbook 级别
import_playbook
功能用于导入外部 playbooks,其核心特性如下:
- 导入内容性质:导入的是完整的 playbook(而非片段)。
- 使用范围限制:仅能在 play 级别使用。
- 执行顺序规则:当导入多个 playbooks 时,将按照导入的先后顺序依次执行。
示例:
# playbook.yaml主剧本内容如下
[lth@controller web 21:14:49]$ vim playbook.yml
[lth@controller web 21:14:54]$ cat playbook.yml
- name: prepare the web serverimport_playbook: pre_web.yml- name: prepare the vsftpd serverimport_playbook: pre_vsftpd.yml- name: prepare the databse serverimport_playbook: pre_db.yml# 编写pre_web.yml
[lth@controller web 21:15:10]$ cat > pre_web.yml << EOF
> - name: Play web
> hosts: node1
> tasks:
> - name: install httpd
> yum:
> name: httpd
> state: present
> EOF# 编写pre_vsftpd.yml
[lth@controller web 21:15:24]$ cat > pre_vsftpd.yml << EOF
> - name: Play vsftpd
> hosts: node1
> tasks:
> - name: install vsftpd
> yum:
> name: vsftpd
> state: present
> EOF# 编写pre_db.yml
[lth@controller web 21:15:29]$ cat > pre_db.yml << EOF
> - name: Play db
> hosts: node1
> tasks:
> - name: install mariadb-server
> yum:
> name: mariadb-server
> state: present
> EOF# 执行
[lth@controller web 21:16:15]$ ansible-playbook playbook.yml