一、循环
Ansible 提供了多种循环机制来简化重复性任务的操作。循环可以让你对一组数据项执行相同的任务,而不需要为每个项单独编写任务。
例如,拷贝文件和创建目录等任务。当需要使用PlayBook
多次创建文件或目录时,我们需要重复的书写。如下:
- name: 创建多个文件(不使用循环)
hosts: localhost
tasks:
- name: 创建文件1
file:
path: /tmp/file1.txt
state: touch
- name: 创建文件2
file:
path: /tmp/file2.txt
state: touch
- name: 创建文件3
file:
path: /tmp/file3.txt
state: touch
使用循环的方式书写,整体的框架只需要写一次,如下:
- name: 创建多个文件
hosts: localhost
tasks:
- name: 创建文件1,2,3
file:
path: "{{ item }}"
state: touch
loop:
- /tmpt/file1.txt
- /tmp/file2.txt
- /tmp/file3.txt
具体格式解析:
- name: 任务描述
module_name:
参数1: "{{ item }}" # 循环变量
参数2: 值
loop:
- 值1
- 值2
- 值3
所以,循环是帮助我们节省重复书写的内容,实现方式接近于变量。需要变动的地方就用变量代替,不变的地方就写死。
二、多循环
很多时候一个任务中,我们有许多需要变动的值,而这个任务又需要重复的执行多次。这个时候用单循环无法解决我们的需求,就需要使用多循环了。
例如,需要创建多个文件,且每个文件的权限不一致,我们就可以使用多循环的方式,将path
和mode
的值用循环变量代替。如下:
- name: create
file:
path: "{{ item.path }}"
state: touch
mode: "{{ item.mode }}"
loop:
- { path: '/opt/file1.txt', mode: '0644' }
- { path: '/opt/file2.txt', mode: '0600' }
有一个场景如果需要多次执行并简化playbook
内容必须要用到多循环:COPY
三、变量
变量是存储和引用数据的重要方式,它们使得 Playbook 更加灵活和可重用。
当某些值可能会被经常用到时,就可以将他定义成一个变量。自定义
ansible 内置了很多变量,可以直接拿来用
1. 自定义变量
当某些值可能会被经常用到时,就可以将其定义成一个变量。
假如,我们在复制文件时,有巨多的文件都存储在/opt/xxx/xxx/xxx/xxx
下,此时就可以将这个路径存储为一个变量,后续使用时直接调用这个变量即可。
另外,若我们需要大量修改这个目录的路径时,只需要修改变量的值即可,不需要再修改每个内容的具体值,既方便也不容易出错。
注意,写在playbook
中的变量属于局部变量,无法被其他playbook
调用。
示例:
---
- name: 使用变量管理文件路径示例
hosts: all
vars:
# 定义基础路径变量
source_dir: "/root/apps/config_files/production"
destination_dir: "/etc/application/config"
# 可以定义子路径变量
log_config_path: "{{ source_dir }}/logging"
db_config_path: "{{ source_dir }}/database"
tasks:
- name: 创建目录
file:
path: "{{ destination_dir }}"
state: directory
recurse: yes
mode: '0755'
- name: 复制日志配置文件
copy:
src: "{{ log_config_path }}/log4j.properties"
dest: "{{ destination_dir }}/log4j.properties"
remote_src: no # 如果源文件在控制节点上
- name: 复制数据库配置文件
copy:
src: "{{ db_config_path }}/db.conf"
dest: "{{ destination_dir }}/db.conf"
- name: 复制所有配置文件(使用通配符)
copy:
src: "{{ source_dir }}/*.conf"
dest: "{{ destination_dir }}/"
2. 主机清单存储变量
当我们需要变量跨playbook
调用时,可以将变量定义在主机清单文件中,但此方法的前提是调用该主机或主机组。
[webservers]
web1.example.com source_dir="/root/apps/config_files/production" # 给单个主机添加变量
[webservers:vars]
ntp_server=ntp.example.com # 给整个组webservers添加变量
proxy=proxy.example.com
timezone=UTC
通过这个方法定义好的变量,可以再playbook中直接应用,方法还是{{ 变量名 }}的形式
3. 内置变量
Ansible
提供了很多内置变量,这些变量由Ansible
自动设置并提供系统信息、执行上下文等重要数据。
当我们运行PlayBook
时,Ansible
自动就会获取目标主机的基本信息,这些基本信息我们可以通过ansible web -m setup >> vars.txt
的方式获取到(web是主机组名)。这里面记录的就是内置变量。
获取之后,我们就可以通过PlayBook
的方式调用内置变量,实现我们的目的。例如,使所有目标主机echo
自己的IP
地址信息。
- name: vars
hosts: webservers
tasks:
- name: vars
shell:
echo "{{ ansible_default_ipv4.address }}" >> vars.txt
综合应用
[root@ansible1 ~]# cat 03_playbook.yml
- name: create directory and file
hosts: all
vars:
dir: "/root"
file_dir1: "{{ dir }}/ansible1"
file_dir2: "{{ dir }}/ansible2"
file_dir3: "{{ dir }}/ansible3"
file_dir4: "{{ dir }}/ansible4"
file_dir5: "{{ dir }}/ansible5"
file_dir6: "{{ dir }}/ansible6"
tasks:
- name: create directory
file:
path: "{{ item.path }}"
owner: "{{ item.owner }}"
group: "{{ item.group }}"
mode: "{{ item.mode }}"
state: directory
loop:
- { path: '/root/ansible1', mode: '0755', owner: 'root', group: 'root' }
- { path: '/root/ansible2', mode: '0755', owner: 'root', group: 'root' }
- { path: '/root/ansible3', mode: '0777', owner: 'root', group: 'root' }
- { path: '/root/ansible4', mode: '0755', owner: 'root', group: 'root' }
- { path: '/root/ansible5', mode: '0755', owner: 'root', group: 'root' }
- { path: '/root/ansible6', mode: '0644', owner: 'root', group: 'root' }
- name: create file
file:
path: "{{ item.path }}"
owner: "{{ item.owner }}"
group: "{{ item.group }}"
mode: "{{ item.mode }}"
state: touch
loop:
- { path: '{{ file_dir1 }}/file1.txt', mode: '0755', owner: 'root', group: 'root' }
- { path: '{{ file_dir2 }}/file2.txt', mode: '0755', owner: 'root', group: 'root' }
- { path: '{{ file_dir3 }}/file3.txt', mode: '0777', owner: 'root', group: 'root' }
- { path: '{{ file_dir4 }}/file4.txt', mode: '0755', owner: 'root', group: 'root' }
- { path: '{{ file_dir5 }}/file5.txt', mode: '0755', owner: 'root', group: 'root' }
- { path: '{{ file_dir6 }}/file6.txt', mode: '0644', owner: 'root', group: 'root' }
[root@ansible1 ~]#
四、Facts
在ansible
执行时,默认会有一个任务先运行:Gathering Fact
TASK [Gathering Facts] ****************************************
ok: [192.168.xxx.xxx]
这并不是我们在playbook
中定义的任务,这是默认执行的任务。
ansible facts
是ansible
在被纳管主机上自动检测的变量。facts
组件是ansible
用于采集被纳管主机信息的一个功能,采集的信息包括IP
地址、操作系统、以太网设备、mac
地址、硬件信息等相关数据。
那么,采集这些信息有什么用呢?很多时候我们需要根据远程主机的信息作为判断条件操作。例如:根据被纳管主机的操作系统版本,安装不同的软件包。或者获取每台设备上的内存剩余等。
ansible
为我们提供了setup
模块,专门获取被纳管主机的所有facts
信息。setup
模块获取的整个facts
信息被包装在一个JSON
格式的数据结构中,ansible_facts
是最外层的值。我们可以通过ansible Ad-Hoc
进行查看。
ansible localhost -m setup >> facts.txt
你可以进行过滤筛选出你想要的信息:
ansible localhost -m setup -a 'filter=ansible_fqdn'
facts
收集到的信息,会被存储为变量,可以称为事实变量,这些变量有新旧两种书写方式:
五、条件判断
ansible
中的条件判断通过when
实现,允许根据变量、facts
或任务结果来控制任务执行。
示例:
tasks:
- name: Shutdown Debian systems
command: /sbin/shutdown -t now
when: ansible_facts['os_family'] == "Debian"
该任务表示,当目标主机的系统是
Debian
时才会执行该任务。
六、逻辑运算符
ansible
的条件判断中支持and、or
及括号组合条件
when: >
(ansible_distribution == "Ubuntu" and ansible_distribution_major_version == "22.04")
or (ansible_distribution == "CentOS" and ansible_distribution_major_version == "8")
七、Handlers 服务状态
Handlers 是 Ansible 中一种特殊的任务类型,用于在 playbook 执行过程中管理服务重启和配置变更后的操作。
基本概念
只在被通知(notify)时才会执行的任务
通常用于服务重启、重新加载配置等操作
在 play 的所有普通任务执行完毕后才会运行
即使被多次通知,也只会执行一次
举例:
如下,是NFS
服务配置的其中一个任务,这个任务的作用是启动NFS
服务。假设我现在将NFS
的配置文件改动并重新执行这个PlayBook
上传到目标主机上,这个配置会生效么?
很显然不会,因为这里写的是started
,而不是restarted
。那是不是代表,我这要把这里的started
替换成restarted
就可以了呢?不行,因为ansible
的特点是每一次执行它会帮助我们查询状态,如果restarted
不就代表状态被改变了吗,那么回显一定是黄色的,这不方便我们判断playbook
执行的结果。
所以,我们应该在这里做Handlers
,帮助我们去检测运行状态。
- name: 启动NFS服务
service:
name: nfs-server
state: started
enabled: yes
我们的需求是:
当第一次进行
NFS
服务安装时,帮助我们启动服务当配置文件没有发生变化时,不做任何动作
当配置文件发生变化时,重启服务
下面这个playbook
就是我们修改完成的,notify
表示当此任务的状态发生改变后,需要执行handlers
中的任务。注意:
handlers
的name
必须和notify
定义的内容一致,否则无法识别到handlers
中的任务会在你所有预定义的任务执行完毕,最后再执行
- name: nfs
hosts: nfs_servers # 在inventory文件中定义的主机组
become: yes # 使用root权限
tasks:
# 安装必要的软件包
- name: 安装NFS服务端软件
yum:
name: nfs-utils
state: present
# 创建共享目录
- name: 创建共享目录
file:
path: /data/share
state: directory
mode: '0777'
# 配置NFS导出
- name: 上传NFS配置文件
copy:
src: files/exports # 本地文件路径: playbook/files/exports
dest: /etc/exports
notify: restart NFS
# 启动NFS服务
- name: start NFS
systemd:
name: nfs-server
state: started
enabled: yes
handlers:
- name: restart NFS
systemd:
name: nfs-server
state: restarted
八、指定任务执行顺序
在ansible
中,有几种方式可以控制playbook
从特定任务开始执行,而不是从头开始运行整个playbook
。
tasks
通过在执行playbook
时添加--start-at-stack
参数,指定从哪个任务开始依次运行:
ansible-playbook playbook.yml --start-at-task="任务描述"
这里的
“任务描述”
就是我们在playbook
当中定义的任务名
通过添加--list-tasks
参数可获取playbook
中所有的tasks
:
ansible-playbook --list-tasks playbook.yaml
选择 tag
tasks
没有那么的灵活,只能控制从哪个tasks
开始运行。无法实现只运行某个任务、或跳过特定的任务等目的。
可以通过为每个任务添加标签,然后实现只运行特定标签的任务,或跳过特定标签的任务。
tags
需要与任务的名称对齐,如下:
tasks:
- name: 安装软件包
yum:
name: nginx
state: present
tags: install
- name: 配置服务
template:
src: nginx.conf.j2
dest: /etc/nginx/nginx.conf
tags: config
&&
ansible-playbook playbook.yml --tags(-t) "config,install"
ansible-playbook playbook.yml --skip-tag=config,install
但是,执行时一定会按照tag
在playbook
中的顺序执行,即使你故意写反,它也一定按照任务顺序执行。