Ansible 流程控制:条件与循环

Ansible 流程控制:条件与循环

条件判断

Ansible 提供了强大的条件判断功能,让你可以根据不同情况执行不同的任务。

when 条件

基本条件

---
- name: 条件执行示例
  hosts: all
  tasks:
    - name: 只在 Debian 系统上安装
      apt:
        name: apt-transport-https
        state: present
      when: ansible_os_family == "Debian"

    - name: 只在 CentOS 系统上安装
      yum:
        name: yum-utils
        state: present
      when: ansible_os_family == "RedHat"

多条件组合

# AND 条件
- name: 同时满足多个条件
  debug:
    msg: "这是 Ubuntu 20.04 系统"
  when:
    - ansible_distribution == "Ubuntu"
    - ansible_distribution_version == "20.04"

# OR 条件
- name: 满足任一条件
  debug:
    msg: "这是 CentOS 或 RHEL"
  when: ansible_distribution in ["CentOS", "RedHat"]

# NOT 条件
- name: 非 Debian 系统
  debug:
    msg: "不是 Debian 系统"
  when: ansible_os_family != "Debian"

布尔条件

# 变量为真
- name: 当变量为真时执行
  debug:
    msg: "功能已启用"
  when: feature_enabled

# 变量为假
- name: 当变量为假时执行
  debug:
    msg: "功能未启用"
  when: not feature_enabled

# 检查变量是否存在
- name: 检查变量定义
  debug:
    msg: "变量已定义"
  when: my_var is defined

# 检查变量是否未定义
- name: 检查变量未定义
  debug:
    msg: "变量未定义"
  when: my_var is undefined

字符串比较

- name: 字符串比较
  debug:
    msg: "环境是生产环境"
  when: deployment_env == "production"

- name: 字符串匹配
  debug:
    msg: "主机名包含 web"
  when: "'web' in inventory_hostname"

- name: 正则表达式匹配
  debug:
    msg: "匹配 IP 地址"
  when: inventory_hostname | match('192\\.168\\..*')

数字比较

# 大于
- name: 磁盘使用率超过 80%
  debug:
    msg: "磁盘空间不足"
  when: disk_usage_percent > 80

# 小于等于
- name: 内存大于 8GB
  debug:
    msg: "内存充足"
  when: ansible_memtotal_mb >= 8192

# 模运算
- name: 索引是偶数
  debug:
    msg: "偶数索引"
  when: index % 2 == 0

列表条件

# 列表包含元素
- name: 检查列表是否包含元素
  debug:
    msg: "包含 nginx"
  when: "'nginx' in installed_packages"

# 列表为空
- name: 检查列表是否为空
  debug:
    msg: "列表为空"
  when: my_list | length == 0

# 列表不为空
- name: 检查列表不为空
  debug:
    msg: "列表不为空"
  when: my_list | length > 0

字典条件

# 检查键是否存在
- name: 检查字典键
  debug:
    msg: "包含该键"
  when: "'port' in service_config"

# 检查值
- name: 检查字典值
  debug:
    msg: "端口是 80"
  when: service_config.port == 80

循环

标准循环(loop)

---
- name: 循环示例
  hosts: all
  tasks:
    - name: 创建多个用户
      user:
        name: "{{ item }}"
        state: present
      loop:
        - alice
        - bob
        - charlie

    - name: 安装多个软件包
      package:
        name: "{{ item }}"
        state: present
      loop:
        - vim
        - git
        - curl
        - wget

字典循环

- name: 使用字典循环
  debug:
    msg: "用户 {{ item.name }} 的 ID 是 {{ item.id }}"
  loop:
    - { name: 'alice', id: 1001 }
    - { name: 'bob', id: 1002 }
    - { name: 'charlie', id: 1003 }

列表字典

- name: 列表字典
  debug:
    msg: "键: {{ item.key }}, 值: {{ item.value }}"
  loop: "{{ my_dict | dict2items }}"

