[TOC]

0x00 Kubernetes中Ingress跨域设置

描述: 在您在kubernetes搭建ingress并通过其访问集群内部部署的项目时,有些功能可能会存在如下报错:Access to XMLHttpRequest at ... has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
上述错误提示这是一个跨域问题,在传统项目中我们更改Nginx配置即可,然后在kubernetes中或者ingress中,我们应该如何处理这种问题呢?

解决方式
我们可以在kubernetes中的跨域设置在Ingress中进行配置,要在Ingress规则中启用跨域资源共享(CORS)只需添加如下注释: nginx.ingress.kubernetes.io/enable-cors: "true", 除此之外我们还可以使用使用以下注释来控制CORS。

  • nginx.ingress.kubernetes.io/cors-allow-methods : 控制接受哪些方法。这是一个多值字段,以”,”分隔,仅接受字母(大写和小写),默认GET, PUT, POST, DELETE, PATCH, OPTIONS

  • nginx.ingress.kubernetes.io/cors-allow-headers : 控制接受哪些Header请求头。这是一个多值字段,以”,”分隔,并接受字母,数字,_和-。默认: DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization

  • nginx.ingress.kubernetes.io/cors-expose-headers: 控制哪些响应头暴露给客户端。这是一个多值字段,以”,”分隔,并接受字母,数字,_,-和。默认值:空。例:nginx.ingress.kubernetes.io/cors-expose-headers: "Version, X-CustomResponseHeader"

  • nginx.ingress.kubernetes.io/cors-allow-origin: 控制CORS接受的原产地。这是一个单字段值,格式如下:http(s)://origin-site.com或http(s)://origin-site.com:port,默认: *,例: nginx.ingress.kubernetes.io/cors-allow-origin: "https://origin-site.com:4443"

  • nginx.ingress.kubernetes.io/cors-allow-credentials: 控制在CORS操作期间是否可以传递凭据。默认: true,例: nginx.ingress.kubernetes.io/cors-allow-credentials: "false"

  • nginx.ingress.kubernetes.io/cors-max-age: 控制可以将预检请求缓存多长时间。默认值:1728000 示例:nginx.ingress.kubernetes.io/cors-max-age: 600

示例演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: front-web
namespace: web
labels:
app: front-web
ref: front
annotations:
kubernetes.io/ingress.class: "nginx"
nginx.ingress.kubernetes.io/enable-cors: "true"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST"
nginx.ingress.kubernetes.io/cors-allow-headers: "Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control"
nginx.ingress.kubernetes.io/cors-expose-headers: "Version, X-CustomResponseHeader"
nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
nginx.ingress.kubernetes.io/cors-allow-origin: {{ .Values.ingress.allowOrigin }}
nginx.ingress.kubernetes.io/cors-max-age: 600

Ingress CORS官方文档: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#enable-cors


0x01 Kubernetes中ingress-nginx文件上传代理访问超时设置

描述: 早上开发一张 504 gateway time-out界面截图给我, 说是在后台导出1数据一分钟后显示此错误页面,由于我们的业务是通过K8s和ingress提供外部访问的,

错误原因: 后台应用界面为使用ingress方式访问, 所以问题点在ingress-nginx-controller有关,通过查询发现ingress报504 gateway time-out错误,通常与proxy timeout有关。
由于默认的ingress-nginx-controller配置的nginx.conf的几个timeout参数都是60 此处修改为300,请根据应用实际情况修改。


解决办法:
描述: 修改该应用域名所对应的的ingress资源,在metadata-annotations下面增加如下几行。

1
2
3
nginx.ingress.kubernetes.io/proxy-connect-timeout: "30"   # 注意,此值不能超过75.
nginx.ingress.kubernetes.io/proxy-send-timeout: "120"
nginx.ingress.kubernetes.io/proxy-read-timeout: "120"

扩展知识1.文件太大报413:Request Entity Too Large

1
2
3
4
# 解决办法:创建 ingress 时添加 annotations(注释)
metadata:
annotations:
nginx.ingress.kubernetes.io/proxy-body-size: 1024m

扩展知识2.当http 的URI太长或者request header过大时会报414 Request URI too large或400 bad request错误解决办法

1
2
3
4
# 客户端请求头缓冲区大小
client_header_buffer_size 128k;
# 请求头总长度大于128k时使用如下设置的缓存区
large_client_header_buffers 4 128k;


0x02 Kubernetes中ingress-nginx获取真实客户端IP

