Ansible的变量

在 Ansible 中使用变量,能让我们的工作变得更加灵活

变量定义规则

变量名应该由字母、数字、下划线组成

变量名需要以字母开头

Ansible 内置的关键字不能作为变量名

在 Playbook 中使用变量

使用 vars 关键字定义名为 Package1 值为 Nginx 的变量,在 task 中使用 进行调用

---
- hosts: all
  vars:
    package1: nginx
  remote_user: root
  tasks:
  - name: task1
    yum:
      name: "{{ package1 }}"
      state: installed

使用块序列化语法定义变量

vars:
  - testvar1: a1
  - testvar2: b2

使用属性的方式定义变量

---
- hosts: all
  remote_user: root
  vars:
    nginx:    # 定义两个变量
      conf80: /etc/nginx/conf.d/80.conf    
      conf8080: /etc/nginx/conf.d/8080.conf
  tasks:
  - name: task1
    file:
      path: "{{ nginx.conf80 }}"    # 第一种调用方法
      state: touch
  - name: task2
    file:
      path: "{{ nginx['conf8080']}}"    # 第二种调用方法

注意:如果引用变量时,变量处于开头的位置,那么变量必须要用双引号引起来,否则语法会报错

- name: task2
    file:
      path: "{{ nginx['conf8080']}}"    # 引用这种变量处于开头位置的必须使用引号引起来

- name: task2
    file:
      path: /root/{{ nginx['conf8080']}}    # 这样的不用

引入文件内的变量,创建 nginx_vars.yaml 文件,直接在文件中以自己喜欢的方式定义变量

testvar1: zxc
testvar2: qwe

- testvar3: rty
- testvar4: poi

nginx:
  conf1: /usr/local/nginx/conf/nginx1.conf
  conf2: /usr/local/nginx/conf/nginx2.conf

在 Playbook 中以 vars_files 关键字引入文件中的变量

---
- hosts: all
  remote_user: root
  vars:        # vars关键字和vars_files关键字可以同时使用
  - /root/vars.yaml
  vars_files:
  - /playbook/nginx_vars.yaml
  tasks:
  - name: task1
    file:
      path: "{{ nginx.conf80 }}"
      state: touch
  - name: task2
    file:
      path: "{{ nginx['conf8080']}}"

变量与 setup 模块

前面我们说过在执行 Playbook 的时候,默认都会运行一个名为 Gathering Facts 的任务,这个任务会收集管理节点的相关信息(例如管理节点的 IP 地址,主机名,系统版本,硬件配置等信息),这些被收集到的信息都会保存在对应的变量中,我们想要使用这些信息时,可以获取对应的变量,从而使用这些信息。关于 setup 模块具体查看前面 Ansible 模块学习

查看从管理节点收集到的所有相关信息

ansible all -m setup
# 由于返回信息的比较多,这里不作示例

查看管理节点的内存使用情况

ansible all -m setup -a 'filter=ansible_memory_mb'

在管理节点创建自定义变量

除了 Ansible 默认收集的信息以外,我们还能够在管理节点写入一些自定义变量,这些自定义变量也是可以被 setup 模块收集到

首先在管理节点创建自定义变量的文件 hello.fact,此类文件必须以 *.fact 命名

# 管理节点
mkdir -p /etc/ansible/facts.d
vim /etc/ansible/facts.d/hello.fact
[info]
name: mwj
age: 24

# 控制节点
ansible dbserver -m setup -a "filter=ansible_local"        # 使用ansible_local关键字过滤信息得到管理节点的自定义变量
10.10.110.122 | SUCCESS => {
    "ansible_facts": {
        "ansible_local": {
            "hello": {
                "info": {
                    "age": "24", 
                    "name": "mwj"
                }
            }
        }, 
        "discovered_interpreter_python": "/usr/bin/python"
    }, 
    "changed": false
}

注意:管理节点上的 hello.fact 文件必须不是可执行文件,不然这个文件不会被成功读取,具体可查看 Ansible 官方文档有详细说明。在管理节点的 /etc/ansible/facts.d/ 这个目录是使用 ansible_local 关键字过滤时的默认路径,如果想要自定义路径可以使用 fact_path 关键字定义

ansible dbserver -m setup -a "fact_path=/tmp/facts.d/"

变量与 debug 模块

debug 模块是帮我们进行调试的,可以把对我们有用的信息输出到控制台上,以便能够定位问题

Playbook 中使用 debug 模块

