專題 - 使用 Ansible 進行快速佈署 - 迴圈, 條件控制, 錯誤排除等功能
上次更新日期 2020/11/27
前面提到的 playbook 裡面,某些時刻我們會需要寫兩段一模一樣的 ansible code,只因為該 code 不接受類似陣列的項目! 無法進行重複的作業。這就好像我們在 Linux 使用 id 去檢查帳號資訊時,只能接一個參數是相同的道理。以前使用 id 時, 可以透過迴圈來處理,那麼 ansible 說是要取代 shell script 來快速佈署系統環境,沒可能沒有迴圈嘛?是的!ansible 有迴圈、有條件控制、還有錯誤克服的情境,雖然跟 shell script 差很多!語法完全不同!不過,意義倒是相同的, 就是減少重複的 code 啦!
- 在 playbook 使用 loop, item, when 進行迴圈與條件控制
- 使用 handler 進行例外呼叫 (類似函數 function)
- 任務失敗時的處置方案: force_handlers, ignore_errors, fail module, block+rescue+always..
- 檔案傳輸複製行為與 Jinja2 樣板
在 playbook 使用 loop, item, when 進行迴圈與條件控制
- 簡單的迴圈設計:
- 在還不知道有迴圈可以使用時,如果你想要知道某個服務有沒有啟用,應該是要這樣做:
# 注意身份與所在目錄!很重要!別搞混! $ whoami; pwd student /home/student/ansible-init # 開始編寫需要的 playbook $ vim loop_noloop.yml --- - name: do not use loop to show service hosts: webserver1 tasks: - name: check httpd service status command: cmd: systemctl is-active httpd register: myoutput - name: show debug: var: myoutput.stdout - name: check sshd service status command: cmd: systemctl is-active sshd register: myoutput - name: show debug: var: myoutput.stdout - name: check tuned service status command: cmd: systemctl is-active tuned register: myoutput - name: show debug: var: myoutput.stdout # 立刻執行看看: $ ansible-playbook loop_noloop.yml PLAY [do not use loop to show service] *********************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [check httpd service status] **************************************************** changed: [webserver1] TASK [show] ************************************************************************** ok: [webserver1] => { "myoutput.stdout": "active" } TASK [check sshd service status] ***************************************************** changed: [webserver1] TASK [show] ************************************************************************** ok: [webserver1] => { "myoutput.stdout": "active" } TASK [check tuned service status] **************************************************** changed: [webserver1] TASK [show] ************************************************************************** ok: [webserver1] => { "myoutput.stdout": "active" } PLAY RECAP *************************************************************************** webserver1 : ok=7 changed=3 unreachable=0 failed=0 skipped0 rescued=0 ignored=0
一層一層執行下來,撰寫這些 playbook 覺得很煩!有沒有好一點的方案呢? - 如果想要設計迴圈,可以透過『 loop: 』這個迴圈控制參數來處理。要注意喔,loop 不是模組,所以無法使用 ansible-doc 去查詢,
不過,基礎的 loop 語法可以這樣寫:
$ vim loop_service1.yml --- - name: do not use loop to show service hosts: webserver1 tasks: - name: check "{{item}}" service status command: cmd: systemctl is-active "{{item}}" loop: - httpd - sshd - tuned $ ansible-playbook loop_service1.yml PLAY [do not use loop to show service] *********************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [check "{{item}}" service status] *********************************************** changed: [webserver1] => (item=httpd) changed: [webserver1] => (item=sshd) changed: [webserver1] => (item=tuned) PLAY RECAP *************************************************************************** webserver1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
如上所示:- loop 只會帶給前一個模組有迴圈的效果
- loop 並不是模組,也不是模組的參數
- loop 必須與模組相同等級,所以 loop: 就會與 command 在同一個縮排處。
- loop 內的項目,在前一個模組裡面,可以使用 "{{ item }}" 這個變數來處理。
- 使用註冊 (register) 的變數,搭配 results 關鍵字的定義,就可以取得 loop 在各個階段所執行的訊息輸出。
以上面的範例為例,未加入 loop 以前,可以透過 register myoutput 搭配 .stdout 就可以得到輸出的資訊,
那如果透過 loop 之後,該如何處理?
$ vim loop_service2.yml --- - name: do not use loop to show service hosts: webserver1 tasks: - name: check "{{item}}" service status command: cmd: systemctl is-active "{{item}}" register: myoutput loop: - httpd - sshd - tuned - name: show "{{ item }}" debug: msg: output is "{{ item.stdout }}" loop: "{{ myoutput['results'] }}" $ ansible-playbook loop_service2.yml PLAY [do not use loop to show service] *********************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [check {{item}} service status] ************************************************* changed: [webserver1] => (item=httpd) changed: [webserver1] => (item=sshd) changed: [webserver1] => (item=tuned) TASK [show {{ item.item }}] ********************************************************** ok: [webserver1] => (item={'cmd': ['systemctl', 'is-active', 'httpd'], 'stdout': 'active', 'stderr': '', 'rc': 0, 'start': '2020-11-11 08:29:05.922439', 'end': '2020-11-11 08:29:05.931727', 'delta': '0:00:00.009288', 'changed': True, 'invocation': {'module_args': {'_raw_params': 'systemctl is-active "httpd"', 'warn': True, '_uses_shell': False, 'stdin_add_newline': True, 'strip_empty_ends': True, 'argv': None, 'chdir': None, 'executable': None, 'creates': None, 'removes': None, 'stdin': None}}, 'stdout_lines': ['active'], 'stderr_lines': [], 'failed': False, 'item': 'httpd', 'ansible_loop_var': 'item'}) => { "msg": "output is \"active\"" } PLAY RECAP *************************************************************************** webserver1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
- 簡單的迴圈設計中,除了上面 loop 這個關鍵字放置的位置與用途之外,還需要注意:
- loop 只與前一個模組有關,取用 loop 內的變數值,就得要使用 {{item}} 變數
- 在 loop 內可以註冊輸出資料,定義某個變數名稱,例如上個案例的 myoutput
- 搭配 debug 輸出資訊時,被註冊的變數需要使用陣列特性,使用 myoutput['results'] 取出變數內容。
- 同樣的,要取出上述的實際資料時,就得要透過類似 item.stdout 或 item.content 之類的方式
- 所以,要記得背誦 item 這個英文。
- 在還不知道有迴圈可以使用時,如果你想要知道某個服務有沒有啟用,應該是要這樣做:
- 簡單的條件控制:
- 基本上,ansible 控制條件主要是透過 when 來設計的。when 的語法與 loop 有點類似,when 並不是模組,但是需要與模組同等級縮排。
而且,when 在使用變數時,似乎不需要加上 "{{ var }}" 的括號,直接使用 var 即可。when 也能調用 ansible_facts 的相關資料。
基本語法有點像這樣:
when: ansible_facts['machine'] == "x86_64" # 兩者相同才進行 when: item == "httpd" # loop 的值為 httpd 才進行 when: max_memory >= 1024 # 記憶體大於 1024M 才進行 when: min_memory is defined # 有 min_memory 這個變數時 when: min_memory is not defined # 沒有 min_memory 這個變數時 when: some_var # 當 some_var 變數是 true 時 when: not some_var # 當 some_var 變數是 false 時
- 簡單設計一下,當變數內容是 httpd 且 Linux 版本是 CentOS 時(注意大小寫),才重新啟動,可以這樣寫:
$ vim condition_start_httpd.yml --- - name: restart httpd hosts: webserver1 vars: myservice: httpd tasks: - name: restart httpd service: name: "{{ myservice }}" state: restarted when: ansible_facts['distribution'] == "CentOS" and myservice == "httpd" $ ansible-playbook condition_start_httpd.yml PLAY [restart httpd] ***************************************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [restart httpd] ***************************************************************** changed: [webserver1] PLAY RECAP *************************************************************************** webserver1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 $
- 沒有條件控制情境下,若失敗,後續動作會停止,也比較沒有效率:
$ vim condition_start_services.yml --- - name: start service with condition hosts: webserver1 vars: myservice: - httpd - named - squid tasks: - name: start services service: name: "{{ item }}" state: restarted loop: "{{ myservice }}" $ ansible-playbook condition_start_services.yml PLAY [start service with condition] ************************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [start services] **************************************************************** changed: [webserver1] => (item=httpd) failed: [webserver1] (item=named) => {"ansible_loop_var": "item", "changed": false, "item": "named", "msg": "Could not find the requested service named: host"} failed: [webserver1] (item=squid) => {"ansible_loop_var": "item", "changed": false, "item": "squid", "msg": "Could not find the requested service squid: host"} PLAY RECAP *************************************************************************** webserver1 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
如上所示,會有一個成功,兩個失敗!因為我們的 managed hosts 並沒有安裝 named 與 squid 這些服務。若能事先檢查有該指令否, 若有存在指令,才重新啟動的話~那可以透過底下的方式進行版本控制: - 使用迴圈的回傳值進行下一個迴圈的條件判斷:在一邊情境下,只要前一個指令有嚴重的錯誤時,後面的任務就會停止運作。
如上面的案例來看,如果有下一個任務,則該任務就會停止。因此,若你需要依據前面的錯誤來進行後面的任務時,
就得要加上『 ignore_errors: true 』才行。如下所示:
$ vim condition_start_services2.yml --- - name: start service with condition hosts: webserver1 vars: myservice: - httpd - named - squid tasks: - name: check daemon program command: which "{{ item }}" ignore_errors: true register: myoutput loop: "{{ myservice }}" - name: start services service: name: "{{ item.item }}" state: restarted loop: "{{ myoutput['results'] }}" when: item.rc == 0 $ ansible-playbook condition_start_services2.yml PLAY [start service with condition] ************************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [check daemon program] ********************************************************** changed: [webserver1] => (item=httpd) failed: [webserver1] (item=named) => {"ansible_loop_var": ... []} failed: [webserver1] (item=squid) => {"ansible_loop_var": ... []} ...ignoring TASK [start services] **************************************************************** changed: [webserver1] => (item={'cmd': ['which', 'httpd'], 'stdout': ...} skipping: [webserver1] => (item={'msg': 'non-zero return code', 'cmd': .. } skipping: [webserver1] => (item={'msg': 'non-zero return code', 'cmd': .. } PLAY RECAP *************************************************************************** webserver1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
如上所示,因為 command 這個模組的輸出資訊中,主要有 return_code (回傳值,在 ansible 當中為 rc 這個變數名稱), 因此,我們在 when 後面使用 item.rc 的資料,就可以依據指令成功 (回傳值為 0) 或失敗 (回傳值不為 0) 的狀況, 來設計是否要進行任務。而因為我們使用的 loop 資料是前一個指令的整體回傳訊息,因此,你可能需要先使用 debug 模組, 參考一下錯誤訊息後,找到相關的變數名稱來設計才好。
- 基本上,ansible 控制條件主要是透過 when 來設計的。when 的語法與 loop 有點類似,when 並不是模組,但是需要與模組同等級縮排。
而且,when 在使用變數時,似乎不需要加上 "{{ var }}" 的括號,直接使用 var 即可。when 也能調用 ansible_facts 的相關資料。
基本語法有點像這樣:
- 實做練習:處理群組、帳號等資訊,再以 id 指令檢測該帳號是否存在?
- 撰寫 loop_exam_user_create.yml 這個 playbook,主要針對 webserver1 這部主機
- 填寫變數,變數名稱 myusers 的內容有 exuser1, exuser2, exuser3, exuser4 等四個用戶
- 開始 tasks 的任務內容:
- 第 1 個任務,先以 group 模組建立名為 exgroup 的群組名稱。
- 第 2 個任務,針對 myusers 這個變數設計迴圈,然後以 user 模組建立起這四個用戶,且這四個用戶需要加入 exgroup 與 users 群組才行。
- 第 3 個任務,使用 command 模組,透過 id 指令檢查四個用戶,同樣加上迴圈設計。另外,註冊一個名為 myoutput 的變數
- 第 4 個任務,以 debug 模組,呼叫 myoutput 變數內容 (記得要用到 results 關鍵字),然後將 stdout 資料撈出來看看結果。
- 最後執行的結果會有點像這樣:
PLAY [create and check users] ******************************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [1st task create group] ********************************************************* changed: [webserver1] TASK [2nd task create users] ********************************************************* changed: [webserver1] => (item=exuser1) changed: [webserver1] => (item=exuser2) changed: [webserver1] => (item=exuser3) changed: [webserver1] => (item=exuser4) TASK [3th task use id check users] *************************************************** changed: [webserver1] => (item=exuser1) changed: [webserver1] => (item=exuser2) changed: [webserver1] => (item=exuser3) changed: [webserver1] => (item=exuser4) TASK [4th task show output] ********************************************************** ok: [webserver1] => (item={'cmd': ['id', ....} => { "msg": "show id output is => \"uid=3106(exuser1) gid=3107(exuser1) groups=3107(exuser1),100(users),3106(exgroup)\"" } ok: [webserver1] => (item={'cmd': ['id', ....} => { "msg": "show id output is => \"uid=3107(exuser2) gid=3108(exuser2) groups=3108(exuser2),100(users),3106(exgroup)\"" } ok: [webserver1] => (item={'cmd': ['id', ....} => { "msg": "show id output is => \"uid=3108(exuser3) gid=3109(exuser3) groups=3109(exuser3),100(users),3106(exgroup)\"" } ok: [webserver1] => (item={'cmd': ['id', ....} => { "msg": "show id output is => \"uid=3109(exuser4) gid=3110(exuser4) groups=3110(exuser4),100(users),3106(exgroup)\"" } PLAY RECAP *************************************************************************** webserver1 : ok=5 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
使用 handler 進行例外呼叫 (類似函數 function)
- handler 指的是類似例外處理的程序,那為什麼需要 handler 這個功能?
- 基本上, ansible 主要是暮等的程序,許多的任務都會一項一項的去 managed hosts 執行。 每項任務可能會被執行,也可能不會有任何的更動。若同一個 playbook 執行多次後,某些流程就會顯示不會更動了!
- 有時,某些任務必需要在某個動作失敗後,才會主動被喚醒來操作的,例如,當發現到 httpd 服務沒有運作時,才給予重新啟動。 但是,一般來說,若前一個任務失敗,通常後續的任務就會直接被略過,除非跟前小節一樣,加上『 ignore_errors = true 』 這樣的設定,才會強迫任務繼續運作下去。
- 我想要讓某個任務物脫離正常的任務範圍之內,而是當某個任務失敗,才主動去呼叫該任務,這個任務就可以寫入處理程序 (handler) 裡面。 也就是說,當某個任務出現『狀態改變』時 (不是 OK 也不是 false 喔!是 changed 時),才去呼叫某個任務!這就是 handler 要做的事。
- 你可能會說,那不是透過 when 也能進行這件事情?基本上,handler 與 when 還是有點不一樣的。when 所在的任務, 還是在一般任務裡面,所以,when 總是會被執行,只是條件不同時就不予以實際執行。那麼 handler 則是在正常程序之外的額外的程序, 所以除非被呼叫,否則一般是不會被執行的。
- 一個簡單的 handler 範例:當設定檔改變過後,才去處理該服務的重新啟動。
- 撰寫名為 handler_restart_httpd.yml 檔案,主要針對 webserver1 主機進行任務,主要的任務有:
- 安裝 httpd 服務
- 將 check.conf 檔案傳輸到 /etc/httpd/conf.d/check.conf 檔案去
- 若上述 check.conf 的內容有改變,就予以呼叫重新啟動 (restart) web 的服務。
# 先來設計 check.conf 的檔案,這個檔案其實很簡單,只是有註解的項目而已 $ vim check.conf # just a comment $ vim hander_restart_httpd.yml --- - name: restart httpd when configure changed hosts: webserver1 tasks: # 安裝 httpd - name: install httpd yum: name: httpd state: present # 上傳設定檔 - name: upload configure file copy: src: check.conf dest: /etc/httpd/conf.d/check.conf # 若有 changed 的狀態,就予以通知 (notify) handler 去執行 restart httpd 的任務 notify: - restart httpd handlers: # 建立名為 restart httpd 的任務 - name: restart httpd service: name: httpd state: restarted
- 上面這個腳本比較重要的地方,就在:
- 在需要注意的項目,亦即是有沒有改變 check.conf 設定檔內容時,若狀態為 changed (該設定檔有被改變), 那就使用 notify 的語法,去呼叫名為 restart httpd 的任務。
- 透過 handlers: 的宣告,與 tasks 在同一個層級,主要在撰寫『非例行的任務』!通常是例外處理!
- notify 底下接的任務名稱,必須在 handlers: 裡面為相同的任務名稱才會被執行喔!
$ ansible-playbook --syntax-check hander_restart_httpd.yml # 第一次執行這個 playbook $ ansible-playbook hander_restart_httpd.yml PLAY [restart httpd when configure changed] ****************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [install httpd] ***************************************************************** ok: [webserver1] TASK [upload configure file] ********************************************************* changed: [webserver1] RUNNING HANDLER [restart httpd] ****************************************************** changed: [webserver1] PLAY RECAP *************************************************************************** webserver1: ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 # 第二次執行這個 playbook $ ansible-playbook hander_restart_httpd.yml PLAY [restart httpd when configure changed] ****************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [install httpd] ***************************************************************** ok: [webserver1] TASK [upload configure file] ********************************************************* ok: [webserver1] PLAY RECAP *************************************************************************** webserver1: ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
大家可以發現,因為第二個任務變成了 ok (我們沒有重新調整 check.conf),所以系統就沒有重新啟動 httpd 囉!甚至連『 HANDLER 』的字樣都沒有被顯示出來哩!
- 撰寫名為 handler_restart_httpd.yml 檔案,主要針對 webserver1 主機進行任務,主要的任務有:
- 使用 handler 的好處 (跟 when 相比的話):
- handlers 任務的執行順序是固定的:
handlers 區塊內定義的任務,也是依序被 handlers 所調用,因此,如果有 A, B, C 三個依序在 handler 內的任務, 這三個任務會依序被調用出來看看需要不需要被執行。這些任務不是隨機被其他任務的 notify 呼叫的, 也不會因為 notify 裡面出現的順序是 B, A, C 而改變其被調用的順序。 - handlers 任務總是最後執行的:
當正常程序 (tasks) 的所有任務執行完畢之後,handlers 內的任務才會開始被依序執行。即使有 notify 到這些任務, 這些 handlers 的任務還是會接在最後一個 tasks 之後才執行。 - 相同任務名稱只有一個會執行:
要執行 handlers 的任務,通常需要 notify 指定任務名稱。handlers 內的任務名稱如果因為某些緣故導致具有相同的任務名稱時, 只有其中一個會被執行。 - handlers 的任務也只會執行一次:
當有多個正常程序的任務重複 notify 同一個 handler 的任務名稱時,該任務也只會執行一次!並不會被重複執行。這點與 shell script 的 function 差很多!完全不同。 - notify 只會通知在 changed 的狀態:
handlers 內的任務,只有在正常任務是在改變 (changed) 的狀態下,才會通知到 handler 的相關任務。當正常程序狀態是 ok 或 false 時, 都不會呼叫到 handler 的任務喔! - handler 任務正常狀態是不會被執行的:
如果正常程序執行過程都是 ok ,並沒有 changed 時,那麼正常程序跑完後,整個 playbook 就停止了,handler 任務並不會被觸發! 再次強調,只有正常程序狀態是 changed 時,才會觸發相關的 handler 任務。
- handlers 任務的執行順序是固定的:
- 實做練習:同樣的,改變了 httpd 的設定檔時,才去重新啟動 mariadb 與 httpd 兩個服務
- 撰寫名為 hander_restart_httpd2.yml 的 playbook,主要由 hander_restart_httpd.yml 複製而來
- 修改 yum 模組的任務,改變成為 loop 的方式,安裝的軟體主要有 httpd 與 mariadb-server 兩個!注意,是使用 loop 喔!
- 改變 handlers 內的 restart httpd 任務,同樣加入 loop 的功能,只是需要啟動的服務分別有 mariadb 與 httpd 這兩個!
- 改變 check.conf 的內容,內文隨便再加一行註解文字即可。
- 最後執行 hander_restart_httpd2.yml 之後,會出現類似這樣的畫面:
PLAY [restart httpd when configure changed] ****************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [install httpd] ***************************************************************** ok: [webserver1] => (item=httpd) ok: [webserver1] => (item=mariadb-server) TASK [upload configure file] ********************************************************* changed: [webserver1] RUNNING HANDLER [restart httpd] ****************************************************** changed: [webserver1] => (item=mariadb) changed: [webserver1] => (item=httpd) PLAY RECAP *************************************************************************** webserver1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
任務失敗時的處置方案: force_handlers, ignore_errors, fail module, block+rescue+always..
設計腳本總有失敗的時候,因此,我們就得要預先『防呆』。透過上面的 handler 我們可以做一些簡易的防呆,不過,還是有很呆的情境啊! 那就是如果正常任務流程失敗,那就可能會導致 handler 無法執行的問題喔!如何解決?來看看先。
- 強制使用 handler 在正常任務失敗之後:
- 如上所述, handler 是在正常程序完成整體流程之後才會被啟動。但是如果正常程序裡面有失敗的任務呢?
如果又忘記加上 ignore_errors 的話,很可能就會讓 handler 的任務不被執行喔!如下的程序:
$ cp hander_restart_httpd.yml hander_restart_httpd3.yml $ vim hander_restart_httpd3.yml --- - name: restart httpd when configure changed hosts: webserver1 tasks: - name: install httpd yum: name: httpd state: present - name: upload configure file copy: src: check.conf dest: /etc/httpd/conf.d/check.conf notify: - restart httpd # 其實只有加上這一個任務,因為沒有任何一個軟體名稱是 hehehaha,所以打包票, # 這個任務一定會失敗! - name: failure install pkg yum: name: hehehaha state: present handlers: - name: restart httpd service: name: httpd state: restarted $ vim check.conf # just hahahah $ ansible-playbook hander_restart_httpd3.yml PLAY [restart httpd when configure changed] ****************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [install httpd] ***************************************************************** ok: [webserver1] TASK [upload configure file] ********************************************************* changed: [webserver1] TASK [failure install pkg] *********************************************************** fatal: [webserver1]: FAILED! => {"changed": false, ...} RUNNING HANDLER [restart httpd] ****************************************************** <==沒有任何任務在這裡! PLAY RECAP *************************************************************************** webserver1 : ok=3 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
很簡單的就可以看到,在 HANDLER 的區塊內,沒有執行任何一個任務喔! - 使用 force_handlers: true 強迫執行:
現在,請在上面的 playbook 當中,在 hosts 底下增加一行『 force_handlers: true 』的項目,並且重新修改 check.conf 的內容, 然後重複執行一下:$ vim hander_restart_httpd3.yml --- - name: restart httpd when configure changed hosts: webserver1 force_handlers: true tasks: .... $ vim check.conf # hehehe $ ansible-playbook hander_restart_httpd3.yml PLAY [restart httpd when configure changed] ****************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [install httpd] ***************************************************************** ok: [webserver1] TASK [upload configure file] ********************************************************* changed: [webserver1] TASK [failure install pkg] *********************************************************** fatal: [webserver1]: FAILED! => {"changed": false, ...} RUNNING HANDLER [restart httpd] ****************************************************** changed: [webserver1] PLAY RECAP *************************************************************************** webserver1 : ok=4 changed=2 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
- 如上所述, handler 是在正常程序完成整體流程之後才會被啟動。但是如果正常程序裡面有失敗的任務呢?
如果又忘記加上 ignore_errors 的話,很可能就會讓 handler 的任務不被執行喔!如下的程序:
- 兩個可以強迫執行被 notify 的 handlers 任務的方法:
- force_handlers: true
如上所示,如果有任何可能會失敗的任務,你還是希望 handler 一定可以被呼叫執行,那就使用 force_handlers 吧! - ignore_errors: true
如果你已經預期某個任務會失敗,在該任務底下加上 ignore_errors: true 也是可以讓任務繼續跑下去的!!
- force_handlers: true
- 使用 failed_when 定義該模組的錯誤,或 fail 模組進行錯誤資料顯示:
- 使用者如果撰寫 shell script 具有良好的程序控制時,有時候反而會造成 ansible 不知如何是好喔!
舉例來說,我們檢查一下你的 webserver1 有沒有 joe 這個人?
$ vim hander_check_fail1.yml --- - name: check user is OK or FAIL hosts: webserver1 tasks: - name: use id to check joe man shell: "id joe && echo OK || echo FAIL" register: myoutput - name: show message debug: var: myoutput $ ansible-playbook hander_check_fail1.yml PLAY [check user is OK or FAIL] ****************************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [use id to check joe man] ******************************************************* changed: [webserver1] TASK [show message] ****************************************************************** ok: [webserver1] => { "myoutput": { "changed": true, "cmd": "id joe && echo OK || echo FAIL", "delta": "0:00:00.007586", "end": "2020-11-12 04:53:10.679374", "failed": false, "rc": 0, "start": "2020-11-12 04:53:10.671788", "stderr": "id: ‘joe’: no such user", "stderr_lines": [ "id: ‘joe’: no such user" ], "stdout": "FAIL", "stdout_lines": [ "FAIL" ] } } PLAY RECAP *************************************************************************** webserver1 : ok=3 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
明明就是沒有這個人!怎麼會顯示沒錯誤呢?這是因為 shell 裡面我們用了 echo 的指令,而 shell 只會記憶一個回傳值, 那就是最後一個指令的回傳值!因此就不會顯示錯誤了。 - 如果你要讓該程序變成錯誤的話,可以使用 failed_when 來處理!
$ vim hander_check_fail1.yml --- - name: check user is OK or FAIL hosts: webserver1 tasks: - name: use id to check joe man shell: "id joe && echo OK || echo FAIL" register: myoutput failed_when: "'FAIL' in myoutput.stdout" - name: show message debug: var: myoutput $ ansible-playbook hander_check_fail1.yml PLAY [check user is OK or FAIL] ****************************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [use id to check joe man] ******************************************************* fatal: [webserver1]: FAILED! => {"changed": true, "cmd": "id joe && echo OK || echo FAIL", "delta": "0:00:00.007584", "end": "2020-11-12 04:58:08.917877", "failed_when_result": true, "rc": 0, "start": "2020-11-12 04:58:08.910293", "stderr": "id: ‘joe’: no such user", "stderr_lines": ["id: ‘joe’: no such user"], "stdout": "FAIL", "stdout_lines": ["FAIL"]} PLAY RECAP *************************************************************************** webserver1 : ok=1 changed=0 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
這樣就會變成是一個錯誤的任務了! - 使用回報為錯誤的 fail 模組來取代 failed_when 的應用:
$ cp hander_check_fail1.yml hander_check_fail2.yml $ vim hander_check_fail2.yml --- - name: check user is OK or FAIL hosts: webserver1 tasks: - name: use id to check joe man shell: "id joe && echo OK || echo FAIL" register: myoutput ignore_errors: true - name: define to be fail task fail: msg: The task is failed when: "'FAIL' in myoutput.stdout" $ ansible-playbook hander_check_fail2.yml PLAY [check user is OK or FAIL] ****************************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [use id to check joe man] ******************************************************* changed: [webserver1] TASK [define to be fail task] ******************************************************** fatal: [webserver1]: FAILED! => {"changed": false, "msg": "The task is failed"} PLAY RECAP *************************************************************************** webserver1 : ok=2 changed=1 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
用 fail 模組取代 fail_when 的好處是,顯示的訊息比較乾淨啦!
- 使用者如果撰寫 shell script 具有良好的程序控制時,有時候反而會造成 ansible 不知如何是好喔!
舉例來說,我們檢查一下你的 webserver1 有沒有 joe 這個人?
- 使用 changed_when 搭配 handlers 處理例外狀況
- 使用 failed_when 搭配 fail 模組,主要是設計在偵測錯誤的狀況。但是,如何克服呢?基本上,還是可以透過 handlers 的! 只是,可能不能使用 failed_when,這是因為 failed_when 是回報為錯誤!但是 handlers 則是需要『 changed 』狀態啊! 所以,我們可以改用 changed_when 來搭配喔!
- 簡單範例,同樣使用上面的範本,當發現到沒有該帳號時,就主動建立該帳號的情境:
$ cp hander_check_fail1.yml hander_check_change.yml $ vim hander_check_change.yml --- - name: check user is OK or FAIL hosts: webserver1 tasks: - name: use id to check joe man shell: "id joe && echo OK || echo FAIL" register: myoutput changed_when: "'FAIL' in myoutput.stdout" notify: - create joe - name: show message debug: var: myoutput.stdout handlers: - name: create joe user: user: joe state: present $ ansible-playbook hander_check_change.yml PLAY [check user is OK or FAIL] ****************************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [use id to check joe man] ******************************************************* changed: [webserver1] TASK [show message] ****************************************************************** ok: [webserver1] => { "myoutput.stdout": "FAIL" } RUNNING HANDLER [create joe] ********************************************************* changed: [webserver1] PLAY RECAP *************************************************************************** webserver1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
你可以連續執行這個 playbook 幾次看看,你就會發現,除了第一次找不到 joe 所以會主動呼叫 handlers 一次之外, 其他時間就不會展示 HANDLER 的任務喔!
- 使用 block, rescue, always 定義任務群組
- 我們可以使用 block 來定義整個任務裡面的子任務!所謂的 block 是可以將某些子任務視為一個集合,
並且將該集合處理完畢這樣。只是,比較可惜的是,目前這個 block 只支援每個單一的任務 (子任務內有迴圈也還正常),
並不支援整體 block 子任務集合的迴圈!先來看看如何定義 block 呢?例如,分別檢查 httpd 是否啟動?
以及是否擁有 /etc/php.ini 這個檔案?
$ vim block_restart_httpd.yml --- - name: block tasks to check httpd daemon hosts: webserver1 tasks: - name: check httpd daemon state block: - name: check httpd is active command: systemctl is-active httpd ignore_errors: true - name: check php.ini exist command: ls /etc/php.ini ignore_errors: true $ ansible webserver1 -m command -a 'systemctl stop httpd' $ ansible-playbook block_restart_httpd.yml PLAY [block tasks to check httpd daemon] ********************************************* TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [check httpd is active] ********************************************************* fatal: [webserver1]: FAILED! => {"changed": true, "cmd": ...} ...ignoring TASK [check php.ini exist] *********************************************************** changed: [webserver1] PLAY RECAP *************************************************************************** webserver1 : ok=3 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=1
這樣就把兩個任務綁在一起了! - block 的子任務集合錯誤克服也是能搭配 handlers 來處理,也能搭配 when 來處置,不過,更好的作法,其實是搭配 rescue (救援)
來處理!也就字面上的意思, block 是正常程序,而 rescue 則是當 block 出現 fail 的時候才會進行的另一個子任務的集合!
$ cp block_restart_httpd.yml block_restart_httpd2.yml $ vim block_restart_httpd2.yml --- - name: block tasks to check httpd daemon hosts: webserver1 tasks: - name: check httpd daemon state block: - name: check httpd is active command: systemctl is-active httpd - name: check php.ini exist command: ls /etc/php.ini rescue: - name: restart httpd command: systemctl restart httpd $ ansible-playbook block_restart_httpd2.yml PLAY [block tasks to check httpd daemon] ********************************************* TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [check httpd is active] ********************************************************* fatal: [webserver1]: FAILED! => {"changed": true, "cmd":...} TASK [restart httpd] ***************************************************************** changed: [webserver1] PLAY RECAP *************************************************************************** webserver1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=1 ignored=0
你必需要注意的是, rescue 只有在發生錯誤 (而且不能 ignore_errors) 才會進行,因此,我們將原本 block 內的 ignore_errors 取消, 然後加上 rescue 的區塊,讓它去重新啟動 httpd !那就 OK 啦!如果你持續進行兩次以上這隻 playbook, 就可以發現 block 正常進行完畢,所以 rescue 就不會被觸發了! - 以 always 設計總是要進行的任務:
$ cp block_restart_httpd2.yml block_restart_httpd3.yml $ vim block_restart_httpd3.yml --- - name: block tasks to check httpd daemon hosts: webserver1 tasks: - name: check httpd daemon state block: - name: check httpd is active command: systemctl is-active httpd - name: check php.ini exist command: ls /etc/php.ini rescue: - name: restart httpd command: systemctl restart httpd always: - name: show httpd status command: systemctl status httpd register: myoutput - name: show output debug: var: myoutput.stdout $ ansible-playbook block_restart_httpd3.yml PLAY [block tasks to check httpd daemon] ********************************************* TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [check httpd is active] ********************************************************* changed: [webserver1] TASK [check php.ini exist] *********************************************************** changed: [webserver1] TASK [show httpd status] ************************************************************* changed: [webserver1] TASK [show output] ******************************************************************* ok: [webserver1] => { "myoutput.stdout": "● httpd.service - The Apache HTTP Server\n .... } PLAY RECAP *************************************************************************** webserver1 : ok=5 changed=3 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
透過 block, rescue, always 也能設計簡單的流程控制任務的執行!
- 我們可以使用 block 來定義整個任務裡面的子任務!所謂的 block 是可以將某些子任務視為一個集合,
並且將該集合處理完畢這樣。只是,比較可惜的是,目前這個 block 只支援每個單一的任務 (子任務內有迴圈也還正常),
並不支援整體 block 子任務集合的迴圈!先來看看如何定義 block 呢?例如,分別檢查 httpd 是否啟動?
以及是否擁有 /etc/php.ini 這個檔案?
- 實做練習:確定系統正確後,安裝相對的服務,上傳新的設定檔,重新啟動服務等任務
- 撰寫名為 block_handler.yum 的 playbook 檔案,主要針對 webserver1 進行任務
- 正常的任務列表如下:
- 第一個任務,名稱使用『 check HW and OS system 』,使用 fail 群組,檢查當 (when) (1)記憶體 ( ansible_facts['memtotal_mb'] ) 小於 1024 時,或 (2)OS 版本不是 CentOS 時,就回報失敗。要注意,因為失敗,所以後續的所有任務都不會被執行喔!
- 第二個任務,名稱使用『 Install web pksg 』,使用 yum 模組,開始安裝 httpd, mod_ssl, php 三個軟體, 無須使用 loop,請直接使用 yum 模組的功能即可。
- 第三個任務,名稱使用『 start and enable httpd 』,透過 service 模組,將 httpd 設定為立刻啟動、開機啟動。
- 第四個任務,名稱使用『 upload config file 』,內容透過 block 與 rescue 進行資料的處理:
- 在 block 內僅有一個任務,複製本地端的 check.conf 到遠端的 /etc/httpd/conf.d/check.conf 去, 若有任何改變 (changed),則通知名為『 restart httpd 』的 handlers 任務。
- 在 rescue 內僅有一個任務,若上述動作失敗,則使用 debug 模組,直接顯示『 upload file failure 』即可。
- 第五個任務,名稱使用『 modify firewall 』,使用 firewalld 放行 http 與 https 兩個防火牆規則, 這個地方應該要使用 loop 方式來處理才行。
- handlers 任務,只有一個:
- 名稱設定一定是『 restart httpd 』主要是提供第四個任務的 block 內部改變而做的。透過 service 模組,讓 httpd 狀態是 restarted 即可。
- 改變 check.conf 檔案,增加一個註解即可
- 最後,直接執行會有點像這樣:
$ ansible-playbook block_handler.yml PLAY [create web service] ************************************************************ TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [check HW and OS system] ******************************************************** skipping: [webserver1] TASK [Install web pksg] ************************************************************** ok: [webserver1] TASK [start and enable httpd] ******************************************************** ok: [webserver1] TASK [upload check.conf] ************************************************************* changed: [webserver1] TASK [modify firewall] *************************************************************** ok: [webserver1] => (item=http) ok: [webserver1] => (item=https) RUNNING HANDLER [restart httpd] ****************************************************** changed: [webserver1] PLAY RECAP *************************************************************************** webserver1 : ok=6 changed=2 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
檔案傳輸複製行為與 Jinja2 樣板
有時候我們可能會複製一些檔案或者是更改一些檔案到 server 端的電腦上面,這時,透過 control node 去對 managed host 進行檔案內容的修改,可能就是一項很重要的業務!這個動作可能是修改檔案、完整的覆蓋檔案、分析 managed host 的資料後套用樣板資料來複寫檔案, 都是可以進行的喔!
- 與檔案傳輸、複製等有關行為的 ansible 模組,底下緊列出模組的主要用途,其他詳細用法,請務必參考 ansible-doc 的查詢!
- blockinfile:傳輸一整個區塊的文字數行
- copy:複製檔案,或者是將 ansible 內的資料傳輸到 managed host 內的檔案,亦能修改檔案權限
- fetch:與 copy 相似,但是方向相反,是由 managed host 複製資料過來 control node,並且可以 managed host 的目錄樹型態存檔
- file:設定權限屬性、建立實體連結、符號連結、建立或刪除檔案等等。
- lineinfile:確認某行資料是否在檔案內、或者是『修改』某檔案的內容(大概就那一行關鍵行)
- stat:嘗試取得檔案資料
- synchronize:透過 rsync 進行資料同步
- 利用 stat 模組:先嘗試取得 webserver1 的 /etc/issue 這個檔案的 (1)sha512 的指紋資料、(2)擁有者名稱、(3)群組名
# 確認在 student 家目錄的正確 ansible 操作目錄中 $ whoami; pwd student /home/student/ansible-init # 新增一個可以增加斷行功能的模組設定 $ vim ansible.cfg [defaults] .... stdout_callback = debug # 開始設計 stat 模組的功能 $ vim file_stat.yml --- - name: check file stat use stat module hosts: webserver1 tasks: - name: check state for /etc/issue stat: path: /etc/issue checksum_algorithm: sha512 register: results - name: show output debug: msg: | the sha512 is, {{ results.stat.checksum }} the owner is {{ results.stat.pw_name }} the group is {{ results.stat.gr_name }} $ ansible-playbook --syntax-check file_stat.yml playbook: file_stat.yml $ ansible-playbook file_stat.yml PLAY [check file stat use stat module] ************************************************ TASK [Gathering Facts] **************************************************************** ok: [webserver1] TASK [check state for /etc/issue] ***************************************************** ok: [webserver1] TASK [show output] ******************************************************************** ok: [webserver1] => {} MSG: the sha512 is, 88c9342e4c2a23e61af218b9b66808925de9d2b0bd6eeb341...c1d41d497a5b0668b472 the owner is root the group is root PLAY RECAP **************************************************************************** webserver1 : ok=3 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
- 利用 file 模組,建立一個給 Samba 用的目錄,相關的設定會有點像這樣喔:
- 目錄名稱: /srv/samba_dir/
- 擁有者與群組: root:student
- 權限: 2775
- SELinux type: samba_share_t
$ vim file_file.yml --- - name: setup a SAMBA share directory hosts: webserver1 tasks: - name: create directory and make permissions file: path: /srv/samba_dir state: directory owner: root group: student mode: '2775' setype: samba_share_t - name: show file output command: cmd: ls -ld /srv/samba_dir register: output - debug: msg: "{{ output.stdout }}" $ ansible-playbook --syntax-check file_file.yml playbook: file_file.yml $ ansible-playbook file_file.yml PLAY [setup a SAMBA share directory] ************************************************** TASK [Gathering Facts] **************************************************************** ok: [webserver1] TASK [create directory and make permissions] ****************************************** changed: [webserver1] TASK [show file output] *************************************************************** changed: [webserver1] TASK [debug] ************************************************************************** ok: [webserver1] => {} MSG: drwxrwsr-x. 2 root student 6 Nov 27 17:38 /srv/samba_dir PLAY RECAP **************************************************************************** webserver1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
- 透過 fetch 模組,將 managed host 的 /var/log/secure 抓下來 (目前不支援目錄下載)
$ vim file_fetch.yml --- - name: copy files to control node hosts: webserver1 tasks: - name: fetch file to here fetch: dest: file_backup src: /var/log/secure $ ansible-playbook file_fetch.yml PLAY [copy files to control node] ***************************************************** TASK [Gathering Facts] **************************************************************** ok: [webserver1] TASK [fetch file to here] ************************************************************* changed: [webserver1] PLAY RECAP **************************************************************************** webserver1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 $ tree file_backup/ file_backup/ └── webserver1 └── var └── log └── secure 3 directories, 1 file
預設的情境下,抓下來的檔名會以個別的主機名稱以及個別的檔案,放置在頂層目錄上。如果要抓下來成為單一檔名, 就得要加上『 flat: true 』在 fetch 模組中了! - 透過 blockinfile 處理大量資料,假設我們要在 /etc/hosts 裡面新增資料,類似:
172.16.100.1 pc1 172.16.100.2 pc2 172.16.100.3 pc3
可以透過底下的方式來產生:$ vim file_blockinfile.yml --- - name: add a block area into file hosts: webserver1 tasks: - name: use blockinfile module blockinfile: path: /etc/hosts block: | 172.16.100.1 pc1 172.16.100.2 pc2 172.16.100.3 pc3 - name: get file content command: cmd: cat /etc/hosts register: output - name: show output debug: msg: "{{ output.stdout }}" $ ansible-playbook file_blockinfile.yml PLAY [add a block area into file] **************************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [use blockinfile module] ******************************************************** changed: [webserver1] TASK [get file content] ************************************************************** changed: [webserver1] TASK [show output] ******************************************************************* ok: [webserver1] => {} MSG: 127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4 ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 192.168.40.254 gateway 192.168.40.200 gitserver200.ksu gitserver 172.17.200.254 control ansible 172.17.200.1 gitclient200.ksu gitclient client1 webserver1 dbserver1 devnode1 .. # BEGIN ANSIBLE MANAGED BLOCK 172.16.100.1 pc1 172.16.100.2 pc2 172.16.100.3 pc3 # END ANSIBLE MANAGED BLOCK PLAY RECAP *************************************************************************** webserver1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
使用 blockinfile 模組,基本上,預設會在增加的資料上/下方,各加一行註解,就如同上面的輸出結果喔!可以查詢看看。 - 透過 Jinja2 這個樣板 (template) 來產生 managed host 的相關檔案內容
- 依據樣板資料產生檔案內容: Jinja2 樣板基本介紹:
- 如上所示,雖然可以透過 copy, file, blockinfile, lineinfile 等模組來修改檔案,但是如果需要比較大量的內容資料時, 使用這些模組就顯的有點麻煩。因此,就有樣板模組的功能產生,其中, ansible 用的是名為 Jinja2 的樣板。
- Jinja2 樣板可以使用許多符號方式 (delimiters) 來進行類似迴圈等行為,可以達成許多有效的內容產生。 常見的符號方式,類似透過『 {{ EXPR }} 』顯示變數資料,『 {{% EXPR %}} 』顯示邏輯運算,像剛剛提到的迴圈, 就可以透過這種百分比的符號來處理。至於,如果只是要讓編輯者知道該資料為何,就是所謂的註解,並不會產生檔案內容時, 可以使用『 {# 這是註解 #} 』來處理喔!
- 例如 /etc/hosts 內,針對本機的 IP 與主機名稱對應的撰寫,可以是這樣的方式:
{# this is /etc/hosts #} {{ ansible_facts['default_ipv4']['address']] }} {{ ansible_facts['hostname'] }}
直接透過 ansible 去分析的 managed hosts 資料的變數內容,直接拿來變更系統的設定項目就是了。 - Jinja2 副檔名建議:一般來說, jinja2 樣板的資料,只要內容正確即可,檔名倒不是很重要的地方。 不過,如果有正確的副檔名,這樣在某些編輯軟體裡面,Jinja2 的樣板,應該會更好建置才對。 所以,一般建議副檔名取名為 .j2 這樣的情境較佳。
- Jinja2 支援的變數與參數資料 - 變數與迴圈
- Jinja2 支援變數格式的使用,例如『 {{ varibale }} 』或者透過 『 {{ ansible_facts[xx] }} 』都可以。
- Jinja2 支援迴圈功能,使用的迴圈格式如下:
{% for user in users %} {{ user }} {% endfor %}
只要讓 users 成為陣列格式,就可以讓 user 一個一個列出來了。此外, Jinja2 也支援 if 的語法:{% for user in users if not user == "root" %} user number {{ loop.index }} - {{ user }} {% endfor %}
當然也能直接使用陣列值{% for host in groups['hosts'] %} {{ host }} {% endfor %}
- Jinja2 支援的狀態參數 (contitions),例如:
{% if finished %} {{ results }} {% endif %}
- 輸出格式的處理,可以轉成 json 或 yaml 喔!當然,也能從 json 或 yaml 轉成一般文字:
{{ output | to_json }} {{ output | to_yaml }} {{ output | from_json }} {{ output | from_yaml }}
- 使用 template 模組來支援 Jinja 格式檔案的方式,例如:
- name: template sample template: src: files/filename.j2 dest: /etc/to/file
- 我們登入系統之後,系統會顯示歡迎訊息的資訊,這些資訊其實是寫入到 /etc/motd 這個檔案內。
假設你想要客製化 /etc/motd 時,並且加入某些特定的資料,例如主機名稱、系統版本、系統管理員等等的資料,
可以先建立一個樣板檔案,然後再處理相關的資訊即可:
# 先建立樣板檔案 $ mkdir files $ vim files/motd.j2 The hostname is {{ ansible_facts['fqdn'] }} My Linux distribution is {{ ansible_facts['distribution'] }} and version is {{ ansible_facts['distribution_version'] }} if you have any question contact with {{ system_owner }} # 開始撰寫 playbook 的內容 $ vim file_jinja.yml --- - name: setup a file content hosts: webserver1 vars: system_owner: student@gitserver200.dic.ksu tasks: - name: setup /etc/motd file content template: src: files/motd.j2 dest: /etc/motd owner: root group: root mode: 0644 - name: get file content command: cmd: cat /etc/motd register: output - name: show output debug: msg: "{{output.stdout}}" $ ansible-playbook file_jinja.yml PLAY [setup a file content] ********************************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [setup /etc/motd file content] ************************************************** changed: [webserver1] TASK [get file content] ************************************************************** changed: [webserver1] TASK [show output] ******************************************************************* ok: [webserver1] => {} MSG: The hostname is gitclient200.ksu My Linux distribution is CentOS and version is 8.1 if you have any question contact with student@gitserver200.dic.ksu PLAY RECAP *************************************************************************** webserver1 : ok=4 changed=2 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
- 依據樣板資料產生檔案內容: Jinja2 樣板基本介紹:
- 實做練習:先導知識,當使用者在本機端的文字終端機 (tty2, tty3..) 嘗試燈入時,會看到類似如下的畫面:
CentOS Linux 8 (Core) Kernel 4.18.0-.... on an x86_64
這個資料其實是 /etc/issue 提供的內容。而如果你想從網路登入時,那麼系統會提供 /etc/issue.net 的內容來顯示。 至於登入之後,就會以 /etc/motd 內容來顯示。現在,假設你登入之後,系統還會額外提供一些資訊的話,該如何處理? 例如,當你登入 managed host 之後,系統會提示如下的畫面:############################################ # # This server is managed by ansible # please contect student@gitserver200.dic.ksu # System total memory: xxx MiB. # System prcessor count: N. # ############################################
- 先依據上面的指示,建立好 motd.j2 的樣板範本,特別注意,上表中那個特殊顏色的字體,務必使用變數或者是 ansible_facts 來設計,這樣才能在不同的系統處理好。
- 編寫 playbook ,內容包括需要的:
- play 的名稱定義為: setup login show up
- 針對 webserver1 主機設計
- 建立 system_owner 變數
- 建立第一個工作,使用 template,來源檔案 files/motd2.j2,目標檔案 /etc/motd,擁有者與群組都是 root, 權限 -rw-r--r--
- 檢查過語法之後,直接執行這個 play,大概會出現類似底下的畫面:
PLAY [setup login show up] *********************************************************** TASK [Gathering Facts] *************************************************************** ok: [webserver1] TASK [setup /etc/motd file content] ************************************************** changed: [webserver1] PLAY RECAP *************************************************************************** webserver1 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
- 執行 ssh student@webserver1 ,並順利登入後,應該會有如底下的畫面才對:
$ ssh student@webserver1 student@webserver1's password: ############################################ # # This server is managed by ansible # please contect student@gitserver200.dic.ksu # System total memory: 1829 MiB. # System prcessor count: 2. # ############################################ Activate the web console with: systemctl enable --now cockpit.socket Last login: Sat Nov 28 05:34:28 2020 from 172.17.200.254