描述: 最近将部分业务通过Ingress进行发布管理, 从而实现应用灰蓝发布、金丝雀发布,更贴近当下自动化运维技术的发展,并为了进行实现七层自定义负载转发, 将不同应用程序配置到指定业务域名下不同的目录,并减少业务管理复杂化,同时节约域名资源,即多个业务可以通过一个域名出去提供服务。

但是在实际环境中却发现一个小问题,在通过ingress-nginx访问后端应用时,无法无法获取真实的客户端IP,因为通常用户ip的传递依靠的是X-Forwarded-*参数,但是默认情况下ingress是没有开启的,其中又由于Ingress-Nginx前端代理是采用硬件负载将真实IP记录在自定义Header中,所以经过一天的资料查找与实践,最终将该问题进行解决,下面将记一波解决思路流程和配置实践。

环境说明

  • 1.逻辑请求访问流程: 客户端 -> 边界防火墙 -> A10(硬件)负载均衡 -> Ingrees-Nginx -> statefulsets.apps (应用配置、扩容及其生命管理) -> Pod (应用程序)。
  • 2.A10硬件负载均衡: 在硬件负载均衡上设置该业务域名的SSL,即实现外网访问https->转内网->http
  • 3.ingress-nginx部署说明: 由helm部署的ingress名称空间 kube-base。
  • 4.ingress-nginx-controller 的 Rule 及其 ConfigMap 配置。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    # (1)自定义ingress管理域名后端映射配置
    $ kubectl get ingress -n app weiyigeek-app
    # NAME CLASS HOSTS ADDRESS PORTS AGE
    # weiyigeek-app ingress-nginx app.weiyigeek.top 11.20.7.61 80 41d
    # 关键配置
    spec:
    ingressClassName: ingress-nginx
    rules:
    - host: app.weiyigeek.top
    http:
    paths:
    - backend:
    service:
    name: app-shangbao
    port:
    number: 80
    path: /shangbao/
    pathType: ImplementationSpecific

    # (2)ingress-nginx-controller 控制器 services 服务查看
    $ kubectl get svc -n kube-base ingress-nginx-controller
    # NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
    # ingress-nginx-controller NodePort 11.20.7.61 <none> 80:30080/TCP,443:30443/TCP 332d
    $ kubectl get svc -n kube-base ingress-nginx-controller
    apiVersion: v1
    kind: Service
    metadata:
    annotations:
    k8s.kuboard.cn/workload: ingress-nginx-controller
    meta.helm.sh/release-name: ingress-nginx
    meta.helm.sh/release-namespace: kube-base
    creationTimestamp: "2021-02-15T04:57:22Z"
    labels:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    name: ingress-nginx-controller
    namespace: kube-base
    resourceVersion: "82785800"
    uid: 79ac32ad-82a6-4a77-b5ad-71d30ebb07c5
    spec:
    clusterIP: 11.20.7.61
    clusterIPs:
    - 11.20.7.61
    externalTrafficPolicy: Cluster # 关键点
    ports:
    - name: http
    nodePort: 30080
    port: 80
    protocol: TCP
    targetPort: 80
    - name: https
    nodePort: 30443
    port: 443
    protocol: TCP
    targetPort: 443
    selector:
    app.kubernetes.io/component: controller
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/name: ingress-nginx
    sessionAffinity: None
    type: NodePort
    status:
    loadBalancer: {}


  • Step 01.通过业务后端日志发现获取的地址为K8S的master节点地址,而非真实的IP地址
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 日志
    2022-01-13 09:59:19.365 [http-nio-80-exec-10] INFO com.******.aop.AspectHandler-Access of com.******.controller.PageController.toPage?index [IP]10.41.40.26,10.41.40.26,172.20.170.128,
    2022-01-13 09:59:19.367 [http-nio-80-exec-10] INFO com.******.aop.AspectHandler-Return of com.******.controller.PageController.toPage:shangbao/index [IP]10.41.40.26,10.41.40.26,172.20.170.128,

    # 程序中获取真实IP地址的请求头几种Header.
    String ip0 = request.getHeader("X-Real-IP");
    String ip1 = request.getHeader("X_FORWARDED_FOR");
    String ip2 = request.getHeader("X-Forwarded-For");
    String ip3 = request.getHeader("Proxy-Client-IP");
    String ip4 = request.getHeader("WL-Proxy-Client-IP");
    String ip5 = request.getHeader("HTTP_CLIENT_IP");
    String ip6 = request.getHeader("HTTP_X_FORWARDED_FOR");
    String ip7 = request.getHeader("x-Original-Forwarded-For");
    String ip8 = request.getRemoteAddr();


  • step 02.通过抓取A10负载均衡流量及其A10访问ingress-nginx的流量,发现硬件负载均衡真实IP带入记录的Header字段是 X_FORWARDED_FOR, 这样做的好初是防止客户端请求时伪造真实IP。
