全世界最簡單的 Ansible 文章
![](https://assets.matters.news/embed/e8c3907e-1260-4afc-a437-915556fa021c.png)
Ansible 是 IT 界的戰略部署工具,只要制定好行動綱領,它就會依照劇本執行自動化部署,有能力讓我們跟美軍一樣在 24 小時內在全球任何一個節點上部署投射 IT 戰力。
Ansible 基礎
在 Ansible 的世界裡,有所謂的主控節點(control node)及受管節點(managed node),主控節點就是指揮部,通常就是我們手中那台工作用的電腦,主控電腦的起手式當然是把 Ansible 裝起來:
$ sudo add-apt-repository --yes --update ppa:ansible/ansible $ sudo apt install ansible
而受管節點呢,不用裝任何代理器,這也是 Ansible 最大的特點,它是用 SSH 登進去工作的,而其他的類似工具則大多需要透過代理器才能工作,免代理器的設計能讓我們省一點心。
一般來說,受管節點不會只有一個,這也才顯得出 Ansible 的價值,這些受管節點可以用一個類似 hosts 的檔案整理之,看起來會像這樣:
[webservers] 192.168.122.17 ansible_user=web17 ansible_password=web17pw 192.168.122.18 ansible_user=web18 ansible_password=web18pw [dbservers] db01.intranet.mydomain.net db02.intranet.mydomain.net
這份清單在 Ansible 的世界稱為 inventory,它的結構類似 INI 格式,應該是一望即知,後面的 ansible_user
、ansible_password
自然就是那台節點的帳密啦!
而那兩台沒有特別標注帳密的資料庫節點,則會用主控節點當前的帳號和密鑰登入,這當然是不現實的,誰會把遠端主機的帳號設成和自己電腦一樣呢。
最基礎的安裝和設定完成,就可以來玩一波了!
假設上面這份 inventory 位置在 ~/Projects/ansible/hosts,那可以如此調用:
$ cd ~/Projects/ansible/ $ ansible webservers -i hosts -a "/bin/echo hello"
成功的訊息如下:
192.168.122.17 | CHANGED | rc=0 >> hello 192.168.122.18 | CHANGED | rc=0 >> hello
在這個簡單的範例中,最後面的 webservers
自然就是指 inventory 中 [webservers]
之中的節點。
再來一個範例:
$ ansible webservers -i hosts -m ping
成功的回應如下:
192.168.122.17 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" } 192.168.122.18 | SUCCESS => { "ansible_facts": { "discovered_interpreter_python": "/usr/bin/python3" }, "changed": false, "ping": "pong" }
上面的 ping
並非我們常用的 ping
命令,而是 Ansible 的 ping module,它用來探測受管節點上的 Python 位置,並以 pong
回覆之。
以上都是直接在命令列使用的方法,比較適合臨時的遠端調用一些比較簡單的指令,這種使用模式在 Ansible 稱為 ad hoc 模式,如果是複雜的情境,那就需要編排明確的行動綱領,也就是後面會提到的 playbook。
在前面兩個例子分別用了 -m ping
和 -a "/bin/echo hello"
,其中的 -m
表示 module,而 -a
表示給 module 的參數。
在第一個例子我們省略了 -m
,此時 Ansible 會調用預設的 command module,它會將引數 /bin/echo hello
在受管節點中執行。
Module
下面是一些各路 module 的範例。
用 root 帳號
$ ansible webservers -i hosts -a "/sbin/reboot" --become --ask-become-pass
其中的 --become
表示 sudo
。
因為 command module 的命令不是在 shell 中執行,因此 shell 的變數、管道、導向都是無效的,如果有需要得改用 shell module:
$ ansible webservers -i hosts -m ansible.builtin.shell -a 'echo $TERM'
設時鐘
$ ansible webservers -i hosts -m community.general.timezone -a 'name=Asia/Taipei' --become --ask-become-pass
設完時區最好重啟 cron 服務:
$ ansible webservers -i hosts -m ansible.builtin.service -a "name=cron state=restarted" --become --ask-become-pass
檔案處理
可以把檔案從主控節點拷貝到受管節點:
$ ansible webservers -i hosts -m ansible.builtin.copy -a "src=/etc/hosts dest=/tmp/hosts"
改檔案權限:
$ ansible webservers -i hosts -m ansible.builtin.file -a "dest=/srv/foo/b.txt mode=600 owner=mdehaan group=mdehaan"
建目錄:
$ ansible webservers -i hosts -m ansible.builtin.file -a "dest=/path/to/c state=directory"
刪目錄:
$ ansible webservers -i hosts -m ansible.builtin.file -a "dest=/path/to/c state=absent"
不知道是否有政治正確的因素,竟然是用 absent
表示刪除,相當不直覺。
套件管理
安裝套件:
$ ansible webservers -i hosts -m ansible.builtin.apt -a "name=mc" --become --ask-become-pass
更新套件:
$ ansible webservers -i hosts -m ansible.builtin.apt -a "name=mc state=latest" --become --ask-become-pass
移除套件:
$ ansible webservers -i hosts -m ansible.builtin.apt -a "name=mc state=absent" --become --ask-become-pass
更新套件清單:
$ ansible webservers -i hosts -m ansible.builtin.apt -a "update_cache=yes" --become --ask-become-pass
升級全部套件:
$ ansible webservers -i hosts -m ansible.builtin.apt -a "upgrade=safe" --become --ask-become-pass
那個 safe
就相當於 apt upgrade
,也可以是 full
,相當於 apt full-upgrade
。
帳號和群組管理
建帳號:
$ ansible webservers -i hosts -m ansible.builtin.user -a "name=foo password=<crypted password here>" --become --ask-become-pass
刪帳號:
$ ansible webservers -i hosts -m ansible.builtin.user -a "name=foo state=absent" --become --ask-become-pass
服務管理
啟動服務:
$ ansible webservers -i hosts -m ansible.builtin.service -a "name=ufw state=started" --become --ask-become-pass
重啟服務:
$ ansible webservers -i hosts -m ansible.builtin.service -a "name=ufw state=restarted" --become --ask-become-pass
停止服務:
$ ansible webservers -i hosts -m ansible.builtin.service -a "name=ufw state=stopped" --become --ask-become-pass
獲得滿滿的節點資訊
$ ansible webservers -i hosts -m ansible.builtin.setup
Playbook
不可能什麼都靠一行指令打天下,複雜的、週期性的任務可以寫成 Playbook 讓 Ansible 替我們完成。
那和自己寫 shell 腳本有什麼不同?一般來說,在三台機器以內自己寫甚至手動搞都還游刃有餘,超過五台那還是用專門的工具吧,既可以加快效率又可以減少失誤。
當然現實上還有另一種交付模式「射後不理」也是頗常見,但這就不在本文的討論範圍內了。
回到 Playbook。Playbook 是 YAML 的結構,下面這是 mytask.yaml:
--- - name: My playbook hosts: webservers tasks: - name: Leaving a mark ansible.builtin.command: cmd: "touch /tmp/ansible_was_here"
跑起來:
$ cd ~/Projects/ansible/ $ ansible-playbook -i hosts mytask.yaml
結果:
PLAY [My playbook] **************************************************************** TASK [Gathering Facts] ************************************************************ ok: [192.168.122.17] ok: [192.168.122.18] TASK [Leaving a mark] ************************************************************* changed: [192.168.122.17] changed: [192.168.122.18] PLAY RECAP ************************************************************************ 192.168.122.17: ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0 192.168.122.18: ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
在這份 playbook 中,制定了一個名為「My playbook」的「play」,旗下有「task」,這唯一的 task 名為「Leaving a mark」。
根據以上的描述,playbook 的基本結構就是 playbook > play > task:
- 一份 playbook 可以有多個 play(一份劇本有多場戲)
- 一個 play 可以有多個 task(一場戲有多個事務)
在上面的例子中,它的作用相當於:
$ ansible webservers -i hosts -m ansible.builtin.command -a "touch /tmp/ansible_was_here"
前面提過,command module 是預設行為,所以可以再簡化成:
$ ansible webservers -i hosts -a "touch /tmp/ansible_was_here"
上面的 playbook 範例中,我們餵了 cmd
參數給 command module,每個 module 都有不同的參數,具體該怎麼餵就要參閱他們的文件了。
Task 的執行順序
如果有多個 task 和多個受管節點,則執行順序如下:
- 在A節點跑 task 1
- 在B節點跑 task 1
- 在A節點跑 task 2
- 在B節點跑 task 2
- 以此類推
不會是在A節點跑完 task 1234 再去B節點。
如果B節點的 task 1 失敗了,那 Ansible 會將B節點落入敗部,後續B節點的 task 也不會跑了,那能敗部復活重返榮耀嗎?不能。
用 root 帳號
在命令列我們用 --become
讓 Ansible 在受管節點以 root 執行工作,在 playbook 也是類似:
- name: Ensure the UFW service is running ansible.builtin.service: name: ufw state: started become: true
這個 task 就相當於
-m ansible.builtin.service -a "name=ufw state=started" --become
那 sudo
密碼怎麼辦呢?用老招 --ask-become-pass
即可,所以要跑這份 playbook 就會這樣下:
$ cd ~/Projects/ansible/ $ ansible-playbook -i hosts --ask-become-pass mytask.yaml
或者也可以在 inventory 檔案內附加 sudo
密碼:
[webservers] 192.168.122.17 ansible_user=web17 ansible_password=web17pw ansible_become_password=web17pw
如果不要 root,而是別的帳號,那可以再用 become_user
指定:
- name: Ensure the Nginx service is running ansible.builtin.service: name: nginx state: started become: true become_user: nginx_admin
以上是 playbook 的基礎,只要掌握這些基礎用法應該就可以滿足大部分的使用場景了。
Ansible 的二八法則
本文真的只是 Ansible 的基礎基礎基基礎,不過即使是 20% 的用法也能滿足 80% 的需求了。
Ansible 還有更多花式玩法:
- 在 playbook 納入變數的概念
- 在 playbook 調用 Jinja2 模板語言和變數
- 產生動態的 inventory
- 讓帳號密碼加密使用
- 在 playbook 調用另一份 playbook
這些錦上添花的用法,端看需求自行求道囉,個人是覺得 Ansible 只是配置工具,影響工作效率但不影響應用效能,過度鑽研反而會降低工作效率,因為你花太多時間研究用不到的技術,除非你的工作真的必須是一位 Ansible 職人,所以剩下的部份等我進 Red Hat 再說吧 :p。
喜欢我的作品吗?别忘了给予支持与赞赏,让我知道在创作的路上有你陪伴,一起延续这份热忱!