詳解:從靜態(tài)清單到動態(tài)Inventory的自動化運維實踐)
作為一個常年跟服務(wù)器打交道的運維我對Ansible的態(tài)度一直是“能自動化的絕不手工”。而在所有Ansible命令里-i參數(shù)可能是你最早接觸、卻又最容易用錯的那一個。別小看它——ansible -i /path/to/inventory這種寫法表面上是指定一個清單文件實際上牽扯到一組主機的組織方式、環(huán)境隔離策略、批量的可重復(fù)性甚至是整個自動化運維體系能不能上線的關(guān)鍵。這篇東西不打算寫成手冊式的列舉而是想把我踩過的坑、驗證過的用法、以及排查思路都攤開聊一聊。無論你是剛接觸Ansible劇本的初學(xué)者還是已經(jīng)在自動化運維里摸爬滾打的老人這篇文章都會圍繞-i參數(shù)的幾種典型用法結(jié)合真實場景把原理、取舍和實操細(xì)節(jié)講清楚??赐曛竽阒辽倌苊靼资裁磿r候該用靜態(tài)inventory什么時候該換動態(tài)inventory-i和--limit到底怎么配合以及為什么說“指定清單”這件小事往往是自動化最值得投入精力的地方。1. 從inventory說起-i參數(shù)到底在干什么1.1 最基礎(chǔ)也最容易忽略的概念講-i參數(shù)之前繞不開inventory。Ansible本身沒有任何“服務(wù)器列表”的固化概念它執(zhí)行命令、跑劇本都依賴于一份或多份主機清單。默認(rèn)情況下Ansible會去找/etc/ansible/hosts但這在生產(chǎn)環(huán)境里幾乎不可用——你不可能讓所有項目都搶同一個文件更不可能在多人協(xié)作的時候?qū)χ粋€全局文件反復(fù)修改。所以真正的做法是用-i明確指定清單文件的路徑。-i的全稱是--inventory后面跟的既可以是一個普通文件也可以是一個目錄甚至可以是逗號分隔的主機列表。三種形式解決三種不同的問題文件形式ansible -i hosts.ini all -m ping適合靜態(tài)環(huán)境簡單直觀。目錄形式ansible -i inventory/dev/ all -m ping適合多環(huán)境管理目錄里可以放多個清單文件。逗號列表形式ansible -i web01,web02, all -m ping完全不依賴文件臨時排查、快速測試時極其方便。我第一次意識到-i的重要性是在同事留下的一堆混亂腳本里。當(dāng)時所有命令都依賴默認(rèn)的/etc/ansible/hosts一旦有人改了那個文件全公司跑腳本的人都受影響。后來我把清單全部改成顯式指定配合目錄隔離整個運維腳本的穩(wěn)定性立刻上了一個臺階。這里想強調(diào)一個觀念顯式優(yōu)于隱式。-i把“我的命令控制哪些主機”這件事變成了一條明線而不是藏在某個全局文件里暗流涌動。1.2 為什么不用默認(rèn)路徑有讀者可能會問既然系統(tǒng)有默認(rèn)的/etc/ansible/hosts為什么非要折騰-i原因是實際場景里默認(rèn)路徑暴露了三個致命問題。第一權(quán)限和隔離。生產(chǎn)、測試、開發(fā)環(huán)境的清單如果擠在一個文件里任何誤操作都可能同時影響多個環(huán)境用-i配合獨立目錄每個環(huán)境自成體系就算有人改錯了影響范圍也可控。第二可重復(fù)性。自動化運維的核心目標(biāo)之一是“同樣的腳本、同樣的參數(shù)在任何時間執(zhí)行結(jié)果一致”默認(rèn)路徑依賴機器本地的文件狀態(tài)換一臺機器跑結(jié)果就不同而把-i寫進腳本里清單和腳本就成了一體的交付物。第三多項目共存。一個服務(wù)器上可能跑著多個業(yè)務(wù)線A項目的清單和B項目的清單本來就應(yīng)該互相隔離全局文件設(shè)計上就不義。還有一個細(xì)節(jié)值得注意-i指定的文件不要求固定的擴展名。很多人習(xí)慣叫hosts或者inventory但Ansible只認(rèn)內(nèi)部格式不認(rèn)后綴名。你隨便命名prod、dev.ini、inventory.yaml都可以只要內(nèi)容符合格式要求。這給了我們在文件組織上極大的自由度后續(xù)在多環(huán)境管理那一節(jié)我會給出具體的目錄結(jié)構(gòu)。2. 實戰(zhàn)場景多環(huán)境、動態(tài)清單、批量部署2.1 場景一多環(huán)境管理目錄化拆解實際工作中最常見的需求就是區(qū)分開發(fā)、測試、生產(chǎn)環(huán)境。如果你把所有環(huán)境的主機都放在一個大文件里那每次執(zhí)行都要小心翼翼地加--limit去圈定范圍既煩瑣又危險。我更推薦把-i指向一個目錄目錄里每個子目錄或文件對應(yīng)一個環(huán)境。舉個例子我的常見目錄結(jié)構(gòu)是這樣inventory/ ├── dev/ │ ├── hosts.ini │ └── group_vars/ ├── test/ │ ├── hosts.ini │ └── group_vars/ └── prod/ ├── hosts.ini └── group_vars/執(zhí)行時就這樣寫ansible-playbook -i inventory/prod/ deploy.yml為什么目錄化更好因為Ansible讀取一個目錄時會自動合并目錄下所有清單內(nèi)容和group_vars、host_vars目錄里的變量定義。這樣一來清單文件、組變量、主機變量就形成了完整的環(huán)境配置包。你交付的不再是“一個主機列表”而是一整套環(huán)境定義。更關(guān)鍵的是-i inventory/prod/和-i inventory/test/兩個命令在邏輯上強制隔離了環(huán)境。哪怕你在劇本里寫錯了變量它的影響范圍也僅限于該命令指定的環(huán)境。我見過不少事故都是因為某個文件被復(fù)用、變量串環(huán)境導(dǎo)致的目錄化之后這類問題基本絕跡。2.2 場景二動態(tài)inventory讓清單自己長出來靜態(tài)文件在多環(huán)境場景下夠用但一旦上了云主機隨時彈性伸縮手工維護靜態(tài)清單就完全不現(xiàn)實了。AWS、阿里云、騰訊云的主機IP天天變你不能每次擴縮容就去改一次hosts文件。這時候就該上動態(tài)inventory。動態(tài)inventory本質(zhì)上是一個可執(zhí)行腳本Ansible運行-i script.py時會執(zhí)行這個腳本并解析它輸出到標(biāo)準(zhǔn)輸出的JSON數(shù)據(jù)。腳本根據(jù)云平臺API查詢實例狀態(tài)動態(tài)生成主機列表和組信息。常見的實現(xiàn)方式有兩種使用官方或社區(qū)提供的動態(tài)inventory插件。比如amazon.aws.aws_ec2、azure.azure_rm、google.cloud.gcp_compute只要在ansible.cfg里啟用對應(yīng)插件配上訪問密鑰和區(qū)域信息就能直接以插件方式動態(tài)獲取主機列表。自己寫一個腳本封裝API調(diào)用。適合定制化需求比如只篩選特定標(biāo)簽、特定VPC內(nèi)的實例。用一句話總結(jié)動態(tài)inventory的價值-i不再指向一個文件而是指向一個“生成器”。它讓Ansible的主機清單和云上真實實例狀態(tài)始終保持一致消除手工同步帶來的滯后和誤差。我自己維護過一個內(nèi)部工具腳本大概邏輯是讀取配置文件里的云廠商AK/SK調(diào)用查詢接口拿回所有實例ID和IP再按實例名稱的前綴歸類到不同組。然后我只需要執(zhí)行ansible -i /opt/scripts/dynamic_inventory.py --list就能看到完整的JSON輸出驗證腳本返回的數(shù)據(jù)結(jié)構(gòu)對不對。確認(rèn)無誤后再把它接到ansible-playbook里使用效果立竿見影。以前處理一批新擴容機器先要把IP人工加進文件再跑劇本現(xiàn)在擴容完成直接跑劇本新節(jié)點自動進入目標(biāo)組。這里要特別提醒一個動態(tài)inventory的細(xì)節(jié)腳本必須有可執(zhí)行權(quán)限而且輸出必須是合法的JSON。這是新手最容易栽的地方。Ansible執(zhí)行動態(tài)清單腳本時會先運行它再解析標(biāo)準(zhǔn)輸出任何一句額外的日志打印或者Python的print調(diào)試信息都會污染輸出導(dǎo)致解析失敗。我常用的排查方法就是先在命令行手動運行一次腳本看輸出的JSON是否完整再做調(diào)試。2.3 場景三臨時指定主機列表一條命令解決排查需求不是所有場景都需要文件和目錄。有時你只是想確認(rèn)某幾臺機器上某個服務(wù)是否啟動或者快速批量執(zhí)行一個命令這時候最方便的反而是直接傳逗號分隔列表。ansible -i 192.168.1.21,192.168.1.22, all -m command -a systemctl status nginx注意寫法末尾有個逗號這是為了告訴Ansible“這是一個列表而不是一個文件名”。如果不加末尾逗號Ansible會把整段字符串當(dāng)作路徑去解析然后報錯找不到文件。這個細(xì)節(jié)非常隱蔽我見過不止一個同事在這上面卡了半小時。這種臨時列表形式最適合配合-m模塊和-a參數(shù)做快速操作例如批量ping、查uptime、分發(fā)公鑰ansible -i node01,node02, all -m authorized_key -a userroot key{{ lookup(\file\, \/home/user/.ssh/id_rsa.pub\) }}它的優(yōu)勢就是零依賴不需要造文件、不需要考慮目錄結(jié)構(gòu)適合臨時搭建的環(huán)境或者故障排查。缺點也很明顯主機信息沒有持久化組變量、主機變量完全沒法用而且容易手滑漏掉某臺機器。所以我的建議是把這種形式限制在“一次性小操作”任何需要重復(fù)執(zhí)行的任務(wù)都應(yīng)該至少落一個靜態(tài)清單文件。3. 參數(shù)變體與核心細(xì)節(jié)解析3.1-i和--limit的辨析很多初學(xué)者會把-i和--limit混為一談或者用起來沒有章法。實際上這兩者解決的問題完全不同。-i決定的是“候選主機池”也就是Ansible能從哪些主機里挑機器--limit決定的是“在這個池子里執(zhí)行哪些主機”。用白話講-i是劃定范圍--limit是細(xì)化到子集。生產(chǎn)中的正確姿勢是把環(huán)境級別的邊界全部交給-i避免意外觸碰環(huán)境外主機把單次執(zhí)行的目標(biāo)圈定交給--limit實現(xiàn)“整個生產(chǎn)池我只動其中某臺”。舉例ansible-playbook -i inventory/prod/ upgrade.yml --limit db-01這條命令的含義是所有生產(chǎn)主機都是可操作范圍但我這次只對db-01執(zhí)行升級。好處是由于-i明確指向生產(chǎn)環(huán)境清單那么即使劇本里出現(xiàn)了組名、變量引用錯誤也不會跑到測試環(huán)境去而--limit讓精確操作變得安全可控。相反如果你只用了--limit卻不指定-i那么主機池默認(rèn)是/etc/ansible/hosts里面混著哪些機器就有風(fēng)險了。這也是我一直堅持“命令里必須帶顯式-i”的原因。3.2 清單文件里的格式要點-i指向的清單文件不是隨便填幾個IP就行它有一套約定俗成的結(jié)構(gòu)。以INI格式為例[web] web01 ansible_host192.168.1.21 ansible_userroot web02 ansible_host192.168.1.22 [db] db01 ansible_host192.168.1.31 [prod:children] web db這個文件里包含了幾個信息組web、組db、父組prod以及每臺主機對應(yīng)的連接參數(shù)。-i指向它之后你既可以用-i hosts.ini web來操作web組也可以用-i hosts.ini prod來同時操作兩個子組。還有一個重要的隱式組all和ungrouped。-i hosts.ini all匹配清單里的所有主機-i hosts.ini ungrouped匹配沒有放進任何組的主機。這決定了你如果不小心把某臺機器漏寫在組里它會不會被all誤執(zhí)行到。我強烈建議在靜態(tài)清單里給每個環(huán)境都建立一個明確的[env:children]父組例如[prod:children]、[test:children]。這樣可以極大地方便腳本里引用也讓-i inventory/prod/ web這樣的命令更直觀。3.3 兩種格式INI、YAML怎么選從Ansible 2.4開始YAML格式的inventory也可以直接用但很多人沒意識到兩種格式在表達復(fù)雜變量時的差異。INI格式寫簡單分組很方便一個文件幾行就完事YAML格式則結(jié)構(gòu)更清晰適合需要大量主機變量的場景。YAML版清單長這樣all: children: web: hosts: web01: ansible_host: 192.168.1.21 web02: ansible_host: 192.168.1.22 db: hosts: db01: ansible_host: 192.168.1.31實際用下來我的判斷標(biāo)準(zhǔn)是如果只是幾十臺機器組結(jié)構(gòu)不復(fù)雜INI綽綽有余如果清單要交給多個團隊維護、變量復(fù)用頻繁YAML更合適。-i對兩者都支持得很好所以選型不必糾結(jié)重點是整個團隊保持一致。4. 常見問題與排查技巧實錄4.1 高頻報錯速查表我整理了這幾年被問到最多、也最典型的五個問題全部跟-i和inventory直接相關(guān)。問題現(xiàn)象常見原因解決方案No hosts matched指定的組名不存在或清單中實際沒有該組先執(zhí)行ansible -i 清單文件 --list-hosts 組名確認(rèn)匹配情況ERROR! Specified inventory hosts dont exist-i路徑寫錯或逗號列表忘加末尾逗號用ls確認(rèn)文件存在列表形式檢查末尾逗號解析清單時報語法錯誤INI里縮進混用、ansible_host值寫了中文或異常字符逐個檢查主機行刪除隱藏字符用ansible-inventory -i 文件 --list驗證動態(tài)清單腳本無輸出或JSON解析失敗腳本沒有執(zhí)行權(quán)限或腳本里有額外print手動運行腳本清理非JSON輸出給腳本加x權(quán)限執(zhí)行到了不該執(zhí)行的主機-i指向了錯誤的環(huán)境目錄命令中固定使用環(huán)境全路徑使用前先跑--list-hosts確認(rèn)范圍這里最推薦的一個習(xí)慣是在跑任何playbook之前先跑一次--list-hosts看匹配范圍。這個命令不會改變系統(tǒng)狀態(tài)純粹打印這次-i加主機模式匹配到了哪些機器。你只需在真正的playbook命令前面加一個--list-hosts參數(shù)就能避免絕大部分“誤操作到不該碰的機器”的慘劇。4.2 權(quán)限與連接問題使用-i指定清單后每個主機的連接方式取決于清單里的連接參數(shù)。最常見的問題是ansible_user沒有正確指定或者SSH端口不是默認(rèn)22。排查思路很直接ansible -i hosts.ini web -m ping -vvv-vvv參數(shù)會輸出完整的SSH連接日志你會看到它走了哪個ansible_user、嘗試了哪個端口、最終停在什么錯誤上。我見過很多“ping不通”的假象最后都是因為清單里的ansible_host寫成了內(nèi)網(wǎng)保留地址而執(zhí)行機根本訪問不到。所以排查連通性時先確認(rèn)執(zhí)行機到目標(biāo)機的網(wǎng)絡(luò)路徑再確認(rèn)清單參數(shù)最后才考慮公鑰和密碼認(rèn)證的問題。另外一個經(jīng)驗是清單里連接參數(shù)如果和命令行的-u、--private-key產(chǎn)生沖突Ansible會優(yōu)先采用命令行指定的值。這就意味著如果你在command里硬性指定了-u root那么清單里的ansible_user就會失效。理解這個優(yōu)先級有助于你快速定位“為什么我改清單里的用戶沒起作用”這類困惑。4.3 常見誤操作和防呆建議我見過最意外的事故是把生產(chǎn)環(huán)境的清單文件內(nèi)容改動后沒有驗證就直接跑批量部署。結(jié)果新加的某臺機器由于沒有初始化直接被劇本執(zhí)行了一堆依賴安裝最后系統(tǒng)狀態(tài)變得不可預(yù)期。這類問題不是-i本身造成的而是沒有在清單變更后做嚴(yán)格驗證。所以我的防呆流程是這樣修改清單后總是先執(zhí)行ansible-inventory -i inventory/prod/ --list確認(rèn)數(shù)據(jù)結(jié)構(gòu)完整。再用ansible -i inventory/prod/ all --list-hosts確認(rèn)目標(biāo)范圍。最后才跑真正的playbook并且盡量加上--check參數(shù)做干跑演練。這套流程不用花很多時間但能幫你躲掉大多數(shù)因為清單變更導(dǎo)致的批量事故。自動化運維半年來我最大的感悟就是慢就是快越小心的前置檢查才越能避免大故障的后置恢復(fù)。5. 實測驗證從命令行到Playbook的組合玩法5.1 用-i組合模塊命令做快速巡檢單純列參數(shù)講概念遠不如實際演示來得直觀。下面我給出幾個我在日常運維里真實會用到的命令每個都緊密結(jié)合-i。第一批量檢查多臺機器的時間同步ansible -i inventory/prod/ prod -m shell -a date timedatectl這個命令會遍歷prod組內(nèi)所有主機先輸出日期時間再查看時區(qū)和NTP同步狀態(tài)。加上-i顯式指定環(huán)境之后我可以放心地把同一腳本丟給測試環(huán)境只需要換路徑ansible -i inventory/test/ test -m shell -a date timedatectl第二批量收集機器信息并生成報告ansible -i inventory/prod/ all -m setup 2/dev/null | grep ansible_hostnamesetup模塊會自動收集主機的facts包括主機名、IP、系統(tǒng)版本、內(nèi)存、磁盤等大量信息。用-i指定環(huán)境后你得到的就是該環(huán)境下的機器“體檢報告”。我在做資產(chǎn)盤點時就用這個方法比登到每臺機器上去看快好幾倍。第三批量分發(fā)配置文件ansible -i inventory/prod/ web -m copy -a src/opt/config/nginx.conf dest/etc/nginx/nginx.conf backupyes這條命令配合-i的目錄化清單直接在指定環(huán)境的web組里分發(fā)配置。backupyes參數(shù)還會自動備份遠端舊配置降低出錯的回滾成本。5.2 Playbook中使用-i的正確姿勢當(dāng)從單條ansible命令切換到ansible-playbook時-i的使用邏輯不變但要注意變量引用的差異。Playbook內(nèi)部通過hosts:關(guān)鍵字選擇執(zhí)行組這個組名必須能由-i指定的清單解析出來。一個典型的用法ansible-playbook -i inventory/prod/ deploy.yml --tags deploy --limit web這條命令的含義是從生產(chǎn)環(huán)境清單中僅選擇web組下的主機且只執(zhí)行playbook中帶有deploy標(biāo)簽的任務(wù)。因為-i指向生產(chǎn)才會有安全的環(huán)境隔離--limit再做一層細(xì)粒度控制兩條配合起來既靈活又安全。我還在playbook里大量使用group_vars按環(huán)境區(qū)分配置。目錄化的-i讓Ansible自動加載對應(yīng)環(huán)境的group_vars這樣同一份playbook在不同環(huán)境跑拿到的配置參數(shù)天然不同。比如生產(chǎn)環(huán)境連的是生產(chǎn)數(shù)據(jù)庫地址測試環(huán)境連的是測試數(shù)據(jù)庫地址這些差異全部由inventory目錄結(jié)構(gòu)消化掉playbook本身保持純凈。5.3 動態(tài)Inventory與Playbook的深度整合動態(tài)inventory和playbook配合的坑在于動態(tài)腳本輸出的組名必須和playbook里的hosts:字段完全一致否則就會報“no hosts matched”。所以我建議先把動態(tài)腳本的輸出導(dǎo)出來看一眼python /opt/scripts/dynamic_inventory.py --list /tmp/inv.json jq .web /tmp/inv.json確認(rèn)web組存在后再執(zhí)行ansible-playbook -i /opt/scripts/dynamic_inventory.py deploy.yml另外一個細(xì)節(jié)是動態(tài)inventory里經(jīng)常需要附帶額外變量比如ansible_user、ansible_ssh_private_key_file等。這些變量可以寫在腳本輸出JSON的_meta字段里Ansible會讀取_meta.hostvars來獲取主機級變量。很多初學(xué)動態(tài)inventory的人只返回了主機列表忘了返回_meta導(dǎo)致playbook跑起來后用錯誤的SSH用戶連接。所以設(shè)計動態(tài)清單腳本時既要輸出組成員關(guān)系也要輸出連接參數(shù)這樣整個鏈路才是閉環(huán)的。6. 擴展玩法自定義Inventory插件體系初探6.1 為什么需要自定義插件多數(shù)團隊的清單需求無非是靜態(tài)文件和動態(tài)腳本兩種。但當(dāng)你需要從內(nèi)部CMDB、工單系統(tǒng)、甚至一個Excel表格里讀取主機信息時動態(tài)腳本仍然不夠優(yōu)雅。Ansible從2.4開始豐富了inventory plugin機制允許通過配置ansible.cfg以插件方式加載自定義的清單來源。用插件最大的好處是可以和Ansible自己的配置體系無縫集成比如插件的配置可以直接寫在ansible.cfg里支持緩存、支持組合過濾條件。而動態(tài)腳本則更加“黑盒”一切行為由腳本自己解釋閱讀和排錯都更費力。6.2 一個簡單的自定義清單插件模板如果你的環(huán)境里已經(jīng)有了一個內(nèi)部API可以返回主機列表那么寫一個簡單插件并不復(fù)雜。下面我提供一個最小可用的框架思路具體實現(xiàn)還是要結(jié)合你的內(nèi)部API格式來調(diào)整。插件主體通常是一個繼承BaseInventoryPlugin的Python類核心實現(xiàn)parse方法。parse方法里從配置讀取API地址請求數(shù)據(jù)解析JSON然后調(diào)用self.inventory.add_group、self.inventory.add_host、self.inventory.set_variable等API把主機信息填充進Ansible的inventory對象。關(guān)鍵點有三個在ansible.cfg里配置enable_plugins確保自定義插件被加載。插件文件名要放在指定的inventory_plugins目錄里Ansible啟動時會到這些目錄下尋找插件。-i后面的值要配置成插件對應(yīng)的清單源描述例如-i my_cmdb.yml其中my_cmdb.yml里寫API地址和認(rèn)證信息。寫自定義插件的門檻確實比動態(tài)腳本高一點但它的回報也是很明顯的插件可以復(fù)用Ansible的緩存機制、變量合并機制調(diào)試起來也比腳本清晰得多。如果你所在團隊的運維規(guī)模常年幾百臺機器、且有成熟的資產(chǎn)系統(tǒng)這條路線值得投入時間去搭建。6.3 我的選型建議與判斷標(biāo)準(zhǔn)聊了這么多最后給一個務(wù)實的選型建議。如果機器數(shù)量在幾十臺級別且變化不頻繁直接用靜態(tài)文件加目錄化組織就夠了完全沒有必要引入動態(tài)機制如果機器上了云、經(jīng)常擴縮容那么動態(tài)inventory腳本是性價比最高的選擇如果你已經(jīng)有了一套CMDB或者資產(chǎn)系統(tǒng)希望所有工具的清單來源統(tǒng)一再考慮自定義inventory插件。做任何方案之前先想清楚-i指向的清單到底承擔(dān)了什么角色它只是給Ansible提供連接信息還是同時承載了環(huán)境隔離、配置分層、權(quán)限邊界想得越清楚方案就越不容易被推翻。我本人目前的架構(gòu)是核心環(huán)境用靜態(tài)目錄化inventory臨時用逗號列表云節(jié)點通過動態(tài)腳本接入內(nèi)部資產(chǎn)系統(tǒng)正逐步用自定義插件替換原先的腳本。這套組合并不復(fù)雜關(guān)鍵是每一步的選擇都有明確理由支撐。7. 踩坑經(jīng)驗與工作習(xí)慣建議7.1 踩過的坑從環(huán)境混淆到變量泄漏先說說我真實經(jīng)歷過的幾次踩坑相信很多人也有共鳴。第一次是環(huán)境混淆事故。當(dāng)時項目剛開始規(guī)模不大我把生產(chǎn)、測試的主機放在同一個inventory文件里靠著--limit來區(qū)分。結(jié)果某次升級時--limit寫錯了一個前綴把一臺測試機器當(dāng)成生產(chǎn)機器執(zhí)行了數(shù)據(jù)遷移腳本。雖然沒有造成真正的數(shù)據(jù)丟失但那次教訓(xùn)讓我徹底放棄了“單文件加限制”的方案轉(zhuǎn)而用目錄強制隔離環(huán)境。第二次是變量泄漏。由于多個環(huán)境的group_vars放在同一個目錄層級下某個變量文件里不小心覆蓋了公共變量導(dǎo)致生產(chǎn)環(huán)境的數(shù)據(jù)庫連接地址被測試環(huán)境的值給串了。還好當(dāng)時有監(jiān)控及時發(fā)現(xiàn)沒有引發(fā)嚴(yán)重故障。從那以后每個環(huán)境的group_vars一律放在獨立的目錄里絕不允許相互引用。第三次是關(guān)于動態(tài)inventory的-i權(quán)限問題。腳本一開始放在某個普通用戶目錄下Ansible執(zhí)行時報“Permission denied”。排查了很久才發(fā)現(xiàn)是腳本沒有加執(zhí)行權(quán)限-i解析時把它當(dāng)作不可執(zhí)行文件處理了。自那以后我每次新建動態(tài)清單腳本都會順便執(zhí)行chmod x并寫進團隊規(guī)約里。7.2 推薦的工作習(xí)慣讓-i成為命令的標(biāo)準(zhǔn)配置從這些坑里我總結(jié)了幾條必須堅持的工作習(xí)慣。第一所有ansible命令都顯式帶-i。不管這條命令是針對開發(fā)環(huán)境還是生產(chǎn)環(huán)境把-i寫出來相當(dāng)于給執(zhí)行目標(biāo)上了保險。你可以把它理解成寫SQL時必須帶上WHERE條件如果忘了寫可能就會更新到全表數(shù)據(jù)。第二環(huán)境目錄命名要統(tǒng)一且不可隨意改動。inventory/dev/、inventory/test/、inventory/prod/一旦定了就不要為了節(jié)省幾個字符去改成d、t、p。全稱命名雖然長但在腳本審查時一眼就能看出目標(biāo)環(huán)境這對多人協(xié)作極其重要。第三在playbook的首個任務(wù)里輸出當(dāng)前執(zhí)行環(huán)境。我經(jīng)常在playbook開頭加一個debug任務(wù)- name: Print current environment debug: msg: Executing on {{ inventory_dir }}這樣每次跑playbook終端都會先打印出inventory目錄路徑確認(rèn)當(dāng)前環(huán)境。如果這里顯示的路徑和自己想的不一致馬上停止執(zhí)行。第四利用ansible-inventory做清單可視化。Ansible提供了一個專門調(diào)試inventory的子命令ansible-inventory用法是ansible-inventory -i inventory/prod/ --list ansible-inventory -i inventory/prod/ --graph--graph會以樹狀結(jié)構(gòu)顯示組與主機的關(guān)系非常直觀。我在寫復(fù)雜清單時都會先跑一下--graph檢查有沒有組嵌套錯誤。7.3 腳本化-i參數(shù)管理的進階建議如果團隊規(guī)模稍大我建議把-i相關(guān)的路徑定義抽成公共變量或者腳本參數(shù)避免每個腳本里硬編碼一長串路徑。比如可以寫一個運維入口腳本#!/bin/bash ENV$1 shift ansible-playbook -i inventory/${ENV}/ $這樣在執(zhí)行時./ops.sh prod deploy.yml --tags restart好處是環(huán)境名稱被約束在dev/test/prod等枚舉值里降低了誤寫路徑的概率也讓新同事更容易上手。雖然這只是一個小包裝但在團隊協(xié)作時的收益相當(dāng)明顯。如果你使用GitLab CI或Jenkins還可以把-i環(huán)境參數(shù)定義成CI變量讓不同流水線天然帶上環(huán)境標(biāo)簽從流程上杜絕“手動指定錯誤環(huán)境”的可能。這也是我把-i從“命令行參數(shù)”提升到“自動化體系基礎(chǔ)設(shè)計”的原因它不是一個小細(xì)節(jié)而是環(huán)境邊界的錨點。所有上游工單系統(tǒng)、CMDB、發(fā)布平臺的對接最終都會落到“當(dāng)前這個任務(wù)應(yīng)該用哪份inventory”這個問題上。我現(xiàn)在每寫一個自動化腳本第一件事就是確定它的inventory來源每設(shè)計一個發(fā)布流程第一件事也是確定它作用于哪個清單環(huán)境。-i不只是參數(shù)而是一種把自動化行為固定在可控邊界內(nèi)的紀(jì)律。有了這個紀(jì)律Ansible的強大能力才能真正安全地發(fā)揮出來。