Ansible系列(三):YAML語法和playbook寫法

ansible的playbook採用yaml語法,它簡單地實現了json格式的事件描述。yaml之於json就像markdown之於html同樣,極度簡化了json的書寫。在學習ansible playbook以前,頗有必要把yaml的語法格式、引用方式作個梳理。html

初步說明


以一個簡單的playbook爲例,說明yaml的基本語法。
```
---
- hosts: 192.168.100.59,192.168.100.65
remote_user: root
pre_tasks: 
- name: set epel repo for Centos 7
yum_repository: 
name: epel7
description: epel7 on CentOS 7
baseurl: http://mirrors.aliyun.com/epel/7/$basearch/
gpgcheck: no
enabled: Truemysql

tasks: 
# install nginx and run it
- name: install nginx
yum: name=nginx state=installed update_cache=yes
- name: start nginx
service: name=nginx state=startednginx

post_tasks: 
- shell: echo "deploy nginx over"
register: ok_var
- debug: msg="{{ ok_var.stdout }}"
```
1. yaml文件以`---`開頭,以代表這是一個`yaml`文件,就像`xml`文件在開頭使用`<?xml version="1.0" encoding="utf-8"?>`宣稱它是`xml`文件同樣。但即便沒有使用`---`開頭,也不會有什麼影響。sql

2. `yaml`中使用"#"做爲註釋符,能夠註釋整行,也能夠註釋行內從"#"開始的內容。shell

3. `yaml`中的字符串一般不用加任何引號,即便它包含了某些特殊字符。但有些狀況下,必須加引號,最多見的是在引用變量的時候。具體見後文。json

4. 關於布爾值的書寫格式,即`true/false`的表達方式。其實`playbook`中的布爾值類型很是靈活,可分爲兩種狀況:centos

- 模塊的參數: 這時布爾值做爲字符串被`ansible`解析。接受`yes/on/1/true/no/off/0/false`。例如上面示例中的`update_cache=yes`。
- 非模塊的參數: 這時布爾值被`yaml`解釋器解析,徹底遵循`yaml`語法。接受不區分大小寫的`true/yes/on/y/false/no/off/n`。例如上面的`gpgcheck=no`和`enabled=True`。數組

建議遵循`ansible`的官方規範,模塊的布爾參數採用`yes/no`,非模塊的布爾參數採用`True/False`。markdown

### 列表
使用`"- "`(減號加一個或多個空格)做爲列表項,也就是`json`中的數組。`yaml`的列表在`playbook`中極重要,必須得搞清楚它的寫法。post

例如:
```
- zhangsan
- lisi
- wangwu
```

還支持內聯寫法:使用中括號。
```
[zhangsan,lisi,wangwu]
```

它們等價於json格式的:
```
[
"zhangsan",
"lisi",
"wangwu"
]
```

再例如:

```
- 班名: 初中1班
人數: 35
班主任: 隔壁老張
今天的任務: 掃操場

- 班名: 初中2班
人數: 38
班主任: 隔壁老王
今天的任務: 搬桌子
```

具體在`ansible playbook`中,列表所描述的是局部環境,它不必定要有名稱,不必定要從同一個屬性開始,只要使用`"-"`,它就表示圈定一個範圍,範圍內的項都屬於該列表。例如:

```
---
- name: list1 # 列表1,同時給了個名稱
hosts: localhost # 指出了hosts是列表1的一個對象
remote_user: root # 列表1的屬性
tasks: # 仍是列表1的屬性

- hosts: 192.168.100.65 # 列表2,可是沒有爲列表命名,而是直入主題
remote_user: root
sudo: yes
tasks:
```

惟一要注意的是,每個`playbook`中必須包含`hosts`和`tasks`項。更嚴格地說,是每一個`play`的頂級列表必須包含這兩項。就像上面的例子中,就表示該`playbook`中包含了兩個`play`,每一個`play`的頂級列表都包含了`hosts`和`tasks`。其實絕大多數狀況下,一個`playbook`中都只定義一個`play`,因此只有一個頂級列表項。頂級列表的各項,其實能夠將其看做是`ansible-playbook`運行時的選項。

另外,`playbook`中某項是一個動做、一個對象或一個實體時,通常都定義成列表的形式。

### 字典
官方手冊上這麼稱呼,其實就是`key=value`的另外一種寫法。使用"冒號+空格"分隔,即`key: value`。它通常看成列表項的屬性。

例如:

