Ansible系列(四):playbook應用和roles自動化批量安裝示例

`playbook`是`ansible`實現批量自動化最重要的手段。在其中可使用變量、引用、循環等功能,相比`ad-hoc`而言,其功能要強大的多。html


### yaml簡單示例
`ansible`的`playbook`採用`yaml`語法。如下是一個`yaml`格式的文件:mysql

```
---
# Members in Bob's family
name: Bob
age: 30
gender: Male
wife: 
name: Alice
age: 27
gender: Female
children: 
- name: Jim
age: 6
gender: Male
- name: Lucy
age: 3
gender: Female
```nginx


### ansible-playbook命令說明及playbook書寫簡單示例
如下是一個簡單的`playbook`示例。該示例執行兩個任務,第一個任務是執行一個`/bin/date`命令,第二個任務是複製`/etc/fstab`文件到目標主機上的`/tmp`下,它們分別使用了`ansible`的`command`模塊和`copy`模塊。web

```
cat /tmp/test.yaml 
---
- hosts: centos7
tasks: 
- name: execute date cmd
command: /bin/date
- name: copy fstab to /tmp
copy: src=/etc/fstab dest=/tmp
```正則表達式

書寫好`playbook`後,使用`ansible-playbook`命令來執行。`ansible-playbook`命令的選項和`ansible`命令選項絕大部分都相同。但也有其特有的選項。如下是截取出來的幫助信息。sql


```
ansible-playbook --help
Usage: ansible-playbook playbook.ymlshell

Options:
-e EXTRA_VARS,--extra-vars=EXTRA_VARS # 設置額外的變量,格式爲key/value。-e "key=KEY",
# 若是是文件方式傳入變量,則-e "@param_file"
--flush-cache # 清空收集到的fact信息緩存
--force-handlers # 即便task執行失敗,也強制執行handlers
--list-tags # 列出全部可獲取到的tags
--list-tasks # 列出全部將要被執行的tasks
-t TAGS,--tags=TAGS # 以tag的方式顯式匹配要執行哪些tag中的任務
--skip-tags=SKIP_TAGS # 以tag的方式忽略某些要執行的任務。被此處匹配的tag中的任務都不會執行
--start-at-task=START_AT_TASK # 今後task開始執行playbook
--step # one-step-at-a-time:在每個任務執行前都進行交互式確認
--syntax-check # 檢查playbook語法
```apache

有如下幾點須要注意下:
1. 默認狀況下,`ansible-playbook`和`ansible`是同樣的,都是同步阻塞模式,須要先在全部主機上執行完一個任務,纔會繼續下一個任務;
2. 在執行前會自動收集fact信息;
3. 從顯示結果中能夠判斷出任務是否真的執行了,抑或者是由於冪等性而沒有執行。
4. 每個play都包含數個task,且都有響應信息play recap。centos

### playbook的內容緩存

#### hosts和remoter_user
對於`playbook`中的每個`play`,使用`hosts`選項能夠定義要執行這些任務的主機或主機組,還可使用`remote_user`指定在遠程主機上執行任務的用戶,實際上`remote_user`是`ssh`鏈接到被控主機上的用戶,天然而然執行命令的身份也將是此用戶。

例如:

```
---
- hosts: centos6,centos7,192.168.100.59
remote_user: root
tasks: XXXXXX
```

雖然在hosts處可使用","分隔主機或主機組,但官方手冊上並無介紹該方法。除此以外,有如下幾種指定主機和主機組的方式:

- all或*,表示inventory中的全部主機。
- :,取並集。例如"host1:host2:group1"表示2臺主機加一個主機組。
- :&,取交集。例如"group1:&group2"表示兩個主機組中都有的主機。
- :!,排除。例如"group1:!host1"表示group1中排除host1主機的剩餘主機。
- 通配符,例如"web*.baidu.com"。
- 數字範圍,例如"web[0-5].baidu.com"。
- 字母範圍,例如"web[a-d].baidu.com"。
- 正則表達式以"~"開頭。例如"~web\d\.baidu\.com"。
此外,在`ansible`命令行或`ansible-playbook`命令行中,可使用`-l`選項來限定執行任務的主機。例如:

```
ansile centos -l host[1:5] -m ping
```

表示`centos`主機組中只有`host1`到`host5`才執行`ping`模塊。

還能夠在某個task上單獨定義執行該task的身份,這將覆蓋全局的定義。

