ansible管理变量和事实

ansible管理变量和事实与实施任务控制

在 Ansible 中,变量和事实(Facts)就像给剧本(Playbook)配备的 "信息工具箱",让你的自动化配置管理更灵活、更智能。

变量:提前准备的 "预设信息"

变量就像你出门前准备的清单,提前可以提前预先定义好各种信息,用的时候直接拿出来用。

  • 怎么定义?

    • 在 Playbook 里直接写

      :就像把清单贴在剧本首页

      yaml 复制代码
      - hosts: webservers
        vars:
          app_port: 8080  # 定义应用端口变量
          app_name: "myapp"  # 定义应用名称变量
        tasks:
          - name: 启动应用
            command: "/opt/{{ app_name }}/start --port {{ app_port }}"
    • 单独放变量文件

      :就像把清单放进文件夹,更整洁

      yaml 复制代码
      # vars/app_vars.yml
      app_port: 8080
      app_name: "myapp"

      然后在 Playbook 里引用:

      yaml 复制代码
      - hosts: webservers
        vars_files:
          - vars/app_vars.yml
    • 命令行传变量

      :临时改清单,比如临时换个端口

      bash 复制代码
      ansible-playbook deploy.yml -e "app_port=9090"
  • 变量的小技巧

    • 可以用{``{ 变量名 }}在任务中调用,像插卡一样灵活
    • 支持条件判断,比如 "如果是生产环境,端口用 443;测试环境用 8080"

事实(Facts):自动收集的 "系统情报"

事实就像 Ansible 派出去的 "侦察兵",会自动收集目标主机的信息(比如 IP 地址、操作系统、内存大小等),不用你手动问。

  • 怎么看收集到的情报?

    跑个命令让侦察兵汇报:

    bash 复制代码
    ansible webservers -m setup

    会得到一堆信息,比如:

    • ansible_facts['os_family']:操作系统类型(比如 RedHat、Debian)
    • ansible_facts['default_ipv4']['address']:主机 IP 地址
    • ansible_facts['memory_mb']['total']:总内存(MB)
  • 在 Playbook 里用事实

    比如根据操作系统选不同的安装命令:

    yaml 复制代码
    - hosts: all
      tasks:
        - name: 安装nginx(Debian系统)
          apt: name=nginx state=present
          when: ansible_facts['os_family'] == "Debian"
    
        - name: 安装nginx(RedHat系统)
          yum: name=nginx state=present
          when: ansible_facts['os_family'] == "RedHat"
  • 小提示

    • 事实默认会自动收集,如果想关掉(比如加快执行速度),可以在 Playbook 里加gather_facts: no
    • 可以自定义事实(local facts),把自己关心的信息存到目标主机的/etc/ansible/facts.d/目录,Ansible 会自动读取

总结:变量 vs 事实

  • 变量:你主动告诉 Ansible 的信息(提前设定)
  • 事实:Ansible 主动从目标主机收集的信息(动态获取)

两者结合,就像给 Ansible 装上了 "大脑"------ 既知道你提前安排的计划,又能根据实际情况灵活调整,让配置管理既聪明又高效~

管理 VARIABLES

变量简介

ansible 利用变量来存储数据,以便在Ansible项目文件中重复引用,有利于简化项目的创建和维护,降低出错率。我们在playbook中可以针对如用户、软件包、服务、文件等进行变量定义。

变量命名规则
  • 只能包含字母、数字和下划线(如包含空格、点、$符号都为非法变量名)
  • 只能以字母开头
变量范围和优先级

ansible项目文件中多个位置支持定义变量,主要包含三个基本范围:

  • Global scope:从命令行或 Ansible 配置设置的变量。
  • Play scope:在play和相关结构中设置的变量。
  • Host scope:由清单、事实(fact)收集或注册的任务,在主机组和个别主机上设置的变量。

优先级从高到低顺序:Global -> Play -> Host

在多个级别上定义了相同名称的变量,则采用优先级别最高的变量。

Play scope

在 Ansible 里,"Play scope"(剧本作用域)可以理解为一个 Play(剧本)的 "管辖范围" 和 "生效边界"。它就像给这个 Play 划了个圈,圈里的规则、变量、设置只在这个范围内起作用,不会跑到圈外去影响其他 Play。

举个生活例子:你家里有客厅和卧室两个区域(相当于两个 Play)。在客厅里你规定 "看电视音量不能超过 30"(这是客厅 Play 的变量 / 设置),这个规则只在客厅生效,到了卧室就不算数了 ------ 这就是每个 Play 有自己独立的 scope。

具体来说,Play scope 包含这些 "圈内要素":

  • 目标主机 :通过hosts指定的主机 / 主机组,只有这些主机受这个 Play 管理
  • 变量 :在 Play 的varsvars_files里定义的变量,只在当前 Play 的任务中可用
  • 提权设置 :当前 Play 里的become相关配置,不会影响其他 Play
  • 事实收集gather_facts的开关状态,只控制当前 Play 是否收集主机信息