WeiyiGeek.A10访问ingress-nginx的流量

WeiyiGeek.A10访问ingress-nginx的流量


  • Step 03.想要Ingress-Nginx传递自定义的X_FORWARDED_FOR字段,我们需要在 ingress-nginx 配置字典中加入如下配置.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 配置 ingress-nginx 字典更改后会自动更新进ingress-nginx的POD中/etc/nginx/nginx.conf的文件里。
$ kubectl get cm -n kube-base ingress-nginx-controller -o yaml
$ kubectl edit cm -n kube-base ingress-nginx-controller
apiVersion: v1
data:
enable-underscores-in-headers: "true"
use-forwarded-headers: "true"
forwarded-for-header: X_FORWARDED_FOR
compute-full-forwarded-for: "true"
proxy-real-ip-cidr: 192.168.4.11,192.168.10.11

# 具体的 ingres-nginx 的 configMap 配置
tee ingress-nginx-controller-cm.yaml <<'EOF'
apiVersion: v1
data:
allow-snippet-annotations: "true"
compute-full-forwarded-for: "true"
enable-real-ip: "true"
enable-underscores-in-headers: "true"
forwarded-for-header: X_FORWARDED_FOR
proxy-real-ip-cidr: 192.168.10.1/24,10.41.40.21/28,172.20.0.0/16
proxy-set-headers: X_FORWARDED_FOR
use-forwarded-headers: "true"
kind: ConfigMap
metadata:
labels:
app.kubernetes.io/component: controller
app.kubernetes.io/instance: ingress-nginx
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
app.kubernetes.io/version: 1.2.0
name: ingress-nginx-controller
namespace: ingress-nginx
EOF

参数解析:

  1. enable-underscores-in-headers: 是否在标题名称中启用下划线, 缺省默认为off,表示如果请求中header name 中包含下划线,则忽略掉不会传递到后端代理或者应用程序,即获取不到该Header,所以此处为了不丢弃A10传递过来的 X_FORWARDED_FOR Header 需要将该参数设置为True。

  2. use-forwarded-headers: 如果设置为True时,则将设定的X-Forwarded-* Header传递给后端, 当Ingress在L7 代理/负载均衡器之后使用此选项。 如果设置为 false 时,则会忽略传入的X-Forwarded-*Header, 当 Ingress 直接暴露在互联网或者 L3/数据包的负载均衡器后面,并且不会更改数据包中的源 IP请使用此选项。

  3. forwarded-for-header: 设置用于标识客户端的原始 IP 地址的 Header 字段。默认值X-Forwarded-For,此处由于A10带入的是自定义记录IP的Header,所以此处填入是X_FORWARDED_FOR.

  4. compute-full-forwarded-for: 将 remote address 附加到 X-Forwarded-For Header而不是替换它。当启用此选项后端应用程序负责根据自己的受信任代理列表排除并提取客户端 IP。

  5. proxy-real-ip-cidr: 如果启用 use-forwarded-headersuse-proxy-protocol,则可以使用该参数其定义了外部负载衡器 、网络代理、CDN等地址,多个地址可以以逗号分隔的 CIDR 。默认值: “0.0.0.0/0”

Tips: 为了统一可移植性,在程序设置或者硬件负载转发中,转发、设置的 header 里不建议采用"_"下划线,可以用驼峰命名或者其他的符号(如减号-)进行代替,上述的X_FORWARDED_FOR字段把我是坑得,欲哭无泪。

上述参数配置后在ingress-nginx-control中的/etc/nginx/nginx.conf结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
# use-forwarded-headers 参数设置后
real_ip_header X_FORWARDED_FOR;
real_ip_recursive on;
set_real_ip_from 192.168.10.11;
set_real_ip_from 192.168.4.11;

