Ingress-nginx工作原理和实践
<p>本文记录/分享 目前项目的 K8s 部署结构和请求追踪改造方案<br><img src="https://img2020.cnblogs.com/blog/587720/202103/587720-20210318104941838-1800951094.png" alt="" loading="lazy"></p>
<p>这个图算是一个通用的前后端分离的 k8s 部署结构:<br>
Nginx Ingress 负责暴露服务(nginx前端静态资源服务), 根据十二要素应用的原<br>
则,将后端 api 作为 nginx 服务的附加动态资源。</p>
<h2 id="ingress-vs-ingress-nginx">Ingress vs Ingress-nginx</h2>
<p>Ingress 是一种向 k8s 集群外部的客户端公开服务的方法, <strong>Ingress 在网络协议栈的应用层工作</strong>,<br>
根据请求的主机名 host 和路径 path 决定请求转发到的服务。<br>
<img src="https://img2020.cnblogs.com/blog/587720/202103/587720-20210318105008372-336416298.png" alt="" loading="lazy"></p>
<p>在应用 Ingress对象提供的功能之前,必须强调集群中存在 Ingress Controller, Ingress 资源才能正常工作。</p>
<p>我这里的 web 项目使用的是常见的 Ingress-nginx (官方还有其他用途的 Ingress),Ingress-nginx 是使用 nginx 作为反向代理和负载均衡器的 K8s Ingress 控制器, 作为 Pod 运行在<code>kube-system</code> 命名空间。</p>
<p>了解 Ingress 工作原理,有利于我们如何与运维人员打交道。</p>
<p><img src="https://img2020.cnblogs.com/blog/587720/202103/587720-20210318105031745-1508335460.jpg" alt="" loading="lazy"></p>
<p>下面通过 Ingress-nginx 暴露 Kibana 服务:</p>
<pre><code>---
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
</code></pre>
<p>Ingress-nginx 中最让我困惑的是它的<code>Paths分流</code>与<code>rewrite-target</code>注解。</p>
<ul>
<li>Paths 分流<br>
一般用于 根据特定的 Path,将请求转发到特定的后端服务 Pod,后端服务 Pod 能接收到 Path 这个信息。<br>
一般后端服务是作为 api。</li>
<li>rewrite-target<br>
将请求重定向到后端服务, 那有什么用处呢?</li>
</ul>
<p>答: 以上面暴露的 kibana 为例, 我们已经可以在<code>https://logging.internal.gridsum.com/</code> 访问完整的 Kibana, 如果我想利用这个域名暴露 ElasticSearch 站点,怎么操作?<br>
这时就可以利用<code>rewrite-target</code>,</p>
<pre><code>---
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
</code></pre>
<p>在此 Ingress 定义中,由<code>(.*)</code>捕获的所有字符都将分配给占位符$2,然后将其用作重写目标注解中的参数。 这样的话:<code>https://logging.internal.gridsum.com/es</code> 将会重定向到后端 elasticsearch 站点,并且忽略了 es 这个 path</p>
<p><img src="https://img2020.cnblogs.com/blog/587720/202103/587720-20210318105110611-542996960.png" alt="" loading="lazy"></p>
<h2 id="ingress-nginx-到-webapp-的日志追踪">Ingress-nginx 到 webapp 的日志追踪</h2>
<p>熟悉我的朋友知道, 我写了《一套标准的ASP.NET Core容器化应用日志收集分析方案》,这里面主要是 BackEnd App 的日志,从我上面的结构图看,</p>
<p>Ingress-nginx----> Nginx FrontEnd App--->BackEnd App 需要一个串联的追踪 Id, 便于观察运维网络和业务应用。</p>
<p>幸好 Ingress-nginx, Nginx 强大的配置能力帮助我们做了很多事情:</p>
<ul>
<li>
<p>客户端请求到达 Ingress-Nginx Controllerr,Ingress-Nginx Controller 会自动添加一个<code>X-Request-ID</code>的请求 Header, 随机值---- 这个配置是默认的</p>
</li>
<li>
<p>请求达到 Nginx FrontEnd App, Nginx 有默认配置<code>proxy_pass_request_headers on;</code>, 自动将请求头都传递到上游的 Backend App</p>
</li>
</ul>
<p>这样跨越整个结构图的 request_id 思路已经清楚了,最后一步只需要我们在 Backend App 中提取请求中携带的<code>X-Request-ID</code>, 并作为日志的关键输出字段。</p>
<p>这就涉及到怎么从<strong>自定义日志的 LayoutRender</strong>。</p>
<p>下面为 NLog 自定义名为<code>x_request_id</code>的 Render,该 Render 从请求的 X-Request-ID 标头中提取值。</p>
<p>① 定义 NLog Render</p>
<pre><code> /// <summary>
/// Represent a unique identifier to represent a request from the request HTTP header X-Request-Id.
/// </summary>
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; }
}
</code></pre>
<p>② 从请求中获取 X-Request-Id 依赖 IHttpContextAccessor 组件<br>
这里使用 依赖查找的方式获取该组件, 故请在 Startup ConfigureService 中生成服务</p>
<pre><code> public void ConfigureServices(IServiceCollection services)
{
// ......
ServiceLocator.ServiceProvider = services.BuildServiceProvider();
}
</code></pre>
<p>③ 最后在 Program 中注册这个 NLog Render:</p>
<pre><code> public static void Main(string[] args)
{
LayoutRenderer.Register<XRequestIdLayoutRender>("x_request_id");
CreateHostBuilder(args).Build().Run();
}
</code></pre>
<p>这样从 Ingress-Nginx 产生的<code>request_id</code>,将会流转到 Backend App, 并在日志分析中起到巨大作用,也便于划清运维/开发的故障责任。</p>
<ul>
<li>https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#generate-request-id</li>
<li>http://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass_request_headers</li>
</ul>
<h3 id="总结">总结</h3>
<ol>
<li>了解了Ingress在应用层工作,根据Host和Path暴露k8s服务</li>
<li>本文梳理了Ingress和常见的Ingress-nginx的关系</li>
<li>对于应用了Ingress的应用,梳理了从Ingress-Nginx到WebApp的日志追踪id, 便于排查网络/业务故障</li>
</ol>
</div>
<div id="MySignature" role="contentinfo">
<HR style="FILTER: alpha(opacity=100,finishopacity=0,style=3)" width="80%" color=#987cb9 SIZE=3>
<div style="text-align:center;">
<p>本文来自博客园,作者:{有态度的马甲},转载请注明原文链接:https://www.cnblogs.com/JulianHuang/p/14554245.html</p>
<strong style="color: red; ">欢迎关注我的原创技术、职场公众号, 加好友谈天说地,一起进化</strong>
<div><imgstyle="width: 250px;height:250px;" src="https://blog-static.cnblogs.com/files/JulianHuang/QR.gif" /> </div>
</div><br><br>
来源:https://www.cnblogs.com/JulianHuang/p/14554245.html
頁:
[1]