比如一个 Playbook 里有两个 Play:

yaml 复制代码
- name: 管理web服务器  # Play 1
  hosts: webservers
  vars:
    app: "nginx"  # 这个变量只在Play 1里有效
  tasks:
    - name: 安装nginx
      yum: name={{ app }} state=present

- name: 管理数据库服务器  # Play 2
  hosts: dbservers
  vars:
    app: "mysql"  # 这个变量只在Play 2里有效
  tasks:
    - name: 安装mysql
      yum: name={{ app }} state=present

这里两个 Play 的app变量互不干扰,因为它们各自有独立的 scope。

简单说,Play scope 就是 Ansible 里的 "楚河汉界",让每个 Play 在自己的地盘里按自己的规则干活,互不打扰~

vars 声明

在 Ansible 中,vars就像给 Playbook 准备的 "变量口袋",用来提前存放各种需要反复使用的信息。声明变量的方式灵活多样,就像你可以把东西放在口袋、抽屉或专门的收纳盒里一样。

1. 直接在 Play 里声明(最直观)

就像把常用物品直接揣在口袋里,随用随拿。在 Play 的vars块里定义变量,只在当前 Play 中生效。

yaml 复制代码
- name: 部署web应用
  hosts: webservers
  vars:
    app_name: "blog"       # 应用名称
    app_port: 8080         # 运行端口
    max_connections: 100   # 最大连接数
  tasks:
    - name: 创建应用目录
      file:
        path: "/opt/{{ app_name }}"  # 引用变量
        state: directory

2. 单独放变量文件(更整洁)

如果变量太多,就像东西太多需要用抽屉分类收纳。把变量写在单独的 YAML 文件里,再在 Playbook 中引用。

yaml 复制代码
# 变量文件:vars/app_settings.yml
app_name: "blog"
app_port: 8080
db_host: "db.example.com"

在 Playbook 中调用这个文件:

yaml 复制代码
- name: 部署web应用
  hosts: webservers
  vars_files:
    - vars/app_settings.yml  # 引入外部变量文件
  tasks:
    - name: 配置数据库连接
      lineinfile:
        path: "/opt/{{ app_name }}/config.ini"
        line: "db_host = {{ db_host }}"

3. 命令行临时声明(应急用)

就像临时从口袋里掏出个备用物品,适合临时修改变量值,不用改 Playbook 本身。用-e参数传递:

bash 复制代码
# 临时把端口改成9090运行
ansible-playbook deploy.yml -e "app_port=9090"

4. 主机 / 组变量(按目标分类)

如果不同主机需要不同变量(比如 web 服务器和数据库服务器配置不同),可以在inventory目录下创建专门的变量文件,Ansible 会自动对应。

目录结构通常是这样:

plaintext 复制代码
inventory/
  ├── hosts                # 主机清单
  ├── group_vars/          # 组变量(对整个组生效)
  │   ├── webservers.yml   # web服务器组的变量
  │   └── dbservers.yml    # 数据库服务器组的变量
  └── host_vars/           # 主机变量(对单台主机生效)
      └── web01.yml        # 给web01主机单独的变量

比如group_vars/webservers.yml里写:

yaml 复制代码
app_type: "nginx"
log_path: "/var/log/nginx"

变量使用小技巧

  • 引用变量时用双大括号{``{ 变量名 }},比如{``{ app_name }}

  • 变量名可以包含字母、数字和下划线(不能以数字开头)

  • 支持嵌套,比如:

    yaml 复制代码
    app:
      name: "blog"
      port: 8080

    引用时用

    复制代码
    {{ app.name }}
    
    {{ app.port }}

简单说,vars声明就是给 Ansible 提前 "备课"------ 把需要用的信息整理好,用的时候直接喊名字就能调出来,不用重复写死,既灵活又好维护~

Host scope

主机变量应用于主机和主机组。主机变量优先级高于主机组变量。

主机清单中定义

较旧的做法是直接在清单文件中定义。不建议采用,但仍可能会遇到。

复制代码
[servers]
node1 user=laoma
node2

[servers:vars]
user=laowang
目录分层结构定义

在项目目录中创建如下目录:

  • group_vars ,定义主机组变量。目录中文件名可以直接使用 主机组名 或者 主机组名.yaml

  • host_vars ,定义主机变量。目录中文件名可以直接使用 主机名 或者 主机名.yaml

主机连接特殊变量