---
- hosts: all
  remote_user: root
  tasks:
  - name: touch file
    file:
      path: /tmp/debug.txt
      state: touch
  - name: debug demo
    debug:
      msg: this is debug info,File created successfully

执行 Playbook 模块查看信息

如下图所示,在 touch 文件之后会输出我们定义好的 debug 信息

PLAY [all] ***********************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************
ok: [dbserver]
ok: [webserver]

TASK [touch file] ****************************************************************************************************************
changed: [webserver]
changed: [dbserver]

TASK [debug demo] ****************************************************************************************************************
ok: [webserver] => {
    "msg": "this is debug info,File created successfully"
}
ok: [dbserver] => {
    "msg": "this is debug info,File created successfully"
}

PLAY RECAP ***********************************************************************************************************************
dbserver                   : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
webserver                  : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

使用 debug 模块输出变量信息

debug 模块除了能够使用 msg 参数输出自定义的信息,还能够使用 var 参数直接输出变量中的信息

---
- hosts: all
  remote_user: root
  vars: 
    testvar: this is a debug variable
  tasks:
  - name: debug demo
    debug:
      var: testvar

使用 debug 模块的 msg 参数一样可以打印变量信息

---
- hosts: all
  remote_user: root
  tasks:
  - name: debug demo
    debug:
      msg: "Remote host memory information: {{ ansible_memory_mb }}"

执行结果如下

PLAY [all] ***********************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************
ok: [dbserver]
ok: [webserver]

TASK [debug demo] ****************************************************************************************************************
ok: [webserver] => {
    "msg": "Remote host memory information: {u'real': {u'total': 216, u'used': 213, u'free': 3}, u'swap': {u'cached': 0, u'total': 1023, u'free': 1022, u'used': 1}, u'nocache': {u'used': 163, u'free': 53}}"
}
ok: [dbserver] => {
    "msg": "Remote host memory information: {u'real': {u'total': 216, u'used': 212, u'free': 4}, u'swap': {u'cached': 0, u'total': 1023, u'free': 1013, u'used': 10}, u'nocache': {u'used': 170, u'free': 46}}"
}

PLAY RECAP ***********************************************************************************************************************
dbserver                   : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0   
webserver                  : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

ansible_memory_mb 其中其实包含了 “nocache”、”real”、 “swap” 三个部分的信息,我们只想获得 “real” 部分的信息,在 Playbook 中引用变量时可以使用如下示例:

msg: "Remote host memory information: {{ ansible_memory_mb.real }}"
msg: "Remote host memory information: {{ ansible_memory_mb['real'] }}"

注册变量

Ansible 的模块运行之后都会返回一些返回值,只是默认情况下,这些返回值并不会显示而已,我们可以把这些返回值写入到某个变量中,这样我们就能够通过引用对应的变量从而获取到这些返回值了,这种将模块的返回值写入到变量中的方法被称为注册变量

下面这个 Playbook 有两个任务,第一个任务使用 shell 模块执行了一条命令,然后在这个任务下使用 register 注册了一个 testvar 的变量,第二个任务是使用 debug 模块的 var 参数打印这个变量,最后输出 shell 模块的返回值

---
- hosts: dbserver
  remote_user: root
  tasks:
  - name: test shell
    shell: "echo test > /tmp/test"
    register: testvar
  - name: shell module return values
    debug:
      var: testvar

Playbook 执行的结果如下图,返回的是一个 json 格式的数据

PLAY [dbserver] ******************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************
ok: [dbserver]

TASK [test shell] ****************************************************************************************************************
changed: [dbserver]

TASK [shell module return values] ************************************************************************************************
ok: [dbserver] => {
    "testvar": {
        "changed": true, 
        "cmd": "echo test > /tmp/test", 
        "delta": "0:00:00.025987", 
        "end": "2020-06-02 22:34:36.185101", 
        "failed": false, 
        "rc": 0, 
        "start": "2020-06-02 22:34:36.159114", 
        "stderr": "", 
        "stderr_lines": [], 
        "stdout": "", 
        "stdout_lines": []
    }
}

PLAY RECAP ***********************************************************************************************************************
dbserver                   : ok=3    changed=1    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

如果你想要查看模块对应的返回值,可以先查找官方手册,但并不是所有模块的官方手册中都对模块的返回值进行了描述,你可以自己去官网查看模块的返回值,这些返回值不仅仅能够用于输出,通常我们会利用到这些返回值,比如通过模块的返回值决定之后的一些动作,所以注册变量在 Playbook 中还是会被经常用到的,在之后的文章中我们会给出示例

