)
剛接手一個新環(huán)境時我習慣先看看手上這批機器到底是什么底細——操作系統(tǒng)版本、CPU架構、內(nèi)存多大、磁盤剩多少、Python裝在哪個路徑。放在以前要么登錄每臺機器敲命令要么寫個腳本批量去跑怎么都不夠優(yōu)雅。后來用上Ansible我發(fā)現(xiàn)自己可以在完全不了解目標主機的情況下直接寫playbook去操作它全靠facts在背后幫我兜底。這篇文章就圍繞Ansible facts展開講清楚它是什么、怎么用、能拿哪些關鍵信息以及我在實際項目中踩過的那些坑。適合剛學Ansible不久、想真正掌控inventory的讀者也適合已經(jīng)寫了不少playbook但總覺得糊里糊涂的人。1. 理解factsAnsible為什么自帶“主機體檢報告”1.1 facts到底是什么Ansible在運行playbook時默認會在執(zhí)行任務之前先把目標主機“掃”一遍收集這臺機器幾乎所有的基本信息——主機名、操作系統(tǒng)、IP地址、內(nèi)存和磁盤容量、CPU型號與核數(shù)、已安裝的軟件包、掛載的文件系統(tǒng)、系統(tǒng)啟動時間等等。這一整套自動采集到的信息就是facts。聽起來很抽象你可以把它理解成Ansible給每臺被管機器自動生成的一份“體檢報告”。報告內(nèi)容包括硬件配置、系統(tǒng)環(huán)境、網(wǎng)絡狀態(tài)等幾十個大類、幾百個小項。這些數(shù)據(jù)會被放進一個叫ansible_facts的變量結構中后續(xù)的任務可以直接引用比如“只在CentOS系統(tǒng)上執(zhí)行某操作”“獲取這臺機器的默認IPv4地址并寫入配置”。我在剛開始學Ansible時有個認知誤區(qū)以為inventory文件里寫的那些ansible_host、ansible_user就是facts。其實那是連接參數(shù)作用在使用SSH連上目標機器。而facts是在連接成功之后由Ansible主動在遠端執(zhí)行內(nèi)置模塊采集來的真實信息兩者完全不同。1.2 孤立的facts與全局變量體系facts之所以重要首先是因為它讓playbook具備了“感知環(huán)境”的能力。同一套playbook下發(fā)到不同的機器組它會根據(jù)facts自動調(diào)整行為不需要人為為每組機器各寫一套邏輯。其次facts是臨時性的。主機重啟、網(wǎng)絡變更、軟件安裝之后facts都會隨之變化。每次執(zhí)行playbook時重新采集能拿到機器的最新狀態(tài)這一點對自動化運維很關鍵。另外facts不只是給“自動化任務”用的。你可以利用gather_facts收集到的信息做資產(chǎn)盤點把機器信息導出成清單輔助運維報表或設備臺賬建設。要理解facts的定位它是Ansible從inventory靜態(tài)描述走向動態(tài)感知的關鍵橋梁。2. 如何采集facts并查看完整信息2.1 使用setup模塊手動采集執(zhí)行playbook時facts默認是自動采集的。如果你想單獨查看某一臺機器或一組機器的facts不需要寫playbook直接用setup模塊就能完成ansible test_host -m setup這條命令會對test_host主機執(zhí)行facts收集并把完整結果以JSON格式打印到終端。由于信息量很大終端輸出會非常長。我常用參數(shù)來精確過濾需要的內(nèi)容ansible test_host -m setup -a filteransible_os_family ansible test_host -m setup -a filteransible_memtotal_mb注意這里的filter是“包含匹配”不是精確匹配。比如filteransible_eth會把所有網(wǎng)卡相關字段都匹配出來。想知道這臺機器有哪些虛擬網(wǎng)卡、哪些物理網(wǎng)卡嗯用這個filter直接列出來了。2.2 playbook中控制facts的采集在playbook里facts采集由gather_facts控制默認值為true- hosts: all gather_facts: true tasks: - name: Show hostname debug: var: ansible_facts[hostname]新版Ansible2.5推薦通過ansible_facts[hostname]這種形式訪問不用再裸用老式的ansible_hostname。兩種寫法目前都兼容但建議在新項目里統(tǒng)一用ansible_facts字典語義更清晰也能避免變量名沖突。還有一個值得注意的點setup模塊支持參數(shù)所以你可以強制刷新緩存里的factsansible test_host -m setup --flush-cache這個命令會忽略已有的facts緩存強制重新采集一遍。后面講緩存機制時這個操作很有用。2.3 gather_facts設為false的場景不是每個playbook都需要facts。比如你只是批量重啟服務、拷貝幾個靜態(tài)文件機器的操作系統(tǒng)版本、內(nèi)存情況根本用不上。這種情況下把gather_facts關掉能省掉不少時間尤其是管理幾百臺機器時效果非常明顯。- hosts: all gather_facts: false tasks: - name: Copy a file copy: src: /local/path/file dest: /opt/file從反饋時間上看關閉facts采集后單臺機器可以省下1到3秒。機器數(shù)量越多節(jié)省的時間越可觀。但有一個前提任務里千萬不能引用任何facts變量否則會直接報“變量未定義”的錯誤到時候還得回頭開啟采集。2.4 哪些facts值得關注的一覽表facts字段非常多為了不讓初學者迷路我整理了一份高頻字段速查分類字段名含義系統(tǒng)ansible_facts[distribution]發(fā)行版名稱如CentOS、Ubuntu系統(tǒng)ansible_facts[distribution_version]發(fā)行版版本號系統(tǒng)ansible_facts[os_family]系統(tǒng)家族如RedHat、Debian網(wǎng)絡ansible_facts[default_ipv4][address]默認路由對應的IPv4地址網(wǎng)絡ansible_facts[dns][nameservers]當前使用的DNS服務器列表硬件ansible_facts[memtotal_mb]總內(nèi)存大小MB硬件ansible_facts[processor_cores]物理CPU核心數(shù)硬件ansible_facts[processor_count]邏輯CPU數(shù)量存儲ansible_facts[mounts]掛載點詳細信息列表存儲ansible_facts[devices][sda][size]第一塊磁盤容量時間ansible_facts[date_time][iso8601]系統(tǒng)當前時間ISO格式包管理ansible_facts[pkg_mgr]使用的包管理器名稱這份表只是冰山一角。實際上facts里還包含每個網(wǎng)卡的MAC地址、IPv6地址、系統(tǒng)時區(qū)、SELinux狀態(tài)、Python解釋器路徑等幾百個字段。能用好其中二三十個日常自動化任務基本就夠了。3. 核心字段解析從系統(tǒng)到網(wǎng)絡、從存儲到虛擬化3.1 系統(tǒng)基礎信息判斷寫playbook時最常見的需求是“根據(jù)系統(tǒng)類型做不同處理”。比如同一套部署腳本在RedHat系用yum安裝依賴在Debian系用apt。以下是兩種判斷寫法- name: Install package according to OS family yum: name: httpd state: present when: ansible_facts[os_family] RedHat - name: Install package according to OS family apt: name: apache2 state: present when: ansible_facts[os_family] Debian使用場景再寬一點比如某個任務只允許在CentOS 7以上執(zhí)行直接組合使用when: - ansible_facts[distribution] CentOS - ansible_facts[distribution_major_version] is version(7, )要留意一個問題CentOS和RedHat的os_family都是RedHat而某些Linux分支比如Fedora也是RedHat家族但軟件包管理上有差異。判斷時需要組合多個字段切忌只憑一個os_family下結論。3.2 網(wǎng)絡信息與IP地址獲取另一個高頻需求是把目標機器的IP地址動態(tài)寫入配置或服務注冊。假設我要獲取默認IPv4地址可以這樣寫- name: Show default IP debug: msg: The default IP is {{ ansible_facts[default_ipv4][address] }}如果你有多個網(wǎng)卡要單獨獲取字段會變成這樣ansible_facts[ansible_eth0][ipv4][address]不過這里我需要特別提醒在Ansible 2.12之前的版本里ansible_facts[ansible_eth0]這種寫法可能會因為內(nèi)部變量排序問題出現(xiàn)錯誤推薦使用無前綴形式ansible_facts[eth0]。我當時升級Ansible版本時踩過這個坑查了很久才發(fā)現(xiàn)是facts索引的問題。獲取所有網(wǎng)卡的所有地址可以對ansible_facts[interfaces]列表做遍歷。這個字段返回的是所有網(wǎng)卡名稱的列表你可以結合具體網(wǎng)卡繼續(xù)向下讀取。3.3 硬件資源與存儲信息在做容量規(guī)劃或資源檢查時內(nèi)存和磁盤是最常關注的。以下是我檢查物理內(nèi)存和磁盤的常用片段- name: Check memory threshold debug: msg: Memory too low! Current {{ ansible_facts[memtotal_mb] }} MB when: ansible_facts[memtotal_mb] 2048磁盤掛在facts里比較復雜mounts是一個列表每個元素包含mount、device、fstype、size_total、size_available等信息。想判斷根分區(qū)是否剩余空間不足用when引用when: item.size_available 1073741824這里size_available的單位是字節(jié)不要和人的直覺搞混。1GB等于1073741824字節(jié)但有些文件系統(tǒng)會按1024進制直接用這個裸數(shù)字比較更準確。如果你只關心磁盤設備本身而不關心掛載點devices字典里按塊設備名稱排列比如/dev/sda的對應鍵是sda里面包含分區(qū)表、大小、旋轉(zhuǎn)類型等字段。遍歷時會非常詳細。3.4 內(nèi)核與虛擬化、云環(huán)境信息知道目標機器是虛擬機還是物理機對不少運維場景很重要。比如某些驅(qū)動安裝、性能調(diào)優(yōu)任務需要區(qū)分虛擬化平臺??梢酝ㄟ^virtualization_type字段做判斷- name: Run only on virtual machines debug: msg: This is a VM running on {{ ansible_facts[virtualization_type] }} when: ansible_facts[virtualization_type] in [VMware, kvm, xen]如果機器部署在云平臺上比如AWS或阿里云facts里還有對應的ansible_facts[ec2]或廠商特定的元數(shù)據(jù)字段能拿到的信息比物理機豐富不少。這在自動擴縮容或云資源自動化場景下很有用。值得注意的是不同平臺收集到的facts覆蓋范圍不一樣比如在容器環(huán)境下virtualization_type可能是docker或containerd。寫playbook時不要想當然認為某些字段一定存在必要時先打印一遍實測數(shù)據(jù)。4. facts的實戰(zhàn)應用條件判斷、模板渲染與自省4.1 條件判斷讓playbook“看懂”機器facts最常見的用途就是配合when做條件執(zhí)行。除了基礎的OS判斷我舉一個更實際的生產(chǎn)場景某服務基于systemd管理但老舊的CentOS 6并沒有systemd需要退回到service模塊??梢赃@樣實現(xiàn)- name: Start service with systemd systemd: name: my_service state: restarted when: ansible_facts[service_mgr] systemd - name: Start service with sysvinit service: name: my_service state: restarted when: ansible_facts[service_mgr] sysvinit這里的service_mgr字段記錄了系統(tǒng)使用哪種服務管理器Ansible收集facts時自動識別。利用這種機制同一套playbook可以覆蓋新舊兩代系統(tǒng)不用維護兩套文件。4.2 把facts渲染進模板文件另一個高頻場景是把facts變量帶進Jinja2模板中。比如我要在每臺機器上生成一個包含自身主機名和IP的配置文件# server.conf hostname{{ ansible_facts[hostname] }} ip{{ ansible_facts[default_ipv4][address] }} platform{{ ansible_facts[distribution] }} {{ ansible_facts[distribution_version] }}對應的playbook任務如下- name: Generate config with facts template: src: server.conf.j2 dest: /etc/my_app/server.conf這種做法可以動態(tài)生成每臺機器專屬的配置不需要在inventory里手工定義一堆變量。模板中還可以使用Jinja2過濾器比如把內(nèi)存大小從字節(jié)轉(zhuǎn)成GB或者對字符串做拼接處理讓配置更易讀。4.3 用facts做動態(tài)資產(chǎn)清單我見過不少團隊用Ansible做資產(chǎn)盤點。其實不需要額外開發(fā)直接利用setup模塊加上模板就可以生成一份格式化的服務器清單。下面是一個思路- hosts: all tasks: - name: Generate inventory report local_action: module: template src: inventory_report.j2 dest: ./reports/{{ inventory_hostname }}_{{ ansible_facts[date_time][date] }}.txt模板里按表格列出主機名、IP、CPU、內(nèi)存、磁盤等信息。當機器數(shù)量增多時還可以直接用add_host和group_by基于facts動態(tài)分組實現(xiàn)真正的“inventory自適應”。這種玩法對我來說幫助最大因為它讓靜態(tài)的inventory變成了一套能實時反映真實環(huán)境的數(shù)據(jù)源省去了額外開發(fā)CMDB的成本。5. 性能優(yōu)化與緩存大型環(huán)境下的facts策略5.1 默認采集的代價每執(zhí)行一次playbookAnsible都會對每臺目標機器生成一個新的SSH連接并運行fact收集。在管理幾十臺以內(nèi)的機器時這個開銷并不明顯但當你管理幾百上千臺機器時facts收集時間會成為整個playbook運行的大頭。我自己在一組約300臺機器的環(huán)境里實測過僅gather_facts階段就要占用整個執(zhí)行時間的三分之一左右。如果你的任務根本用不到facts關掉它是零成本優(yōu)化如果必須要用則需要考慮緩存方案。5.2 啟用facts緩存redis方案Ansible支持多種fact緩存后端我用得最順的是redis適合中大型環(huán)境。配置方法在ansible.cfg中完成[defaults] gathering smart fact_caching redis fact_caching_timeout 86400這里gathering smart表示如果facts緩存存在且未過期就直接使用緩存結果只有緩存缺失或過期時才重新采集。fact_caching_timeout設置緩存有效期單位為秒86400就是24小時。使用redis作為緩存后端需要提前在控制機安裝redis并啟動服務。另外一個容易被忽略的細節(jié)如果修改了目標機器的網(wǎng)絡或系統(tǒng)配置舊的facts緩存不會自動失效需要手動執(zhí)行ansible test_host -m setup --flush-cache來刷新否則任務會一直基于舊數(shù)據(jù)運行。如果你不想引入redis用JSON文件做緩存也可以適合小型環(huán)境[defaults] fact_caching jsonfile fact_caching_connection /tmp/ansible/facts_cache fact_caching_timeout 86400這種方式配置更簡單不需要額外的服務依賴。5.3 減少facts采集的另一條路按需加載除了緩存我還會配合gather_facts: false然后在具體任務里按需用setup模塊手動采集個別機器甚至用booleans精簡。setup模塊還支持gather_subset參數(shù)只采集特定子集例如只采集硬件信息ansible test_host -m setup -a gather_subsethardwaregather_subset可用的值包括all、min、hardware、network、virtual等。min是最小集速度最快。如果只需要少數(shù)幾個字段它比全量采集快出不少。但注意gather_subset和filter并不完全相同。filter是在收集結果之后做字段過濾數(shù)據(jù)采集動作依然全量執(zhí)行gather_subset則是在源頭控制采集范圍性能收益更大。6. 自定義facts讓機器匯報私有狀態(tài)6.1 local facts機制有些信息Ansible的默認采集是拿不到的比如你只有通過某個腳本才能獲取的業(yè)務自定義狀態(tài)像是數(shù)據(jù)庫集群的主備角色、應用當前的運行模式。這時候可以用local facts把信息注入facts體系。local facts的存放位置是目標機器的/etc/ansible/facts.d/目錄。目錄下的每個.fact文件會被Ansible在收集facts時解析并把文件內(nèi)容放進ansible_facts[local]字典中。假設你寫了一個腳本輸出以下JSON到/etc/ansible/facts.d/cluster_info.fact{ role: primary, status: healthy, sync_lag_seconds: 0 }之后在playbook里就可以直接引用- name: Check cluster role debug: msg: This node is {{ ansible_facts[local][cluster_info][role] }}local facts的價值是把業(yè)務信息變成Ansible原生可讀的數(shù)據(jù)結構既可以用在條件判斷里也可以渲染到模板中。6.2 用INI格式定義local factslocal facts文件除了JSON還支持INI格式。比如[web] port 8080 ssl_enabled false引用方式變?yōu)閍nsible_facts[local][web][port]INI格式更簡潔適合寫少量鍵值對JSON格式表達能力更強適合嵌套結構。兩種混用場景也存在看團隊習慣。6.3 寫local facts文件的注意事項寫local facts時有幾個坑我踩過文件擴展名必須是.fact但內(nèi)容既可以是可以直接解析的靜態(tài)數(shù)據(jù)也可以是可執(zhí)行腳本Ansible會判斷文件內(nèi)容類型。如果是腳本需要確保該腳本有執(zhí)行權限并且以輸出合法JSON為最終結果否則會被Ansible當作普通文件嘗試解析報錯率很高。local facts目錄如果不存在Ansible會自動創(chuàng)建但目標機器必須有寫權限。使用非root用戶管理時需要檢查sudo規(guī)則。腳本型local facts不能使用Jinja2模板渲染因為它在facts收集階段只是被執(zhí)行。如果你需要動態(tài)渲染建議把渲染好的配置文件用template模塊下發(fā)到.fact路徑。local facts用得好可以讓每臺機器把自己最核心的業(yè)務狀態(tài)主動告訴Ansible而不用通過復雜的inventory變量或外部CMDB去同步。7. 常見問題與排查技巧實錄7.1 為什么我拿不到某個facts字段新手常遇到“明明在文檔里看到這個字段但執(zhí)行playbook時報變量未定義”。原因通常有幾個Ansible版本差異。不同版本的facts字段名可能有調(diào)整過濾字段時建議先跑一次setup模塊看實際輸出用哪個字段直接對照結果來。平臺差異。Windows、網(wǎng)絡設備的facts結構和Linux完全不同不能照搬Linux字段。使用了gather_facts: false但某個任務里又引用了facts變量。排查時先檢查play的gather_facts設置。namespaced版本問題。老式寫法ansible_hostname在某些情況下還能用但如果你用ansible_facts[ansible_hostname]反而可能取不到值因為嵌套關系變了。推薦寫ansible_facts[hostname]。排查這類問題最快的辦法就是先用setup模塊過濾一下實際看看數(shù)據(jù)長什么樣而不是反復猜字段名。7.2 變量優(yōu)先級沖突同名變量覆蓋facts在Ansible中playbook中定義的變量優(yōu)先級高于facts。如果你在vars里定義了一個server_ip而facts里也有同名key任務里引用時會優(yōu)先使用vars里的值。這一點需要小心避免引發(fā)“看起來沒生效”的詭異現(xiàn)象。建議給自定義變量起有區(qū)分度的名字盡量不要和facts字段重合。如果確實需要覆蓋要清楚自己正在做什么否則排查半天找不到原因。7.3 gather_facts超時問題管理大量機器時gather_facts偶爾出現(xiàn)超時或連接中斷。主要誘因是目標機器的SSH配置、網(wǎng)絡延遲、Python版本差異等??梢詮膸追矫嫒胧挚刂茩C到目標機器的網(wǎng)絡質(zhì)量如果SSH本身連接慢facts采集自然慢。目標機器的Python路徑不一致時Ansible會自動探測解釋器這會增加耗時。可以在ansible.cfg中顯式設置ansible_python_interpreter。嘗試用gather_subsetmin減少采集范圍。調(diào)高超時時間配置如timeout 30。實際項目中我發(fā)現(xiàn)很多超時并不是Ansible本身慢而是因為目標機器負載過高導致執(zhí)行環(huán)境響應慢。這種情況下適當調(diào)低并發(fā)數(shù)用串行分批執(zhí)行反而更穩(wěn)定。7.4 緩存中的facts過期使用facts緩存后有一個很容易被忽略的問題目標機器變化了Ansible卻仍然按舊數(shù)據(jù)執(zhí)行。比如新加了IP、擴容了內(nèi)存、裝了新網(wǎng)卡但緩存沒刷新。這時候手動刷新單臺機器ansible target_host -m setup --flush-cache刷新后重新跑playbook就能看到新數(shù)據(jù)。如果大批量刷新注意控制并發(fā)避免短時間內(nèi)對大量目標機器發(fā)起密集的fact收集請求導致目標機器或網(wǎng)絡負載過高。7.5 常見問題速查表問題現(xiàn)象可能原因解決辦法變量未定義引用了不存在的facts字段用setup模塊過濾觀察實際字段與文檔字段不一致Ansible版本差異升級到較新版本或按實際輸出為準每次playbook執(zhí)行很慢未使用facts緩存在ansible.cfg開啟redis或jsonfile緩存任務里取不到factsgather_facts被設為false檢查playbook開頭gather_facts配置修改了主機信息但沒變化facts緩存未刷新執(zhí)行setup --flush-cachelocal facts不生效文件擴展名不對或沒有執(zhí)行權限檢查.fact后綴和權限8. 經(jīng)驗總結我的facts使用守則寫到這里我把這幾年使用facts的幾條個人心得整理一下不一定都是大道理但都是實操換來的。第一不到萬不得已不要全量打印facts。很多云平臺的日志系統(tǒng)會收集完整執(zhí)行輸出直接debug一個完整的ansible_facts會把幾百行JSON灌進日志排查問題反而困難。需要什么就filter什么。第二facts采集不是免費的午餐。如果你管理的主機比較多且每個playbook都默認采集facts批量任務的整體耗時會上漲得非常快。用不到facts的playbook第一行就寫gather_facts: false按需開啟。第三local facts是你的好朋友。當你覺得默認facts不夠用時先別急著引入復雜的外部系統(tǒng)寫一個腳本輸出JSON放到facts.d目錄事情就解決了一大半。第四版本升級后務必回歸測試facts用法。Ansible在2.5、2.8、2.12這幾個版本里都對facts做了結構調(diào)整老寫法不一定在新版本還能跑。每次升級Ansible主版本我都會先跑一遍setup模塊的抽樣測試確認核心字段沒有變化。最后分享一個小技巧在編寫新的playbook時我會先加一個臨時的debug任務把需要引用的字段先打出來跑一遍確認無誤后再刪掉。這個方法幫我省掉了大量“靠猜字段名排錯”的時間。