Ansible之Playbook(四):循环与判断
Ansible之Playbook:循环与判断
一、循环 (Loops)
循环允许你对一组数据(如列表、字典)重复执行同一个任务。Ansible 提供了多种循环方式。
1. 标准循环 (loop或with_items)
- 用途:对一组简单项(通常是列表)执行任务。
- 语法 (推荐
loop):- name: Install multiple packages on OpenEuler dnf: name: "{{ item }}" state: present loop: - htop - tmux - lsof - 说明:
loop:关键字后面跟一个列表。- 在任务内部,使用
{{ item }}来引用当前循环到的元素。 - 此例在 OpenEuler 上使用
dnf模块安装了三个软件包 (htop,tmux,lsof)。Ansible 会为列表中的每个包名执行一次安装任务。
with_items(旧语法,仍可用):with_items: - htop - tmux - lsof
2. 字典循环 (with_dict或loop+dict2items)
- 用途:当你的数据是键值对(字典)时,循环处理每个键值对。
- 语法 (
with_dict):- name: Create users with specific UIDs on OpenEuler user: name: "{{ item.key }}" uid: "{{ item.value }}" with_dict: alice: 1001 bob: 1002 charlie: 1003 - 语法 (
loop+dict2items过滤器 - 更现代):- name: Create users with specific UIDs on OpenEuler (modern) user: name: "{{ item.key }}" uid: "{{ item.value }}" loop: "{{ {'alice': 1001, 'bob': 1002, 'charlie': 1003} | dict2items }}" - 说明:
- 循环到的
item是一个包含.key和.value的对象。 - 此例在 OpenEuler 上创建了三个用户,并分别指定了 UID。
- 循环到的
3. 文件循环 (with_fileglob)
- 用途:基于文件模式(通配符)循环处理匹配的文件。
- 语法:
- name: Copy all .conf files from local to OpenEuler copy: src: "{{ item }}" dest: /etc/conf.d/ with_fileglob: - "files/*.conf" # 匹配本地 files/ 目录下的所有 .conf 文件
4. 条件循环 (until)
- 用途:重复执行一个任务,直到满足某个条件为止(通常用于等待某个服务启动或端口可用)。
- 语法:
- name: Wait for SSH port 22 to be open on OpenEuler wait_for: port: 22 host: "{{ inventory_hostname }}" timeout: 30 register: result until: result is success retries: 5 delay: 3 - 说明:
wait_for模块检查端口 22 是否开放。register: result保存检查结果。until: result is success定义成功条件。retries: 5最多重试 5 次。delay: 3每次重试间隔 3 秒。
二、判断 (Conditionals)
判断语句 (when) 允许你根据条件决定是否执行某个任务、角色或包含的文件。
1. 基本条件判断 (when)
- 用途:基于变量、事实(facts)或其他条件控制任务执行。
- 语法:
- name: Install GUI packages only if OpenEuler is a desktop machine dnf: name: - gnome-session - firefox state: present when: ansible_facts['distribution'] == 'openEuler' and ansible_facts['desktop'] == 'gnome' - 说明:
when:语句后面的表达式结果为True时,任务才会执行。- 此例检查系统是否是 OpenEuler (
ansible_facts['distribution']) 并且桌面环境是否是 GNOME (ansible_facts['desktop']),只有同时满足才安装 GUI 包。
- 常见条件来源:
- 变量 (
vars):when: my_variable == 'some_value' - 主机事实 (
ansible_facts):when: ansible_facts['os_family'] == 'RedHat'(OpenEuler 属于此家族) - 任务执行结果 (
register):- name: Check if a service exists command: systemctl status some-service register: service_check ignore_errors: true - name: Start the service only if it exists systemd: name: some-service state: started when: service_check.rc == 0 # 上一条命令成功退出 (rc=0) 表示服务存在
- 变量 (
2. 结合循环的条件判断
- 用途:在循环中对每个项目进行条件过滤。
- 语法:
- name: Ensure critical services are running on OpenEuler systemd: name: "{{ item }}" state: started loop: - sshd - chronyd - firewalld when: ansible_facts['services'][item]['state'] != 'running' - 说明:
- 循环启动
sshd,chronyd,firewalld服务。 - 但
when条件检查了当前服务的状态(通过ansible_facts['services'][item]['state'])。只有当服务不是running状态时,才执行启动任务。避免了不必要的重启。
- 循环启动
3. 条件导入/包含
include_tasks/import_taskswithwhen:- name: Include firewall configuration tasks for OpenEuler if needed include_tasks: configure_firewall.yml when: configure_firewall # 当变量 configure_firewall 为 True 时才包含include_role/import_rolewithwhen:- name: Apply the hardening role only to production OpenEuler servers include_role: name: os_hardening when: inventory_hostname in groups['production']
三、在 OpenEuler 上的典型应用场景示例
批量软件包管理:
- name: Install required development tools on OpenEuler dnf: name: "{{ item }}" state: present loop: "{{ dev_tools_packages }}" vars: dev_tools_packages: - gcc - make - kernel-devel - git配置文件管理 (带备份):
- name: Backup existing config files before modification copy: src: "{{ item }}" dest: "{{ item }}.backup-{{ ansible_date_time.epoch }}" remote_src: yes # 源文件在目标主机 (OpenEuler) 上 loop: - /etc/ssh/sshd_config - /etc/sysctl.conf when: backup_configs | default(true) | bool # 默认开启备份,可通过变量控制服务状态检查与恢复:
- name: Check status of essential services on OpenEuler systemd: name: "{{ item }}" state: started enabled: yes loop: - crond - rsyslog - auditd register: service_results ignore_errors: yes # 即使某个服务启动失败,也继续检查下一个 - name: Report any failed service startups debug: msg: "Service {{ item.item }} failed to start! Check journalctl." loop: "{{ service_results.results }}" # 遍历之前注册的每个服务的结果 when: item is failed # 仅当对应服务任务失败时才打印消息
四、最佳实践
- 命名清晰:为循环变量或注册结果 (
register) 使用有意义的名称。 - 优先使用
loop:它是现代 Ansible 推荐的标准循环语法。 - 条件前置:尽可能在
when中使用事实 (ansible_facts) 或预定义的变量,减少在目标主机上运行额外命令的需要。 - 避免深层嵌套:过于复杂的循环嵌套条件判断会降低 Playbook 的可读性和可维护性。考虑拆分成多个任务或使用角色。
- 测试:使用
--check(dry-run) 和--diff模式测试你的 Playbook,特别是复杂的循环和条件逻辑。 - OpenEuler 特性:熟悉 OpenEuler 特有的服务名 (如
firewalld而非iptables)、包管理 (dnf)、路径等,确保条件判断准确。
