第五章: 用角色合成可重用Ansible内容
用角色合成可重用Ansible內容
對于很多項目來說,簡單單獨的ansible劇本就滿足了。隨著時間的變化以及項目的增長,就需要添加額外劇本、變量文件、任務文件的分隔。組織中的其他項目可能需要復用一些內容,其他項目加入目錄樹或者有些內容需要在多個項目間拷貝。隨著場景的復雜度和大小的增長,急切希望有松散組織的少量劇本、任務文件、以及變量文件。創建這樣的層次結構可能是令人生畏的,為什么ansible的很多使用開始簡單,一旦分散的文件變得笨拙并難以維護的麻煩出現,就只能發展成良好組織的形式。遷移也變得困難,可能需要重寫劇本的重要部分,就進一步延遲重組工作。
本章,我們就介紹下ansible中可組合、可復用、組織良好內容的最佳實踐。本章中的經驗將幫助開發人員設計在項目增長良好的ansible內容,避免后續進行困難的重新設計工作。下面是概述內容:
- 任務、處理器、變量以及包含概念的劇本。
- 角色。
- 利用角色設計頂層劇本。
- 跨項目共享角色。
任務、處理器、變量以及包含概念的劇本
理解如何有效組織ansible項目結構的第一步就是掌握包含文件的概念。實際上包含文件就是允許以特定話題來定義內容,并且可以被項目中的其他文件引用一次或多次。這個包含特性支持DRY的概念(Don't Repeat Yourself)。
包含任務
任務文件是定義一個或多個任務的yaml文件。這些任務不是直接綁定到任何特定的劇情或劇本上的;它們純粹就是任務列表。這些文件可以由劇本或其他任務文件通過include操作符引用。include操作符接收一個任務文件的路徑,我們在第一章中已經看到,路徑可以是從引用它的文件所在目錄的相對路徑。
將任務分解成多個獨立的文件,我們就可以對其引用多次或者在多個劇本中引用它們。如果我們希望修改其中一個任務的話,我們就只需要修改任務所在的單獨文件,無需關心有多少地方引用過它。
--- - name: include a task filedebug:msg: "I am the main task"- include: more-tasks.yaml給引入的任務傳遞變量
有時候,我們會分隔很多任務文件,但是又希望這些任務文件根據變量不同而產生不同的行為。include操作符允許我們在引入的時候定義并覆蓋變量數據。定義的作用域就只在包含的任務文件里邊。
下面我們創建一個任務文件,可以接收一個pathname和filename變量,任務會創建傳入的路徑,并創建這個文件。
備注: 書上使用path和file,執行劇本的時候老是報錯,但是找不到原因,改成pathname, filename就可以了。 --- - name: create leading pathfile:path: "{{ pathname }}"state: directory- name: touch the filefile:path: "{{ pathname + '/' + filename }}"state: touch然后定義一個劇本:
--- - name: touch fileshosts: localhostgather_facts: falsetasks:- include: tasks/files.yamlpathname: /tmp/foofilename: herd執行劇本結果如下:
ansible-playbook touchfiles.yaml -vv ansible-playbook 2.6.2config file = /etc/ansible/ansible.cfgconfigured module search path = [u'/usr/share/my_modules']ansible python module location = /usr/local/lib/python2.7/site-packages/ansibleexecutable location = /usr/local/bin/ansible-playbookpython 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.yamlPLAYBOOK: touchfiles.yaml ****************************************************************************************************************************************************** 1 plays in touchfiles.yamlPLAY [touch files] ************************************************************************************************************************************************************* META: ran handlersTASK [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 handlersPLAY RECAP ********************************************************************************************************************************************************************* localhost : ok=4 changed=3 unreachable=0 failed=0給引入的任務傳遞復雜數據
當我們想給包含的任務文件傳入復雜的數據,例如列表或hash,我們在引入任務文件的時候可以使用另外一種語法。
--- - name: create leading pathfile:path: "{{ item.value.path }}"state: directorywith_dict: "{{ files }}"- name: touch the filefile:path: "{{ item.value.path + '/' + item.key }}"state: touchwith_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 fileshosts: localhostgather_facts: falsetasks:- include: tasks/files2.yamlvars:files:herp:path: /tmp/fooderp: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 fileshosts: localhostgather_facts: falsetasks:- name: a taskdebug:msg: "I am a changing task"changed_when: truenotify: a handlerhandlers:- include: handlers.yamlwhen: foo | default('true') | bool --- - name: a handlerdebug: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
- 通過include_vars
- 通過--extra-vars(-e)
vars_files
vars_files是一個劇情指令。它定義了要讀取變量數據的文件列表。這些文件在劇情自己解析的時候就會讀取出來并解析的。和包含任務和處理器一樣,路徑是相對引用文件的相對路徑。
--- - name: varshosts: localhostgather_facts: falsevars_files:- variables.yamltasks:- name: a taskdebug:msg: "I am a {{ name }}" name: derp動態vars_files包含
--- - name: varshosts: localhostgather_facts: falsevars_files:- "{{ varfile }}"tasks:- name: a taskdebug: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模塊。這個模塊會以一個任務的行為加載變量,并且為每個主機做這些。和其他的很多模塊不同,include_vars模塊是在ansible主機本地執行的;因此,所有路徑依然是相對于劇情文件自身的相對路徑。因為變量加載是以任務的形式完成的,文件名字中的變量計算就是任務執行的時候發生的。文件名中的變量數據可以是特定主機的、也可以由前面任務定義的。另外,文件自身在執行時也不是必須存在的,它也可以由前面任務生成。這是一個非常強大和靈活的概念,如果恰當使用可以導致非常動態的劇本。
動態劇本實現可使用include_vars模塊。 --- - name: varshosts: localhostgather_facts: falsetasks:- name: load variablesinclude_vars: "{{ varfile }}"- name: a taskdebug:msg: "I am a {{ name }}"劇本的執行和之前的保持一致,但是輸出和前面的迭代稍有不同。
和其他任務一樣,在單個任務中可以循環加載多個文件。 當我們使用with_first_found遍歷列表,直到找到第一個文件為止就非常有效了。
--- - name: varshosts: localhostgather_facts: truetasks:- name: load variablesinclude_vars: "{{ item }}"with_first_found:- "{{ ansible_distribution }}.yaml"- "{{ ansible_os_family }}.yaml"- variables.yaml- name: a taskdebug:msg: "I am a {{ name }}"這次運行效果和前面基本一樣,區別在于這次執行了主機信息搜集,另外我們執行的時候,沒有傳入額外的varfile參數了。
同時我們看到加載的文件名是variables.yaml, 因為其他兩個文件不存在。實際上,這種實現通常用在給特定操作系統的主機加載變量的情況中。
各種不同的操作系統相關的變量可以保存在不同的變量文件中。通過利用變量ansible_distribution,由事實收集產生的,那么將ansible_distribution作為變量文件名的一部分,就可以使用with_first_found參數來加載它們了。同時可以建一個默認的不實用任何變量數據的變量集合文件,作為故障保護措施。
extra-vars
最后一種從文件中加載變量的方法是使用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 hreptemplate:src: herp/derp.j2dest: /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: commonsimple_var_a: Truesimple_var_b: False- role: apachecomplex_var:key1: value1key2: value2short_list:- 8080- 8081當提供依賴變量時,兩個名字是保留的,它們不能用于角色變量: tags, when。這兩個分別用于傳入標簽和條件的。
標簽
標簽可以應用到依賴角色中發現的所有任務上。和前面包含任務文件中傳入標簽類似。標簽可以是列表,可以是單個項目。
--- dependencies:- role: commonsimple_var_a: Truesimple_var_b: Falsetags: common_demo- role: apachecomplex_var:key1: value1key2: value2short_list:- 8080- 8081tags:- apache_demo- 8080- 8081角色依賴條件
在條件中不能防止依賴角色的處理,但是給依賴應用條件可以跳過依賴角色層級中的所有任務。這是任務包含中使用條件的鏡像功能。
dependencies:- role: commonsimple_var_a: Truesimple_var_b: Falsetags: common_demo- role: apachecomplex_var:key1: value1key2: value2short_list:- 8080- 8081tags:- apache_demo- 8080- 8081when: backend_server == 'apache'角色應用
角色不是劇情。它們不處理任何關于角色應該運行在那些主機、使用什么連接、是否串形操作、或者其他劇情行為的選項。角色必須在劇本中的劇情里邊應用,在劇情中可以表達這些選項。
在劇情中應用角色,可以使用roles操作符。這個操作符期望應用給主機的所有角色。有點類似前面說的角色依賴,當描述了應用的角色,數據也可以一起傳入,例如變量、標簽、條件等等。語法完全一樣。
混合角色和任務
劇情使用角色但不限于角色。這些劇情可以有它自己的任務,也可以有其他的任務塊: pre_tasks和post_tasks。這些任務的執行不依賴它們在劇情中出現的順序;而是遵照一種嚴格的順序。
- 進行變量加載
- 事實搜集
- pre_tasks執行
- 由pre_tasks中通知的處理器執行
- 角色執行
- 任務執行
- 角色或任務通知的處理器執行
- post_tasks執行
- 由post_tasks通知的處理器執行
劇情的處理器可以在多個點被刷新。首先是pre_tasks執行,這個過程可能會刷新特定的處理器,然后是角色和任務,注意角色先執行,任務后執行(不論它們誰先出現誰后出現)。角色和任務執行的過程中可能再次刷新處理器。 然后執行post_tasks, 最后由post_tasks刷新的處理器會執行。
另外,任何地方都可以使用meta: flush_handlers調用來刷新處理器。
雖然執行順序不一定是它們在劇情中出現的順序,但是我們最好讓它們以執行順序出現,這樣我們的劇情就會比較容易看懂,執行的時候不會感覺困惑了。
角色共享
使用角色的一個有點就是有能力在劇情、劇本、整個項目空間、甚至跨組織之間共享角色。角色設計成獨立的(或明確的依賴角色),以便它們可以存在于應用角色的劇本的項目空間之外。角色可以安裝到ansible主機的共享路徑下面,或者可以通過源碼控制來分發。
ansible galaxy
ansible galaxy是一個查找和共享ansible角色的社區。
另外ansible-galaxy工具可以從網站上連接并安裝角色。默認會安裝到/etc/ansible/roles目錄下面,當然如果配置了roles_path的話,就安裝到你配置的目錄下面。
- 安裝角色: ansible-galaxy install -p installpath role-name
- 列出安裝角色: ansible-galaxy list -p installpath
- 查看安裝角色信息: ansible-galaxy info -p installpath role-name
- 刪除角色: ansible-galaxy remove -p installpath role-name
- 創建角色: ansible-galaxy init role-name
安裝角色比較靈活,可以直接提供名字,也可以通過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.
目錄
- 第三章: 控制任務條件
- 目錄: 掌握ansible(Mastering ansible)
- 第五章: 最小化滾動部署的宕機
- ansible galaxy
總結
以上是生活随笔為你收集整理的第五章: 用角色合成可重用Ansible内容的全部內容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: 总是腰疼背痛 竟是“穿错鞋”惹的祸
- 下一篇: tga缩略图预览_甜蜜的缩略图预览库