```
- 班名: 初中1班
人數: 
總數: 35
男: 19
女: 16
班主任: 
大名: 隔壁老張
這廝多大: 39
這廝任教多少年: 15
今天的任務: 掃操場

- 班名: 初中2班
人數: 
總數: 38
男: 19
女: 19
班主任: 
大名: 隔壁老王
這廝多大: 30
喜調戲女老師: True
今天的任務: 搬桌子
未完成任務怎麼辦:
- 繼續搬,直到完成
- 寫反省
```

具體到`playbook`中,通常"虛擬性"的內容均可以經過字典的方式書寫,而實體化的、動做性的、對象性的內容則應該定義爲列表形式。


```
---
- hosts: localhost # 列表1
remote_user: root
tasks:
- name: test1 # 子列表,下面是shell模塊,是一個動做,因此定義爲列表,只不過加了個name
shell: echo /tmp/a.txt
register: hi_var
- debug: var=hi_var.stdout # 調用模塊,這是動做,因此也是列表
- include: /tmp/nginx.yml # 一樣是動做,包含文件
- include: /tmp/mysql.yml
- copy: # 調用模塊,定義爲列表。但模塊參數是虛擬性內容,應定義爲字典而非列表
src: /etc/resolv.conf # 模塊參數1
dest: /tmp # 模塊參數2

- hosts: 192.168.100.65 # 列表2
remote_user: root
vars:
nginx_port: 80 # 定義變量,是虛擬性的內容,應定義爲字典而非列表
mysql_port: 3306
vars_files: 
- nginx_port.yml # 沒法寫成key/value格式,且是實體文件,所以定義爲列表
tasks:
- name: test2
shell: echo /tmp/a.txt
register: hi_var # register是和最近一個動做綁定的
- debug: var=hi_var.stdout
```

從上面示例的`copy`模塊能夠得出,模塊的參數是虛擬性內容,也能使用字典的方式定義。

字典格式的`key/value`,也支持內聯格式寫法:使用大括號。

```
{大名: 隔壁老王,這廝多大: 30,喜調戲女老師: True}
{nginx_port: 80,mysql_port: 3306}
```

這等價於json格式的:

```
{
"大名": "隔壁老王",
"這廝多大": 30,
"喜調戲女老師": "True"
}
{
"nginx_port": 80,
"mysql_port": 3306
}
```

再結合其父項,因而轉換成json格式的內容:

```
"班主任": {
"大名": "隔壁老王",
"這廝多大": 30,
"喜調戲女老師": "True"
}

"vars": {
"nginx_port": 80,
"mysql_port": 3306
}
```

再加上列表項(使用中括號),因而:

```
[
{
"hosts": "192.168.100.65",
"remote_user": "root",
"vars": {
"nginx_port": 80,
"mysql_port": 3306
},
"vars_files": [
"nginx_port.yml"
],
"tasks": [
{
"name": "test2",
"shell": "echo /tmp/a.txt",
"register": "hi_var"
},
{
"debug": "var=hi_var.stdout"
}
]
}
]
```
### 分行寫
`playbook`中有3種方式進行續行。

在`"key: "`的後面使用大於號。
在`"key: "`的後面使用豎線。這種方式能夠像腳本同樣寫不少行語句。
多層縮進。
例如,下面的3中方法。

```
---
- hosts: localhost
tasks: 
- shell: echo 2 >>/tmp/test.txt
creates=/tmp/haha.txt # 比模塊shell縮進更多
- shell: > # 在"key: "後使用大於號
echo 2 >>/tmp/test.txt
creates=/tmp/haha.txt
- shell: | # 指定多行命令
echo 2 >>/tmp/test.txt
echo 3 >>/tmp/test.txt
args:
creates: /tmp/haha.txt
```

### 向模塊傳遞參數
模塊的參數通常來講是`key=value`格式的,有3種傳遞的方式:

直接寫在模塊後,此時要求使用`key=value`格式。這是讓`ansible`內部去解析字符串。由於可分行寫,因此有多種寫法。

寫成字典型,即`key: value`。此時要求多層縮進。這是讓`yaml`去解析字典。
使用內置屬性args,而後多層縮進定義參數列表。這是讓`ansible`明確指定用`yaml`來解析。
例如:

```
---
- hosts: localhost
tasks: 
- yum: name=unix2dos state=installed # key=value直接傳遞
- yum: 
name: unxi2dos
state: installed # "key: value"字典格式傳遞
- yum: 
args: # 使用args傳遞
name: unix2dos
state:installed
```