```
---
- hosts: centos6,centos7,192.168.100.59
remote_user: root
tasks: 
- name: run a command
shell: /bin/date
- name: copy a file to /tmp
copy: src=/etc/fstab dest=/tmp
remote_user: myuser
也支持權限升級的方式。

---
- hosts: centos6,centos7,192.168.100.59
remote_user: yourname
tasks: 
- name: run a command
shell: /bin/date
- name: copy a file to /tmp
copy: src=/etc/fstab dest=/tmp
become: yes
become_method: sudo
become_user: root # 此項默認值就是爲root,因此可省
```

從上面的示例能夠看出`remote_user`實際上並非執行任務的絕對身份,它只是`ssh`鏈接過去的身份,只不過沒有指定`become`的時候,它正好就用此身份來運行任務。

#### task list
1. 特性

每一個play都包含一個hosts和一個tasks,hosts定義的是inventory中待控制的主機,tasks下定義的是一系列task任務列表,好比調用各個模塊。這些task按順序一次執行一個,直到全部被篩選出來的主機都執行了這個task以後纔會移動到下一個task上進行一樣的操做。

須要注意的是,雖然只有被篩選出來的主機會執行對應的task,可是全部主機(此處的全部主機表示的是,hosts選項所指定的那些主機)都會收到相同的task指令,全部主機收到指令後,ansible主控端會篩選某些主機,並經過ssh在遠程執行任務。也就是說,若是查看ansible-playbook -vvvv的信息,將會發現臨時任務文件會經過sftp發送到全部的被控主機上,可是隻有一部分被篩選(若是進行了篩選)的主機纔會ssh過去並遠程執行命令。

當某一臺被控主機執行某個任務出錯或失敗時,它將會被移除出任務輪詢列表。也就是說,對於某主機來講,某任務執行失敗,後續的全部任務都不會再去執行。固然,這不會影響其餘的主機執行任務(除非主機的任務之間有依賴關係)。

最重要的是,ansible中的task是**冪等性**的,屢次執行不會影響那些成功執行過的任務。另外冪等性還表如今執行失敗後若是修正了playbook再次執行,將不會影響那些本來已經執行成功的任務,即便是不一樣主機也不會影響。僅這方面而言,ansible對於排錯來講是極其友好的。固然,某些特殊的模塊或者特殊定義的task並不必定老是冪等的,例如最簡單的,執行一個command或者shell模塊的命令,它會重複執行。但也有辦法使其變得冪等,以command和shell模塊爲例,它們有兩個選項:creates和removes,它們分別表示被控主機上指定的文件存在(不存在)時就不執行命令。

2. 定義task的細節

(1). 能夠爲每一個task加上name項,也能夠多個task依賴於一個name。

例以下面的兩個例子。從兩個示例中能夠看出,兩個task其實都是屬於一個name的,第二個task無需再使用name命名。

示例一:
```
tasks: 
- name: do something to initialize mariadb
file: path=/mydata/data state=directory owner=mysql group=mysql mode=0755
- shell: /usr/bin/mysql_install_db --datadir=/mydata/data --user=mysql creates=/mydata/data/ibdata1
```

示例二:

```
tasks: 
- name: echo var passed by nginx 
shell: echo "{{ hi_var }}"
register: var_result
- debug: msg="{{ var_result.stdout }}"
```

實際上,name只是一種描述性語句,它能夠定義在任何地方。例如,定義在play的頂端。

```
---
- name: start a play
hosts: localhost
tasks:
```

(2). 既然是task,那麼必然會有其要執行的一個或多個任務,其本質是加載並執行ansible對應的模塊。在playbook中,每調用的一個模塊都稱爲一個action。
例如,定義一個確保服務是啓動狀態的task,有如下三種方法傳遞模塊參數:

```
tasks: 
- name: be sure the sshd is running
service: name=sshd state=started # 方法一: 定義爲key=value,直接傳遞參數給模塊

service: # 方法二: 定義爲key: value方式
name: sshd
state: started

service: # 方法三: 使用args內置關鍵字,而後定義爲key: value方式
args: 
name: sshd
state: started
```

但要注意,ping模塊、command和shell模塊是不須要key=value格式的。對於ping命令,能夠直接省略key=value。對於command和shell,只須要給定命令路徑和要接上去的選項或參數便可,且沒法使用上面的方法二。例以下面定義的是一個ntpdate命令,只需給定它的參數便可。

