Python 中可觀測性的七個關鍵部分
你寫的應用會執(zhí)行很多代碼,而且是以一種基本上看不到的方式執(zhí)行。所以你是怎么知道:
- 代碼是否在運行?
- 是不是在正常工作?
- 誰在使用它,如何使用?
可觀測性是一種能力,可以通過查看數(shù)據(jù)來告訴你,你的代碼在做什么。在這篇文章中,主要關注的問題是分布式系統(tǒng)中的服務器代碼。并不是說客戶端應用代碼的可觀測性不重要,只是說客戶端往往不是用 Python 寫的。也不是說可觀測性對數(shù)據(jù)科學不重要,而是在數(shù)據(jù)科學領域的可觀測性工具(大多是 Juptyter 和快速反饋)是不同的。
為什么可觀測性很重要
所以,為什么可觀測性重要呢?在軟件開發(fā)生命周期(SDLC)中,可觀測性是一個關鍵的部分。
交付一個應用不是結束,這只是一個新周期的開始。在這個周期中,第一個階段是確認這個新版本運行正常。否則的話,很有可能需要回滾。哪些功能正常運行?哪些功能有細微的錯誤?你需要知道發(fā)生了什么,才能知道接下來要怎么做。這些東西有時候會以奇怪的方式不能正常運行。不管是天災,還是底層基礎設施的問題,或者應用進入了一種奇怪的狀態(tài),這些東西可能在任何時間以任何理由停止工作。
在標準 SDLC 之外,你需要知道一切都在運行中。如果沒有,有辦法知道是怎么不能運行的,這是非常關鍵的。
反饋
可觀測性的第一部分是獲得反饋。當代碼給出它正在做什么的信息時,反饋可以在很多方面提供幫助。在模擬環(huán)境或測試環(huán)境中,反饋有助于發(fā)現(xiàn)問題,更重要的是,以更快的方式對它們進行分類。這可以改善在驗證步驟中的工具和交流。
當進行金絲雀部署canary deployment或更改特性標志時,你需要知道是否要繼續(xù),還是等更長時間,或者回滾,反饋就顯得很重要了。
監(jiān)控
有時候你懷疑有些東西不太對。也許是一個依賴服務有問題,或者是社交網(wǎng)站爆出了大量你的網(wǎng)站的問題。也許在相關的系統(tǒng)中有復雜的操作,然后你想確保你的系統(tǒng)能完美處理。在這些情況下,你就想把可觀測性系統(tǒng)的數(shù)據(jù)整合到控制面板上。
當寫一個應用的時候,這些控制面板需要是設計標準的一部分。只有當你的應用能把數(shù)據(jù)共享給這些控制面板,它們才會把這些數(shù)據(jù)顯示出來。
警報
看控制面板超過 15 分鐘就像看著油漆變干一樣。任何人都不應該遭受這種折磨。對于這種任務,我們要有報警系統(tǒng)。報警系統(tǒng)將可觀測性數(shù)據(jù)與預期數(shù)據(jù)進行對比,當它們不匹配的時候就發(fā)出通知。完全深入研究時間管理超出了本文的范圍。然而,從兩方面來說,可觀測應用是報警友好的alert-friendly:
- 它們有足夠多,足夠好的數(shù)據(jù),發(fā)出的警報才是高質(zhì)量的。
- 警報有足夠的數(shù)據(jù),或者接收者可以很容易的得到數(shù)據(jù),這樣有助于找到源頭。
高質(zhì)量警報有三個特點:
- 較少的錯報:如果有警報,那一定是有問題了。
- 較少的漏報:如果有問題,那一定有警報觸發(fā)。
- 及時性:警報會迅速發(fā)出以減少恢復時間。
這三個特點是互相有沖突的。你可以通過提高監(jiān)測的標準來減少錯誤警報,代價是增加了漏報。你也可以通過降低監(jiān)測的門檻來減少漏報,代價是增加錯報。通過收集更多數(shù)據(jù),你也可以同時減少錯報和漏報,而代價是降低了及時性。
同時改善這三個參數(shù)就更難了。這就要求高質(zhì)量的可觀測性數(shù)據(jù)。更高質(zhì)量的數(shù)據(jù)可以同時改善這三個特點。
日志
有的人喜歡嘲笑用打印來調(diào)試的方法。但是,在一個大多數(shù)軟件都不在你本機運行的世界里,你所能做的只有打印調(diào)試。日志記錄就是打印調(diào)試的一種形式。盡管它有很多缺點,但 Python 日志庫提供了標準化的日志記錄。更重要的是,它意味著你可以通過這些庫去記錄日志。
應用程序要負責配置日志的記錄方式。諷刺地是,在應用程序?qū)ε渲萌罩矩撠熈硕嗄暌院?,現(xiàn)在越來越不是這樣了。在現(xiàn)代容器編排orchestration環(huán)境中,現(xiàn)代應用程序記錄標準錯誤和標準輸出,并且信任編排orchestration系統(tǒng)可以合理的處理日志。
然而,你不應該依賴庫,或者說,其他任何地方。如果你想讓操作的人知道發(fā)生了什么,使用日志,而不是打印。
日志級別
日志記錄的一個最重要功能就是 日志級別。不同的日志級別可以讓你合理的過濾并分流日志。但是這只有在日志級別保持一致的情況下才能做到。最后,你應該在整個應用程序中保持日志級別的一致性。
選擇不兼容語義的庫可以通過在應用層面的適當配置來追溯修復,這只需要通過使用 Python 中最重要的通用風格做到:getLogger(__name-_)。
大多數(shù)合理的庫都會遵循這個約定。過濾器Filters可以在日志對象發(fā)出之前就地修改它們。你可以給處理程序附加一個過濾器,這個處理程序會根據(jù)名稱修改消息,使其具有合適的級別。
import logging
LOGGER=logging.getLogger(__name__)
考慮到這一點,你現(xiàn)在必須明確日志級別的語義。這其中有很多選項,但是下面這些是我的最愛:
- Error?:發(fā)送一個即時警告。應用程序處于一個需要操作人員引起注意的狀態(tài)。(這意味著包含Critical? 和Error)
- Warning:我喜歡把這些稱作“工作時間警報”。這種情況下,應該有人在一個工作日內(nèi)關注一下。
- Info:這是在正常工作流程中發(fā)出的。如果懷疑有問題的時候,這個是用來幫助人們了解應用程序在做什么的。
- Debug:默認情況下,這個不應該在生產(chǎn)環(huán)境中出現(xiàn)。在模擬環(huán)境或開發(fā)環(huán)境下,可以發(fā)出來,也可以不發(fā)。如果需要更多的信息,在生產(chǎn)環(huán)境也可以特地被打開。
任何情況下都不要在日志中包含個人身份信息(PII)或密碼。無論日志級別是什么,都是如此,比如級別更改,激活調(diào)試級別等等。日志聚合系統(tǒng)很少是 PII 安全的,特別是隨著 PII 法規(guī)的不斷發(fā)展(HIPAA、GDPR 等等)。
日志聚合
現(xiàn)代系統(tǒng)幾乎都是分布式的。冗余、擴展性,有時是管轄權需要更多的水平分布。微服務意味著垂直分布。登錄到每個機器去查看日志已經(jīng)是不現(xiàn)實的了。出于合理的控制原因,允許開發(fā)人員登錄到機器中會給予他們更多的權限,這不是個好主意。
所有的日志都應該被發(fā)到一個聚合器。有一些商業(yè)的方案,你可以配置一個 ELK 棧,或者也可以使用其他的數(shù)據(jù)庫(SQL 或則 no-SQL)。作為一個真正的低技術解決方案,你可以將日志寫入文件,然后將它們發(fā)送到對象存儲中。有很多解決方案,但是最重要的事情是選擇一個,并且將所有東西聚合到一起。
記錄查詢
在將所有東西記錄到一個地方后,會有很多日志。具體的聚合器可以定義如何寫查詢,但是無論是通過從存儲中搜索還是寫 NoSQL 查詢,記錄查詢以匹配源和細節(jié)都是很有用的。
指標抓取
指標抓取是一個服務器拉取模型。指標服務器定時和應用程序連接,并且拉取指標。
最后,這意味著服務器需要連接和找到所有相關的應用服務器。
以 Prometheus 為標準
如果你的指標聚合器是 Prometheus,那么 Prometheus 格式做為一個端點是很有用的。但是,即使聚合器不是 Prometheus,也是很有用的。幾乎所有的系統(tǒng)都包含與 Prometheus 端點兼容的墊片。
使用客戶端 Python 庫給你的應用程序加一個 Prometheus 墊片,這將使它能夠被大多數(shù)的指標聚合器所抓取。當 Prometheus 發(fā)現(xiàn)一個服務器,它就期望找到一個指標端點。這經(jīng)常是應用程序路由的一部分,通常在 /metrics 路徑下。不管 Web 應用的平臺是什么,如果你能在一個端點下運行一個定制類型的定制字節(jié)流,Prometheus 就可以將它抓取。
對于大多數(shù)流行的框架,總有一個中間件插件或者類似的東西收集指標,如延遲和錯誤率。通常這還不夠。你需要收集定制的應用數(shù)據(jù):比如,每個端點的緩存命中/缺失率,數(shù)據(jù)庫延遲,等等。
使用計數(shù)器
Prometheus 支持多個數(shù)據(jù)類型。一個重要且巧妙的類型就是計數(shù)器。計數(shù)器總是在前進 —— 但有一點需要注意。
當應用重置,計數(shù)器會歸零。計數(shù)器中的這些“歷時”通過將計數(shù)器“創(chuàng)建時間”作為元數(shù)據(jù)發(fā)送來管理。Prometheus 知道不去比較兩個不同歷時的計數(shù)器。
使用儀表值
儀表值會簡單很多:它們測量瞬時值。用它們來測量會上下起伏的數(shù)據(jù):比如,分配的總內(nèi)存大小,緩存大小,等等。
使用枚舉值
枚舉值對于整個應用程序的狀態(tài)是很有用的,盡管它們可以以更精細的方式被收集。比如,你正使用一個功能門控框架,一個有多個狀態(tài)(比如,使用中、關閉、屏蔽等)的功能,也許使用枚舉會更有用。
分析
分析不同于指標,因為它們要對應連續(xù)的事件。比如,在網(wǎng)絡服務器中,事件是一個外部請求及其產(chǎn)生的工作。特別是,在事件完成之前事件分析是不能被發(fā)送的。
事件包含特定的指標:延遲,數(shù)量,以及可能產(chǎn)生的對其他服務請求的細節(jié),等等。
結構化日志
現(xiàn)在一個可能的選擇是將日志結構化。發(fā)送事件只發(fā)送帶有正確格式的有效載荷的日志。這個數(shù)據(jù)可以從日志聚合器請求,然后解析,并且放入一個合適的系統(tǒng),這樣可以對它的可見性。
錯誤追蹤
你可以使用日志來追蹤錯誤,也可以用分析來追蹤錯誤。但是一個專門的錯誤系統(tǒng)還是值得的。一個為錯誤而優(yōu)化的系統(tǒng)可以發(fā)送更多的錯誤,因為錯誤畢竟還是罕見的。這樣它就可以發(fā)送正確的數(shù)據(jù),并且用這些數(shù)據(jù),它能做更多智能的事情。Python 中的錯誤追蹤系統(tǒng)通常和一般的異常處理關聯(lián),然后收集數(shù)據(jù),并且把它發(fā)到一個專門的錯誤聚合器。
使用 Sentry
很多情況下,自己運行 Sentry 是正確的做法。當錯誤發(fā)生時,就說明有些東西就出問題了??煽康貏h除敏感數(shù)據(jù)是不可能的,因為一定有會出現(xiàn)敏感數(shù)據(jù)被發(fā)送到不應該的地方。
通常,這種工作量并不會很大:異常并不常出現(xiàn)。最后,這個系統(tǒng)并不需要很高的質(zhì)量,也不需要高可靠性的備份。昨天的錯誤應該已經(jīng)修復了,希望如此,如果沒有,你還會發(fā)現(xiàn)的!
快速、安全、可重復:三者都要
可觀測的系統(tǒng)開發(fā)起來更快,因為它們可以給你提供反饋。它們運行起來也更安全,因為當出問題的時候,它們也會更早的讓你知道。最后,因為有反饋回路,可觀測性也有助于圍繞它構建可重復的過程。可觀測性可以讓你了解你的應用程序。而更了解它們,就勝利了一半。
磨刀不誤砍柴功
構建所有的可觀測層是一件困難的事情??倳屓烁杏X是在浪費的工作,或者更像是“可以有,但是不急”。
之后再做這個可以嗎?也許吧,但是不應該。正確的構建可觀測性可以加速后面所有階段的開發(fā):測試、監(jiān)控,甚至是培訓新人。在一個和科技行業(yè)一樣動蕩的行業(yè),減少培訓新人的工作量絕對是值得的。
事實上,可觀測性很重要,所以盡早把它寫出來,然后就可以在整個過程中進行維護。反過來,它也會幫你維護你的軟件。