编程 · 24 2 月, 2025 0

Ansible Playbook速成实战:继而实现一些大胆的想法

本文内容主要是为了实现《从一个简单例子开始》中老板那些大胆的想法而写的。当然作为Ansible Playbook的作成参考也不是不行。

本文持续更新中。

Ansible基本要素

Play

Playbook 的基本组成单元。一个 Playbook 可以包含一个或多个 Play,每个 Play 定义了在哪些主机上执行哪些任务,以及如何执行这些任务。不同的 Play 之间通过---分隔。如:

---
- name: Play01
  hosts: xxx
  # ...

---
- name: Play02
  hosts: xxx
  # ...

Play是Playbook的顶层结构

以下内容只能在 Play 中定义:

  • hosts
  • gather_facts
  • roles
  • pre_tasks 和 post_tasks
  • max_fail_percentage
  • serial
  • any_errors_fatal
  • strategy

以下内容可以在 Play 或 Task 中定义,但 Play 级别的定义会影响整个 Play:

  • become 和 become_*
  • vars
  • handlers

Inventory

动态inventory

如果需要从外部系统(如云平台)动态获取主机信息,可以使用脚本生成JSON格式的inventory。比如,例子中的inventory需要脚本能够输出(打印)以下内容:

{
  "my_servers": {
    "hosts": [
      "SRV01",
      "SRV02",
      "SRV03"
    ],
    "vars": {
      "ansible_user": "my_work",
      "ansible_become": "yes"
    }
  },
  "_meta": {
    "hostvars": {
      "SRV01": {
        "ansible_host": "192.168.0.1",
        "ansible_become_password": "你的sudo密码"
      },
      "SRV02": {
        "ansible_host": "192.168.0.2",
        "ansible_become_password": "你的sudo密码"
      },
      "SRV03": {
        "ansible_host": "192.168.0.3",
        "ansible_become_password": "你的sudo密码"
      }
    }
  }
}

逻辑结构

循环

列表遍历

使用item变量和with_items模块:

- name: 执行脚本
  command: "{{ item }}"
  with_items:
    - "hostname"
    - "uname -a"
    - "date"
  register: service_status

- name: 打印结果
  # 输出结果也需要遍历哦
  debug:
    var: "{{ item.stdout }}"
  with_items: "{{ service_status.results }}"

变量

变量的作用范围

  • vars的作用域取决于定义的位置,可以是 Play、Task、Role 或全局
  • register的作用域通常是当前 Play 或 Block
  • 通过set_fact可以将register的结果保存为全局变量,跨 Play 使用

使用例:

---
- name: 综合示例
  hosts: all
  vars:
    play_var: "Play 变量"
  tasks:
    - name: 定义 Task 变量
      debug:
        msg: "{{ task_var }}"
      vars:
        task_var: "Task 变量"

    - name: 获取当前用户
      command: whoami
      register: whoami_result

    - name: 打印注册变量
      debug:
        msg: "{{ whoami_result.stdout }}"

    - name: 保存到全局变量
      set_fact:
        global_whoami: "{{ whoami_result.stdout }}"

---
- name: 使用全局变量
  hosts: all
  tasks:
    - name: 打印全局变量
      debug:
        msg: "{{ global_whoami }}"

Ansible内置变量

可以在playbook中直接使用的变量。这里列举一些常用的:

Inventory变量(参数)

变量名 用途
ansible_host 目标主机的 IP 地址或域名
ansible_user 连接到目标主机的用户名
ansible_port SSH 端口(默认是 22)
ansible_ssh_private_key_file SSH 私钥文件的路径
ansible_connection 连接类型(例如 ssh、local、docker、winrm)
ansible_become 是否启用权限提升(例如 sudo)
ansible_become_user 权限提升时切换到的用户(例如 root)
ansible_become_method 权限提升的方式(例如 sudo、su)
ansible_become_password 权限提升时使用的密码

任务中使用的变量

变量名 用途
inventory_hostname 当前主机的host名称
play_hosts 当前playbook中所有主机的列表
ansible_user_dir (远程)当前用户的主目录
ansible_user_id (远程)当前用户的用户名

Ansible内置常用模块

执行命令行

除了例子中使用的command,以下方式也可以在远程主机执行命令行。

1. Shell

- name: 在一个任务中执行多条命令
  hosts: all
  tasks:
    - name: 运行多条命令
      shell: |
        whoami &&
        date &&
        ls -l /tmp
      register: multi_command_result

    - name: 打印多条命令的结果
      debug:
        var: multi_command_result.stdout

其中:

  • “&&”表示前一条命令成功后才执行下一条命令
  • “;”表示无论前一条命令是否成功,都执行下一条命令

2. Raw

用于在不依赖 Python 的环境中执行命令(例如未安装 Python 的主机)。

- name: 使用 raw 模块执行多条命令
  tasks:
    - name: 运行多条命令
      raw: |
        whoami
        date
        ls -l /tmp
      register: raw_command_result

    - name: 打印 raw 命令的结果
      debug:
        var: raw_command_result.stdout