详情参考:主机连接特殊变量

  • ansible_connection,与主机的连接类型,可以是 smart、ssh 或 paramiko。默认为smart。

  • ansible_host,要连接的主机的名称,默认值就是主机清单名称。

  • ansible_port,ssh 端口号,如果不是 22。

  • ansible_user,ssh 用户名。

  • ansible_ssh_pass,要使用的 ssh 密码。切勿以纯文本形式存储此变量,始终使用保管库。

  • ansible_ssh_private_key_file,ssh 使用的私钥文件。如果使用多个密钥并且您不想使用 SSH 代理,这很有用。

  • ansible_ssh_common_args,此设置始终附加到 sftp、scp 和 ssh 的默认命令行。

  • ansible_sftp_extra_args,此设置始终附加到默认的 sftp 命令行。

  • ansible_scp_extra_args,此设置始终附加到默认的 scp 命令行。

  • ansible_ssh_extra_args,此设置始终附加到默认的 ssh 命令行。

  • ansible_become,等效于 ansible_sudo 或 ansible_su,允许强制提权。

  • ansible_become_method,允许设置权限提升方法。

  • ansible_become_user,等效于 ansible_sudo_user 或 ansible_su_user,允许设置您通过权限升级成为的用户。

  • ansible_become_pass,等效于 ansible_sudo_pass 或 ansible_su_pass,允许您设置权限提升密码(切勿以纯文本形式存储此变量;始终使用保管库。请参阅变量和保管库)。

数组变量

除了将与同一元素相关的配置数据(软件包列表、服务列表和用户列表等)分配到多个变量外,管理员也可以使用数组变量,将多个值存储在同一变量中。

示例:

yaml 复制代码
user1_first_name: Bob
user1_last_name: Jones
user1_home_dir: /users/bjones
user2_first_name: Anne
user2_last_name: Cook
user2_home_dir: /users/acook

改写如下:

yaml 复制代码
users:
  bjones:
    first_name: Bob
    last_name: Jones
    home_dir: /users/bjones
  acook:
    first_name: Anne
    last_name: Cook
    home_dir: /users/acook

数组变量引用方式一:

yaml 复制代码
# Returns 'Bob'
users.bjones.first_name
# Returns '/users/acook'
users.acook.home_dir

数组变量引用方式二:

yaml 复制代码
# Returns 'Bob'
users['bjones']['first_name']
# Returns '/users/acook'
users['acook']['home_dir']

引用方式总结:

  • 如果使用方法一**.分隔符**引用的关键字与python的功能函数同名,例如discard、copy、add,那么就会出现问题。方法二['']引用方式可以避免这种错误。
  • 尽管两种方法都可以使用,为了减少排故难度,Ansible中统一使用其中一种方法。

示例1:

yaml 复制代码
---
- name: test vars statement in play
  hosts: node1
  vars: 
    users:
      laoma:
        user_name: laoma
        home_path: /home/laoma
      laowang:
        user_name: laowang
        home_path: /home/laowang
  tasks:
    - name: add user {{ users.laoma.user_name }}
      user:
        name: '{{ users.laoma.user_name }}'
        home: "{{ users.laoma.home_path }}"
 
    - name: debug laowang
      debug: 
        msg: >
          username is {{ users['laowang']['user_name'] }}
          home_path is {{ users['laowang']['home_path'] }}

示例2:

yaml 复制代码
---
- name: test vars statement in play
  hosts: node1
  vars: 
    users:
      - user_name: laoma1
        home_path: /home/laoma1
      - user_name: laoma2
        home_path: /home/laoma2
  tasks:
    - name: add user {{ users.0.user_name }}
      user:
        name: "{{ users.0.user_name }}"
        home: "{{ users.0.home_path }}"

    - name: debug {{ users[1].user_name }}
      debug: 
        msg: "{{ users[1].user_name }}"
register 语句

**register 语句捕获任务输出。**输出保存在一个临时变量中,稍后在playbook中可用于调试用途或者达成其他目的。

示例:

yaml 复制代码
---
- name: Installs a package and prints the result
  hosts: node1
  tasks:
    - name: Install the package
      yum:
        name: httpd
        state: installed
      register: install_result
    - debug: 
        var: install_result

在 Ansible 里,register就像给任务装了个 "记录仪",能把任务执行的结果(比如命令输出、状态信息)存起来,方便后面的任务 "回看" 或 "利用" 这些结果。

打个比方:就像你让同事去查一个文件的大小,他回来告诉你 "文件有 100MB"------register就相当于把这句话记在笔记本上,你后面可以根据这个结果决定 "要不要备份"(如果大于 50MB 就备份)。

基本用法:记录任务结果

在任务里加register: 变量名,就会把结果存到这个变量里。比如记录ls命令的输出:

yaml 复制代码
- name: 查看/tmp目录内容
  command: ls /tmp
  register: tmp_files  # 把结果存到tmp_files变量里

- name: 打印刚才的结果
  debug:
    var: tmp_files  # 显示变量内容

运行后会看到tmp_files里包含很多信息:命令是否成功(success)、输出内容(stdout)、错误信息(stderr)等。

实用场景:根据结果做判断

最常用的是结合when条件,根据记录的结果决定下一步操作。

比如:检查某个进程是否存在,存在就重启,不存在就启动:

yaml 复制代码
- name: 检查nginx进程
  command: pgrep nginx
  register: nginx_status
  ignore_errors: yes  # 即使命令失败(进程不存在)也不终止Playbook