# We can't use $proxy_add_x_forwarded_for because the realip module replaces the remote_addr too soon
# 我们不能使用 `$proxy_add_x_forwarded_for`, 因为 realip 模块过早地替换了远程 remote_addr。
map $http_x_forwarded_for $full_x_forwarded_for {
default "$http_x_forwarded_for, $realip_remote_addr";
'' "$realip_remote_addr";
}


  • Step 04.为了在后端演示ingress-nginx传递过来的参数, 创建一个nginx应用在log_format参数设置如下"$http_X_FORWARDED_FOR" - "$http_X_Real_IP"'
    1
    2
    3
    4
    5
    6
    7
    8
    # /etc/nginx/nginx.conf
    log_format main '$remote_addr - $remote_user [$time_local] "$request" '
    '$status $body_bytes_sent "$http_referer" '
    '"$http_user_agent" "$http_x_forwarded_for" - "$http_X_FORWARDED_FOR" - "$http_X_Real_IP"';
    # 输出结果 "10.41.40.22" -- "10.41.40.22" -- "10.20.172.103"

    proxy_set_header X-Real-IP $remote_addr; # realip 模块 已经将 X_FORWARDED_FOR 字段赋值给 $remote_addr, 所以该字段也记录了真实IP.
    proxy_set_header X-Forwarded-For $full_x_forwarded_for;

Tips: remote_addr: 代表客户端的IP,但它的值不是由客户端提供的,而是服务端根据客户端的ip指定的,如果你使用了代理则该字段记录的的是代理IP。
Tips: X-Forwarded-For: 简称 XFF 它是一个头部扩展,TTP协议并没有对它的定义,它最开始是由 Squid 这个缓存代理软件引入用来表示 HTTP 请求端真实 IP,当前被各大 HTTP 代理、负载均衡等转发服务广泛使用并被写入 RFC 7239(Forwarded HTTP Extension)标准之中,格式为:X-Forwarded-For: client, proxy1, proxy2(注意:如果未经严格处理可以被伪造),例如如果一个 HTTP 请求到达服务器之前,经过了三个代理 Proxy1、Proxy2、Proxy3,IP 分别为 IP1、IP2、IP3,用户真实 IP 为 IP0,而服务端X-Forwarded-For 输出的是IP0, IP1, IP2,Proxy3 直连服务器,它会给 XFF 追加 IP2,表示它是在帮 Proxy2 转发请求,所以说列表中并没有 IP3,IP3 可以在服务端通过 Remote Address 字段获得
Tips: X-Real-IP: 自定义头部字段,通常被 HTTP 代理用来表示与它产生 TCP 连接的设备 IP,该设备可能是其他代理,也可能是真正的请求端,关键要看经过代理的层级次数或是是否始终将真实IP一路传下来(注意:如果未经严格处理,可以被伪造)。


  • Step 05.在应用 Pod 中进行利用tcpdump抓包,日志记录真实IP和效果如下所示:
    1
    2
    3
    4
    5
    6
    $ kubectl exec -it -n app shangbao-text sh
    # tcpdump -nnX port 80 -vv -w test.pcap

    # 日志效果:
    2022-01-13 22:31:00.178 INFO 1 --- [p-nio-80-exec-4] com.******.aop.AspectHandler: Access of com.******.controller.PageController.toPage?index [IP]183.222.192.169,183.222.192.169,10.41.40.21,172.20.35.192,
    2022-01-13 22:31:00.180 INFO 1 --- [p-nio-80-exec-4] com.******.aop.AspectHandler: Return of com.******.controller.PageController.toPage:shangbao/index [IP]183.222.192.169,183.222.192.169,10.41.40.21,172.20.35.192,
WeiyiGeek.在Pod中抓包使用wirshake打开

WeiyiGeek.在Pod中抓包使用wirshake打开

官方文档地址: https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/configmap/#proxy-real-ip-cidr

知识补充: Ingress Pod中无法保留源IP
问题现象: Ingress Pod中无法保留真实客户端IP,显示为节点IP或100.XX.XX.XX网段或其它地址。

问题原因: Ingress所使用的Service中externalTrafficPolicy设为了Cluster, SLB上使用了七层代理, 使用了WAF接入或WAF透明接入服务。
解决方案:

  • 对于设置externalTrafficPolicy为Cluster,且前端使用了四层SLB的情况。可以将externalTrafficPolicy改为Local。但可能会导致集群内部使用SLB IP访问Ingress不通,具体解决方案,请参见集群内访问集群LoadBalancer暴露的SLB地址不通。
  • 对于使用了七层代理(七层SLB、WAF、透明WAF)的情况,可以按以下步骤解决:
    确保使用的七层代理且开启了X-Forwarded-For请求头。
    在Ingress Controller的ConfigMap中(默认为kube-system命名空间下的nginx-configuration)添加enable-real-ip: “true”。
    观察日志,验证是否可以获取到源IP。
    对于链路较长,存在多次转发的情况(例如在Ingress Controller前额外配置了反向代理服务),可以在开启enable-real-ip时通过观察日志中remote_addr的值,来确定真实IP是否是以X-Forwarded-For请求头传递到Ingress容器中。若不是,请在请求到达Ingress Controller之前利用X-Forwarded-For等方式携带客户端真实IP。

