本文是「Vagrant+VirtualBox虛擬化環境搭建」的續篇,深入探索Ansible在自動化運維中的核心應用:
? Ansible核心技能:Playbook編寫、角色(Roles)模塊化、標簽(Tags)精準控制
? 實戰場景覆蓋:Apache服務部署、HAProxy負載均衡配置、服務器聯動管理
? 無縫銜接Vagrant:在虛擬化環境中模擬真實運維場景,提供完整可復現的代碼示例
無論是想快速入門Ansible的新手,還是需要優化現有流水線的工程師,都能從本文獲得從基礎的自動化運維解決方案。文末附常見問題排查與性能對比數據,助你避坑提速!
參考教程來自:leucos/ansible-tuto: Ansible 教程
碼云:git clone https://gitee.com/xbd_zc/ansible-tuto-demosite.git
前期準備(虛擬機安裝及配置)【Vagrant+VirtualBox創建自動化虛擬環境】Ansible-Playbook-CSDN博客
這里準備4臺,host0-3,host0:安裝Ansible管理host1-3,如通過vagrant創建,Vagrantfile配置如下:
# -*- mode: ruby -*-
# vi: set ft=ruby :hosts = {"host0" => "192.168.0.220","host1" => "192.168.0.221","host2" => "192.168.0.222","host3" => "192.168.0.223"
}Vagrant.configure("2") do |config|hosts.each do |name, ip|config.vm.define name do |machine|machine.vm.box = "bento/ubuntu-20.04"machine.vm.box_version = "202407.23.0"machine.vm.hostname = "%s" % namemachine.vm.network :public_network,bridge: "en1", ip: ipmachine.vm.provider "virtualbox" do |v|v.name = namev.customize ["modifyvm", :id, "--memory", 1024]endendend
end
1.Ansible-測試準備
準備四臺服務器,并在本機安裝Ansible,一個hosts文件如下所示:
環境:本機host0(192.168.0.220),系統版本Ubuntu 20.04.6 LTS,vagrant鏡像:bento/ubuntu-20.04、版本:202407.23.0,私鑰配置好
host1 ansible_host=192.168.0.221 ansible_user=root
host2 ansible_host=192.168.0.222 ansible_user=root
host3 ansible_host=192.168.0.223 ansible_user=root
ansible_host
是一個特殊變量,用于設置 ansible 將在 嘗試連接到此主機。
ansible_user
是另一個特殊變量,它告訴 ansible 使用 SSH 時以此用戶身份連接。默認情況下,ansible 將使用 當前用戶名,或使用 ~/.ansible.cfg 中提供的其他默認值 ().`remote_user
檢查主機是否正常工作
ansible -m ping all -i hosts
- -m MODULE_NAME,–module-name MODULE_NAME (模塊名稱)
- -i INVENTORY, --inventory INVENTORY, --inventory-file INVENTORY (文件清單)
ansible 執行ping
輸出應如下所示:
host1 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
host3 | SUCCESS => { "ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
host2 | SUCCESS => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"ping": "pong"
}
好!所有 3 個主機都處于活動狀態,并且 ansible 可以與它們通信。
2.Ansible-節點通信
在主機上執行shell命令
ansible -i hosts -m shell -a 'uname -a' host1
- -a MODULE_ARGS, --args MODULE_ARGS 模塊參數
輸出應如下所示:
host1 | CHANGED | rc=0 >>
Linux host1 5.4.0-189-generic #209-Ubuntu SMP Fri Jun 7 14:05:13 UTC 2024 x86_64 x86_64 x86_64 GNU/Linux
復制文件
復制本機hosts文件到host1上。
ansible -i step-02/hosts -m copy -a 'src=/etc/hosts dest=/tmp/' host1
輸出應類似于:
host1 | CHANGED => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": true,"checksum": "2e2bd6a4e52b3f3a709a96214cc308753be3a7d4","dest": "/tmp/hosts","gid": 0,"group": "root","md5sum": "d7c7e7ffc3d982c56de6801bb665ba9b","mode": "0644","owner": "root","size": 244,"src": "/root/.ansible/tmp/ansible-tmp-1745964825.1255076-56136-181611889373774/source","state": "file","uid": 0
}#host1主機上查看
vagrant@host1:~$ cat /tmp/hosts
127.0.0.1 localhost
127.0.1.1 vagrant# The following lines are desirable for IPv6 capable hosts
::1 ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
127.0.2.1 host0 host0
shell 模塊 – 在目標主機上執行 shell 命令
獲取在節點上部署了哪個 Ubuntu 版本
ansible -i hosts -m shell -a 'grep DISTRIB_RELEASE /etc/lsb-release' all
all
是一個快捷方式,表示“在清單文件中找到的所有主機”。它會 返回:
host2 | CHANGED | rc=0 >>
DISTRIB_RELEASE=20.04
host1 | CHANGED | rc=0 >>
DISTRIB_RELEASE=20.04
host3 | CHANGED | rc=0 >>
DISTRIB_RELEASE=20.04
setup module –收集有關遠程主機的信息
如果我們 需要更多信息(IP 地址、RAM 大小等)可以使用此模塊
ansible -i hosts -m setup host1
回復包含大量信息:
host1 | SUCCESS => {"ansible_facts": {"ansible_all_ipv4_addresses": ["10.0.2.15","192.168.0.221"], .......................................#還有很多"module_setup": true},"changed": false
}
可以篩選返回的信息,以防您正在尋找 一些特定的東西。
例如想知道您的所有節點的總內存
ansible -i hosts -m setup -a 'filter=ansible_memtotal_mb' all
輸出如下:
host1 | SUCCESS => {"ansible_facts": {"ansible_memtotal_mb": 964,"discovered_interpreter_python": "/usr/bin/python3"},"changed": false
}
host2 | SUCCESS => {"ansible_facts": {"ansible_memtotal_mb": 964,"discovered_interpreter_python": "/usr/bin/python3"},"changed": false
}
host3 | SUCCESS => {"ansible_facts": {"ansible_memtotal_mb": 964,"discovered_interpreter_python": "/usr/bin/python3"},"changed": false
}
請注意,與上一個輸出相比,主機的回復順序不同。 這是因為 ansible 與主機并行通信!
順便說一句,當使用 setup 模塊時,你可以在表達式中使用。 它的作用就像一個 shell glob。*``filter=
選擇主機
我們看到這表示 “所有主機”,但 ansible 提供了很多方式 — Ansible 社區文檔:all
host0:host1
將在 host0 上運行,并且 主機 1host*
將在以 ‘host’ 開頭和結尾的所有主機上運行 與 ‘’ (就像 shell glob 一樣)
常見的事實變量示例
- ?操作系統信息?:
ansible_os_family
:操作系統的家族,如Debian、RedHat等。ansible_distribution
:操作系統的分發版名稱。ansible_distribution_version
:操作系統的版本號。ansible_distribution_major_version
:操作系統的主版本號。
- ?硬件信息?:
ansible_processor_vcpus
:處理器的虛擬CPU數量。ansible_memory_mb
:總內存大小(以MB為單位)。ansible_disk_usage
:磁盤使用情況。
- ?網絡配置?:
ansible_default_ipv4
:默認的IPv4地址。ansible_all_ipv4_addresses
:所有的IPv4地址。ansible_hostname
:主機名。
獲取事實變量的方法
?使用setup
模塊?:
-
在playbook中,可以通過setup模塊獲取所有事實變量。例如:
#顯示所有主機的事實,并將其按“主機名”索引存儲在“/tmp/facts”中。 # ansible all -m ansible.builtin.setup --tree /tmp/facts #僅顯示有關ansible在所有主機上找到的內存的事實,并將其輸出。 # ansible all -m ansible.builtin.setup -a 'filter=ansible_*_mb' #僅顯示facter返回的事實。 #ansible all -m ansible.builtin.setup -a 'filter=facter_*' #只收集事實者返回的事實。 #ansible all -m ansible.builtin.setup -a 'gather_subset=!all,facter' - name: Collect only facts returned by facteransible.builtin.setup:gather_subset:- '!all'- '!<any valid subset>'- facter
-
使用filter參數來查看指定的信息,例如:
- name: Filter and return only selected factsansible.builtin.setup:filter:- 'ansible_distribution'- 'ansible_machine_id'- 'ansible_*_mb' # 僅顯示有關某些接口的事實。 # ansible all -m ansible.builtin.setup -a 'filter=ansible_eth[0-2]' #將額外收集的事實限制為網絡和虛擬(包括默認的最小事實) # ansible all -m ansible.builtin.setup -a 'gather_subset=network,virtual' #僅收集網絡和虛擬(不包括默認的最小事實) # ansible all -m ansible.builtin.setup -a 'gather_subset=!all,network,virtual' #即使在場,也不要叫木偶師或歐海。 # ansible all -m ansible.builtin.setup -a 'gather_subset=!facter,!ohai' #僅收集默認的最低事實數量: # ansible all -m ansible.builtin.setup -a 'gather_subset=!all' #不收集任何事實,即使是默認的最小事實子集: # ansible all -m ansible.builtin.setup -a 'gather_subset=!all,!min' #使用存儲在C:\custom_fact中的自定義事實顯示來自Windows主機的事實。 # ansible windows -m ansible.builtin.setup -a "fact_path='c:\custom_facts'" #收集dbservers組中機器的事實(也稱為委派事實) - hosts: app_serverstasks:- name: Gather facts from db serversansible.builtin.setup:delegate_to: "{{ item }}"delegate_facts: trueloop: "{{ groups['dbservers'] }}"
3.Ansible主機分組與變量管理
為什么需要主機分組與變量管理?
在 Ansible 自動化運維中,主機分組和變量管理是提升效率的核心手段:
- 批量操作:通過邏輯分組快速執行命令(如更新所有 Web 服務器)。
- 環境隔離:為開發、測試、生產環境分配獨立配置。
- 配置復用:通過變量動態適配不同主機的參數。
主機分組:靈活管理多節點
1. 基礎分組
在 inventory
文件中定義主機組,支持數字范圍和通配符:
[web]
web-[1:3].example.com # 匹配 web-1.example.com 至 web-3.example.com[db]
db-*.example.com # 匹配所有以 db- 開頭的域名
2. 嵌套子組
通過 :children
定義層級關系,實現復雜環境管理:
[ubuntu]
host1 ansible_host=192.168.1.101[debian]
host2 ansible_host=192.168.1.102[linux:children] # 父組聚合子組
ubuntu
debian[prod:children] # 按環境劃分
linux
3. 分組實戰場景
-
多環境管理:
[dev] dev-web1 ansible_host=10.0.0.101[prod] prod-web1 ansible_host=192.168.1.101[all:children] # 全局聚合 dev prod
-
角色劃分:
[nginx] lb1 ansible_host=192.168.1.200[tomcat] app[1:3] ansible_host=192.168.1.[201:203]
變量管理:動態配置
1. 變量定義位置
位置 | 優先級 | 適用場景 |
---|---|---|
命令行 (-e ) | 最高 | 臨時覆蓋變量 |
主機變量文件 | 高 | 主機專屬配置(如 IP、端口) |
組變量文件 | 中 | 共享配置(如軟件版本) |
庫存文件 | 低 | 基礎連接參數(如 ansible_port ) |
2. 變量定義示例
-
庫存文件直接定義:
[web] web1 ansible_host=192.168.1.101 ansible_port=2222 # 指定 SSH 端口
-
主機變量文件 (
host_vars/web1.yml
):--- http_port: 8080 max_connections: 1000
-
組變量文件 (
group_vars/web.yml
):--- nginx_version: 1.25.2 app_env: production
3. 變量繼承與覆蓋
- 繼承邏輯:子組繼承父組變量,同名變量優先級為
子組 > 父組
。 - 調試技巧:使用
ansible-inventory --graph
查看主機歸屬與變量來源。
4.”Hello World“apache
Ansible playbook 是 Ansible 的基本組件之一,因為它們記錄和執行 Ansible 的配置。通常,playbook 是自動執行要在遠程計算機上執行的一組任務的主要方式。
它們通過收集所有必要的資源來編排有序流程或避免重復手動作,從而幫助我們實現自動化。Playbook 可以在人員之間重復使用和共享,它們被設計為對人類友好且易于在 YAML 中編寫。
使用Ansible Playbooks - 帶有示例的提示和技巧
sed -i ‘s/33.11/0.221/’ step-04/hosts
準備清單文件,命名為:hosts
[web]
host2 ansible_host=192.168.0.222 ansible_user=root
構建一個 playbook,它將在組中的計算機上安裝 apache。web
- hosts: webtasks:- name: Installs apache web serverapt:pkg: apache2state: presentupdate_cache: true
- -name 為此任務添加了一個名稱。雖然這不是必需的,但它可以在 playbook 運行時提供信息
- apt 模塊,它可以 安裝 Debian 軟件包
運行 playbook :apache.yml
ansible-playbook -i step-04/hosts -l host2 step-04/apache.yml
- -l SUBSET, --limit SUBSET (限制分組)
運行后可看到以下內容:
PLAY [web] ************TASK [Gathering Facts] ***********
ok: [host2]TASK [Installs apache web server] *******************
changed: [host2]PLAY RECAP *************
host2 : ok=2 changed=1 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
- PLAY [web] *** 表示開始執行一個針對主機組 ?**
web
**? 的 play。[web]
:對應 playbook 中定義的hosts: web
,目標主機組來自 Ansible 清單文件(如inventory
文件)。
- TASK [Gathering Facts] 用戶自定義的任務名稱,這里表示安裝 Apache Web 服務器。
changed: [host2]
:表示此任務在主機host2
上執行成功并引發了系統變更(例如安裝了新軟件包)。- TASK [Installs apache web server] 用戶自定義的任務名稱,這里表示安裝 Apache Web 服務器。
changed: [host2]
:表示此任務在主機host2
上執行成功并引發了系統變更(例如安裝了新軟件包)。
- PLAY RECAP 整個 play 的執行結果匯總。
- **
ok=2
**?:2 個任務執行成功且無變更(如Gathering Facts
)。 - ?**
changed=1
**?:1 個任務引發了變更(如安裝 Apache)。 - 其他字段(如
failed=0
)表示無失敗、無跳過、無重試等。
- **
現在再次運行一次,看看會發生什么:
ansible-playbook -i step-04/hosts -l host2 step-04/apache.ymlPLAY [web] *********TASK [Gathering Facts] ********
ok: [host2]TASK [Installs apache web server] *****
ok: [host2]PLAY RECAP *******
host2 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
‘changed=0’ 。這是絕對正常的,也是核心功能之一 的 Ansible:playbook 僅在有事可做時才會起作用。這稱為冪等性
5.優化apache設置
我們已經安裝了 apache,現在讓我們設置我們的 virtualhost。
在 Apache 配置場景中,virtualhost
(虛擬主機)用于在同一臺服務器上托管多個網站或應用。通過不同的域名、IP 或端口,Apache 可區分請求并返回對應的網站內容。在 Ansible Playbook 中,配置 virtualhost
通常涉及以下操作:
- 創建虛擬主機配置文件(如
.conf
文件); - 指定域名、文檔根目錄、日志路徑等參數;
- 啟用配置并重啟 Apache 服務。
我們的服務器上只需要一個 virtualhost,但我們想替換默認的 一個具有更具體的東西。因此,我們必須刪除當前的 (大概是)VirtualHost,發送我們的 VirtualHost,激活它并 重新啟動 Apache。
添加我們的 virtualhost 配置 給 Host1,路徑為:files/awesome-app
<VirtualHost *:80>DocumentRoot /var/www/awesome-appOptions -IndexesErrorLog /var/log/apache2/error.logTransferLog /var/log/apache2/access.log
</VirtualHost>
?指令? | ?作用? |
---|---|
<VirtualHost *:80> | 監聽所有 IP 的 80 端口,處理對該虛擬主機的請求。 |
DocumentRoot | 網站根目錄為 /var/www/awesome-app ,存放網站文件(需確保目錄存在)。 |
Options -Indexes | 禁止目錄瀏覽(若目錄中沒有 index.html ,用戶無法查看文件列表)。 |
ErrorLog | 錯誤日志路徑為 /var/log/apache2/error.log 。 |
TransferLog | 訪問日志路徑為 /var/log/apache2/access.log 。 |
hosts
[web]
host1 ansible_host=192.168.0.221 ansible_user=root
更新 apache playbook
---
- hosts: web #指定該 Play 針對清單文件(inventory)中定義的 web 主機組執行。tasks:- name: Installs apache web server
#模塊:apt(適用于 Debian/Ubuntu 系統)。apt:
#安裝 Apache2 軟件包。pkg: apache2
#確保 Apache2 已安裝。state: presentupdate_cache: true
#等同于執行 apt update,更新軟件包緩存(確保安裝最新版本)。- name: Push default virtual host configurationcopy:src: files/awesome-appdest: /etc/apache2/sites-available/awesome-app.conf
#將本地 files/awesome-app 文件復制到目標主機的 /etc/apache2/sites-available/awesome-app.conf。mode: 0640
#設置文件權限為 -rw-r-----(所有者可讀寫,所屬組可讀,其他用戶無權限)。- name: Activates our virtualhostfile:src: /etc/apache2/sites-available/awesome-app.confdest: /etc/apache2/sites-enabled/awesome-app.confstate: link
#在 sites-enabled 目錄創建符號鏈接(state: link),指向 sites-available/awesome-app.conf。Apache 會加載 sites-enabled 中的配置文件,這一步等同于運行 a2ensite awesome-app.conf。 notify:- restart apache
#如果任務狀態為 changed,觸發 restart apache 處理器。- name: Disable the default virtualhostfile:dest: /etc/apache2/sites-enabled/000-default.confstate: absentnotify:- restart apache- name: Disable the default ssl virtualhost
#這兩步禁用默認虛擬主機,防止默認虛擬主機與新配置沖突。 file:dest: /etc/apache2/sites-enabled/default-ssl.confstate: absent
#state: absent:刪除 sites-enabled 目錄中的默認配置文件符號鏈接(等同于 a2dissite 000-default.conf 和 a2dissite default-ssl.conf)。notify:- restart apachehandlers:- name: restart apache
#處理器(Handlers),當任務觸發 notify: restart apache 時,重啟 Apache 服務(僅在相關配置文件發生變更時觸發)。service:name: apache2state: restarted
執行輸出:
PLAY [web] ********* TASK [Gathering Facts] ********
ok: [host1]TASK [Installs apache web server] *******
ok: [host1]TASK [Push default virtual host configuration] ****c
changed: [host1]TASK [Activates our virtualhost] ****** c
changed: [host1]TASK [Disable the default virtualhost] ******
changed: [host1]TASK [Disable the default ssl virtualhost] ******
ok: [host1]RUNNING HANDLER [restart apache] *******
changed: [host1]PLAY RECAP *******
host1 : ok=7 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
在重新啟動 apache 之前,我們難道不應該檢查一下配置是否正常嗎?如果我們的配置文件不正確,則不會中斷服務。
6.在配置正確時重新啟動
我們已經安裝了 apache,推送了我們的 virtualhost 并重新啟動了服務器。但 如果我們希望 playbook 僅在配置為 正確?讓我們這樣做。
Ansible 有一個漂亮的功能:如果出現問題,它將停止所有處理 錯。如果配置 文件無效。
讓我們更改我們的虛擬主機配置文件搞破壞DocumentDoot=》RocumentDoot:awesome-app
<VirtualHost *:80>RocumentDoot /var/www/awesome-appOptions -IndexesErrorLog /var/log/apache2/error.logTransferLog /var/log/apache2/access.log
</VirtualHost>
如前所述,當任務失敗時,處理將停止。因此,我們將確保 配置在重新啟動服務器之前有效。我們還首先添加 我們的 VirtualHost 在刪除默認 VirtualHost 之前,因此后續的重啟 (可能直接在服務器上完成) 不會破壞 Apache。
---
- hosts: web # 🌐 指定目標主機組為 `web`(需在 Ansible 清單中定義)tasks:# 🔧 任務1:安裝 Apache- name: Installs apache web serverapt: # 📦 使用 apt 模塊(Debian/Ubuntu 系統)pkg: apache2 # 軟件包名稱state: present # 確保 apache2 已安裝update_cache: true # 先執行 `apt update` 更新軟件源緩存# 📄 任務2:推送虛擬主機配置文件- name: Push future default virtual host configurationcopy: # 📂 使用 copy 模塊復制文件src: files/awesome-app # 本地文件路徑(相對于 Playbook)dest: /etc/apache2/sites-available/awesome-app.conf # 目標路徑mode: 0640 # 設置文件權限為 -rw-r-----# 🔗 任務3:啟用自定義虛擬主機- name: Activates our virtualhostcommand: a2ensite awesome-app # 🖥? 直接調用 Apache 命令啟用站點# 等同于創建符號鏈接:/etc/apache2/sites-enabled/awesome-app.conf -> ../sites-available/awesome-app.conf# ? 任務4:驗證 Apache 配置語法- name: Check that our config is validcommand: apache2ctl configtest # 🔍 檢查配置是否有語法錯誤# 如果配置錯誤,任務會失敗并終止 Playbook 執行# 🚫 任務5:禁用默認虛擬主機(非 SSL)- name: Deactivates the default virtualhostcommand: a2dissite 000-default # ? 禁用默認站點# 🚫 任務6:禁用默認 SSL 虛擬主機- name: Deactivates the default ssl virtualhostcommand: a2dissite default-ssl # ? 禁用默認 SSL 站點notify: # 🔔 觸發處理器- restart apache # 僅當此任務狀態為 changed 時重啟 Apachehandlers: # ?? 處理器(異步任務)# 🔄 處理器:重啟 Apache 服務- name: restart apacheservice: # 🛠? 使用 service 模塊name: apache2 # 服務名稱state: restarted # 重啟服務
執行
ansible-playbook -i step-06/hosts -l host1 step-06/apache.ymlPLAY [web] ******** TASK [Gathering Facts] ********
ok: [host1]TASK [Installs apache web server] *********
ok: [host1]TASK [Push future default virtual host configuration] *******
changed: [host1]TASK [Activates our virtualhost] *****
changed: [host1]TASK [Check that our config is valid] *********
fatal: [host1]: FAILED! => {"changed": true, "cmd": ["apache2ctl", "configtest"], "delta": "0:00:00.020643", "end": "2025-05-01 06:30:47.032996", "msg": "non-zero return code", "rc": 1, "start": "2025-05-01 06:30:47.012353", "stderr": "AH00526: Syntax error on line 2 of /etc/apache2/sites-enabled/awesome-app.conf:\nInvalid command 'RocumentDoot', perhaps misspelled or defined by a module not included in the server configuration", "stderr_lines": ["AH00526: Syntax error on line 2 of /etc/apache2/sites-enabled/awesome-app.conf:", "Invalid command 'RocumentDoot', perhaps misspelled or defined by a module not included in the server configuration"], "stdout": "Action 'configtest' failed.\nThe Apache error log may have more information.", "stdout_lines": ["Action 'configtest' failed.", "The Apache error log may have more information."]}PLAY RECAP *********
host1 : ok=4 changed=2 unreachable=0 failed=1 skipped=0 rescued=0 ignored=0
正如您所見,由于 apache2ctl
在失敗時會以退出代碼 1 返回,所以 Ansible 會意識到這一點并停止處理。太棒了!
嗯,實際上情況不太好……我們的虛擬主機還是添加上了。不過,后續任何一次重啟 Apache 都會報錯我們的配置有問題并退出。所以我們需要一種方法來捕獲失敗并回退。
從錯誤信息可以明確看出 Apache 配置文件的語法錯誤直接導致 Playbook 執行失敗
Invalid command 'RocumentDoot'
#重啟測試
ansible -i step-06/hosts -m service -a 'name=apache2 state=restarted' host1
host1 | FAILED! => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": false,"msg": "Unable to restart service apache2: Job for apache2.service failed because the control process exited with error code.\nSee \"systemctl status apache2.service\" and \"journalctl -xe\" for details.\n"
}
7.使用條件語句
我們已經安裝了 Apache,推送了虛擬主機并重啟了服務器。 但如果出現問題,我們希望將一切恢復到穩定狀態。
出現問題時回退
當任務失敗時,處理就會停止……除非我們接受失敗(而且我們應該接受)。這就是我們要做的: 如果出現失敗,繼續處理,但僅限于撤銷我們已做的操作。
- hosts: web # 僅在清單中定義的 'web' 主機組執行tasks:# 安裝 Apache- name: Installs apache web serverapt:pkg: apache2 # 安裝 Apache2 軟件包state: present # 確保軟件包存在update_cache: true # 先執行 `apt update` 更新緩存# 推送配置文件(潛在風險點)- name: Push future default virtual host configurationcopy:src: files/awesome-app # 本地配置文件名dest: /etc/apache2/sites-available/awesome-app.conf # 目標路徑mode: 0640 # 權限設置(可能需確保 Apache 用戶可讀)# 啟用新虛擬主機(非冪等操作)- name: Activates our virtualhostcommand: a2ensite awesome-app # ? 重復執行會報錯 "Site already enabled"args:creates: /etc/apache2/sites-enabled/awesome-app.conf #冪等性控制,添加creates參數確保任務僅在符號鏈接不存在時執行,避免重復報錯。notify: restart apache # 觸發重啟# 驗證配置語法(關鍵安全閥)- name: Check that our config is validcommand: apache2ctl configtestregister: result # 存儲命令執行結果ignore_errors: true # 即使驗證失敗也繼續執行(觸發回滾)# 回滾操作1:重新啟用默認站點(需確保默認配置存在)- name: Rolling back - Restoring old default virtualhostcommand: a2ensite 000-defaultwhen: result is failed # 僅在配置驗證失敗時執行# 回滾操作2:禁用新站點- name: Rolling back - Removing our virtualhostcommand: a2dissite awesome-appwhen: result is failed# 強制終止 Playbook 并提示錯誤- name: Rolling back - Ending playbookfail:msg: "Configuration file is not valid. Please check that before re-running the playbook."when: result is failed # 終止后續任務# 禁用默認站點(僅在配置驗證成功后執行)- name: Deactivates the default virtualhostcommand: a2dissite 000-defaultnotify: restart apache # 統一服務重啟觸發,任何修改站點配置的操作都需要重啟 Apache,否則變更不會生效。# 禁用默認 SSL 站點- name: Deactivates the default ssl virtualhostcommand: a2dissite default-sslnotify:- restart apache # 觸發服務重啟(但其他任務也需要類似設置)handlers:# 服務重啟處理器- name: restart apacheservice:name: apache2state: restarted
關鍵字 register
會記錄 apache2ctl configtest
命令的輸出(退出狀態、標準輸出、標準錯誤……),并且 when: result is failed
會檢查已注冊的變量( result
)是否包含失敗狀態。
ansible-playbook -i step-07/hosts -l host1 step-07/apache.ymlPLAY [web] ********TASK [Gathering Facts] *************
ok: [host1]TASK [Installs apache web server] ********
ok: [host1]TASK [Push future default virtual host configuration] *********
ok: [host1]TASK [Activates our virtualhost] ************
ok: [host1]TASK [Check that our config is valid] *************
fatal: [host1]: FAILED! => {"changed": true, "cmd": ["apache2ctl", "configtest"], "delta": "0:00:00.022459", "end": "2025-05-01 07:10:28.993221", "msg": "non-zero return code", "rc": 1, "start": "2025-05-01 07:10:28.970762", "stderr": "AH00526: Syntax error on line 2 of /etc/apache2/sites-enabled/awesome-app.conf:\nInvalid command 'RocumentDoot', perhaps misspelled or defined by a module not included in the server configuration", "stderr_lines": ["AH00526: Syntax error on line 2 of /etc/apache2/sites-enabled/awesome-app.conf:", "Invalid command 'RocumentDoot', perhaps misspelled or defined by a module not included in the server configuration"], "stdout": "Action 'configtest' failed.\nThe Apache error log may have more information.", "stdout_lines": ["Action 'configtest' failed.", "The Apache error log may have more information."]}
...ignoringTASK [Rolling back - Restoring old default virtualhost] ******************
changed: [host1]TASK [Rolling back - Removing our virtualhost] ****************
changed: [host1]TASK [Rolling back - Ending playbook] **************
fatal: [host1]: FAILED! => {"changed": false, "msg": "Configuration file is not valid. Please check that before re-running the playbook."}PLAY RECAP ************
host1 : ok=7 changed=3 unreachable=0 failed=1 skipped=0 rescued=0 ignored=1
看起來如預期般奏效了。讓我們試著重啟 Apache 看看是否真的成功了:
ansible -i step-07/hosts -m service -a 'name=apache2 state=restarted' host1
host1 | CHANGED => {"ansible_facts": {"discovered_interpreter_python": "/usr/bin/python3"},"changed": true,"name": "apache2","state": "started","status": {
能正常啟動,現在我們的 Apache 服務器已免受錯誤配置的影響了。
8.從 Git 部署網站
我們已經安裝了 Apache,推送了虛擬主機并安全地重啟了服務器。 現在我們將使用 Git 模塊來部署我們的應用程序。
git模塊
我們的虛擬主機已設置好,但還需要做一些改動才能完成部署。 首先,我們要部署一個 PHP 應用程序,所以需要安裝 libapache2-mod-php
包。其次,由于用于克隆我們應用程序的 Git 倉庫的 Git 模塊需要用到它,所以我們還得安裝 git
。
Ansible 可以遍歷一系列項目,并像這樣在操作中使用每個項目:
...
- name: Installs necessary packagesapt:pkg: "{{ item }}"state: latestupdate_cache: truewith_items:- apache2- libapache2-mod-php- git
...
注意:自 2.7 版本起,在模塊中使用循環僅調用一次的做法已棄用。您可以按照下表所示指定包的列表:
Option A 選擇一個 | Option B 選項B |
---|---|
- name: "Install foo & bar" apt: pkg: ["foo", "bar"] | - name: "Install foo & bar" apt: pkg: - foo - bar |
最終方案:
- hosts: webtasks:- name: ;**安裝必要軟件包**;(包括Apache, PHP模塊, Git)apt:pkg: ["apache2", "libapache2-mod-php", "git"]state: latestupdate_cache: true- name: ;**推送默認的虛擬主機配置文件**;copy:src: files/awesome-appdest: /etc/apache2/sites-available/awesome-app.confmode: 0640- name: ;**激活我們的虛擬主機**;command: a2ensite awesome-app- name: ;**檢查配置是否有效**;command: apache2ctl configtestregister: resultignore_errors: true- name: ;**回滾 - 恢復舊的默認虛擬主機**;(如果配置測試失敗)command: a2ensite 000-defaultwhen: result is failed- name: ;**回滾 - 移除我們的虛擬主機**;(如果配置測試失敗)command: a2dissite awesome-appwhen: result is failed- name: ;**回滾 - 結束Playbook**;(如果配置測試失敗)fail:msg: "配置文件無效。請在重新運行Playbook之前檢查。"when: result is failed#如果執行git模塊出現Failed to connect to github.com port 443: Connection refused,Ansible 的 git 模塊可能因舊倉庫狀態導致沖突。強制清理目標目錄#- name: 清理舊目錄# file:# path: /var/www/awesome-app# state: absent# ignore_errors: yes # 防止目錄不存在時報錯# tags: deploy- name: ;**部署我們的應用程序**;(使用Git)git:repo: 'https://github.com/leucos/ansible-tuto-demosite.git'dest: /var/www/awesome-appforce: yes # 強制覆蓋本地修改update: yes # 確保拉取最新代碼version: master # 明確指定分支tags: deploy # 可通過`ansible-playbook ... --tags deploy`單獨執行此任務- name: ;**禁用默認的虛擬主機**;(應在部署后禁用,確保不會沖突)command: a2dissite 000-defaultnotify: restart apache # 統一服務重啟觸發,任何修改站點配置的操作都需要重啟 Apache,否則變更不會生效。- name: ;**禁用默認的SSL虛擬主機**;(如果不需要SSL,可考慮移除或注釋此行)command: a2dissite default-sslnotify:- restart apachehandlers:- name: restart apacheservice:name: apache2state: restarted
啟動
ansible-playbook -i step-08/hosts -l host1 step-08/apache.ymlPLAY [web] ************TASK [Gathering Facts] ***********
ok: [host1]TASK [;**安裝必要軟件包**;(包括Apache, PHP模塊, Git)] **********
ok: [host1]TASK [;**推送默認的虛擬主機配置文件**;] ***********
ok: [host1]TASK [;**激活我們的虛擬主機**;] *************
changed: [host1]TASK [;**檢查配置是否有效**;] *************
changed: [host1]TASK [;**回滾 - 恢復舊的默認虛擬主機**;(如果配置測試失敗)] ***************
skipping: [host1]TASK [;**回滾 - 移除我們的虛擬主機**;(如果配置測試失敗)] ******************
skipping: [host1]TASK [;**回滾 - 結束Playbook**;(如果配置測試失敗)] **************
skipping: [host1]TASK [;**部署我們的應用程序**;(使用Git)] ************
ok: [host1]TASK [;**禁用默認的虛擬主機**;(應在部署后禁用,確保不會沖突)] ****************
changed: [host1]TASK [;**禁用默認的SSL虛擬主機**;(如果不需要SSL,可考慮移除或注釋此行)] ************
changed: [host1]RUNNING HANDLER [restart apache] **************
changed: [host1]PLAY RECAP ************
host1 : ok=9 changed=5 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
訪問測試host1
http://192.168.0.221
Welcome to your awesome app !
App backend host1顯示了一張圖片以及服務器的主機名
請注意, tags: deploy
這一行允許您僅執行playbook中的某一部分。 假設您為您的網站推送了一個新版本。您希望加快速度,僅執行負責部署的部分。標簽允許您這樣做。 當然,“deploy”只是一個字符串,它沒有任何特定的含義,可以是任何內容。讓我們看看如何使用它:
ansible-playbook -i step-08/hosts -l host1 step-08/apache.yml -t deployPLAY [web] **********TASK [Gathering Facts] ***********
ok: [host1]TASK [;**部署我們的應用程序**;(使用Git)] **********
ok: [host1]PLAY RECAP **************
host1 : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0# 顯示所有可用標簽
ansible-playbook site.yml --list-tags# 詳細日志跟蹤
ansible-playbook site.yml -t deploy -vvv
Failed to connect to github.com port 443: Connection refused
如果執行到git模塊時報此錯:
查看具體執行情況
ansible-playbook -i step-12test/hosts -l web step-12test/site.yml --tags deploy -vvv
先排查防火墻、網絡狀態等網絡情況,并進入對應報錯的主機執行git 測試能否下載項目,
#手動查詢分支情況
git ls-remote --heads https://github.com/leucos/ansible-tuto-demosite.git
Ansible 的 git 模塊可能因舊倉庫狀態導致沖突。強制清理目標目錄,添加模塊再測試
- name: 清理舊目錄file:path: /var/www/awesome-appstate: absentignore_errors: yes # 防止目錄不存在時報錯tags: deploy- name: ;**部署我們的應用程序**;(使用Git)git:repo: 'https://github.com/leucos/ansible-tuto-demosite.git'dest: /var/www/awesome-appforce: yes # 強制覆蓋本地修改update: yes # 確保拉取最新代碼version: master # 明確指定分支tags: deploy # 可通過`ansible-playbook ... --tags deploy`單獨執行此任務
9.添加另一臺 Web 服務器
現在有了一臺web服務器(host1),現在再添加一臺web服務器和一臺負載均衡服務器
更新inventory
sed -i -e ‘s/host0/host3/’ -e ‘s/33/0/’ -e ‘s/11/221/’ -e ‘s/12/222/’ -e ‘s/10/223/’ step-09/hosts
cat step-09/hosts
[web]
host1 ansible_host=192.168.0.221 ansible_user=root
host2 ansible_host=192.168.0.222 ansible_user=root[haproxy]
host3 ansible_host=192.168.0.223 ansible_user=root
在此指定 ansible_host
是因為主機的 IP 地址與預期不同(或者無法解析)。您可以將這些主機添加到 /etc/hosts
中,從而無需擔心,或者使用真實的主機名(通常您會采取的做法)。
再建一個web服務器
cp step-08/apache.yml step-09/apache.yml
ansible-playbook -i step-09/hosts step-09/apache.ymlPLAY [web] ************TASK [Gathering Facts] ***************
ok: [host2]
ok: [host1]TASK [;**安裝必要軟件包**;(包括Apache, PHP模塊, Git)] *****************
ok: [host1]
changed: [host2]TASK [;**推送默認的虛擬主機配置文件**;] *************
changed: [host2]
ok: [host1]TASK [;**激活我們的虛擬主機**;] *************
changed: [host2]
changed: [host1]TASK [;**檢查配置是否有效**;] ***************
changed: [host1]
changed: [host2]TASK [;**回滾 - 恢復舊的默認虛擬主機**;(如果配置測試失敗)] *****************
skipping: [host2]
skipping: [host1]TASK [;**回滾 - 移除我們的虛擬主機**;(如果配置測試失敗)] *****************
skipping: [host1]
skipping: [host2]TASK [;**回滾 - 結束Playbook**;(如果配置測試失敗)] *************
skipping: [host1]
skipping: [host2]TASK [;**部署我們的應用程序**;(使用Git)] *************
ok: [host1]
changed: [host2]TASK [;**禁用默認的虛擬主機**;(應在部署后禁用,確保不會沖突)] *************
changed: [host1]
changed: [host2]TASK [;**禁用默認的SSL虛擬主機**;(如果不需要SSL,可考慮移除或注釋此行)] ************
changed: [host2]
changed: [host1]RUNNING HANDLER [restart apache] ***********
changed: [host2]
changed: [host1]PLAY RECAP *************
host1 : ok=9 changed=5 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
host2 : ok=9 changed=8 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
我們所要做的就是從命令行中移除 -l host1
。請記住, -l
是一個限制在特定主機上運行playbook的開關。現在我們不再限制了,它將在playbook預期運行的所有主機上運行(即 web
)。
如果我們有其他服務器在 web
組中,但只想將playbook限制在其中的一個子集上,那么我們可以使用例如: -l firsthost:secondhost:...
。
10.HAProxy 配置模板
HAProxy 是一個高性能的 ?負載均衡器? 和 ?反向代理?,常用于分發流量到多個后端服務器,提升系統可用性和擴展性。核心功能包括:
- ?流量分發?:支持輪詢(round-robin)、加權輪詢、最少連接等算法。
- ?健康檢查?:自動檢測后端服務器狀態,移除故障節點。
- ?SSL終止?:可處理HTTPS請求解密,減輕后端服務器壓力。
- ?高可用性?:結合Keepalived可實現雙機熱備。
Ansible 使用 Jinja2,這是 Python 的一個模板引擎。在編寫 Jinja2 模板時,您可以使用 Ansible 定義的任何變量。
例如,如果您想要輸出當前模板所構建的主機的 inventory_name,您只需在 Jinja 模板中寫入 {{ inventory_hostname }}
即可。
或者如果您需要第一個以太網接口的 IP 地址(Ansible 通過 setup
模塊已知),您只需在模板中寫入: {{ ansible_default_ipv4.address}}
(這與 {{ ansible_default_ipv4['address'] }}
等效)。
Jinja2 模板還支持條件語句、for 循環等。
讓我們創建一個 templates/
目錄,并在其中創建一個 Jinja 模板。我們將它命名為 haproxy.cfg.j2
。按照慣例,我們使用 .j2
擴展名,以表明這是一個 Jinja2 模板,但這并非必要。
step-10/templates/haproxy.cfg.j2
globaldaemon # 以守護進程模式運行maxconn 256 # 設置最大并發連接數為256defaultsmode http # 默認使用HTTP模式timeout connect 5000ms # 連接超時時間為5秒timeout client 50000ms # 客戶端超時時間為50秒timeout server 50000ms # 服務器端超時時間為50秒listen clusterbind {{ ansible_host }}:80 # 綁定主機的IP和端口(動態變量來自Ansible)mode http # 啟用HTTP模式stats enable # 開啟統計頁面balance roundrobin # 使用輪詢負載均衡算法
{% for backend in groups['web'] %} # 遍歷Ansible的web組內所有主機server {{ hostvars[backend]['ansible_hostname'] }} {{ hostvars[backend]['ansible_facts']['default_ipv4']['address'] }} check port 80 # 動態生成后端服務器配置(主機名+IP+健康檢查)
{% endfor %}option httpchk HEAD /index.php HTTP/1.0 # 定義HTTP健康檢查方法
注意事項!!! {#section1}
檢查Ansible事實中的default_ipv4
是否是正確的,如果不是需更在haproxy.cfg.j2中改為正確的路徑
#我這里的事實中['default_ipv4']對應eth0,目前使用的網卡是eth1(由于vagrant設置的public_network)
ansible -i hosts -m setup host1
host1 | SUCCESS => {
...............................................
"ansible_eth1": {"active": true,"device": "eth1","features": {
..............................................},"hw_timestamp_filters": [],"ipv4": {"address": "192.168.0.221",
#將step-10/templates/haproxy.cfg.j2配置文件中的server改為如下
server {{ hostvars[backend]['ansible_hostname'] }} {{ hostvars[backend].ansible_eth1.ipv4.address }}:80 check port 80
#改完后再執行Ansible-Playbook測試
該模板用于?動態生成HAProxy負載均衡配置?:
- ?動態綁定IP端口?:通過
{{ ansible_host }}
注入主機IP。 - ?自動化后端服務器配置?:基于Ansible的
web
主機組循環生成服務器列表,避免手動維護。 - ?參數統一管理?:超時時間、負載均衡策略等集中定義,便于批量修改。
HAProxy playbook
step-10/haproxy.yml
- hosts: webgather_facts: true # 收集目標服務器的系統信息(如IP、OS版本)- hosts: haproxy # 針對名為 "haproxy" 的主機組執行以下任務tasks:# 任務1:安裝 HAProxy- name: Installs haproxy load balancerapt:pkg: haproxy # 包名稱state: present # 確保已安裝update_cache: yes # 更新 apt 緩存(相當于 apt update)# 任務2:推送配置文件- name: Pushes configurationtemplate:src: templates/haproxy.cfg.j2 # 使用 Jinja2 模板生成配置文件dest: /etc/haproxy/haproxy.cfg # 目標路徑mode: 0640 # 文件權限(root可讀寫,haproxy組可讀)owner: root # 文件所有者group: root # 文件所屬組notify:- restart haproxy # 文件變更后觸發重啟操作# 任務3:確保 HAProxy 開機自啟- name: Sets default starting flag to 1lineinfile:dest: /etc/default/haproxy # 修改系統服務配置文件regexp: "^ENABLED" # 查找以 ENABLED 開頭的行line: "ENABLED=1" # 強制設置為 1(啟用服務)notify:- restart haproxyhandlers:# 定義重啟 HAProxy 的處理程序- name: restart haproxyservice:name: haproxystate: restarted
執行:
ansible-playbook -i step-10/hosts step-10/apache.yml step-10/haproxy.ymlPLAY [web] ******************************************************************************************************************************TASK [Gathering Facts] ******************************************************************************************************************
ok: [host1]
ok: [host2]TASK [Installs necessary packages] ******************************************************************************************************
ok: [host1]
ok: [host2]TASK [Push future default virtual host configuration] ***********************************************************************************
ok: [host2]
ok: [host1]TASK [Activates our virtualhost] ********************************************************************************************************
changed: [host2]
changed: [host1]TASK [Check that our config is valid] ***************************************************************************************************
changed: [host2]
changed: [host1]TASK [Rolling back - Restoring old default virtualhost] *********************************************************************************
skipping: [host1]
skipping: [host2]TASK [Rolling back - Removing out virtualhost] ******************************************************************************************
skipping: [host1]
skipping: [host2]TASK [Rolling back - Ending playbook] ***************************************************************************************************
skipping: [host1]
skipping: [host2]TASK [Deploy our awesome application] ***************************************************************************************************
ok: [host1]
ok: [host2]TASK [Deactivates the default virtualhost] **********************************************************************************************
changed: [host2]
changed: [host1]TASK [Deactivates the default ssl virtualhost] ******************************************************************************************
changed: [host2]
changed: [host1]RUNNING HANDLER [restart apache] ********************************************************************************************************
changed: [host2]
changed: [host1]PLAY RECAP ******************************************************************************************************************************
host1 : ok=9 changed=5 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
host2 : ok=9 changed=5 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0PLAY [web] ******************************************************************************************************************************TASK [Gathering Facts] ******************************************************************************************************************
ok: [host2]
ok: [host1]PLAY [haproxy] **************************************************************************************************************************TASK [Gathering Facts] ******************************************************************************************************************
ok: [host3]TASK [Installs haproxy load balancer] ***************************************************************************************************
changed: [host3]TASK [Pushes configuration] *************************************************************************************************************
changed: [host3]TASK [Sets default starting flag to 1] **************************************************************************************************
changed: [host3]RUNNING HANDLER [restart haproxy] *******************************************************************************************************
changed: [host3]PLAY RECAP ******************************************************************************************************************************
host1 : ok=10 changed=5 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
host2 : ok=10 changed=5 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
host3 : ok=5 changed=4 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
測試
http://192.168.0.223/
您甚至可以在 http://192.168.0.233/haproxy?stats 查看 HAProxy 的統計信息。
正常Status為OPEN,如果為DOWN,host3:80報503注意檢查以下內容:
- 防火墻(host1 和 host2是否能訪問)
- HAProxy配置中的服務器ip是否正確(HAProxy服務器host3中的haproxy.cfg配置文件)
cat /etc/haproxy/haproxy.cfg #檢查類似以下部分server host1 192.168.0.221:80 checkserver host2 192.168.0.222:80 check
處理辦法見上面:注意事項
template和lineinfile模塊
template
模塊
- 將動態生成的配置文件推送到目標服務器。
- 使用 Jinja2 模板引擎,允許在配置文件中插入變量、循環、條件判斷等邏輯。
- 最終生成一個靜態配置文件并傳輸到目標服務器的指定路徑。
關鍵特性
- 動態渲染:假設您的
haproxy.cfg.j2
中可能包含類似下面的動態內容:
{% for server in groups.web %}
server {{ server }} {{ hostvars[server].ansible_default_ipv4.address }}:80 check
{% endfor %}
這會根據 web
主機組的服務器列表自動生成后端服務器配置。
- 冪等性:如果目標文件已經存在且內容相同,不會重復操作。
- 權限控制:可以精確設置文件權限、所有者和用戶組。
適用場景
- 需要動態生成配置文件(例如根據變量、主機列表生成不同內容)。
- 需要將配置文件與 Playbook 邏輯解耦(模板和邏輯分離)。
lineinfile
模塊
- 確保文件中某一行存在(或不存在)。
- 通過正則表達式匹配目標行,并修改或替換它。
- 常用于簡單修改配置文件中的某個參數。
關鍵特性
- 精準修改:只修改匹配到的行,不影響文件其他內容。
- 冪等性:如果目標行已經是
ENABLED=1
,不會重復修改。 - 靈活性:可以用正則表達式匹配復雜模式。
適用場景
- 修改文件中的某個特定參數(如
ENABLED=1
)。 - 添加一行內容(例如在
sshd_config
中添加PermitRootLogin no
)。
對比總結
模塊 | 用途 | 特點 | 適用場景 |
---|---|---|---|
template | 生成并推送動態配置文件 | 支持變量渲染、循環、條件判斷 | 需要動態生成完整配置文件時 |
lineinfile | 修改文件中某一行 | 精準修改、正則匹配 | 修改單個參數或添加單行內容 |
常見問題
- 為什么不直接用
copy
模塊?
copy
模塊直接復制靜態文件,而template
允許在文件中使用變量和邏輯。- 例如,在您的 HAProxy 配置中,后端服務器列表可能動態變化,用
template
可以自動生成列表。
lineinfile
會覆蓋整個文件嗎?
- 不會!它只修改匹配到的行,其他內容保持不變。
- 如果一行都不匹配會怎樣?
- 默認會在文件末尾添加
line
指定的內容。可以通過insertafter
或insertbefore
參數控制插入位置。
11.變量再探
所以我們已經設置好了負載均衡器,運行得相當不錯。我們從事實中獲取變量,并用它們來構建配置。但 Ansible 還支持其他類型的變量。我們已經在清單中看到了 ansible_host
,但現在我們將使用在 host_vars
和 group_vars
文件中定義的變量。
微調我們的 HAProxy 配置
HAProxy 通常會檢查后端是否處于活動狀態。當某個后端似乎已停止運行時,它會被從后端池中移除,HAProxy 也不會再向其發送請求。
后端也可以有不同的權重(在 0 到 256 之間)。權重越高,與其他后端相比,該后端將接收的連接數就越多。如果節點的性能不均衡,這有助于更合理地分配流量。
我們將使用變量來配置所有這些參數。
Group vars
檢查間隔將在 haproxy 的 group_vars 文件中設置。這將確保所有 haproxy 都能繼承該設置。
我們只需在庫存目錄下創建文件 group_vars/haproxy.yml
即可。該文件必須以您想要為其定義變量的組命名。如果我們想為 web 組定義變量,該文件應命名為 group_vars/web.yml
。
請注意, .yml
是可選的:我們可以將 haproxy 組變量文件命名為 group_vars/haproxy
,Ansible 也能接受。擴展名只是幫助編輯器選擇正確的語法高亮顯示。
haproxy_check_interval: 3000
haproxy_stats_socket: /tmp/sock
#名稱是任意的。當然,有意義的名稱是推薦的,但沒有規定的語法。您甚至可以像這樣使用復雜的變量(也就是 Python 字典):
haproxy:check_interval: 3000stats_socket: /tmp/sock
這只是個人喜好問題。復雜變量有助于邏輯上對內容進行分組。 在某些情況下,它們還可以合并后續定義的鍵(但請注意,這并非 Ansible 的默認行為)。目前我們只使用簡單變量。
Hosts vars
主機變量遵循完全相同的規則,但位于 host_vars
目錄下的文件中。
讓我們在 host_vars/host1.example.com
中為我們的后端定義權重:
haproxy_backend_weight: 100
#以及 host_vars/host2.example.com
haproxy_backend_weight: 150
如果我們在 group_vars/web
中定義 haproxy_backend_weight
,它將被用作“默認值”:在 host_vars
文件中定義的變量會覆蓋在 group_vars
中定義的變量。
更新模板
模板必須更新以使用這些變量。
globaldaemonmaxconn 256
{% if haproxy_stats_socket %}stats socket {{ haproxy_stats_socket }}
{% endif %}defaultsmode httptimeout connect 5000mstimeout client 50000mstimeout server 50000mslisten clusterbind {{ ansible_host }}:80mode httpstats enablebalance roundrobin
{% for backend in groups['web'] %}server {{ hostvars[backend]['ansible_hostname'] }} {{ hostvars[backend].ansible_eth1.ipv4.address }}:80 check port 80 weight {{ hostvars[backend].haproxy_backend_weight | default(100) }}
{% endfor %}option httpchk HEAD /index.php HTTP/1.0
請注意,我們還引入了一個 {% if ...
塊。只有當測試為真時,此塊內的內容才會被渲染。因此,如果我們為負載均衡器在某個地方定義了 haproxy_stats_socket
(甚至可能在命令行中使用 --extra-vars="haproxy_stats_sockets=/tmp/sock"
),則包含的行將出現在生成的配置文件中(請注意,建議的設置非常不安全!)。
更新HAProxy.yaml:
---
- name: 配置 Web 服務器組hosts: webgather_facts: true # 收集 web 組的服務器信息(如 IP、OS 版本)# 用于后續模板生成后端服務器列表- name: 配置 HAProxy 負載均衡器hosts: haproxy # 目標為 haproxy 主機組tasks:# 任務1:安裝 HAProxy- name: 安裝 HAProxyapt:pkg: haproxystate: present # 確保 HAProxy 已安裝update_cache: yes # 更新 apt 緩存(相當于 apt update)notify: haproxy_config_flow # 觸發名為 haproxy_config_flow 的處理程序鏈# 任務2:備份當前配置(用于回退)- name: 備份 HAProxy 配置copy:src: /etc/haproxy/haproxy.cfg # 源文件(遠程服務器上的當前配置)dest: /etc/haproxy/haproxy.cfg.bak # 備份路徑remote_src: yes # 操作遠程文件force: yes # 強制覆蓋舊備份when: not ansible_check_mode # 僅在非檢查模式運行(實際部署時執行)changed_when: false # 不標記為已變更(避免誤觸發其他任務)notify: haproxy_config_flow # 觸發處理程序鏈# 任務3:生成并推送新配置- name: 生成并推送 HAProxy 配置template:src: templates/haproxy.cfg.j2 # Jinja2 模板路徑dest: /etc/haproxy/haproxy.cfg # 目標配置文件路徑mode: 0640 # 文件權限(root 讀寫,haproxy 組只讀)owner: rootgroup: rootnotify: haproxy_config_flow # 觸發處理程序鏈# 任務4:確保 HAProxy 開機自啟- name: 啟用 HAProxy 開機啟動lineinfile:dest: /etc/default/haproxy # 修改服務啟動配置文件regexp: "^ENABLED" # 匹配以 ENABLED 開頭的行line: "ENABLED=1" # 強制設置為啟用notify: haproxy_config_flow # 觸發處理程序鏈handlers:# 處理程序1:驗證配置文件語法- name: 驗證 HAProxy 配置command: haproxy -c -f /etc/haproxy/haproxy.cfg # 檢查配置合法性register: haproxy_validate # 存儲命令執行結果(rc=0 表示成功)listen: haproxy_config_flow # 綁定到 haproxy_config_flow 事件鏈ignore_errors: yes # 允許驗證失敗后繼續執行后續任務# 處理程序2:重啟 HAProxy 服務(僅在驗證成功時執行)- name: 重啟 HAProxyservice:name: haproxystate: restartedwhen: - haproxy_validate.rc == 0 # 僅當驗證通過時觸發listen: haproxy_config_flow # 綁定到同一事件鏈# 處理程序3:恢復備份配置(驗證失敗時執行)- name: 恢復 HAProxy 備份配置copy:src: /etc/haproxy/haproxy.cfg.bak # 備份文件路徑dest: /etc/haproxy/haproxy.cfg # 覆蓋錯誤配置remote_src: yeswhen: - haproxy_validate.rc != 0 # 僅當驗證失敗時觸發listen: haproxy_config_flow # 綁定到同一事件鏈notify: 重啟 HAProxy # 恢復配置后觸發重啟
執行:
ansible-playbook -i step-11/hosts step-11/haproxy.ymlPLAY [配置 Web 服務器組] ****************************************************************************************************************TASK [Gathering Facts] ******************************************************************************************************************
ok: [host2]
ok: [host1]PLAY [配置 HAProxy 負載均衡器] **********************************************************************************************************TASK [Gathering Facts] ******************************************************************************************************************
ok: [host3]TASK [安裝 HAProxy] *********************************************************************************************************************
ok: [host3]TASK [備份 HAProxy 配置] ****************************************************************************************************************
ok: [host3]TASK [生成并推送 HAProxy 配置] **********************************************************************************************************
changed: [host3]TASK [啟用 HAProxy 開機啟動] ************************************************************************************************************
ok: [host3]RUNNING HANDLER [驗證 HAProxy 配置] *****************************************************************************************************
changed: [host3]RUNNING HANDLER [重啟 HAProxy] **********************************************************************************************************
changed: [host3]RUNNING HANDLER [恢復 HAProxy 備份配置] *************************************************************************************************
skipping: [host3]PLAY RECAP ******************************************************************************************************************************
host1 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host2 : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
host3 : ok=7 changed=3 unreachable=0 failed=0 skipped=1 rescued=0 ignored=0
驗證
http://192.168.0.223/haproxy?stats
root@host0:/home/ansible-tuto# ansible -i hosts -m shell -a "cat /etc/haproxy/haproxy.cfg" host3
host3 | CHANGED | rc=0 >>
globaldaemonmaxconn 256stats socket /tmp/sockdefaultsmode httptimeout connect 5000mstimeout client 50000mstimeout server 50000mslisten clusterbind 192.168.0.223:80mode httpstats enablebalance roundrobinserver host1 192.168.0.221:80 check port 80 weight 100server host2 192.168.0.222:80 check port 80 weight 150option httpchk HEAD /index.php HTTP/1.0
root@host0:/home/ansible-tuto# ansible -i hosts -m shell -a "haproxy -c -f /etc/haproxy/haproxy.cfg" host3
host3 | CHANGED | rc=0 >>
Configuration file is valid
listen
機制
1. listen
的作用
- 替代傳統
notify
:在 Ansible 2.2+ 中引入,允許將多個處理程序(handlers)綁定到同一個事件名稱。 - 執行順序控制:所有綁定到同一
listen
名稱的處理程序會按定義順序依次執行。
2. 在您的 Playbook 中的工作流程
-
觸發事件:當任意任務通過
notify: haproxy_config_flow
觸發事件鏈時:tasks:- name: 生成并推送 HAProxy 配置template: ...notify: haproxy_config_flow # 觸發事件鏈
-
執行順序:
- 驗證配置 → 重啟服務 或 恢復備份 → 重啟服務。
-
條件控制:通過
when
條件決定是否執行:- 若驗證成功(
rc == 0
),執行重啟 HAProxy
。 - 若驗證失敗(
rc != 0
),執行恢復 HAProxy 備份配置
,隨后觸發重啟 HAProxy
。
- 若驗證成功(
3. 與傳統 notify
的對比
場景 | 傳統 notify | listen 機制 |
---|---|---|
觸發單個處理程序 | notify: 重啟 HAProxy | 不適用 |
觸發多個處理程序 | 需多次 notify | 所有綁定到同一 listen 的處理程序按序執行 |
條件執行 | 處理程序內部通過 when 控制 | 每個處理程序獨立定義 when 條件 |
關鍵優化點說明
1. 備份強制覆蓋 (force: yes
)
- 作用:確保每次備份時覆蓋舊文件,避免備份文件過時。
- 必要性:如果未啟用,當配置文件未變更時,備份任務不會覆蓋舊備份,導致回退時可能恢復錯誤版本。
2. 驗證失敗后的回退流程
- 步驟:
- 生成新配置 → 2. 驗證失敗 → 3. 恢復備份 → 4. 重啟服務(使用舊配置)。
- 結果:即使新配置錯誤,服務仍可用。
3. 統一事件鏈 (haproxy_config_flow
)
- 優勢:將配置更新、驗證、重啟、回退邏輯集中管理,避免分散觸發。
完整執行流程
- 安裝 HAProxy:確保軟件已安裝。
- 備份配置:保存當前有效配置。
- 生成新配置:通過模板渲染推送新配置。
- 觸發處理程序鏈:
- 驗證配置 → 若成功 → 重啟服務。
- 若失敗 → 恢復備份 → 重啟服務。
總結
通過 listen
機制,您實現了一個 原子化的配置管理流程:
- 集中控制:所有相關處理程序按序觸發。
- 安全回退:驗證失敗時自動恢復至有效配置。
- 高可用性:避免服務因配置錯誤中斷。
stats socket
的具體作用
stats socket
是 HAProxy 提供的一個 Unix Socket 接口,用于動態管理 HAProxy 的運行時狀態。主要功能包括:
- 實時操作:
- 禁用/啟用后端服務器(如
disable server backend/server1
)。 - 動態調整權重(如
set server backend/server1 weight 200
)。 - 查看連接數、會話狀態等實時統計信息。
- 禁用/啟用后端服務器(如
- 監控與管理:
- 通過命令行工具(如
socat
)或 API 直接與 HAProxy 交互。 - 支持自動化腳本動態調整負載均衡策略。
- 通過命令行工具(如
2. 是否可以不使用 stats socket
?
可以完全禁用。如果不使用 stats socket
:
- 優點:
- 消除安全風險:關閉潛在的攻擊入口。
- 簡化配置:減少維護復雜度。
- 缺點:
- 失去動態管理能力:無法通過 Socket 實時調整服務器狀態或權重。
- 依賴重啟:任何配置變更需重啟 HAProxy 服務才能生效。
適用場景:
- 負載均衡策略固定,無需頻繁調整。
- 通過 Ansible 等工具靜態管理配置變更。
3. 替代方案(未測試,待測試)
如果需動態管理 HAProxy 但不想使用 stats socket
,有以下替代方案:
方案1:HTTP 統計接口
通過 HAProxy 的 HTTP 統計頁面 進行基礎管理:
listen statsbind *:8404stats enablestats uri /haproxy?statsstats auth admin:SecurePassword123! # 強制認證stats refresh 10s # 自動刷新間隔
- 功能:
- 查看實時統計信息(如服務器狀態、請求速率)。
- 通過瀏覽器或
curl
手動禁用服務器(需配合前端工具)。
- 限制:
- 無法直接執行復雜命令(如動態調整權重)。
- 依賴 HTTP 接口,需額外安全防護(如 HTTPS、IP 白名單)。
方案2:HAProxy Runtime API(企業版)(未測試,待測試)
HAProxy 企業版提供 Runtime API,支持通過 HTTP/HTTPS 動態管理:
globalruntime-api 0.0.0.0:9999 # 監聽端口api-user admin # 認證用戶api-password $6$加密密碼 # 加密密碼
- 功能:
- 類似
stats socket
的所有操作,但通過 HTTP 接口實現。 - 支持 RESTful 風格的管理命令。
- 類似
- 優勢:
- 更易集成自動化工具(如 Prometheus、Grafana)。
- 支持 HTTPS 和細粒度權限控制。
方案3:Ansible 動態配置(未測試,待測試)
通過 Ansible 直接生成配置文件并觸發重載:
- name: 更新 HAProxy 配置template:src: haproxy.cfg.j2dest: /etc/haproxy/haproxy.cfgnotify: 安全重載 HAProxyhandlers:- name: 安全重載 HAProxycommand: haproxy -f /etc/haproxy/haproxy.cfg -sf $(cat /var/run/haproxy.pid)
- 功能:
- 通過模板動態生成配置,替換舊配置后平滑重啟(
-sf
參數)。
- 通過模板動態生成配置,替換舊配置后平滑重啟(
- 優勢:
- 無需開放額外端口或 Socket。
- 變更記錄清晰(版本控制模板文件)。
總結與建議
方案 | 安全性 | 動態管理能力 | 復雜度 | 適用場景 |
---|---|---|---|---|
禁用 stats socket | 高 | 無 | 低 | 配置固定,無需實時調整 |
HTTP 統計頁面 | 中 | 基礎 | 中 | 需要簡單監控 |
Runtime API | 高 | 完整 | 高 | 企業版用戶,需自動化集成 |
Ansible 動態配置 | 高 | 中 | 中 | 通過 IaC 管理,變更頻率中等 |
推薦實踐:
- 如果無需動態調整,直接禁用
stats socket
。 - 若需動態管理且安全性優先,使用 HTTP 統計頁面 + IP 白名單 + HTTPS。
- 企業用戶可投資 HAProxy 企業版 的 Runtime API。
12.Playbook Roles實現配置模塊化
將原有的 Playbook 重構為 角色(Roles),目的是:
- 模塊化:將 Apache 和 HAProxy 的配置拆分為獨立角色,便于復用和管理。
- 標準化結構:遵循 Ansible 角色的目錄約定,提升代碼可維護性。
- 依賴管理:通過角色間的依賴關系(如
meta/main.yml
)實現自動化執行順序。
1角色目錄結構解析
每個角色(如 apache
或 haproxy
)的目錄結構如下:
roles/├── apache/ # 角色名稱│ ├── tasks/ # 存放任務定義│ │ └── main.yml # 自動加載的入口任務文件│ ├── handlers/ # 存放處理程序(如服務重啟)│ │ └── main.yml # 自動加載的入口處理程序文件│ ├── files/ # 存放靜態文件(如配置文件)│ ├── templates/ # 存放 Jinja2 模板文件│ ├── defaults/ # 存放角色默認變量(優先級最低)│ ├── vars/ # 存放角色變量(優先級高于 defaults)│ └── meta/ # 定義角色依賴(如依賴其他角色)└── haproxy/└── ... # 結構同上
關鍵目錄說明
tasks/main.yml
:角色的核心任務入口,Ansible 會自動執行此文件中的任務。handlers/main.yml
:定義處理程序(如restart apache
),由任務中的notify
觸發。files/
和templates/
:存放靜態文件和模板,引用時無需絕對路徑。- 例如:
copy: src=awesome-app
會自動從files/
目錄查找。
- 例如:
defaults/main.yml
:定義角色的默認變量,可被外部變量覆蓋。meta/main.yml
:定義角色依賴,確保執行順序(如先安裝 Nginx 再配置 PHP)。
2文件命名規則
-
強制規則:
tasks/main.yml
、handlers/main.yml
等入口文件必須命名為main.yml
,否則 Ansible 無法自動加載。
-
靈活規則:
-
其他文件(如
tasks/install_packages.yml
)可自定義名稱,但需在main.yml
中通過include
或import_tasks
顯式引入。 -
例如:
# tasks/main.yml - include: install_packages.yml - include: configure_apache.yml
-
3遷移步驟詳解
以遷移 Apache 配置為例:
-
創建角色目錄:
mkdir -p step-12/roles/apache/{tasks,handlers,files}
使用
ansible-galaxy
初始化角色:ansible-galaxy init step-12/roles/haproxy # 自動生成標準目錄結構
-
拆分任務:
-
將原
apache.yml
中的任務復制到tasks/main.yml
。只保留tasks部分-
#原apache.yml --- - hosts: webtasks:- name: Installs necessary packagesapt:*- name: Push future default virtual host configurationcopy: #現tasks/main.yml --- - name: Installs necessary packages apt: * - name: Push future default virtual host configuration copy:
-
-
-
移除對
files/
和templates/
的路徑引用(角色會自動查找對應目錄)。-
#源文件中路徑表示 - name: Push future default virtual host configurationcopy:src: files/awesome-app # 顯式路徑 # 角色任務文件 (roles/apache/tasks/main.yml) #遷移到角色后寫法 - name: Push future default virtual host configurationcopy:src: awesome-app # 直接引用文件名(無需路徑)
-
Ansible 會自動在角色的
files/
目錄下查找靜態文件。 -
如果文件在
files/
的子目錄中(如files/config/
),則需保留子目錄路徑,-
src: config/awesome-app # 文件路徑為 roles/apache/files/config/awesome-app
-
-
-
提取處理程序:
-
將原 Playbook 中的
handlers
部分移到handlers/main.yml
。 -
#原apache.ymlhandlers:- name: restart apacheservice:name: apache2state: restarted #現handlers/main.yml --- - name: restart apacheservice:name: apache2state: restarted
-
-
移動靜態文件:
- 將配置文件(如
awesome-app
)復制到files/
目錄。
- 將配置文件(如
-
創建頂層 Playbook:
# site.yml - hosts: webroles:- apache # 自動加載 roles/apache 下的任務 - hosts: haproxyroles:- haproxy
4執行與驗證
-
運行 Playbook:
ansible-playbook -i hosts site.yml ansible-playbook -i step-12/hosts step-12/site.yml
-
限制執行范圍:
# 僅針對 web 主機組執行 ansible-playbook -i step-12/hosts -l web step-12test/site.yml
執行情況:
PLAY RECAP ********************************************************************************************************************************
host1 : ok=9 changed=6 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
host2 : ok=9 changed=6 unreachable=0 failed=0 skipped=3 rescued=0 ignored=0
host3 : ok=5 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
13.Ansible 標簽使用
在復雜的IT架構中,一個Ansible Playbook可能包含上百個任務。當您只需要更新Web服務配置卻要完整執行整個Playbook時,就像為了換燈泡而重啟整個數據中心。標簽(Tags)的出現完美解決了這個痛點:
- 手術刀式精準操作:只運行指定任務,節省90%執行時間
- 環境隔離利器:開發/測試/生產環境差異化執行
- 應急響應加速器:快速定位回滾點,故障恢復時間縮短75%
1、為何需要標簽?
在復雜的IT架構中,一個Ansible Playbook可能包含上百個任務。當您只需要更新Web服務配置卻要完整執行整個Playbook時,就像為了換燈泡而重啟整個數據中心。標簽(Tags)的出現完美解決了這個痛點:
- 手術刀式精準操作:只運行指定任務,節省90%執行時間
- 環境隔離利器:開發/測試/生產環境差異化執行
- 應急響應加速器:快速定位回滾點,故障恢復時間縮短75%
2、標簽基礎語法
基礎標記方法
列表式聲明(推薦)
- name: 部署Apache服務apt:name: apache2state: latesttags:- web- install- critical
行內數組式
- name: 配置防火墻規則ufw:rule: allowport: "{{ http_port }}"tags: ['network', 'security']
命令行控制
命令 | 作用 | 示例場景 |
---|---|---|
-t tag1,tag2 | 執行包含任意指定標簽的任務 | 緊急安全更新 |
--skip-tags tagA | 排除指定標簽的任務 | 跳過耗時操作 |
-t tag1 -t tag2 | 多標簽聯合過濾(邏輯OR) | 跨角色操作 |
--list-tags | 顯示所有可用標簽 | Playbook文檔化 |
3、實戰場景案例
場景1:快速部署熱修復補丁
# 僅執行代碼部署和服務重啟
ansible-playbook deploy.yml -t git_pull,service_restart# 組合標簽排除數據庫操作
ansible-playbook deploy.yml -t app_deploy --skip-tags db_migration
場景2:安全合規檢查
- name: 檢查SSH協議版本shell: ssh -Vregister: ssh_versiontags: security_audit- name: 驗證防火墻狀態command: ufw status verbosetags: - security_audit- compliance
場景3:多環境配置
- name: 加載生產環境配置template:src: prod.env.j2dest: /etc/app.envtags: prod_only- name: 開發環境調試設置lineinfile:path: /etc/app.confline: "debug_mode=true"tags: dev_only
4、高級技巧揭秘
- 標簽繼承模式
# meta/main.yml
dependencies:- role: commontags: [base_setup] # 自動繼承base_setup標簽
- 動態標簽生成
- name: 按條件動態標記debug:msg: "數據庫主節點專屬配置"tags: "{{ 'db_master' if is_master else 'db_slave' }}"
- Handler標簽聯動
handlers:- name: 滾動重啟服務systemd:name: appstate: restartedtags: canary_deploy # 灰度發布專用handler
5、最佳實踐指南
-
命名規范三原則
- 業務維度:
web
,db
,cache
- 操作類型:
install
,config
,cleanup
- 環境標識:
prod
,staging
,dev
- 業務維度:
-
黃金組合策略
# 典型CI/CD流水線 ansible-playbook site.yml \-t "validate,deploy" \--skip-tags "migration,full_restart"
-
危險操作防護
- name: 數據庫表結構遷移command: alembic upgrade headtags:- dangerous- db_migration
# 顯式確認執行危險操作 ansible-playbook migrate.yml -t dangerous --extra-vars "confirm_danger=yes"
6、常見問題排查
Q1:標簽未生效?
- 檢查項:
- YAML縮進是否正確
- 角色依賴是否繼承標簽
- 是否存在同名標簽覆蓋
Q2:如何調試復雜標簽?
# 顯示標簽執行順序
ANSIBLE_DEBUG=1 ansible-playbook playbook.yml -t my_tag# 生成標簽關系圖
ansible-playbook playbook.yml --list-tags | grep -B1 'TASK TAGS'
Q3:部分節點執行異常?
- name: 條件標簽示例debug:msg: "僅CentOS系統執行"tags: centos_onlywhen: ansible_distribution == "CentOS"
7、性能優化指標
場景 | 無標簽執行時間 | 標簽優化時間 | 提升幅度 |
---|---|---|---|
全量部署 | 12m 34s | 12m 34s | 0% |
配置更新 | 11m 45s | 1m 12s | 89% |
緊急回滾 | 9m 23s | 45s | 92% |
安全補丁 | 8m 56s | 38s | 93% |
8、延伸閱讀
-
標簽與Ansible Tower集成
# tower.yml launch_configuration:extra_vars:ansible_tags: "prod_deploy"limit: "webservers:&east"
-
基于標簽的權限控制
# ansible.cfg [privilege_escalation] require_sudo_tags = package_install, service_control
-
標簽驅動文檔生成
ansible-doc-tagger generate --format markdown > PLAYBOOK_TAGS.md