在當(dāng)代的互聯(lián)網(wǎng)技術(shù)領(lǐng)域,微服務(wù)架構(gòu)已成為應(yīng)用部署的主流模式,它雖然增強(qiáng)了開發(fā)流程的敏捷性和系統(tǒng)的可擴(kuò)展性,但隨著服務(wù)數(shù)量的增加,系統(tǒng)的內(nèi)部通信變得越來越復(fù)雜。特別是在服務(wù)的接口和依賴關(guān)系不透明的情況下,整個(gè)系統(tǒng)的維護(hù)和優(yōu)化工作變得異常困難。
今天要介紹的流量攔截技術(shù),從本質(zhì)上講,提供了一種“上帝視角”來觀察和分析在復(fù)雜網(wǎng)絡(luò)系統(tǒng)中流動(dòng)的數(shù)據(jù)。這種方法使得開發(fā)和運(yùn)維團(tuán)隊(duì)能夠?qū)νǔk[蔽和不透明的服務(wù)間交互進(jìn)行“抽絲剝繭”,揭露隱藏在數(shù)據(jù)流中的詳細(xì)信息和交互模式。通過這種高度的可視化和分析能力,團(tuán)隊(duì)可以深入理解服務(wù)的運(yùn)行機(jī)制,即使這些服務(wù)表面上看起來像是封閉和不透明的“黑盒”。
方案
Pipy 的升級(jí)到 1.0 版本標(biāo)志著它從一個(gè)單純處理數(shù)據(jù)流的代理,轉(zhuǎn)變?yōu)橐粋€(gè)全面的可編程應(yīng)用引擎。這一重要的演進(jìn)不僅擴(kuò)展了 Pipy 的使用場景,也極大地提高了其在現(xiàn)代網(wǎng)絡(luò)架構(gòu)中的價(jià)值和靈活性。將 Pipy 作為 BPF(Berkeley Packet Filter)的控制器使用,是其作為控制面能力的一個(gè)典型示例。這種能力的加入,為網(wǎng)絡(luò)流量管理和系統(tǒng)監(jiān)控提供了更加強(qiáng)大和靈活的工具。
本方案利用 Pipy 代理作為控制面,在主機(jī)節(jié)點(diǎn)上加載 BPF(Berkeley Packet Filter)程序,根據(jù)預(yù)設(shè)的配置(如端口映射關(guān)系)動(dòng)態(tài)攔截并轉(zhuǎn)發(fā)目標(biāo)流量。這一過程中,訪問原始端口(originalPort)的流量會(huì)被轉(zhuǎn)發(fā)到一個(gè)監(jiān)聽指定端口(port)上的代理服務(wù)。代理服務(wù)進(jìn)一步根據(jù)流量的協(xié)議類型,對(duì)數(shù)據(jù)包進(jìn)行解碼和結(jié)構(gòu)化處理,然后將解析后的請(qǐng)求內(nèi)容通過 HTTP 協(xié)議發(fā)送到如 Elasticsearch 的存儲(chǔ)進(jìn)行存儲(chǔ)和分析。此外,流量還會(huì)被原封不動(dòng)地轉(zhuǎn)發(fā)到原始目標(biāo)端口,確保服務(wù)的連續(xù)性和透明性。
接下來我們演示如何使用 Pipy 來進(jìn)行 HTTP 和 Dubbo 的流量攔截和分析。
演示
這篇演示中,我們將用到項(xiàng)目 traffic-interceptor[1],這個(gè)項(xiàng)目中包含三個(gè)部分:
- ? rest2dubbo:使用 Pipy 做 HTTP 到 Dubbo 的協(xié)議轉(zhuǎn)換。
- ? port-interceptor:使用 Pipy 實(shí)現(xiàn)的端口攔截控制器,通過 BPF 將訪問某些原始端口(originalPort)的流量轉(zhuǎn)發(fā)到指定的端口(port)進(jìn)行流量攔截。
- ? traffic-dump:使用 Pipy 實(shí)現(xiàn)的代理,監(jiān)聽指定的端口(port)然后將流量轉(zhuǎn)發(fā)到原始端口(originalPort),同時(shí)會(huì)將流量的信息結(jié)構(gòu)化后發(fā)送到 ElasticSearch 中。
下面是演示的架構(gòu)圖:
前置條件
- ? Ubuntu 22.04(內(nèi)核版本 6.5):運(yùn)行 Dubbo 和 HTTP 應(yīng)用的主機(jī)
- ? 客戶端主機(jī)(系統(tǒng)類型不限):用于運(yùn)行 rest2dubbo 代理、發(fā)起 HTTP 請(qǐng)求
- ? 下載并安裝 Pipy 1.0.0[2]
- ? Docker
- ? jq
環(huán)境準(zhǔn)備
- 1. 啟動(dòng) Dubbo 應(yīng)用,服務(wù)在 20880 端口。
docker run --rm -d --name bookwarehouse -e spring.profiles.active=dubbo,dev -e dubbo.registry.register=false -p 20880:20880 addozhang/bookwarehouse-dubbo:0.3.1
- 1. 啟動(dòng) HTTP 應(yīng)用。
pipy "pipy.listen(8000, $ => $.serveHTTP(new Message('Hi, Pipy')))"
- 1. 啟動(dòng) ElasticSearch。
docker run --rm -d --name es -p 9200:9200 -e "discovery.type=single-node" docker.elastic.co/elasticsearch/elasticsearch:7.10.1 環(huán)境驗(yàn)證
在客戶端主機(jī)上,啟動(dòng) rest2dump 代理。這個(gè)代理會(huì)接收 HTTP 請(qǐng)求,通過協(xié)議轉(zhuǎn)換成 Dubbo 請(qǐng)求后轉(zhuǎn)發(fā)給上面的 Dubbo 應(yīng)用。
git clone https://github.com/flomesh-io/pipy-demos.git
cd pipy-demos/traffic-interceptor
在啟動(dòng)代理之前,需要將 rest2dubbo/main.js 中的 上游服務(wù)地址[3]改為運(yùn)行 Dubbo 服務(wù)的主機(jī)地址,比如我使用的主機(jī)地址是 20.24.215.69。然后啟動(dòng)代理。
pipy rest2dubbo/main.js
啟動(dòng)成功后從日志中可以看到其監(jiān)聽 8888 端口,通過 curl 來發(fā)送請(qǐng)求。
curl 'http://127.0.0.1:8888/v1/getBook' --header 'Content-Type: application/json' --data '{"id": 1}'
將收到如下響應(yīng)。這說明我們的 rest2dump 的協(xié)議轉(zhuǎn)換代理運(yùn)行正常。
{
"date": "2024-03-14T15:18:56.982Z",
"isbn": "9787517054221",
"author": "Flomesh",
"name": "Pipy 入門",
"value": 0,
"id": "1"
}
同樣測試 HTTP 應(yīng)用的訪問。
curl http://20.24.215.69:8000/hi
正常收到響應(yīng)。
Hi, Pipy
接下來我們就可以測試流量攔截了,先啟動(dòng) Traffic Dump 代理。
啟動(dòng) Traffic Dump 代理
Traffic Dump 代理的運(yùn)行很簡單,執(zhí)行下面的命令。
git clone https://github.com/flomesh-io/pipy-demos.git
cd pipy-demos/traffic-interceptor
pipy traffic-dump/main.js
運(yùn)行之后可以從日志中可以看到其監(jiān)聽 9000 和 30880 兩個(gè)端口。
啟動(dòng)端口攔截
首先我們需要編譯 BPF 程序。
cd port-interceptor
make
在 bin 目錄中可以看到編譯好的 BPF 程序 port-interceptor.o。
執(zhí)行下面的命令啟動(dòng)端口攔截。
sudo pipy /main.js
注意:該腳本需要 sudo,因?yàn)樗枰{(diào)用 tc 將 BPF 程序掛接到內(nèi)核中的數(shù)據(jù)路徑,這需要管理員權(quán)限。
啟動(dòng)后可以看到 Pipy 更新 BPF map 以及加載 BPF 程序的日志。
2024-03-14 15:48:11.881 [INF] Updating BPF maps...
2024-03-14 15:48:11.881 [INF] Created port mapping 8000 <---> 9000
2024-03-14 15:48:11.881 [INF] Created port mapping 8443 <---> 9443
2024-03-14 15:48:11.881 [INF] Created port mapping 20880 <---> 30880 驗(yàn)證
再次重發(fā)上面的 HTTP 和 Dubbo 請(qǐng)求,都可以正常收到響應(yīng)。
curl http://20.24.215.69:8000/hi
curl 'http://127.0.0.1:8888/v1/getBook' --header 'Content-Type: application/json' --data '{"id": 1}'
訪問 ElasticSearch 查詢保存的請(qǐng)求記錄。
HTTP 請(qǐng)求:
curl -s -X GET "http://localhost:9200/http/_search" -H 'Content-Type: application/json' | jq .hits.hits
[
{
"_index": "http",
"_type": "_doc",
"_id": "W9mqPY4Bi_IvwOBjFiex",
"_score": 1,
"_source": {
"time": 1710431540691,
"host": "20.24.215.69:8000",
"path": "/hi",
"headers": "{\"host\":\"20.24.215.69:8000\",\"user-agent\":\"curl/8.4.0\",\"accept\":\"*/*\"}"
}
}
]
Dubbo 請(qǐng)求:
curl -s -X GET "http://localhost:9200/dubbo/_search" -H 'Content-Type: application/json' | jq .hits.hits
[
{
"_index": "dubbo",
"_type": "_doc",
"_id": "WtmqPY4Bi_IvwOBjEycL",
"_score": 1,
"_source": {
"time": 1710431539513,
"dubboVer": "2.0.2",
"interface": "io.flomesh.demo.api.BookWarehouseService",
"ver": "v1",
"method": "getBook",
"args": [
"Ljava/lang/String;",
"1"
],
"raw": "[\"2.0.2\",\"io.flomesh.demo.api.BookWarehouseService\",\"v1\",\"getBook\",\"Ljava/lang/String;\",\"1\",{\"kind\":\"map\",\"elements\":[[\"input\",\"196\"],[\"path\",\"io.flomesh.demo.api.BookWarehouseService\"],[\"interface\",\"io.flomesh.demo.api.BookstoreService\"],[\"version\",\"v1\"]]}]"
}
}
]
Bingo!演示完成。