Debug Nginx-ingress-controller

背景

使用nginx-ingress遇到backends不更新的问题,在修改svc的 targetPort之后nginx-ingress的backends不能正常更新成新的targetPort。

关于endpointslices,是k8s 1.16 引入的新feature , 目的就是当客户端去get一个很大的endpoint的list的时候可能存在的卡顿,解析资源比较大引发的问题。 所以社区引入endpointslices 资源, 把大的endpoint 分片成很多100个端点块的资源,通过kcm的 --max-endpoints-per-slice 参数调整块大小。

新feature是有很多的问题的,比如kube-proxy 是在1.20之后再引入去获取 endpointslice,看社区出现过一个在使用ExternalName 导致的bug, 在1.23之后的k8s版本中修复了, 类似issue: https://github.com/kubernetes/kubernetes/issues/105986

nginx-ingress-controller 看是在 v1.9.0 之后引入使用的 endpointslices
https://github.com/kubernetes/ingress-nginx/commits/controller-v1.9.0/internal/ingress/controller/endpointslices.go

https://kubernetes.io/zh-cn/docs/concepts/services-networking/endpoint-slices/
https://github.com/kubernetes/kubernetes/commit/75f6c249235b40b24e9ea1efdb1ff81dd76a8d68
https://kubernetes.io/docs/reference/command-line-tools-reference/kube-controller-manager/

环境

集群版本 nginx-ingress的版本
1.22.15 v1.10.4

复现步骤

调整nginx-ingress-controller的日志级别为5 --v=5

1
2
3
4
5
6
containers:
- args:
- /nginx-ingress-controller
- '--ingress-class=nginx'
- '--v=5'
- ....

检查现有状态

1)查看svc的的端口

1
$ kubectl get svc nginx-svc -o yaml

2)查看原来 backends

1
$ kubectl exec deployment/nginx-ingress-controller -n kube-system  -- /dbg backends get default-nginx-svc-8080

3)先打开nginx-ingress-controller日志跟踪

1
$ kubectl logs  -n kube-system  nginx-ingress-controller-xxx -f |  grep -E "socket.go|controller.go|main.go|queue.go|endpointslices.go|nginx.go|reflector.go"

只修改targetPort从11111 为 2222

1
$ kubectl edit svc nginx-svc

问题复现

1)查看svc的的端口

  • svc已经正常修改为2222了

2)查看原来 backends

  • 可以看到backends 的targetPort还是原来的 1111

3)查看日志输出 nginx-ingress-controller日志跟踪

  • 可以看到 endpointslices.go:166 , 还是获取的之前的targetPort为 1111 的endpointslices 资源,估计这个就是没有更新的原因

初步原因:

那就先查一下审计吧~

  • nginx-ingress去watch的时间: 15:32:56:218090
  • 更新endpointslices/nginx-svc-r89mv的时间: 15:32:56.221695

破案了啊, 果然nginx-ingress的watch到的时间, 比kcm更新endpointslices 时间早了 221695 - 218090 = 3605us , 根因 找到了呀~

那么问题来了,为啥是偶发的 在debug日志看一下

  • 和后端服务的endpoint数量有关系
  • 延迟这个东西说不准,有时候快有时候慢的

那应该就是kcm 去更新 endpointslices 的时候 和 nginx-ingress watch到的这个events 之间有延迟,所以就需要去看看代码了~

怎么解决

社区问题,还是先找社区看看是否有类似的问题。给社区提交issue : https://github.com/kubernetes/ingress-nginx/issues/11863
哈哈哈, 被remove bug 了~ ,可是这个已经很bug了。

可恶, 对于issue社区应发的思考

  • 对于提交问题的需要详尽的描述问题, 可是应该已经很详细了
  • 社区成员没有义务,需要按他提供的方式去提供信息,要么就放弃吧

求仙问卜 不如源码得知!!!

1
$ git clone https://github.com/kubernetes/ingress-nginx.git

.ingress-nginx/internal/ingress/controller/endpointslices.go

看就是watch到 endpointSlices更新之后,就去loop最新的endpointSlices 资源, 我感觉加一个sleep就可以,延迟个 200ms,再去get endpointSlices

编译后结果, 已经能正常更新了!!!

给社区提交一个PR,万一merge了呢~
https://github.com/kubernetes/ingress-nginx/pull/11896

拓展

如何编译nginx-ingress-controller呢。

准备