3. Script

可以使用script在远程主机直接执行shell脚本文件。

- name: 执行本地脚本
  tasks:
    - name: 上传并运行脚本
      script: /path/to/local/script.sh
      register: script_result

    - name: 打印脚本执行结果
      debug:
        var: script_result.stdout

4. Block

如果你希望将多个命令作为独立任务执行,可以使用block将它们组织在一起。

- name: 使用 block 组织多个任务
  tasks:
    - block:
        - name: 运行 whoami
          command: whoami
          register: whoami_result

        - name: 运行 date
          command: date
          register: date_result

        - name: 运行 ls
          command: ls -l /tmp
          register: ls_result
      rescue:
      # 处理任务失败的情况
        - name: 处理错误
          debug:
            msg: "某个任务失败"
      always:
      # 无论任务是否成功都会执行
        - name: 打印所有结果
          debug:
            msg: |
              whoami: {{ whoami_result.stdout }}
              date: {{ date_result.stdout }}
              ls: {{ ls_result.stdout }}

文件操作

写文件

可以使用copy模块写文件。

- name: 写入文件
  copy:
    content: |
      这是第一行
      这是第二行
    # 使用“src: <path>”则可以实现文件复制
    dest: /path/to/remote/file.txt

读取文件

可以使用lookup插件读取文件文本。

- name: 读取文件
  debug:
    msg: "{{ lookup('file', '/path/to/file.txt') }}"

删除文件/修改权限

使用file模块可以删除文件,或修改文件权限。

- name: 删除文件
  file:
    path: /path/to/remote/file.txt
    state: absent

- name: 设置文件权限
  file:
    path: /path/to/remote/file.txt
    mode: '0644'

判断文件是否存在

使用stat模块可以判断文件是否存在。

- name: 获取文件状态
  stat:
    path: /path/to/remote/file.txt
  register: file_stat

- name: 打印文件是否存在
  debug:
    msg: "文件存在"
  when: file_stat.stat.exists

- name: 打印文件不存在
  debug:
    msg: "文件不存在"
  when: not file_stat.stat.exists

条件检查

Assert

assert模块用于在 Playbook 中执行断言检查。如果断言条件不满足,任务会失败并停止 Playbook 的执行。

注意:条件表达式不要使用{{ }}表示变量,变量名直接写就行。如果字符串与变量重名,则对字符串用" "

- name: 获取Hostname
  command: "hostname"
  register: host_current

- name: 检查Hostname
  assert:
    that:
      - host_current.stdout == inventory_hostname
      # 可以设置多个条件
    fail_msg: "Host名有问题,终止执行"
    success_msg: "Host名正确,继续执行"
  # 此处还可以加入assert发动的条件:
  # when: xxx
其他条件?

例子中只比较了字符串是否一致。这里再整理一些常用的。

条件 语句
数值比较 my_var > 0
文件存在 file_stat.stat.exists
file_stat详见“判断文件是否存在”章节
逻辑或 条件1 or 条件2
字符串包含 s in text
正则表达式(※) host_current.stdout is match(“^Linux %s .+$” % inventory_hostname)

※ 这里使用了is match,即完全匹配。也可以使用is search进行部分匹配。

Jinja2

Jinja2 是一个现代的、设计优雅的模板引擎,广泛用于 Python 生态系统中。它是 Ansible 中默认的模板引擎,用于动态生成文本内容(如配置文件、脚本、命令等)。Jinja2 提供了强大的功能,包括变量替换、条件判断、循环、过滤器等。

模板渲染

如果要使用模板,先要制作模板,并保存在pj/templates/xxx.j2

CREATE DATE: {{ get_date }}
UNAME: {{ get_uname }}

在playbook里这么写:

- name: 渲染模板
  vars:
    # 变量名与模板文件中的一致
    get_date: "{{ service_status.results.2.stdout }}"
    get_uname: "{{ service_status.results.1.stdout }}"
  template:
    src: templates/xxx.j2
    dest: "/home/manager/xxx_filled.txt"
  delegate_to: localhost
  # 如果不写delegate_to这行,默认将在远程设备的dest存储文件

于是你将在本地 文件/home/manager/xxx_filled.txt看到以下内容:

CREATE DATE: 2025年 02月 25日 星期二 12:55:58 UTC
UNAME: Linux SRV01 3.10.0-1160.59.1.el7.x86_64 #1 SMP Wed Feb 23 16:47:03 UTC 2022 x86_64 x86_64 x86_64 GNU/Linux

条件判断

Jinja2 支持if条件语句,可以根据条件生成不同的内容。

- name: 打印天气
  debug:
    msg: >
      {% if is_raining %}
        今天下雨了,记得带伞!
      {% else %}
        今天天气晴朗,出门享受阳光吧!
      {% endif %}

is_raining在前文定义为真,此时将输出“今天下雨了,记得带伞!”。