需求背景
在业务稳定运行过程中,希望能通过修改临时配置文件控制业务的行为,要求实时生效。
实现方案
已知karaf框架下,/etc/xxx.cfg文件的配置都是支持实时修改和生效的,参考这种实现机制自定义一个cfg文件即可:
1 | package com.xxx.xxx; |
参考资料
https://github.com/apache/karaf/tree/karaf-4.2.16/examples/karaf-config-example
在业务稳定运行过程中,希望能通过修改临时配置文件控制业务的行为,要求实时生效。
已知karaf框架下,/etc/xxx.cfg文件的配置都是支持实时修改和生效的,参考这种实现机制自定义一个cfg文件即可:
1 | package com.xxx.xxx; |
https://github.com/apache/karaf/tree/karaf-4.2.16/examples/karaf-config-example
在Windows环境中做一些删除或移动文件或文件夹的操作时,有时候会出现如下报错:
1 | 操作无法完成,因为其中的文件夹或文件已在另一个程序中打开,请关闭该文件夹或文件,然后重试。 |
报错信息说的很清楚,是文件或文件夹被其他程序占用了,这个时候我们如何知道是哪个程序占用的呢?
今天分享一个微软官方提供的小工具Handle[1],在不需要重启电脑的情况下快速查到是什么进程正在占用文件。
下载Handle,参考资料[1]。
使用Handle,打开命令提示符,导航到Handle工具下载的目录。使用命令handle filename来识别占用该文件的进程。举例如下:
1 | handle C:\path\to\your\file.txt |
Handle会列出所有打开文件的句柄以及对应的进程ID(PID)和进程名称。找到了占用进程,就可以通过进程ID终止进程:
1 | # cmd命令行 |
https://learn.microsoft.com/en-us/sysinternals/downloads/handle
部署在麒麟V10SP03环境上的K8S集群,kube-proxy组件使用conntrack命令删除表项失败。
1 | [root@node1 ~]# docker run --privileged --net=host --rm kube-proxy conntrack -D -p icmp -s 192.168.1.2 |
考虑到问题场景是使用的docker镜像带的conntrack工具,为了排除docker相关影响,在宿主机上安装conntrack工具并执行命令:
1 | [root@node1 ~]# conntrack -D -p icmp -s 192.168.1.2 |
验证结果报错,再排除conntrack工具层面的影响,直接使用如下c++程序调用libnetfilter_conntrack库构造删除:
1 | #include <stdio.h> |
验证结果依然报错:
1 | [root@single ~]# ./conntrack_delete |
通过gdb查看,问题出在调用libnetfilter_conntrack.so库,初步判断跟内核有关。
1 | (gdb) b nfct_query |
之后查看了麒麟官网的内核版本列表,未发现相关bug,通过更新到最新内核4.19.90-52.39测试,问题依然存在。
回退到麒麟V10SP02的环境(内核版本是4.19.90-24.4),测试没有该问题。
基本确认,是SP03版本的内核引入的bug,提交issue后,官方计划在4.19.90-52.40内核版本里解决。
更新内核版本到4.19.90-52.40。
https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/base/x86_64/Packages/
https://update.cs2c.com.cn/NS/V10/V10SP3/os/adv/lic/updates/x86_64/Packages/
K8S集群内的一个Pod服务定时发起icmpv6的ping报文到集群外的设备,偶现请求无响应应。
抓包分析
先在节点上和Pod所在网卡上抓包,分析确认,请求包在节点上有响应,但未进入Pod内,初步判断请求是在节点发往Pod内的过程中丢了。
网卡是否丢包
查看集群上的网卡配置,环境使用的是bound网卡:
1 | [root@node1 ~]# ifconfig bound0 |
查看到RX的drop报文数量很大,且不断增加。考虑到早期出现过bound配置导致的丢包问题,排查相关配置:
1 | [root@node1 ~]# cat /sys/class/net/bondo/bonding/mode |
从配置看,Link Failure Count: 1,也就是出现过一次网卡链路问题,看起来跟持续的网络丢包关系不大,从dmesg日志也可以印证这一点。
1 | [root@node1 ~]# dmesg -T |grep bond |
继续看抓包结果:
1 | 节点上抓包结果: |
对比发现,Pod内是发了3个请求包,均没有响应,跟业务的请求配置2s超时 + 重试3次吻合。但从节点的角度看有4个请求和响应包,且从时间看,是从第2个包开始对应的。
由此怀疑,第1个请求响应包是别的服务触发的,跟现场了解到确实存在两个服务ping设备,且这个请求使用的id是0xef2,跟后面3个请求包的id是相同的,初步判断是id相同引起的后续包无法响应。
查icmpv6相关资料[1],没有明确的官方文档可以证明id相同的两个ping报文存在问题,从一些个人总结资料[2]里看,是不允许相同的。为了确认是否有问题,在家里使用跟业务类似的c++程序模拟ping设备,构造两个容器内使用相同的id请求集群外的ip地址,可以稳定复现该问题。而同时测试两个系统原生ping命令,未复现该问题。
分析两次复现的抓包结果,并查找相关资料[3],可以解释两种结果的差异原因:
1)为什么c++实现的ping有问题? –ping报文使用的id号的实现是顺序累加,当请求不通时,该程序会重试,且重试的报文依然使用相同的id号,这就导致一个id号冲突必然会导致重试也不通,直到下个轮询里使用新的id号探测恢复
2)为什么原生ping没有问题? –ping报文使用的id号是通过ping的进程id和一个十六进制的与计算得到的,每次独立的ping操作会使用一个计算得到的随机id作为icmp报文的id
id相同为什么会有问题呢?到底是哪里的机制影响了请求的响应?通过家里不断构造场景测试,问题原因和几个疑问的分析结论如下:
问题原因:icmp的id相同导致系统记录的conntrack表项无法区分出两个不同的响应包该回给谁,如下所示,2个ping请求,一个在节点上,一个在容器内,2个ping请求使用了相同的id,这会导致两个ping请求均匹配到第一条表项,进而导致容器内的ping请求得不到响应:
1 | [root@node1 ~]# cat /proc/net/nf_conntrack|grep icmpv6 |
问题1:为什么节点上两个ping使用同一个id没问题?
因为请求和响应都在节点上,记录的conntrack表项是同一条,且id也相同,所以即使请求和响应不对应,两个请求方都可以得到响应。
1 | ipv6 10 icmpv6 58 29 src=1000:0000:0000:0000:0000:0000:0212:0165 dst=1000:0000:0000:0000:0000:0000:0212:0160 type=128 code=0 id=14640 src=1000:0000:0000:0000:0000:0000:0212:0160 dst=1000:0000:0000:0000:0000:0000:0212:0165 type=129 code=0 id=0 mark=0 zone=0 use=2 |
问题2:为什么一个节点一个容器ping同一个id才有问题?
无必然关系,只要是出集群的请求 + nat转换 + ping id相同,都会存在这个问题,而nat转换是容器出集群依赖的必要机制。非容器场景如果使用了nat机制,理论上同样会出现这个问题。
问题3: 为什么使用不同的id发请求没问题?
在请求源ip,目标ip相同的情况下,不同的id请求会在conntrack表项会新增id不同的记录,请求的响应可以依据id做区分,并正常响应:
1 | [root@node1 ~]# cat /proc/net/nf_conntrack|grep icmpv6 |
问题4:使用相同id请求,异常会持续多久?
由表项老化时间决定,默认是30s。
id号。ping相同的设备,或规划使用不同的id号,避免冲突。https://datatracker.ietf.org/doc/html/rfc4443
https://community.icinga.com/t/how-to-avoid-icmp-identifiers-colliding/5290
K8S集群中有一个节点的docker stats命令查看不到资源使用:
1 | [root@node1 ~]# docker stats |
先看docker日志,存在大量如下异常:
1 | time="xxx" level=error msg="collecting stats for xxx:no metrics reveived" |
根据错误信息未找到相关问题,继续看这个命令的结果是从哪里读取的,根据资料[1]可知,该数据是从cgroup fs中计算得到。
随机找一个容器id,进入cgroup中看看相关指标文件是否正常:
1 | [root@node1 ~]# cat /sys/fs/cgroup/cpu,cpuacct/kubepods.slice/kubepods-burstable.slice/kubepods-burstable-podxxx/docker-xxx.scope/cpuacct.usage |
看起来没啥问题,查docker相关issue,未找到相关问题。
查containerd服务,发现一直在打印如下异常:
1 | /sys/fs/cgroup/cpuacct/kubepods.slice/besteffort.slice/podxxx/xxx/cpuacct.stat is expected to have 4 fields |
根据上面的错误信息,再次查看相关cgroup,看内容不像是4个字段:
1 | [root@node1 ~]# cat /sys/fs/cgroup/cpuacct/kubepods.slice/kubepods-besteffort.slice/podxxx/xxx/cpuacct.stat |
找一个正常节点,查看相关cgroup:
1 | [root@node1 ~]# cat /sys/fs/cgroup/cpuacct/kubepods.slice/kubepods-burstable.slice/podxxx/xxx/cpuacct.stat |
对比发现,问题环境里多了一个sched_delay字段,该字段表示由于调度延迟而导致的 CPU 时间延迟。查看到相关资料[2],此问题源自https://github.com/containerd/cgroups。 当尝试从文件/sys/fs/cgroup/cpuacct/cpuacct.stat检索字段时,会报告该错误。这个限制是不合理的,已在containerd/cgroups@5fe29ea中修复。
查看修改记录,containerd/cgroups的解决版本如下:
1 | v3.0.3 v3.0.2 v3.0.1 v3.0.0 v1.1.0 |
对应的containerd版本是从v1.7.0开始升级cgroup版本到v1.0.0,解决了该问题。
为什么只有一个节点存在该问题?
根据修复记录的说明,某些系统内核才会触发该问题。查看正常节点和异常节点的内核版本,发现异常节点的内核版本是4.14.0,而正常节点的内核版本是5.x。
1.升级containerd到v1.7.0及以上版本;
2.升级操作系统内核版本;
1.https://cloud.tencent.com/developer/article/1096453
找一个能查看etcd中存储的解码后的k8s数据的方法或工具。查看开源工具[1],很久没有维护了,看相关issue,该工具已经加入etcd-io。
根据官方文档[2]操作,下载源码包:
1 | [root@node1]# git clone git@github.com:etcd-io/auger.git |
编译版本:
1 | [root@node1]# cd auger/ |
提示go的版本不匹配,更新版本:
1 | [root@node1 auger]# vim Makefile |
继续编译:
1 | [root@node1 auger]# make release |
使用proxy.golang.org代理导致很多依赖包下载失败,修改GOPROXY代理
1 | [root@node1 ~]# docker exec -it 9b41dd00e91a sh |
继续编译:
1 | [root@node1 auger]# make release |
编译成功,执行二进制文件测试,提示glibc版本没找到:
1 | [root@node1 auger]# ./build/auger -help |
查看本地的glibc版本,发现版本不匹配:
1 | [root@node1 l14185]# rpm -qa|grep glibc |
解决方案有两个:
- 修改编译使用的镜像,找一个
glibc版本跟节点上一致的编译镜像;- 直接在节点上编译;
以直接在节点上编译为例,下载指定版本的go安装包,直接执行go build命令:
1 | [root@node1]# GOOS=linux GOARCH=amd64 go build -o build/auger |
查看帮助信息:
1 | [root@iZbp1esczkzr2k2fughijkZ auger]# ./build/auger |
查看解码后的etcd数据:
1 | [root@node1]# ETCDCTL_API=3 etcdctl get /registry/pods/kube-system/coredns-795cc9c45c-j7nl4 | ./auger decode |
直接使用
auger命令时,需要保证etcd服务未启动,或者把etcd的数据库文件拷贝一份再解析,否则会导致解析卡住。
解析卡住的strace命令现象如下:
1 | [root@node1]# strace ./auger checksum -f /var/lib/etcd/default.etcd/member/snap/db |
拷贝一份数据库文件,对比校验结果:
1 | [root@node1]# cp /var/lib/etcd/default.etcd/member/snap/db /root/etcd.db |
需要实时看到业务环境里的抓包结果,查看资料[1,2],了解到有两种配置方法:
wireshark的远程接口功能wireshrk的SSH remote capture功能1.安装依赖包
1 | yum install glibc-static |
2.下载rpcapd的源码包
下载跟wireshark版本相近的4.0.1-WpcapSrc.zip[3]
3.编译配置
1 | [root@node1 ~]# CFLAGS=-static ./configure |
根据报错信息,安装缺少的相关依赖包:
1 | yum install flex bison |
编译报错:
1 | [root@node1 libpcap]# make |
重新下载4.1.1-WpcapSrc.zip源码包,编译成功:
1 | [root@node1 libpcap]# make |
启动rpcapd服务
1 | [root@node1 rpcapd]# ./rpcapd -4 -n -p 2002 |
查看监听结果
1 | [root@node1 ~]# netstat -anp|grep -w 2002 |
启动Wireshark,在Wireshark的捕获->选项->管理接口->远程接口页面下新增主机和端口,提示错误“PCAP没有发现”,查看资料[4],需要下载npcap解决。
远程接口连接后,后台提示如下错误:
1 | [root@node1 rpcapd]# ./rpcapd -4 -n -p 2002 |
按照资料[4]的解决方法,Wireshark页面配置后提示超时,后台依然报错:
1 | [root@node1 rpcapd]# ./rpcapd -n -p 2002 |
考虑到该方法依赖rpcapd,且该依赖包需要在相同的环境下编译,暂不采用。
启动Wireshark,在Wireshark的捕获->选项->输入页面下找到SSH remote capture,点击左侧的设置图标,打开ssh登录设置。
在弹出页面上配置ssh的连接参数,包括服务器地址,端口,用户名,密码(也可以用证书)等等。
配置完成后,点击开始按钮,开始远程抓包。
1.https://zhuanlan.zhihu.com/p/551549544
2.https://blog.csdn.net/weixin_40991654/article/details/126779792
3.https://www.winpcap.org/archive/
4.https://blog.csdn.net/m0_37678467/article/details/127940287
由于工作需要,定位问题时可能需要访问redhat的知识库,参考资料[1],执行以下几步即可搞定:
第一步:登录 https://access.redhat.com/ 创建一个账号;
第二步:访问 https://developers.redhat.com/products/rhel/download 激活订阅(收到邮件并激活);
第三步:访问 https://access.redhat.com/management 确认一下我们的账号是否有 developer subscription:
1 | 14904535 Red Hat Developer Subscription for Individuals |
第四步:用注册的用户名密码,激活一个rhel系统:
1 | subscription-manager register --auto-attach --username ******** --password ******** |
第五步:访问https://access.redhat.com/solutions/6178422测试知识库是否能访问;
关于激活一个rhel系统的操作,为了快速方便,这里使用vagrant软件快速部署一个redhat8的操作系统。这个流程也仅需要以下几步:
1 | # 初始化Vagrantfile文件 |
https://wangzheng422.github.io/docker_env/notes/2022/2022.04.no-cost.rhel.sub.html
在K8S环境中,某个业务由于误操作重启了系统的dbus服务,导致所有的Pod启动失败,相关日志如下:
1 | unable to ensure pod container exists: failed to create container for [kubepods besteffort ...] : dbus: connection closed by user |
根据错误信息,查到相关issue[1],原因如下:
kubelet服务在创建Pod时会调用/var/run/dbus/system_bus_socket,如果dbus服务由于某些异常发生重启,/var/run/dbus/system_bus_socket这个文件就会被重新创建。此时,kubelet继续向旧的socket发送数据,就会出现上述的报错信息。
临时方案:重启kubelet服务
永久方案:升级K8S版本到v1.25+
后续问题
重启过dbus和kubelet服务后,出现非root用户ssh远程慢的现象。查看secure日志,发现如下错误:
1 | pam_systemd(crond:session): Failed to create session: Activation of org.freedesktop.login1 timed out |
查看资料[2],原因是ssh依赖systemd-logind服务,而该服务又依赖dbus服务,通过重启systemd-logind服务解决:
1 | [root@core log]# systemctl restart systemd-logind |
K8S集群所有节点之间网络异常,无法执行正常的SSH操作。
基于该现象,首先怀疑是使用的密码错误,先排查使用的密码和实际密码和是否一致,经确认业务存储的密码跟实际密码是一致的,排除密码不一致的问题;
再排查是不是有异常的ip使用错误密码连接:
这里使用的是
ipv6地址,需要注意,默认的netstat命令看到的ipv6地址是不全的,无法方便看出完整的ip地址,需要添加-W命令完整显示:
1 | [root@node1 ~]# netstat -anp -v|grep -w 22 |
完整ipv6地址的ssh连接如下:
1 | [root@node1 ~]# netstat -anp -W|grep -w 22 |grep -v ::4 |
从上面的记录看,至少当前没有异常ip做ssh连接,再确认一下是不是之前出现过错误密码导致密码被锁的情况;
查看/var/log/secure日志(日志已发生过轮转,无法确认出问题的初始时间点),查看系统最近没有发生过重启,继续看journal --boot里的登录失败日志,找到了出问题的时间点,并且可以看到源ip地址2000:8080:5a0a:2f47::2一直使用错误密码登录:
1 | cat boot.log |grep "Failed password"|less |
正常来说,使用错误密码登录失败后,密码被锁到指定时间后会自动解锁。但问题环境当前没有错误密码连接的情况下,使用正确密码依然无法连接。
临时注释/etc/pam.d/security-auth和/etc/pam.d/password-auth中auth相关的配置,验证ssh异常是否是密码锁配置导致:
1 | # auth required pam_tally2.so onerr=fail deny=5 unlock_time=900 even_deny_root |
修改完观察一段时间,ssh恢复正常,还原回去后,ssh又出现异常,基本确认是配置问题。从系统相关同事了解到,这里使用的密码锁定模块tally是个老模块,因为存在缺陷已经被废弃,其中一个问题就是:在使用错误密码被锁后,即使密码正确了,也无法解除锁定。建议使用faillock模块替代,配置方法如下:
1
2
3
4
5
6
7
8
9
10
11
12
13[root@node1 ~]# vim /etc/pam.d/system-auth 或者 vi /etc/pam.d/login
# 在文件开头增加如下内容:
auth [success=1 default=bad] pam_unix.so
auth [default=die] pam_faillock.so authfail deny=5 even_deny_root unlock_time=900 root_unlock_time=10
auth sufficient pam_faillock.so authsucc deny=5 even_deny_root unlock_time=900 root_unlock_time=10
auth required pam_deny.so
[root@node1 ~]# vim /etc/pam.d/password-auth 或者 vi /etc/pam.d/sshd
在文件第二行(第一行为 #%PAM-1.0 )增加如下内容:
auth [success=1 default=bad] pam_unix.so
auth [default=die] pam_faillock.so authfail deny=5 even_deny_root unlock_time=900 root_unlock_time=10
auth sufficient pam_faillock.so authsucc deny=5 even_deny_root unlock_time=900 root_unlock_time=10
auth required pam_deny.so
说明:
faillock模块远程登录、本地登录过程中,用户锁定均不会有任何提示,只会出现锁定期间即使密码输入正确也无法登录系统的现象,解锁后可正常登录。
至于为什么出现这个问题,最后了解到是客户那边的漏扫平台使用弱密码故意扫的,正常只会扫一次,不清楚为什么触发扫了多次。
锁密码的安全加固使用faillock模块替代老版本的tally模块。