变量与用户交互信息

在运行 shell 脚本时,有些时候需要用户输入信息,脚本再根据用户输入的信息决定下一步的动作,这种交互是必须的。我们也可以在Playbook 中实现这种交互,首先提示用户输入信息,然后将用户输入的信息存放到指定的变量中,当我们需要使用这些信息时,只要引用对应的变量即可

下面我们使用 vars_prompt 关键字定义了两个变量,变量名为别为 your_name 和 your_age,变量下面是提示用户输入时的信息

---
- hosts: dbserver
  remote_user: root
  vars_prompt:
    - name: "your_name"
      prompt: "what is your name"
    - name : "your_age"
      prompt: "how old are you"

  tasks:
  - name: output  vars
    debug:
      msg: your name is {{your_name}},you are {{your_age}} years old.

Playbook 执行如下图,提示用户输入信息时默认是不显示信息的,这和输入密码的场景类似

what is your name: 
how old are you: 

PLAY [dbserver] ******************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************
ok: [dbserver]

TASK [output  vars] **************************************************************************************************************
ok: [dbserver] => {
    "msg": "your name is mwj,you are 24 years old."
}

PLAY RECAP ***********************************************************************************************************************
dbserver                   : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

如果想要用户输入信息时显示信息内容,可以将 private 参数设置为 no

---
- hosts: dbserver
  remote_user: root
  vars_prompt:
    - name: "your_name"
      prompt: "what is your name"
    - name : "your_age"
      prompt: "how old are you"
      private: no    # 显示用户输入的内容
  tasks:
  - name: output  vars
    debug:
      msg: your name is {{your_name}},you are {{your_age}} years old.

我们还可以为提示信息设置默认值,如果用户不输入任何信息就将默认值赋予变量,如果用户输入信息,就把输入的信息赋值给变量

---
- hosts: dbserver
  remote_user: root
  vars_prompt:
  - name: "your_name"
    prompt: "what is your name\n"
    private: no
    default: mike
  tasks:
  - name: output  vars
    debug:
      msg: your name is {{your_name}}

上面 Playbook 的执行过程如下,中括号内的内容是我们设置的默认值,如果用户直接回车那就将中括号内的内容直接赋值给变量

what is your name
 [mike]: mwj

PLAY [dbserver] ******************************************************************************************************************

TASK [Gathering Facts] ***********************************************************************************************************
ok: [dbserver]

TASK [output  vars] **************************************************************************************************************
ok: [dbserver] => {
    "msg": "your name is mwj"
}

PLAY RECAP ***********************************************************************************************************************
dbserver                   : ok=2    changed=0    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

通过命令行传入变量

我们可以在执行 Playbook 时直接传入需要使用到的变量。编写一个 Playbook,打印一个 passwd 的变量

---
- hosts: dbserver
  remote_user: root
  tasks:
  - name: "passing in variables from the command line"
    debug:
      msg: "{{ passwd }}"

执行 Playbook 时传入变量

ansible-playbook --extra-vars "passwd=mdf123456" passwd.yaml
# --extra-vars参数可以简写成-e,还可以一次性传入多个变量,用空格隔开
ansible-playbook -e "passwd=mdf123456 username=ewe" passwd.yaml

注意:如果 Playbook 中并没有定义 passwd 变量,在执行 Playbook 时也没有传入 passwd 变量,则会报错。如果在 Playbook 中事先定义好了 passwd 变量,在执行时再次传入名字相同的变量,最终还是以传入的变量值为准,命令行传入的变量的优先级要高于 Playbook 中的变量

不仅 ansible-playbook 可以使用 “-e” 传递变量,Ansible 命令行一样可以,在执行 ad-hoc 命令时可以使用下面的方法传入变量

ansible dbserver -e "name=mwj" -m shell -a "echo my name in {{ name }}"

json 格式传入变量,除了以键值对的方式传入变量,我们还可以传入 json 格式的变量

ansible-playbook passwd.yaml -e '{"username":"mwj","passwd":"123456"}'
ansible-playbook passwd.yaml -e '{"countlist":["one","two","three","four"]}'    # {{countlist[0]}}或{{countlist.0}}引用变量

执行 Playbook 时传入变量文件,编写变量文件,可以是 json 格式或者 yaml 格式的文件

namevar: mwj
countlist:
- one
- two
- three
- four

Playbook 内容调用变量

---
- hosts: dbderver
  remote_user: root
  tasks:
  - name: "name"
    debug:
    msg: "{{ namevar }} {{ countlist[0] }}"

