Ansible 使用变量
Ansible 可以通过变量来提高 Playbook 的编写效率。例如:通过 Ansible 安装软件包,可以将软件包写入到一个变量,然后在 Playbook 中调用变量,这样在修改需要安装的软件包时,不需要修改 Playbook,只需要修改变量的值就可以。
Ansible 也有一些自带的变量。可以看后边的事实变量和魔法变量。
设置变量
设置变量有多种方式,最简单的是在 Playbook 上设置:
- name: var
hosts: all
vars:
list_var:
- servera
- serverb
dict_var:
servera_info:
username: root
password: redhat
serverb_info:
username: admin
password: redhat
list_var
是列表变量,dict_var
是字典变量。
上边是设置全局变量,也可以在单个 tasks 下设置:
- name: print var
ansible.builtin.debug:
var: test_var
vars:
test_var: "Hello World!"
也可以用文件设置,假设 Playbook 所在目录有一个文件 vars/vars.yml
:
- name: var
hosts: all
vars_files:
- vars/vars.yml
vars/vars.yml
文件内容:
list_var2:
- var1
- var2
dict_var2:
ssh_username: root
ssh_password: redhat
也可以在命令行设置变量:
[root@study ansible]# ansible localhost -m debug -a 'var=username' \
--extra-vars '{"username":"root","password":"redhat"}'
localhost | SUCCESS => {
"username": "root"
}
# 如果变量涉及很多特殊符号,建议通过文件设置并引用
[root@study ansible]# cat vars/vars.yml
list_var2:
- var1
- var2
dict_var2:
ssh_username: root
ssh_password: redhat
[root@study ansible]# ansible localhost -m debug -a 'var=list_var2' \
-e '@vars/vars.yml'
localhost | SUCCESS => {
"list_var2": [
"var1",
"var2"
]
}
引用变量
在 Playbook 中使用变量
引用变量只需要用 { }
将变量扩起来,以下是利用已有变量设置新变量的 Playbook:
vars:
list_var:
- servera
- serverb
dict_var:
servera_info:
username: root
password: redhat
serverb_info:
username: admin
password: redhat
servera_ssh_info: "username: {{ dict_var.servera_info.username }}, password: {{ dict_var.servera_info.password }}"
打印变量
可以通过 ansible.builtin.debug
打印变量:
tasks:
- name: print var
ansible.builtin.debug:
var: list_var[1]
- name: print var
ansible.builtin.debug:
var: dict_var['servera_info']
list_var[1]
是打印列表变量的第二项,dict_var['servera_info']
打印 dict_var
变量中 KEY 为 servera_info
的字典变量。
字典变量引用的时候支持嵌套变量,
dict_var['servera_info']
里的单引号表示server_info
是一个普通字符串。如果不加单引号(dict_var[servera_info]
),servera_info
表示一个变量名,实际引用的是会有所区别,详见下边示例。
这是一个变量嵌套的例子:
- name: var
hosts: all
gather_facts: false
vars:
servera_info: servera
dict_var:
servera:
username: redhat
password: redhat
tasks:
- name: print var
ansible.builtin.debug:
var: dict_var[servera_info]
servera_info
的值是 servera
,然后通过 dict_var[servera_info]
获取 dict_var['servera']
变量的值。
注册变量
和设置变量不同,注册变量是将模块的执行结果注册到一个变量中,以便后续引用。
注册变量使用 register
。
下边是一个注册变量的例子:
- name: register
hosts: localhost
gather_facts: false
tasks:
- name: register
ansible.builtin.command: ls /tmp
register: get_tmp
changed_when: false
- name: print register var
ansible.builtin.debug:
var: get_tmp
- name: if testfile not in /tmp, create /tmp/testfile
ansible.builtin.file:
path: /tmp/testfile
state: touch
when: "'testfile' not in get_tmp.stdout_lines"
这个 Playbook 的第一个 tasks 会在被控节点执行 ls /tmp
,并将结果注册到名为 get_tmp
的变量中,第二个 tasks 可以输出 get_tmp
变量的内容,第三个 tasks 回去判断 /tmp
下是否有 testfile
文件,如果没有就创建,有就跳过这个 tasks。
通过 Jinja2 引用变量
Jinja2 是一个 Python 的模板引擎,用于在文本中插入动态内容,最常用于 生成 HTML、配置文件、YAML、Shell 脚本等。它常用于跟 ansible.builtin.template
配合。
举个例子,配置 HTTP 服务器。如果 HTTP 服务器只是单节点提供服务,那么监听端口就是 80 端口,如果是多节点做负载均衡,那么监听端口设置为 8080,如果都不符合就使用 8443。如果使用 Jinja2 的话就可以写一个判断,内容如下(片段):
{% if NODE_TYPE == 'SINGLE' %}
Listen 80
{% elif NODE_TYPE == 'LOAD_BALANCING' %}
Listen 8080
{% else %}
Listen 8443
{% endif %}
这里的
NODE_TYPE
是个变量,需要在主机清单或者 Playbook 里设置。
上边使用的是判断,Jinja2 还支持循环和变量。下边写一个循环的例子:
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{% for host in groups['all'] %}
{{ hostvars[host]['ansible_ssh_host'] }} {{ hostvars[host]['ansible_facts']['fqdn'] }} {{ hostvars[host]['ansible_facts']['hostname'] }}
{% endfor %}
这里边不光使用了循环,还是用了变量,Jinja2 支持使用 Ansible 的事实变量、魔法变量和自定义变量。
上边这个 Jinja2 文件复制过去实现的效果如下:
[root@ansible-controller ansible-navigator]# cat templates/hosts.j2
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{% for host in groups['all'] %}
{{ hostvars[host]['ansible_ssh_host'] }} {{ hostvars[host]['ansible_facts']['fqdn'] }} {{ hostvars[host]['ansible_facts']['hostname'] }}
{% endfor %}
[root@ansible-controller ansible-navigator]# cat template.yml
---
- name: template
hosts: all
tasks:
- name: template
ansible.builtin.template:
src: ./templates/hosts.j2
dest: /etc/hosts
[root@ansible-controller ansible-navigator]# ansible-navigator run --ep template.yml -- -b -K
BECOME password:
...outpu omitted...
[root@ansible-controller ansible-navigator]# ansible-navigator exec -- ansible all -m command -a 'cat /etc/hosts'
master1 | CHANGED | rc=0 >>
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.221.142 master1.example.com master1
192.168.221.143 worker1.example.com worker1
worker1 | CHANGED | rc=0 >>
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.221.142 master1.example.com master1
192.168.221.143 worker1.example.com worker1
创建 Jinja2 文件时,建议结尾使用
.j2
后缀。(如hosts.j2
)
Ansible 事实变量
查看 Ansible 事实变量
Ansible 可以收集被控主机的信息并将其注册为事实变量,变量名为 ansible_facts
,可以通过 ansible.builtin.debug
模块打印被控主机的事实变量:
- name: Print all available facts
ansible.builtin.debug:
var: ansible_facts
Ansible 事实变量能够查看很多信息:
- 硬件:CPU、内存、主板等
- 软件:地址、主机名、文件系统挂载等
Ansible 事实变量输出参考:https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_vars_facts.html#ansible-facts。
除了通过
ansible_facts
获取事实变量以外,还可以通过hostvars['servera']['ansible_facts']
的方式获取特定主机的事实变量。这个方法只能在 Playbook 中使用。
缓存 Ansible 事实变量
Ansible 默认在执行 Playbook 时收集事实变量并保存在内存中,Playbook 执行结束后清理收集的事实变量,可以将变量缓存到一个文件或特定数据库中,这里介绍两种缓存方式:
- 缓存到文件
- 缓存到 Redis
缓存到文件
修改 Ansible 配置文件的如下几行
# (string) Chooses which cache plugin to use, the default 'memory' is ephemeral.
fact_caching=jsonfile
# (string) Defines connection or path information for the cache plugin.
fact_caching_connection=./cache
# (string) Prefix to use for cache plugin files/tables.
fact_caching_prefix=ansible_facts_
# (integer) Expiration timeout for the cache plugin data.
fact_caching_timeout=86400
这几行表示将事实变量以 JSON 的方式缓存到文件中,文件保存到 ./cache
目录中,文件名以 ansible_facts_
为前缀,缓存有效时间 86400 秒。
这里说下缓存过期,如果是缓存到文件,Ansible 在执行 Playbook 的时候会去判断缓存是否在有效期内,不在有效期内就不会使用缓存。
测试缓存:
[root@study ansible]# ls cache/
ls: cannot access 'cache/': No such file or directory
[root@study ansible]# ansible localhost -m setup &> /dev/null
[root@study ansible]# ls cache/
ansible_facts_localhost
[root@study ansible]# cat ansible_facts_cache.yml
- name: test ansible facts cache
hosts: localhost
gather_facts: false
tasks:
- name: Print ansible facts
ansible.builtin.debug:
var: ansible_facts['hostname']
[root@study ansible]# ansible-playbook ansible_facts_cache.yml
PLAY [test ansible facts cache] ***********************************************************************************************************************************
TASK [Print ansible facts] ****************************************************************************************************************************************
ok: [localhost] => {
"ansible_facts['hostname']": "study"
}
PLAY RECAP ********************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
缓存到 Redis
要将事实变量缓存到 Redis 需要安装 community.general
集合,执行 ansible-galaxy collection install community.general
安装集合。
查看新装的缓存插件:
[root@study ansible]# ansible-doc -t cache -l
ansible.builtin.jsonfile JSON formatted files
ansible.builtin.memory RAM backed, non persistent
community.general.memcached Use memcached DB for cache
community.general.pickle Pickle formatted files
community.general.redis Use Redis DB for cache
community.general.yaml YAML formatted files
安装并启动 Redis:
# 安装 Redis
[root@study ansible]# dnf install -y redis
# 启动 Redis
[root@study ansible]# systemctl start redis
[root@study ansible]# ss -ntlp | grep 6379
LISTEN 0 511 127.0.0.1:6379 0.0.0.0:* users:(("redis-server",pid=45313,fd=6))
# 检查 Redis 数据
[root@study ansible]# redis-cli
127.0.0.1:6379> KEYS *
(empty list or set)
127.0.0.1:6379> exit
谨慎使用
KEYS *
。
修改 Ansible 配置文件:
# (string) Chooses which cache plugin to use, the default 'memory' is ephemeral.
fact_caching=redis
# (string) Defines connection or path information for the cache plugin.
fact_caching_connection=127.0.0.1:6379:0
# (string) Prefix to use for cache plugin files/tables.
fact_caching_prefix=ansible_facts_
# (integer) Expiration timeout for the cache plugin data.
fact_caching_timeout=86400
这里到期的话 Redis 里的信息就会删除。
收集事实变量,检查缓存:
[root@study ansible]# ansible localhost -m setup &> /dev/null
[root@study ansible]# redis-cli
127.0.0.1:6379> KEYS *
1) "ansible_facts_localhost"
2) "ansible_cache_keys"
127.0.0.1:6379> exit
[root@study ansible]#
这里多说一下,因为 Redis 可以设置密码,也支持加密,所以 fact_caching_connection
的配置可以去查看 community.general.redis
的帮助文档:
[root@study ansible]# ansible-doc -t cache community.general.redis
...output omitted...
= _uri
A colon separated string of connection information for Redis.
The format is `host:port:db:password', for example `localhost:6379:0:changeme'.
To use encryption in transit, prefix the connection with `tls://', as in `tls://localhost:6379:0:changeme'.
To use redis sentinel, use separator `;', for example `localhost:26379;localhost:26379;0:changeme'. Requires redis>=2.9.0.
set_via:
env:
- name: ANSIBLE_CACHE_PLUGIN_CONNECTION
ini:
- key: fact_caching_connection
section: defaults
type: string
禁用 Ansible 事实变量
Ansible 在执行 Playbook 时默认收集事实变量,如果明确不使用事实变量,可以通过 gather_facts: false
禁用事实变量收集来提高执行效率。
- hosts: whatever
gather_facts: false
添加自定义事实
在被控节点的 /etc/ansible/facts.d
目录下可以添加自定义事实变量。
下边是自定义变量的定义和查看:
[root@study ansible]# cat /etc/ansible/facts.d/local_var.fact
[webserver_vars]
linux = true
webserver_port = 80
dbserver_port = 3306
[root@study ansible]# ansible localhost -m setup -a 'filter=ansible_local'
localhost | SUCCESS => {
"ansible_facts": {
"ansible_local": {
"local_var": {
"webserver_vars": {
"dbserver_port": "3306",
"linux": "true",
"webserver_port": "80"
}
}
}
},
"changed": false
}
local_var
、webserver_vars
、webserver_port
都对应不同的 KEY 的名字。
下边是调用本地事实的方式:
- name: Print custom facts
ansible.builtin.debug:
var: ansible_facts.ansible_local
事实变量的 KEY 会将大写的 KEY 名转化成小写的,比方说
WebServer_Port = 80
会转化成webserver_port = 80
。
Ansible 魔法变量
调用魔法变量
和事实变量不同,魔法变量不需要去被控主机收集信息,它主要有两个来源:
- 通过主机清单设置的变量,变量可以是设置的新变量(就是在主机清单中添加的自定义变量),也可以是 Anisble 的内置变量(连接变量:
ansible_ssh_user
和ansible_ssh_password
) - Playbook 执行过程中附加的变量
以下是一些常用魔法变量:
hostvars
groups
group_names
inventory_hostname
上边这几个变量其实都包含在 hostvars
,可以用 ansible.builtin.debug
模块查看(ansible localhost -m debug -a 'var=hostvars'
)
以下是一些魔法变量的使用方法:
获取特定主机的变量信息
{{ hostvars['test.example.com']['ansible_facts']['distribution'] }}
循环主机组
{% for host in groups['app_servers'] %}
{{ hostvars[host]['ansible_facts']['eth0']['ipv4']['address'] }}
{% endfor %}
判断主机 servera 在 webserver 组内
{% if 'servera' in groups['webserver'] %}
{{ hostvars[host]['ansible_facts']['eth0']['ipv4']['address'] }}
{% endif %}
除了 hostvars
看到的变量之外,还有如下几个变量:
ansible_play_hosts
ansible_play_batch
role_path
(包含当前角色的路径名,并且仅在角色内工作。)
调用魔法变量的案例
现在假设有一批主机(主机名和 IP 地址已配置,IP 地址统一配置在 eth0 网卡),那么就可以通过如下 Playbook 配置 /etc/hosts
文件:
[root@study ansible]# cat set_hosts.yml
- name: set all hosts
hosts: all
gather_facts: true
tasks:
- name: Configure the hosts file with the template
ansible.builtin.template:
src: ./templates/hosts.j2
dest: /etc/hosts
owner: root
group: root
mode: '0644'
[root@study ansible]# cat templates/hosts.j2
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
{% for host in groups['all'] %}
{{ hostvars[host]['ansible_facts']['eth0']['ipv4']['address'] }} {{ hostvars[host]['ansible_facts']['hostname'] }}
{% endfor %}
这个是通过 JinJa2
模板和 ansible.builtin.template
模块来配置 /etc/hosts
。
./templates/hosts.j2
文件是JinJa2
模板,它循环all
主机组,将组内所有主机 eth0 网卡的 IP 地址和主机名按行打印出来- 然后 Ansible 通过
ansible.builtin.template
模块来调用这个JinJa2
模板对all
主机组执行。