- name: 如果进程存在,就重启nginx
  service:
    name: nginx
    state: restarted
  when: nginx_status.rc == 0  # rc=0表示命令成功(进程存在)

- name: 如果进程不存在,就启动nginx
  service:
    name: nginx
    state: started
  when: nginx_status.rc != 0  # rc≠0表示命令失败(进程不存在)

这里nginx_status.rc是命令的返回码(rc即 return code),0 代表成功,非 0 代表失败。

常用的结果字段

register变量里有很多有用的 "子信息",常用的有:

  • stdout:命令的标准输出(比如ls列出的文件)
  • stderr:命令的错误输出(如果命令失败)
  • rc:返回码(0 = 成功,非 0 = 失败)
  • changed:任务是否改变了系统状态(布尔值)
  • failed:任务是否失败(布尔值)

比如只想看命令输出的内容:

yaml 复制代码
- name: 查看系统版本
  command: cat /etc/os-release
  register: os_info

- name: 打印系统版本信息
  debug:
    msg: "系统版本:{{ os_info.stdout }}"  # 只取stdout部分

简单说,register就是 Ansible 里的 "记事贴"------ 让任务之间能 "传递消息",根据前面的结果动态决定后面的操作,让 Playbook 变得更智能、更灵活~

MAGIC 变量

magic 变量由 Ansible 自动设置,可用于获取与特定受管主机相关的信息。

假设当前清单内容为:

ini 复制代码
controller

[webs]
node1
node2

[dbs]
node3
node4

最常用四个 Magic 变量:

  • inventory_hostname,包含清单中配置的当前受管主机的主机名称。这可能因为各种原因而与FACTS报告的主机名称不同。

    bash 复制代码
    [laoma@controller web]$ ansible node1 -m debug -a 'var=inventory_hostname'
    node1 | SUCCESS => {
        "inventory_hostname": "node1"
    }
  • group_names,列出当前受管主机所属的所有主机组。

    bash 复制代码
    [laoma@controller web]$ ansible node1 -m debug -a 'var=group_names'
    node1 | SUCCESS => {
        "group_names": [
            "webs"
        ]
    }
  • groups,列出清单中的所有组,以及组中含有的主机。

    bash 复制代码
    [laoma@controller web]$ ansible node1 -m debug -a 'var=groups'
    node1 | SUCCESS => {
        "groups": {
            "all": [
                "workstation",
                "node1",
                "node2",
                "node3",
                "node4"
            ],
            "dbs": [
                "node3",
                "node4"
            ],
            "ungrouped": [
                "controller"
            ],
            "webs": [
                "node1",
                "node2"
            ]
        }
    }
  • hostvars,包含所有受管主机的变量,可用于获取另一台受管主机的变量的值。如果还没有为受管主机收集FACTS,则它不会包含该主机的 FACTS。

    例如:hostvars.controller.group_names

在 Ansible 里,"MAGIC 变量"(魔法变量)就像自带的 "万能钥匙",不需要你手动定义,Ansible 会自动生成并提供这些变量,帮你快速获取 inventory(主机清单)里的各种信息,让 Playbook 更灵活地处理主机间的关系。

它们之所以叫 "魔法变量",是因为你不用声明就能直接用,就像凭空出现的工具,专门解决和主机清单相关的问题。

最常用的几个 "魔法变量":

1. inventory_hostname:当前主机的 "身份证"

返回当前正在处理的主机在 inventory 里的名字(不是主机的 hostname,而是你在清单里写的名字)。

比如 inventory 里写着 web01.example.com,那这个变量就返回它。

yaml 复制代码
- name: 显示当前主机名
  debug:
    msg: "正在处理的主机:{{ inventory_hostname }}"
2. groups:主机组的 "花名册"

返回所有主机组的列表,以及每个组里的主机。比如想知道 webservers 组有哪些主机:

yaml 复制代码
- name: 显示web服务器组的所有主机
  debug:
    msg: "web组主机:{{ groups['webservers'] }}"

如果想获取所有主机组的名字,用 groups.keys()

3. group_names:当前主机的 "所属群组"

返回当前主机所在的所有组(列表形式)。比如一台主机既在 webservers 组又在 prod 组,这个变量就会返回这两个组名。

yaml 复制代码
- name: 显示当前主机所属的组
  debug:
    msg: "我属于这些组:{{ group_names }}"
4. hostvars:其他主机的 "信息库"

可以获取其他主机的变量或 facts 信息,相当于 "跨主机查资料"。

比如想获取 db01 主机的 IP 地址(需要先收集过 facts):

yaml 复制代码
- name: 显示db01的IP
  debug:
    msg: "数据库IP:{{ hostvars['db01']['ansible_default_ipv4']['address'] }}"
5. play_hosts:当前 Play 的 "任务清单"

返回当前 Play 中正在处理的所有主机(受 hosts 字段限制的主机列表)。

yaml 复制代码
- name: 显示当前Play要处理的所有主机
  debug:
    msg: "本次任务涉及主机:{{ play_hosts }}"