命令行传入对应的文件,使用 @ 符号加上变量文件的路径,变量文件中的所有变量都可以在 Playbook 中引用

ansible-playbook test.yaml -e '@/ansible/var1'

在主机清单中配置变量

在主机清单中,可以配置我们的管理节点,也可以将部分管理节点分为一组,其实在配置清单时还可以为主机或主机组设置变量

主机变量:在主机清单中配置变量时,可以同时为管理节点配置对应的变量,当操作这个主机时,即可直接使用对应的变量,而其他主机不能引用到这个变量

# ini风格
dbserver ansible_host=10.1.1.70 name: mwj age: 24

# yaml风格
all:
  children:
    server:
      hosts:
        dbserver:
          ansible_host: 10.10.110.122
          ansible_port: 22
          name: mwj
          age: 24
        webserver:
          ansible_host: 10.10.110.123
          ansible_port: 22

可以在命令行引用主机变量,也可以在 Playbook 中引用主机变量

ansible dbserver -m shell -a 'echo {{name}}'

使用层级关系定义更复杂的主机变量

all:
  children:
    server:
      hosts:
        dbserver:
          ansible_host: 10.10.110.122
          ansible_port: 22
          name: 
            n1: mike
            n2: masha
            n3: laki
# 引用时使用{{ name.n1 }}或{{ name['n1'] }}

主机组变量:在主机清单中,我们可以将多个主机分为一组,这样方便我们同时去操作同一组的管理节点,我们可以为这个主机组定义变量,组内的所有主机都可以使用

# ini风格
[webserver]
web01 ansible_host: 10.10.110.121
web02 ansible_host: 10.10.110.122
web03 ansible_host: 10.10.110.123

[webserver:vars]
path="/usr/local/nginx/html/"
user="root"

# yaml格式
all:
  children:
    server:
      hosts:
        dbserver:
          ansible_host: 10.10.110.122
          ansible_port: 22
        webserver:
          ansible_host: 10.10.110.123
          ansible_port: 22
      vars:
        user: "root"
        path: "/usr/local/nginx/html/"

set_fact 定义变量

set_fact 是一个模块,我们可以通过 set_fact 模块在 tasks 中定义变量 testvar1,然后打印这个变量

---
- hosts: dbserver
  remote_user: root
  tasks:
  - set_fact:
      testvar1: mid
  - debug:
      msg: "{{ testvar1 }}"

set_fact 定义变量的特殊性

通过 set_fact 模块创建的变量还有一个特殊性,通过 set_fact 创建的变量就像主机上的 facts 信息一样,可以在之后的 play 中被引用。而我们使用 vars 关键字创建的变量则不能被其他 Playbook 所引用到

下面这个 Playbook 有两个 play,第一个 play 中有两个变量分别是 ts1 和 ts2,它们分别用 vars 和 set_fact 定义,只有使用 set_fact 定义的 ts2 变量,才能被下面这个 play 所引用,而使用 vars 定义的 ts1 变量则不能被下面的 play 所引用

---
- hosts: dbserver
  remote_user: root
  vars:
    ts1: team1
  tasks:
  - set_fact:
      ts2: team2
  - debug:
      msg: "{{ ts1 }}---{{ ts2 }}"

- hosts: webserver
  remote_user: root
  tasks:
  - name: get ts1    # 这里引用会报错
    debug: 
      msg: "{{ ts1 }}"
  - name: get ts2
    debug:
      msg: "{{ ts2 }}"

注意:set_fact 变量类似于管理节点的全局变量,可以跨 play 获取变量,注册变量也能被之后的 play 所引用

内置变量

除了我们各种各样的定义变量之外,Ansible 还有一些内置的变量供我们使用,这些内置变量的变量名是被 Ansible 所保留的,我们定义变量时不能使用这些变量名

内置变量 ansible_version,查看 Ansible 的版本

ansible all -m debug -a 'msg={{ansible_version}}'

内置变量 hostvars,hostvars 可以帮助我们在操作当前管理节点时获取到其他管理节点中的信息。下面 Playbook 有两个 play,第一个没有任何 task,只是将 webserver 主机的信息收集起来,供后面的 play 调用。第二个 play 则是使用了 debug 模块打印了 webserver 的内置变量 hostvars,输出了 webserver 的 IP 地址,这就是在操作 dbserver 管理节点时获取了 webserver 管理节点的信息

---
- name: "gather facts of webserver"
  hosts: webserver
  remote_user: root

