背景
在微服務大行其道的今天,如何觀測眾多微服務、快速理清服務間的依賴、如何對服務之間的呼叫效能進行衡量,成了擺在大家面前的難題。對此,Skywalking應運而生,它是託管在 Apache 基金會下的開源專案,旨在幫助開發者監控分散式程式的效能、瞭解各個服務的呼叫關係和執行情況。
Skywalking支援多種語言和框架,包含Java、Golang、Python等,功能強大、介面友好等特點使其迅速成為業界最流行的APM軟體之一。然而在運用Skywalking的過程中,我們常常更關注服務之間的呼叫鏈路、效能資料,往往會忽略流量入口(閘道器)到服務之間的Trace串聯,導致我們經常在閘道器層面觀測到一個錯誤呼叫後,無法透過TraceID快速檢視本次呼叫的鏈路,從而白白浪費寶貴的排障時間。
本文重點介紹如何將 Ingress Nginx 整合進 Skywalking,將其作為 Skywalking 的一個節點,並且在access log 中列印TraceID,從而在出現故障的時候,可以透過日誌中的TraceID快速找到呼叫鏈路,達到快速故障定位的效果。
注:本文使用的 Kubernetes 版本是 1.24.15,Ingress Nginx controller 版本是 v1.8.1,Skywalking版本是9.2.0。
方案
在介紹方案之前,我們先了解一下相關的背景知識,用於更好的理解整合方案。
- Ingress Nginx Configmap:Ingress Nginx 的各種配置存放地,可以透過該Configmap配置logformat、所開啟的外掛等。
- Skywalking Nginx Lua:Skywalking 官方提供的 Lua 版本 lib,提供了一系列的操作,自己可以在Nginx的配置檔案中編寫Lua指令碼,適時建立Span、結束Span,從而把 Nginx 當作Skywalking中的一個服務節點整合進Skywalking。
- Ingress Nginx 自定義外掛:Lua指令碼編寫的外掛,用於對 Ingress Nginx 做程式設計,想要使用外掛必須要將外掛放到 Ingress Controller 容器的 /etc/nginx/lua/plugins/外掛名稱 目錄中,且需要在 Ingress Controller 的configmap中開啟它。自定義外掛支援以下幾個鉤子:
- a. init_worker: 用於對Nginx Worker做一些初始化。
- b. rewrite: 用於修改請求、更改標頭、重定向、丟棄請求、進行身份驗證等。
- c. header_filter: 當接收到後端response header 時呼叫此函式,通常用來記錄和修改後端的response header。
- d. body_filter: 這是在收到後端response body 時呼叫的,一般用來記錄response body。
- e. log: 當請求處理完成並將響應傳遞給客戶端時,會呼叫此函式。
- sw8:SkyWalking 跨程序傳播的Header Key,它的格式是 1-TRACEID-SEGMENTID-3-PARENT_SERVICE-PARENT_INSTANCE-PARENT_ENDPOINT-IPPORT(其中TraceID、SpanID等都透過base64進行編碼),我們可以透過此Header解析出對應的 TraceID。
瞭解了上述原理後,我們的方案就顯而易見了,就是將 Skywalking Nginx Lua 整合進 Ingress Nginx中,並編寫外掛,在不同階段執行相關操作:
- a. 在 rewrite 階段生成新Span並解析出TraceID將其放在新Header中(方便access log 列印)
- b. 在 body_filter 階段結束該Span
- c. 在log階段提交對應的資料到Skywalking服務端
- d. 修改 Nginx log format,將儲存 TraceID 的Header 列印出來
步驟
1. 整合Skywalking Nginx Lua進Ingress Nginx
Skywalking Nginx Lua 的核心是它的 lib 目錄,裡邊包含了所有需要用到的函式操作,所以我們需要將該 lib 目錄的內容放到 Ingress Nginx 的Pod 中,讓我們編寫的外掛能夠呼叫到它。具體我們可以將 lib 目錄放到網盤中,然後透過 Volume 的形式掛載進去,也可以將 lib 的內容寫入configmap,然後掛載Volume到Pod中。本文選擇第二種方式,將 lib 的內容放到 configmap 中,然後掛載進去,雖說這種方式不夠優雅,但好在不用依賴網盤。
我們先clone Skywalking Nginx Lua 這個庫,然後將 lib 下的所有.lua檔案打平放到同一個目錄中
git clone https://github.com/apache/skywalking-nginx-lua.git
mkdir sk-lua-cm
cp skywalking-nginx-lua/lib/skywalking/*.lua sk-lua-cm/
cp skywalking-nginx-lua/lib/skywalking/dependencies/*.lua sk-lua-cm/
cp skywalking-nginx-lua/lib/resty/*.lua sk-lua-cm/
再透過 kubectl 命令將sk-lua-cm中的所有檔案建立到一個configmap中,注意將 -n 後邊的引數換成你自己Ingress Nginx 所在的 namespace。
kubectl create cm skywalking-nginx-lua-agent --from-file=./sk-lua-cm/ -n ingress-nginx
2. 編寫Ingress Nginx 的外掛
引入了 Skywalking 的 lib 後就可以編寫對應的 Ingress Nginx 自定義外掛了,程式碼比較簡單,以下是程式碼詳情(命名為main.lua)。
local _M = {}
function _M.init_worker()
local metadata_buffer = ngx.shared.tracing_buffer
require("skywalking.util").set_randomseed()
local serviceName = os.getenv("SKY_SERVICE_NAME")
if not serviceName then
serviceName="ingress-nginx"
end
metadata_buffer:set('serviceName', serviceName)
local serviceInstanceName = os.getenv("SKY_INSTANCE_NAME")
if not serviceInstanceName then
serviceName="ingress-nginx"
end
metadata_buffer:set('serviceInstanceName', serviceName)
metadata_buffer:set('includeHostInEntrySpan', false)
require("skywalking.client"):startBackendTimer(os.getenv("SKY_OAP_ADDR"))
skywalking_tracer = require("skywalking.tracer")
end
function _M.rewrite()
local upstreamName = ngx.var.proxy_upstream_name
skywalking_tracer:start(upstreamName)
if ngx.var.http_sw8 ~= "" then
local sw8Str = ngx.var.http_sw8
local sw8Item = require('skywalking.util').split(sw8Str, "-")
if #sw8Item >= 2 then
ngx.req.set_header("trace_id", ngx.decode_base64(sw8Item[2]))
end
end
end
function _M.body_filter()
if ngx.arg[2] then
skywalking_tracer:finish()
end
end
function _M.log()
skywalking_tracer:prepareForReport()
end
return _M
劃重點:在上述程式碼中獲取了幾個環境變數,需要記住,後邊需要用到。
- i. SKY_SERVICE_NAME:Ingress Nginx 在 Skywalking 中的 Service 名稱
- ii. SKY_INSTANCE_NAME:Ingress Nginx 例項在 Skywalking 中的例項名稱
- iii. SKY_OAP_ADDR:Skywalking後端地址
編寫好外掛程式碼後就可以基於此建立configmap了,依然需要注意 -n 後邊的namespace,需要改成你實際Ingress Nginx所在的 namespace。
kubectl create cm skywalking-lua-plug --from-file=main.lua -n ingress-nginx
3. 掛載相關 Lua 指令碼進 Ingress Nginx Controller 的 Pod 中
修改 Ingress Nginx Controller 的 Deployment 配置,主要修改以下幾點:
a. 環境變數
- name: SKY_OAP_ADDR
value: http://skywalking-oap.skywalking.svc.cluster.local:12800
- name: SKY_SERVICE_NAME
value: ingress-nginx
- name: SKY_INSTANCE_NAME
value: ingress-nginx
b. volumes 宣告
- name: sky-nginx-plugin
configMap:
name: skywalking-lua-plug
- name: skywalking-nginx-lua-agent
configMap:
name: skywalking-nginx-lua-agent
c. volumeMounts 宣告
- mountPath: /etc/nginx/lua/plugins/skywalking/main.lua
subPath: "main.lua"
name: sky-nginx-plugin
- mountPath: /etc/nginx/lua/resty/http.lua
subPath: "http.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/tablepool.lua
subPath: "tablepool.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/resty/http_headers.lua
subPath: "http_headers.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/resty/jit-uuid.lua
subPath: "jit-uuid.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/skywalking/client.lua
subPath: "client.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/skywalking/constants.lua
subPath: "constants.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/skywalking/correlation_context.lua
subPath: "correlation_context.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/skywalking/dependencies/base64.lua
subPath: "base64.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/skywalking/management.lua
subPath: "management.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/skywalking/segment.lua
subPath: "segment.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/skywalking/segment_ref.lua
subPath: "segment_ref.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/skywalking/span.lua
subPath: "span.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/skywalking/span_layer.lua
subPath: "span_layer.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/skywalking/tracer.lua
subPath: "tracer.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/skywalking/tracing_context.lua
subPath: "tracing_context.lua"
name: skywalking-nginx-lua-agent
- mountPath: /etc/nginx/lua/skywalking/util.lua
subPath: "util.lua"
name: skywalking-nginx-lua-agent
4. 修改 Ingress Nginx Controller 所使用的configmap配置
plugins: "skywalking"
lua-shared-dicts: "tracing_buffer: 100m"
main-snippet: |
env SKY_SERVICE_NAME;
env SKY_INSTANCE_NAME;
env SKY_OAP_ADDR;
log-format-upstream: |
$remote_addr - $remote_user [$time_local] "$request" $status $body_bytes_sent "$http_referer" "$http_user_agent" $request_length $request_time [$proxy_upstream_name] [$proxy_alternative_upstream_name] $upstream_addr $upstream_response_length $upstream_response_time $upstream_status $request_id $http_trace_id
該配置中配置瞭如下幾個資訊:
- a. plugins:開啟skywalking外掛
- b. lua-shared-dicts:宣告 trace 使用的變數和大小
- c. main-snippet:其中宣告瞭需要使用到的環境變數,切記在外掛中使用的環境變數必須放到這裡來
- d. log-format-upstream:log 格式,我們在這個裡新增了一個 http_trace_id 這個header的列印(上一步解析出來的TraceID)
5. 重啟 Pod 生效
把下列的 xxx 換成 Ingress Nginx Controller 的 Pod 名稱
kubectl delete pod xxxx -n=ingress-nginx
效果展示
本節展示最終在Skywalking UI 上的展示效果。
- 可以在 Skywalking 的拓撲圖中看到有一個 Ingress Nginx 的服務節點加入
- 在Trace查詢頁面可以看到有一個 Nginx 的 Span
- 如下是列印出來的TraceID
如何更好使用Trace?
透過上述介紹,我們已經把 Ingress Nginx 整合進了Skywalking,接下來介紹一下如何更好的使用Trace資料來快速定位故障。
資料建設
-
將Skywalking資料來源接入Flashcat,接入的方法很簡單,只需要填寫對應的地址、賬號、密碼,然後起一個名字即可
-
採集 Ingress Nginx 日誌到 kafka 中,這裡可以使用 categraf 的 log 採集器
-
將日誌接入 Flashcat 的日誌分析子系統生成報表,在這張報表中可以看到對應的域名、介面、流量、成功率等(當然,這些維度都可以自定義),在建立報表的時候設定好日誌中哪個欄位是TraceID欄位。
- 透過日誌報表生成滅火圖(IT系統健康度一覽表),例如下圖就是典型的電商系統核心API健康度一覽表。
- 透過資料庫、Metrics、日誌等不同來源建立北極星指標(核心業務指標),例如:電商系統的下單量、支付量等
串聯打通
透過Flashcat的串聯能力,建立北極星和滅火圖的串聯。
故障定位路徑
當建設好對應的指標和關聯後,就可以開啟我們的故障定位之旅了。
- 當北極星指標故障(核心業務受損)時,北極星頁面上對應的指標會飄紅且傳送報警,例如下圖中的商品實時下單量掉底了,該業務卡片會飄紅
- 此時我們點選曲線上掉底時刻的資料點,可以開啟關聯的滅火圖,一眼就可以看到是訂單子系統在飄紅(可能發生了故障)
- 我們點選飄紅的滅火圖路徑,可以下鑽到具體的卡片組中去
- 然後可以看到滅火圖卡片組中的訂單更新DB介面成功率為0,我們點選旁邊的詳情可以開啟對應的詳情曲線(透過上一步中的日誌報表生成)
- 透過詳情可以看到,這個時候的成功率已經掉底了,那麼我們同樣可以點選曲線上掉底的時間點開啟日誌詳情
- 在這個頁面中,我們可以直接檢視異常日誌的日誌詳情,也可以點選右側的 trace 按鈕開啟該呼叫的Trace鏈路
透過Trace鏈路可以看到Redis的埠不通導致更新失敗了,這個時候我們就需要去排查依賴的Redis是否正常了。
當然,以上只是Flashcat在整合可觀測性三大支柱(Metrics、Log、Trace)方面的一個小例子,如果您對Flashcat這套產品感興趣,可以隨時與我們交流:https://flashcat.cloud/contact/。