一、实验环境

沿用前两天的环境

使用 template 模板机 克隆下方 4 台虚拟机,配置好 IP 地址后,通过 WindTerm 远程连接虚拟机,作为 Ansible 控制节点与被管理节点。

请添加图片描述


二、Ansible 模块进阶

2.1 setup 模块

  • setup 模块 用于采集被管理主机的系统事实信息,这些信息被称为 Facts
  • ansible_facts 变量用于存储采集到的系统信息,供后续任务调用。
  • 每次执行 Playbook 时,默认第一个任务就是 Gathering Facts(收集事实信息)。
    请添加图片描述
  • 使用 setup 模块可以手动查看或过滤这些 Facts 信息

常用命令示例

# 查看所有 Facts 信息
ansible test -m setup
192.168.8.11 | SUCCESS => {
    "ansible_facts": {
        "ansible_all_ipv4_addresses": [
            "192.168.1.11"
… 省略部分内容…
  • 找出下列facts信息(有父子关系时使用.分隔)
    • ansible_bios_version(版本号)
    • ansible_memtotal_mb(内存)
    • ansible_hostname(主机名)
# 过滤指定 Facts(使用 filter 参数)
ansible web1 -m setup -a "filter=ansible_bios_version" #过滤版本号
ansible web1 -m setup -a "filter=ansible_memtotal_mb"  #过滤内存
ansible web1 -m setup -a "filter=ansible_hostname"     #过滤主机名

常用 Facts 变量(有父子关系时使用 . 分隔调用):

  • ansible_bios_version
  • ansible_memtotal_mb
  • ansible_hostname
  • ansible_all_ipv4_addresses
  • ansible_ens160.ipv4.address

2.2 debug 模块

debug 模块 可以显示变量的值。debug取的值源自于与setup模块获取的Facts。主要用于辅助排错和验证变量内容。

常用参数

  • msg:要输出的信息(引用变量时需要使用 {{ }}

示例 Playbook

---
- hosts: web1
  tasks:
    - debug:
        msg: "主机名是:{{ ansible_hostname }}"

    - debug:
        msg: "总内存大小:{{ ansible_memtotal_mb }}"

补充:var模块也可以直接调用变量且不需要加双花括号,但是不能和字符串一起使用,只能单独调用字符串


三、Ansible 变量

Ansible 支持十几种定义变量的方式,本节介绍其中最常用的四种,并按优先级从高到低排序:

  • Playbook 变量(在 Play 中使用 vars: 定义, Play 级别的变量,单个play生效)
  • Inventory 变量(主机变量和组变量)
  • 变量文件(通过 vars_files: 加载的外部变量文件)
  • Host Facts 变量(由 setup 模块收集的系统信息)

补充:详细的优先级顺序(了解):

1. 命令行 -e 变量(最高)
2. Playbook 中 vars: 定义的变量
3. vars_files: 加载的变量文件
4. role 中 vars/ 目录下的变量
5. Host Facts(setup 模块采集的变量)
6. Inventory 主机变量(host_vars/)
7. Inventory 组变量(group_vars/)
8. Playbook 中 include_vars 导入的变量
9. role 中 defaults/ 目录下的变量(最低)

3.1 Inventory 变量

Inventory 变量是在主机清单配置文件(hosts)中为特定主机(组)定义的变量。

hosts 文件示例

[webserver]
web[1:2]

[db]
db1 myvar1="hello the world"    myvar2="content"       #为db1生成两个变量
#写在哪个主机后边,这个变量就属于谁

[cluster:children]                       #children代表下面是组成员
webserver
db

[webserver:vars]                        # 为webserver组定义变量,改组成员均可调用。vars 是固定关键字,代表下面的是变量
yourname="tina"

编写调用 Inventory 变量的 Playbook,执行playbook验证:

---
- hosts: db1
  tasks:
    - name: use inventory vars
      shell: echo {{ myvar1 }} > /tmp/{{ myvar2 }}  # 这里 {{}} 不在开头,所以不需要加双引号

- hosts: webserver
  tasks:
    - name: use inventory vars yourname
      user:
        name: "{{ yourname }}"          # {{}} 开头时需要加双引号

3.2 Host Facts 变量

Host Facts 变量可以直接调用 setup 模块收集到的系统信息。

示例 Playbook

---
- hosts: web1
  tasks:
    - name: Host Facts 变量应用
      copy:
        content: "{{ ansible_hostname }}:{{ ansible_bios_version }}"
        dest: /tmp/facts.txt

3.3 Playbook 变量

在 Playbook 中使用 vars 关键字定义变量。
注意:仅在当前 play 内有效

示例 Playbook

---
- hosts: web1
  vars:
    iname: heal
    ipass: '123456'                     # 密码必须是字符串,需要加引号
  tasks:
    - name: Use variables create user.
      user:
        name: "{{ iname }}"
        password: "{{ ipass | password_hash('sha512') }}"

3.4 变量文件

将变量单独定义在一个 YAML 文件中,在 Playbook 中通过 vars_files 调用。

变量文件 variables.yml
注意格式为键值对

---
iname: cloud
ipass: '123456'

调用变量文件的 Playbook

---
- hosts: web1
  vars_files: variables.yml  #调用YAML变量文件
  tasks:
    - name: create user.
      user:
        name: "{{ iname }}"                                  # iname 来自变量文件
        password: "{{ ipass | password_hash('sha512') }}"    # ipass 来自变量文件

四、Ansible 进阶

4.1 template 模块

  • template 模块作用类似于copy,都可以拷贝文件。

  • copy 模块只能复制静态文件

  • template 模块主要用于生成动态文件,并且可以调用文件中的变量
    如果希望每个主机拷贝的文件内容不一样(例如包含各自的 IP 或主机名),就需要使用 template 模块。

  • template 模块结合 Jinja2 模板引擎,可以实现动态内容生成。

    • Jinja2 表达式语法: {{ 变量 }}
    • 之前在 Playbook 中调用变量,实际上也是 Jinja2 的功能。
    • template 模块的主要作用是生成动态配置文件

示例 1:为 webserver 组中的主机生成不同内容的首页

模板文件 index.html

Welcome to {{ ansible_hostname }} on {{ ansible_ens160.ipv4.address }}.
# 模板文件中调用变量不需要双引号

Playbook:

---
- hosts: webserver
  tasks:
    - name: use template copy index.html to webserver.
      template:
        src: ~/ansible/template/index.html
        dest: /usr/local/nginx/html/index.html

示例 2:使用自定义变量

  • template 使用 .j2 后缀模板文件
    • 例如:nginx.conf.j2
    • 里面可以写 {{ 变量名 }}

模板文件 source.j2

{{ welcome }} {{ iname }} ...

Playbook:

---
- hosts: webserver
  vars:
    welcome: 'hello'      #为变量赋值  
    iname: 'jack'         #为变量赋值  
  tasks:
    - name: 使用template模块将文件复制到远程主机
      template:
        src: ~/ansible/template/source.j2    #调用文件,调用文件中的变量
        dest: /tmp/

4.2 Ansible 高级语法(重要)

4.2.1 error 处理机制

默认情况下,Ansible 遇到错误会立即停止 Playbook 的执行。

忽略单个任务的错误

---
- hosts: web1
  tasks:
    - name: start a service that does not exist.
      service:
        name: hehe                              
        state: started
      ignore_errors: true                        # 与模块对齐,则仅针对当前模块任务,忽略错误

    - name: touch a file.
      file:
        path: /tmp/service.txt
        state: touch

忽略整个play的错误

---
- hosts: web1
  ignore_errors: true                            # 当前 Play 内忽略错误
  tasks:
    - name: start a service that does not exist.
      service:
        name: hehe
        state: started

    - name: touch a file.
      file:
        path: /tmp/service.txt
        state: touch

4.2.2 handlers

handlers是一种特殊的任务,只在被其他任务通过 notify 通知后(任务处于changed状态时),才在 Playbook 结束时执行一次,通常用于触发服务的重启或重载等收尾操作。

注意:即使被多次 notify,也只执行一次。

handlers 的特点

  • 仅当任务触发(notify)handlers 时才执行(不触发不执行)。
  • 多个任务 notify 同一个 handler 时,只会执行一次。
  • 仅当任务的执行状态为 changed 时,handlers 才会执行
  • handlers 任务在当前play内的所有其他 tasks 执行完毕后才执行

注意:一个play只能有一个handlers(但一个剧本可以有多个)

示例

---
- hosts: web1
  tasks:
    - name: 创建一个目录          #多次执行该任务,playbook状态将不再是changed
      file:
        path: /tmp/parents/subdir/
        state: directory
      notify: touch file          # notify 后面的名称必须与 handlers 中的任务名称一致
      #notify和模块处于同一层级
  
  handlers:       #handlers和tasks属于同一层级
    - name: touch file
      file:
        path: /tmp/parents/subdir/new.txt
        state: touch

注意:notify 后面的名称必须与 handlers 中的任务名称一致

细节补充:即使使用了ignore_errors: true,也不会影响handlers的触发条件

4.2.3 when 条件判断

使用 when 可以为任务添加执行条件,只有条件为真时才执行该任务。

常用比较操作符==!=>>=<<=

多个条件可以使用 andor 连接。

注意when 表达式中引用变量时不需要使用 {{ }}

示例 1:内存不足 700MB 时停止 NetworkManager 服务

---
- hosts: web1
  tasks:
    - name: 检查内存,内存不足 700MB 时停止 NetworkManager 服务
      service:
        name: NetworkManager
        state: stopped
      when: ansible_memfree_mb < 700

示例 2:判断操作系统为 Rocky 8 时创建文件(多行条件)

---
- hosts: web1
  tasks:
    - name: 判断操作系统为 Rocky 8 时创建文件
      file:
        path: /tmp/when.txt
        state: touch
      when: >
        ansible_distribution == "Rocky" #注意:字符串要加双引号
        and
        ansible_distribution_major_version == "8"

4.2.4 block 任务块

使用 block 可以将多个任务组合成一个组,方便统一管理。

block 还支持 rescuealways 子句:

  • rescue:block 中任务执行失败时执行的补救任务。
  • always:无论 block 是否成功,都会执行的任务。

基础 block 示例

---
- hosts: db1
  tasks:
    - name: 定义一组任务
      block:   
        - name: 安装 httpd
          yum:
            name: httpd
            state: present
        - name: start httpd
          service:
            name: httpd
            state: started
      when: ansible_distribution == "Rocky" #满足此条件时,执行block任务块

rescue + always 示例

---
- hosts: web1
  tasks:
    - block:
        - name: 任务块创建 test1.txt
          file:
            path: /tmp/test1.txt
            state: touch
      rescue:
        - name: 任务块执行失败会创建 test2.txt
          file:
            path: /tmp/test2.txt
            state: touch
      always:
        - name: 任何情况下都会创建 test3.txt
          file:
            path: /tmp/test3.txt
            state: touch

直接运行剧本,会有test1.txt和test3.txt
将block改成: path: /tmp/abc/test1.txt,会有test2.txt和test3.txt

为什么当block报错整个任务不会停止?因为整个 block: + rescue: + always: 算 Ansible 的「一个任务单元」,单个任务失败不会让剧本停止,且rescue 就是专门用来捕获 block 里的失败,不让任务中断


4.2.5 loop 循环

当多个任务使用相同模块时,可以使用 loop 循环来避免重复编写。

简单列表循环

---
- hosts: test
  tasks:
    - name: 借助loop循环依次创建school,legend,life目录
      file:
        path: /tmp/{{ item }}                # item 是循环中的固定关键字
        state: directory
      loop:                                  #loop中的所有元素会逐个丢给item
        - School
        - Legend
        - Life

复杂字典列表循环

---
- hosts: web1
  tasks:
    - name: 使用loop循环完成用户的批量创建,并设置密码
      user:
        name: "{{ item.iname }}"  #注意,这里'tiem'属于固定格式
        password: "{{ item.ipass | password_hash('sha512') }}"
      loop:                                  # 循环成员为字典
        - { iname: 'term', ipass: '123456' }
        - { iname: 'amy', ipass: '654321' }

这里item是固定格式,必须通过 item.元素 来调用定义的字典元素


五、Ansible Roles

5.1 Roles 概述

在实际生产环境中,随着功能增多,Playbook 文件会越来越多,且可能互相调用变量文件等,管理起来非常困难。

Ansible 从 1.2 版本开始支持 Roles
Roles 是管理ansible文件的一种规范(目录结构),用于更好地组织和管理 Playbook、变量、模板、handlers 等文件。

ansible的角色(role)像是做软件开发一样。开发是做一堆配置文件,功能文件,然后再main文件中进行集成;role就像是把配置文件,资源文件放到对应目录,然后在剧本中集成/调用

5.2 Roles 规范的目录结构

目录 / 文件 作用说明
defaults/main.yml 定义变量的默认值(优先级较低)
files/ 存放静态文件
handlers/main.yml 定义 handlers
meta/main.yml 角色元数据(作者、版本、依赖等)
README.md 整个角色的说明文档
tasks/main.yml 定义任务的核心文件
templates/ 存放 Jinja2 动态模板文件
vars/main.yml 定义变量(优先级较高)

不同模块会自动到对应目录下查找数据。

标准的目录结构:

role_name/
├── defaults/      # 默认变量(优先级最低)
│   └── main.yml
├── vars/          # 角色变量(优先级高于 defaults)
│   └── main.yml
├── tasks/         # 主要任务列表(必须有)
│   └── main.yml
├── handlers/      # 处理器,用于触发服务重启等
│   └── main.yml
├── templates/     # Jinja2 模板文件(.j2)
│   └── *.j2
├── files/         # 静态文件,直接拷贝
│   └── *
├── meta/          # 角色元数据(依赖、作者、版本等)
│   └── main.yml
└── README.md      # 角色说明文档(可选)

5.3 Roles 应用

5.3.1 创建 Role

使用 ansible-galaxy init <rolename> 命令可以创建、管理自己的roles

mkdir ~/ansible/roles
ansible-galaxy init ~/ansible/roles/issue
tree ~/ansible/roles/issue/

会发现生成了这样结构的目录:

请添加图片描述

5.3.2 修改 Role
  • 定义issue文件的模板文件

模板文件 templates/issue.j2

[root@control ansible]# vim   ~/ansible/roles/issue/templates/issue.j2
This is the system {{ ansible_hostname }}
Today's date is: {{ ansible_date_time.date }}
Contact to {{ admin }}

定义变量文件 vars/main.yml

[root@control ansible]# vim ~/ansible/roles/issue/vars/main.yml
---
admin: yoyo@test.com

任务文件 tasks/main.yml(Role 中的任务文件不需要写 tasks: 关键词):
Role的各个文件之间相互调用不需要写路径

[root@control ansible]#vim ~/ansible/roles/issue/tasks/main.yml

---
- name: delever issue file
  template:
    src: issue.j2
    dest: /etc/issue
5.3.3 在 Playbook 中调用 Role

方法一:在role同级目录创建 Playbook 直接调用。

方法二:在 ansible.cfg 中指定 roles_path=路径

[root@control ansible]# vim  ~/ansible/ansible.cfg 
[defaults]
remote_user = alice
inventory = ~/ansible/hosts
roles_path = ~/ansible/roles        #指定Roles读取位置
[privilege_escalation]
become=True
become_method=sudo
become_user=root
become_ask_pass=False

编写playbook文件,通过roles关键词调用role

[root@control ansible]# cat  ~/ansible/issue.yml
---
- hosts: web2
  roles:                 #调用Role
    - issue
#   - role2              #支持加载多个Role
[root@control ansible]# ansible-playbook  issue.yml

调用 Role 的 Playbook

---
- hosts: web2
  roles:
    - issue
    # - role2          # 支持同时加载多个 Role

六、总结

本节重点掌握内容:

  • 特殊模块setupdebugtemplate
  • 变量管理:Inventory 变量、Host Facts 变量、Playbook 变量、变量文件
  • 高级语法:error 处理机制(ignore_errors)、handlers、when 条件判断、block 任务块、loop 循环
  • Roles:角色作用、标准目录结构、角色创建与调用

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