1. 程式人生 > >Ingress-nginx工作原理和實踐

Ingress-nginx工作原理和實踐

本文記錄/分享 目前專案的 K8s 部署結構和請求追蹤改造方案 ![](https://img2020.cnblogs.com/blog/587720/202103/587720-20210318104941838-1800951094.png) 這個圖算是一個通用的前後端分離的 k8s 部署結構: Nginx Ingress 負責暴露服務(nginx前端靜態資源服務), 根據十二要素應用的原 則,將後端 api 作為 nginx 服務的附加動態資源。 ## Ingress vs Ingress-nginx Ingress 是一種向 k8s 叢集外部的客戶端公開服務的方法, **Ingress 在網路協議棧的應用層工作**, 根據請求的主機名 host 和路徑 path 決定請求轉發到的服務。 ![](https://img2020.cnblogs.com/blog/587720/202103/587720-20210318105008372-336416298.png) 在應用 Ingress物件提供的功能之前,必須強調叢集中存在 Ingress Controller, Ingress 資源才能正常工作。 我這裡的 web 專案使用的是常見的 Ingress-nginx (官方還有其他用途的 Ingress),Ingress-nginx 是使用 nginx 作為反向代理和負載均衡器的 K8s Ingress 控制器, 作為 Pod 執行在`kube-system` 名稱空間。 瞭解 Ingress 工作原理,有利於我們如何與運維人員打交道。 ![](https://img2020.cnblogs.com/blog/587720/202103/587720-20210318105031745-1508335460.jpg) 下面通過 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 ![](https://img2020.cnblogs.com/blog/587720/202103/587720-20210318105110611-542996960.png) ## Ingress-nginx 到 webapp 的日誌追蹤 熟悉我的朋友知道, 我寫了《[一套標準的ASP.NET Core容器化應用日誌收集分析方案](https://www.cnblogs.com/JulianHuang/p/14049455.html)》,這裡面主要是 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 ``` /// /// Represent a unique identifier to represent a request from the request HTTP header X-Request-Id. ///
[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); } } /// /// Represent a http context layout renderer to access the current http context. ///
public abstract class HttpContextLayoutRendererBase : LayoutRenderer { private IHttpContextAccessor _httpContextAccessor; /// /// Gets the . /// protected IHttpContextAccessor HttpContextAccessor { get { return _httpContextAccessor ?? (_httpContextAccessor = ServiceLocator.ServiceProvider.GetService()); } } } 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("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 ### 總結 1. 瞭解了Ingress在應用層工作,根據Host和Path暴露k8s服務 2. 本文梳理了Ingress和常見的Ingress-nginx的關係 3. 對於應用了Ingress的應用,梳理了從Ingress-Nginx到WebApp的日誌追蹤id, 便於排查網路/業