- name: "get facts webserver"
  hosts: dbserver
  remote_user: root
  tasks:
  - debug:
      msg: "{{ hostvars['webserver'].ansible_ens32.ipv4 }}"

# 如果没有第一个play,在执行时调用[Gathering Facts]任务,将webserver的信息收集起来,后面dbserver调用这个变量就会报错

内置变量 inventory_hostname,通过 inventory_hostname 变量可以获取到管理节点的当前主机名称,注意这个不是指 Linux 系统的主机名,而是对应管理节点在控制节点的主机清单中的配置名称

# 主机清单
[abc]
10.10.110.122
dbserver ansible_host: 10.10.110.123

使用内置变量 inventory_hostname 获取各个主机的对应的主机名

ansible abc -m debug -a 'msg={{inventory_hostname}}'
10.10.110.122 | SUCCESS => {
    "msg": "10.10.110.122"    
}
dbserver | SUCCESS => {
    "msg": "dbserver"
}
# 定义是IP则返回IP,定义是别名则返回别名

内置变量 inventory_hostname_short,与内置变量 inventory_hostname 类似,通过 inventory_hostname_short 也可以获取当前 play 操作的管理节点在清单中对应的名称,但是这个名称更加简短

[abc]
10.10.110.122
dbserver.com ansible_host=10.10.110.123

按上面主机清单的配置,我们可以使用 inventory_hostname_short 获取到管理节点的简短名称

ansible all -m debug -a 'msg={{inventory_hostname_short}}'
10.10.110.122 | SUCCESS => {
    "msg": "10"
}
dbserver.com | SUCCESS => {
    "msg": "dbserver"
}
# 可以看到无论是IP还是主机名,inventory_hostname_short都会取得主机名中第一个"."之前的字符作为主机的简短名称

内置变量 play_hosts,通过内置变量 play_hosts 可以获取到当前 play 所操作的所有管理节点的主机名列表

---
- hosts: 10.10.110.122,dbserver.com
  remote_user: root
  tasks:
  - name: debug
    debug:
      msg: "{{ play_hosts }}"

# 返回的是所操作的所有管理节点的主机名列表
ok: [10.10.110.122] => {
    "msg": [
        "10.10.110.122", 
        "dbserver.com"
    ]
}
ok: [dbserver.com] => {
    "msg": [
        "10.10.110.122", 
        "dbserver.com"
    ]
}

内置变量 inventory_dir,我们可以通过 inventory_dir 变量获取到 ansible 主机中清单文件的存放路径

ansible all -m debug -a 'msg={{inventory_dir}}'
10.10.110.122 | SUCCESS => {
    "msg": "/etc/ansible"
}
dbserver.com | SUCCESS => {
    "msg": "/etc/ansible"
}

重新加载变量文件

先来看一个小示例,假如 Playbook 中有三个任务,第一个任务调用了控制节点的一个变量文件,第二个任务在变量文件中新增了一个变量,第三个任务在变量文件中引用新增的那个变量,看看结果会如何

cat /root/playbook/var_file.yaml    # 变量文件已有v1变量
v1: 111

---
- hosts: master
  remote_user: root
  tasks:
  vars_files:
  - /root/playbook/var_file.yaml
  tasks:
  - debug:
      msg: "{{ v1 }}"
  - lineinfile:
      path: "/root/playbook/var_file.yaml"
      line: "v2: 222"    # 往变量文件新增v2变量
  - debug:    
      msg: "{{ v1 }},{{ v2 }}"    # 输出v1和v2变量,这里输出v2变量会出错

fatal: [master]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'v2' is undefined\n\nThe error appears to be in '/root/playbook/include.yaml': line 13, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n      line: \"v2: 222\"\n  - debug:\n    ^ here\n"}

上面的示例中,其实 v2 变量已经成功添加到变量文件中了,但是由于我们是先读取了变量文件,再写入 v2 变量到文件,这时候我们没有重新读取变量文件,那么就会报错 v2 变量未定义了,我们可以使用 include_vars 关键字从新加载变量文件

---
- hosts: master
  remote_user: root
  tasks:
  vars_files:
  - /root/playbook/var_file.yaml
  tasks:
  - debug:
      msg: "{{ v1 }}"
  - lineinfile:
      path: "/root/playbook/var_file.yaml"
      line: "v2: 222"
  - include_vars: "/root/playbook/var_file.yaml"    # 重新加载变量文件
  - debug:
      msg: "{{ v1 }},{{ v2 }}"    # 这时候输出v2变量就不会出错