带索引的循环

- name: 带索引的循环
  debug:
    msg: "索引 {{ index }}: {{ item }}"
  loop:
    - apple
    - banana
    - cherry
  loop_control:
    index_var: index

循环控制

# 限制循环次数
- name: 只处理前 3 个
  debug:
    msg: "{{ item }}"
  loop: "{{ my_list }}"
  loop_control:
    loop_var: item
  when: loop_var_index < 3

# 跳过特定元素
- name: 跳过特定元素
  debug:
    msg: "{{ item }}"
  loop: "{{ my_list }}"
  when: item != "skip_me"

循环变量命名

# 自定义循环变量名
- name: 嵌套循环
  debug:
    msg: "外层: {{ outer_item }}, 内层: {{ inner_item }}"
  loop:
    - group1
    - group2
  loop_control:
    loop_var: outer_item
  include_tasks:
    file: inner_loop.yml
    loop:
      - user1
      - user2
    loop_control:
      loop_var: inner_item

until 循环

until 循环会重复执行任务,直到满足条件为止。

---
- name: 等待服务启动
  hosts: all
  tasks:
    - name: 等待 Nginx 启动
      uri:
        url: http://localhost/health
        status_code: 200
      register: result
      until: result.status == 200
      retries: 30
      delay: 5

    - name: 等待文件出现
      stat:
        path: /tmp/deploy_complete
      register: file_status
      until: file_status.stat.exists
      retries: 60
      delay: 1

until 循环与 register

- name: 重试命令直到成功
      shell: /opt/app/check_status.sh
      register: app_status
      until: app_status.rc == 0
      retries: 10
      delay: 2
      changed_when: false

高级条件判断

嵌套条件

- name: 嵌套条件
  debug:
    msg: "满足所有嵌套条件"
  when:
    - ansible_distribution == "Ubuntu"
    - ansible_distribution_version in ["20.04", "22.04"]
    - (ansible_memtotal_mb | int) >= 4096

过滤器条件

# 使用过滤器
- name: 检查文件是否存在
  debug:
    msg: "文件存在"
  when: result.stat.exists | default(false)

# 使用 truthy/falsy
- name: 检查是否启用
  debug:
    msg: "已启用"
  when: feature_enabled | bool

# 使用默认值
- name: 使用默认值
  debug:
    msg: "值是 {{ config_value | default('default', true) }}"

复杂条件

# 检查多种条件
- name: 复杂条件判断
  debug:
    msg: "满足条件"
  when: >
    (ansible_distribution == "Ubuntu" and ansible_distribution_version == "22.04")
    or
    (ansible_distribution == "CentOS" and ansible_distribution_version == "8")

高级循环

嵌套循环

- name: 嵌套循环
  debug:
    msg: "{{ item[0] }} - {{ item[1] }}"
  loop: "{{ ['server1', 'server2'] | product(['user1', 'user2']) | list }}"

循环注册

- name: 循环并注册结果
      shell: echo "{{ item }}"
      register: echo_results
      loop:
        - one
        - two
        - three
      changed_when: false

    - name: 显示所有结果
      debug:
        msg: "{{ item.stdout }}"
      loop: "{{ echo_results.results }}"

循环中的条件

- name: 循环中的条件
  debug:
    msg: "{{ item }}"
  loop:
    - apple
    - banana
    - cherry
  when: item.startswith('b')

失败处理

failed_when

- name: 自定义失败条件
  shell: /usr/local/bin/check_disk.sh
  register: disk_check
  failed_when:
    - "'CRITICAL' in disk_check.stdout"
    - disk_check.rc != 0

ignore_errors

- name: 忽略错误继续执行
      command: /usr/local/bin/optional_command.sh
      ignore_errors: yes
      register: optional_result

    - name: 根据上一步结果决定
      debug:
        msg: "上一步失败了,但继续执行"
      when: optional_result.failed