```
tasks: 
- name: execute command ntpdate
shell: /usr/sbin/ntpdate ntp1.aliyun.com
- name: ping host
ping:
```

對於command或shell模塊來講,有時候要考慮命令的返回狀態碼。若是要忽略非0狀態碼繼續執行任務,可使用如下兩種方式:

```
tasks: 
- name: ignore non_zero return code
shell: /usr/sbin/ntpdate ntp1.aliyun.com || /bin/true
```
或者
```
tasks: 
- name: another way to ignore the non_zero return code
shell: /usr/sbin/ntpdate ntp1.aliyun.com
ignore_errors: true
```
(3). 若是action的key=value太多,致使內容太長,能夠在上一行的縮進級別基礎上繼續縮進表示續行。
例如,下面的owner比src多縮進了4個空格。

```
tasks:
- name: Copy ansible inventory file to client
copy: src=/etc/fstab dest=/tmp
owner=root group=root mode=0644
```

(4).在action的value部分,能夠引用已經定義的變量,能夠是已定義好的自定義的變量,也能夠是內置變量。變量相關內容見後文。

(5).使用include指令,能夠將其餘的playbook文件包含到此playbook文件中。

### notify和handler
ansible中幾乎全部的模塊都具備冪等性,這意味着被控主機的狀態是否發生改變是能被捕捉的,即每一個任務的changed=true或changed=false。ansible在捕捉到changed=true時,能夠觸發notify組件(若是定義了該組件)。

notify是一個組件,並不是一個模塊,它能夠直接定義action,其主要目的是調用handler。例如:

```
tasks: 
- name: copy template file to remote host
template: src=/etc/ansible/nginx.conf.j2 dest=/etc/nginx/nginx.conf
notify: 
- restart nginx
- test web page
copy: src=nginx/index.html.j2 dest=/usr/share/nginx/html/index.html
notify: 
- restart nginx
```

這表示當執行template模塊的任務時,若是捕捉到changed=true,那麼就會觸發notify,若是分發的index.html改變了,那麼也重啓nginx(固然這是不必的)。notify下定義了兩個待調用的handler。handler主要用於重啓服務或者觸發系統重啓,除此以外不多使用handler。如下是這兩個handler的內容:

```
handlers: 
- name: restart nginx
service: name=nginx state=restarted
- name: test web page
shell: curl -I http://192.168.100.10/index.html | grep 200 || /bin/false
```

handler的定義和tasks的定義徹底同樣,惟一須要限定的是handler中task的name必須和notify中定義的名稱相同。

注意,notify是在執行完一個play中全部task後被觸發的,在一個play中也只會被觸發一次。意味着若是一個play中有多個task出現了changed=true,它也只會觸發一次。例如上面的示例中,向nginx複製配置文件和複製index.html時若是都發生了改變,都會觸發重啓apache操做。可是隻會在執行完play後重啓一次,以免多餘的重啓。

### 標籤tag
能夠爲playbook中的每一個任務都打上標籤,標籤的主要做用是能夠在ansible-playbook中設置只執行哪些被打上tag的任務或忽略被打上tag的任務。

```
tasks: 
- name: make sure apache is running
service: name=httpd state=started
tags: apache
- name: make sure mysql is running
service: name=mysqld state=started
tags: mysql
```

如下是ansible-playbook命令關於tag的選項。

```
--list-tags # list all available tags
-t TAGS, --tags=TAGS # only run plays and tasks tagged with these values
--skip-tags=SKIP_TAGS # only run plays and tasks whose tags do not match these values
```
### include和roles
若是將全部的play都寫在一個playbook中,很容易致使這個playbook文件變得臃腫龐大,且不易讀。所以,能夠將多個不一樣任務分別寫在不一樣的playbook中,而後使用include將其包含進去便可。而role則是整合playbook的方式。不管是include仍是role,其目的都是分割大playbook以及複用某些細化的play甚至是task。


#### include
能夠將task列表和handlers獨立寫在其餘的文件中,而後在某個playbook文件中使用include來包含它們。除此以外,還能夠寫獨立的playbook文件,使用include來包含這個文件。

也便是說,include能夠導入兩種文件:導入task、導入playbook。

1. 一種是任務列表式的文件(沒有tasks或handlers指令),它只能在tasks或handlers指令的子選項處使用include包含。這種方式能夠傳遞變量到被包含的文件中。

