ansible—管理变量、机密、事实
管理变量
Ansible支持利用变量来存储值,并在Ansible项目的所有文件中重复使用这些值。这可以简化项目的创建和维护,并减少错误的数量
为什么要设定变量:将 playbook 中的某些值使用变量代替,从而简化 playbook 的编写
通过变量,可以轻松地在Ansible项目中管理给定环境的动态值。例如,变量可能包含下面这些值:
要创建的用户
要安装的软件包
要重新启动的服务
要删除的文件
要从互联网检索的存档
### 变量的命名
变量的名称必须以字母开头,并且只能包含字母、数字和下划线。
无效和有效的Ansible变量名称示例:
无效的变量名称 | 有效的变量名称 |
---|---|
web server | web_server |
remote.file | remote_file |
1st file | file_1 file1 |
remoteserver$1 | remote_server_1 remote_server1 |
定义变量
可以在Ansible项目中的多个位置定义变量。这些变量大致可简化为三个范围级别:
全局范围:从命令行或Ansible配置设置的变量
Play范围:在play和相关结构中设置的变量
主机范围:由清单、事实收集或注册的任务,在主机组和个别主机上设置的变量
注意:如果多个级别上定义了相同名称的变量,优先采用级别最高的变量,窄范围优先于广范围
在Playbook中定义变量
编写playbook时,可以定义自己的变量,然后在任务中调用这些值。例如,名为web_package的变量可以使用值httpd来定义。然后,任务可以使用yum模块调用该变量来安装httpd软件包。
Playbook变量可以通过多种方式定义。一种常见的方式是将变量放在playbook开头的vars块中:
---
- hosts: all
vars:
IP:192.168.200.136
NAME: node1
tasks:
- name: test
lineingile:
PATH: /etc/hosts
line: '{{ IP }} {{ NAME }}'
state: present
//发现在受控主机上插入了最下面一行内容
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.200.136 node1
在也可以在外部文件中定义playbook变量。此时不使用playbook中的vars块,可以改为使用vars_files指令,后面跟上相对于playbook位置的外部变量文件名称列表:
---
- hosts: 192.168.200.136
vars_files:
- /opt/cai/index.yml
tasks:
- name: test
lineinfile:
path: /etc/hosts
line: '{{ IP }} {{ NAME }}'
state: present
//命令结果
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.200.136 node1
192.168.200.140 node2
/opt/cai/index.yml //路径 与变量文件的目录在同一路径下
index.yml内容
IP: 192.168.200.140
NAME: node2
在playbook中使用变量
声明了变量后,可以在任务中使用这些变量。若要引用变量,可以将变量名放在双大括号内。在任务执行时,Ansible会将变量替换为其值。
注意:当变量用作开始一个值的第一元素时,必须使用引号
vars:
user: test
tasks:
- name: Creates the user {{ user }}
user:
name: "{{ user }}"
主机变量和组管理
直接应用于主机的清单变量分为两在类:
主机变量,应用于特定主机
组管理,应用于一个主机组或一组主机组中的所有主机
主机变量优先于组变量,但playbook中定义的变量的优先级比这两者更高。
方式一:比较旧,不建议采用
缺点:这种做法使的清单文件难以处理,在同一文件中混合提供主机和变量信息,语法也过时
#定义 server1.example.com 的 ansible_user 主机变量:
[webservers]
server1.example.com ansible_user=student
#定义 dbservers 主机组的 user 组变量:
[dbserver]
dbserver1.example.com
dbserver2.example.com
[dbservers:vars]
user=student
#定义嵌套组 user 变量:
[servers1]
node1.example.com
node2.example.com
[servers2]
node3.example.com
node4.example.com
[servers:children]
servers1
servers2
[servers:vars]
user=student
方法二: 使用目录填充主机组变量
定义主机和主机组的变量的首选做法是在与清单文件或目录相同的工作目录中,创建group_vars和host_vars两个目录。这两个目录分别包含用于定义组变量和主机变量的文件。
创建 group_vars/servers 的 YAML 文件,设置变量为值:user: student
在 host_vars 目录中创建名称与主机匹配的文件来存放主机变量
//在host_vars/192.168.200.136文件内编写主机变量
[servers]
ansible_user: root
ansible_password: 1
在group_vars/192.168.200.136文件内编写主变量
[servers]
172.16.103.129
[servers:vars]
user=root
//整体结构图
[root@localhost group_vars]# tree /opt/project/
/opt/project/
├── ansible.cfg
├── cai
│ └── index.yml
├── group_vars
│ └── 192.168.200.136
├── host_vars
│ └── 192.168.200.136
└── inventory
从命令行覆盖变量
清单变量可被playbook中设置的变量覆盖,这两种变量又可通过在命令行中传递参数到ansible或ansible-playbook命令来覆盖。在命令行上设置的变量称为额外变量。
当需要覆盖一次性运行的playbook的变量的已定义值时,额外变量非常有用。例如:
//编辑一个主机变量文件(此文件在host_vars目录里)
ansible_user: root
ansible_password:
ip: 1.1.1.1
name: node1
//再编写一个test.yml文件
**注意:此文件的优先级要大一级你所编写的配置文件,即test.yml和host_vars在同一目录**
---
- hosts: 192.168.200.136
tasks:
- name: test
lineinfile:
path: /etc/hosts
line: "{{ ip }} {{ name }}"
state: present
//执行结果
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
1.1.1.1 node1
//在test.yml配置文件里添加一行IP,发现主机清单变量可被playbook中设置的变量覆盖
---
- hosts: 192.168.200.136
vars:
ip: 2.2.2.2
tasks:
- name: test
lineinfile:
path: /etc/hosts
line: "{{ ip }} {{ name }}"
state: present
//主机清单变量和playbook设置的变量又能被ansible-playbook在命令行中传递参数到ansible或ansible-playbook命令来覆盖。
[root@localhost project]# ansible-playbook -e "ip=3.3.3.3" testsss.yml
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
1.1.1.1 node1
2.2.2.2 node1
3.3.3.3 node1
使用数组作为变量
除了将同一元素相关的配置数据(软件包列表、服务列表和用户列表等)分配到多个变量外,也可以使用数组。这种做法的一个好处在于,数组是可以浏览的。
假设下列代码片段:
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
示例:
//test.yml文件配置
---
- hosts: 192.168.200.136
gather_facts: no
tasks:
- name: test
lineinfile:
path: /etc/hosts
line: "{{ info.node4.ip }} {{ info.node4.domain }}"
state: present
//主机清单配置
ansible_user: root
ansible_password:
info:
node4:
ip: 4.4.4.4
domain: www.example.com
//执行结果
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
1.1.1.1 node1
2.2.2.2 node1
3.3.3.3 node1
4.4.4.4 www.example.com
使用已注册变量捕获命令输出
可以使用register语句捕获命令输出。输出保存在一个临时变量中,然后在playbook中可用于调试用途或者达成其他目的,例如基于命令输出的特定配置。
级别:
标准输入:STDIN
标准输出:STDOUT
标准错误:STDERR
示例:
//test.yml变量配置
---
- hosts: 192.168.200.136
gather_facts: no
tasks:
- name: test
yum:
name: httpd
state: present
register: install_result
- debug: var=install_result
//打印输出
[root@localhost project]# ansible-playbook testsss.yml
PLAY [192.168.200.136] *************************************************************
TASK [test] ************************************************************************
changed: [192.168.200.136]
TASK [debug] ***********************************************************************
ok: [192.168.200.136] => {
"install_result": {
"ansible_facts": {
"discovered_interpreter_python": "/usr/libexec/platform-python"
},
"changed": true,
"failed": false,
"msg": "",
"rc": 0,
"results": [
"Installed: mod_http2-1.15.7-3.module_el8.4.0+778+c970deab.x86_64",
"Installed: apr-1.6.3-11.el8.x86_64",
"Installed: apr-util-1.6.1-6.el8.x86_64",
"Installed: apr-util-bdb-1.6.1-6.el8.x86_64",
"Installed: apr-util-openssl-1.6.1-6.el8.x86_64",
"Installed: httpd-2.4.37-39.module_el8.4.0+778+c970deab.x86_64",
"Installed: httpd-filesystem-2.4.37-39.module_el8.4.0+778+c970deab.noarch",
"Installed: centos-logos-httpd-85.8-1.el8.noarch",
"Installed: httpd-tools-2.4.37-39.module_el8.4.0+778+c970deab.x86_64"
]
}
}
PLAY RECAP *************************************************************************
192.168.200.136 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
管理机密
Ansible可能需要访问密码或API密钥等敏感数据,以便能配置受管主机。通常,此信息可能以纯文本形式存储在清单变量或其他Ansible文件中。但若如此,任何有权访问Ansible文件的用户或存储这些Ansible文件的版本控制系统都能够访问此敏感数据。这显示存在安全风险。
Ansible提供的Ansible Vault可以加密和解密任何由Ansible使用的结构化数据文件。若要使用Ansible Vault,可通过一个名为ansible-vault的命令行工具创建、编辑、加密、解密和查看文件。Ansible Vault可以加密任何由Ansible使用的结构化数据文件。这可能包括清单变量、playbook中含有的变量文件、在执行playbook时作为参数传递的变量文件,或者Ansible角色中定义的变量。
要创建新的加密文件,可使用ansible-vault create filename命令。该命令将提示输入新的vault密码,然后利用默认编辑器vi打开文件。我们可以设置和导出EDITOR环境变量,通过设置和导出指定其他默认编辑器。例如,若要将默认编辑器设为nano,可设置为export EDITOR=nano。
示例:
//创建加密文件
[root@localhost project]# ansible-vault create group_vars/cai.yml
New Vault password:
Confirm New Vault password:
[root@localhost project]#
//在配置文件里写入自己设置的密码
password=123@.com
//用cat查看
[root@localhost project]# cat group_vars/cai.yml
$ANSIBLE_VAULT;1.1;AES256
64626134613065333066303266366438653134663561353637333138656265613131376430343364
3238653238376536663034396630303933643239386136660a666565396231353731353466616162
65633230393232356632653839346537303561323738393461323734656232353730373534363733
3535636662653533350a663739383630393232623765373539656238356162353963396231666263
61393966653236336465376465653366376138323164376537393666623963623433
用vault密码文件来存储vault密码,而不是通过标准输入途径输入vault密码。这样做需要使用文件权限和其他方式来严密保护该文件。
//先创建一个密码文件,然后在里面设置密码
[root@localhost project]# vim .pass
[root@localhost project]# cat .pass
asfknslfalsvkam lsng;av l adl;nf;as x,cakfnlkasfa
//用密码文件来指定密码加密
[root@localhost project]# ansible-vault create --vault-password-file=.pass group_vars/webservers.yml
查看加密文件
以使用ansible-vault view filename命令查看Ansible Vault加密的文件,而不必打开它进行编辑,但是查看时需要输入加密文件的加密密码。
[root@localhost project]# ansible-vault view group_vars/webservers.yml
Vault password:
port: 8080
[root@localhost project]#
//还可以用此命令来查看加密文件,查看时不需要密码
[root@localhost project]# ansible-vault view --vault-password-file=.pass group_vars/webservers.yml
port: 8080
编辑现有的加密文件
要编辑现有的加密文件,Ansible Vault提供了ansible-vault edit filename命令。此命令将文件解密为一个临时文件,并允许编辑。保存时,它将复制其内容并删除临时文件。
//可以看到原先的8080改成了8888
[root@localhost project]# ansible-vault edit --vault-password-file=.pass group_vars/webservers.yml
[root@localhost project]# ansible-vault view --vault-password-file=.pass group_vars/webservers.yml
port: 8888
[root@localhost project]#
加密现有的文件
要加密已存在的文件,请使用ansible-vault encrypt filename命令。此命令可取多个欲加密文件的名称作为参数。
[root@localhost project]# ansible-vault encrypt --vault-password-file=.pass inventory
Encryption successful
[root@localhost project]# cat inventory
$ANSIBLE_VAULT;1.1;AES256
38316264653861623735356636393938326239393435646532393262343461616130396162306532
3561303937363261303463633164666539326237616265650a373537323531316532363531316539
66303939343235373265633835623662663665383361343461343763333761353433653833656238
6363633833306638610a613735366534386166353033356561393766306463656636656139383265
66306664656362303839386435616463353336656339396666303431326630386336663432633339
63353731383266303636383739653538383863653234613537666136373064393635623762383337
33356136653562646563663835363339643638343561356433336137643836616238613832306435
35633338626638333135366163643265363561613539396639383837313635313135353962613561
65396439336633643865353166616438303465653864333466373766653838386636653630373036
30393335353133386563316235366539303436353135343866663161633939623732623462666433
63336630616363303630306636356138393433353665613638333239656134386262623336326362
35323161313133313765323665336236383261646661343064643137323635366339363137303332
3461
[root@localhost project]#
解密现有的文件
解密现有的文件现有的加密文件可以通过ansible-vault decrypt filename命令永久解密。在解密单个文件时,可使用–output选项以其他名称保存解密的文件。
Decryption successful
[root@localhost project]# cat inventory
[webserver]
192.168.200.129 ansible_user=root ansible_password=1
192.168.200.130 ansible_user=root ansible_password=1
192.168.200.136 ansible_user=root ansible_password=1
[root@localhost project]#
更改加密文件的密码
使用ansible-vault rekey filename命令更改加密文件的密码。此命令可一次性更新多个数据文件的密钥。它将提示提供原始密码和新密码。
New Vault password:
Confirm New Vault password:
Rekey successful
[root@localhost project]#
在使用vault密码文件时,可以使用–new-vault-password-file选项:
先创建一个文件,在里面写入新密码
[root@localhost project]# touch abc
[root@localhost project]# ls
abc ansible.cfg cai group_vars host_vars inventory testsss.yml
[root@localhost project]# echo "123qwe" > abc
[root@localhost project]# ansible-vault rekey --new-vault-password-file=abc group_vars/webservers.yml
Vault password:
Rekey successful
[root@localhost project]#
playbook和ansible vault详用
要运行通过Ansible Vault加密的文件的playbook,需要向ansible-playbook命令提供加密密码。如果不提供密码,playbook将返回错误:
[root@localhost project]# ansible-playbook apache.yml
ERROR! Attempting to decrypt but no vault secrets found
[root@localhost project]#
要为playbook提供vault密码,可使用–vault-id选项。例如,要以交互方式提供vault密码,请使用下例中所示的–vault-id @prompt:
[root@localhost project]# ansible-playbook --vault-id @prompt apache.yml
Vault password (default):
PLAY [apache] **********************************************************************
TASK [Gathering Facts] *************************************************************
ok: [192.168.200.139]
此外,也可使用–vault-password-file选项指定以纯文本存储加密密码的文件。密码应当在该文件中存储为一行字符串。由于该文件包含敏感的纯文本密码,因此务必要通过文件权限和其他安全措施对其加以保护。
[root@localhost project]# ansible-playbook --vault-password-file=.pass apache.yml
PLAY [apache] **********************************************************************
TASK [Gathering Facts] *************************************************************
ok: [192.168.200.139]
也可以使用ANSIBLE_VAULT_PASSWORD_FILE环境变量,指定密码文件的默认位置。
[root@localhost project]# export ANSIBLE_VAULT_PASSWORD_FILE=.pass
[root@localhost project]# ansible-playbook apache.yml
PLAY [apache] **********************************************************************
TASK [Gathering Facts] *************************************************************
ok: [192.168.200.139]
变量文件管理的推荐做法
若要简化管理,务必要设置Ansible项目,使敏感变量和其他变量保存在相互独立的文件中。然后,包含敏感变量的文件可通过ansible-vault命令进行保护。
除了使用group_vars和host_vars中的文件外,也可对每一主机组或受管主机使用目录。这些目录可包含多个变量文件,它们都由该主机组或受管主机使用。例如,在playbook.yml的以下项目目录中,webservers的主机组的成员将使用:
.
├── ansible.cfg
├── group_vars
│ └── firewalld
│ └── vars
├── host_vars
│ └── 192.168.200.139
│ ├── apache.yml
│ └── vault
├── inventory
└── playbook.yml
管理实事
描述Ansible事实
Ansible事实是Ansible在受管主机上自动检测到的变量。事实中包含有与主机相关的信息,可以像play中的常规变量、条件、循环或依赖于从受管主机收集的值的任何其他语句那样使用。
实事可以包括:主机名称
内核版本
网络接口
IP地址
操作系统版本
各种环境变量
CPU数量
提供的或可用的内存
可用磁盘空间
借助事实,可以方便地检索受管主机的状态,并根据该状态确定要执行的操作。
例如:
可以根据含有受管主机当前内核版本的事实运行条件任务,以此来重启服务器
可以根据通过事实报告的可用内存来自定义MySQL配置文件
可以根据事实的值设置配置文件中使用的IPv4地址
通常,每个play在执行第一个任务之前会先自动运行setup模块来收集事实
查看为受管主机收集的事实的一种方式是,运行一个收集事实并使用debug模块显示ansible_facts变量值的简短playbook。
示例:
//playbook文件
---
- hosts: apache
tasks:
- name: test
debug:
var: ansible_facts
//执行结果
[root@localhost project]# ansible-playbook test.yml
PLAY [apache] **********************************************************************
TASK [Gathering Facts] *************************************************************
ok: [192.168.200.139]
TASK [test] ************************************************************************
ok: [192.168.200.139] => {
"ansible_facts": {
"all_ipv4_addresses": [
"192.168.200.139",
"192.168.122.1"
此处省略n行
Ansible事实的示例
事实 | 变量 |
---|---|
所有网络接口的名称列表 | ansible_facts[‘interfaces’] |
短主机名 | ansible_facts[‘hostname’] |
完全限定域名 | ansible_facts[‘fqdn’] |
IPv4地址 | ansible_facts[‘default_ipv4’] |
/dev/vda1磁盘分区的大小 | ansible_facts[‘devices’][‘vda’][‘partitions’][‘vda1’][‘size’] |
当前运行的内核版本 | ansible_facts[‘kernel’] |
DNS服务器列表 | ansible_facts[‘dns’][‘nameservers’] |
如果变量的值为散列/字典类型,则可使用两种语法来获取其值。
比如:
ansible_facts[‘default_ipv4’][‘address’]也可以写成ansible_facts.default_ipv4.address
ansible_facts[‘dns’][‘nameservers’]也可以写成ansible_facts.dns.nameservers
在playbook中使用事实时,Ansible将事实的变量名动态替换为对应的值:
---
- hosts: apache
tasks:
- name: test
debug:
msg: the ip address of {{ ansible_facts.fqdn }} is {{ ansible_facts.all_ipv4_address }}
//执行playbook,
[root@localhost project]# ansible-playbook test.yml
PLAY [apache] **********************************************************************
TASK [Gathering Facts] *************************************************************
ok: [192.168.200.139]
TASK [test] ************************************************************************
ok: [192.168.200.139] => {
"msg": "the ip address of hzz-client is [u'192.168.200.139', u'192.168.122.1']"
}
PLAY RECAP *************************************************************************
192.168.200.139 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Ansible事实作为变量注入
在Ansible2.5之前,事实是作为前缀为字符串ansible_的单个变量注入,而不是作为ansible_facts变量的一部分注入。例如,ansible_facts[‘distribution’]事实会被称为ansible_distribution。
许多较旧的playbook仍然使用作为变量注入的事实,而不是在ansible_facts变量下创建命名空间的新语法。我们可以使用临时命令来运行setup模块,以此形式显示所有事实的值。
示例:
[root@localhost project]# ansible all -m setup
192.168.200.139 | SUCCESS => {
"ansible_facts": {
"ansible_all_ipv4_addresses": [
"192.168.200.139",
"192.168.122.1"
],
"ansible_all_ipv6_addresses": [
"fe80::2451:a286:970:dcd7"
],
"ansible_apparmor": {
"status": "disabled"
},
"ansible_architecture": "x86_64",
"ansible_bios_date": "07/22/2020",
"ansible_bios_version": "6.00",
"ansible_cmdline": {
"BOOT_IMAGE": "(hd0,msdos1)/vmlinuz-4.18.0-193.el8.x86_64",
"crashkernel": "auto",
"quiet": true,
"rd.lvm.lv": "rhel/swap",
"resume": "/dev/mapper/rhel-swap",
"rhgb": true,
"ro": true,
"root": "/dev/mapper/rhel-root"
},
"ansible_date_time": {
"date": "2021-07-24",
"day": "24",
"epoch": "1627137945",
"hour": "22",
"iso8601": "2021-07-24T14:45:45Z",
"iso8601_basic": "20210724T224545756140",
"iso8601_basic_short": "20210724T224545",
"iso8601_micro": "2021-07-24T14:45:45.756140Z",
"minute": "45",
"month": "07",
"second": "45",
"time": "22:45:45",
"tz": "CST",
"tz_offset": "+0800",
"weekday": "星期六",
"weekday_number": "6",
"weeknumber": "29",
"year": "2021"
},
"ansible_default_ipv4": {
"address": "192.168.200.139",
"alias": "ens160",
"broadcast": "192.168.200.255",
"gateway": "192.168.200.2",
"interface": "ens160",
"macaddress": "00:0c:29:9f:84:f0",
"mtu": 1500,
"netmask": "255.255.255.0",
"network": "192.168.200.0",
"type": "ether"
},
"ansible_default_ipv6": {},
"ansible_device_links": {
"ids": {
"dm-0": [
关闭事实收集
有时我们不想为play收集事实。这样做的原因可能有:
不准备使用任何事实
希望加快play速度
希望减小play在受管主机上造成的负载
受管主机因为某种原因无法运行setup模块
需要安装一些必备软件后再收集事实
以上种种原因导致我们可能想要永久或暂时关闭事实收集的功能,要为play禁用事实收集功能,可将gather_facts关键字设置为no:
---
- name: test
hosts: apache
gather_facts: no
tasks:
即使play设置了gather_facts: no,也可以随时通过运行使用setup模块的任务来手动收集事实:
name: test
hosts: apache
gather_facts: no
tasks:
- name: user
user:
- name: debug
debug:
var: ansible_facts
创建自定义事实
除了使用系统捕获的事实外,我们还可以自定义事实,并将其本地存储在每个受管主机上。这些事实整合到setup模块在受管主机上运行时收集的标准事实列表中。它们让受管主机能够向Ansible提供任意变量,以用于调整play的行为。
自定义事实可以在静态文件中定义,格式可为INI文件或采用JSON。它们也可以是生成JSON输出的可执行脚本,如同动态清单脚本一样。
默认情况下,setup模块从各受管主机的/etc/ansible/facts.d目录下的文件和脚本中加载自定义事实。各个文件或脚本的名称必须以.fact结尾才能被使用。动态自定义事实脚本必须输出JSON格式的事实,而且必须是可执行文件。JSON数据可以存储在静态文本文件中,或者通过可执行脚本输出到标准输出
以下是采用INI格式编写的静态自定义事实文件示例:
//在受控主机上编写
mkdir -p /etc/ansible/facts.d
[root@localhost facts.d]# vim test.fact
[packages]
web_package = httpd
db_package = mariadb-server
[users]
user1 = joe
user2 = jane
在主控主机上执行命令,然后可以查找到users和package
[root@localhost project]# ansible apache -m setup|less
"users": {
"user1": "joe",
"user2": "jane"
}
}
},
"ansible_lsb": {},
"ansible_lvm": {
"lvs": {
"root": {
"size_g": "17.00",
"vg": "rhel"
},
"swap": {
"size_g": "2.00",
: