對於不少項目來講,簡單單獨的ansible劇本就知足了。隨着時間的變化以及項目的增加,就須要添加額外劇本、變量文件、任務文件的分隔。組織中的其餘項目可能須要複用一些內容,其餘項目加入目錄樹或者有些內容須要在多個項目間拷貝。隨着場景的複雜度和大小的增加,急切但願有鬆散組織的少許劇本、任務文件、以及變量文件。建立這樣的層次結構多是使人生畏的,爲何ansible的不少使用開始簡單,一旦分散的文件變得笨拙並難以維護的麻煩出現,就只能發展成良好組織的形式。遷移也變得困難,可能須要重寫劇本的重要部分,就進一步延遲重組工做。python
本章,咱們就介紹下ansible中可組合、可複用、組織良好內容的最佳實踐。本章中的經驗將幫助開發人員設計在項目增加良好的ansible內容,避免後續進行困難的從新設計工做。下面是概述內容:git
理解如何有效組織ansible項目結構的第一步就是掌握包含文件的概念。實際上包含文件就是容許以特定話題來定義內容,而且能夠被項目中的其餘文件引用一次或屢次。這個包含特性支持DRY的概念(Don't Repeat Yourself)。github
任務文件是定義一個或多個任務的yaml文件。這些任務不是直接綁定到任何特定的劇情或劇本上的;它們純粹就是任務列表。這些文件能夠由劇本或其餘任務文件經過include操做符引用。include操做符接收一個任務文件的路徑,咱們在第一章中已經看到,路徑能夠是從引用它的文件所在目錄的相對路徑。apache
將任務分解成多個獨立的文件,咱們就能夠對其引用屢次或者在多個劇本中引用它們。若是咱們但願修改其中一個任務的話,咱們就只須要修改任務所在的單獨文件,無需關心有多少地方引用過它。架構
--- - name: include a task file debug: msg: "I am the main task" - include: more-tasks.yaml
有時候,咱們會分隔不少任務文件,可是又但願這些任務文件根據變量不一樣而產生不一樣的行爲。include操做符容許咱們在引入的時候定義並覆蓋變量數據。定義的做用域就只在包含的任務文件裏邊。app
下面咱們建立一個任務文件,能夠接收一個pathname和filename變量,任務會建立傳入的路徑,並建立這個文件。框架
備註: 書上使用path和file,執行劇本的時候總是報錯,可是找不到緣由,改爲pathname, filename就能夠了。
--- - name: create leading path file: path: "{{ pathname }}" state: directory - name: touch the file file: path: "{{ pathname + '/' + filename }}" state: touch
而後定義一個劇本:python2.7
--- - name: touch files hosts: localhost gather_facts: false tasks: - include: tasks/files.yaml pathname: /tmp/foo filename: herd
執行劇本結果以下:ide
ansible-playbook touchfiles.yaml -vv ansible-playbook 2.6.2 config file = /etc/ansible/ansible.cfg configured module search path = [u'/usr/share/my_modules'] ansible python module location = /usr/local/lib/python2.7/site-packages/ansible executable location = /usr/local/bin/ansible-playbook python version = 2.7.15 (default, Jul 23 2018, 21:27:06) [GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.2)] Using /etc/ansible/ansible.cfg as config file statically imported: /Users/apple/Sites/workspace/k8s-cluster/ansibledemo/tasks/files.yaml statically imported: /Users/apple/Sites/workspace/k8s-cluster/ansibledemo/tasks/files.yaml PLAYBOOK: touchfiles.yaml ****************************************************************************************************************************************************** 1 plays in touchfiles.yaml PLAY [touch files] ************************************************************************************************************************************************************* META: ran handlers TASK [create leading path] ***************************************************************************************************************************************************** task path: /Users/apple/Sites/workspace/k8s-cluster/ansibledemo/tasks/files.yaml:2 changed: [localhost] => {"changed": true, "gid": 0, "group": "wheel", "mode": "0755", "owner": "apple", "path": "/tmp/foo", "size": 64, "state": "directory", "uid": 501} TASK [touch the file] ********************************************************************************************************************************************************** task path: /Users/apple/Sites/workspace/k8s-cluster/ansibledemo/tasks/files.yaml:7 changed: [localhost] => {"changed": true, "dest": "/tmp/foo/herd", "gid": 0, "group": "wheel", "mode": "0644", "owner": "apple", "size": 0, "state": "file", "uid": 501} TASK [create leading path] ***************************************************************************************************************************************************** task path: /Users/apple/Sites/workspace/k8s-cluster/ansibledemo/tasks/files.yaml:2 ok: [localhost] => {"changed": false, "gid": 0, "group": "wheel", "mode": "0755", "owner": "apple", "path": "/tmp/foo", "size": 96, "state": "directory", "uid": 501} TASK [touch the file] ********************************************************************************************************************************************************** task path: /Users/apple/Sites/workspace/k8s-cluster/ansibledemo/tasks/files.yaml:7 changed: [localhost] => {"changed": true, "dest": "/tmp/foo/cerd", "gid": 0, "group": "wheel", "mode": "0644", "owner": "apple", "size": 0, "state": "file", "uid": 501} META: ran handlers META: ran handlers PLAY RECAP ********************************************************************************************************************************************************************* localhost : ok=4 changed=3 unreachable=0 failed=0
當咱們想給包含的任務文件傳入複雜的數據,例如列表或hash,咱們在引入任務文件的時候可使用另一種語法。工具
--- - name: create leading path file: path: "{{ item.value.path }}" state: directory with_dict: "{{ files }}" - name: touch the file file: path: "{{ item.value.path + '/' + item.key }}" state: touch with_dict: "{{ files }}"
注意上面with_dict所提供的變量,使用引號包圍起來。 若是直接寫with_dict: files,會報"msg": "with_dict expects a dict", ansible版本2.6.x。對於1.9以前版本的應該能夠。
參考: https://github.com/ansible/an...。
而後從新修改touchfiles.yaml文件內容:
--- - name: touch files hosts: localhost gather_facts: false tasks: - include: tasks/files2.yaml vars: files: herp: path: /tmp/foo derp: path: /tmp/foo
注意: 在使用yaml語法給包含語句提供變量數據的時候,變量能夠直接列舉出來,使用不使用頂層的vars關鍵詞均可以。 可是使用vars很是有用,若是變量的名字和ansible的控制參數重名的話,使用它就不會形成衝突。這也是前面我改path -> pathname, file -> filename的緣由。
相似於向引入文件傳入數據,咱們也能夠將條件傳入包含的文件中。這是經過給include綁定一個when語句.這個條件不會致使ansible計算測試來決定文件是否被包含進去;而是,他指示ansible在包含的文件(以及前面所說的任何可能包含的其餘文件)中給裏邊的每一個任務添加條件。
不能條件性的包含一個文件。文件老是包含進去的;然而,任務條件能夠應用給它們的每一個任務。
須要記住的重要一點就是,全部主機都將評估全部包含的任務。沒有辦法影響ansible爲某些主機不包含一個文件。 大多數狀況下,條件能夠被應用到包含層級裏邊的每一個任務,這樣包含的任務能夠跳過。一種基於主機事實的包含任務能夠利用group_by行爲產檢來根據主機事實建立動態組。而後,你能夠給那個組它們本身的劇情來包含特定的任務。這點能夠留給讀者本身練習。
一種基於主機事實的包含任務能夠利用group_by行爲產檢來根據主機事實建立動態組。而後,你能夠給那個組它們本身的劇情來包含特定的任務。這點能夠留給讀者本身練習。
當包含任務文件的時候,能夠給文件裏邊的全部任務打標籤。tags就是用來定義一個或多個標籤,並將它們應用到包含層級中的全部任務的。在包含時間點上打標籤的能力可讓任務文件本身對任務該如何被打標籤是無主見的, 而且能夠容許任務集能夠屢次包含,可是每次均可以傳遞不一樣的數據和標籤。
標籤能夠在include語句或者在劇情(play)自身定義,若是在劇情中定義,能夠涵蓋這個劇情裏邊的全部包含文件,包括其餘非包含文件中的任務。
打標籤以後,咱們能夠選擇讓哪一個標籤運行,經過ansible-playbook的--tags命令行參數來指定標籤名。
ansible-playbook -i mastery-hosts includer.yaml -vv --tags second
另外,咱們可使用--skip-tags跳過某些標籤的任務來運行。
處理器本質上是任務。它們是經過其餘任務通知的方式觸發的潛在任務集。像這樣的話,處理器任務一樣能夠像通常任務同樣被包含進去。include指令在handlers塊裏邊也是合法的。
和任務包含不一樣的是,變量數據不能在包含處理器任務的時候一塊兒傳入。可是,仍是能夠給處理器包含綁定條件的,這樣能夠應用給包含文件裏邊的每個處理器。
--- - name: touch files hosts: localhost gather_facts: false tasks: - name: a task debug: msg: "I am a changing task" changed_when: true notify: a handler handlers: - include: handlers.yaml when: foo | default('true') | bool
--- - name: a handler debug: msg: "handling a thing"
咱們能夠分別執行以下命令:
# 由於foo爲true, 所以handler會執行 ansible-playbook -i mastery-hosts includer.yaml -vv # 下面執行的時候,咱們使用了外部變量foo, 定義爲false, 所以handler就跳過去了。 ansible-playbook -i mastery-hosts includer.yaml -vv -e foo=false
變量數據也能夠被分離到可加載文件裏邊。這樣就容許在多個劇情或劇本,以及項目目錄以外的包含變量數據(好比密碼數據)之間共享變量。變量文件是簡單的yaml格式文件,以key-value對的形式出現。和任務包含文件不一樣,變量包含文件不能再包含其餘更多文件。
變量能夠以三種不一樣的方式被包含進來:
vars_files是一個劇情指令。它定義了要讀取變量數據的文件列表。這些文件在劇情本身解析的時候就會讀取出來並解析的。和包含任務和處理器同樣,路徑是相對引用文件的相對路徑。
--- - name: vars hosts: localhost gather_facts: false vars_files: - variables.yaml tasks: - name: a task debug: msg: "I am a {{ name }}"
name: derp
--- - name: vars hosts: localhost gather_facts: false vars_files: - "{{ varfile }}" tasks: - name: a task debug: msg: "I am a {{ name }}"
而後能夠經過ansible-playbook命令的-e參數傳入動態的變量包含文件名。
ansible-playbook -i mastery-hosts includer.yaml -vv -e varfile=variables.yaml
另外,變量值須要在執行時間定義,所以要加載的文件在執行時間必須存在。即使若是引用的文件在劇本中位置多是4個劇情後面出現的,這個引用的文件本身是由第一個劇情產生的,除非這個文件在執行時間存在,不然ansible-playbook就會報錯。
從文件中包含變量數據的第二種方法是使用include_vars模塊。這個模塊會以一個任務的行爲加載變量,而且爲每一個主機作這些。和其餘的不少模塊不一樣,include_vars模塊是在ansible主機本地執行的;所以,全部路徑依然是相對於劇情文件自身的相對路徑。由於變量加載是以任務的形式完成的,文件名字中的變量計算就是任務執行的時候發生的。文件名中的變量數據能夠是特定主機的、也能夠由前面任務定義的。另外,文件自身在執行時也不是必須存在的,它也能夠由前面任務生成。這是一個很是強大和靈活的概念,若是恰當使用能夠致使很是動態的劇本。
動態劇本實現可以使用include_vars模塊。
--- - name: vars hosts: localhost gather_facts: false tasks: - name: load variables include_vars: "{{ varfile }}" - name: a task debug: msg: "I am a {{ name }}"
劇本的執行和以前的保持一致,可是輸出和前面的迭代稍有不一樣。
和其餘任務同樣,在單個任務中能夠循環加載多個文件。 當咱們使用with_first_found遍歷列表,直到找到第一個文件爲止就很是有效了。
--- - name: vars hosts: localhost gather_facts: true tasks: - name: load variables include_vars: "{{ item }}" with_first_found: - "{{ ansible_distribution }}.yaml" - "{{ ansible_os_family }}.yaml" - variables.yaml - name: a task debug: msg: "I am a {{ name }}"
此次運行效果和前面基本同樣,區別在於此次執行了主機信息蒐集,另外咱們執行的時候,沒有傳入額外的varfile參數了。
同時咱們看到加載的文件名是variables.yaml, 由於其餘兩個文件不存在。實際上,這種實現一般用在給特定操做系統的主機加載變量的狀況中。
各類不一樣的操做系統相關的變量能夠保存在不一樣的變量文件中。經過利用變量ansible_distribution,由事實收集產生的,那麼將ansible_distribution做爲變量文件名的一部分,就可使用with_first_found參數來加載它們了。同時能夠建一個默認的不實用任何變量數據的變量集合文件,做爲故障保護措施。
最後一種從文件中加載變量的方法是使用ansible-playbook的--extra-vars(-e)參數。一般來講,這個參數接收key=value格式的值;然而,若是提供文件名的話,可使用@符號放文件名前面,ansible會讀取整個文件並加載變量。
注意: 使用extra-vars加載變量文件的時候,ansible-playbook執行的時候這個變量文件必須存在。
劇本文件能夠包含其餘整個劇本文件。這種架構對於將一些獨立的劇本打包到一塊兒組成更大的,更加複雜的劇本就很是有用了。劇本包含比任務包含要更基礎些。在包含劇本的時候,不能執行變量替換,不能應用條件,不能應用標籤。要包含的劇本文件在執行時間也必須存在。
對變量、任務、處理器和劇本的包含有了功能性理解,咱們就能夠轉向更高級的角色主題。角色超越了對一些劇本以及分開文件的引用的基本結構。角色爲徹底獨立或相互依賴的變量、任務、文件、模版以及模塊集合提供了一個框架。每一個角色一般限於一個特定的主題或想要的最終效果,全部達成結果的必要步驟,要麼位於角色自身,要麼位於依賴的角色列表中。
角色自己不是劇本。沒有辦法直接執行一個角色。角色沒有設置它會應用於什麼主機。頂級劇本是將你資產中的主機綁定到應該應用這些主機的角色上的膠水。
角色在文件系統中有一個結構化的佈局。這個結構在於提供自動化圍繞,包括任務、處理程序、變量、模版和角色依賴。該結構還容許在角色裏邊的任意地方輕鬆的引用文件和模版。
角色都位於劇本檔案的子目錄中,即roles目錄。固然,這個是經過配置中的roles_path來配置的,可是這裏咱們就使用默認值。 每一個角色自身都是一個目錄樹。 角色名就是位於roles目錄下面的子目錄的名字。每一個角色均可以有一些特殊意義的子目錄,在角色應用給主機集合的時候會被處理成特殊意義的。
一個角色能夠包含全部這些元素或者少數的這些元素。沒有的元素會被忽略掉。一些角色存在的價值就是爲跨項目提供一些通用的處理器。其餘角色以單獨的依賴點存在,而依賴點又依賴一些其餘角色。
任務是角色的主要角色。若是roles/<role_name>/tasks/main.yaml存在的話,那裏邊的全部任務以及他包含的全部其餘文件就會被嵌入到劇情中,並被執行。
相似於任務,若是存在的話,處理器會自動從roles/<role_name>/handlers/main.yaml中加載。這些處理器能夠被角色中的任何任務引用,或者列表中依賴這個角色的其餘角色的任務引用。
角色中能夠定義兩種類型的變量。這些都是角色變量,能夠從roles/<role_name>/vars/main.yaml中加載,還有一種就是角色默認值,位於roles/<role_name>/defaults/main.yaml。二者區別在於優先級。角色默認值優先級最低。字面上講就是,變量的任何其餘定義都比角色默認值優先級高。角色默認值能夠認爲是實際數據的佔位,引用什麼變量開發者可能感興趣的是使用特定站點值來定義。
另外一方面,角色變量具備較高的優先級。角色變量能夠被覆蓋,但通常來講,當一個角色中同一個數據集被引用屢次時會使用。 若是使用站點本地值來重定義數據集,那麼這些變量應該在角色默認值中列出來,而不是在角色變量中。
角色能夠包含自定義模塊。雖然ansible項目很是善於審查和接受新提交的模塊,可是在某些狀況下,向上遊提交自定義模塊可能並不可取,甚至無效的。這種狀況下,將模塊交付給角色多是更好的選擇。模塊位於roles/<role_name>/library/下面,能夠在角色或後續的角色中的全部任務中使用。這個地方提供的模塊會覆蓋文件系統中的其餘任何地方的同名模塊,這也是一種在上游尚未接受和使用新版發佈以前,分散的給核心模塊添加功能的方式。
角色能夠表達對另外角色的依賴。一般作法是,角色集都依賴一個共同的角色,這樣就依賴任意任務、處理器、模塊等等了。可能依賴的這些角色只須要在通用角色中定義一次。當ansible爲主機集合處理一個角色的時候,他首先查找在roles/<role_name>/meta/main.yaml中列舉的全部依賴。
若是定義了任何依賴的話,在開始最初的角色任務以前,這些依賴角色會被處理,它們裏邊的任務也會執行(固然在檢查完列舉的全部依賴以後),直到全部依賴都完成。
任務和處理器模塊能夠引用相對位於roles/<role_name>/files/下面的文件。文件名能夠不用任何前綴,那麼會在files目錄取得源碼。也容許使用相對前綴,爲了訪問在files子目錄的文件。相似template, copy和script模塊能夠利用這個。
相似的,模版是template模塊使用的,能夠從templates目錄中引用模版文件。
- name: configure hrep template: src: herp/derp.j2 dest: /etc/herp/derp.j2
爲了演示角色結構看起來的樣子,下面是一個demo角色的目錄結構:
roles/demo ├── defaults │ └── main.yaml ├── files │ └── foo ├── handlers │ └── main.yaml ├── library │ └── samplemod.py ├── meta │ └── main.yaml ├── tasks │ └── main.yaml ├── templates │ └── bar.j2 └── vars └── main.yaml
在建立角色的時候,這裏的每個目錄或文件都不是必須的,只有存在的文件纔會被處理。
前面咱們提到,角色能夠依賴其餘角色。這些關係叫作依賴關係,它們是在meta/main.yaml中描述的。 這個文件指望又一個頂級的數據hash, 使用dependencies鍵;裏邊的數據是一個角色列表。
--- dependencies: - role: common - role: apache
這個例子中,ansible會首先徹底處理common角色(以及它可能表達的任何依賴),而後繼續apache角色,最後開始角色本身的任務。
依賴能夠經過不帶任何前綴的名字來引用,若是它們在同一目錄下存在,或者在配置的roles_path中存在。不然,須要提供能夠定位角色的完整路徑。
當表達依賴的時候,可以給依賴傳遞數據。數據能夠是變量、標籤、甚至是條件。
列舉依賴的時候傳遞變量會覆蓋defaults/main.yaml或vars/main.yaml中匹配的變量值。這點對於使用通用角色,例如apache,做爲依賴時就很是有用,能夠提供特定應用的數據,例如須要開啓的端口號,或者啓用什麼apache模塊。變量能夠表達爲角色列表的額外key。
dependencies: - role: common simple_var_a: True simple_var_b: False - role: apache complex_var: key1: value1 key2: value2 short_list: - 8080 - 8081
當提供依賴變量時,兩個名字是保留的,它們不能用於角色變量: tags, when。這兩個分別用於傳入標籤和條件的。
標籤能夠應用到依賴角色中發現的全部任務上。和前面包含任務文件中傳入標籤相似。標籤能夠是列表,能夠是單個項目。
--- dependencies: - role: common simple_var_a: True simple_var_b: False tags: common_demo - role: apache complex_var: key1: value1 key2: value2 short_list: - 8080 - 8081 tags: - apache_demo - 8080 - 8081
在條件中不能防止依賴角色的處理,可是給依賴應用條件能夠跳過依賴角色層級中的全部任務。這是任務包含中使用條件的鏡像功能。
dependencies: - role: common simple_var_a: True simple_var_b: False tags: common_demo - role: apache complex_var: key1: value1 key2: value2 short_list: - 8080 - 8081 tags: - apache_demo - 8080 - 8081 when: backend_server == 'apache'
角色不是劇情。它們不處理任何關於角色應該運行在那些主機、使用什麼鏈接、是否串形操做、或者其餘劇情行爲的選項。角色必須在劇本中的劇情裏邊應用,在劇情中能夠表達這些選項。
在劇情中應用角色,可使用roles操做符。這個操做符指望應用給主機的全部角色。有點相似前面說的角色依賴,當描述了應用的角色,數據也能夠一塊兒傳入,例如變量、標籤、條件等等。語法徹底同樣。
--- - hosts: localhost gather_facts: false roles: - role: simple derp: newval - role: second_role othervar: value - role: third_role - role: another_role
劇情使用角色但不限於角色。這些劇情能夠有它本身的任務,也能夠有其餘的任務塊: pre_tasks和post_tasks。這些任務的執行不依賴它們在劇情中出現的順序;而是遵守一種嚴格的順序。
劇情的處理器能夠在多個點被刷新。首先是pre_tasks執行,這個過程可能會刷新特定的處理器,而後是角色和任務,注意角色先執行,任務後執行(不論它們誰先出現誰後出現)。角色和任務執行的過程當中可能再次刷新處理器。 而後執行post_tasks, 最後由post_tasks刷新的處理器會執行。
另外,任何地方均可以使用meta: flush_handlers調用來刷新處理器。
雖然執行順序不必定是它們在劇情中出現的順序,可是咱們最好讓它們以執行順序出現,這樣咱們的劇情就會比較容易看懂,執行的時候不會感受困惑了。
使用角色的一個有點就是有能力在劇情、劇本、整個項目空間、甚至跨組織之間共享角色。角色設計成獨立的(或明確的依賴角色),以便它們能夠存在於應用角色的劇本的項目空間以外。角色能夠安裝到ansible主機的共享路徑下面,或者能夠經過源碼控制來分發。
ansible galaxy是一個查找和共享ansible角色的社區。
另外ansible-galaxy工具能夠從網站上鍊接並安裝角色。默認會安裝到/etc/ansible/roles目錄下面,固然若是配置了roles_path的話,就安裝到你配置的目錄下面。
安裝角色比較靈活,能夠直接提供名字,也能夠經過git地址,還能夠直接是tar包,還能夠配置單獨的yaml同時安裝多個角色包。
--- - src: <name or url> path: <optional install path> version: <optional version> name: <optional name override> scm: <optional defined source control mechanism> - src: <name or url> path: <optional install path> version: <optional version> name: <optional name override> scm: <optional defined source control mechanism> - src: <name or url> path: <optional install path> version: <optional version> name: <optional name override> scm: <optional defined source control mechanism>
而後執行安裝命令,經過--roles-file(-r)選項來安裝:
ansible-galaxy install -r install-roles.yaml
Ansible provides the capability to divide content logically into separate files. This capability helps project developers to not repeat the same code over and over again. Roles within Ansible take this capability a step further and wrap some magic around the paths to the content. Roles are tunable, reusable, portable, and shareable blocks of functionality. Ansible Galaxy exists as a community hub for developers to find, rate, and share roles. The ansible-galaxy command-line tool provides a method
of interacting with the Ansible Galaxy site or other role-sharing mechanisms. These capabilities and tools help with the organization and utilization of common code.
In the next chapter, we'll cover different deployment and upgrade strategies and the Ansible features useful for each strategy.