Istio 可觀測性之日志,深入了解 Istio 通過 Envoy 來提供訪問日志功能
訪問日志提供了一種從單個工作負(fù)載實(shí)例的角度監(jiān)控和理解行為的方法,同樣訪問日志是我們在生產(chǎn)環(huán)境中必不可少的一種監(jiān)控手段,Istio 通過 Envoy 來提供訪問日志功能,Envoy Proxy 打印訪問信息到標(biāo)準(zhǔn)輸出,Envoy 容器的標(biāo)準(zhǔn)輸出能夠通過 kubectl logs 命令打印出來。
Istio 能夠以一組可配置的格式為服務(wù)流量生成訪問日志,使運(yùn)維人員可以完全控制日志記錄的方式、內(nèi)容、時間和地點(diǎn)。下面是一個典型的 Istio 訪問日志示例:
[2023-12-04T06:17:42.719Z] "GET /productpage HTTP/1.1" 200 - via_upstream - "-" 0 5289 23 22 "10.244.0.0" "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36" "f3a98cd1-6970-42c0-9c86-d179b93aa779" "192.168.0.100:31896" "10.244.1.254:9080" inbound|9080|| 127.0.0.6:45629 10.244.1.254:9080 10.244.0.0:0 outbound_.9080_._.productpage.default.svc.cluster.local default
在現(xiàn)在的 Telemetry V2 版本的架構(gòu)中,訪問日志直接通過服務(wù)網(wǎng)格的數(shù)據(jù)平面 Envoy 上生成并上報給日志后端。根據(jù)后端日志采集方式的不同,會有不同的通道和方式。Envoy 可以通過控制臺或者文件輸出,由各種日志代理采集,也可以通過 gRPC 協(xié)議直接上報日志給標(biāo)準(zhǔn)的訪問日志服務(wù) ALS(Envoy Access Log Service),比如 Skywalking 就支持,一般流程如下所示
- Envoy 根據(jù)服務(wù)網(wǎng)格配置提取應(yīng)用的訪問信息。
- 上報訪問日志,比如通過 gRPC 協(xié)議上報給 ALS 服務(wù)。
- ALS 服務(wù) 對接后端,將日志寫到 Elasticsearch、Kafka 等后端服務(wù)中。
- 通過 Kibanba、Grafana 等工具從后端服務(wù)檢索日志。
開啟 Envoy 訪問日志
同樣的方式在 Istio 中我們可以通過 MeshConfig 和 Telemetry API 的方式來啟用訪問日志。如果想通過 MeshConfig 方式來配置,需要在安裝配置中添加以下字段(默認(rèn)已經(jīng)配置了):
spec:
meshConfig:
accessLogFile: /dev/stdout
或者,在原來的 istioctl install 命令中添加相同的設(shè)置,例如:
istioctl install <flags-you-used-to-install-Istio> --set meshConfig.accessLogFile=/dev/stdout
此外還可以通過設(shè)置 accessLogEncoding 為 JSON 或 TEXT 來配置日志的格式。另外還可以設(shè)置 accessLogFormat 來自定義訪問日志的格式,如果沒有指定 accessLogFormat 的話 Istio 將使用以下默認(rèn)的訪問日志格式:
[%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% %RESPONSE_CODE_DETAILS% %CONNECTION_TERMINATION_DETAILS%
\"%UPSTREAM_TRANSPORT_FAILURE_REASON%\" %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\"
\"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\" %UPSTREAM_CLUSTER% %UPSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_LOCAL_ADDRESS% %DOWNSTREAM_REMOTE_ADDRESS% %REQUESTED_SERVER_NAME% %ROUTE_NAME%\n
當(dāng)然我們還是強(qiáng)烈推薦使用 Telemetry API 來開啟或關(guān)閉訪問日志,如下所示:
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
name: mesh-default
namespace: istio-system
spec:
accessLogging:
- providers:
- name: envoy
上面的示例使用默認(rèn)的 envoy 訪問日志提供程序,當(dāng)然我們也可以應(yīng)用于單獨(dú)的命名空間或單獨(dú)的工作負(fù)載,以在細(xì)粒度級別控制日志記錄。
Loki
接下來我們來將訪問日志發(fā)送到 Grafana Loki 進(jìn)行統(tǒng)一的日志管理,Loki 是一個水平可擴(kuò)展、高可用的多租戶日志聚合系統(tǒng)。
首先我們需要先確保 Loki 已經(jīng)安裝,我們這里同樣只是為了測試,直接使用下面的方式安裝即可,如果在生產(chǎn)環(huán)境中使用,則需要參考官方文檔進(jìn)行分布式部署。
kubectl apply -f samples/addons/loki.yaml -n istio-system
由于 Istio 默認(rèn)并沒有直接支持 Loki 這個 Provider,我們可以查看 MeshConfig 的 ExtensionProvider 字段,可以看到 Istio 默認(rèn)支持的 Provider 有:
字段 | 類型 | 描述 | 是否必需 |
name | string | 必填。用于唯一標(biāo)識擴(kuò)展提供商的名稱。 | 否 |
envoyExtAuthzHttp | EnvoyExternalAuthorizationHttpProvider (oneof) | 配置實(shí)現(xiàn)了 Envoy ext_authz 過濾器授權(quán)檢查服務(wù)的外部授權(quán)器,使用 HTTP API。 | 否 |
envoyExtAuthzGrpc | EnvoyExternalAuthorizationGrpcProvider (oneof) | 配置實(shí)現(xiàn)了 Envoy ext_authz 過濾器授權(quán)檢查服務(wù)的外部授權(quán)器,使用 gRPC API。 | 否 |
zipkin | ZipkinTracingProvider (oneof) | 配置使用 Zipkin API 的跟蹤提供商。 | 否 |
datadog | DatadogTracingProvider (oneof) | 配置 Datadog 跟蹤提供商。 | 否 |
stackdriver | StackdriverProvider (oneof) | 配置 Stackdriver 提供商。 | 否 |
skywalking | SkyWalkingTracingProvider (oneof) | 配置 Apache SkyWalking 提供商。 | 否 |
opentelemetry | OpenTelemetryTracingProvider (oneof) | 配置 OpenTelemetry 跟蹤提供商。 | 否 |
prometheus | PrometheusMetricsProvider (oneof) | 配置 Prometheus 指標(biāo)提供商。 | 否 |
envoyFileAccessLog | EnvoyFileAccessLogProvider (oneof) | 配置 Envoy 文件訪問日志提供商。 | 否 |
envoyHttpAls | EnvoyHttpGrpcV3LogProvider (oneof) | 針對 HTTP 流量配置 Envoy 訪問日志服務(wù)提供商。 | 否 |
envoyTcpAls | EnvoyTcpGrpcV3LogProvider (oneof) | 針對 TCP 流量配置 Envoy 訪問日志服務(wù)提供商。 | 否 |
envoyOtelAls | EnvoyOpenTelemetryLogProvider (oneof) | 配置 Envoy Open Telemetry 訪問日志服務(wù)提供商。 | 否 |
沒有 Loki 這個 Provider,那么我們需要怎樣才能將日志發(fā)送到 Loki 中呢?這里我們可以使用 OpenTelemetry 來收集日志,然后再通過 OpenTelemetry Collector 來將日志發(fā)送到 Loki 中。
OpenTelemetry
OpenTelemetry(簡稱 OTel) 是一個開源的可觀測框架,用于生成、收集和描述應(yīng)用程序的觀測數(shù)據(jù)。它提供了一組 API、庫、Agent 和 Collector,用于捕獲分布式跟蹤和度量數(shù)據(jù),并將其發(fā)送到分析軟件、存儲庫或其他服務(wù),OTel 的目標(biāo)是提供一套標(biāo)準(zhǔn)化、與廠商無關(guān)的 SDK、API 和工具集,用于將數(shù)據(jù)攝取、轉(zhuǎn)換和發(fā)送到可觀測性后端(開源或商業(yè)廠商)。
OpenTelemetry Collector
OpenTelemetry Collector 提供了一個與廠商無關(guān)的實(shí)現(xiàn)方式,用于接收、處理和導(dǎo)出遙測數(shù)據(jù),它消除了運(yùn)行、操作和維護(hù)多個代理/收集器的需求。
事實(shí)上收集器也并不是必需的,有的時候我們可以直接將遙測數(shù)據(jù)發(fā)送到外部的可視化工具中,比如 Jaeger、Zipkin 等等,但是這樣的話我們就需要在每個應(yīng)用中都進(jìn)行配置,這樣的話就會導(dǎo)致配置非常繁瑣,而且也不利于統(tǒng)一管理,所以這里我們就可以使用 OpenTelemetry Collector 來解決這個問題。
而且 OpenTelemetry Collector 本身部署起來也非常靈活,可以將其部署為代理或網(wǎng)關(guān)。區(qū)別在于作為代理時,收集器實(shí)例與應(yīng)用程序在同一主機(jī)上運(yùn)行(sidecar 容器、daemonset 等)。此外一個或多個收集器實(shí)例也可以作為獨(dú)立服務(wù)以每個集群、數(shù)據(jù)中心和地區(qū)的網(wǎng)關(guān)形式運(yùn)行。
一般來說建議新應(yīng)用選擇代理部署,現(xiàn)有應(yīng)用選擇網(wǎng)關(guān)部署的方式,如果是 Kubernetes 環(huán)境,當(dāng)然更建議部署為守護(hù)進(jìn)程(代理模式)的方式。
收集器由四個組件組成,通過管道(Pipeline)進(jìn)行啟用:
- 接收器(Receiver)將數(shù)據(jù)發(fā)送到收集器中,可以通過推送或拉取方式發(fā)送
- 處理器(Processor)決定如何處理接收到的數(shù)據(jù)
- 導(dǎo)出器(Exporter)決定將數(shù)據(jù)發(fā)送到哪里,可以通過拉取或推送方式完成,上面代碼中的 OTLPTraceExporter 就是一個導(dǎo)出器
- 連接器(Connectors):連接器既是輸出者又是接收者。連接器連接兩個管道:它作為一個管道末端的導(dǎo)出器消耗數(shù)據(jù),并作為另一個管道開始處的接收器發(fā)出數(shù)據(jù)。它可以消耗和發(fā)出相同數(shù)據(jù)類型或不同數(shù)據(jù)類型的數(shù)據(jù)。
OTel Collector
當(dāng)然我們也可以基于社區(qū)的組件進(jìn)行自定義,以增強(qiáng)和擴(kuò)展收集器管道。例如我們可以創(chuàng)建一個專用的導(dǎo)出器來接收并攝取指標(biāo)、追蹤和日志。
OpenTelemetry Collector 部署
在了解了 OpenTelemetry 的相關(guān)概念后,接下來我們需要部署 OpenTelemetry Collector,同樣我們直接使用 Istio 提供的 samples 中的配置即可:
kubectl apply -f samples/open-telemetry/loki/otel.yaml -n istio-system
該命令會部署一個 OpenTelemetry 采集器,其中比較重要的是該采集器的配置:
apiVersion: v1
kind: ConfigMap
metadata:
name: opentelemetry-collector-conf
labels:
app: opentelemetry-collector
data:
opentelemetry-collector-config: |
receivers:
otlp:
protocols:
grpc:
http:
processors:
batch:
attributes:
actions:
- action: insert
key: loki.attribute.labels
value: pod, namespace,cluster,mesh
exporters:
loki:
endpoint: "http://loki.istio-system.svc:3100/loki/api/v1/push"
logging:
loglevel: debug
extensions:
health_check:
service:
extensions:
- health_check
pipelines:
logs:
receivers: [otlp]
processors: [attributes]
exporters: [loki, logging]
上面的配置中我們主要關(guān)注 exporters 字段,其中 loki 就是我們要將日志發(fā)送到的 Loki 服務(wù),endpoint 字段指定了 Loki 服務(wù)的地址,這里我們直接使用 Loki 的 Service 名稱即可,因?yàn)?Loki 服務(wù)暴露了 3100 端口,所以我們可以直接使用 http://loki.istio-system.svc:3100/loki/api/v1/push 來訪問 Loki 服務(wù)。而 receivers 字段表示接收器,這里配置的是 otlp,表示使用 OpenTelemetry 的 OTLP 標(biāo)準(zhǔn)協(xié)議來接收數(shù)據(jù)。processors 字段表示處理器,這里我們使用了 attributes 處理器,它的作用是向日志中添加一些自定義的屬性,比如 pod、namespace、cluster、mesh 等等,這樣我們在 Loki 中就可以通過這些屬性來進(jìn)行檢索了。最后需要注意的是必須要在 service.pipelines 中明確聲明要啟用的管道以及管道中使用的接收器、處理器和導(dǎo)出器,否則不會生效。
現(xiàn)在在 Istio 根命名空間中包含如下的一些工作負(fù)載:
$ kubectl get pods -n istio-system
NAME READY STATUS RESTARTS AGE
grafana-5f9b8c6c5d-jv65v 1/1 Running 16 (5h12m ago) 32d
istio-egressgateway-556f6f58f4-mqp5z 1/1 Running 0 4h26m
istio-ingressgateway-9c8b9b586-p2w67 1/1 Running 0 4h26m
istiod-644f5d55fc-dlktv 1/1 Running 0 4h26m
jaeger-db6bdfcb4-9s8lr 1/1 Running 0 3h47m
kiali-7c9d5f9f96-cp4mb 1/1 Running 18 (5h12m ago) 32d
loki-0 1/1 Running 0 32m
opentelemetry-collector-5ccc9c9c55-msg5x 1/1 Running 0 60s
prometheus-5d5d6d6fc-lfz87 2/2 Running 2 (5h12m ago) 2d19h
接下來我們就需要在 Istio 中添加一個 OpenTelemetry 訪問日志服務(wù)的 Provider,添加如下配置:
# iop.yaml
apiVersion: install.istio.io/v1alpha1
kind: IstioOperator
spec:
profile: demo
meshConfig:
extensionProviders:
- name: otel
envoyOtelAls:
service: opentelemetry-collector.istio-system.svc.cluster.local
port: 4317
logFormat:
labels:
pod: "%ENVIRONMENT(POD_NAME)%"
namespace: "%ENVIRONMENT(POD_NAMESPACE)%"
cluster: "%ENVIRONMENT(ISTIO_META_CLUSTER_ID)%"
mesh: "%ENVIRONMENT(ISTIO_META_MESH_ID)%"
在上面的配置中我們添加了一個名為 otel 的 Provider,該 Provider 是一個 envoyOtelAls,表示使用 OpenTelemetry 的訪問日志服務(wù),對應(yīng)的后端服務(wù)為 opentelemetry-collector.istio-system.svc.cluster.local,端口為 4317,這里我們直接使用 OpenTelemetry Collector 的 Service 名稱即可。最后我們還配置了 logFormat,表示日志的格式,這里我們添加了一些自定義的屬性,比如 pod、namespace、cluster、mesh 等等,然后在 OpenTelemetry 采集器中會把這些屬性轉(zhuǎn)換為 Loki 的標(biāo)簽,這樣我們在 Loki 中就可以通過這些屬性來進(jìn)行檢索了。
直接使用 istioctl 命令來安裝配置該對象即可:
istioctl install -f iop.yaml -y
到這里我們的準(zhǔn)備工作就完成了。
使用 Telemetry API 配置訪問日志
接下來我們只需要通過 Telemetry API 來啟用上面我們配置的日志 Provider 就可以開始收集日志了,如下所示:
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
name: mesh-logging-default
namespace: istio-system
spec:
accessLogging:
- providers:
- name: otel
應(yīng)用該資源對象后,整個服務(wù)網(wǎng)格的日志就都會被上報到 OTel 采集器,然后在 Loki 中就可以看到日志了。
這里我們直接打開 Grafana 的 Loki Dashboard 即可:
istioctl dashboard grafana
首先要在 Grafana 中添加 Loki 數(shù)據(jù)源:
Loki 數(shù)據(jù)源
然后接下來我們?nèi)ピL問 Productpage 應(yīng)用產(chǎn)生一些日志數(shù)據(jù),再切換回到 Grafana 中,切換到 Explore 頁面,然后選擇 Loki 數(shù)據(jù)源,就可以看到 Loki 中的日志了:
日志查詢
同樣的我們還可以使用 Telemetry API 來做一些更加細(xì)粒度的配置。
比如可以使用以下配置禁用 sleep 服務(wù)的訪問日志:
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
name: disable-sleep-logging
namespace: default
spec:
selector:
matchLabels:
app: sleep
accessLogging:
- providers:
- name: otel
disabled: true
還可以使用 match 字段來指定要過濾的流量,比如可以使用以下配置禁用 httpbin 服務(wù)的入站訪問日志:
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
name: disable-httpbin-logging
spec:
selector:
matchLabels:
app: httpbin
accessLogging:
- providers:
- name: otel
match:
mode: SERVER # 入站模式
disabled: true # 禁用
此外我們可以通過 CEL 表達(dá)式過濾訪問日志。只有響應(yīng)碼大于等于 500 時,才會顯示訪問日志,如下所示:
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
name: filter-sleep-logging
spec:
selector:
matchLabels:
app: sleep
accessLogging:
- providers:
- name: otel
filter:
expression: response.code >= 500
比如只有響應(yīng)碼大于等于 400 或請求轉(zhuǎn)到 BlackHoleCluster 或 PassthroughCluster 時,才顯示訪問日志:
apiVersion: telemetry.istio.io/v1alpha1
kind: Telemetry
metadata:
name: default-exception-logging
namespace: istio-system
spec:
accessLogging:
- providers:
- name: otel
filter:
expression: "response.code >= 400 || xds.cluster_name == 'BlackHoleCluster' || xds.cluster_name == 'PassthroughCluster' "
參考文檔
- https://istio.io/latest/docs/tasks/observability/。
- https://opentelemetry.io/docs/。