总结说明:
描述: 由上述可知并不是所有的场景都能通过X-Forwarded-For来获取用户正式ip, 例如当服务器前端使用了CDN的时候,X-Forwarded-For 方式获取到的可能就是CDN的来源ip了,此种情况可以和CDN配置管理后台约定一个字段名来记录用户真实ip, 然后代理将这个字段逐层传递最后到应用服务端。

各种方式在CDN环境下,获取真实IP地址的优缺点:

  • CDN自定义header头
    优点:获取到最真实的用户IP地址,用户绝对不可能伪装IP。
    缺点:需要CDN厂商提供。

  • 获取forwarded-for头
    优点:可以获取到用户的IP地址。
    缺点:程序需要改动,以及用户IP有可能是伪装的。

  • 使用realip获取
    优点:程序不需要改动,直接使用remote_addr即可获取IP地址。
    缺点:ip地址有可能被伪装,而且需要知道所有CDN节点的ip地址或者ip段。

至此完毕!


0x03 Kubernetes中ingress-nginx如何在外部设置nginx snippet

描述: 我们可以在ingress-nginx的configMap和ingress域名规则中,利用ConfigMap和Annotations进行自定义配置 snippet 并注入nginx.conf中。

  • ConfigMap: 使用ConfigMap在NGINX中设置全局配置。
  • Annotations: 如果需要特定入口规则的特定配置,请使用此选项。

1.在ConfigMap中配置

1
2
3
4
5
6
$ kubectl edit cm -n kube-base ingress-nginx-controller

main-snippet: 将自定义配置添加到 nginx 配置的主要部分。
http-snippet: 将自定义配置添加到 nginx 配置的 http 部分。
server-snippet: 将自定义配置添加到 nginx 配置中的所有服务器。
location-snippet: 将自定义配置添加到 nginx 配置中的所有位置。您不能使用它来添加代理到 Kubernetes pod 的新位置,因为该片段无法访问 Go 模板函数。如果要添加自定义位置,则必须提供自己的 nginx.tmpl。


2.在ingress域名规则中配置

1
2
3
4
5
6
7
$ kubectl edit ingress -n app weiyigeek-app
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
proxy_set_header X-FORWARDED-FOR $http_X_FORWARDED_FOR;


实践示例:

1
2
3
4
nginx.ingress.kubernetes.io/server-snippet: |
if ($host ~* "(.*).weiyigeek.top") {
return 301 http://m.weiyigeek.top/$1$request_uri ;
}


0x04 Kubernetes中ingress-nginx指定node亲和性和nginx-ingress-controller修改缺省端口

描述: 在我们需要指定ingress-nginx-controller应用Pod允许运行在那些工作节点时可以对其进行Node和Pod亲和性设置, 与此同时我们还可以更改 nginx-ingress-controller 的服务端口, 但注意修改后需要更改对应的Service服务发现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
$ kubectl get deployments.apps  -n kube-base  ingress-nginx-controller -o wide
NAME READY UP-TO-DATE AVAILABLE AGE CONTAINERS IMAGES SELECTOR
ingress-nginx-controller 9/9 9 9 329d controller harbor.cloud/weiyigeek/ingress-nginx-controller:v0.44.0 app.kubernetes.io/component=controller,app.kubernetes.io/instance=ingress-nginx,app.kubernetes.io/name=ingress-nginx

