本文記錄/分享 目前專案的 K8s 部署結構和請求追蹤改造方案
這個圖算是一個通用的前後端分離的 k8s 部署結構:
Nginx Ingress 負責暴露服務(nginx前端靜態資源服務), 根據十二要素應用的原
則,將後端 api 作為 nginx 服務的附加動態資源。
Ingress vs Ingress-nginx
Ingress 是一種向 k8s 叢集外部的客戶端公開服務的方法, Ingress 在網路協議棧的應用層工作,
根據請求的主機名 host 和路徑 path 決定請求轉發到的服務。
在應用 Ingress物件提供的功能之前,必須強調叢集中存在 Ingress Controller, Ingress 資源才能正常工作。
我這裡的 web 專案使用的是常見的 Ingress-nginx (官方還有其他用途的 Ingress),Ingress-nginx 是使用 nginx 作為反向代理和負載均衡器的 K8s Ingress 控制器, 作為 Pod 執行在kube-system
名稱空間。
瞭解 Ingress 工作原理,有利於我們如何與運維人員打交道。
下面通過 Ingress-nginx 暴露 Kibana 服務:
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: kibana
labels:
app: kibana
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "30"
nginx.ingress.kubernetes.io/proxy-read-timeout: "1800"
nginx.ingress.kubernetes.io/proxy-send-timeout: "1800"
nginx.ingress.kubernetes.io/proxy-body-size: "8m"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
tls:
- hosts:
- 'https://logging.internal.gridsum.com/'
secretName: tls-cert
rules:
- host: 'https://logging.internal.gridsum.com'
http:
paths:
- path: /
backend:
serviceName: kibana
servicePort: 5601
Ingress-nginx 中最讓我困惑的是它的Paths分流
與rewrite-target
註解。
- Paths 分流
一般用於 根據特定的 Path,將請求轉發到特定的後端服務 Pod,後端服務 Pod 能接收到 Path 這個資訊。
一般後端服務是作為 api。 - rewrite-target
將請求重定向到後端服務, 那有什麼用處呢?
答: 以上面暴露的 kibana 為例, 我們已經可以在https://logging.internal.gridsum.com/
訪問完整的 Kibana, 如果我想利用這個域名暴露 ElasticSearch 站點,怎麼操作?
這時就可以利用rewrite-target
,
---
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: elasticsearch
labels:
app: kibana
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "30"
nginx.ingress.kubernetes.io/proxy-read-timeout: "1800"
nginx.ingress.kubernetes.io/proxy-send-timeout: "1800"
nginx.ingress.kubernetes.io/proxy-body-size: "8m"
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/rewrite-target: "/$2"
spec:
tls:
- hosts:
- 'logging.internal.gridsum.com'
secretName: tls-cert
rules:
- host: 'logging.internal.gridsum.com'
http:
paths:
- path: /es(/|$)(.*)
backend:
serviceName: elasticsearch
servicePort: 9200
在此 Ingress 定義中,由(.*)
捕獲的所有字元都將分配給佔位符$2,然後將其用作重寫目標註解中的引數。 這樣的話:https://logging.internal.gridsum.com/es
將會重定向到後端 elasticsearch 站點,並且忽略了 es 這個 path
Ingress-nginx 到 webapp 的日誌追蹤
熟悉我的朋友知道, 我寫了《一套標準的ASP.NET Core容器化應用日誌收集分析方案》,這裡面主要是 BackEnd App 的日誌,從我上面的結構圖看,
Ingress-nginx----> Nginx FrontEnd App--->BackEnd App 需要一個串聯的追蹤 Id, 便於觀察運維網路和業務應用。
幸好 Ingress-nginx, Nginx 強大的配置能力幫助我們做了很多事情:
-
客戶端請求到達 Ingress-Nginx Controllerr,Ingress-Nginx Controller 會自動新增一個
X-Request-ID
的請求 Header, 隨機值---- 這個配置是預設的 -
請求達到 Nginx FrontEnd App, Nginx 有預設配置
proxy_pass_request_headers on;
, 自動將請求頭都傳遞到上游的 Backend App
這樣跨越整個結構圖的 request_id 思路已經清楚了,最後一步只需要我們在 Backend App 中提取請求中攜帶的X-Request-ID
, 並作為日誌的關鍵輸出欄位。
這就涉及到怎麼從自定義日誌的 LayoutRender。
下面為 NLog 自定義名為x_request_id
的 Render,該 Render 從請求的 X-Request-ID 標頭中提取值。
① 定義 NLog Render
/// <summary>
/// Represent a unique identifier to represent a request from the request HTTP header X-Request-Id.
/// </summary>
[LayoutRenderer("x_request_id")]
public class XRequestIdLayoutRender : HttpContextLayoutRendererBase
{
protected override void Append(StringBuilder builder, LogEventInfo logEvent)
{
var identityName = HttpContextAccessor.HttpContext?.Request?.Headers?["X-Request-Id"].FirstOrDefault();
builder.Append(identityName);
}
}
/// <summary>
/// Represent a http context layout renderer to access the current http context.
/// </summary>
public abstract class HttpContextLayoutRendererBase : LayoutRenderer
{
private IHttpContextAccessor _httpContextAccessor;
/// <summary>
/// Gets the <see cref="IHttpContextAccessor"/>.
/// </summary>
protected IHttpContextAccessor HttpContextAccessor { get { return _httpContextAccessor ?? (_httpContextAccessor = ServiceLocator.ServiceProvider.GetService<IHttpContextAccessor>()); } }
}
internal sealed class ServiceLocator
{
public static IServiceProvider ServiceProvider { get; set; }
}
② 從請求中獲取 X-Request-Id 依賴 IHttpContextAccessor 元件
這裡使用 依賴查詢的方式獲取該元件, 故請在 Startup ConfigureService 中生成服務
public void ConfigureServices(IServiceCollection services)
{
// ......
ServiceLocator.ServiceProvider = services.BuildServiceProvider();
}
③ 最後在 Program 中註冊這個 NLog Render:
public static void Main(string[] args)
{
LayoutRenderer.Register<XRequestIdLayoutRender>("x_request_id");
CreateHostBuilder(args).Build().Run();
}
這樣從 Ingress-Nginx 產生的request_id
,將會流轉到 Backend App, 並在日誌分析中起到巨大作用,也便於劃清運維/開發的故障責任。
- https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#generate-request-id
- http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass_request_headers
總結
- 瞭解了Ingress在應用層工作,根據Host和Path暴露k8s服務
- 本文梳理了Ingress和常見的Ingress-nginx的關係
- 對於應用了Ingress的應用,梳理了從Ingress-Nginx到WebApp的日誌追蹤id, 便於排查網路/業務故障