但要注意,當模塊的參數是`free_form`時,即格式不定,例如`shell`和`command`模塊指定要執行的命令,它沒法寫成`key/value`格式,此時不能使用上面的第二種方式。也就是說,下面第一個模塊是正確的,第二個模塊是錯誤的,由於`shell`模塊的命令`"echo haha"`是自由格式的,沒法寫成`key/value`格式。

```
---
- hosts: localhost
tasks: 
- yum: 
name: unxi2dos
state: installed
- shell: 
echo haha
creates: /tmp/haha.txt
```

因此,調用一個模塊的方式就有了多種形式。例如:

```
---
- hosts: localhost
tasks:
- shell: echo 1 >/tmp/test.txt creates=/tmp/haha.txt
- shell: echo 2 >>/tmp/test.txt
creates=/tmp/haha.txt
- shell: echo 3 >>/tmp/test.txt
args:
creates: /tmp/haha.txt
- shell: >
echo 4 >>/tmp/test.txt
creates=/tmp/haha.txt
- shell: |
echo 5.1 >>/tmp/test.txt
echo 5.2 >>/tmp/test.txt
args:
creates: /tmp/haha.txt
- yum: 
name: dos2unix
state: installed
```
### playbook和play的關係
一個`playbook`中能夠包含多個`play`。每一個`play`都至少包含有`tasks`和`hosts`這兩項,還能夠包含其餘非必須項,如`vars,vars_files,remote_user`等。`tasks`中能夠經過模塊調用定義一系列的`action`。只不過,絕大多數時候,一個`playbook`都只定義一個`play`。

因此,大體關係爲:

```
playbook: [play1,play2,play3]
play: [hosts,tasks,vars,remote_user...]
tasks: [module1,module2,...]
```

也就是說,每一個頂級列表都是一個`play`。例如,下面的`playbook`中包含了兩個`play`。

```
---
- name: list1
hosts: localhost
remote_user: root
tasks:

- hosts: 192.168.100.65
remote_user: root
sudo: yes
tasks:
```

須要注意,有些時候`play`中使用了`role`,可能看上去沒有`tasks`,這是由於`role`自己就是整合`playbook`的,因此沒有也不要緊。但沒有使用`role`的時候,必須得包含`hosts`和`tasks`。例如:

```
---
- hosts: centos
remote_user: root
pre_tasks: 
- name: config the yum repo for centos 7
yum_repository:
name: epel
description: epel
baseurl: http://mirrors.aliyun.com/epel/7/$basearch/
gpgcheck: no
when: ansible_distribution_major_version == "7"

- name: config the yum repo for centos 6
yum_repository:
name: epel
description: epel
baseurl: http://mirrors.aliyun.com/epel/6/$basearch/
gpgcheck: no
when: ansible_distribution_major_version == "6"

roles: 
- nginx

post_tasks:
- shell: echo 'deploy nginx/mysql over'
register: ok_var
- debug: msg='{{ ok_var.stdout }}'
```

### playbook中何時使用引號
`playbook`中定義的都是些列表和字典。絕大多數時候,都不須要使用引號,但有兩個特殊狀況須要考慮使用引號。

出現大括號`{}`。
出現冒號加空格`: `。
大括號要使用引號包圍,是由於不使用引號時會被`yaml`解析成內聯字典。例如要使用大括號引用變量的時候,以及想輸出大括號符號的時候。


```
---
- hosts: localhost
tasks:
- shell: echo "{{inventory_hostname}}:haha"
```

冒號尾隨空格時要使用引號包圍,是由於它會被解析爲`key: value`的形式。並且包圍冒號的引號還更嚴格。例以下面的debug模塊中即便使用了引號也是錯誤的。

```
---
- hosts: localhost
tasks:
- shell: echo "{{inventory_hostname}}:haha"
register: hello
- debug: msg="{{hello.stdout}}: heihei"
```

由於它把`{{...}}`當成`key`,`heihei`當成`value`了。所以,必須將整個`debug`模塊的參數都包圍起來,顯式指定這一段是模塊的參數。但這樣會和原來的雙引號衝突,所以使用單引號。

```
---
- hosts: localhost
tasks:
- shell: echo "{{inventory_hostname}}:haha"
register: hello
- debug: 'msg="{{hello.stdout}}: heihei"'
```

可是,若是將`shell`模塊中的冒號後也尾隨上空格,即寫成`echo "{{inventory_hostname}}: haha"`,那麼`shell`模塊也會報錯。所以也要使用多個引號,正確的以下:

```---- hosts: localhosttasks:- shell: 'echo "{{inventory_hostname}}: haha"'register: hello- debug: 'msg="{{hello.stdout}}: heihei"'```

相關文章
相關標籤/搜索