block 和 rescue

---
- name: 错误处理示例
  hosts: all
  tasks:
    - name: 使用 block 和 rescue
      block:
        - name: 尝试执行
          command: /usr/bin/some_command

      rescue:
        - name: 出错时执行
          debug:
            msg: "命令执行失败,执行恢复操作"

      always:
        - name: 无论成功失败都执行
          debug:
            msg: "清理工作"

实战示例

系统初始化

---
- name: 系统初始化
  hosts: all
  become: yes

  tasks:
    - name: 更新软件包(仅 Debian)
      apt:
        update_cache: yes
        cache_valid_time: 3600
      when: ansible_os_family == "Debian"

    - name: 更新软件包(仅 RedHat)
      yum:
        update_cache: yes
      when: ansible_os_family == "RedHat"

    - name: 安装常用工具
      package:
        name: "{{ item }}"
        state: present
      loop:
        - vim
        - git
        - curl
        - wget
        - htop
        - iotop

    - name: 配置时区(仅当未配置时)
      timezone:
        name: Asia/Shanghai
      when: ansible_date_time.tz != 'CST'

    - name: 配置 NTP
      template:
        src: chrony.conf.j2
        dest: /etc/chrony.conf
      notify: restart chronyd
      when: ansible_service_mgr == 'systemd'

应用部署

---
- name: 应用部署
  hosts: app_servers
  become: yes

  tasks:
    - name: 检查是否已安装
      stat:
        path: "{{ app_dir }}/current"
      register: app_installed

    - name: 备份现有版本
      shell: mv {{ app_dir }}/current {{ app_dir }}/backup_{{ ansible_date_time.epoch }}
      when:
        - app_installed.stat.exists
        - app_installed.stat.isdir

    - name: 拉取新版本
      git:
        repo: "{{ app_repo }}"
        dest: "{{ app_dir }}/release_{{ app_version }}"
        version: "{{ app_version }}"

    - name: 更新当前链接
      file:
        src: "{{ app_dir }}/release_{{ app_version }}"
        dest: "{{ app_dir }}/current"
        state: link

    - name: 等待服务健康
      uri:
        url: http://localhost:8080/health
        status_code: 200
      register: health_check
      until: health_check.status == 200
      retries: 30
      delay: 2

    - name: 清理旧版本
      shell: find {{ app_dir }} -maxdepth 1 -type d -name 'release_*' ! -name 'release_{{ app_version }}' -mtime +7 -exec rm -rf {} \;
      when: cleanup_old_releases

配置管理

---
- name: 配置管理
  hosts: config_servers
  become: yes

  tasks:
    - name: 为每个环境创建配置
      template:
        src: "{{ item.env }}.conf.j2"
        dest: "/etc/myapp/{{ item.env }}.conf"
      loop: "{{ environments }}"
      when: item.enabled

    - name: 更新主配置
      template:
        src: main.conf.j2
        dest: /etc/myapp/main.conf
      notify: reload service

    - name: 验证配置
      command: myapp --validate-config
      register: config_validation
      changed_when: false
      failed_when: config_validation.rc != 0

    - name: 配置验证成功
      debug:
        msg: "配置验证通过"
      when: config_validation.rc == 0

最佳实践

  1. 条件优先: 尽量使用条件判断而非创建多个 Play
  2. 循环控制: 使用 loop 代替 with_items
  3. 错误处理: 合理使用 block 和 rescue
  4. 性能优化: 循环中的条件会降低性能,考虑使用 Jinja2 过滤器
  5. 代码清晰: 复杂条件使用多行格式
  6. 测试验证: 充分测试所有条件分支

总结

通过本教程,你已经学会了:

  • when 条件的使用方法
  • 各种条件判断技巧
  • 标准循环和高级循环
  • until 循环的使用
  • 失败处理和错误恢复
  • 实战应用场景

发表回复

后才能评论