05 管理变量、机密和事实
1. 管理变量
1.1 Ansible变量简介
Ansible支持利用变量来存储值,并在Ansible项目的所有文件中重复使用这些值。这可以简化项目的创建和维护,并减少错误的数量。
通过变量,可以轻松地在Ansible项目中管理给定环境的动态值。例如,变量可能包含下面这些值:
- 要创建的用户
- 要安装的软件包
- 要重新启动的服务
- 要删除的文件
- 要从互联网检索的存档
1.1.1 命名变量
变量的名称必须以字母开头,并且只能包含字母、数字和下划线。
无效和有效的Ansible变量名称示例
| 无效的变量名称 | 有效的变量名称 |
|---|---|
| web server | web_server |
| remote.file | remote_file |
| 1st file | file_1 file1 |
| remoteserver$1 | remote_server_1 remote_server1 |
1.1.2 定义变量
可以在Ansible项目中的多个位置定义变量。不过,这些变量大致可简化为三个范围级别:
- 全局范围:从命令行或Ansible配置设置的变量
- Play范围:在play和相关结构中设置的变量
- 主机范围:由清单、事实收集或注册的任务,在主机组和个别主机上设置的变量
如果在多个xeklh定义了相同名称的变量,则采用优先级别最高的变量。窄范围优先于更广泛的范围:由清单定义的变量将被playbook定义的变量覆盖,后者将被命令行中定义的变量覆盖。
1.2 playbook中的变量
变量在Ansible Playbook中发挥着重要作用,因为它们可以简化playbook中变量数据的管理。
1.2.1 在Playbook中定义变量
编写playbook时,可以定义自己的变量,然后在任务中调用这些值。例如,名为web_package的变量可以使用值httpd来定义。然后,任务可以使用yum模块调用该变量来安装httpd软件包。
Playbook变量可以通过多种方式定义。一种常见的方式是将变量放在playbook开头的vars块中:
--- - hosts: webservers vars: # 定义变量 web: httpd tasks: - name: provides repo file copy: src: files/CentOS-Base.repo dest: /etc/yum.repos.d/
也可以在外部文件中定义playbook变量。此时不使用playbook中的vars块,可以改为使用vars_files指令,后面跟上相对于playbook位置的外部变量文件名称列表:
[root@localhost httpd]# ls ansible.cfg files install.yml inventory [root@localhost httpd]# mkdir vars 创建一个vars目录 [root@localhost httpd]# ls ansible.cfg files install.yml inventory vars [root@localhost httpd]# vim vars/httpd # 设置变量 web: httpd # web等于httpd --- - hosts: webservers vars_files: # 用这个来定义变量 - vars/httpd # 引用vars/httpd里的变量,这种方式我们用的比较多,不要把变量直接写到play中,写到一个文件里,在play中引用,应为变量不可能只有一个,有多个 tasks: - name: provides repo file copy: src: files/CentOS-Base.repo dest: /etc/yum.repos.d/
而后,可以使用YAML格式在这一/这些文件中定义playbook变量:
- vars/httpd # - 是列表,可以指定某一个或者指定多个
1.2.2 在Playbook中使用变量
声明了变量后,可以在任务中使用这些变量。若要引用变量,可以将变量名放在双大括号内。在任务执行时,Ansible会将变量替换为其值。
--- - hosts: webservers vars: web: httpd tasks: - name: provides repo file copy: src: files/CentOS-Base.repo dest: /etc/yum.repos.d/ - name: install apache dnf: name: "{{ web }}" state: latest - name: provides web site copy: src: files/game dest: /var/www/html/ - name: config apache copy: src: files/httpd-vhosts.conf dest: /etc/httpd/conf.d/ - name: run {{ web }} # 冒号后面不是直接跟的不用引 service: name: "{{ web }}" # 冒号后面直接跟的要用双引号引起来 state: started enabled: yes - name: close firewalld service: name: firewalld state: stopped enabled: no
1.3 主机变量和组变量
直接应用于主机的清单变量分为两在类:
- 主机变量,应用于特定主机
- 组管理,应用于一个主机组或一组主机组中的所有主机
主机变量优先于组变量,但playbook中定义的变量的优先级比这两者更高。
若要定义主机变量和组变量,一种方法是直接在清单文件中定义。这是较旧的做法,不建议采用,但你可能会在未来的工作当中遇到。
定义web01主机的ansible_user主机变量:
[webservers] web01.example.com ansible_user=root ansible_password=runtime
定义webservers主机组的user组变量:
[webservers] web01.example.com [webservers:vars] # 为webservers这个主机组设置的变量 ansible_user=root ansible_password=runtime
定义servers组的user组变量,该组由两个主机组成,每个主机组有两个服务器:
[servers1] node1.example.com node2.example.com [servers2] node3.example.com node4.example.com [servers:children] #servers包含了servers1,servers2也就是包含node1234 servers1 servers2 [servers:vars] # 所以这里的变量包含了 1234 通常不推荐这种写法 user=joe
此做法存在一些缺点,它使得清单文件更难以处理,在同一文件中混合提供了主机和变量信息,而且采用的也是过时的语法。
1.3.1 使用目录填充主机和组变量
定义主机和主机组的变量的首选做法是在与清单文件或目录相同的工作目录中,创建group_vars和host_vars两个目录。这两个目录分别包含用于定义组变量和主机变量的文件。
建议的做法是使用host_vars和group_vars目录定义清单变量,而不直接在清单文件中定义它们。
为了定义用于servers组的组变量,需要创建名为group_vars/servers的YAML文件,然后该文件的内容将使用与playbook相同的语法将变量设置为值:
[root@localhost httpd]# mkdir host_vars group_vars # 这两个目录分别包含用于定义组变量和主机变量的文件 [root@localhost httpd]# ls ansible.cfg files group_vars host_vars install.yml inventory vars # vars变量是给playbook用的 [root@localhost httpd]# cat inventory # 查看清单文件有那些组包含那些主机 [webservers] web01.example.com [root@localhost httpd]# vim group_vars/webservers # 如果要给主机组指定变量就在group_vars里面加上主机组的名字 [root@localhost httpd]# vim host_vars/web01.example.com# 给那个主机指定变量就在host_vars加上主机的名字
类似的,为了定义用于特定主机的主机变量,需要在host_vars目录中创建名称与主机匹配的文件来存放主机变量。
下面的示例更加详细的说明了这一做法。例如在一个场景中,需要管理两个数据中心,并在~/project/inventory清单文件中定义数据中心主机:
[root@localhost ~]# mkdir ~/project [root@localhost ~]# vim ~/project/inventory [datacenter1] node1.example.com node2.example.com [datacenter2] node3.example.com node4.example.com [datacenters:children] # 包含两个大组,和大组里的所有主机 datacenter1 datacenter2
如果需要为两个数据中心的所有服务器定义一个通用值,可以为datacenters主机组设置一个组变量:
[root@localhost ~]# mkdir ~/project/groupo_vars [root@localhost ~]# vim ~/project/groupo_vars/datacenters # 给大组设置了变量 package: httpd
如果要为每个数据中心定义不同的值,可以为每个数据中心主机组设置组变量:
[root@localhost ~]# vim ~/project/groupo_vars/datacenter1 # 两个小组设置了变量 package: httpd [root@localhost ~]# vim ~/project/groupo_vars/datacenter2 package: apache2
如果要为每一数据中心的各个主机定义不同的值,则在单独的主机变量文件中定义变量:
[root@localhost ~]# mkdir ~/project/host_vars [root@localhost ~]# vim ~/project/host_vars/node1.example.com # 给不同的主机设置变量 package: httpd [root@localhost ~]# vim ~/project/host_vars/node2.example.com package: apache2 [root@localhost ~]# vim ~/project/host_vars/node3.example.com package: mariadb-server [root@localhost ~]# vim ~/project/host_vars/node4.example.com package: mysql-server
以上示例项目project的目录结构如果包含上面所有示例文件,将如下所示:
[root@localhost httpd]# ls ansible.cfg(配置文件) files(playbook所需要的文件) group_vars(主机组变量) host_vars(主机变量) install.yml (playbook) inventory (清单文件) vars(playbook变量) [root@localhost opt]# tree httpd/ httpd/ ├── ansible.cfg ├── files │ ├── CentOS-Base.repo │ ├── game │ │ ├── 服务器之家.url │ │ ├── 精品免费商业源码下载.url │ │ ├── css │ │ │ ├── font.css │ │ │ └── style.css │ │ ├── fonts │ │ │ ├── OPPOSans-B.woff2 │ │ │ ├── OPPOSans-M.woff2 │ │ │ ├── OPPOSans-R.woff2 │ │ │ └── PangMenZhengDao.woff2 │ │ ├── images │ │ │ ├── icon_APP.gif │ │ │ ├── icon_bili.svg │ │ │ ├── icon_mail.svg │ │ │ ├── icon_mouse2.svg │ │ │ ├── icon_mouse.svg │ │ │ ├── icon_VR.gif │ │ │ ├── icon_WEB.gif │ │ │ ├── icon_wechat.svg │ │ │ ├── lock.svg │ │ │ └── vx.png │ │ ├── index.html │ │ ├── js │ │ │ ├── clipboard.min.js │ │ │ ├── jquery.movebg.js │ │ │ ├── jquery.SuperSlide.2.1.1.js │ │ │ ├── respond.min.js │ │ │ └── uaredirect.js │ │ └── m │ │ ├── css │ │ │ └── style.css │ │ └── index.html │ └── httpd-vhosts.conf ├── group_vars ├── host_vars ├── install.yml ├── inventory └── vars └── httpd 11 directories, 32 files
1.4 从命令行覆盖变量
清单变量可被playbook中设置的变量覆盖,这两种变量又可通过在命令行中传递参数到ansible或ansible-playbook命令来覆盖。在命令行上设置的变量称为额外变量。
当需要覆盖一次性运行的playbook的变量的已定义值时,额外变量非常有用。例如:
[root@localhost httpd]# ansible all -e "ansible_password=023654" -m ping # #-e在命令行中添加变量覆盖之前清单文件中设置的变量
1.5 使用数组作为变量
除了将同一元素相关的配置数据(软件包列表、服务列表和用户列表等)分配到多个变量外,也可以使用数组。这种做法的一个好处在于,数组是可以浏览的。
例如,假设下列代码片段:
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
这将可以改写成名为users的数组:
users: bjones: first_name: Bob last_name: jones home_dir: /users/bjones acook: first_name: Anne last_name: Cook home_dir: /users/acook
然后可以使用以下变量来访问用户数据:
# Returns 'Bob' users.bjones.first_name # Returns '/users/acook' users.acook.home_dir
由于变量被定义为Python字典,因此可以使用替代语法:
# Returns 'Bob' users['bjones']['first-name'] # Returns '/users/acook' users['acook']['home_dir']
如果键名与python方法或属性的名称(如discard、copy和add)相同,点表示法可能会造成问题。使用中括号表示法有助于避免冲突和错误。
但要声明的是,上面介绍的两种语法都有效,但为了方便故障排除,建议在任何给定Ansible项目的所有文件中一致地采用一种语法,不要混用。
1.6 使用已注册变量捕获命令输出
可以使用register语句捕获命令输出。输出保存在一个临时变量中,然后在playbook中可用于调试用途或者达成其他目的,例如基于命令输出的特定配置。
以下playbook演示了如何为调试用途捕获命令输出:
[root@localhost httpd]# vim test.yml --- - hosts: all tasks: - name: test command: "ls /root/" # 执行命令 register: result # 执行的结果等于这个变量 - debug: var=result # 运行该playbook时,debug模块用于将注册变量的值转储到终端。 [root@localhost httpd]# ansible-playbook test.yml PLAY [all] ************************************************************************************ TASK [Gathering Facts] ************************************************************************ ok: [web01.example.com] TASK [test] *********************************************************************************** changed: [web01.example.com] TASK [debug] ********************************************************************************** ok: [web01.example.com] => { "result": { # 变量 "changed": true, "cmd": [ "ls", "/root/" # 执行的命令 ], "delta": "0:00:00.004035", "end": "2022-06-05 11:58:23.686900", "failed": false, "rc": 0, "start": "2022-06-05 11:58:23.682865", "stderr": "", "stderr_lines": [], "stdout": "anaconda-ks.cfg", # 值 "stdout_lines": [ "anaconda-ks.cfg" ] } } PLAY RECAP ************************************************************************************ web01.example.com : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
一条命令执行的结果,赋予某个变量这种方式就是注册变量
2. 管理机密
2.1 Ansible Vault
Ansible可能需要访问密码或API密钥等敏感数据,以便能配置受管主机。通常,此信息可能以纯文本形式存储在清单变量或其他Ansible文件中。但若如此,任何有权访问Ansible文件的用户或存储这些Ansible文件的版本控制系统都能够访问此敏感数据。这显示存在安全风险。
Ansible提供的Ansible Vault可以加密和解密任何由Ansible使用的结构化数据文件。若要使用Ansible Vault,可通过一个名为ansible-vault的命令行工具创建、编辑、加密、解密和查看文件。Ansible Vault可以加密任何由Ansible使用的结构化数据文件。这可能包括清单变量、playbook中含有的变量文件、在执行playbook时作为参数传递的变量文件,或者Ansible角色中定义的变量。
2.1.1 创建加密的文件
要创建新的加密文件,可使用ansible-vault create filename命令。该命令将提示输入新的vault密码,然后利用默认编辑器vi打开文件。我们可以设置和导出EDITOR环境变量,通过设置和导出指定其他默认编辑器。例如,若要将默认编辑器设为nano,可设置为export EDITOR=nano。
[root@localhost vars]# ansible-vault create abc # 创建一个不存在的加密文件 New Vault password: # 设置密码 Confirm New Vault password: name: httpd # 自动进去vi编辑器添加内容 port: 80 [root@localhost vars]# ls abc httpd [root@localhost vars]# ll 总用量 8 -rw------- 1 root root 419 6月 5 13:45 abc # 600的权限只有root才能读写 -rw-r--r-- 1 root root 11 6月 4 20:47 httpd [root@localhost vars]# cat abc # 是加密的,所以看不了 $ANSIBLE_VAULT;1.1;AES256 38343564346134346462616663313462643862323337353637636233356165386266386366613130 3838653133633136326661646563306237306639323336620a613064363365396130343034636639 35313334613036616338363434616233333161616461613237386438666136646166666431636431 6430393939393837310a343332386461623066333962353732653965316663383231666261306366 66353135353432663862316438323564663464393265666262663161386339313264 [root@localhost vars]# ansible-vault view abc # 查看加密的文件,只能输入密码才能查看 Vault password: name: httpd port: 80
我们还可以用vault密码文件来存储vault密码,而不是通过标准输入途径输入vault密码。这样做需要使用文件权限和其他方式来严密保护该文件。
[root@localhost httpd]# ls ansible.cfg files group_vars host_vars install.yml inventory test.yml vars [root@localhost httpd]# vim .pass # 创建一个隐藏文件 123456 # 把密码加进去 [root@localhost httpd]# ls ansible.cfg files group_vars host_vars install.yml inventory test.yml vars [root@localhost httpd]# ll # 查看不到.pss文件 总用量 32 -rw-r--r--. 1 root root 19973 5月 29 15:44 ansible.cfg drwxr-xr-x. 3 root root 67 6月 1 10:58 files drwxr-xr-x 2 root root 24 6月 5 11:31 group_vars drwxr-xr-x 2 root root 6 6月 4 23:50 host_vars -rw-r--r-- 1 root root 690 6月 4 20:52 install.yml -rw-r--r-- 1 root root 33 6月 4 23:45 inventory -rw-r--r-- 1 root root 118 6月 5 11:55 test.yml drwxr-xr-x 2 root root 30 6月 5 13:45 vars [root@localhost httpd]# ansible-vault view --vault-password-file .pass vars/abc # 用.pass文件中的提前设置好的密码,直接去查看abc文件就可以不用输密码 name: httpd port: 80
2.1.2 查看加密的文件
可以使用ansible-vault view filename命令查看Ansible Vault加密的文件,而不必打开它进行编辑。
[root@localhost vars]# ansible-vault view abc # 查看加密文件 Vault password: name: httpd port: 80
查看时需要输入加密文件的加密密码。
2.1.3 编辑现有的加密文件
要编辑现有的加密文件,Ansible Vault提供了ansible-vault edit filename命令。此命令将文件解密为一个临时文件,并允许编辑。保存时,它将复制其内容并删除临时文件。
[root@localhost httpd]# ansible-vault edit vars/abc # edit编辑现有加密文件 Vault password: # 输入密码 name: httpd port: 81 #修改 [root@localhost httpd]# ansible-vault view --vault-password-file .pass vars/abc # view查看 name: httpd port: 81
编辑时需要输入加密文件的加密密码。
edit子命令始终重写文件,因此只应在进行更改时使用它。要查看文件的内容而不进行更改时,应使用view子命令。
2.1.4 加密现有的文件
要加密已存在的文件,请使用ansible-vault encrypt filename命令。此命令可取多个欲加密文件的名称作为参数
[root@localhost httpd]# cat vars/httpd web: httpd # 现有文件没加密 [root@localhost httpd]# ansible-vault encrypt vars/httpd # 对现有的vars下面的httpd加密 New Vault password: # 设置密码 Confirm New Vault password: # 再次输入 Encryption successful # 有successful表示成功 [root@localhost httpd]# cat vars/httpd 现在查看就看不了 $ANSIBLE_VAULT;1.1;AES256 30306634623565393430623966306665326234383232386234306662653935633438303135636537 6435653331396361653733373736666366373265316661310a376430626639336437626634383062 39333032373461356135663162353166306663623536303638616461333434636136613064383662 3037663762363161310a343463313034313832613763613230666439363638323438363065316266 3939
2.1.5 解密现有的文件
现有的加密文件可以通过ansible-vault decrypt filename命令永久解密。在解密单个文件时,可使用--output选项以其他名称保存解密的文件。
[root@localhost httpd]# ansible-vault decrypt vars/abc # 永久解密 Vault password: #输入密码 Decryption successful [root@localhost httpd]# cat vars/abc # 可以直接看了 name: httpd port: 81
2.1.6 更改加密文件的密码
使用ansible-vault rekey filename命令更改加密文件的密码。此命令可一次性更新多个数据文件的密钥。它将提示提供原始密码和新密码。
[root@localhost httpd]# ansible-vault rekey vars/httpd # 重新设置新密码 Vault password: # 老密码 New Vault password: # 新密码 Confirm New Vault password: #新密码 Rekey successful #成功 [root@localhost httpd]# ansible-vault view --vault-password-file .pass vars/httpd # 就可以查看了 web: httpd
在使用vault密码文件时,请使用--new-vault-password-file选项:
[root@localhost httpd]# ls -a # 现在只有.pass文件 . ansible.cfg group_vars install.yml .inventory.swp test.yml .. files host_vars inventory .pass vars [root@localhost httpd]# vim .httpd #把新密码写到.httpd里 123123 [root@localhost httpd]# ansible-vault rekey --new-vault-password-file .httpd vars/httpd #用.httpd对vars/httpd重新加密 Vault password: # 输入老密码 Rekey successful # 成功不需要输入新密码
2.2 playbook和ansible vault
要运行可访问通过Ansible Vault加密的文件的playbook,需要向ansible-playbook命令提供加密密码。如果不提供密码,playbook将返回错误:
[root@localhost httpd]# cat vars/httpd # 加密了 $ANSIBLE_VAULT;1.1;AES256 63346639376263386131333637386165323264626262396336336531613562316238313837323634 3334663936373861336664656264383030666236613836650a666330363137306564653563666437 31633361316133306530663865393430313238656662313532626330613536633935366331616633 3439626662653761360a373836636637626337376330396530396439346463313232633264633732 3132 [root@localhost httpd]# cat install.yml --- - hosts: webservers vars_files: - vars/httpd # # playbook中也有用到 [root@localhost httpd]# ansible-playbook -C install.yml # 因为vars/httpd加密所以跑不了会报错 ERROR! Attempting to decrypt but no vault secrets found
要为playbook提供vault密码,可使用--vault-id选项。例如,要以交互方式提供vault密码,请使用下例中所示的--vault-id @prompt:
[root@localhost httpd]# ansible-playbook --vault-id @prompt install.yml Vault password (default): # 手动输密码
此外,也可使用--vault-password-file选项指定以纯文本存储加密密码的文件。密码应当在该文件中存储为一行字符串。由于该文件包含敏感的纯文本密码,因此务必要通过文件权限和其他安全措施对其加以保护。
[root@localhost httpd]# ansible-playbook --vault-password-file .httpd install.yml # 运行时告诉密码存放的文件也可以运行,也不用手动输入密码
也可以使用ANSIBLE_VAULT_PASSWORD_FILE环境变量,指定密码文件的默认位置。
[root@localhost httpd]# export ANSIBLE_VAULT_PASSWORD_FILE=/opt/httpd/.httpd # 设置一个变量
[root@localhost httpd]# ansible-playbook -C install.yml # 就可以直接跑
从Ansible2.4开始,可以通过ansible-playbook使用多个Ansible Vault密码。要使用多个密码,需要将多个--vault-id或--vault-password-file选项传递给ansible-playbook命令。
[root@localhost httpd]# ansible-vault view vars/httpd Vault password: web: httpd [root@localhost httpd]# ansible-vault view vars/port Vault password: port: 82 # 两个加密的 [root@localhost httpd]# ansible-playbook --vault-id one@prompt --vault-id two@prompt install.yml # 运行一个play要用到那两个加密文件, Vault password (one): #输入第一个密码要按顺序 Vault password (two): #输入第二个密码
注意:@prompt前面的vaultIDone和two可以是任何字符,甚至可以完全省略它们。不过,如果在使用ansible-vault命令加密文件时使用--vault-id id选项,则在运行ansible-playbook时,将最先尝试匹配ID的密码。如果不匹配,将会尝试用户提供的其他密码。没有ID的vaultID@prompt实际上是default@prompt的简写,这意味着提示输入vaultIDdefault的密码。
2.2.1 变量文件管理的推荐做法
若要简化管理,务必要设置Ansible项目,使敏感变量和其他变量保存在相互独立的文件中。然后,包含敏感变量的文件可通过ansible-vault命令进行保护。
管理组变量和主机变量的首选方式是在playbook级别上创建目录。group_vars目录通常包含名称与它们所应用的主机组匹配的变量文件。host_vars目录通常包含名称与它们所应用的受管主机名称匹配的变量文件。
不过,除了使用group_vars和host_vars中的文件外,也可对每一主机组或受管主机使用目录。这些目录可包含多个变量文件,它们都由该主机组或受管主机使用。例如,在playbook.yml的以下项目目录中,webservers的主机组的成员将使用group_vars/webservers/vars文件中的变量,而172.16.103.129将使用host_vars/172.16.103.129/vars和host_vars/172.16.103.129/vault中的变量:
. ├── ansible.cfg ├── group_vars │ └── webservers │ └── vars ├── host_vars │ └── 172.16.103.129 │ ├── vars │ └── vault ├── inventory └── playbook.yml
在这种情况中,其好处在于用于172.16.103.129的大部分变量可以放在vars文件中,敏感变量则可单独放在vault文件中保密。然后使用ansible-vault加密vault文件,而将vars文件保留为纯文本。
在本例中,host_vars/172.16.103.129目录内使用的文件名没有什么特别之处。该目录可以包含更多文件,一些由Ansible Vault加密,另一些则不加密。
Playbook变量(与清单变量相对)也可通过Ansible Vault保护。敏感的playbook变量可以放在单独的文件中,此文件通过Ansible Vault加密,并能vars_files指令包含在该playbook中。这也是推荐做法,因为playbook变量的优先级高于清单变量。
如果需要在playbook中使用多个vault密码,请确保每个加密文件分配一个vaultID,并在运行playbook时输入具有该vaultID的匹配密码。这可确保在解密vault加密文件时先选择正确的密码,这比强制Ansible尝试用户提供的所有vault密码直至找到正确的密码要快。
3. 管理事实
3.1 描述Ansible事实
Ansible事实是Ansible在受管主机上自动检测到的变量。事实中包含有与主机相关的信息,可以像play中的常规变量、条件、循环或依赖于从受管主机收集的值的任何其他语句那样使用。
为受管主机收集的一些事实可能包括:
- 主机名称
- 内核版本
- 网络接口
- IP地址
- 操作系统版本
- 各种环境变量
- CPU数量
- 提供的或可用的内存
- 可用磁盘空间
[root@localhost httpd]# ansible all -m setup| less web01.example.com | SUCCESS => { "ansible_facts": { "ansible_all_ipv4_addresses": [ "192.168.149.136" # 我们用的最多的就是id ], "ansible_all_ipv6_addresses": [ "fe80::20c:29ff:fe8a:d3b7" ], "ansible_apparmor": { "status": "disabled" }, "ansible_architecture": "x86_64", "ansible_bios_date": "07/22/2020", "ansible_bios_version": "6.00",
借助事实,可以方便地检索受管主机的状态,并根据该状态确定要执行的操作。例如:
- 可以根据含有受管主机当前内核版本的事实运行条件任务,以此来重启服务器
- 可以根据通过事实报告的可用内存来自定义MySQL配置文件
- 可以根据事实的值设置配置文件中使用的IPv4地址
通常,每个play在执行第一个任务之前会先自动运行setup模块来收集事实。
查看为受管主机收集的事实的一种方式是,运行一个收集事实并使用debug模块显示ansible_facts变量值的简短playbook。
- name: Fact dump hosts: all tasks: - name: Print all facts debug: var: ansible_facts
运行该playbook时,事实将显示在作业输出中:
ansible-playbook facts.yml
Playbook以JSON格式显示ansible_facts变量的内容。
下表显示了可能从受管节点收集的并可在playbook中使用的一些事实:
Ansible事实的示例
| 事实 | 变量 |
|---|---|
| 短主机名 | ansible_facts['hostname'] |
| 完全限定域名 | ansible_facts['fqdn'] |
| IPv4地址 | ansible_facts['default_ipv4']['address'] |
| 所有网络接口的名称列表 | ansible_facts['interfaces'] |
| /dev/vda1磁盘分区的大小 | ansible_facts['devices']['vda']['partitions']['vda1']['size'] |
| DNS服务器列表 | ansible_facts['dns']['nameservers'] |
| 当前运行的内核版本 | ansible_facts['kernel'] |
如果变量的值为散列/字典类型,则可使用两种语法来获取其值。比如:
ansible_facts['default_ipv4']['address']也可以写成ansible_facts.default_ipv4.address ansible_facts['dns']['nameservers']也可以写成ansible_facts.dns.nameservers
在playbook中使用事实时,Ansible将事实的变量名动态替换为对应的值:
--- - hosts: all tasks: - name: test debug: msg: > The default ipaddress of {{ ansible_facts["fqdn"] }} is {{ ansible_facts["default_ipv4"]["address"] }} [root@localhost httpd]# ansible-playbook test.yml PLAY [all] ************************************************************************************ TASK [Gathering Facts] ************************************************************************ ok: [web01.example.com] TASK [test] *********************************************************************************** ok: [web01.example.com] => { "msg": "The default ipaddress of web01.example.com is 192.168.149.136\n" # 引用成功 } PLAY RECAP ************************************************************************************ web01.example.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
--- - hosts: all,localhost # 把本机也加上去 tasks: - name: test debug: msg: > The default ipaddress of {{ ansible_facts["fqdn"] }} is {{ ansible_facts["default_ipv4"]["address"] }} [root@localhost httpd]# ansible-playbook test.yml PLAY [all,localhost] ************************************************************************** TASK [Gathering Facts] ************************************************************************ ok: [web01.example.com] ok: [localhost] TASK [test] *********************************************************************************** ok: [web01.example.com] => { "msg": "The default ipaddress of web01.example.com is 192.168.149.136\n" } ok: [localhost] => { "msg": "The default ipaddress of localhost.localdomain is 192.168.149.135\n" # 本机的也有了 } PLAY RECAP ************************************************************************************ localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 web01.example.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
--- - hosts: all tasks: - name: test debug: msg: > The default ipaddress of {{ ansible_facts["fqdn"] }} is {{ ansible_default_ipv4["address"] }}. # 不要facts也可以 [root@localhost httpd]# ansible-playbook test.yml PLAY [all] ************************************************************************************ TASK [Gathering Facts] ************************************************************************ ok: [web01.example.com] TASK [test] *********************************************************************************** ok: [web01.example.com] => { "msg": "The default ipaddress of web01.example.com is 192.168.149.136.\n" } PLAY RECAP ************************************************************************************ web01.example.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
3.2 Ansible事实作为变量注入
在Ansible2.5之前,事实是作为前缀为字符串ansible_的单个变量注入,而不是作为ansible_facts变量的一部分注入。例如,ansible_facts['distribution']事实会被称为ansible_distribution。
许多较旧的playbook仍然使用作为变量注入的事实,而不是在ansible_facts变量下创建命名空间的新语法。我们可以使用临时命令来运行setup模块,以此形式显示所有事实的值。以下示例中使用一个临时命令在受管主机172.16.103.129上运行setup模块:
ansible 172.16.103.129 -m setup
选定的Ansible事实名称比较
| ansible_facts形式 | 旧事实变量形式 |
|---|---|
| ansible_facts['hostname'] | ansible_hostname |
| ansible_facts['fqdn'] | ansible_fqdn |
| ansible_facts['default_ipv4']['address'] | ansible_default_ipv4['address'] |
| ansible_facts['interfaces'] | ansible_interfaces |
| ansible_facts['devices']['vda']['partitions']['vda1']['size'] | ansible_devices['vda']['partitions']['vda1']['size'] |
| ansible_facts['dns']['nameservers'] | ansible_dns['nameservers'] |
| ansible_facts['kernel'] | ansible_kernel |
目前,Ansible同时识别新的事实命名系统(使用ansible_facts)和旧的2.5前“作为单独变量注入的事实”命名系统。
将Ansible配置文件的[default]部分中inject_facts_as_vars参数设置为False,可关闭旧命名系统。默认设置目前为True。
[root@localhost httpd]# ls ansible.cfg files group_vars host_vars install.yml inventory test.yml vars [root@localhost httpd]# vim ansible.cfg inject_facts_as_vars = False 修改成False 就不持支旧命名系统 --- - hosts: all tasks: - name: test debug: msg: > The default ipaddress of {{ ansible_facts["fqdn"] }} is {{ ansible_default_ipv4["address"] }}. # 这种写法就不持支了 [root@localhost httpd]# ansible-playbook test.yml # 运行就会报错 PLAY [all] ************************************************************************************ TASK [Gathering Facts] ************************************************************************ ok: [web01.example.com] TASK [test] *********************************************************************************** fatal: [web01.example.com]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'ansible_default_ipv4' is undefined\n\nThe error appears to be in '/opt/httpd/test.yml': line 4, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n tasks:\n - name: test\n ^ here\n"} PLAY RECAP ************************************************************************************ web01.example.com : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
inject_facts_as_vars的默认值在Ansible的未来版本中可能会更改为False。如果设置为False,则只能使用新的ansible_facts.*命名系统引用Ansible事实。所以建议大家一开始就要适应这种方式。
3.3 关闭事实收集
有时我们不想为play收集事实。这样做的原因可能有:
- 不准备使用任何事实
- 希望加快play速度
- 希望减小play在受管主机上造成的负载
- 受管主机因为某种原因无法运行setup模块
- 需要安装一些必备软件后再收集事实
以上种种原因导致我们可能想要永久或暂时关闭事实收集的功能,要为play禁用事实收集功能,可将gather_facts关键字设置为no:
[root@localhost httpd]# vim install.yml # 这个playbook没有用到事实 --- - hosts: webservers gather_facts: no # 可以选择关闭 vars_files: - vars/httpd tasks: - name: provides repo file copy: src: files/CentOS-Base.repo dest: /etc/yum.repos.d/ [root@localhost httpd]# ansible-playbook install.yml -C # 运行一下 PLAY [webservers] # 它就直接跑了,没有收集事实 ********************************************************************************************** TASK [provides repo file] ************************************************************************************** ok: [web01.example.com] TASK [install apache] ****************************************************************************************** ok: [web01.example.com] TASK [provides web site] *************************************************************************
3.4 创建自定义事实
除了使用系统捕获的事实外,我们还可以自定义事实,并将其本地存储在每个受管主机上。这些事实整合到setup模块在受管主机上运行时收集的标准事实列表中。它们让受管主机能够向Ansible提供任意变量,以用于调整play的行为。
自定义事实可以在静态文件中定义,格式可为INI文件或采用JSON。它们也可以是生成JSON输出的可执行脚本,如同动态清单脚本一样。
有了自定义事实,我们可以为受管主机定义特定的值,供play用于填充配置文件或有条件地运行任务。动态自定义事实允许在play运行时以编程方式确定这些事实的值,甚至还可以确定提供哪些事实。
默认情况下,setup模块从各受管主机的/etc/ansible/facts.d目录下的文件和脚本中加载自定义事实。各个文件或脚本的名称必须以.fact结尾才能被使用。动态自定义事实脚本必须输出JSON格式的事实,而且必须是可执行文件。
以下是采用INI格式编写的静态自定义事实文件。INI格式的自定义事实文件包含由一个部分定义的顶层值,后跟用于待定义的事实的键值对:
[root@web01 ~]# mkdir /etc/ansible/facts.d -p #创建一个自定义事实的目录 [root@web01 ~]# cd /etc/ansible/facts.d/ [root@web01 facts.d]# vi web01.fact # 编辑这个文件 [users] name = tom # 名字 age = 20 # 值
可以利用临时命令在受管主机上运行setup模块来检查自定义事实的结构
[root@localhost httpd]# ansible all -m setup | less #控制主机查看 "ansible_local": { "web01": { "users": { "age": "20", "name": "tom"
同样的事实可能以JSON格式提供。以下JSON事实等同于以上示例中INI格式指定的事实。JSON数据可以存储在静态文本文件中,或者通过可执行脚本输出到标准输出:
[root@web01 facts.d]# vi web01.fact # 也可以写成这种格式,但是不推荐用 { "packages": { "web_package": "httpd", "db_package": "mariadb-server" }, "users": { "user1": "joe", "user2": "jane" } } [root@localhost httpd]# ansible all -m setup | less # 查看事实 "users": { "user1": "joe", # 也可以看到 "user2": "jane" } } },
自定义事实文件不能采用playbook那样的YAML格式。JSON格式是最为接近的等效格式。
自定义事实由setup模块存储在ansible_facts.ansible_local变量中。
事实按照定义它们的文件的名称来整理。例如,假设前面的自定义事实由受管主机上保存为/etc/ansible/facts.d/custom.fact的文件生成。在这种情况下,ansible_facts.ansible_local['custom']['users']['user1']的值为joe。
[root@web01 facts.d]# vi web01.fact # 编写自定义事实 [users] name = tom age = 20 [root@localhost httpd]# vim test.yml --- - hosts: all tasks: - name: test debug: msg: > My name is {{ ansible_facts['ansible_local']['web01']['users']['name'] }} , # 系统事实只有输local自定义事实必须是ansible_local I'm {{ ansible_facts['ansible_local']['web01']['users']['age'] }} yesrs old. [root@localhost httpd]# ansible-playbook test.yml -C # 用playbook获取自定义事实 PLAY [all] ***************************************************************************************************** TASK [Gathering Facts] ***************************************************************************************** ok: [web01.example.com] TASK [test] **************************************************************************************************** ok: [web01.example.com] => { "msg": "My name is tom , I'm 20 yesrs old.\n" # 打印出来了 } PLAY RECAP ***************************************************************************************************** web01.example.com : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
3.5 使用魔法变量
一些变量并非事实或通过setup模块配置,但也由Ansible自动设置。这些魔法变量也可用于获取与特定受管主机相关的信息。
最常用的有四个:
| 魔法变量 | 说明 |
|---|---|
| hostvars | 包含受管主机的变量,可以用于获取另一台受管主机的变量的值。 如果还没有为受管主机收集事实,则它不会包含该主机的事实。 |
| group_names | 列出当前受管主机所属的所有组 |
| groups | 列出清单中的所有组和主机 |
| inventory_hostname | 包含清单中配置的当前受管主机的主机名称。 因为各种原因有可能与事实报告的主机名称不同 |
另外还有许多其他的“魔法变量”。有关更多信息,请参见以下链接:
https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#variable-precedence-where-should-i-put-a-variable。
若要深入了解它们的值,一个途径是使用debug模块报告特定主机的hostvars变量的内容:
[root@localhost httpd]# ansible all -m debug -a 'var=hostvars["localhost"]' web01.example.com | SUCCESS => { "hostvars[\"localhost\"]": { "ansible_check_mode": false, "ansible_connection": "local", "ansible_diff_mode": false, "ansible_facts": {}, "ansible_forks": 5, "ansible_inventory_sources": [ "/opt/httpd/inventory" ], "ansible_local": {}, "ansible_playbook_python": "/usr/libexec/platform-python", "ansible_python_interpreter": "/usr/libexec/platform-python", "ansible_verbosity": 0, "ansible_version": { "full": "2.9.27", "major": 2, "minor": 9, "revision": 27, "string": "2.9.27" }, "group_names": [], "groups": { "all": [ "web01.example.com" ], "ungrouped": [], "webservers": [ "web01.example.com"
[root@localhost httpd]# ansible-playbook --vault-id one@prompt --vault-id two@prompt install.yml Vault password (one): Vault password (two):

浙公网安备 33010602011771号