为什么需要魔法变量?

它们就像 Ansible 内置的 "导航系统",帮你在复杂的主机清单中定位信息:

  • 比如跨主机通信(web 服务器需要知道数据库服务器的 IP)
  • 比如根据主机所在组执行不同任务
  • 比如动态获取当前处理的主机信息

管理 SECRETS

Ansible Vault 简介

Ansible可能需要访问密码或API密钥等敏感数据,此信息可能以纯文本形式存储在清单变量或其他Ansible文件中。任何有权访问Ansible文件的用户或存储这些Ansible文件的版本控制系统都能够访问此敏感数据。

这显然存在安全风险。Ansible随附的 Ansible Vault 可以加密任何由Ansible使用的结构化数据文件,包括清单变量、playbook中含有的变量文件、在执行playbook时作为参数传递的变量文件,以及Ansible角色中定义的变量。

变量管理推荐做法
  • 包含敏感变量的文件可通过 ansible-vault 命令进行保护。
  • 敏感变量和所有其他变量保存在相互独立的文件中。
  • 管理组变量和主机变量的首选方式是在项目目录中创建子目录。

可为每个主机组或受管主机使用独立的目录。这些目录可包含多个变量文件,它们都由该主机组或受管主机使用。

管理 FACTS

FACTS 介绍

FACTS 是 Ansible 在受管主机上自动检测到的变量,默认保存在内容中,只存在于本次playbook执行期间。

FACTS含有主机相关的信息,可以像play中的常规变量一样使用。

受管主机的 facts 包括:

• 主机名称 • 内核版本 • 网络接口 • IP地址 • 操作系统版本 • 各种环境变量

• CPU数量 • 提供的或可用的内存 • 可用磁盘空间

借助 facts,可以方便地检索受管主机的状态,并根据该状态确定要执行的操作。

例如:

  • 可以根据当前内核版本的FACTS运行条件任务,以此来重新启动服务器。
  • 可以根据通过FACTS报告的可用内存来自定义 MySQL 配置文件。
  • 可以根据FACTS的值设置配置文件中使用的 IPv4 地址。

通常,每个play在执行第一个任务之前会先自动收集FACTS。

查看 FACTS 内容

示例1:查看所有变量

yaml 复制代码
---
- name: Dump facts
  hosts: node1

  tasks:
    - name: Print all facts
      debug:
        var: ansible_facts

示例2:查看单个变量

yaml 复制代码
---
- hosts: node1
  tasks:
    - name: Print Ansible facts
      debug: 
        msg: >
          The default IPv4 address of {{ ansible_fqdn }}
          is {{ ansible_default_ipv4.address }}
