若是說ansible的modules是工具,inventory配置文件是原材料,那麼playbook就是一封說明書,這裏會記錄任務是如何如何執行的,固然若是你願意,這裏也能夠定義一些變量、鏈接參數等等。html
playbook能夠由單個或者多個play組成。node
單個play示例:python
---
- hosts: webservers vars: http_port: 80 max_clients: 200 remote_user: root tasks: - name: ensure apache is at the latest version yum: name: 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
上面的示例中全部的任務做用於webservers所包含的主機,經過root用戶鏈接到目的主機,對apache服務進行了安裝、配置、啓動等操做,當配置文件有更改時,會觸發hanlders裏的重啓apache操做,vars裏定義的「http_port」和 「 max_clients」將會在模版文件「/srv/httpd.j2」中遵循Jinja2語法被使用到。web
playbooks是使用yaml語法格式,因此看起來比較通俗易懂。經過上面的示例能夠看出一個play能夠包含以下內容:sql
一個playbooks也能夠編寫多個play,示例以下:shell
---
- hosts: webservers remote_user: root tasks: - name: ensure apache is at the latest version yum: name: httpd state: latest - name: write the apache config file template: src: /srv/httpd.j2 dest: /etc/httpd.conf - hosts: databases remote_user: root tasks: - name: ensure postgresql is at the latest version yum: name: postgresql state: latest - name: ensure that postgresql is started service: name: postgresql state: started
上面的示例中,第一個play經過root用戶鏈接到webservers主機組,進行了apache服務的安裝和配置操做;第二個play經過root用戶連接到databases主機組,進行了數據庫的安裝和啓動操做。數據庫
上一章節中咱們說到,一個playbooks能夠放置多個play,一個play裏面能夠有多個tasks(modules),可是,當要管理的資源愈來愈多時,咱們發現將全部play都寫在一個yml文件裏會很臃腫,很差維護。apache
此時咱們能夠經過「import_playbook」方法引用其餘的playbooks文件;json
此時咱們能夠經過「import_playbook」方法引用其餘的playbooks文件;使用「import_tasks」、「include_tasks」、「import_role」、「include_role」、「roles」引用其餘的tasks文件。ruby
import_playbook
比較簡單,直接上示例,文件main.yml:
- import_playbook: webservers.yml - import_playbook: databases.yml
上述示例中使用import_playbook將webservers.yml和databases.yml文件裏的play引用到main.yml,和直接將兩個文件裏的內容直接粘過來是同樣的效果,執行順序天然也會按照play定義的順序執行。
import_tasks和include_tasks
能夠參考筆者以前寫的文章 ansible中include_tasks和import_tasks
import_role和include_role
ansible2.3引入了include_role,ansible 2.4版本後,新增了import_role,經過這兩個方法能夠在tasks裏面導入role,示例以下:
---
- hosts: webservers tasks: - debug: msg: "before we run our role"
- import_role: name: example - include_role: name: example - debug: msg: "after we ran our role"
從上面的示例能夠看出,在tasks中使用import_role和include_role方法導入了role example,role裏面的task會按順序執行。
固然咱們也能夠引用的同時定義變量:
---
- hosts: webservers roles: - common - role: foo_app_instance vars: dir: '/opt/a' app_port: 5000
- role: foo_app_instance vars: dir: '/opt/b' app_port: 5001
也能夠給role打tag:
---
- hosts: webservers tasks: - import_role: name: foo tags: - bar - baz
使用條件語句(後面有詳細寫when語句用法):
---
- hosts: webservers tasks: - include_role: name: some_role when: "ansible_os_family == 'RedHat'"
roles
除了使用import_role和include_role導入role,咱們也能夠直接使用roles方法來導入,示例以下:
--- - hosts: webservers roles: - common - webservers ###OR - hosts: webservers roles: - role: '/path/to/my/roles/common'
和import和include方法相比,roles方法是僅僅能夠導入role類型的playbook,而上述兩個方法能夠在其餘tasks中穿插一些role類型的playbook。
在生產中,roles是比較經常使用的因此後面的章節會有對roles的單獨講解,這裏就不在展開了。
經過以上的總結,咱們能夠看出,在ansible裏,若是咱們想複用其餘文件的playbooks,可使用include、import、roles三種方法,據我所知也只有這三種方法。
至此,咱們知道了可使用import*和include*導入其餘的playbooks,那麼這二者的區別是什麼呢?
在ansible 2.4版本中引入了dynamic和static的概念,在這以前只能使用include來導入其餘的tasks文件,如今include也能用,但官方在考慮在將來版本廢棄掉。
靜態指全部import*的方法,動態指include*的方法。
關於動態和靜態的兩點區別,總結以下:
具體介紹能夠參考筆者以前寫的文章ansible中include_tasks和import_tasks
ansible中能夠定義變量的地方能夠有不少,在這裏主要寫下playbooks裏面的變量定義,其餘部分的變量會在後續的「ansible基礎-變量」詳細闡述。
變量的定義一般使用YAML語法格式,示例以下:
--- vars: field1: one 字典變量: --- foo: field1: one field2: two
play中定義全局變量
---
- hosts: webservers vars: http_port: 80
上面示例中 http_port參數能夠在這個play中的tasks、playbooks、roles中引用。
tasks中定義變量
固然,咱們也能夠在某個task中定義局部變量,這個變量只能在本task內使用,示例以下:
---
- hosts: node1 gather_facts: false tasks: - name: Use var debug vars: - name: weimeng - age: 26 debug: var: name,age
include和import中定義變量
include_role定義變量只在被引用的role中生效:
- hosts: node1 gather_facts: false tasks: - include_role: name: role_A vars: age: 24
include_tasks定義變量只在被引用的task中生效:
tasks: - import_tasks: wordpress.yml vars: wp_user: timmy - import_tasks: wordpress.yml vars: wp_user: alice - import_tasks: wordpress.yml vars: wp_user: bob
roles中定義變量
playbook引用role也能夠直接定義變量,示例以下:
---
- hosts: webservers roles: - role: bar tags: ["foo"] # using YAML shorthand, this is equivalent to the above - { role: foo, tags: ["bar", "baz"] }
註冊變量
在playbook中,咱們能夠將一個task的執行結果註冊爲一個變量,供另一個task使用。例如:
---
- hosts: webservers roles: - role: bar tags: ["foo"] # using YAML shorthand, this is equivalent to the above - { role: foo, tags: ["bar", "baz"] }
將一個task的結果註冊爲一個變量,而後經過這個變量判斷另一個task是否執行,這是註冊變量很經常使用的方式。
經過命令行定義變量
在咱們執行playbook時能夠在命令行中指定自定義變量,例如:
ansible-playbook release.yml --extra-vars "version=1.23.45 other_variable=foo」
在同一個scope內,若是與其餘地方的變量衝突,命令行指定的參數優先級最高。
上面介紹了在playbook中如何定義變量,那麼變量可否和task同樣定義在單獨的yml文件內,而後使用相似於include_tasks的語句引用過來呢? 答案是確定的。
在playbook內引用變量文件使用的是vars_files:語句,示例以下:
---
- hosts: all remote_user: root vars: favcolor: blue vars_files: - /vars/external_vars.yml tasks: - name: this is just a placeholder command: /bin/echo foo
在變量文件/vars/external_vars.yml中,咱們只須要使用YAML語法格式進行變量定義便可。
除了咱們自定義的變量,ansible還支持另一種變量,這個變量相似於puppet的facter,ansible叫作fact。
ansible的fact會根據目的主機的系統信息生成一個json格式的變量集合,咱們在play中能夠直接引用。例如比較經常使用的變量:ip地址、主機名、操做系統類型等等。
puppet的facter依賴ruby的一個安裝包,經過ruby程序收集系統信息,而ansible的fact是經過python程序收集。
咱們能夠經過setup模塊來獲取目的主機的fact信息:
ansible hostname -m setup
fact會在playbook執行以前收集信息,默認是打開的,咱們也能夠經過指定gather_fact參數爲false/no/False關閉fact。在沒有配置fact cache的狀況下,若是關閉fact,playbook的執行速度會有一個顯著的提高,示例以下:
---
- hosts: whatever gather_facts: no
前面咱們介紹了下變量的定義/引用方式和fact變量,那麼在playbook中咱們如何使用這些變量呢?
變量一般會在模版、條件判斷語句、新的變量定義等處能用到。
使用變量的方法很簡單,只須要將變量寫在兩個大括號內而且先後都有空格便可,同時咱們必須將這個大括號用雙引號引發來,若是變量穿插在字符串內使用,雙引號也要將字符串部分引發來。
示例以下:
- hosts: app_servers vars: app_path: "{{ base_path }}/22"
若是一個變量定義比較複雜,例如列表、字典或fact(json格式),咱們能夠經過以下方式訪問:
列表變量訪問:
{{ foo[0] }}
字典變量訪問:
{{ foo[name] }}
或
{{ foo.name }}
json格式訪問變量訪問:
{{ansible_eth0["ipv4"]["address"] }}
或
{{ansible_eth0.ipv4.address }}
這裏說一個小技巧,在咱們排錯過程當中不少狀況咱們要debug一些變量。此時,可使用debug模塊輸出變量。
debug模塊有兩種使用方式,vars和msg :
---
- hosts: node1 gather_facts: false vars: - name: weimeng - age: 26 tasks: - name: Use var debug debug: var: name,age - name: Use msg debug debug: msg: "my name is {{ name }},and my age is {{ age }}"
輸入以下:
➜ lab-ansible ansible-playbook playbooks/task_vars.yml [WARNING]: Found variable using reserved name: name PLAY [node1] ******************************************************************* TASK [Use var debug] *********************************************************** ok: [node1] => { "name,age": "(u'weimeng', 26)" } TASK [Use msg debug] *********************************************************** ok: [node1] => { "msg": "my name is weimeng,and my age is 26" } PLAY RECAP ********************************************************************* node1 : ok=2 changed=0 unreachable=0 failed=0
經過對比咱們能夠看出,「vars」適用於直接debug變量,而「msg」能夠摻雜一些字符串,咱們能夠根據實際狀況來選擇使用。
本章節主要介紹了playbook的相關變量。ansible變量的知識點仍是不少的,因此我計劃在後邊會單獨介紹ansible的變量,這裏就點到爲止。
ansible條件語句不是不少,比較經常使用的就是when語句和循環語句。
當知足必定的條件時,咱們想要跳過某個task,這時候when語句出場了。當when語句的參數爲true時,纔會執行這個task,不然反之。
yum模塊的name能夠以列表的形式指定多個安裝包,可是不少其餘模塊是不支持列表的,例如file的path,copy的src,等等;或者說咱們想迭代的將一個列表元素傳遞給某個模塊處理,若是有多少個元素寫多個task就很麻煩。此時咱們可使用ansible的循環語句loop(ansible 2.5之後),在2.5版本以前可使用with_,loop相似於舊版本的with_list語句。
ansible的when語句用於判斷是否執行這個task,例如
tasks: - name: "shut down Debian flavored systems" command: /sbin/shutdown -t now when: ansible_os_family == "Debian" # note that Ansible facts and vars like ansible_os_family can be used # directly in conditionals without double curly braces
示例中若是系統的類型是「Debian」纔會執行/sbin/shutdown -t now命令。
條件語句也可使用「and」和「or」:
tasks: - name: "shut down CentOS 6 and Debian 7 systems" command: /sbin/shutdown -t now when: (ansible_distribution == "CentOS" and ansible_distribution_major_version == "6") or (ansible_distribution == "Debian" and ansible_distribution_major_version == "7")
條件也能夠寫成列表的形式,這種形式和and語句起到同樣的效果:
tasks: - name: "shut down CentOS 6 systems" command: /sbin/shutdown -t now when: - ansible_distribution == "CentOS"
- ansible_distribution_major_version == "6"
register變量條件語句
經過對某個task的執行結果是否成功,決定另一個task是否要執行:
tasks: - command: /bin/false register: result ignore_errors: True - command: /bin/something when: result is failed # In older versions of ansible use ``success``, now both are valid but succeeded uses the correct tense. - command: /bin/something_else when: result is succeeded - command: /bin/still/something_else when: result is skipped
變量是否被定義語句:
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 undefined
上面說到loop相似於舊版本的with_list語句,也就是說loop會將列表的元素逐個傳遞給上面的module,從而達到重複執行的目的。
最簡單的形式:
--- tasks: - command: echo { item } loop: [ 0, 2, 4, 6, 8, 10 ]
loop與when結合使用:
--- tasks: - command: echo { item } loop: [ 0, 2, 4, 6, 8, 10 ] when: item > 5
一般loop語句會結合各式各樣的filter去使用,例如「 loop: 「{ { [\'alice\', \'bob\'] |product([\'clientdb\', \'employeedb\', \'providerdb\'])|list }}」」,這個例子和with_nested語句起到同樣的效果。也就是說舊版本的with_ + lookup() 所能實現的,新版本的loop+filter一樣能實現。
通常playbook裏的task執行順序和python同樣,由上至下,定義的順序即執行的順序。一樣的,使用include*和import*導入playbook或tasks也會安照導入順序執行。
當playbook中有使用roles導入task和自定義tasks時,咱們會發現ansible總會先執行roles導入的task,而後執行自定義的tasks,例如:
- hosts: localhost gather_facts: no vars: - ff: 1 - gg: 2 tasks: - debug: var: ff roles: - role: role_B
輸出結果:
➜ lab-ansible ansible-playbook playbooks/roles_vars.yml PLAY [localhost] *************************************************************** TASK [role_B : debug] ********************************************************** ok: [localhost] => { "a": 2 } TASK [debug] ******************************************************************* ok: [localhost] => { "ff": 1 } PLAY RECAP ********************************************************************* localhost : ok=2 changed=0 unreachable=0 failed=0
從上面示例發現,雖然咱們將tasks定義在了前面,可是tasks任務仍是在roles任務以後執行。此時咱們可使用pre_task和post_task來強制指定執行順序,例如:
---
- hosts: localhost gather_facts: no vars: - ff: 1
- gg: 2 pre_tasks: - import_role: name: role_A vars: age: 23 roles: - role: role_B tasks: - debug: var: ff post_tasks: - debug: var: gg
總結下playbook裏任務的執行順序:
使用「pre_tasks:」定義的任務
使用「roles:」引用的任務
使用「tasks:」自定義的任務
使用「post_tasks」定義的任務
在部署應用時,一般的步驟是安裝軟件包==>更改配置文件==>初始化數據庫==>啓動(重啓)服務;升級的步驟通常是:升級軟件包==>更改配置文件==>初始化數據庫==>重啓服務。咱們發現不論是新部署仍是升級,最後一步都是要從新加載程序的,也就是說當咱們升級了軟件或者更改了配置文件都須要重啓一下應用。
爲了實現觸發服務重啓,ansible使用handlers方法定義重啓的動做,handlers並非每次執行playbook都會觸發,而是某些指定資源狀態改變時纔會觸發指定的handlers(這裏使用「資源」一詞借鑑於puppet)。
示例以下:
- name: template configuration file template: src: template.j2 dest: /etc/foo.conf notify: - restart memcached - restart apache
上面的示例中,當/etc/foo.conf文件內容有改動時(返回changed),會觸發重啓memcached和apache服務,若是文件內容沒有變化時(返回ok),則不會觸發handlers。
ansible執行過程當中並不會當即觸發handlers動做,而是以play爲單位,一個play執行完後最後纔會觸發handlers。
這樣設計也是很合理的,試想在一個play內,若是觸發一次就執行一次handlers,那麼除了最後一次的重啓,前面觸發的重啓都是無用功。
另外須要注意的一點是,handlers觸發的執行順序是按照定義順序執行,而不是按照notify指定的順序執行。
固然若是咱們想要當即觸發,也是能夠的,在play定義「- meta: flush_handlers」便可。
另外須要注意的一點是,handlers觸發的執行順序是按照定義順序執行,而不是按照notify指定的順序執行。
歡迎你們關注個人公衆號: