Ingress-nginx工作原理和實踐
阿新 • • 發佈:2021-03-18
本文記錄/分享 目前專案的 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, 便於排查網路/業