# 编辑查看 ingress-nginx-controller 缺省的80、443端口
$ kubectl get deployments.apps -n kube-base ingress-nginx-controller
# 亲和性设置
spec:
affinity:
nodeAffinity:
requiredDuringSchedulingIgnoredDuringExecution:
nodeSelectorTerms:
- matchExpressions:
- key: node
operator: In
values:
- app
- work
podAntiAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- podAffinityTerm:
labelSelector:
matchExpressions:
- key: app.kubernetes.io/name
operator: In
values:
- ingress-nginx
topologyKey: kubernetes.io/hostname
weight: 100
# nginx-ingress-controller 启动参数
containers:
- args:
- /nginx-ingress-controller
- --default-backend-service=$(POD_NAMESPACE)/default-backend
- --publish-service=$(POD_NAMESPACE)/ingress-nginx-controller
- --election-id=ingress-controller-leader
- --ingress-class=ingress-nginx
- --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
- --validating-webhook=:6443
- --validating-webhook-certificate=/usr/local/certificates/cert
- --validating-webhook-key=/usr/local/certificates/key
# 关键点:增加以下两个参数参数(一个是http端口,一个是https端口)
- --http-port=8080
- --https-port=8443
name: controller
ports:
- containerPort: 8080
hostPort: 8080
name: http
protocol: TCP
- containerPort: 8443
hostPort: 8443
name: https
protocol: TCP
- containerPort: 6443
hostPort: 6443
name: webhook
protocol: TCP

0x05 Kubernetes中ingress-nginx如何为项目配置子路径

方式1.创建带有app-root注释的Ingress规则
描述: 将 approot.weiyigeek.top 请求重定向到 approot.weiyigeek.top/app1 子目录上。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/app-root: /app1
name: approot
namespace: default
spec:
rules:
- host: approot.weiyigeek.top
http:
paths:
- backend:
serviceName: http-svc
servicePort: 80
path: /

# 检查rewrite是否正常
curl -I -k approot.weiyigeek.top
HTTP/1.1 302 Moved Temporarily


方式2.创建带有rewrite注释的Ingress规则
描述: 在这个Ingress定义中元组(.*)捕获的所有字符都将分配给占位符 $2,然后将其用作重写目标注释中的参数。

例如,上面的入口定义将进行以下重写:

  • weiyigeek.top/demo 重写为 weiyigeek.top/
  • weiyigeek.top/demo/ 重写为 weiyigeek.top/
  • weiyigeek.top/demo/issues 重写为 weiyigeek.top/issues
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
name: rewrite
namespace: default
spec:
rules:
- host: rewrite.bar.com
http:
paths:
- backend:
serviceName: http-svc
servicePort: 80
path: /demo(/|$)(.*)
# 元组1 $1 = (/|$)
# 元组2 $2 = (.*)


方式3.通过注释server-snippet片段脚本进行Ingress规则重写
描述: 例如将访问 web.weiyigeek.top/index 的请求重定向到http://m.weiyigeek.top/web/index.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 方式1
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
annotations:
nginx.ingress.kubernetes.io/server-snippet: |
if ($host ~* "(.*).weiyigeek.top") {
return 301 http://m.weiyigeek.top/$1$request_uri ;
}
name: web
namespace: default
spec:
rules:
- host: '*.weiyigeek.top'
http:
paths:
- backend:
serviceName: web-nginx
servicePort: 80
path: /

# 方式2.访问test.weiyigeek.top/service/index重定向到test.weiyigeek.top/s1/index地址
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: example-ingress
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
rewrite /service/(.*) /s1/$1 break;
spec:
rules:
- host: test.weiyigeek.top
http:
paths:
- path: /service/
backend:
serviceName: app1
servicePort: 8080



0x06 Kubernetes 中 ingress-nginx 上的 HTTP 的速率限制请求

描述: 在某些情况我们可以使用ingress-nginx针对请求速率进行请求限制。

通常我们使用如下两种方式管理通过配置映射注释调整来完成。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# 配置映射-ConfigMap
$ kubectl get cm -n ingress-nginx ingress-nginx-controller -o yaml
data:
http-snippet: |
limit_req_zone $http_authorization zone=my-zone:20m rate=5r/s;
limit_req_zone $binary_remote_addr zone=my-zone:20m rate=10r/s;
limit_req_zone $http_someheader zone=my-zone:20m rate=20r/s;

# 配置注释-annotations
# - 入口资源中的注释:
metadata:
annotations:
nginx.ingress.kubernetes.io/configuration-snippet: |
limit_req zone=my-zone-1 burst=10 nodelay;
limit_req_log_level notice;
limit_req_status 429;

# - 一个入口定义的不同位置的不同节流示例:
metadata:
annotations:
nginx.ingress.kubernetes.io/server-snippet: |
location /content/images/ {
limit_req zone=my-zone-2 burst=50 nodelay;
}
location /content/texts/ {
limit_req zone=my-zone-3 burst=50 nodelay;
}
nginx.ingress.kubernetes.io/configuration-snippet: |
limit_req zone=my-zone-1 burst=10 nodelay;
limit_req_log_level notice;
limit_req_status 429;

温馨提示: 请注意 http-snippet 不允许作为注释,而只能在ConfigMap中进行映射配置!



0x07 Kubernetes中ingress-nginx优化配置

描述: 在K8s集群中部署安装 ingress-nginx 后默认并未进行相应的优化配置,本小结将针对于生产环境的中的 ingress-nginx 控制器进行优化。

我们可以从 ingress-nginx-controller 资源的 Pod 、ConfigMap 、以及业务的 ingress 规则入手。


ingress-nginx-controller Pod

温馨提示: 我们需要针对承载 ingress-nginx 的相关 Pod 容器进行内核参数优化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
$ kubectl get deployments.apps -n ingress-nginx ingress-nginx-controller
NAME READY UP-TO-DATE AVAILABLE AGE
ingress-nginx-controller 9/9 9 9 5d20h

$ kubectl get deployments.apps -n ingress-nginx ingress-nginx-controller -o yaml
# 在 spec.template.spec 对象下添加一个初始化 initContainers 容器
initContainers:
- name: sysctl
image: alpine:3.10
imagePullPolicy: IfNotPresent
command:
- sh
- -c
- |
mount -o remount rw /proc/sys
sysctl -w net.core.somaxconn=65535
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.ip_local_port_range="1024 65535"
sysctl -w fs.file-max=1048576
sysctl -w fs.inotify.max_user_instances=16384
sysctl -w fs.inotify.max_user_watches=524288
sysctl -w fs.inotify.max_queued_events=16384
securityContext:
privileged: true


ingress-nginx-controller ConfigMap

温馨提示: 我们需要按照需要将下述K/V配置项插入到 ingress-nginx 的 configMap 里的 data 对象下。

ingress-nginx 资源查看

1
2
3
4
5
# 查看 Ingress-nginx 全局配置参数:
kubectl get cm -n ingress-nginx nginx-ingress-controller -o yaml

# 修改 Ingress-nginx 全局配置参数:
kubectl edit cm -n ingress-nginx nginx-ingress-controller

优化配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
# 负载工作机制,轮询
load-balance: "round_robin"

# 错误日志等级设置 (debug, info, notice, warn, error, crit, alert, or emerg)
error-log-level: "notice"

# 启用Gzip资源压缩 (3k以上)
use-gzip: "true"
gzip-level: "2"
gzip-min-length: "3072"
gzip-types: "text/html text/plain text/css text/javascript application/javascript application/x-javascript application/xml application/x-httpd-php application/x-font-ttf application/json image/x-icon image/svg+xml image/avif image/webp font/ttf font/opentype"
# 不建议进行照片压缩 image/jpeg image/gif image/png 可能反而会增加其体积

# 启用Brotli资源压缩(同等条件下优于Gzip,任选一个)
enable-brotli: "true"
brotli-level: 5
brotli-min-length: 3072
brotli-types: "text/plain text/css text/javascript application/javascript application/x-javascript application/xml application/x-httpd-php application/x-font-ttf image/x-icon image/svg+xml image/avif image/webp font/ttf font/opentype"
# 不建议进行照片压缩 image/jpeg image/gif image/png 可能反而会增加其体积

# 启用http2支持(实际上默认是开启的,如果过关闭请将其设置为true)
use-http2: "true"

# ssl 会话复用
ssl_session_cache: "shared:SSL:10m;"
ssl-session-timeout: "10m"

# worker 每个工作进程可以打开的最大文件数与同时打开最大连接数设置
worker-processes: "auto"
max-worker-open-files: "10240"
max-worker-connections: "32767"

# 连接复用
enable-multi-accept: "true"

# keep-alive 连接超时和最大请求数调整
keep-alive: "75"
keep-alive-requests: "10000"

# upstream-keepalive 与上游Pod连接超时与最大请求数调整
upstream-keepalive-time: "30m"
upstream-keepalive-timeout: "60"
upstream-keepalive-requests: "10000"
upstream-keepalive-connections: "512"

# proxy-connect 设置 ingress-nginx 与 pstream pod 之间连接请求超时实践。
# 设置与代理服务器建立连接的超时时间(不能超过75s)
proxy-connect-timeout: "30"
# 设置将请求传输到代理服务器的超时时间(以秒为单位)(超时仅在两个连续的写操作之间设置,而不是为整个请求的传输设置)
proxy-send-timeout: "120"
# 设置从代理服务器读取响应的超时时间(以秒为单位)
proxy-read-timeout: "120"


安全配置

1
2
# 启用 modsecurity waf模块拦截常规Web攻击
enable-modsecurity: "true"


开发测试:

1
2
# 启用 nginx Opentracing 扩展, 默认值:禁用
enable-opentracing: true


温馨提示:

  • 在 Nginx 中的 upstream 主要是配置均衡池和调度方法.
  • 在 Nginx 中的 proxy_pass 主要是配置代理服务器ip或服务器组的名字.

温馨提示: 在高并发场景下,我们需配置upstream-keepalive-*相关参数, 使得 nginx 尽可能快速处理 HTTP 请求(尽量少释放并重建 TCP 连接),同时控制 nginx 内存使用量。

温馨提示: ingress nginx 与 upstream pod 建立 TCP 连接并进行通信,其中涉及 3 个超时配置我们需要重点关注。

  • proxy-read-timeout 选项 设置 nginx 与 upstream pod 之间读操作的超时时间,默认设置为 60s,当业务方服务异常导致响应耗时飙涨时,异常请求会长时间夯住 ingress 网关,我们在拉取所有服务正常请求的 P99.99 耗时之后,将网关与 upstream pod 之间读写超时均缩短到 3s,使得 nginx 可以及时掐断异常请求,避免长时间被夯住。
  • proxy-connect-timeout 选项 设置 nginx 与 upstream pod 连接建立的超时时间,默认设置为 5s,由于在 nginx 和业务均在内网同机房通信,我们将此超时时间缩短到 1s。


ingress Rule

描述: 通常我们需要为单个业务进行相应配置, 此时我们便需要再业务的ingress部署清单中进行修正。

例如: 编辑 blog.weiyigeek.top 虚拟主机站点的 ingress 规则 (kubectl edit ingress -n weiyigeek blog.weiyigeek.top)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
# 解决: 413 Request Entity Too Large 问题
ingress.kubernetes.io/proxy-body-size: "128m"

# 解决:后端大文件上传问题
nginx.ingress.kubernetes.io/client-body-buffer-size: 64m
nginx.ingress.kubernetes.io/proxy-max-temp-file-size: 128m

# 解决: 上传文件较慢问题
nginx.ingress.kubernetes.io/proxy-buffer-size: 64m
nginx.ingress.kubernetes.io/proxy-buffering: "on"
nginx.ingress.kubernetes.io/proxy-buffers-number: "4"

# 解决: 504 网关超时即后端backend超时问题
nginx.ingress.kubernetes.io/proxy-connect-timeout: 60
nginx.ingress.kubernetes.io/proxy-read-timeout: 180s
nginx.ingress.kubernetes.io/proxy-send-timeout: 60s

# 解决: 处理Nginx代理转发与后端服务文件上传缓存区设置(原生命令)
nginx.ingress.kubernetes.io/server-snippet: |
location ~ /fastfile {
client_max_body_size 1024m; # 允许客户端请求的最大单文件字节数,若超过所设定的大小,返回413错误.人话:能上传多大文件
client_header_timeout 60; # 读取请求头的超时时间,若超过所设定的大小,返回408错误。
client_body_timeout 60; # 读取请求实体的超时时间,若超过所设定的大小,返回413错误。
client_body_buffer_size 10m; # 缓冲区代理缓冲用户端请求的最大字节数,人话:一次能接受多少文件,建议根据带宽上限设置,减少磁盘读写,加快速度
proxy_connect_timeout 60; # Nginx与后端代理连接超时时间,http请求无法立即被容器(tomcat, netty等)处理,被放在nginx的待处理池中等待被处理。
proxy_read_timeout 180; # 后端服务器响应时间(代理接收超时)时间,http请求被容器(tomcat, netty等)处理后,nginx会等待处理结果,也就是容器返回的response。
proxy_send_timeout 30; # http请求被服务器处理完后,把数据传返回给Nginx的用时,默认60秒。
proxy_buffer_size 1024k; # 设置代理服务器(nginx)保存用户头信息的缓冲区大小
proxy_buffers 6 500k; # proxy_buffers缓冲区,网页平均在32k以下的话>,这样设置
proxy_busy_buffers_size 1024k; # 高负荷下缓冲大小(proxy_buffers*2)
proxy_temp_file_write_size 1024k; # 设定缓存文件夹大小,大于这个值将从upstream服务器传输
}