ansible基础-变量

By | 2021年10月15日
目录
[隐藏]

一 变量的命名规范

变量的命名应该符如下合两个规范:

  • 变量应该由字母、数字、下划线组成
  • 变量应该以字母开头

例如: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 教材

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注