问题现象
我们的程序通过 podman exec 在容器中周期性执行命令,偶尔会出现命令进程退出后成为僵尸进程zombie,一直不被回收。查看进程关系,僵尸进程的父进程为conmon。此时conmon进程阻塞在poll() 系统调用,无法回收已退出的命令进程。
1 | [root@dev05 ~]# ps -ef |grep 1580889 |
Keep learning, keep living...
我们的程序通过 podman exec 在容器中周期性执行命令,偶尔会出现命令进程退出后成为僵尸进程zombie,一直不被回收。查看进程关系,僵尸进程的父进程为conmon。此时conmon进程阻塞在poll() 系统调用,无法回收已退出的命令进程。
1 | [root@dev05 ~]# ps -ef |grep 1580889 |
nginx以root启动时,如果配置文件中没有指定user指令,那么默认会设定user为nobody, 数字为65534。而如果使用非root用户启动,那么nginx则不处理用户,直接以当前用户启动worker进程。
nginx的启动过程中的函数ngx_init_cycle会调用函数ngx_create_paths创建临时文件目录。在创建目录后,也会对目录的属主进行处理。如果属主不同于user指令配置的用户,那么就会调用chown更改属主为user配置的用户。
在执行nginx -t时,nginx也会调用函数ngx_init_cycle, 也就是在执行nginx -t时也有可能更改临时目录的属主,从而导致在运行的nginx worker进程没有权限读取临时目录导致业务异常。
比如,以nginx用户启动nginx, 此时临时目录属主为nginx。
1 | nginx 273095 0.0 0.0 26788 384 ? Ss 10:11 0:00 nginx: master process ./sbin/nginx |
1 | [nginx@dev02 nginx]$ ls -l |
这些年总结过多次容器网络数据包路径相关的文章, 如:
但每次过段时间后,总会忘记细节。分析容器网络异常是否是由于某些基于netfilter的驱动影响时,总是要重新梳理。这次再从内核实现的角度来分析一次容器网络数据包的转发路径。
还是以外部访问bridge模式docker容器的场景进行分析。
外部主机访问docker容器的数据包到达网卡后, 由内核函数:netif_receive_skb处理进入协议栈处理。对于IP数据包,会调用到函数ip_rcv:
1 | return NF_HOOK(NFPROTO_IPV4, NF_INET_PRE_ROUTING, NULL, skb, |
在这里会进行netfilter的PRE_ROUTING阶段处理。入包在PRE_ROUTING阶段会由docker的iptables规则完成DNAT操作,数据包目的IP变更为docker容器的IP。
crash是一个用于分析Linux内核转储文件(vmcore)的工具。正在运行的内核安装上debuginfo包之后,直接运行crash也可以直接分析运行中的内核,这对于分析一些内核问题极为有用。
在我们的某个场景中,需要分析NFQUEUE队列中的数据包内容。队列在内核中的表示是结构体nfqnl_instance,数据包sk_buff通过nf_queue_entry结构链在队列nfqnl_instance中。
要找到对应的nfqnl_instance,需要执行多条crash命令:
之前的文章<<NFQUEUE机制导致DNS请求5秒超时分析>>分析过在3.10.0-1015.el7版本之前的内核上,conntrack模块在插入条目时存在竞争条件,使用NFQUEUE机制导致AAAA请求包被丢弃,从而导致DNS请求出现5秒超时的现象。当时给的解决方案是可以在/etc/resolv.conf中添加options single-request-reopen来规避。
最近又遇到这个问题,但该规避方案并没有生效,因而做了进一步分析,发现glibc的DNS行为在不同响应上有所不同。
在没有NFQUEUE机制也没有开启single-request-reopen的环境中,访问正常解析的域名, 如:
1 | [root@dev07 ~]# time curl -s www.baidu.com -o /dev/null |
抓包结果为:
1 | 16:29:40.344792 IP 10.10.0.7.34972 > 10.10.0.2.53: 20510+ A? www.baidu.com. (31) |
可以看到resolver同时发出A和AAAA两个请求。
多年前写过介绍nginx限制连接模块的文章<<ngx_http_limit_conn_module模块分析>>, 最近业务中用到limit_conn指令限制请求,重新了解了一下它的用法。
根据nginx文档, 可以理解主要逻辑是根据limit_conn_zone所指定的key值计算连接数,当连接数超过limit_conn所指定的值时,则返回错误码。
由于限制值是在limit_conn指令而不是在limit_conn_zone指令中设置的,而limit_conn是可以配置多次的,当配置多次相同zone的limit_conn指令并且限制值不同,那么生效的是哪个限制值呢?
如果在同一个location中配置多个相同zone的limit_conn指令,示例配置为:
1 | location /a { |
我们的oVirt虚拟化平台上有一个需求,需要对同一网桥上的虚拟机之间进行网络隔离。
参考Docker实现中对于不同网桥的网络隔离,可以简单的采用iptables规则来实现。
Docker在iptables的filter表的FORWARD链的规则如下:
1 | [root@localhost ~]# iptables -nL -v |
Linux内核中conntrack模块使用哈希表来存储连接跟踪条目,当哈希表条目达到上限时,系统会将新分配conntrack条目的数据包DROP掉,从而导致网络受到影响。此时,日志中会记录:
1 | nf_conntrack: table full, dropping packet |
哈希表条目上限由参数net.netfilter.nf_conntrack_max设置。
网上文章对这个问题的解决方法往往是调大该参数。但在涉及多个network namespace的场景下,不能简单的这样做,还是要根据自身场景分析清楚具体原因。
根据CentOS7 3.10.0-957版本内核源码,实际上每个network namespace的conntrack哈希表是独立的。在表示network namespace的结构体net中的成员ct表示conntrack相关信息:
1 | struct net { |
netns_ct结构中保存有独立的哈希表相关信息:
1 | struct netns_ct { |
Linux内核提供了sysctl机制用于动态配置内核及内核模块的参数, 每个参数对应/proc/sys/下的一个文件,可以通过sysctl命令或直接操作/proc/sys/下的文件对参数进行读写。比如,net.ipv4.ip_forward对应文件/proc/sys/net/ipv4/ip_forward。sysctl命令实际是是对/proc/sys/操作的封装。
如果需要将sysctl参数持久化,可以将参数写入文件/etc/sysctl.conf文件中,这样参数在系统重启后依然生效。这是如何实现的呢?实际是由systemd-sysctl(或其他类似功能的服务)在系统完成内核模块的加载后,再来加载/etc/sysctl.conf里的参数。
如果需要立即生效/etc/sysctl.conf中的参数,可以执行sysctl -p。但如果此时内核模块未并加载,由于/proc/sys/目录下并不存在对应的参数文件,因而执行会失败。尽管这种场景下sysctl -p执行失败,但通过modprobe命令加载内核模块完成后,查看对应的sysctl参数,却发现sysctl参数已经生效。那这种场景下是如何令/etc/sysctl.conf中的参数生效的呢?
近期遇到一个C++程序退出的问题,经过调查发现,程序是由于接收到SIGPIPE信号而退出。该程序是多线程程序,使用libcurl进行HTTPS访问,同时设置了CURLOPT_NOSIGNAL选项,但没有自己处理SIGPIPE信号。在一些情况下,连接已经处理关闭状态,但应用程序不知道,依然向连接发送数据,就会导致SIGPIPE信号产生,进而导致程序退出。修复方法比较简单,只要应用程序设置SIGPIPE信号的处理程序即可。
上述问题要发生的一个前提是CURLOPT_NOSIGNAL选项需要被设置,什么情况下需要设置它呢?libcurl的作者的博文上写到过这个问题:
1 | 12. Understanding CURLOPT_NOSIGNAL |