自動化運維工具Ansible實戰---Playbooks劇本使用
一、Playbook 簡介
Playbooks與Ad-Hoc相比,是一種完全不同的運用Ansible的方式,而且是非常之強大的;也是系統ansible命令的集合,其利用yaml語言編寫,執行過程,ansbile-playbook命令根據自上而下的順序依次執行。
簡單來說,Playbooks 是一種簡單的配置管理系統與多機器部署系統的基礎。與現有的其他系統有不同之處,且非常適合於複雜應用的部署。
同時,Playbooks開創了很多特性,它可以允許你傳輸某個命令的狀態到後面的指令,如你可以從一臺機器的檔案中抓取內容並附為變數,然後在另一臺機器中使用,這使得你可以實現一些複雜的部署機制,這是ansible命令無法實現的。
Playbooks可用於宣告配置,更強大的地方在於,在Playbooks中可以編排有序的執行過程,甚至於做到在多組機器間,來回有序的執行特別指定的步驟。並且可以同步或非同步的發起任務。
我們使用Ad-Hoc時,主要是使用 /usr/bin/ansible 程式執行任務.而使用Playbooks時,更多是將之放入原始碼控制之中,用之推送你的配置或是用於確認你的遠端系統的配置是否符合配置規範。
在如右的連結中:ansible-examples repository,有一些整套的Playbooks,它們闡明瞭上述的這些技巧。
二、Playbook 語言的示例
playbooks 的格式是yaml,語法做到最小化,意在避免 playbooks 成為一種程式語言或是指令碼,但它也並不是一個配置模型或過程的模型。
playbook是由一個或多個“play”組成的列表。play的主要功能在於將事先歸併為一組的主機裝扮成事先通過Ansible中的tasks定義好的角色(play的內容被稱為tasks,即任務)。從根本上來講所謂tasks無非是呼叫Ansible的一個module。將多個“play”組織在一個playbook中即可以讓它們聯同起來按事先編排的機制一同工作。
“plays”算是一個類比,可以通過多個plays告訴系統做不同的事情,不僅是定義一種特定的狀態或模型。也可以在不同時間執行不同的plays。
對於初學者,這裡有一個playbook,其中僅包含一個play:
--- - hosts: webservers vars: http_port: 80 max_clients: 200 remote_user: root tasks: - name: ensure apache is at the latest version yum: pkg=httpd state=latest - name: write the apache config file template: src=/srv/httpd.j2 dest=/etc/httpd.conf notify: - restart apache - name: ensure apache is running service: name=httpd state=started handlers: - name: restart apache service: name=httpd state=restarted
第一行中,檔案開頭為 ---;這是YAML將檔案解釋為正確的文件的要求。YAML允許多個“文件”存在於一個檔案中,每個“文件”由 --- 符號分割,但Ansible只需要一個檔案存在一個文件即可,因此這裡需要存在於檔案的開始行第一行。
YAML對空格非常敏感,並使用空格來將不同的資訊分組在一起,在整個檔案中應該只使用空格而不使用製表符,並且必須使用一致的間距,才能正確讀取檔案。相同縮排級別的專案被視為同級元素。
以 - 開頭的專案被視為列表專案。作為雜湊或字典操作,它具有key:value格式的項。YAML文件基本上定義了一個分層的樹結構,其中位於左側是包含的元素。YAML副檔名通常為.yaml或者.yml。
接下來,我們將分別講解 playbook 語言的多個特性
三、Playbook 構成
Playbook主要有以下四部分構成:
- target section:定義將要執行playbook的遠端主機組
- variable section:定義playbook執行時需要使用的變數
- task section:定義將要在遠端主機上執行的任務列表
- handler section:定義task執行完成以後需要呼叫的任務
而Playbook對應的目錄層有五個,分別如下:
一般所需的目錄層有:(視情況可變化)
- vars 變數層
- tasks 任務層
- handlers 觸發條件
- files 檔案
- template 模板
接下來,我們將分別介紹構成playbook的四層結構。
1、Hosts(主機)與Users(使用者)
我們可以為playbook中的每一個play,個別的選擇操作的目標機器是哪些,以哪個使用者身份去完成要執行的步驟(called tasks)
---
- hosts: webservers
remote_user: root
tasks:
- name: test connection
remote_user: yourname
sudo: yes
playbook中的每一個play的目的都是為了讓某個或某些主機以某個指定的使用者身份執行任務。
hosts:用於指定要執行指定任務的主機其可以是一個或多個,由逗號為分隔符分隔主機組
remote_user:用於指定遠端主機上的執行任務的使用者。不過remote_user也可用於各tasks中。也可以通過指定其通過sudo的方式在遠端主機上執行任務其可用於play全域性或某任務。此外甚至可以在sudo時使用sudo_user指定sudo時切換的使用者
user:與remote_user相同
sudo:如果設定為yes,執行該任務組的使用者在執行任務的時候,獲取root許可權
sudo_user:如果設定user為bob,sudo為yes,sudo_user為tom時,則bob使用者在執行任務時會獲得tom使用者的許可權
connection:通過什麼方式連線到遠端主機,預設為ssh
gather_facts:除非明確說明不需要在遠端主機上執行setup模組,否則預設自動執行。如果確實不需要setup模組傳遞過來的變數,則可以將該選項設定為False
說明:
- 引數remote_user以前寫做user,在Ansible 1.4以後才改為remote_user。主要為了不跟user模組混淆(user模組用於在遠端系統上建立使用者)
- 如果我們需要在使用sudo時指定密碼,可在執行ansible-playbook命令時加上選項 --ask-sudo-pass (-K)。如果使用sudo時,playbook疑似被掛起,可能是在sudo prompt處被卡住,這時可執行 Control-C(正文結束字元)殺死卡住的任務,再重新執行一次。
- 當使用sudo_user切換到非root使用者時,模組的引數會暫時寫入/tmp目錄下的一個隨機臨時檔案。當命令執行結束後。臨時檔案立即刪除。這種情況發生在普通使用者的切換時,比如從“bob”切換到“tom”,切換到root賬戶時,不會發生,如從“bob”切換到 “root”,直接以普通使用者或root身份登入也不會發生。如果不希望這些資料在短暫的時間內可以被讀取(不可寫),請避免在sudo_user中傳遞未加密的密碼。其它情況下,“/tmp” 目錄不被使用,這種情況不會發生。Ansible 也有意識的在日誌中不記錄密碼引數
2、tasks 列表和 action
每一個play包含了一個tasks列表(任務列表)。
任務列表中的各任務按次序逐個在hosts中指定的所有主機上執行即在所有主機上完成第一個任務後再開始第二個。在自上而下執行某playbook時如果中途發生錯誤,所有已執行任務都將回滾,因此在更正playbook後重新執行即可。
每一個tasks必須有一個名稱name,這樣在執行playbook時,從其輸出的任務執行資訊中可以很好的辨別出是屬於哪一個tasks的。如果沒有定義name,“action”的值將會用作輸出資訊中標記特定的tasks。
tasks的目的是使用指定的引數執行模組,而在模組引數中可以使用變數。模組執行是冪等的,這意味著多次執行是安全的,因為其結果均一致。每個tassk都應該有其name用於playbook的執行結果輸出,建議其內容儘可能清晰地描述任務執行步驟。如果未提供name則action的結果將用於輸出。
如果要宣告一個tasks,以前有一種格式:“action: module options”(可能在一些老的playbooks中還能見到)。現在推薦使用更常見的格式:“module: options”。
下面是一種基本的taska的定義,service moudle使用key=value格式的引數,這也是大多數module使用的引數格式:
tasks:
- name: make sure apache is running
service: name=httpd state=running
# 在眾多模組中,只有command和shell模組僅需要給定一個列表而無需使用“key=value”格式如下:
tasks:
- name: disable selinux
command: /sbin/setenforce 0
# 使用command module和shell module時,我們需要關心返回碼資訊,如果有一條命令,它的成功執行的返回碼不是0,我們可以這樣做:
tasks:
- name: run this command and ignore the result
shell: /usr/bin/somecommand || /bin/true
# 或者使用ignore_errors來忽略錯誤資訊
tasks:
- name: run this command and ignore the result
shell: /usr/bin/somecommand
ignore_errors: True
# 如果action行看起來太長,可以使用space(空格)或者indent(縮排)隔開連續的一行:
tasks:
- name: Copy ansible inventory file to client
copy: src=/etc/ansible/hosts dest=/etc/ansible/hosts
owner=root group=root mode=0644
3、Handlers 在發生改變時執行的操作
上面我們曾提到過,module具有“冪等”性,所以當遠端主機被人改動時,可以重放playbooks達到恢復的目的。playbooks本身可以識別這種改動,並且有一個基本的event system(事件系統),可以響應這種改動。
(當發生改動時)“notify”這個actions會在playbook的每一個tasks結束時被觸發,而且即使有多個不同的tasks通知改動的發生,“notify” actions只會被觸發一次。這樣可以避免多次有改變發生時每次都執行指定的操作,取而代之僅在所有的變化發生完成後一次性地執行指定操作。
在“notify”中列出的操作稱為handlers,即“notify”中呼叫handlers中定義的操作。
說明:
在“notify”中定義內容一定要和tasks中定義的 - name 內容一樣,這樣才能達到觸發的效果,否則會不生效。
舉例來說,比如多個resources指出因為一個配置檔案被改動,所以apache需要重新啟動,但是重新啟動的操作只會被執行一次。
這裡有一個例子,當一個檔案的內容被改動時,重啟兩個services:
- name: template configuration file
template: src=template.j2 dest=/etc/foo.conf
notify:
- restart memcached
- restart apache
“notify”下列出的即是 handlers。
handlers也是一些tasks的列表,通過名字來引用,它們和一般的tasks並沒有什麼區別。handlers是由通知者進行notify,如果沒有被notify,handlers不會執行。不管有多少個通知者進行了notify,等到play中的所有tasks執行完成之後,handlers也只會被執行一次。
這裡是一個 handlers 的示例:
handlers:
- name: restart memcached
service: name=memcached state=restarted
- name: restart apache
service: name=apache state=restarted
Handlers最佳的應用場景是用來重啟服務,或者觸發系統重啟操作。除此以外很少用到了。
說明:handlers會按照宣告的順序執行。
4、tags
tags用於讓使用者選擇執行或略過playbook中的部分程式碼。ansible具有冪等性,因此會自動跳過沒有變化的部分,即便如此,有些程式碼為測試其確實沒有發生變化的時間依然會非常的長。此時如果確信其沒有變化就可以通過tags跳過這些程式碼片斷。
5、示例
通過playbook新增使用者示例
(1)給遠端主機新增使用者test
[[email protected] ~]# vim user.yml
---
- name: create user
hosts: all
user: root
gather_facts: False
vars:
- user: test
tasks:
- name: create user
user: name={{ user }}
上面的playbook 實現的功能是新增一個使用者:
name:對該playbook實現的功能做一個概述,後面執行過程中,會列印name變數的值
hosts:指定了對哪些主機進行參作
user:指定了使用什麼使用者登入遠端主機操作
gather_facts:指定了在以下任務部分執行前,是否先執行setup模組獲取主機相關資訊,這在後面的task會使用到setup獲取的資訊時用到
vars:指定了變數,這裡指定了一個user變數,其值為test,需要注意的是,變數值一定要用引號引起來
tasks:指定了一個任務,其下面的name引數同樣是對任務的描述,在執行過程中會打印出來。user提定了呼叫user模組,name是user模組裡的一個引數,而增加的使用者名稱字呼叫了上面user變數的值
檢視執行結果如下:
[[email protected] ~]# ansible-playbook user.yml
(2)刪除遠端主機test的賬號
只需user: name="{{ user }}" state=absent remove=yes 即可
[[email protected] ~]# vim user.yml
---
- name: create user
hosts: all
user: root
gather_facts: False
vars:
- user: test
tasks:
- name: create user
user: name={{ user }} state=absent remove=yes
[[email protected] ~]# ansible all -m command -a "id test"
(3)通過playbook 安裝apache
安裝 httpd 服務,將本地準備好的配置檔案 copy 過去,並且啟動服務
[[email protected] ~]# vim apache.yml
---
- hosts: all
remote_user: root
gather_facts: False
tasks:
- name: install apache on CentOS 7
yum: name=httpd state=present
- name: copy httpd conf
copy: src=/etc/httpd/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf
- name: start apache service
service: name=httpd state=started
[[email protected] ~]# ansible-playbook apache.yml
[[email protected] ~]# ansible all -m shell -a "lsof -i:80"
(4)通過playbook 安裝apache(修改埠,並帶有vars變數)
將httpd.conf監聽的埠改為8080,然後重新覆蓋配置檔案,當這個配置檔案發生改變時,就觸發handler進行服務重啟
notify 這個 action可用於在每個play的最後被觸發,這樣可以避免多次有改變發生時每次都執行指定的操作,notify中列出的操作稱為handler
[[email protected] ~]# vim apache.yml
---
- hosts: all
remote_user: root
gather_facts: False
vars:
src_http_dir: /etc/httpd/conf
dest_http_dir: /etc/httpd/conf
tasks:
- name: install apache on CentOS 7
yum: name=httpd state=present
- name: copy httpd conf
copy: src={{ src_http_dir }}/httpd.conf dest={{ dest_http_dir }}/httpd.conf
notify:
- systemctl restart httpd
- name: start apache service
service: name=httpd state=restarted
handlers:
- name: restart apache
service: name=httpd state=restarted
[[email protected] ~]# vim /etc/httpd/conf/httpd.conf
42 Listen 80 --》 修改為8080
[[email protected] ~]# ansible-playbook apache.yml
[[email protected] ~]# ansible all -m shell -a "lsof -i:8080"
四、Playbook 常用模組
Playbook的模組與在Ansible命令列下使用的模組有一些不同。這主要是因為在playbook中會使用到一些facts變數和一些通過setup模組從遠端主機上獲取到的變數。有些模組沒法在命令列下執行,就是因為它們需要這些變數。而且即使那些可以在命令列下工作的模組也可以通過playbook的模組獲取一些更高階的功能。
具體模組可參考官網(http://docs.ansible.com/ansible/latest/list_of_all_modules.html)。
這裡從官方分類的模組裡選擇最常用的一些模組進行介紹。
1、template模組
(1)簡介
- 在實際應用中,我們的配置檔案有些地方可能會根據遠端主機的配置的不同而有稍許的不同,template可以使用變數來接收遠端主機上setup收集到的facts資訊,針對不同配置的主機,定製配置檔案。用法大致與copy模組相同
- template_host包含模板機器的節點名稱
- template_uid所有者的數字使用者標識
- template_path是模板的路徑
- template_fullpath是模板的絕對路徑
- template_run_date是模板呈現的日期
(2)引數
attributes(2.3後新增):檔案或目錄的屬性
backup:如果原目標檔案存在,則先備份目標檔案
block_end_string(2.4後新增):標記塊結束的字串
block_start_string(2.4後新增):標記塊的開始的字串
dest:目標檔案路徑
follow(2.4後新增):是否遵循目標中的檔案連結
force:是否強制覆蓋,預設為yes
group:目標檔案或目錄的所屬組
owner:目標檔案或目錄的所屬主
mode:目標檔案的許可權。對於那些習慣於/usr/bin/chmod的記住,模式實際上是八進位制數字(如0644or 01777)。離開前導零可能會有意想不到的結果。從版本1.8開始,可以將模式指定為符號模式(例如u+rwx或u=rw,g=r,o=r)
newline_sequence(2.4後新增):指定用於模板檔案的換行符序列(\n、\r、\r\n)
src:源模板檔案路徑
trim_blocks(2.4後新增):如果這設定為True,則刪除塊後的第一個換行符
validate:在複製之前通過命令驗證目標檔案,如果驗證通過則複製
variable_end_string(2.4後新增):標記列印語句結束的字串
variable_start_string(2.4後新增):標記列印語句開頭的字串
(3)示例
# 官方簡單示例
- template: src=/mytemplates/foo.j2 dest=/etc/file.conf owner=bin group=wheel mode=0644
- template: src=/mytemplates/foo.j2 dest=/etc/file.conf owner=bin group=wheel mode="u=rw,g=r,o=r"
- template: src=/mine/sudoers dest=/etc/sudoers validate='visudo -cf %s'
playbook的引用該模板配置檔案的方法示例:
- name: Setup BIND
host: all
tasks:
- name: configure BIND
template: src=/etc/httpd/conf/httpd.conf dest=/etc/httpd/conf/httpd.conf owner=root group=root mode=0644
# 或者是這樣
- template:
src: /etc/httpd/conf/httpd.conf
dest: /etc/file.conf
owner: root
group: root
mode: 0644
# 建立DOS樣式文字檔案
- template:
src: config.ini.j2
dest: /share/windows/config.ini
newline_sequence: '\r\n'
2、set_fact模組
(1)簡介
- set_fact模組可以自定義facts,這些自定義的facts可以通過template或者變數的方式在playbook中使用
- 如果你想要獲取一個程序使用的記憶體的百分比,則必須通過set_fact來進行計算之後得出其值,並將其值在playbook中引用
(2)引數
cacheable(2.4後新增):可快取
key_value:該set_fact模組將key=value作為變數,設定在劇本範圍中
(3)示例
配置mysql innodb buffer size的示例
[[email protected] ~]# echo "# Configure the buffer pool" >> /etc/my.cnf
[[email protected] ~]# echo "innodb_buffer_pool_size = {{ innodb_buffer_pool_size_mb|int }}M" >> /etc/my.cnf
[[email protected] ~]# vim set_fact.yml
---
- name: Configure Mariadb
hosts: all
tasks:
- name: install Mariadb
yum: name=mariadb-server state=installed
- name: Calculate InnoDB buffer pool size
set_fact: innodb_buffer_pool_size_mb={{ ansible_memtotal_mb / 2 }}
- name: Configure Mariadb
template: src=/etc/my.cnf dest=/etc/my.cnf owner=root group=root mode=0644
- name: Start Mariadb
service: name=mariadb state=started enabled=yes
handlers:
- name: restart Mariadb
service: name=mariadb state=restarted
[[email protected] ~]# ansible-playbook set_fact.yml
在遠端主機上檢視該配置
[[email protected] ~]# vim /etc/my.cnf
3、pause模組
(1)簡介
- 在playbook執行的過程中暫停一定時間或者提示使用者進行某些操作
- 要為每個主機暫停、等待、休眠,可以使用wait_for模組
- 如果您想提前暫停而不是設定為過期,或者您需要完全中止劇本執行,則可以使用Ctrl + c。要繼續提前按ctrl + c然後c。要中止一個劇本按ctrl + c然後a
- 該模組也支援Windows目標
(2)引數
echo(2.5後增加):控制鍵入時是否顯示鍵盤輸入。如果設定了“秒”或“分鐘”,則不起作用
minutes:暫停多少分鐘
seconds:暫停多少秒
prompt:列印一串資訊提示使用者操作
(3)示例
# 暫停5分鐘以建立應用程式快取。
- pause:
minutes: 5
# 有用的提醒,提醒您注意更新後的內容。
- pause:
prompt: "Make sure org.foo.FooOverload exception is not present"
# 暫停以後獲得一些資訊。
- pause:
prompt: "Enter a secret"
echo: no
4、wait_for模組
(1)簡介
- wait_for模組是在playbook的執行過程中,等待某些操作完成以後再進行後續操作
(2)引數
active_connection_states(2.3後新增) :被計為活動連線的TCP連線狀態列表
connect_timeout:在下一個任務執行之前等待連線的超時時間
delay:等待一個埠或者檔案或者連線到指定的狀態時,預設超時時間為300秒,在這等待的300s的時間裡,wait_for模組會一直輪詢指定的物件是否到達指定的狀態,delay即為多長時間輪詢一次狀態
exclude_hosts(1.8後新增):在查詢狀態的活動TCP連線時要忽略的主機或IP的列表drained
host:wait_for模組等待的主機的地址,預設為127.0.0.1
msg(2.4後新增):這會覆蓋正常的錯誤訊息,使其不符合所需的條件
port:wait_for模組等待的主機的埠
path:檔案路徑,只有當這個檔案存在時,下一任務才開始執行,即等待該檔案建立完成
search_regex(1.4後新增):可以用來匹配檔案或套接字連線中的字串。預設為多行正則表示式
sleep(2.3後新增):檢查之間睡眠的秒數,在2.3之前,這被硬編碼為1秒
state:等待的狀態,即等待的檔案或埠或者連線狀態達到指定的狀態時,下一個任務開始執行。當等的物件為埠時,狀態有started,stoped,即埠已經監聽或者埠已經關閉;當等待的物件為檔案時,狀態有present或者started,absent,即檔案已建立或者刪除;當等待的物件為一個連線時,狀態有drained,即連線已建立。預設為started
timeout:wait_for的等待的超時時間,預設為300秒
(3)示例
- name: create task
hosts: all
tasks:
# 等待8080埠已正常監聽,才開始下一個任務,直到超時
- wait_for: port=8080 state=started
# 等待8000埠正常監聽,每隔10s檢查一次,直至等待超時
- wait_for: port=8081 delay=10
# 等待8000埠直至有連線建立
- wait_for: host=0.0.0.0 port=8000 delay=10 state=drained
# 等待8000埠有連線建立,如果連線來自192.168.8.66或者192.168.8.8,則忽略
- wait_for: host=0.0.0.0 port=8000 state=drained exclude_hosts=192.168.8.66,192.168.8.8
# 等待/tmp/foo檔案已建立
- wait_for: path=/tmp/foo
# 等待/tmp/foo檔案已建立,而且該檔案中需要包含completed字串
- wait_for: path=/tmp/foo search_regex=completed
# 等待/var/lock/file.lock被刪除
- wait_for: path=/var/lock/file.lock state=absent
# 等待指定的程序被銷燬
- wait_for: path=/proc/3466/status state=absent
# 等待openssh啟動,10s檢查一次
- local_action: wait_for port=22 host="{{ ansible_ssh_host | default(inventory_hostname) }}" search_regex=OpenSSH delay=10
5、assemble模組
(1)簡介
- assemble模組用於組裝檔案,即將多個零散的檔案(稱為碎片),合併一個目標檔案
(2)引數
attributes(2.3後新增):檔案或目錄應的屬性
backup:建立一個備份檔案(如果yes),包括時間戳資訊
decrypt(2.4後新增):控制使用保管庫對原始檔進行自動解密
delimiter(1.4後新增):分隔檔案內容的分隔符
dest:使用所有原始檔的連線建立的檔案,合併後的大檔案路徑
group:合併後的大檔案的所屬組
owner:合併後的大檔案的所屬主
ignore_hidden(2.0後新增):組裝時,是否忽略隱藏檔案,預設為no
mode:合併後的大檔案的許可權。對於那些習慣於/usr/bin/chmod的記住,模式實際上是八進位制數字(如0644or 01777)。離開前導零可能會有意想不到的結果。從版本1.8開始,可以將模式指定為符號模式(例如u+rwx或u=rw,g=r,o=r)
regexp:在regex匹配檔名時彙編檔案。如果未設定,則所有檔案都被組合。所有“\”(反斜槓)必須轉義為“\”才能符合yaml語法
remote_src(1.4後新增):如果為False,它將在本地主機上搜索src,如果為True,它將轉到src的遠端主機。預設值為True
src:原始檔(即零散檔案)的路徑
validate(2.0後新增):與template的validate相同,指定命令驗證檔案
(3)示例
# 指定原始檔(即零散檔案)的路徑,合併一個目標檔案someapp.conf
- assemble:
src: /etc/someapp/fragments
dest: /etc/someapp/someapp.conf
# 指定一個分隔符,將在每個片段之間插入
- assemble:
src: /etc/someapp/fragments
dest: /etc/someapp/someapp.conf
delimiter: '### START FRAGMENT ###'
# 在SSHD傳遞驗證後,將新的“sshd_config”檔案複製到目標地址
- assemble:
src: /etc/ssh/conf.d/
dest: /etc/ssh/sshd_config
validate: '/usr/sbin/sshd -t -f %s'
6、add_host模組
1、簡介
- add_host模組使用變數在清單中建立新的主機組,以便在以後的相同劇本中使用。獲取變數以便我們可以更充分地定義新主機
- add_host模組在playbook執行的過程中,動態的新增主機到指定的主機組中
2、引數
groups:要新增主機至指定的組,以逗號分隔
name:要新增的主機名或IP地址,包含冒號和埠號
3、示例
# 新增主機到webservers組中,主機的變數foo的值為42
- name: add host to group 'just_created' with variable foo=42
add_host:
name: "{{ ip_from_ec2 }}"
groups: just_created
foo: 42
# 將主機新增到多個組
- name: add host to multiple groups
add_host:
hostname: "{{ new_ip }}"
groups:
- group1
- group2
# 向主機新增一個非本地埠的主機
- name: add a host with a non-standard port local to your machines
add_host:
name: "{{ new_ip }}:{{ new_port }}"
# 新增一個通過隧道到達的主機別名(Ansible <= 1.9)
- name: add a host alias that we reach through a tunnel (Ansible <= 1.9)
add_host:
hostname: "{{ new_ip }}"
ansible_ssh_host: "{{ inventory_hostname }}"
ansible_ssh_port: "{{ new_port }}"
# 新增一個通過隧道到達的主機別名(Ansible >= 2.0)
- name: add a host alias that we reach through a tunnel (Ansible >= 2.0)
add_host:
hostname: "{{ new_ip }}"
ansible_host: "{{ inventory_hostname }}"
ansible_port: "{{ new_port }}"
7、group_by模組
(1)簡介
- group_by模組在playbook執行的過程中,動態的建立主機組
(2)引數
key:其值將被用作組的變數
parents(2.4後新增):父組的列表
(3)示例
# 建立主機組
- group_by:
key: machine_{{ ansible_machine }}
# 建立類似“kvm-host”的主機組
- group_by:
key: virt_{{ ansible_virtualization_type }}_{{ ansible_virtualization_role }}
# 建立巢狀主機組
- group_by:
key: el{{ ansible_distribution_major_version }}-{{ ansible_architecture }}
parents:
- el{{ ansible_distribution_major_version }}
8、debug模組
(1)簡介
- debug模組在執行過程中列印語句,可用於除錯變數或表示式中輸出資訊
- 用於與“when:”指令一起除錯
(2)引數
msg:除錯輸出的訊息,列印自定義訊息
var:將某個任務執行的輸出作為變數傳遞給debug模組,debug會直接將其列印輸出
verbosity(2.1後新增):debug的執行除錯級別
(3)示例
# 為每個主機列印IP地址和閘道器
- debug:
msg: "System {{ inventory_hostname }} has uuid {{ ansible_product_uuid }}"
- debug:
msg: "System {{ inventory_hostname }} has gateway {{ ansible_default_ipv4.gateway }}"
when: ansible_default_ipv4.gateway is defined
- shell: /usr/bin/uptime
register: result
- debug:
var: result # 直接將上一條指令的結果作為變數傳遞給var,由debug打印出result的值
verbosity: 2
- name: Display all variables/facts known for a host
debug:
var: hostvars[inventory_hostname]
verbosity: 4
9、fail模組
(1)簡介
- fail模組用於終止當前playbook的執行,通常與條件語句組合使用,當滿足條件時,終止當前play的執行
- 可以直接由failed_when取代
(2)引數
msg:終止前打印出資訊,用於執行失敗的自定義訊息
(3)示例
# 執行失敗錢打印出自定義資訊
- fail:
msg: "The system may not be provisioned according to the CMDB status."
when: cmdb_status != "to-be-staged"
五、Playbook 迴圈
在使用Ansible做自動化運維的時候,免不了的要重複執行某些操作,如:新增幾個使用者,建立幾個MySQL使用者併為之賦予許可權,操作某個目錄下所有檔案等等。好在playbook支援迴圈語句,可以使得某些需求很容易而且很規範的實現。
以下主要來自他人分享。
1、with_items
with_items是playbooks中最基本也是最常用的迴圈語句。
示例:
tasks:
- name:Secure config files
file: path=/etc/{{ item }} mode=0600 owner=root group=root
with_items:
- my.cnf
- shadow
- fstab
# 或
with_items:"{{ somelist }}"
上面的例子說明在/etc下建立許可權級別為0600,屬主屬組都是root三個檔案,分別為my.cnf、shadow、fstab
使用with_items迭代迴圈的變數可以是個單純的列表,也可以是一個較為複雜 的資料結果,如字典型別:
tasks:
- name: add several users
user: name={{ item.name }} state=present groups={{ item.groups }}
with_items:
- { name: 'testuser1', groups: 'wheel' }
- { name: 'testuser2', groups: 'root' }
2、with_nested巢狀迴圈
示例:
tasks:
- name: give users access to multiple databases
mysql_user: name={{ item[0] }} priv={{ item[1] }}.*:ALL append_privs=yes password=foo
with_nested:
- [ 'alice', 'bob' ]
- [ 'clientdb', 'employeedb', 'providerdb' ]
item[0]是迴圈的第一個列表的值['alice','bob']。
item[1]是第二個列表的值。表示迴圈建立alice和bob兩個使用者,並且為其賦予在三個資料庫上的所有許可權。
也可以將使用者列表事先賦值給一個變數:
tasks:
- name: here, 'users' contains the above list of employees
mysql_user: name={{ item[0] }} priv={{ item[1] }}.*:ALL append_privs=yes password=foo
with_nested:
- "{{users}}"
- [ 'clientdb', 'employeedb', 'providerdb' ]
3、with_dict
with_dict可以遍歷更復雜的資料結構:
假如有如下變數內容:
users:
alice:
name: Alice Appleworth
telephone: 123-456-7890
bob:
name: Bob Bananarama
telephone: 987-654-3210
現在需要輸出每個使用者的使用者名稱和手機號:
tasks:
- name: Print phone records
debug: msg="User {{ item.key }} is {{ item.value.name }} ({{ item.value.telephone }})"
with_dict: "{{ users }}"
4、with_fileglob檔案匹配遍歷
可以指定一個目錄,使用with_fileglob可以迴圈這個目錄中的所有檔案,示例如下:
tasks:
- name:Make key directory
file: path=/root/.sshkeys ensure=directory mode=0700 owner=root group=root
- name:Upload public keys
copy: src={{ item }} dest=/root/.sshkeys mode=0600 owner=root group=root
with_fileglob:
- keys/*.pub
- name:Assemble keys into authorized_keys file
assemble: src=/root/.sshkeys dest=/root/.ssh/authorized_keysmode=0600 owner=root group=root
5、with_subelement遍歷子元素
假如現在需要遍歷一個使用者列表,並建立每個使用者,而且還需要為每個使用者配置以特定的SSH key登入。變數檔案內容如下:
users:
- name: alice
authorized:
- /tmp/alice/onekey.pub
- /tmp/alice/twokey.pub
mysql:
password: mysql-password
hosts:
- "%"
- "127.0.0.1"
- "::1"
- "localhost"
privs:
- "*.*:SELECT"
- "DB1.*:ALL"
- name: bob
authorized:
- /tmp/bob/id_rsa.pub
mysql:
password: other-mysql-password
hosts:
- "db1"
privs:
- "*.*:SELECT"
- "DB2.*:ALL"
# playbook中定義如下:
- user: name={{ item.name }} state=present generate_ssh_key=yes
with_items: "`users`"
- authorized_key: "user={{ item.0.name }} key='{{ lookup('file', item.1) }}'"
with_subelements:
- users
- authorized
# 也可以遍歷巢狀的子列表:
- name: Setup MySQL users
mysql_user: name={{ item.0.name }} password={{ item.0.mysql.password }} host={{ item.1 }} priv={{ item.0.mysql.privs | join('/') }}
with_subelements:
- users
- mysql.hosts
6、with_sequence迴圈整數序列
with_sequence可以生成一個自增的整數序列,可以指定起始值和結束值,也可以指定增長步長。 引數以key=value的形式指定,format指定輸出的格式。數字可以是十進位制、十六進位制、八進位制:
- hosts: all
tasks:
# create groups
- group: name=evens state=present
- group: name=odds state=present
# create some test users
- user: name={{ item }} state=present groups=evens
with_sequence: start=0 end=32 format=testuser%02d
# create a series of directories with even numbers for some reason
- file: dest=/var/stuff/{{ item }} state=directory
with_sequence: start=4 end=16 stride=2 # stride用於指定步長
# a simpler way to use the sequence plugin
# create 4 groups
- group: name=group{{ item }} state=present
with_sequence: count=4
7、with_random_choice隨機選擇
從列表中隨機取一個值:
- debug: msg={{ item }}
with_random_choice:
- "go through the door"
- "drink from the goblet"
- "press the red button"
- "do nothing"
8、do-Util迴圈
示例:
- action: shell /usr/bin/foo
register: result
until: result.stdout.find("all systems go") != -1
retries: 5
delay: 10
重複執行shell模組,當shell模組執行的命令輸出內容包含"all systems go"的時候停止。重試5次,延遲時間10秒。retries預設值為3,delay預設值為5。任務的返回值為最後一次迴圈的返回結果。
9、迴圈註冊變數
在迴圈中使用register時,儲存的結果中包含results關鍵字,該關鍵字儲存模組執行結果的列表。
- shell: echo "{{ item }}"
with_items:
- one
- two
register: echo
變數echo內容如下:
{
"changed": true,
"msg": "All items completed",
"results": [
{
"changed": true,
"cmd": "echo \"one\" ",
"delta": "0:00:00.003110",
"end": "2013-12-19 12:00:05.187153",
"invocation": {
"module_args": "echo \"one\"",
"module_name": "shell"
},
"item": "one",
"rc": 0,
"start": "2013-12-19 12:00:05.184043",
"stderr": "",
"stdout": "one"
},
{
"changed": true,
"cmd": "echo \"two\" ",
"delta": "0:00:00.002920",
"end": "2013-12-19 12:00:05.245502",
"invocation": {
"module_args": "echo \"two\"",
"module_name": "shell"
},
"item": "two",
"rc": 0,
"start": "2013-12-19 12:00:05.242582",
"stderr": "",
"stdout": "two"
}
]
}
遍歷註冊變數的結果:
- name: Fail if return code is not 0
fail:
msg: "The command ({{ item.cmd }}) did not have a 0 return code"
when: item.rc != 0
with_items: "`echo`.`results`"
10、with_together遍歷資料並行集合
示例:
- hosts: webservers
remote_user: root
vars:
alpha: [ 'a','b','c','d']
numbers: [ 1,2,3,4 ]
tasks:
- debug: msg="{{ item.0 }} and {{ item.1 }}"
with_together:
- "{{ alpha }}"
- "{{ numbers }}"
輸出的結果為:
ok: [192.168.1.65] => (item=['a', 1]) => {
"item": [
"a",
1
],
"msg": "a and 1"
}
ok: [192.168.1.65] => (item=['b', 2]) => {
"item": [
"b",
2
],
"msg": "b and 2"
}
ok: [192.168.1.65] => (item=['c', 3]) => {
"item": [
"c",
3
],
"msg": "c and 3"
}
ok: [192.168.1.65] => (item=['d', 4]) => {
"item": [
"d",
4
],
"msg": "d and 4"
}
loop模組一般在下面的場景中使用:
- 類似的配置模組重複了多遍
- fact是一個列表
- 建立多個檔案,然後使用assemble聚合成一個大檔案
- 使用with_fileglob匹配特定的檔案管理
六、Playbook 條件語句
在有的時候play的結果依賴於變數、fact或者是前一個任務的執行結果,從而需要使用到條件語句。
以下主要來自他人分享。
1、when
有的時候在特定的主機需要跳過特定的步驟,例如在安裝包的時候,需要指定主機的作業系統型別,或者是當作業系統的硬碟滿了之後,需要清空檔案等,可以使用when語句來做判斷 。when關鍵字後面跟著的是python的表示式,在表示式中你能夠使用任何的變數或者fact,當表示式的結果返回的是false,便會跳過本次的任務。
(1)基本用法
---
- name: Install VIM
hosts: all tasks:
- name:Install VIM via yum
yum: name=vim-enhanced state=installed
when: ansible_os_family =="RedHat"
- name:Install VIM via apt
apt: name=vim state=installed
when: ansible_os_family =="Debian"
- name: Unexpected OS family
debug: msg="OS Family {{ ansible_os_family }} is not supported" fail=yes
when: not ansible_os_family =="RedHat" or ansible_os_family =="Debian"
條件語句還有一種用法,它還可以讓你當達到一定的條件的時候暫停下來,等待你的輸入確認。一般情況下,當ansible遭遇到error時,它會直接結束執行。那其實你可以當遭遇到不是預期的情況的時候給使用pause模組,這樣可以讓使用者自己決定是否繼續執行任務:
- name: pause for unexpected conditions
pause: prompt="Unexpected OS"
when: ansible_os_family !="RedHat"
(2)在when中使用jinja2的語法
tasks:
- command: /bin/false
register: result # 將命令執行的結果傳遞給result變數
ignore_errors: True # 忽略錯誤
- command: /bin/something
when: result|failed # 如果註冊變數的值 是任務failed則返回true
- command: /bin/something_else
when: result|success # 如果註冊變數的值是任務success則返回true
- command: /bin/still/something_else
when: result|skipped # 如果註冊變數的值是任務skipped則返回true
- command: /bin/foo
when: result|changed # 如果註冊變數的值是任務changed則返回true
- hosts: all
user: root
vars:
epic: true
tasks: - shell: echo "This certainly is epic!" when: epic
- shell: echo "This certainly is not epic!"
when: not epic
(3)如果變數不存在,則可以通過jinja2的'defined'命令跳過
tasks:
- shell: echo "I've got '{{ foo }}' and am not afraid to use it!"
when: foo is defined
- fail: msg="Bailing out. this play requires 'bar'"
when: bar is not defined
(4)when在迴圈語句中的使用方法
tasks:
- command: echo {{ item }}
with_items: [ 0, 2, 4, 6, 8, 10 ]
when: item > 56、在include和roles中使用when:
# 在include中使用的示例:- include: tasks/sometasks.yml
when: "'reticulating splines' in output"
# 在roles中使用的示例:- hosts: webservers
roles:
- { role: debian_stock_config, when: ansible_os_family == 'Debian' }
2、條件匯入
有些時候,你也許想在一個Playbook中以不同的方式做事,比如說在debian和centos上安裝apache,apache的包名不同,除了when語句,還可以使用下面的示例來解決:
---
- hosts: all
remote_user: root
vars_files:
- "vars/common.yml"
- [ "vars/{{ ansible_os_family }}.yml", "vars/os_defaults.yml" ]
tasks:
- name: make sure apache is running
service: name={{ apache }} state=running
很多不同的yml檔案只是包含鍵和值,如下:
[[email protected] ~]# for vars/CentOS.yml
apache: httpd
somethingelse: 42
如果作業系統是“CentOS”,Ansible匯入的第一個檔案將是“vars/CentOS.yml”,緊接著 是“/var/os_defaults.yml”,如果這個檔案不存在。而且在列表中沒有找到,就會報錯。 在Debian系統中,最先檢視的將是“vars/Debian.yml”而不是“vars/CentOS.yml”,如果沒找到,則尋找預設檔案“vars/os_defaults.yml”。
3、with_first_found
有些時候,我們想基於不同的作業系統,選擇不同的配置檔案,及配置檔案的存放路徑,可以藉助with_first_found來解決:
- name: template a file
template: src={{ item }} dest=/etc/myapp/foo.conf
with_first_found:
- files:
- {{ ansible_distribution }}.conf
- default.conf
paths:
- search_location_one/somedir/
- /opt/other_location/somedir/
4、failed_when
failed_when其實是ansible的一種錯誤處理機制,是由fail模組使用了when條件語句的組合效果。示例如下:
- name: this command prints FAILED when it fails
command: /usr/bin/example-command -x -y -z
register: command_result
failed_when: "'FAILED' in command_result.stderr"
我們也可以直接通過fail模組和when條件語句,寫成如下:
- name: this command prints FAILED when it fails
command: /usr/bin/example-command -x -y -z
register: command_result
ignore_errors: True
- name: fail the play if the previous command did not succeed
fail: msg="the command failed"
when: "'FAILED' in command_result.stderr"
5、changed_when
當我們控制一些遠端主機執行某些任務時,當任務在遠端主機上成功執行,狀態發生更改時,會返回changed狀態響應,狀態未發生更改時,會返回OK狀態響應,當任務被跳過時,會返回skipped狀態響應。我們可以通過changed_when來手動更改changed響應狀態。示例如下:
- shell: /usr/bin/billybass --mode="take me to the river"
register: bass_result
changed_when: "bass_result.rc != 2" # 只有該條task執行以後,bass_result.rc的值不為2時,才會返回changed狀態
# 永遠不會報告“改變”的狀態
- shell: wall 'beep'
changed_when: False # 當changed_when為false時,該條task在執行以後,永遠不會返回changed狀態