刚他妈开完会,一帮产品经理和不懂技术的领导在那儿扯淡,什么用户体验,什么快速迭代,迭代你奶奶个腿!出问题还不是老子来擦屁股?就说昨天晚上,K8S 集群里的一个 Ingress,流量突然就断了,监控告警刷屏一样。查了半天,发现是 conntrack 表满了!
Conntrack 这玩意儿,说白了就是 Linux 内核用来跟踪 TCP 连接状态的。在 NAT 环境下,它尤其重要。因为 Ingress Controller 作为流量入口,通常会做 SNAT,把客户端 IP 转换成自己的 IP 去访问后端服务。没有 conntrack,内核就不知道哪个响应包该发给哪个客户端,整个连接就乱套了。
现在 K8S 动不动就上千个 Pod,每个 Pod 后面可能还有成百上千的连接。Conntrack 表默认大小就那么点儿,稍微有点并发就满了。更操蛋的是,现在这帮年轻人,一上来就搞什么 Service Mesh,Envoy 代理满天飞,每经过一个代理,就多一层 NAT,conntrack 的压力就更大。他们懂个屁!就知道抄概念,底层的玩意儿一概不关心。
好了,废话不多说,直接上排查步骤和解决方案。
一、排查步骤
1. 确认 conntrack 表是否满了:
bash
sysctl net.netfilter.nf_conntrack_count
sysctl net.netfilter.nf_conntrack_max
如果 nf_conntrack_count 接近或等于 nf_conntrack_max,那就是满了。
2. 查看 conntrack 的丢包情况:
bash
ss -s
看输出里的 TCP established、TCP orphaned、TCP timewait 等指标。如果 TCP timewait 数量异常高,说明 TIME_WAIT 状态的连接太多,占用了 conntrack 的资源。
3. 抓包分析:
bash
tcpdump -i any -n -nn -vvv -s 0 port 80 or port 443
抓包看看是不是有大量的 SYN 包,或者 RST 包。如果是 SYN 包,说明有大量的连接请求被拒绝;如果是 RST 包,说明连接被异常关闭。这两种情况都会导致 conntrack 资源快速消耗。
二、解决方案
1. 调整 conntrack 表大小:
bash
sysctl -w net.netfilter.nf_conntrack_max=262144
sysctl -w net.netfilter.nf_conntrack_tcp_timeout_established=3600
nf_conntrack_max 调大一点,但也不能太大,否则会占用大量的内存。nf_conntrack_tcp_timeout_established 可以适当调小,缩短 ESTABLISHED 状态连接的超时时间,释放 conntrack 资源。
注意: 这个只是临时生效,重启机器就没了。要永久生效,需要修改 /etc/sysctl.conf 文件,然后执行 sysctl -p。
net.netfilter.nf_conntrack_max = 262144
net.netfilter.nf_conntrack_tcp_timeout_established = 3600
2. 优化 TCP 连接:
* 启用 TCP Fast Open (TFO): 可以减少 TCP 连接建立的时间,降低 SYN 包的重传率。
bash
sysctl -w net.ipv4.tcp_fastopen=3
同样,要永久生效,需要在 /etc/sysctl.conf 文件中添加 net.ipv4.tcp_fastopen = 3。
* 调整 TIME_WAIT 状态的连接: TIME_WAIT 状态的连接过多,会占用大量的 conntrack 资源。可以通过以下参数进行调整:
bash
sysctl -w net.ipv4.tcp_tw_reuse=1
sysctl -w net.ipv4.tcp_tw_recycle=1
注意: tcp_tw_recycle 在 NAT 环境下可能会有问题,不建议开启。
3. 优化 Ingress Controller 配置:
* 合理设置健康检查: 频繁的健康检查会产生大量的连接,增加 conntrack 的压力。
* 使用连接池: Ingress Controller 可以使用连接池来复用 TCP 连接,减少连接建立和关闭的开销。
4. 使用 eBPF 进行 conntrack 优化 (高级玩法,慎用):
eBPF 允许你在内核中运行自定义代码,可以用来优化 conntrack 的行为。例如,你可以使用 eBPF 来快速删除不活跃的连接,或者根据特定的策略来选择是否跟踪某个连接。但这玩意儿调试起来很麻烦,一不小心就把内核搞崩了。
总结
Conntrack 满了,是个很常见的 K8S 问题。解决思路就是:要么增加 conntrack 表的大小,要么减少 conntrack 表的占用。但是,治标不治本。根本的解决方案还是要优化你的应用程序和网络架构,减少不必要的连接,提高连接的复用率。
现在的年轻人,总想着搞一些花里胡哨的东西,Service Mesh、Serverless,搞得越来越复杂。底层的这些东西,还是要好好学学,否则,出了问题,只能抓瞎。
TMD,说了这么多,还是解决不了这帮 SB 搞出来的烂摊子。老子要下班!
-
K8S Ingress 流量转发让我深挖一下 conntrack 这坨屎
-
深挖Containerd CRI的Socket死锁
早上开会,又听那帮产品经理在那儿吹逼,什么云原生、微服务,叨逼叨个没完,真想把他们的脑浆子抠出来看看是不是浆糊。一个个连DockerFile都不会写,还他妈云原生架构,原生个JB!
行,既然他们这么喜欢K8S,那我就来好好聊聊这玩意儿,特别是Containerd这块儿。别以为写几个YAML就能上天了,底层的东西,你们这帮小年轻根本没概念。
今天就说说Containerd CRI的Socket死锁。这玩意儿,说白了,就是Containerd在处理CRI请求的时候,Socket卡住了,导致Pod起不来,应用挂逼。别跟我说重启Containerd,重启能解决问题,还要我这老家伙干嘛?
这事儿的根源往往在于Containerd处理CRI请求的逻辑不够健壮,特别是在并发高,或者网络环境复杂的情况下,容易出现Socket资源竞争,最终导致死锁。具体现象就是`ctr task exec`、`kubectl exec`之类的命令卡住,Pod状态一直是`ContainerCreating`或者`CrashLoopBackOff`。
要排查这玩意儿,首先得看Containerd的日志。`/var/log/containerd/containerd.log`,瞪大你的狗眼,看看里面有没有类似下面的错误信息:
“`
ERRO[2024-10-27T10:00:00.000000000+08:00] failed to serve request error=”rpc error: code = Unavailable desc = connection error: desc = \”transport: Error while dialing: dial unix /run/containerd/containerd.sock: connect: connection refused\””
“`
这玩意儿,就是Containerd的Socket通信出了问题。要么是Containerd进程挂了,要么就是Socket连接被占满了。
接下来,得祭出神器`strace`。这玩意儿能追踪进程的系统调用,是排查底层问题的利器。
“`bash
strace -p $(pidof containerd) -s 2048 -f -o containerd.strace
“`
这条命令会跟踪Containerd进程的所有系统调用,并将结果输出到`containerd.strace`文件中。然后,用你那可怜的脑子分析这个文件。重点关注`accept`、`send`、`recv`、`close`等Socket相关的系统调用。看看有没有长时间阻塞在某个调用上的情况。
比如,你可能会发现大量的线程都阻塞在`accept`调用上,这意味着Containerd没有足够的线程来处理新的连接请求。这可能是由于线程池配置不合理,或者某些请求处理时间过长导致的。
解决这个问题,可以尝试以下几种方法:
1. **调整Containerd的配置**。在`/etc/containerd/config.toml`中,可以调整`grpc.max_concurrent_streams`参数,增加并发处理能力。
“`toml
[grpc]
max_concurrent_streams = 1000 # 默认是500,可以适当增加
“`
重启Containerd生效。
2. **检查网络环境**。确保Containerd和kubelet之间的网络通信正常。如果使用了网络插件,也要检查网络插件的配置是否正确。
3. **优化应用代码**。如果某些请求处理时间过长,可能是由于应用代码存在性能问题。需要对应用代码进行优化,减少请求处理时间。
还有一种情况,就是Socket文件被其他进程占用了。可以用`lsof`命令来查看:
“`bash
lsof /run/containerd/containerd.sock
“`
如果发现有其他进程占用了这个Socket文件,那就把那个进程干掉。
最后,如果以上方法都无效,那就只能祭出终极大法:**升级Containerd版本**。新版本通常会修复一些已知的Bug,并优化性能。
总之,解决Containerd CRI的Socket死锁问题,需要深入理解Containerd的底层原理,熟练掌握各种排查工具,以及具备丰富的实战经验。别指望百度一下就能解决问题,真正的技术,是靠积累和思考得来的。
TMD,写完这篇文章,感觉又老了十岁。这帮小年轻,啥时候才能真正理解底层技术的重要性?天天只会玩YAML,迟早要出大事! -
Docker容器中使用Nvidia GPU
在容器环境中使用物理机的Nvidia GPU,宿主机自身需要安装好显卡驱动,还要结合使用官方提供的Toolkit,创建新的容器运行时,并在启动容器时使用对应的运行时环境。以下是在Ubuntu 22.04系统中,docker容器使用物理机显卡的简单配置过程.
Bash
# 物理机上执行,检测显卡是否成功驱动nvidia-smi -L参考:https://docs.nvidia.com/datacenter/cloud-native/container-toolkit/latest/install-guide.html
- 配置APT仓库
Bashcurl -fsSL https://nvidia.github.io/libnvidia-container/gpgkey | sudo gpg --dearmor -o /usr/share/keyrings/nvidia-container-toolkit-keyring.gpg \ && curl -s -L https://nvidia.github.io/libnvidia-container/stable/deb/nvidia-container-toolkit.list | \ sed 's#deb https://#deb [signed-by=/usr/share/keyrings/nvidia-container-toolkit-keyring.gpg] https://#g' | \ sudo tee /etc/apt/sources.list.d/nvidia-container-toolkit.list sudo apt-get update- 安装软件包
Bashsudo apt-get install -y nvidia-container-toolkit- docker容器运行时配置
Bashsudo nvidia-ctk runtime configure --runtime=docker sudo systemctl restart docker- 测试
Bashdocker run -it --rm --gpus=all ubuntu:22.04 nvidia-smi -L
附:docker配置文件(/etc/docker/daemon.json)参考
执行nvidia-ctk runtime configure –runtime=docker时会往配置中文件中写入相应的runtime配置
JSON{ "default-ulimits": { "memlock": { "Hard": -1, "Name": "memlock", "Soft": -1 } }, "exec-opts": [ "native.cgroupdriver=cgroupfs" ], "runtimes": { "nvidia": { "path": "nvidia-container-runtime", "runtimeArgs": [] } }, "bridge": "none", "iptables": false, "live-restore": true }