需求背景
在业务稳定运行过程中,希望能通过修改临时配置文件控制业务的行为,要求实时生效。
实现方案
已知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
模块。