1. 程式人生 > >自動化運維工具Ansible實戰(五)Playbooks劇本使用

自動化運維工具Ansible實戰(五)Playbooks劇本使用

Ansible 自動化運維 Playbook

一、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主要有以下四部分構成:

  1. target section:定義將要執行playbook的遠程主機組
  2. variable section:定義playbook運行時需要使用的變量
  3. task section:定義將要在遠程主機上執行的任務列表
  4. handler section:定義task執行完成以後需要調用的任務

而Playbook對應的目錄層有五個,分別如下:
一般所需的目錄層有:(視情況可變化)

  1. vars 變量層
  2. tasks 任務層
  3. handlers 觸發條件
  4. files 文件
  5. 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

[root@Ansible ~]# 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變量的值

查看執行結果如下:
[root@Ansible ~]# ansible-playbook user.yml
技術分享圖片

(2)刪除遠程主機test的賬號
只需user: name="{{ user }}" state=absent remove=yes 即可

[root@Ansible ~]# 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
[root@Ansible ~]# ansible all -m command -a "id test"

技術分享圖片

(3)通過playbook 安裝apache
安裝 httpd 服務,將本地準備好的配置文件 copy 過去,並且啟動服務

[root@Ansible ~]# 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 
[root@Ansible ~]# ansible-playbook apache.yml
[root@Ansible ~]# ansible all -m shell -a "lsof -i:80"

技術分享圖片

(4)通過playbook 安裝apache(修改端口,並帶有vars變量)
將httpd.conf監聽的端口改為8080,然後重新覆蓋配置文件,當這個配置文件發生改變時,就觸發handler進行服務重啟
notify 這個 action可用於在每個play的最後被觸發,這樣可以避免多次有改變發生時每次都執行指定的操作,notify中列出的操作稱為handler

[root@Ansible ~]# 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
[root@Ansible ~]# vim /etc/httpd/conf/httpd.conf
42 Listen 80  --》 修改為8080
[root@Ansible ~]# ansible-playbook apache.yml 
[root@Ansible ~]# 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的示例

[root@Ansible ~]# echo "# Configure the buffer pool" >> /etc/my.cnf
[root@Ansible ~]# echo "innodb_buffer_pool_size = {{ innodb_buffer_pool_size_mb|int }}M" >> /etc/my.cnf
[root@Ansible ~]# 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
[root@Ansible ~]# ansible-playbook set_fact.yml

技術分享圖片

在遠程主機上查看該配置
[root@Client ~]# 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模塊一般在下面的場景中使用:

  1. 類似的配置模塊重復了多遍
  2. fact是一個列表
  3. 創建多個文件,然後使用assemble聚合成一個大文件
  4. 使用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文件只是包含鍵和值,如下:

[root@Ansible ~]# 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狀態

自動化運維工具Ansible實戰(五)Playbooks劇本使用