例如:host_port、HOST_PORT、var5是符合命名规范的,foo-port、 foo port、foo.port 、12都不符合命名规范。
变量的定义通常是YAML形式,在inventory host文件中也可以使用INI形式。
ansible变量不仅可以支持简单的key=value格式,而且也支持更复杂数据结构,例如字典类型等。
二 变量的作用域
变量的作用域可以分为四种:
作用于全局的变量
作用于play的变量
作用于task的变量
作用于host的变量
接下来我们根据变量的作用域,详细分析下ansible变量的定义、使用和调用顺序。
三 作用于全局的变量
3.1 配置文件变量
ansible配置文件会定义一些变量信息,主要是对执行环境、连接信息变量的定义。
例如inventory目录、library目录、与目的主机连接方式、越权信息、连接超时时间等等。
3.2 系统环境变量
在ansible连接到目的主机时,会以non-login shell登陆到目的主机,此时目的主机的/etc/bashrc和~/.basrc的环境变量会被加载,所以这两个文件中设置的环境变量会作用于playbook全局。
3.3 命令行变量
我们可以在执行playbook的命令行指定变量,需要注意的是,
命令行指定的变量在所有其他变量中优先级是最高的。
也就是说如果命令行指定的变量和其他地方指定的变量有冲突时,那么ansible最终会采用命令行定义的变量。
命令行指定变量示例如下:
ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo"
四 作用于play的变量
4.1 playbook中的变量
vars语句定义全局变量
我们可以在playbook中使用「vars」语句定义变量,该变量作用于整个play。
# inventory/playbooks/
test.yaml
-
hosts: node1
vars:
http_port:
80
上面示例中中「http_port」是一个作用于整个play的变量,对这个play里的tasks、roles、import、include等等之下定义的task均生效。
引用变量文件
除了将变量写在playbook中,我们也可以将变量放在一个单独的YAML文件中,通过「vars_files」语句来导入。
「vars_files」变量只能作用于play全局,不能在某个task中单独被引用。「vars_files」参数可以使用系统绝对路径或playbook文件的相对路径。
举个🌰:
我们在playbooks目录下创建一个vars-files.yaml文件:
# playbooks/vars-
files.yaml
age:
100
在playbook中使用vars_files语句引用该变量文件:
# playbooks/
test.yaml
-
hosts: node1
vars_files: .
/vars-
files.yaml
tasks:
-
debug:
msg:
"
My age is {{ age }}
"
4.2 roles中的变量
4.2.1 default变量
default变量位于roles/defaults/main.yml文件中,该变量作用于role里的所有play,通常作为模版或模块里的默认参数。
default变量与ansible filter变量 「{{ some_variable | default("some_value") }}」具有同样的作用,在所有ansible变量中优先级最低。
4.2.2 dependencies变量
dependencies变量位于roles/meta/main.yaml文件中,该变量与「role」语句同级缩进,作用于本身的role和dependencies role。
举个🌰:
role_A 和 role_B定义了相同的task,debug出「age」变量:
# tasks
file
for
role_B
-
debug:
var: age
# tasks
file
for
role_B
-
debug:
var: age
role_A/meta/main.yaml定义role_A依赖role_B,并指定「age」变量等于26:
dependencies:
-
role: role_B
vars:
age:
27
写playbook,引用role_A:
# playbooks/
test.yaml
-
hosts: node1
roles:
- role: role_A
执行结果如下:
➜ lab-ansible ansible-playbook playbooks/test.yaml
PLAY [node1] ********************************************************************************************
TASK [Gathering Facts] **********************************************************************************
ok: [node1]
TASK [role_B : debug] ***********************************************************************************
ok: [node1] => {
"age": 27
TASK [role_A : debug] ***********************************************************************************
ok: [node1] => {
"age": 27
PLAY RECAP **********************************************************************************************
node1 : ok=3 changed=0 unreachable=0 failed=0
输出结果显示,dependencise变量「age」在role_A和role_B均生效。
4.2.3 vars变量
vars变量位于roles/vars/main.yml,该变量作用于role里的所有模块。通常将除了默认变量的其他的变量放在这个文件内。
4.3 register变量
register方法能够将一个task的执行结果注册为一个变量。书写格式要与模块名称对齐,该变量作用于整个play。
通常register变量和when语句联合使用,以达到满足某些条件才运行task的目的。
五 作用于task的变量
5.1 playbook中的变量
with modules
我们可以为某个模块定义变量,该变量作用于这个task。
举个🌰,示例中为「debug」模块定义了「name」和「age」变量并在「msg」参数后使用了这两个变量:
# playbooks/
test.yaml
-
hosts: node1
tasks:
-
debug:
msg:
"
My name is {{ name }} and I'm {{ age }} years old
"
vars:
name: Maurice
age:
27
with import*/include*
在使用import_playbook、import_tasks、include_tasks、import_role、include_role时可以在import*/include*的同级位置指定变量,该变量作用于导入的所有play。
使用import_role举个🌰:
playbook导入role_A,并定义变量「age」,这样role_A内的play就可以使用「age」变量了:
# playbooks/
test.yaml
-
hosts: node1
tasks:
-
import_role:
name: role_A
vars:
age:
1000
其他import*/include*的语句使用方法类似,只要记住
缩进与import*/include*语句保持一致
即可。
with roles
在playbook中使用roles语句来导入role时也可以定义变量,该变量作用于role包含的所有play。
举个🌰:
playbook使用roles语句导入role_A,并定义变量「age」:
# playbooks/
test.yaml
-
hosts: node1
roles:
-
role: role_A
vars:
age:
1000
通过上面两个示例我们发现,roles和import_role语句定义变量写法上很相似,其实import_role和include_role是新版本的语法,功能上完全可以代替roles语句。如果你使用的ansible版本>=2.4,建议
使用include_role和import_role
语句。
5.2 roles中的变量
指的是在tasks/main.yaml或handlers/main.yaml内书写task时指定的变量,该变量作用于某个task,这个变量类型和上述章节中「5.1playbook中的变量—with modules」类似,这里就不再举例说明。
六 作用于host的变量
6.1 系统变量Facts
6.1.1 facts变量
ansible中有个特殊的变量,这些变量不是开发者定义的,而是ansible根据目的主机环境信息自动收集的,称之为fact变量。
fact变量很实用,和「when」语句配合使用会让你的代码更加健壮。
举个🌰,如果当前的操作系统为「RedHat」类型,则通过yum安装需要的软件包:
# playbooks/
test.yaml
-
hosts: node1
tasks:
-
yum
:
name: firewalld
state: present
when: ansible_os_family
==
'
RedHat
'
6.1.2 facts缓存
在执行playbook时,我们发现在「Gathering Facts」步骤时总会卡住一会,如果定义的play多了,会非常耗时。其实这步就是ansible在收集目的主机的facts信息。
如果我们定义的playbook中并没有使用到fact变量,那么我们可以选择将其关闭,只需添加「gather_facts: false」即可。
如果必须要使用facts信息,我们可以将fact信息缓存到redis服务或本地json文件中,这样当我们第二次执行playbook时,ansible就会读取缓存信息,从而加快运行速度。
假设本地redis服务正常运行,我们只需更改ansible配置文件即可达到缓存fact的目的。
redis缓存:
[defaults]
gathering = smart
fact_caching = redis
fact_caching_timeout = 86400
json文件缓存:
[defaults]
gathering = smart
fact_caching = jsonfile
fact_caching_connection = /path/to/cachedir
fact_caching_timeout = 86400
6.2 inventory中的变量
6.2.1 主机变量
关于主机和主机组变量,我们在「
ansible基础-安装与配置
:3.1 主机与主机组」中有介绍,当时介绍了组和主机变量的定义方法、变量的分离、优先级等知识点。其实主机变量的知识点不复杂,这里做下总结。主机变量是指作用在某一台主机上的变量。位置可以与主机定义写在一起也可以写在inventory/host_vars/a_host_name.yaml文件里。通常前者使用使用INI格式,后者使用YAML格式。这里要注意一下YAML的语法,在「:」后面要留有一个空格。如果组变量和主机变量都对同一个主机定义了相同的变量,那么
ansible最终会采用主机变量而放弃组变量
。主机变量示例:INI格式:
# inventory/hosts
[nodes]
node1
node2
node3
[nodes:vars]
http_port=80
database_port=3306
转换为YAML格式:
# inventory/
hosts
nodes:
hosts:
node1:
node2:
node3:
vars:
http_port: \
'
80\'
database_port: \
'
3306\'
不含节点定义的主机变量定义:
# inventory/host_vars/
node1.yaml
http_port: \
'
80\'
database_port: \
'
3306\'
6.2.2 组变量
和主机变量类似,组变量作用于主机组,即多个主机。位置可以与主机组定义写在一起也可以写在inventory/group_vars/a_group_name.yaml文件里。通常前者使用使用INI格式,后者使用YAML格式。
INI格式:
# inventory/hosts
[nodes]
node1
node2
node3
[nodes:vars]
http_port=80
database_port=3306
转换为YAML格式:
# inventory/
hosts
nodes:
hosts:
node1:
node2:
node3:
vars:
http_port: \
'
80\'
database_port: \
'
3306\'
定义一个字典变量,位于inventory/group_vars/nodes.yaml:
# 一位职工记录
name: Maurice
job: Developer
skill: Develop program
employed: True
foods:
-
Apple
-
Orange
languages:
shell: Elite
python: Elite
c
++: Lame
将上面示例转换为一行:
# 一位职工记录
{name: Maurice,job: Developer,skill: Develop program,employed: True,foods: [\
'
Apple\',\'Orange\'],languages: {shell: Elite,python: Elite,c++: Lame}}
很显然,YANL格式分行来写会更加直观和美观。
七 变量的调用顺序
通过上面描述,我们发现ansible能够定义变量的地方真的是太多太多了,我个人觉得ansible变量这块的设计有点复杂和冗余。
在生产中,我们要读懂别人的代码或者让自己的代码更加健壮,就必须清楚的知道ansible最终会使用哪个变量。这里我总结下ansible的调用变量的顺序,当小伙伴迷茫时可以回来看下这个列表(越靠后变量优先级越高,越会被ansible采用)
命令行参数(非-e指定的参数,eg: "-u user -b yes")
roles defaults目录下的变量
组变量:inventory 文件
组变量:inventory/group_vars/all
组变量:playbook/group_vars/all
组变量:inventory/group_vars/*
组变量:playbook/group_vars/*
主机变量:inventory 文件
主机变量:inventory/group_vars/*
主机变量:playbook/group_vars/*
facts变量
play变量:vars定义的
play变量:vars_prompt定义的
play变量:vars_files导入的
roles vars目录下的变量
block中task定义的变量
playbook中task定义的变量
include_vars导入的变量
set_facts/register注册的变量
使用roles/include_role/import_role语句时定义的变量
使用include语句(ansible旧版本)时定义的变量
命令行-e参数指定的额外变量(优先级最高)
八 变量的使用
8.1 模块使用变量
一个变量被定义后,在它的作用域内的play可以直接调用,例如:
我们定义了整个play作用域的变量「name」和「age」,那么在之后的两个debug模块内可以直接调用。
# playbooks/
test.yaml
-
hosts: node1
vars:
name: Maurice
age:
27
tasks:
-
debug:
msg:
"
My name is {{ name }} and I'm {{ age }} years old
"
-
debug:
msg:
"
Hello Maurice
"
when: name
==
'
Maurice
'
输出结果展示:
➜ lab-ansible ansible-playbook playbooks/test.yaml
PLAY [node1] ********************************************************************************************
TASK [Gathering Facts] **********************************************************************************
ok: [node1]
TASK [debug] ********************************************************************************************
ok: [node1] => {
"msg": "My name is Maurice and I'm 27 years old"
TASK [debug] ********************************************************************************************
ok: [node1] => {
"msg": "Hello Maurice"
PLAY RECAP **********************************************************************************************
node1 : ok=3 changed=0 unreachable=0 failed=0
8.2 模版使用变量
变量被频繁使用的还有roles里的模版,位于roles/template/xxx.j2,该模版使用python的Jinja2模版语法。
roles模版多被用于生成服务的配置文件,所以会调用很多的变量。
示例如下:
"customerMonthInfo": "{{ cmp_server_customerMonthInfo }}",
"type_black_list": [{% for type_black in cmp_server_type_black_list %}"{{ type_black }}"{{ '' if loop.last else ',' }}{% endfor %}],
上述示例中「customerMonthInfo」的参数比较简单,就是变量「cmp_server_customerMonthInfo」的值
「type_black_list」参数从列表变量「cmp_server_type_black_list」中获取,执行结果是个字符串,字符串由该列表的元素以逗号为间隔组成,最后一个参数后没有逗号。
九 本节应该掌握的技能
掌握变量的命名规范
掌握变量定义的方法
掌握变量的作用域及调用顺序
会在模块和模版里使用变量
熟悉Jinja2模版的语法规则
十 参考链接
https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html#passing-variables-on-the-command-line
https://docs.ansible.com/ansible/latest/user_guide/playbooks_reuse_roles.html
https://docs.ansible.com/ansible/latest/user_guide/intro_inventory.html
红帽DO407 Automation with Ansible 教材
欢迎大家关注我的公众号: