Ansible 定義變量的方法

1.1 ansible facts

facts組件是用來收集被管理節點信息的,使用setup模塊可以獲取這些信息。

ansible-doc -s setup           
- name: Gathers facts about remote hosts

以下是某次收集的信息示例。由於收集的信息項非常多,所以截取了部分內容項。

ansible 192.168.100.64 -m setup192.168.100.64 | SUCCESS => {    "ansible_facts": {        "ansible_all_ipv4_addresses": [            "192.168.100.64"
        ], 
        "ansible_all_ipv6_addresses": [            "fe80::20c:29ff:fe03:a452"
        ], 
        "ansible_apparmor": {            "status": "disabled"
        }, 
        "ansible_architecture": "x86_64", 
        "ansible_bios_date": "07/02/2015", 
        "ansible_bios_version": "6.00", 
        "ansible_cmdline": {            "BOOT_IMAGE": "/vmlinuz-3.10.0-327.el7.x86_64", 
            "LANG": "en_US.UTF-8", 
            "biosdevname": "0", 
            "crashkernel": "auto", 
            "net.ifnames": "0", 
            "quiet": true, 
            "ro": true, 
            "root": "UUID=b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8"
        }, 
........................................        "ansible_default_ipv6": {}, 
        "ansible_devices": {            "sda": {                "holders": [], 
                "host": "SCSI storage controller: LSI Logic / Symbios Logic 53c1030 PCI-X Fusion-MPT Dual Ultra320 SCSI (rev 01)", 
                "model": "VMware Virtual S", 
                "partitions": {                    "sda1": {                        "holders": [], 
                        "sectors": "512000", 
                        "sectorsize": 512, 
                        "size": "250.00 MB", 
                        "start": "2048", 
                        "uuid": "367d6a77-033b-4037-bbcb-416705ead095"
                    }, 
                    "sda2": {                        "holders": [], 
                        "sectors": "37332992", 
                        "sectorsize": 512, 
                        "size": "17.80 GB", 
                        "start": "514048", 
                        "uuid": "b2a70faf-aea4-4d8e-8be8-c7109ac9c8b8"
                    }, 
................................        "ansible_user_dir": "/root", 
        "ansible_user_gecos": "root", 
        "ansible_user_gid": 0, 
        "ansible_user_id": "root", 
        "ansible_user_shell": "/bin/bash", 
        "ansible_user_uid": 0, 
        "ansible_userspace_architecture": "x86_64", 
        "ansible_userspace_bits": "64", 
        "ansible_virtualization_role": "guest", 
        "ansible_virtualization_type": "VMware", 
        "module_setup": true
    }, 
    "changed": false}

使用filter可以篩選指定的facts信息。例如:

ansible 192.168.100.64 -m setup -a "filter=changed"192.168.100.64 | SUCCESS => {    "ansible_facts": {}, 
    "changed": false}

ansible localhost -m setup -a "filter=*ipv4"localhost | SUCCESS => {    "ansible_facts": {        "ansible_default_ipv4": {            "address": "192.168.100.62", 
            "alias": "eth0", 
            "broadcast": "192.168.100.255", 
            "gateway": "192.168.100.2", 
            "interface": "eth0", 
            "macaddress": "00:0c:29:d9:0b:71", 
            "mtu": 1500, 
            "netmask": "255.255.255.0", 
            "network": "192.168.100.0", 
            "type": "ether"
        }
    }, 
    "changed": false}

facts收集的信息是json格式的,其內任一項都可以當作變量被直接引用(如在playbook、jinja2模板中)引用。見下文。

1.2 變量引用json數據的方式

在ansible中,任何一個模塊都會返回json格式的數據,即使是錯誤信息都是json格式的。

在ansible中,json格式的數據,其內每一項都可以通過變量來引用它。當然,引用的前提是先將其註冊爲變量。

例如,下面的playbook是將shell模塊中echo命令的結果註冊爲變量,並使用debug模塊輸出。

---
    - hosts: 192.168.100.65
      tasks:
        - shell: echo hello world
          register: say_hi
        - debug: var=say_hi

debug輸出的結果如下:

TASK [debug] *********************************************
ok: [192.168.100.65] => {    "say_hi": {        "changed": true, 
        "cmd": "echo hello world", 
        "delta": "0:00:00.002086", 
        "end": "2017-09-20 21:03:40.484507", 
        "rc": 0, 
        "start": "2017-09-20 21:03:40.482421", 
        "stderr": "", 
        "stderr_lines": [], 
        "stdout": "hello world", 
        "stdout_lines": [            "hello world"
        ]
    }
}

可以看出,結果是一段json格式的數據,最頂端的key爲say_hi,其內是一大段的字典(即使用大括號包圍的),其中的stdout_lines還包含了一個json數組,也就是所謂的yaml列表項(即使用中括號包圍的)。

1.2.1 引用json字典數據的方式

如果想要輸出json數據的某一字典項,則應該使用"key.dict"或"key['dict']"的方式引用。例如最常見的stdout項"hello world"是想要輸出的項,以下兩種方式都能引用該字典變量。

---
    - hosts: 192.168.100.65
      tasks:
        - shell: echo hello world
          register: say_hi
        - debug: var=say_hi.stdout
        - debug: var=sya_hi['stdout']

ansible-playbook的部分輸出結果如下:

TASK [debug] ************************************************ok: [192.168.100.65] => {    "say_hi.stdout": "hello world"}

TASK [debug] ************************************************ok: [192.168.100.65] => {    "say_hi['stdout']": "hello world"}

"key.dict"或"key['dict']"的方式都能引用,但在dict字符串本身就包含"."的時候,應該使用中括號的方式引用。例如:

anykey['192.168.100.65']

1.2.2 引用json數組數據的方式

如果想要輸出json數據中的某一數組項(列表項),則應該使用"key[N]"的方式引用數組中的第N項,其中N是數組的index,從0開始計算。如果不使用index,則輸出的是整個數組列表。

例如想要輸出上面的stdout_lines中的"hello world",它是數組stdout_lines中第一項所以使用stdout_lines[0]來引用,再加上stdout_lines上面的say_hi,於是引用方式如下:

---
    - hosts: 192.168.100.65
      tasks:
        - shell: echo hello world
          register: say_hi
        - debug: var=say_hi.stdout_lines[0]

由於stdout_lines中僅有一項,所以即使不使用index的方式即say_hi.stdout_lines也能得到期望的結果。輸出結果如下:

TASK [debug] *************************************************
ok: [192.168.100.65] => {    "say_hi.stdout_lines[0]": "hello world"}

再看下面一段json數據。

"ipv6": [
   {       "address": "fe80::20c:29ff:fe26:1498", 
       "prefix": "64", 
       "scope": "link"
   }
]

其中key=ipv6,其內有且僅有是一個列表項,但該列表內包含了數個字典項。要引用列表內的字典,例如上面的address項。應該如下引用:

ipv6[0].address

1.2.3 引用facts數據

既然已經瞭解了json數據中的字典和列表列表項的引用方式,顯然facts中的一大堆數據就能引用並派上用場了。例如以下是一段facts數據。

shell> ansible localhost -m setup -a "filter=*eth*"    localhost | SUCCESS => {    "ansible_facts": {        "ansible_eth0": {            "active": true, 
            "device": "eth0", 
            "features": {                "busy_poll": "off [fixed]", 
                "fcoe_mtu": "off [fixed]", 
                "generic_receive_offload": "on", 
                .........................
            }, 
            "ipv4": {                "address": "192.168.100.62", 
                "broadcast": "192.168.100.255", 
                "netmask": "255.255.255.0", 
                "network": "192.168.100.0"
            }, 
            "macaddress": "00:0c:29:d9:0b:71", 
            "module": "e1000", 
             ............................
        }
    }, 
    "changed": false}

顯然,facts數據的頂級key爲ansible_facts,在引用時應該將其包含在變量表達式中。但自動收集的facts比較特殊,它以ansible_facts作爲key,ansible每次收集後會自動將其註冊爲變量,所以facts中的數據都可以直接通過變量引用,甚至連頂級key ansible_facts都要省略。

例如引用上面的ipv4的地址address項。

ansible_eth0.ipv4.address

而不能寫成:

ansible_facts.ansible_eth0.ipv4.address

但其他任意時候,都應該帶上所有的key。

1.3 設置本地facts

在ansible收集facts時,還會自動收集/etc/ansible/facts.d/*.fact文件內的數據到facts中,且以ansible_local做爲key。目前fact支持兩種類型的文件:ini和json。當然,如果fact文件的json或ini格式寫錯了導致無法解析,那麼肯定也無法收集。

例如,在/etc/ansible/facts.d目錄下存在一個my.fact的文件,其內數據如下:

shell> cat /etc/ansible/facts.d/my.fact 
{    "family": {        "father": {            "name": "Zhangsan",            "age": "39"
        },        "mother": {            "name": "Lisi",            "age": "35"
        }
    }
}

ansible收集facts後的本地facts數據如下:

shell> ansible localhost -m setup -a "filter=ansible_local"localhost | SUCCESS => {    "ansible_facts": {        "ansible_local": {            "my": {                "family": {                    "father": {                        "age": "39", 
                        "name": "Zhangsan"
                    }, 
                    "mother": {                        "age": "35", 
                        "name": "Lisi"
                    }
                }
            }
        }
    }, 
    "changed": false}

可見,如果想要引用本地文件中的某個key,除了帶上ansible_local外,還必須得帶上fact文件的文件名。例如,引用father的name。

ansible_local.my.family.father.name

1.4 輸出和引用變量

上文已經展示了一種變量的引用方式:使用debug的var參數。debug的另一個參數msg也能輸出變量,且msg可以輸出自定義信息,而var參數只能輸出變量。

另外,msg和var引用參數的方式有所不同。例如:

---
    - hosts: 192.168.100.65
      tasks:
        - debug: 'msg="ipv4 address: {{ansible_eth0.ipv4.address}}"'
        - debug: var=ansible_eth0.ipv4.address

msg引用變量需要加上雙大括號包圍,既然加了大括號,爲了防止被解析爲內聯字典,還得加引號包圍。這裏使用了兩段引號,因爲其內還包括了一個": ",加引號可以防止它被解析爲"key: "的格式。而var參數引用變量則直接指定變量名。

這就像bash中引用變量的方式是一樣的,有些時候需要加上$,有些時候不能加$。也就是說,當引用的是變量的值,就需要加雙大括號,就像加$一樣,而引用變量本身,則不能加雙大括號。其實雙大括號是jinja2中的分隔符。

執行的部分結果如下:

TASK [debug] *****************************************************ok: [192.168.100.65] => {    "msg": "ipv4 address: 192.168.100.65"}

TASK [debug] *****************************************************ok: [192.168.100.65] => {    "ansible_eth0.ipv4.address": "192.168.100.65"}

幾乎所有地方都可以引用變量,例如循環、when語句、信息輸出語句、template文件等等。只不過有些地方不能使用雙大括號,有些地方需要使用。

1.5 註冊和定義變量的各種方式

ansible中定義變量的方式有很多種,大致有:(1)將模塊的執行結果註冊爲變量;(2)直接定義字典類型的變量;(3)role中文件內定義變量;(4)命令行傳遞變量;(5)藉助with_items迭代將多個task的結果賦值給一個變量;(6)inventory中的主機或主機組變量;(7)內置變量。

1.5.1 register註冊變量

使用register選項,可以將當前task的輸出結果賦值給一個變量。例如,下面的示例中將echo的結果"haha"賦值給say_hi變量。注意,模塊的輸出結果是json格式的,所以,引用變量時要指定引用的對象。

---
    - hosts: localhost
      tasks: 
        - shell: echo haha
          register: say_hi
        - debug: var=say_hi.stdout

1.5.2 set_fact定義變量

set_fact和register的功能很相似,也是將值賦值給變量。它更像shell中變量的賦值方式,可以將某個變量的值賦值給另一個變量,也可以將字符串賦值給變量。

例如:

---
    - hosts: 192.168.100.65
      tasks:
        - shell: echo haha
          register: say_hi
        - set_fact: var1="{{say_hi.stdout}}"
        - set_fact: var2="your name is"
        - debug: msg="{{var2}} {{var1}}"

1.5.3 vars定義變量

可以在play或task層次使用vars定義字典型變量。如果同名,則task層次的變量覆蓋play層次的變量。

例如:

---
    - hosts: localhost
      vars: 
        var1: value1
        var2: value2
      tasks: 
         - debug: msg="{{var1}} {{var2}}"
           vars: 
             var2: value2.2

輸出結果爲:

TASK [debug] ********************************************
ok: [localhost] => {    "msg": "value1 value2.2"}

1.5.4 vars_files定義變量

和vars一樣,只不過它是將變量以字典格式定義在獨立的文件中,且vars_files不能定義在task層次,只能定義在play層次。

---
    - hosts: localhost
      vars_files: 
        - /tmp/var_file1.yml
        - var_file2.yml
      tasks: 
         - debug: msg="{{var1}} {{var2}}"

上面var_file2.yml使用的是相對路徑,基於playbook所在的路徑。例如該playbook爲/tmp/x.yml,則var_file2.yml也應該在/tmp下。當然,完全可以使用絕對路徑。

1.5.5 roles中的變量

由於role是整合playbook的,它有默認的文件組織結構。其中有一個目錄vars,其內的main.yml用於定義變量。還有defaults目錄內的main.yml則是定義role默認變量的,默認變量的優先級最低。

shell> tree /yaml
/yaml
├── roles
│   └── nginx│       ├── defaults
│           └── main.yml      │       ├── files
│       ├── handlers
│       ├── meta
│       ├── tasks
│       ├── templates│       └── vars
│           └── main.yml└── site.yml

main.yml中變量定義方式也是字典格式,例如:

---
    mysql_port: 3306

1.5.6 命令行傳遞變量

ansible和ansible-playbook命令的"-e"選項都可以傳遞變量,傳遞的方式有兩種:-e key=value-e @var_file。注意,當key=value方式傳遞變量時,如果變量中包含特殊字符,必須防止其被shell解析。

例如:

ansible localhost -m shell -a "echo {{say_hi}}" -e 'say_hi="hello world"'ansible localhost -m shell -a "echo {{say_hi}}" -e @/tmp/var_file1.yml

其中/tmp/var_file1.yml中的內容如下:

---
    say_hi: hello world

1.5.7 藉助with_items疊加變量

ansible中可以藉助with_items實現列表迭代的功能,作用於變量註冊的行爲上,就可以實現將多個結果賦值給同一個變量。

例如下面的playbook中,給出了3個item列表,並在shell模塊中通過固定變量"{{item}}"分別迭代,第一次迭代的是haha,第二次迭代的是heihei,第三次迭代的是hehe,也就實現了3次循環。最後,將結果註冊爲變量hi_var。

---
    - hosts: localhost
      remote_user: root
      tasks:
        - name: test #
          shell: echo "{{item}}"
          with_items:
            - haha
            - heihei
            - hehe
          register: hi_var
        - debug: var=hi_var.results[0].stdout
        - debug: var=hi_var.results[1].stdout
        - debug: var=hi_var.results[2].stdout

每次迭代的過程中,調用item的模塊都會將結果保存在一個key爲results的數組中。因此,引用迭代後註冊的變量時,需要在變量名中加上results,並指定數組名。例如上面的hi_var.results[N].stdout

還可以使用for循環遍歷列表。例如:

- debug: msg="{% for i in hi_var.results %} {{i.stdout}} {% endfor %}"

其實,看一下hi_var的輸出就很容易理解了。以下是hi_var的第一個列表的輸出。

"hi_var": {    "changed": true, 
    "msg": "All items completed", 
    "results": [
        {            "_ansible_item_result": true, 
            "_ansible_no_log": false, 
            "_ansible_parsed": true, 
            "changed": true, 
            "cmd": "echo \"haha\"", 
            "delta": "0:00:00.001942", 
            "end": "2017-09-21 04:45:57.032946", 
            "invocation": {                "module_args": {                    "_raw_params": "echo \"haha\"", 
                    "_uses_shell": true, 
                    "chdir": null, 
                    "creates": null, 
                    "executable": null, 
                    "removes": null, 
                    "warn": true
                }
            }, 
            "item": "haha", 
            "rc": 0, 
            "start": "2017-09-21 04:45:57.031004", 
            "stderr": "", 
            "stderr_lines": [], 
            "stdout": "haha", 
            "stdout_lines": [                "haha"
            ]
        }

1.5.8 inventory中主機變量和主機組變量

在inventory文件中可以爲主機和主機組定義變量,不僅包括內置變量賦值,還包括自定義變量賦值。例如以下inventory文件。

192.168.100.65 ansible_ssh_port=22 var1=1[centos7]192.168.100.63192.168.100.64192.168.100.65 var1=2[centos7:vars]var1=2.2var2=3[all:vars]var2=4

其中ansible_ssh_port是主機內置變量,爲其賦值22,這類變量是設置類變量,不能被引用。此外還在多處爲主機192.168.100.65進行了賦值。其中[centos7:vars][all:vars]表示爲主機組賦值,前者是爲centos7這個組賦值,後者是爲所有組賦值。

以下是執行語句:

shell> ansible 192.168.100.65 -i /tmp/hosts -m shell -a 'echo "{{var1}} {{var2}}"'192.168.100.65 | SUCCESS | rc=0 >>2 3

從結果可知,主機變量優先級高於主機組變量,給定的主機組變量優先級高於all特殊組。

除了在inventory文件中定義主機、主機組變量,還可以將其定義在host_vars和group_vars目錄下的獨立的文件中,但要求這些host_vars或group_vars這兩個目錄和inventory文件或playbook文件在同一個目錄下,且變量的文件以對應的主機名或主機組名命名。

例如,inventory文件路徑爲/etc/ansible/hosts,playbook文件路徑爲/tmp/x.yml,則主機192.168.100.65和主機組centos7的變量文件路徑可以爲以下幾種:

  • /etc/ansible/host_vars/192.168.100.65

  • /etc/ansible/group_vars/centos7

  • /tmp/host_vars/192.168.100.65

  • /tmp/group_vars/centos7

以下爲幾個host_vars和group_vars目錄下的文件內容。

shell> cat /etc/ansible/{host_vars/192.168.100.65,group_vars/centos7} \           /tmp/{host_vars/192.168.100.65,group_vars/centos7}var1: 1var2: 2var3: 3var4: 4

以下爲/tmp/x.yml的內容。

---
    - hosts: 192.168.100.65
      tasks:
        - debug: msg='{{var1}} {{var2}} {{var3}} {{var4}}'

執行結果如下:

TASK [debug] **********************************************
ok: [192.168.100.65] => {    "msg": "1 2 3 4"}

1.5.9 內置變量

ansible除了inventory中內置的一堆不可被引用的設置類變量,還有幾個全局都可以引用的內置變量,主要有以下幾個:
inventory_hostname、inventory_hostname_short、groups、group_names、hostvars、play_hosts、inventory_dir和ansible_version。

1.inventory_hostname和inventory_hostname_short

分表代表的是inventory中被控節點的主機名和主機名的第一部分,如果定義的是主機別名,則變量的值也是別名。

例如inventory中centos7主機組定義爲如下:

[centos7]
192.168.100.63
host1 ansible_ssh_host=192.168.100.64
www.host2.com ansible_ssh_host=192.168.100.65

分別輸出它們的inventory_hostnameinventory_hostname_short

shell> ansible centos7 -m debug -a 'msg="{{inventory_hostname}} & {{inventory_hostname_short}}"'192.168.100.63 | SUCCESS => {    "msg": "192.168.100.63 & 192"}
host1 | SUCCESS => {    "msg": "host1 & host1"}
www.host2.com | SUCCESS => {    "msg": "www.host2.com & www"}

2.groups和group_names

group_names返回的是主機所屬主機組,如果該主機在多個組中,則返回多個組,如果它不在組中,則返回ungrouped這個特殊組。

例如,某個inventory文件如下:

192.168.100.60192.168.100.63192.168.100.64192.168.100.65 [centos6]192.168.100.60[centos7]192.168.100.63host1 ansible_ssh_host=192.168.100.64www.host2.com ansible_ssh_host=192.168.100.65[centos:children]centos6centos7

其中100.60定義在centos6和centos中,所以返回這兩個組。同理100.63返回centos7和centos。100.64和100.65則返回ungrouped,雖然它們在centos7中都定義了別名,但至少將100.64和100.65作爲主機名時,它們是不在任何主機中的。另一方面,host1和www.host2.com這兩個別名主機都返回centos7和centos兩個組。

groups變量則是返回其所在inventory文件中所有組和其內主機名。注意,該變量對每個控制節點都返回一次,所以返回的內容可能非常多。例如,上面的inventory中,如果指定被控節點爲centos7,則會重複返回3次(因爲有3臺被控主機)該inventory文件。其中的第三臺主機www.host2.com的返回結果爲:

www.host2.com | SUCCESS => {    "msg": {        "all": [            "192.168.100.60", 
            "192.168.100.63", 
            "192.168.100.64", 
            "192.168.100.65", 
            "host1", 
            "www.host2.com"
        ], 
        "centos": [            "192.168.100.60", 
            "192.168.100.63", 
            "host1", 
            "www.host2.com"
        ], 
        "centos6": [            "192.168.100.60"
        ], 
        "centos7": [            "192.168.100.63", 
            "host1", 
            "www.host2.com"
        ], 
        "ungrouped": [            "192.168.100.60", 
            "192.168.100.63", 
            "192.168.100.64", 
            "192.168.100.65"
        ]
    }
}

3.hostvars

該變量用於引用其他主機上收集的facts中的數據,或者引用其他主機的主機變量、主機組變量。其key爲主機名或主機組名。

舉個例子,假如使用ansible部署一臺php服務器host1,且配置文件內需要指向另一臺數據庫服務器host2的ip地址ip2,可以直接在配置文件中指定ip2,但也可以在模板配置文件中直接引用host2收集的facts數據中的ansible_eth0.ipv4.address變量。

例如,centos7主機組中包含了192.168.100.[63:65]共3臺主機。playbook內容如下:

---
    - hosts: centos7
      tasks:
        - debug: msg="{{hostvars['192.168.100.63'].ansible_eth0.ipv4.address}}"

執行結果如下:

TASK [debug] *********************************************************
ok: [192.168.100.63] => {    "msg": "192.168.100.63"}
ok: [192.168.100.64] => {    "msg": "192.168.100.63"}
ok: [192.168.100.65] => {    "msg": "192.168.100.63"}

但注意,在引用其他主機facts中數據時,要求被引用主機進行了facts收集動作,或者有facts緩存。否則都沒收集,當然無法引用其facts數據。也就是說,當被引用主機沒有facts緩存時,ansible的控制節點中必須同時包含引用主機和被引用主機。

除了引用其他主機的facts數據,還可以引用其他主機的主機變量和主機組變量,且不要求被引用主機有facts數據,因爲主機變量和主機組變量是在ansible執行任務前加載的。

例如,inventory中格式如下:

192.168.100.59[centos7]192.168.100.63 var63=63192.168.100.64192.168.100.65[centos7:vars]var64=64

playbook內容如下:

---
    - hosts: 192.168.100.59
      tasks:
        - debug: msg="{{hostvars['192.168.100.63'].var63}} & {{hostvars['192.168.100.65'].var64}}"

執行結果如下:

TASK [debug] ***************************************
ok: [192.168.100.59] => {    "msg": "63 & 64"}

4.play_hosts和inventory_dir

play_hosts代表的是當前play所涉及inventory內的所有主機名列表。

例如,inventory內容爲:

192.168.100.59[centos6]192.168.100.62192.168.100.63[centos7]192.168.100.64192.168.100.65

那麼,該inventory內的任意一或多臺主機作爲ansible或ansible-playbook的被控節點時,都會返回整個inventory內的所有主機名稱。

inventory_dir是所使用inventory所在的目錄。

5.ansible_version

代表的是ansible軟件的版本號。變量返回的內容如下:

{
        "full": "2.3.1.0", 
        "major": 2, 
        "minor": 3, 
        "revision": 1, 
        "string": "2.3.1.0"
    }

最後,不得不說ansible的變量定義方式太豐富了,但是ansible的官方手冊真的噁心到吐,太爛了


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章