假設某個task列表文件/yaml/a.yaml內容以下:

```
---
- name: execute ntpdate
shell: /usr/sbin/ntpdate ntp1.aliyun.com
```

在同目錄/yaml下有一個名爲test.yaml的playbook(除了role,playbook中全部相對路徑都是基於playbook的),在此playbook中使用include來包含它,若是使用相對路徑將會包含同目錄下的文件。

```
---
- hosts: centos7
tasks:
- include: a.yaml
```

能夠在include的時候傳遞變量給對應的文件,這樣在被包含的文件中就能夠引用該變量的值。

```
---
- hosts: centos7
tasks:
- include: a.yaml sayhi="hello world"
```

或者

```
---
- hosts: centos7
tasks:
- include: a.yaml
vars: 
sayhi: "hello world"
```

而後能夠在被包含的文件a.yaml中使用該變量。例如:

```
---
- name: execute ntpdate
shell: /usr/sbin/ntpdate ntp1.aliyun.com
- name: say hi to world
debug: msg="{{ sayhi }}"
```
2. 另外一種是include整個playbook文件,即include的動做是加載一個或多個play,因此寫在頂級列表的層次。

```
- name: this is a play at the top level of a file
hosts: all
remote_user: root

tasks:

- name: say hi
tags: foo
shell: echo "hi..."

- include: load_balancers.yml sayhi="hello world"
- include: webservers.yml
- include: dbservers.yml

any other operations
```

須要說明的是,在ansible 2.4版本中,添加了includes和imports兩種導入的方式,它們對靜態和動態導入支持的更細化,而ansible 2.3及之前的include語句已經廢棄,但仍可用。

#### roles
roles意爲角色,主要用於封裝playbook實現複用性。在ansible中,roles經過文件的組織結構來展示。

對於一個role,它的文件組織結構以下圖所示:
![ansible role_1](http://ot8956ufo.bkt.clouddn.com/ansible_role_1.png)

首先須要有一個roles目錄。同時,在roles目錄所在目錄中,還要有一個playbook文件,此處爲nginx.yml,nginx.yml文件是ansible-playbook須要執行的文件,在此文件中定義了角色,當執行到角色時,將會到roles中對應的角色目錄中尋找相關文件。

roles目錄中的子目錄是便是各個role。例如,此處只有一個名爲nginx的role,在role目錄中,有幾個固定名稱的目錄(若是沒有則忽略)。在這些目錄中,還要有一些固定名稱的文件,除了固定名稱的文件,其餘的文件能夠隨意命名。如下是各個目錄的含義:

- tasks目錄:存放task列表。若role要生效,此目錄必需要有一個主task文件main.yml,在main.yml中可使用include包含同目錄(即tasks)中的其餘文件。
- handlers目錄:存放handlers的目錄,若要生效,則文件必須名爲main.yml文件。
- files目錄:在task中執行copy或script模塊時,若是使用的是相對路徑,則會到此目錄中尋找對應的文件。
- templates目錄:在task中執行template模塊時,若是使用的是相對路徑,則會到此目錄中尋找對應的模塊文件。
- vars目錄:定義專屬於該role的變量,若是要有var文件,則必須爲main.yml文件。
- defaults目錄:定義角色默認變量,角色默認變量的優先級最低,會被任意其餘層次的同名變量覆蓋。若是要有defaults文件,則必須爲main.yml文件。
- meta目錄:用於定義角色依賴,若是要有角色依賴關係,則文件必須爲main.yml。

因此,相對完整的role的文件組織結構以下圖:
![ansible role_2](http://ot8956ufo.bkt.clouddn.com/ansible_role_2.png)
若是是多個role,則在roles同級目錄下定義多個入站(做用相似於C語言的main函數)文件(如上面的nginx.yml),並在roles目錄下建立對應的role目錄便可。
![ansible role_3](http://ot8956ufo.bkt.clouddn.com/ansible_role_3.png)

固然,若是不是使用相對路徑,那麼role的文件結構就無所謂了,可是roles功能開發出來,就是爲了解決文件混亂和playbook臃腫問題的。因此若是能夠,儘可能使用推薦的role文件結構。

另外,若是role中出現的task、var、handler等和單獨定義的對象同名衝突了,則優先執行role中的內容。

如下是nginx role的入站文件nginx.yml的內容。

```---- hosts: centos7roles:- nginx```

相關文章
相關標籤/搜索