部分 FACTS
FACT VARIABLE
短主机名 ansible_facts['hostname']
完全限定的域名 ansible_facts['fqdn']
主要IPv4地址(基于路由) ansible_facts['default_ipv4']['address']
所有网络接口的名称列表 ansible_facts['interfaces']
/dev/vdal磁盘分区的大小 ansible_facts['devices']['vda']['partitions']['vda1]['size']
DNS服务器列表 ansible_facts['dns']['nameservers']
当前运行的内核的版本 ansible_facts['kernel']
setup 和 gather_facts 模块

setup 和 gather_facts 模块都可以用来收集facts:

  • gather_facts 模块,只能用来收集facts。

  • setup 模块,除了用来收集facts,还提供额外选项:

    • filter 选项,用于查看特定facts值。

    • gather_subset 选项,用于控制收集facts范围

关闭 FACTS 收集

关闭 FACTS 收集部分原因:

  • 不使用任何FACTS
  • 希望加快play速度或减小play在受管主机上造成的负载
  • 受管主机因为某种原因而无法运行setup模块
  • 需要安装一些必备软件后再收集FACTS

Ansible配置文件设置

ini 复制代码
[defaults]
gathering = explicit

play中设置

yaml 复制代码
---
- name: Fact dump
  hosts: node1
  gather_facts: no

即使关闭以后,也可以随时使用setup模块收集facts。

实施任务控制

编写循环任务

利用循环,管理员无需编写多个使用同一模块的任务。例如,确保存在五个用户,不需要编写五个任务,而是只需编写一个任务来对含有五个用户的列表迭代。

Ansible支持使用 loop 关键字对一组项目迭代任务。您可以配置循环以利用列表中的各个项目、列表中各个文件的内容、生成的数字序列或更为复杂的结构来重复任务。

简单循环

简单循环对一组项目迭代任务。loop关键字添加到任务中, 将应对其迭代任务的项目列表取为值。循环变量item保存每个迭代过程中使用的值。

示例:

yaml 复制代码
---
- name: add several users
  hosts: node1
  gather_facts: no
  tasks:
    - name: add user jane
      user:
        name: "jane"
        groups: "wheel"
        state: present
    - name: add user joe
      user:
        name: "joe"
        state: present
        groups: "wheel"

使用loop循环改写:

yaml 复制代码
- name: test loop
  hosts: node1
  gather_facts: no
  tasks:
    - name: add users
      user:
        name: "{{ item }}"
        groups: "wheel"
        state: present
      loop:
         - jane
         - joe

这个 Playbook 的作用是在node1主机上批量创建两个用户(jane 和 joe),并将他们加入wheel组。

  • name: test loop:这是 Play 的名称,用于标识这个 Play 的用途
  • hosts: node1:指定在node1这台主机上执行
  • gather_facts: no:关闭 facts 收集,加快执行速度(因为这个任务不需要系统信息)
  • 任务部分使用了loop关键字,后面跟着一个用户列表[jane, joe]

在循环过程中,Ansible 会自动将列表中的每个元素依次赋值给item变量,然后执行user模块:

  • 第一次循环:item = jane,创建用户 jane 并加入 wheel 组
  • 第二次循环:item = joe,创建用户 joe 并加入 wheel 组

user模块的参数说明:

  • name: "{``{ item }}":用户名,这里引用循环变量
  • groups: "wheel":指定用户所属的附加组(wheel 组通常用于 sudo 权限)
  • state: present:确保用户存在(如果不存在则创建)

执行这个 Playbook 后,目标主机上会新增 jane 和 joe 两个用户,且都属于 wheel 组。这种循环方式非常适合需要批量执行相同操作的场景,避免了重复编写任务代码。

循环散列或字典列表

在以下示例中,列表中的每个项实际上是散列或字典。

示例中的每个散列或字典具有两个键,即name和groups,当前item循环变量中每个键的值可以分别通过item.name和item.groups变量来检索。

示例:

yaml 复制代码
- name: test loop
  hosts: node1
  gather_facts: no
  tasks:
    - name: add users
      user:
        name: "{{ item.name }}"
        groups: "{{ item.groups }}"
        state: present
      loop: 
        - name: jane
          groups: wheel
        - name: joe
          groups: root

改写为:

yaml 复制代码
---
- name: add several users
  hosts: node1
  gather_facts: no
  vars:
    users:
      - name: jane
        groups: wheel
      - name: joe
        groups: root
  tasks:
    - name: add users 
      user:
        name: "{{ item.name }}"
        state: present
        groups: "{{ item.groups }}"  
      loop: "{{ users }}"
Register 与 Loop

示例:

yaml 复制代码
---
- name: Loop Register Test
  hosts: node1
  gather_facts: no
  tasks:
    - name: Looping Echo Task
      shell: "echo This is my item: {{ item }}"
      loop:
        - one
        - two
      register: result
    
    - name: Show result variable
      debug:
        var: result
    
    - name: Show result variable stdout
      debug:
        msg: "STDOUT from previous task: {{ item.stdout }}"
      loop: "{{ result.results }}"

编写条件任务

Ansible可使用conditionals在符合特定条件时执行任务或play。

例如,管理员可利用条件来区分不同的受管节点,并根据它们所符合的条件来分配功能角色。 Playbook变量、注册的变量和ANSIBLE FACTS都可通过条件来进行测试。可以使用比较字符串、数字数据和布尔值的运算符。

用例:

  1. 定义变量min_memory,判断被管理节点可用内存是否满足该值。
  2. 捕获命令输出,判定task是否执行完成,以便决定是否进行下一步操作。
  3. 被管理节点上收集到的网络facts,判定是否适合哪种绑定(bonding或者trunking)。
  4. 根据CPU的数量决定如何调优web服务器。
  5. Registered变量与预定义的变量对比,判断是否有变化。例如文件的MD5值。
when 语句

ansible playbook 中使用 when 来运行条件任务。

when 用于有条件地运行任务,取要测试的条件作为值。如果条件满足,则运行任务。若条件不满足,则跳过任务。

注意:通常的惯例是将可能存在的任何when关键字放在任务名称和模块(及模块参数)的后面。原因是任务是YAML散列/字典,when 语句只是任务中的一个键,就如任务的名称以及它所使用的模块一样。

常见判断
操作 示例
等于(值为字符串) ansible_machine == "x86_64"
等于(值为数字) max_memory == 512
小于 min_memory < 128
大于 min_memory > 256
小于等于 min_memory <= 256
大于等于 min_memory >= 512
不等于 min_memory != 512
变量存在 min_memory is defined
变量不存在 min_memory is not defined
布尔变量值是1、True或yes的求值为 memory_available
布尔变量值是0、False或no的求值为 memory_available
memory_available变量值为真,最终结果为 not memory_available
第一个变量的值存在,作为第二个变量的列表中的值 ansible_distribution in supported_distros
布尔值变量判断

示例:

yaml 复制代码
---
- name: test 
  hosts: node1
  gather_facts: no
  vars:
    run_my_task: true
  tasks:
    - name: test when
      debug:
        msg: "Hello run my task"
      when: run_my_task

示例中的when语句导致:任务仅在run_my_task为 true时运行。

变量是否定义判断

变量判断:

  • defined == not undefined 变量定义返回真
  • undefined == not defined 变量未定义返回真
  • none 变量定义了,但是值是空值,返回真

示例1:

yaml 复制代码
---
- hosts: node1
  gather_facts: no
  vars:
    username: laoma
  tasks:
    - debug:
        msg: "var: username is defined"
      when: username is defined

示例2:判断受管主机是否具有相应设备。

yaml 复制代码
---
- name: create and use lv
  hosts: node1
  tasks:
    - name: Create a logical volume of 4000m
      lvol:
        vg: research
        lv: data
        size: 4000
      when: ansible_lvm.vgs.research is defined
    - debug:
        msg: Volume group does not exist
      when: ansible_lvm.vgs.research is not defined
文件属性判断
  • file:如果路径是一个普通文件,返回真
  • directory:如果路径是一个目录,返回真
  • link:如果路径是一个软连接,返回真
  • mount:如果路径是一个挂载点,返回真
  • exist:如果路径是存在,返回真

示例:

yaml 复制代码
---
- hosts: node1
  gather_facts: no
  vars:
    file_name: /etc/hosts
  tasks:
    - debug:
        msg: "{{ file_name }} is regular file"
      when: file_name is file
任务执行结果判断
  • succeeded,通过任务的返回信息判断,任务执行成功返回真。
  • failed,通过任务的返回信息判断,任务执行失败返回真。
  • changed,通过任务的返回信息判断,任务执行状态为changed返回真。
  • skipped,通过任务的返回信息判断,任务没有满足条件跳过执行,返回真。

示例:

yaml 复制代码
---
- hosts: node1
  gather_facts: no
  vars:
    doshell: "yes"
  tasks:
    - shell: cat /etc/hosts
      register: result
      ignore_errors: true
      when: doshell == "yes"
    - name: success
      debug:
        msg: success
      when: result is succeeded
    - name: failed
      debug:
        msg: failed
      when: result is failed
    - name: changed
      debug:
        msg: changed
      when: result is changed
    - name: skipped
      debug:
        msg: skip
      when: result is skipped

其他测试:

  • 设置doshell: "no"
  • 设置shell: cat /etc/hosts-no-exist
in 和 not in 判断

示例1:给用户添加组

yaml 复制代码
---
- name: test 
  hosts: node1
  gather_facts: no
  vars:
    username: devops
    supergroup: wheel
  tasks:
    - name: gather user information
      shell: id {{ username }}
      register: result
    - name: Task run if user is in supergroups
      user:
        name: "{{ username }}"
        groups: "{{ supergroup }}"
        append: yes
      when:  supergroup not in result.stdout

示例2:给用户添加多个组

yaml 复制代码
---
- name: test 
  hosts: node1
  gather_facts: no
  vars:
    username: devops
    supergroups: 
      - wheel
      - root
  tasks:
    - name: gather user information
      shell: id {{ username }}
      register: result
    - name: Task run username is in supergroups
      user:
        name: "{{ username }}"
        groups: "{{ item }}"
        append: yes
      when:  item not in result.stdout
      loop: "{{ supergroups }}"

Ansible Handlers

Ansible Handlers 功能

Ansible的模块的设计是可以多次执行的,当被管理节点是预期状态时,是不会做任何更改的。然而,有时候执行了一个任务,还需要进一步执行下一个任务。

例如,更改了服务配置文件之后,需要重新加载配置文件才能生效。Handlers是由其他任务通知执行的任务,可看做inactive任务,通过notify调用。

示例:

复制代码
---
- name: deploy web server
  hosts: node1
  tasks:
    - name: install packages
      yum:
        name: httpd
        state: present
      notify:
        - enable and restart apache

    - name: install httpd-manual
      yum:
        name: httpd-manual
        state: present
      notify:
        - enable and restart apache
    
    - debug: 
        msg: last task in tasks

  handlers:
    - name: enable and restart apache
      service:
        name: httpd
        state: restarted
        enabled: yes

在 Ansible 中,Handlers(处理器) 就像一个 "待命的助手",专门用来处理那些 "只有在系统状态发生变化时才需要执行" 的操作。它有点像 "触发器"------ 只有当某个任务真正改变了系统状态(比如修改了配置文件),Handlers 才会被触发执行。

为什么需要 Handlers?

举个例子:当你修改了 Nginx 的配置文件(nginx.conf),只有在配置文件真的被改动时,才需要重启 Nginx 服务。如果配置文件没变化,重启操作就是多余的。

Handlers 正是为这种场景设计的:它能 "监听" 任务是否导致了系统状态变更(通过changed: true标识),只有变更发生时才执行指定操作,避免无效重复执行。

基本用法:定义和触发 Handlers

Handlers 的使用分为两步:定义 Handlers在任务中通知(notify)Handlers

yaml 复制代码
- name: 配置Nginx并按需重启
  hosts: webservers
  tasks:
    - name: 修改Nginx配置文件
      copy:
        src: ./nginx.conf
        dest: /etc/nginx/nginx.conf
      notify:  # 当这个任务导致状态变更时,通知Handlers
        - 重启Nginx服务  # 这里的名称要和Handlers中定义的一致

  handlers:  # 定义Handlers(放在Play的handlers块中)
    - name: 重启Nginx服务  # 名称要和notify中的完全匹配
      service:
        name: nginx
        state: restarted
执行逻辑:
  1. 执行 "修改 Nginx 配置文件" 任务:
    • 如果配置文件有变化(changed: true),则标记 "重启 Nginx 服务" 这个 Handler 为 "待执行"
    • 如果配置文件无变化(changed: false),则不通知 Handler
  2. 当 Play 中所有任务执行完毕后,Ansible 会统一执行所有被标记为 "待执行" 的 Handlers。

Handlers 的特点

  1. 延迟执行:Handlers 不会在被通知后立即执行,而是等到当前 Play 中所有任务都执行完才统一运行。

  2. 幂等性保障:即使多个任务通知同一个 Handler,它也只会执行一次。例如:

    yaml 复制代码
    tasks:
      - name: 修改配置文件A
        copy: src=a.conf dest=/etc/nginx/
        notify: 重启Nginx服务
    
      - name: 修改配置文件B
        copy: src=b.conf dest=/etc/nginx/
        notify: 重启Nginx服务

    无论两个任务是否都触发了变更,"重启 Nginx 服务" 只会执行一次,避免多次重启。

  3. 名称唯一 :Handlers 通过name标识,notify必须严格匹配名称(包括大小写),否则无法触发。

常见使用场景

Handlers 最适合处理 "配置变更后需要生效的操作",例如:

  • 服务重启(Nginx、MySQL、Apache 等)
  • 重新加载配置(systemctl reload
  • 重建缓存(如systemctl daemon-reload
  • 重新编译(当源码或 Makefile 被修改时)

简单说,Handlers 就像 "按需执行的收尾工作"------ 平时待命,只有当系统真的发生了需要它处理的变化时,才会出手完成必要的后续操作,让 Playbook 更高效、更符合实际运维逻辑。

处理 Errors

Errors 介绍

Ansible评估各任务的返回代码,从而确定任务是成功还是失败。通常而言, 当某个主机执行任务失败时,Ansible将立即终止该主机继续执行play,其他主机可以继续执行play。

bash 复制代码
---
- name: test
  hosts: node1,node2
  tasks:
    - name: show /etc/myhosts
      shell: cat /etc/myhosts
    - name: echo end
      debug:
        msg: echo end
ignore_errors

您可能希望即使在任务失败时也继续执行play。例如,您或许预期特定任务有可能会失败,并且希望通过有条件地运行某项其他任务来恢复。

ignore_errors可以定义在以下位置:

  • 定义在 play 中,则play中所有任务忽略错误。
  • 定义在 task 中,则特定task忽略错误。

示例:

yaml 复制代码
---
- name: test
  hosts: node1
  tasks:
    - name: install a not exist package
      yum:
        name: notexitpackage
        state: present
      ignore_errors: yes
      register: result
    - name: debug install result
      debug:
        msg: notexitpackage is not exit
      when: result is failed
fail 模块

fail 模块,执行该任务,任务必定 failed。

示例:

yaml 复制代码
- name: test fail module
  hosts: node1
  gather_facts: no
  tasks:
    - debug:
        msg: task1
    - fail:
    - debug:
        msg: task3

提示:fail模块本身也可以配置when判断,实现说明情况下任务是失败的。

failed_when

指明什么条件下,判定任务执行失败。

示例:

yaml 复制代码
- name: test failed_when
  hosts: node1
  tasks:
    - shell: /root/adduser
      register: command_result
      failed_when: "'failed' in command_result.stdout"

环境准备:

bash 复制代码
[root@node1 ~]# cat /root/adduser 
#!/bin/bash
useradd devops &> /dev/null
if [ $? -eq 0 ];then
  echo add user devops success
else
  echo add user devops failed
fi
[root@node1 ~]# chmod +x /root/adduser 

以上示例:

  • 当devops用户不存在时,shell模块跳过执行。
  • 当devops用户存在时,shell模块执行失败。

以上示例可改写为fail模块和when语句联合使用:

yaml 复制代码
- name: test fail module
  hosts: node1
  tasks:
    - shell: /root/adduser
      register: command_result

    - fail:
        msg: "add user devops failed"
      when: "'failed' in command_result.stdout"
changed_when

指明什么条件下,判定任务执行结果为changed。

示例1:

yaml 复制代码
- name: changed_when
  hosts: node1
  tasks:
    - name: upgrade-database
      shell: /usr/local/bin/upgrade-database
      register: result
      changed_when: "'Success' in result.stdout"
      notify:
        - restart_database
  handlers:
    - name: restart_database
      service:
        name: mariadb
        state: restarted