1、找一台中国香港的ECS吧。 内地不行,不要问为什么~

2、安装docker-ce最新版本环境

1
$ sudo curl -fsSL https://get.docker.com | bash -s docker --mirror Aliyun

3、安装git

1
$ yum -y install git

4、go环境安装

1
2
3
4
$ wget https://go.dev/dl/go1.23.2.linux-amd64.tar.gz
$ rm -rf /usr/local/go && tar -C /usr/local -xzf go1.23.2.linux-amd64.tar.gz
$ echo "export PATH=$PATH:/usr/local/go/bin" >> /etc/profile
$ source /etc/profile

编译

1、设置环境变量

1
$ export DOCKER_IN_DOCKER_ENABLED=true

2、克隆源代码

1
2
$ git clone https://github.com/kubernetes/ingress-nginx.git
$ cd ingress-nginx

3、切换分支(必须)

1
2
3
4
5
6
7
8
9
10
11
$ git branch -a
* main
remotes/origin/HEAD -> origin/main
remotes/origin/feature-go-crossplane
remotes/origin/gateway-api
remotes/origin/gh-pages
remotes/origin/legacy
remotes/origin/main
remotes/origin/release-1.10 # 案例目标分支
remotes/origin/release-1.11
$ git checkout remotes/origin/release-1.10

4、修改代码, 参考上文

5、更新 go.mod 文件

1
$ go mod tidy

6、编译

1
$ make build

7、打镜像

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
$ make image

removing old image gcr.io/k8s-staging-ingress-nginx/controller:v1.10.4
Untagged: gcr.io/k8s-staging-ingress-nginx/controller:v1.10.4
Building docker image (amd64)...
[+] Building 7.5s (15/15) FINISHED docker:default
=> [internal] load build definition from Dockerfile 0.0s
=> => transferring dockerfile: 3.20kB 0.0s
=> [internal] load metadata for registry.k8s.io/ingress-nginx/nginx-1.25:v0.0.12@sha256:2d471b3a34dc43d10c3f3d7f2a6e8a2ecf7654a4197e56374261c1c708b16365 0.2s
=> [internal] load .dockerignore 0.0s
=> => transferring context: 65B 0.0s
=> [ 1/10] FROM registry.k8s.io/ingress-nginx/nginx-1.25:v0.0.12@sha256:2d471b3a34dc43d10c3f3d7f2a6e8a2ecf7654a4197e56374261c1c708b16365 0.0s
=> [internal] load build context 0.6s
=> => transferring context: 60.78MB 0.6s
=> CACHED [ 2/10] WORKDIR /etc/nginx 0.0s
=> [ 3/10] RUN apk update && apk upgrade && apk add --no-cache diffutils && rm -rf /var/cache/apk/* 3.6s
=> [ 4/10] COPY --chown=www-data:www-data etc /etc 0.2s
=> [ 5/10] COPY --chown=www-data:www-data bin/amd64/dbg / 0.2s
=> [ 6/10] COPY --chown=www-data:www-data bin/amd64/nginx-ingress-controller / 0.3s
=> [ 7/10] COPY --chown=www-data:www-data bin/amd64/wait-shutdown / 0.1s
=> [ 8/10] RUN bash -xeu -c ' writeDirs=( /etc/ingress-controller/ssl /etc/ingress-controller/auth /etc/ingress-controller/geoip /etc/ingress-controller/tele 0.4s
=> [ 9/10] RUN apk add --no-cache libcap && setcap cap_net_bind_service=+ep /nginx-ingress-controller && setcap -v cap_net_bind_service=+ep /nginx-ingress-controller 1.5s
=> [10/10] RUN ln -sf /dev/stdout /var/log/nginx/access.log && ln -sf /dev/stderr /var/log/nginx/error.log 0.4s
=> exporting to image 0.5s
=> => exporting layers 0.5s
=> => writing image sha256:29cf5397c07c97a35cef1ed37b2124cf7bd721bb2dedcbd61d97ac283a7d23a1 0.0s
=> => naming to gcr.io/k8s-staging-ingress-nginx/controller:v1.10.4

8、推镜像

1
2
3
$ docker tag gcr.io/k8s-staging-ingress-nginx/controller:v1.10.4 registry.cn-hangzhou.aliyuncs.com/zmquan/nginx:v1.10.4

$ docker push registry.cn-hangzhou.aliyuncs.com/zmquan/nginx:v1.10.4

编译: 参考这个大佬的文档