快速界定故障:Socket Tracer網絡監控實踐
開篇綜述:Socket Tracer定位是傳輸層(Socket&TCP)的指標采集工具,通過補齊網絡監控的這部分盲區,來達到快速界定網絡問題的目標。
一、背景
隨著軟件應用的集群化、容器化、微服務化,產品的穩定性越來越依賴網絡。現有的專有云和一體機產品,部署在裸機,從硬件服務器、交換機到os都是不可靠的,且監控盲區較多,其中網絡是重災區。對于網絡不穩定導致的中間件鏈接超時、設備掉線、視頻推流卡頓等問題,缺乏有效的網絡層監控指標定界問題。一旦現場不存在,由網絡引發的問題很難定位。現有的網絡監控方案,都集中在網卡維度做流量、錯包等指標統計,粒度過粗,只有從Socket和TCP連接維度,監控socket緩存狀態,采集TCP建連、斷開、實時流量、延遲、重傳等信息,才可以最直接的反映業務的網絡狀態。
二、目標
現有的網絡監控工具如 ss、netstat 等,可以顯示服務器當前 Socket 狀態快照,在問題現場可以有效的輔助我們排查問題。當現場不存在,我們希望能有工具能保存歷史網絡狀態。然而單純記錄歷史 Socket 五元組信息,在復雜拓撲場景是不夠的,因為IP很可能是動態的,還須將當前時刻的 Socket 連接和業務屬性(Pod Name、設備身份...)關聯,才能達到還原問題現場,快速界定故障域的目的。
1.典型場景
- 集群中間件訪問超時問題定界。
- 數據采集丟包問題定界:例如設備側聲稱發包,但網關沒有收到,現場不在了,互相扯皮。
- 設備連云鏈路檢測。
- 視頻直播卡頓問題定界。
- ...
2.能力綜述
Socket維度信息采集
- 流量(tx/rx)、延遲(srtt),待重傳包數量、總重傳次數、收發隊列長度,Accept隊列長度。
- TCP 生命周期監控:監聽TCP Close事件,統計連接時長,收發包總字節數。
- TCP Reset異常監控:收到或者發送Reset的異常,及異常時刻的TCP State。
云原生監控方案適配
- 現有的netstat、ss等網絡信息統計工具,無法做到跨network namespce的socket信息統計。在云原生環境使用不便。需要做到監控k8s集群所有節點,及節點上所有Pod的Socket狀態。
- 采集數據指標化,支持對接 Prometheus 的 Exporter 接口。
- 支持指標推送到 VictoriaMetrics。
指標選取原理
TCP的指標有很多,為什么采集上述的指標信息,出發點是希望找到可以反映應用程序狀態和網絡連通狀態指標,如果有其它指標采集建議也歡迎留言。下面展開分析下上述指標的采集原因:
1)TCP Retransmit
包重傳的超時時間是RTO,通常是200ms左右,當我們觀察到一段時間出現了TCP包重傳,后續又恢復正常了,可以判斷這個時間段出現了網絡抖動, 就可以找網絡的同學來幫忙排查問題了。
2)TCP SRTT
RTT(round-trip time)為數據完全發送完(完成最后一個比特推送到數據鏈路上)到收到確認信號的時間。
SRTT(smoothed round trip time)是平滑過的RTT。
通過srtt歷史曲線圖或柱狀圖,觀察出來延遲的區間變化,就可以知道網絡連接的srtt是否抖動。如果業務延遲發生了抖動,srtt很穩定,就可以說明大概率不是網絡的問題,可能是業務的問題,或者調度的問題等等; 反之,如果srtt也發生了抖動,那么可以先檢查一下網絡連接。
3)TCP Tx/Rx
監控鏈接的流量,結合對現場業務的理解,在業務出現丟失數據場景,可以輔助定位時網絡問題還是應用問題:
- 傳輸層收到數據,但堆積在rcv_queue中,可能是應用層處理代碼阻塞。
- 傳輸層Rx沒有增加,則很可能是對端沒有發送數據。
4)TCP reset reasons
Reset 包是導致TCP異常斷開的常見原因之一,下面對可能觸發 reset 事件的原因做一個匯總(如有錯漏歡迎補充):
- Non-Existence TCP endpoint: Port or IP(Restrict Local IP address):服務端不存在。(SYN -> Server reset)
- TCP SYN matches existing sessions:服務端、或者防火墻已存在相同5元組連接。(SYN -> Server reset)
- Listening endPoint Queue Full :應用層沒有及時accept,導致服務端Accept隊列滿(全鏈接隊列滿),分兩種情況:對于新來握手請求 SYN -> SYN包會被Server默默丟棄,不會觸發reset;碰巧在Client 回 ACK(握手第三個包)時,accept 隊列滿了,Server 會根據 tcp_abort_on_overflow sysctl 配置,決定是否發送 reset。
- Half-Open Connections:服務端程序重啟,導致鏈接信息丟失。(中間數據-> Server reset)
- RESET by Firewalls in transit:在防火墻維護session狀態的場景(NAT網關),防火墻Session TTL過期。(中間數據-> FW reset)
- Time-Wait Assassination:Client Time-Wait 期間,收到 Server 端遲到的數據包,回送Ack給Server,導致Server發送Rst包。(Server 遲到數據 -> Client Ack-> Server Reset)
- Aborting Connection:客戶端Abort,內核發送reset給服務端。(Client Reset)
三、實現原理
Socket Tracer 使用 eBPF+Tracepoint 捕捉 TCP 的 reset&new&close 等事件,使用 netlink + tcp_diag 周期抓取內核 Socket 信息快照。
1.eBPF
背后的思想是:“與其把數據包復制到用戶空間執行用戶態程序過濾,不如把過濾程序灌進內核去”。
eBPF 是一個在內核中運行的虛擬機,它可以去運行用戶。在用戶態實現的這種 eBPF 的代碼,在內核以本地代碼的形式和速度去執行,它可以跟內核的 Trace 系統相結合,給我們提供了幾乎無限的可觀測性。
eBPF 的基本原理:它所有的接口都是通過 BPF 系統調用來跟內核進行交互,eBPF 程序通過 LVM 和 Cline 進行編譯,產生 eBPF 的字節碼,通過 BPF 系統調用,加載到內核,驗證代碼的安全性,從而通過 JIT 實時的轉化成 Native 的 X86 的指令。eBPF整體架構如下:
2.kprobe
當安裝一個kprobes探測點時,kprobe首先備份被探測的指令,然后使用斷點指令(即在i386和x86_64的int3指令)來取代被探測指令的頭一個或幾個字節。
當CPU執行到探測點時,將因運行斷點指令而執行trap操作,那將導致保存CPU的寄存器,調用相應的trap處理函數,而trap處理函數將調用相應的notifier_call_chain(內核中一種異步工作機制)中注冊的所有notifier函數,kprobe正是通過向trap對應的notifier_call_chain注冊關聯到探測點的處理函數來實現探測處理的。
當kprobe注冊的notifier被執行時,它首先執行關聯到探測點的pre_handler函數,并把相應的kprobe struct和保存的寄存器作為該函數的參數,接著,kprobe單步執行被探測指令的備份,最后,kprobe執行post_handler。等所有這些運行完畢后,緊跟在被探測指令后的指令流將被正常執行。
3.tracepoint
tracepoint和kprobe相比,tracepoint是一個靜態的hook函數,是預先在內核里面編寫好才使用。tracepoint實現是基于hooks的思想,在函數的入口就被放置一個probe點,這個probe點就會跟蹤調用這個函數的各種信息,并將追蹤的信息保存到一個環形隊列中去,如果用戶希望讀取這些內核,就會通過debugfs形式來訪問。
4.方案選型
eBPF 調用方案
對比調用eBPF能力的三種方案,bpftrace / bcc / libbpf,最終選擇bcc:
- bpftrace提供了腳本語言,方便輸出內核信息到控制臺,做CLI工具很方便。但沒有提供API接口,不方便后臺代碼調用和信息讀取。
- libbpf會直接把內核bpf代碼編譯成bin文件,再放到目標機運行。目標是一次編譯,四處運行,為了解決跨內核版本(配置)的可移植問題,需依賴BTF kernel選項,目前絕大部分內核默認沒有打開該功能,需要修改配置重新編譯kernel才行。
- bcc在目標機環境運行階段,動態編譯bpf內核代碼,來解決可移植性問題。是現階段使用最廣的方案,絕大部分bpf監控工具都基于bcc-tools;并且提供API接口,便于代碼集成,其中內核代碼基于C語言,應用層代碼提供python和go兩種語言API。
Socket 信息采集方案
eBPF+kprobe在目標函數上動態掛載hook函數,在高頻調用(收發包函數)的場景額外開銷較大,因此在周期統計socket鏈接收發數據量、重傳次數等場景,我們參考 ss 的實現,基于 linux netlink + tcp_diag 內核模塊,該方案適合應用主動抓取某個時間點socket 統計信息快照,可以減少額外性能開銷。
5.整體架構圖
四、部署和配置方法
1.命令行參數定義
包含Socket采集過濾配置文件路徑,socket信息采集周期,vm-insert的URL地址。
- bash-5.0# ./socktracer --help
- Usage of ./socktracer:
- -configfile string
- Socket tracer target config file (default "/etc/filter.yaml")
- -metricsCacheNum int
- Metrics local cached number (default 10000)
- -namespace string
- Namespace for metrics
- -sockScanInterval int
- Socket statistical information scan interval (default 30)
- -version
- Show version information and exit
- -vmaddr string
- URL of Victoria Metrics write address
- -web.listen-address string
- Address to listen on for web interface and telemetry. (default ":8080")
- -web.metrics-path string
- Path under which to expose metrics. (default "/metrics")
2.Socket采集過濾配置文件格式
一臺服務器上的Socket連接數量非常多,數據量和比較大,我們往往只關心部分服務的相關連接,就像tcpdump我們也往往會對IP和端口做過濾一樣。過濾配置文件使用yaml格式,定義如下:
- filter.yaml配置文件定義,用于配置過濾需跟蹤的 Socket 鏈接:type SocketID struct {
- Protocol string `yaml:"protocol"` // Only support TCPv4 currently, will support UDP and IPv6 when needed.
- LocalIP string `yaml:"localIP"` // Specify local IP, will overwrite LocalPod when both set
- LocalPod string `yaml:"localPod"` // Specify prefix of local podName, will be overwritten by LocalIP when both set
- LocalPort int `yaml:"localPort"` // Specify local Port, set to 0 means do not filter local port
- PeerIP string `yaml:"peerIP"` // Specify peer IP, will overwrite PeerPod when both set
- PeerPort int `yaml:"peerPort"` // Specify peer Port, set to 0 means do not filter peer port
- PeerPod string `yaml:"peerPod"` // Specify prefix of peer podName, will be overwritten by PeerIP when both set
- }
- 樣例:bash-5.0# cat /etc/filter.yaml
- filters:
- - localIP: "" //采集目標1:不指定本地IP,可以不配置或者設置為空
- localPort: 6379 //指定本地端口,通常選擇固定的server端口作為過濾條件
- localPod: "default/redis-" //指定本地PodName前綴,格式為: namespace/podName前綴
- - localPod: "default/iotx-event-center" // 采集目標2:指定本地PodName前綴。peerPod: "default/redis-" //指定對端PodName前綴,格式為: namespace/podName前綴
- peerPort: 6379 //指定對端端口
五、前臺展示
1.Grafana Dashboard
下圖中,連接到 redis server 的所有TCP連接(來自不同的Client+Port)都會被監控,展示總并發連接數和連接的 rtt(延遲) 等信息:
2.內核版本依賴(>=4.9)
Socket 信息采集依賴 tcp_diag 內核模塊。
eBPF 還在快速發展期,內核中的功能也日趨增強,一般推薦基于Linux 4.4+ (4.9 以上會更好) 內核的來使用 eBPF。部分 Linux Event 和 BPF 版本支持見下圖: