plantegg

java tcp mysql performance network docker Linux

nslookup 域名结果正确,但是 ping 域名 返回 unknown host

2018-02 update : 最根本的原因 https://access.redhat.com/solutions/1426263

下面让我们来看看这个问题的定位过程

先Google一下: nslookup ok but ping fail, 这个关键词居然被Google自动提示了,看来碰到这个问题同学的好多

Google到的帖子大概有如下原因:

  • 域名最后没有加 . 然后被自动追加了 tbsite.net aliyun.com alidc.net,自然 ping不到了
  • /etc/resolv.conf 配置的nameserver 要保证都是正常服务的
  • /etc/nsswitch.conf 中的这行:hosts: files dns 配置成了 hosts: files mdns dns,而server不支持mdns
  • 域名是单标签的(domain 单标签; domain.com 多标签),单标签在windows下走的NBNS而不是DNS协议

检查完我的环境不是上面描述的情况,比较悲催,居然碰到了一个Google不到的问题

抓包看为什么解析不了

DNS协议是典型的UDP应用,一来一回就搞定了查询,效率比TCP三次握手要高多了,DNS Server也支持TCP,不过一般不用TCP

sudo tcpdump -i eth0 udp and port 53 

抓包发现ping 不通域名的时候都是把域名丢到了 /etc/resolv.conf 中的第二台nameserver,或者根本没有发送 dns查询。

这里要多解释一下我们的环境, /etc/resolv.conf 配置了2台 nameserver,第一台负责解析内部域名,另外一台负责解析其它域名,如果内部域名的解析请求丢到了第二台上自然会解析不到。

所以这个问题的根本原因是挑选的nameserver 不对,按照 /etc/resolv.conf 的逻辑都是使用第一个nameserver,失败后才使用第二、第三个备用nameserver。

比较奇怪,出问题的都是新申请到的一批ECS,仔细对比了一下正常的机器,发现有问题的 ECS /etc/resolv.conf 中放了一个词rotate,赶紧查了一下rotate的作用(轮询多个nameserver),然后把rotate去掉果然就好了。

风波再起

本来以为问题彻底解决了,结果还是有一台机器ping仍然是unknow host,眼睛都看瞎了没发现啥问题,抓包发现总是把dns请求交给第二个nameserver,或者根本不发送dns请求,这就有意思了,跟我们理解的不太一样。

看着像有cache之类的,于是在正常和不正常的机器上使用 strace ,果然发现了点不一样的东西:

image.png

ping的过程中访问了 nscd(name service cache daemon) 同时发现 nscd返回值图中红框的 0,跟正常机器比较发现正常机器红框中是 -1,于是检查 /var/run/nscd/ 下面的东西,kill 掉 nscd进程,然后删掉这个文件夹,再ping,一切都正常了。

从strace来看所有的ping都会尝试看看 nscd 是否在运行,在的话找nscd要域名解析结果,如果nscd没有运行,那么再找 /etc/resolv.conf中的nameserver做域名解析

而nslookup和dig这样的命令就不会尝试找nscd,所以没有这个问题。

如下文字摘自网络:

NSCD(name service cache daemon)是GLIBC关于网络库的一个组件,服务基于glibc开发的各类网络服务,基本上来讲我们能见到的一些编程语言和开发框架最终均会调用到glibc的网络解析的函数(如GETHOSTBYNAME or GETHOSTBYADDR等),因此绝大部分程序能够使用NSCD提供的缓存服务。当然了如果是应用端自己用socker编写了一个网络client就无法使用NSCD提供的缓存服务,比如DNS领域常见的dig命令不会使用NSCD提供的缓存,而作为对比ping得到的DNS解析结果将使用NSCD提供的缓存

connect函数返回值的说明:

RETURN VALUE
   If  the  connection or binding succeeds, zero is returned.  On error, -1 is returned,and errno is set appropriately.

Windows下客户端是默认有dns cache的,但是Linux Client上默认没有dns cache,DNS Server上是有cache的,所以忽视了这个问题。这个nscd是之前看ping不通,google到这么一个命令,但是应该没有搞明白它的作用,就执行了一个网上的命令,把nscd拉起来,然后ping 因为rotate的问题,还是不通,同时nscd cache了这个不通的结果,导致了新的问题

域名解析流程(或者说 glibc的 gethostbyname() 函数流程–背后是NameServiceSwitch)

  • DNS域名解析的时候先根据 /etc/nsswitch.conf 配置的顺序进行dns解析(name service switch),一般是这样配置:hosts: files dns 【files代表 /etc/hosts ; dns 代表 /etc/resolv.conf】(ping是这个流程,但是nslookup和dig不是)
  • 如果本地有DNS Client Cache,先走Cache查询,所以有时候看不到DNS网络包。Linux下nscd可以做这个cache,Windows下有 ipconfig /displaydns ipconfig /flushdns
  • 如果 /etc/resolv.conf 中配置了多个nameserver,默认使用第一个,只有第一个失败【如53端口不响应、查不到域名后再用后面的nameserver顶上】
  • 如果 /etc/resolv.conf 中配置了rotate,那么多个nameserver轮流使用. 但是因为glibc库的原因用了rotate 会触发nameserver排序的时候第二个总是排在第一位

nslookup和dig程序是bind程序包所带的工具,专门用来检测DNS Server的,实现上更简单,就一个目的,给DNS Server发DNS解析请求,没有调gethostbyname()函数,也就不遵循上述流程,而是直接到 /etc/resolv.conf 取第一个nameserver当dns server进行解析

glibc函数

glibc 的解析器(revolver code) 提供了下面两个函数实现名称到 ip 地址的解析, gethostbyname 函数以同步阻塞的方式提供服务, 没有超时等选项, 仅提供 IPv4 的解析. getaddrinfo 则没有这些限制, 同时支持 IPv4, IPv6, 也支持 IPv4 到 IPv6 的映射选项. 包含 Linux 在内的很多系统都已废弃 gethostbyname 函数, 使用 getaddrinfo 函数代替. 不过从现实的情况来看, 还是有很多程序或网络库使用 gethostbyname 进行服务.

备注:
线上开启 nscd 前, 建议做好程序的测试, nscd 仅支持通过 glibc, c 标准机制运行的程序, 没有基于 glibc 运行的程序可能不支持 nscd. 另外一些 go, perl 等编程语言网络库的解析函数是单独实现的, 不会走 nscd 的 socket, 这种情况下程序可以进行名称解析, 但不会使用 nscd 缓存. 不过我们在测试环境中使用go, java 的常规网络库都可以正常连接 nscd 的 socket 进行请求; perl 语言使用 Net::DNS 模块, 不会使用 nscd 缓存; python 语言使用 python-dns 模块, 不会使用 nscd 缓存. python 和 perl 不使用模块的时候进行解析还是遵循上述的过程, 同时使用 nscd 缓存.

下面是glibc中对rotate的处理:

这是glibc 2.2.5(2010年的版本),如果有rotate逻辑就是把第一个nameserver总是丢到最后一个去(为了均衡nameserver的负载,保护第一个nameserver):

image.png

在2017年这个代码逻辑终于改了,不过还不是默认用第一个,而是随机取一个,rotate搞成random了,这样更不好排查问题了

image.png

image.png

也就是2010年之前的glibc版本在rotate模式下都是把第一个nameserver默认挪到最后一个(为了保护第一个nameserver),这样rotate模式下默认第一个nameserver总是/etc/resolov.conf配置文件中的第二个,到2017年改掉了这个问题,不过改成随机取nameserver, 作者不认为这是一个bug,他觉得配置rotate就是要平衡多个nameserver的性能,所以random最公平,因为大多程序都是查一次域名缓存好久,不随机轮询的话第一个nameserver压力太大

参考 glibc bug

Linux内核与glibc

getaddrinfo() is a newer function that was introduced to replace gethostbyname() and provides a more flexible and robust way of resolving hostnames. getaddrinfo() can resolve both IPv4 and IPv6 addresses, and it supports more complex name resolution scenarios, such as service name resolution and name resolution with specific protocol families.

linux主要是基于glibc,分析glibc源码(V2.17)getaddrinfo 逻辑如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// search1、search2等是resolv.conf中配置的search域,会根据resolv.conf中ndots配置和qname的级数来决定
// 先查加search域的域名还是后查加search域的域名,除没有配置nameserver外,任何错误都会重试下一个
foreach domain(qname、qname.search1、qname.search2...)
foreach retry // resolv.conf中配置的retry数目
foreach ns // resolv.conf中配置的nameserver,最多有效的只有前三个,增加rotate 的option后,会从
//nameserver中(最多前三个)随机选择一个进行请求
foreach qtype(A、AAAA)
// 根据请求显试指定和系统是双栈还是单栈v4、or单栈v6来决定,如果两个
// 类型的话会同时并发请求,不会等另外一个返回。如果在resov.conf中指定
// single-request-reopen,则会每个类型请求新建一个链接,否则复用同一个
foreach (without_edns0, with_edns0 )
// 注意:默认不支持edns0,如果要支持的话需要设置宏RES_USE_EDNS0,并重编译
if (timeout || rcode == servfail
|| rcode == notimp || rcode == refuse)
// timeout时间第一次为resolv.conf中配置,后面的依据下面公式
//计算:(timeout<<index_of_dns_server)/num_of_dns_server
conftinue;

还有一种情况,开了easyconnect这样的vpn软件,同时又配置了自己的dns server

这个时候ping 某个自己的dns server中才有的域名是没法解析到的,即使你配置了自己的dns server,如果这个时候你通过 nslookup 自己的域名, 自己的dns-server-ip 确实是能够解析到的。但是你只是 nslookup 自己的域名 就不行,明显可以看到这个时候nslookup把域名发给了127.0.0.1:53来解析,而这个端口正是easyconnect这个软件在监听,你也可以理解easyconnect这样的软件的工作方式就是必须要挟持你的dns解析,可以理解的是这个时候nslookup肯定解析不到你的域名(只把dns解析丢给第一个nameserver–127.0.0.1),但是不能理解的是还是ping不通域名,正常ping的逻辑丢给第一个127.0.0.1解析不到域名的话,会丢给第二个dns-server继续尝试解析,但是这里的easyconnect没有进行第二次尝试,这也许是它实现上没有考虑到或者故意这样实现的。

其他情况

resolv.conf中search只能支持最多6个后缀(代码中写死了): This cannot be modified for RHEL 6.x and below and is resolved in RHEL7 glibc package versions at or exceeding glibc-2.17-222.el7.

nameserver:指定nameserver,必须配置,每行指定一个nameserver,最多只能生效3行

总结

  • /etc/resolv.conf rotate参数的关键作用
  • nscd对域名解析的cache
  • nslookup背后执行原理和ping不一样(前者不调glibc的gethostbyname() 函数), nslookup不会检查 /etc/hosts、/etc/nsswitch.conf, 而是直接从 /etc/resolv.conf 中取nameserver; 但是ping或者我们在程序一般最终都是通过调glibc的gethostbyname() 函数对域名进行解析的,也就是按照 /etc/nsswitch.conf 指示的来
  • 在没有源代码的情况下strace和抓包能够看到问题的本质
  • 根因: https://access.redhat.com/solutions/1426263
  • resolv.conf中最多只能使用前六个搜索域

下一篇介绍《在公司网下,我的windows7笔记本的wifi总是报dns域名异常无法上网(通过IP地址可以上网)》困扰了我两年,最近换了新笔记本还是有这个问题才痛下决心咬牙解决

参考资料

https://superuser.com/questions/495759/why-is-ping-unable-to-resolve-a-name-when-nslookup-works-fine

https://stackoverflow.com/questions/330395/dns-problem-nslookup-works-ping-doesnt

来自redhat上原因的描述,但是从代码的原作者的描述来看,他认为rotate下这个行为是合理的

Linux 系统如何处理名称解析

网络丢包

查看网卡是否丢包,一般是ring buffer太小

ethtool -S eth0 | grep rx_ | grep errors

当驱动处理速度跟不上网卡收包速度时,驱动来不及分配缓冲区,NIC接收到的数据包无法及时写到sk_buffer(由网卡驱动直接在内核中分配的内存,并存放数据包,供内核软中断的时候读取),就会产生堆积,当NIC内部缓冲区写满后,就会丢弃部分数据,引起丢包。这部分丢包为rx_fifo_errors,在 /proc/net/dev中体现为fifo字段增长,在ifconfig中体现为overruns指标增长。

查看ring buffer的大小设置

ethtool ‐g eth0  

Socket buffer太小导致的丢包(一般不多见)

内核收到包后,会给对应的socket,每个socket会有 sk_rmem_alloc/sk_wmem_alloc/sk_omem_alloc、sk_rcvbuf(bytes)来存放包

When sk_rmem_alloc >
sk_rcvbuf the TCP stack will call a routine which “collapses” the receive queue

查看collapses:

netstat -sn | egrep "prune|collap"; sleep 30; netstat -sn | egrep "prune|collap"
17671 packets pruned from receive queue because of socket buffer overrun
18671 packets pruned from receive queue because of socket buffer overrun

测试发现在小包情况下,这两个值相对会增大且比较快。增大 net.ipv4.tcp_rmem 和 net.core.rmem_max、net.core.rmem_default 后没什么效果 – 需要进一步验证

net.core.netdev_budget

sysctl net.core.netdev_budget //默认300, The default value of the budget is 300. This will
cause the SoftIRQ process to drain 300 messages from the NIC before getting off the CPU
如果 /proc/net/softnet_stat 第三列一直在增加的话需要,表示SoftIRQ 获取的CPU时间太短,来不及处理足够多的网络包,那么需要增大这个值
net/core/dev.c->net_rx_action 函数中会按netdev_budget 执行softirq,budget每次执行都要减少,一直到没有了,就退出softirq

一般默认软中断只绑定在CPU0上,如果包的数量巨大的话会导致 CPU0利用率 100%(主要是si),这个时候可以检查文件 /proc/net/softnet_stat 的第三列 或者 RX overruns 是否在持续增大

net.core.netdev_max_backlog

enqueue_to_backlog函数中,会对CPU的softnet_data 实例中的接收队列(input_pkt_queue)进行判断,如果队列中的数据长度超过netdev_max_backlog ,那么数据包将直接丢弃,这就产生了丢包。

参数net.core.netdev_max_backlog指定的,默认大小是 1000。

netdev_max_backlog 接收包队列(网卡收到还没有进行协议的处理队列),每个cpu core一个队列,如果/proc/net/softnet_stat第二列增加就表示这个队列溢出了,需要改大。

/proc/net/softnet_stat:(第一列和第三列的关系?)
The 1st column is the number of frames received by the interrupt handler. (第一列是中断处理程序接收的帧数)
The 2nd column is the number of frames dropped due to netdev_max_backlog being exceeded. netdev_max_backlog
The 3rd column is the number of times ksoftirqd ran out of netdev_budget or CPU time when there was still work to be done net.core.netdev_budget

rp_filter

https://www.yuque.com/plantegg/weyi1s/uc7a5g

关于ifconfig的种种解释

  • RX errors: 表示总的收包的错误数量,这包括 too-long-frames 错误,Ring Buffer 溢出错误,crc 校验错误,帧同步错误,fifo overruns 以及 missed pkg 等等。
  • RX dropped: 表示数据包已经进入了 Ring Buffer,但是由于内存不够等系统原因,导致在拷贝到内存的过程中被丢弃。
  • RX overruns: 表示了 fifo 的 overruns,这是由于 Ring Buffer(aka Driver Queue) 传输的 IO 大于 kernel 能够处理的 IO 导致的,而 Ring Buffer 则是指在发起 IRQ 请求之前的那块 buffer。很明显,overruns 的增大意味着数据包没到 Ring Buffer 就被网卡物理层给丢弃了,而 CPU 无法及时地处理中断是造成 Ring Buffer 满的原因之一,上面那台有问题的机器就是因为 interruprs 分布的不均匀(都压在 core0),没有做 affinity 而造成的丢包。
  • RX frame: 表示 misaligned 的 frames。

dropped数量持续增加,建议增大Ring Buffer ,使用ethtool ‐G 进行设置。

txqueuelen:1000 对应着qdisc队列的长度(发送队列和网卡关联着)

而对应的接收队列由内核参数来设置:

net.core.netdev_max_backlog

Adapter buffer defaults are commonly set to a smaller size than the maximum//网卡进出队列大小调整 ethtool -G eth rx 8192 tx 8192

image.png

核心流程

image.png

接收数据包是一个复杂的过程,涉及很多底层的技术细节,但大致需要以下几个步骤:

  1. 网卡收到数据包。
  2. 将数据包从网卡硬件缓存转移到服务器内存中。
  3. 通知内核处理。
  4. 经过TCP/IP协议逐层处理。
  5. 应用程序通过read()从socket buffer读取数据。

通过 dropwatch来查看丢包点

dropwatch -l kas (-l 加载符号表) // 丢包点位置等于 ip_rcv地址+ cf(偏移量)

image.png

一个典型的接收包调用堆栈:

 0xffffffff8157af10 : tcp_may_send_now+0x0/0x160 [kernel]
 0xffffffff815765f8 : tcp_fastretrans_alert+0x868/0xb50 [kernel]
 0xffffffff8157729d : tcp_ack+0x8bd/0x12c0 [kernel]
 0xffffffff81578295 : tcp_rcv_established+0x1d5/0x750 [kernel]
 0xffffffff81582bca : tcp_v4_do_rcv+0x10a/0x340 [kernel]
 0xffffffff81584411 : tcp_v4_rcv+0x831/0x9f0 [kernel]
 0xffffffff8155e114 : ip_local_deliver_finish+0xb4/0x1f0 [kernel]
 0xffffffff8155e3f9 : ip_local_deliver+0x59/0xd0 [kernel]
 0xffffffff8155dd8d : ip_rcv_finish+0x7d/0x350 [kernel]
 0xffffffff8155e726 : ip_rcv+0x2b6/0x410 [kernel]
 0xffffffff81522d42 : __netif_receive_skb_core+0x582/0x7d0 [kernel]
 0xffffffff81522fa8 : __netif_receive_skb+0x18/0x60 [kernel]
 0xffffffff81523c7e : process_backlog+0xae/0x180 [kernel]
 0xffffffff81523462 : net_rx_action+0x152/0x240 [kernel]
 0xffffffff8107dfff : __do_softirq+0xef/0x280 [kernel]
 0xffffffff8163f61c : call_softirq+0x1c/0x30 [kernel]
 0xffffffff81016fc5 : do_softirq+0x65/0xa0 [kernel]
 0xffffffff8107d254 : local_bh_enable_ip+0x94/0xa0 [kernel]
 0xffffffff81634f4b : _raw_spin_unlock_bh+0x1b/0x40 [kernel]
 0xffffffff8150d968 : release_sock+0x118/0x170 [kernel]

如果客户端建立连接的时候抛异常,可能的原因(握手失败,建不上连接):

  • 网络不通,诊断:ping ip
  • 端口不通, 诊断:telnet ip port
  • rp_filter 命中(rp_filter=1, 多网卡环境), 诊断: netstat -s | grep -i filter ;
  • snat/dnat的时候宿主机port冲突,内核会扔掉 syn包。 troubleshooting: sudo conntrack -S | grep insert_failed //有不为0的
  • 全连接队列满的情况,诊断: netstat -s | egrep “listen|LISTEN”
  • syn flood攻击, 诊断:同上
  • 若远端服务器的内核参数 net.ipv4.tcp_tw_recycle 和 net.ipv4.tcp_timestamps 的值都为 1,则远端服务器会检查每一个报文中的时间戳(Timestamp),若 Timestamp 不是递增的关系,不会响应这个报文。配置 NAT 后,远端服务器看到来自不同的客户端的源 IP 相同,但 NAT 前每一台客户端的时间可能会有偏差,报文中的 Timestamp 就不是递增的情况。nat后的连接,开启timestamp。因为快速回收time_wait的需要,会校验时间该ip上次tcp通讯的timestamp大于本次tcp(nat后的不同机器经过nat后ip一样,保证不了timestamp递增),诊断:是否有nat和是否开启了timestamps
  • NAT 哈希表满导致 ECS 实例丢包 nf_conntrack full

iptables和tcpdump

sudo iptables -A INPUT -p tcp –destination-port 8089 -j DROP

tcpdump 是直接从网卡驱动拿包,也就是包还没进入内核tcpdump就拿到了,而iptables是工作在内核层,也就是即使被DROP还是能tcpdump到8089的packet。

参考资料:

https://mp.weixin.qq.com/s?__biz=MjM5NjQ5MTI5OA==&mid=2651747704&idx=3&sn=cd76ad912729a125fd56710cb42792ba&chksm=bd12ac358a6525235f51e3937d99ea113ed45542c51bc58bb9588fa1198f34d95b7d13ae1ae2&mpshare=1&scene=1&srcid=07047U4tN9Y3m97WQUJSLENt#rd

http://blog.hyfather.com/blog/2013/03/04/ifconfig/

网络环路

本文主要探讨网络环路的成因,危害以及预防

交换机之间多条网线导致环路

image.png

如图sw1/2/3 三个交换机形成一个环路,一个arp广播包从sw1出来到sw2,然后到sw3,再然后又从sw3回到sw1,形成一个环路,这个arp包会重复前面的传播过程进而导致这个包一直在三个交换机之间死循环,进而把三个交换机的CPU、带宽全部打满,整个网络瘫痪

对这种网络环路网络工程师们非常忌惮,因为一旦形成非常不好排查,并且整个网络瘫痪,基本上是严防死守。同时交换机也提供了各种功能(算法、策略)来自动检测网络环路并阻断网络环路。

比如上图中交换机能检测到虚线形成了环路,并自动把这个交换机口Down掉以阻止成环。

交换机对环路的阻断–STP(Spanning TreeProtocol)协议

STP协议的基本思想十分简单。大家知道,自然界中生长的树是不会出现环路的,如果网络也能够像一棵树一样生长就不会出现环路。于是,STP协议中定义了根桥(RootBridge)、根端口(RootPort)、指定端口(DesignatedPort)、路径开销(PathCost)等概念,目的就在于通过构造一棵自然树的方法达到裁剪冗余环路的目的,同时实现链路备份和路径最优化。用于构造这棵树的算法称为生成树算法SPA(Spanning TreeAlgorithm)。(摘自:http://network.51cto.com/art/201307/404013.htm)

STP是通过BPDU的网络包来在交换机之间交换信息、判断是否成环

一个STP的Case

下图是抓到的STP网络包
image.png

STP协议的后果就是带宽效率低,所以出现了PVST、PVST+、RSTP、MISTP、MSTP,这些协议可能不同厂家的交换机都不一样,互相之间也不一定兼容,所以是否生效要以实际测试为准

用tcpdump抓取stp包

$ sudo tcpdump -vvv -p -n -i eth1 stp
tcpdump: listening on eth1, link-type EN10MB (Ethernet), capture size 65535 bytes

15:44:10.772423 STP 802.1d, Config, Flags [none], bridge-id  8000.MAC.8687, length 43
message-age 0.00s, max-age 20.00s, hello-time 2.00s, forwarding-delay 15.00s
root-id 8000.MAC, root-pathcost 0
15:44:12.768245 STP 802.1d, Config, Flags [none], bridge-id 8000.MAC8.8687, length 43
message-age 0.00s, max-age 20.00s, hello-time 2.00s, forwarding-delay 15.00s
root-id 8000.MAC, root-pathcost 0
15:44:14.766513 STP 802.1d, Config, Flags [none], bridge-id 8000.MAC.8687, length 43
message-age 0.00s, max-age 20.00s, hello-time 2.00s, forwarding-delay 15.00s
root-id 8000.MAC, root-pathcost 0
15:44:16.766478 STP 802.1d, Config, Flags [none], bridge-id 8000.MAC.8687, length 43
message-age 0.00s, max-age 20.00s, hello-time 2.00s, forwarding-delay 15.00s
root-id 8000.MAC, root-pathcost 0
15:44:18.767851 STP 802.1d, Config, Flags [none], bridge-id 8000.MAC.8687, length 43
message-age 0.00s, max-age 20.00s, hello-time 2.00s, forwarding-delay 15.00s
root-id 8000.MAC, root-pathcost 0	

交换机上看到的STP

C4948-D2-08-36U#show run int g1/31
Building configuration...

Current configuration : 482 bytes
!
interface GigabitEthernet1/31
 description to D2-9-09/10U-GWR730-eth1
 switchport access vlan 270
 switchport mode access
 switchport port-security maximum 50
 switchport port-security
 switchport port-security aging time 2
 switchport port-security violation restrict
 switchport port-security aging type inactivity
 switchport port-security aging static
 storm-control broadcast level 20.00
 spanning-tree portfast
 spanning-tree bpduguard enable
 spanning-tree guard root
end

SDN或者说OVS对网络环路的影响

前面讨论的都是硬件交换机之间的网络环路以及硬件交换机对这些环路的处理,那么在SDN和OVS的场景下有没有可能成环呢? 成环后硬件交换机能不能检测到,或者软交换机自己能否检测到并阻止这些环路呢?

来看一个OVS场景下的成环Case

image.png

上图中红色虚线部分组成了一个环路,是为了组成环路而人为构造的场景,同时发现OVS只支持STP算法,打开也没有用,因为OVS和硬件交换机之间没法通过BPDU来协商判断环路(物理交换机丢掉了硬件交换机的BPDU包)。

也就是在硬件网络环境固定的情况下,我们可以在Linux环境下鼓捣出来一个网络环路,同时让Linux所在的物理二层网络瘫痪掉(好屌)

在这种网络环路下后果

  • 整个二层网络瘫痪,所有交换机CPU 100%,带宽100%
  • 连接在交换机上的所有服务器SYS CPU飙升到 30%左右(没有啥意义了,服务器没法跟外部做任何交流了)

交换机的CPU状态:

image.png

成环后抓到的arp广播风暴网络包(实际我只发了一个arp包):

image.png

其它网络环路

  • 直接把两个交换机用两根网线连接起来就是个环路
  • 拿一根网线两头连接在同一个交换机的两个网口上(短路) 2006年的一个Case: https://www.zhihu.com/question/49545070,不过现在的交换机基本上都能识别这种短路
  • 两个交换机之间做bond失败,导致环路或者三角形(三角形的话会导致多个网口对应同一个mac地址,进而导致这个mac地址网络不通,三角形不会形成网络风暴)

参考资料:

https://www.zhihu.com/question/49545070

http://network.51cto.com/art/201307/404013.htm

一个没有遵守tcp规则导致的问题

问题描述

应用连接数据库一段时间后,执行SQL的时候总是抛出异常,通过抓包分析发现每次发送SQL给数据的时候,数据库总是Reset这个连接

image.png

注意图中34号包,server(5029)发了一个fin包给client ,想要断开连接。client没断开,接着发了一个查询SQL给server。

进一步分析所有断开连接(发送第一个fin包)的时间点,得到如图:

image.png

基本上可以猜测,server(5029端口)在建立连接100秒终止后如果没有任何请求过来就主动发送fin包给client,要断开连接,但是这个时候client比较无耻,收到端口请求后没搭理(除非是故意的),这个时候意味着server准备好关闭了,也不会再给client发送数据了(ack除外)。

但是client虽然收到了fin断开连接的请求不但不理,过一会还不识时务发SQL查询给server,server一看不懂了(server早就申明连接关闭,没法发数据给client了),就只能回复reset,强制告诉client断开连接吧,client这时才迫于无奈断开了这次连接(图一绿框)

client的应用代码层肯定会抛出异常。

server强行断开连接

image.png

18745号包,client发了一个查询SQL给server,server先是回复ack 18941号包,然后回复fin 19604号包,强行断开连接,client端只能抛异常了

疑难问题汇总

一网通客户 vxlan 网络始终不通,宿主机能抓到发出去的包,但是抓不到回复包。对端容器所在的宿主机抓不到进来的包

一定是网络上把这个包扔掉了

证明问题

  • 先选择两台宿主机,停掉上面的 ovs 容器(腾出4789端口)
  • 一台宿主机上执行: nc -l -u 4789 //在4789端口上启动udp服务
  • 另外一台主机上执行: nc -u 第一台宿主机的IP 4789 //从第二台宿主机连第一台的4789端口
  • 从两边都发送一些内容看看,看是否能到达对方

如果通过nc发送的东西也无法到达对方(跟方舟没有关系了)那么就是链路上的问题


一网通客户 vxlan 网络能通,但是pca容器初始化的时候失败

通过报错信息发现pca容器访问数据库SocketTimeout,同时看到异常信息都是Timeout大于15分钟以上了。

需找问题

  • 先在 pca容器和数据库容器互相 ping 证明网络没有问题,能够互通
  • 在 pca 容器中通过mysql 命令行连上 mysql,并创建table,insert一些记录,结果也没有问题
  • 抓包发现pca容器访问数据库的时候在重传包(以往经验)

screenshot

细化证明问题

  • ping -s -M 尝试发送1460大小的包
  • 检查宿主机、容器MTU设置

确认问题在宿主机网卡MTU设置为1350,从而导致容器发出的包被宿主机网卡丢掉

新零售客户通过vpn部署好中间件后,修改笔记本的dns设置后通过浏览器来访问中间件的console,但是报找不到server。同时在cmd中ping 这个域名能通,但是nslookup解析不了这个域名

ping 这个域名能通,但是nslookup不行,基本可以确认网络没有大问题,之所以ping可以nslookup不行,是因为他们底层取dns server的逻辑不一样。

先检查dns设置:

image.png
如上图,配置的填写

image.png

多出来一个127.0.0.1肯定有问题,明明配置的时候只填了114.114.114.114. nslookup、浏览器默认把域名解析丢给了127.0.0.1,但是 ping丢给了114.114.114.114,所以看到如上描述的结果。

经过思考发现应该是本机同时运行了easyconnect(vpn软件),127.0.0.1 是他强行塞进来的。马上停掉easyconnect再ipconfig /all 验证一下这个时候的dns server,果然127.0.0.1不见了, nslookup 也正常了。

某航空客户 windows下通过方舟dns域名解析不了方舟域名,但是宿主机上可以。windows机器能ping通dns server ip, 但是nslookup 解析不了域名,显示request time out

image.png

能ping通说明网络能通,但是dns域名要能解析依赖于:

  • 网络能通
  • dns server上有dns服务(53udp端口)
  • 中间的防火墙对这个udp53端口限制了

如上图,这里的问题非常明显是中间的防火墙没放行 udp 53端口

方舟环境在ECS底座上DNS会继承rotate模式,导致域名解析不正常,ping 域名不通,但是nslookup能通

nslookup 域名结果正确,但是 ping 域名失败

某银行 POC 环境物理机搬迁到新机房后网络不通,通过在物理机上抓包,抓不到任何容器的包

image.png

如图所示容器中发了 arp包(IP 10.100.2.2 寻找10.100.2.1 的mac地址),这个包从bond0 网卡发出去了,也是带的正确的 vlanid 1011,但是交换机没有回复,那么就一定是交换机上vlan配置不对,需要找分配这个vlan的网工来检查交换机的配置

能抓到进出的容器包–外部环境正确,方舟底座的问题

不能抓到出去的容器包–方舟底座的问题

能抓到出去的容器包,抓不到回来的包–外部环境的问题

所以这里是方舟底座的问题。检查ovs、vlan插件一切都正常,见鬼了

检查宿主机网卡状态,发现没插网线,如果容器所用的宿主机网卡没有插网线,那么ovs就不会转发任何包到宿主机网卡

一台应用服务器无法访问部分drds-server

应用机器: 10.100.10.201 这台机器抛502异常比较多,进一步诊断发现 ping youku.tddl.tbsite.net 的时候解析到 10.100.53.15/16就不通

直接ping 10.100.53.15/16 也不通,经过诊断发现是交换机上记录了两个 10.100.10.201的mac地址导致网络不通。

youku-mac-ip.gif

上图是不通的IP,下图是正常IP

经过调查发现是土豆业务也用了10.100.10.201这个IP导致交换机的ARP mac table冲突,土豆删除这个IP后故障就恢复了。

当时交换机上发现的两条记录:

00:18:51:38:b1:cd 10.100.10.201 
8c:dc:d4:b3:af:14 10.100.10.201

某个客户默认修改了umask导致黑屏脚本权限不够,部署中途不断卡壳,直接在黑屏脚本中修复了admin这个用户的umask

  1. 客户环境的 umask 是 0027 会导致所有copy文件的权限都不对了
  2. 因为admin没权限执行 /bin/jq 导致daemon.json是空的
  3. /etc/docker/daemon.json 文件是空的,docker启动报错

修复centos下udp和批量处理脚本因为环境变量的确实不能执行modprobe和ping等等命令的问题,同时将alios的这块修复逻辑放到了方舟安装脚本中,init的时候会先把这个问题修复

Linux环境变量问题汇总

Centos系统重启后 /etc/resolv.conf总是被还原,开始以为是系统Bug,研究后发现是可以配置的,dhcp默认会每次重启后拉取DNS自动更新 /etc/resolv.conf

MonkeyKing burn cpu: mkt-burncpu.sh 脚本在方舟服务器上运行一段时间后,进程不见了,MK团队认为是方舟杀掉了他们。

好奇心迫使我去看代码、看openssl测试输出日志(MonkeyKing burn cpu内部调用 openssl speed 测试cpu的速度),这个测试一轮跑完了opessl就结束了,本身就不是死循环一直跑, 不是方舟杀掉的。

另外说明这个问题一直存在开发、测试MonkeyKing功能的团队就没有发现,或者之前一直只需要跑不到10分钟就自己主动把它杀掉让出CPU。

image.png

某汽车客户 部署过程中愚公不能正常启动,怀疑是依赖的zk问题,zk网络访问正常

尝试telnet zk发现不通,客户现场安装了kerberos导致telnet测试有问题(telnet被kerberos替换过),换一个其他环境的telnet 二进制文件就可以了(md5sum、telnet –help)

开发反应两个容器之间的网络不稳定,偶尔报连不上某些容器

主要是出现在tlog-console访问hbase容器的时候报连接异常

  1. 在 task_1114_g0_tlog-console_tlog_1(10.16.11.131) 的56789 端口上启动了一个简单的http服务,然后从 task_1114_g0_tlog-hbase_tlog(10.16.11.108) 每秒钟去访问一下10.16.11.131:56789 , 如果丢包率很高的时候服务 10.16.11.131:56789 也很慢或者访问不到就是网络问题,否则就有可能是hbase服务不响应导致的丢包、网络不通(仅仅是影响hbase服务)
  2. 反过来在hbase上同样启动http服务,tlog-console不停地去get
  3. 整个过程我的http服务响应非常迅速稳定,从没出现过异常
  4. 在重现问题侯,贺飞发现 是tlog线程数目先增多,retran才逐渐增高的, retran升高,并没有影响在那台机器上ping 或者telnet hbase的服务
  5. 通过以上方式证明跟容器、网络无关,是应用本身的问题,交由产品开发继续解决

最终开发确认网络没有问题后一门心思闷头自查得出结论:

信息更新:

问题:
tlog-console进程线程数多,卡在连接hbase上的问题

直接原因:

  1. tlog-console有巡检程序,每m分钟会检查运行超过n秒的线程,并且中断这个线程; 这个操作直接导致hbase客户端在等待hbaseserver返回数据的时候被中断,这种中断会经常发生,累积久了,就会打爆tlog-console服务的线程数目,这时候,tlogconsole机器的retran就会变多,连接hbaseserver就会出问题, 具体的机理不明

解决问题的有效操作:

  1. 停止对tlog-console的巡检程序后,问题没有发生过

其他潜在问题,这些问题是检查问题的时候,发现的其他潜在问题,已经反馈给tlog团队:

  1. Htable实例不是线程安全,有逻辑多线程使用相同的htable实例
  2. 程序中有new HTable 不close的路径

某化工私有云DRDS扩容总是报资源不足,主要是因为有些drds-server容器指定了–cpu-shares=128(相当于4Core–1024/物理核数 等于每个核对应的cpu-shares ), 导致物理机CPU不够。现场将所有容器的–cpu-shares改成2后修复这个问题,但是最终需要产品方

主要是swarm对cpu-shares的判断上有错误,swarm默认认定每台机器的总cpu-shares是1024,也就是 1024/物理核数 等于每个核对应的cpu-shares

如果需要精细化CPU控制,cpu-shares比cpu-set之类的要精确,利用率更高。但是也更容易出现问题

mq-diamond的异常日志总是打爆磁盘。mq-diamond 容器一天输出500G日志的问题,本质是调用的依赖不可用了,导致mq-diamond 频繁输出日志,两天就用掉了1T磁盘.

这里有两个问题需要处理:

  1. mq-diamond 依赖的服务可用;
  2. mq-diamond 自身保护,不要被自己的日志把磁盘撑爆了

对于问题二修改log4j来保护;对于问题1查看异常内容,mq-diamond尝试连接server:ip1,ip2,ip3 正常这里应该是一个ip而不是三个ip放一起。判断是mq-diamond从mq-cai获取diamond iplist有问题,这个iplist应该放在三行,但是实际被放到了1行,用逗号隔开

手工修改这个文件,放到三行,问题没完,还是异常,我自己崩溃没管。最后听mq-diamond的开发讲他们取iplist的url比较特殊,是自己定义的,所以我修改的地方不起作用。反思,为什么修改不起作用的时候不去看看Nginx的access日志? 这样可以证明我修改的文件实际没有被使用,同时还能找到真正被使用的配置文件

内核migration进程bug导致宿主机Load非常高,同时CPU idle也很高(两者矛盾)

内核migration进程bug导致对应的CPU核卡死(图一),这个核上的所有进程得不到执行(Load高,CPU没有任何消耗, 图二),直到内核进程 watchdog 发现这个问题并恢复它。

出现这个bug后的症状,通过top命令看到CPU没有任何消耗但是Load偏高,如果应用进程恰好被调度到这个出问题的CPU核上,那么这个进程会卡住(大概20秒)没有任何响应,比如 ping 进程(图三图四),watchdog恢复这个问题后,多个网络包在同一时间全部通。其实所影响的不仅仅是网络卡顿,中间件容器里面的服务如果调度到这个CPU核上同样得不到执行,从外面就是感觉容器不响应了

image.png

image.png

image.png

image.png

拿如上证据求助内核开发

关键信息在这里:
代码第297行
2017-09-15T06:52:37.820783+00:00 ascliveedas4.sgdc kernel: [598346.499872] WARNING: at net/sched/sch_generic.c:297 dev_watchdog+0x270/0x280()
2017-09-15T06:52:37.820784+00:00 ascliveedas4.sgdc kernel: [598346.499873] NETDEV WATCHDOG: ens2f0 (ixgbe): transmit queue 28 timed out

kernel version: kernel-3.10.0-327.22.2.el7.src.rpm

265 static void dev_watchdog(unsigned long arg)
266 {
267 struct net_device *dev = (struct net_device *)arg;
268
269 netif_tx_lock(dev);
270 if (!qdisc_tx_is_noop(dev)) {
271 if (netif_device_present(dev) &&
272 netif_running(dev) &&
273 netif_carrier_ok(dev)) {
274 int some_queue_timedout = 0;
275 unsigned int i;
276 unsigned long trans_start;
277
278 for (i = 0; i < dev->num_tx_queues; i++) {
279 struct netdev_queue *txq;
280
281 txq = netdev_get_tx_queue(dev, i);
282 /*
283  * old device drivers set dev->trans_start
284  */
285 trans_start = txq->trans_start ? : dev->trans_start;
286 if (netif_xmit_stopped(txq) &&
287 time_after(jiffies, (trans_start +
288  dev->watchdog_timeo))) {
289 some_queue_timedout = 1;
290 txq->trans_timeout++;
291 break;
292 }
293 }
294
295 if (some_queue_timedout) {
296 WARN_ONCE(1, KERN_INFO "NETDEV WATCHDOG: %s (%s): transmit queue %u timed out\n",
297dev->name, netdev_drivername(dev), i);
298 dev->netdev_ops->ndo_tx_timeout(dev);
299 }
300 if (!mod_timer(&dev->watchdog_timer,
301round_jiffies(jiffies +
302  dev->watchdog_timeo)))
303 dev_hold(dev);
304 }



$ cat  kernel_log.0915
2017-09-15T02:19:55.975310+00:00 ascliveedas4.sgdc kernel: [582026.288227] openvswitch: netlink: Key type 62 is out of range max 22
2017-09-15T03:49:41.312168+00:00 ascliveedas4.sgdc kernel: [587409.546584] md: md0: data-check interrupted.
2017-09-15T06:52:37.820782+00:00 ascliveedas4.sgdc kernel: [598346.499865] ------------[ cut here ]------------
2017-09-15T06:52:37.820783+00:00 ascliveedas4.sgdc kernel: [598346.499872] WARNING: at net/sched/sch_generic.c:297 dev_watchdog+0x270/0x280()
2017-09-15T06:52:37.820784+00:00 ascliveedas4.sgdc kernel: [598346.499873] NETDEV WATCHDOG: ens2f0 (ixgbe): transmit queue 28 timed out
2017-09-15T06:52:37.820784+00:00 ascliveedas4.sgdc kernel: [598346.499916] Modules linked in: 8021q garp mrp xt_nat veth xt_addrtype ipt_MASQUERADE nf_nat_masquerade_ipv4 iptable_nat nf_conntrack_ipv4 nf_defrag_ipv4 nf_nat_ipv4 iptable_filter xt_conntrack nf_nat nf_conntrack bridge stp llc tcp_diag udp_diag inet_diag binfmt_misc overlay() vfat fat intel_powerclamp coretemp intel_rapl kvm_intel kvm crc32_pclmul ghash_clmulni_intel aesni_intel lrw gf128mul glue_helper ablk_helper cryptd raid10 ipmi_devintf iTCO_wdt iTCO_vendor_support sb_edac lpc_ich hpwdt edac_core hpilo i2c_i801 ipmi_si sg mfd_core pcspkr ioatdma ipmi_msghandler acpi_power_meter shpchp wmi pcc_cpufreq openvswitch libcrc32c nfsd auth_rpcgss nfs_acl lockd grace sunrpc ip_tables ext4 mbcache jbd2 sd_mod crc_t10dif crct10dif_generic mgag200 syscopyarea sysfillrect sysimgblt drm_kms_helper ixgbe crct10dif_pclmul ahci ttm crct10dif_common igb crc32c_intel mdio libahci ptp drm pps_core i2c_algo_bit libata i2c_core dca dm_mirror dm_region_hash dm_log dm_mod
2017-09-15T06:52:37.820786+00:00 ascliveedas4.sgdc kernel: [598346.499928] CPU: 10 PID: 123 Comm: migration/10 Tainted: G L ------------ T 3.10.0-327.22.2.el7.x86_64#1
2017-09-15T06:52:37.820787+00:00 ascliveedas4.sgdc kernel: [598346.499929] Hardware name: HP ProLiant DL160 Gen9/ProLiant DL160 Gen9, BIOS U20 12/27/2015
2017-09-15T06:52:37.820788+00:00 ascliveedas4.sgdc kernel: [598346.499935]  ffff88207fc43d88 000000001cdfb0f1 ffff88207fc43d40 ffffffff816360fc
2017-09-15T06:52:37.820789+00:00 ascliveedas4.sgdc kernel: [598346.499939]  ffff88207fc43d78 ffffffff8107b200 000000000000001c ffff881024660000
2017-09-15T06:52:37.820790+00:00 ascliveedas4.sgdc kernel: [598346.499942]  ffff881024654f40 0000000000000040 000000000000000a ffff88207fc43de0
2017-09-15T06:52:37.820791+00:00 ascliveedas4.sgdc kernel: [598346.499943] Call Trace:
2017-09-15T06:52:37.820792+00:00 ascliveedas4.sgdc kernel: [598346.499952]  <IRQ>  [<ffffffff816360fc>] dump_stack+0x19/0x1b
2017-09-15T06:52:37.820794+00:00 ascliveedas4.sgdc kernel: [598346.499956]  [<ffffffff8107b200>] warn_slowpath_common+0x70/0xb0
2017-09-15T06:52:37.820795+00:00 ascliveedas4.sgdc kernel: [598346.499959]  [<ffffffff8107b29c>] warn_slowpath_fmt+0x5c/0x80
2017-09-15T06:52:37.820795+00:00 ascliveedas4.sgdc kernel: [598346.499964]  [<ffffffff8154d4f0>] dev_watchdog+0x270/0x280
2017-09-15T06:52:37.820796+00:00 ascliveedas4.sgdc kernel: [598346.499966]  [<ffffffff8154d280>] ? dev_graft_qdisc+0x80/0x80
2017-09-15T06:52:37.820797+00:00 ascliveedas4.sgdc kernel: [598346.499972]  [<ffffffff8108b0a6>] call_timer_fn+0x36/0x110
2017-09-15T06:52:37.820798+00:00 ascliveedas4.sgdc kernel: [598346.499974]  [<ffffffff8154d280>] ? dev_graft_qdisc+0x80/0x80
2017-09-15T06:52:37.820799+00:00 ascliveedas4.sgdc kernel: [598346.499977]  [<ffffffff8108dd97>] run_timer_softirq+0x237/0x340
2017-09-15T06:52:37.820800+00:00 ascliveedas4.sgdc kernel: [598346.499980]  [<ffffffff81084b0f>] __do_softirq+0xef/0x280
2017-09-15T06:52:37.820801+00:00 ascliveedas4.sgdc kernel: [598346.499985]  [<ffffffff81103360>] ? cpu_stop_should_run+0x50/0x50
2017-09-15T06:52:37.820801+00:00 ascliveedas4.sgdc kernel: [598346.499988]  [<ffffffff8164819c>] call_softirq+0x1c/0x30
2017-09-15T06:52:37.820802+00:00 ascliveedas4.sgdc kernel: [598346.499994]  [<ffffffff81016fc5>] do_softirq+0x65/0xa0
2017-09-15T06:52:37.820803+00:00 ascliveedas4.sgdc kernel: [598346.499996]  [<ffffffff81084ea5>] irq_exit+0x115/0x120
2017-09-15T06:52:37.820804+00:00 ascliveedas4.sgdc kernel: [598346.499999]  [<ffffffff81648e15>] smp_apic_timer_interrupt+0x45/0x60
2017-09-15T06:52:37.820805+00:00 ascliveedas4.sgdc kernel: [598346.500003]  [<ffffffff816474dd>] apic_timer_interrupt+0x6d/0x80
2017-09-15T06:52:37.820813+00:00 ascliveedas4.sgdc kernel: [598346.500007]  <EOI>  [<ffffffff811033df>] ? multi_cpu_stop+0x7f/0xf0
2017-09-15T06:52:37.820815+00:00 ascliveedas4.sgdc kernel: [598346.500010]  [<ffffffff81103666>] cpu_stopper_thread+0x96/0x170

某银行客户RAID阵列坏掉,导致物理机重启后容器的net-alias域名解析不到

docker daemon 的endpoint用的容器名存在zk中,如果创建一个重复名字的容器,那么会失败,然后回滚,回滚动作会把zk中别人的endpoint删掉,从而导致域名不通。

物理机异常后,我们的调度程序会在其它物理机重新调度生成这个容器,但是当原来的物理机回来后,这里有两个一样的容器会自动删掉宕机的物理机上的这个容器,从而误删net-alias,进而域名无法解析

某快递客户PHP短连接访问DRDS会导致极低概率出现连接被reset、货运快递客户事务没生效导致数据库写入的金额对不上账

参考文章

https://mp.weixin.qq.com/s?__biz=MzU5Mzc0NDUyNg==&mid=2247483793&idx=1&sn=c7b4ec96d186dd74689482077522337f&scene=21#wechat_redirect

Load很高,CPU使用率很低

第一次碰到这种Case:物理机的Load很高,CPU使用率很低

先看CPU、Load情况

如图一:
vmstat显示很有多任务等待排队执行(r)top都能看到Load很高,但是CPU idle 95%以上
image.png
image.png

这个现象不太合乎常规,也许是在等磁盘IO、也许在等网络返回会导致CPU利用率很低而Load很高

贴个vmstat 说明文档(图片来源于网络N年了,找不到出处)
image.png

检查磁盘状态,很正常(vmstat 第二列也一直为0)

image.png

再看Load是在5号下午15:50突然飙起来的:

image.png

同一时间段的网络流量、TCP连接相关数据很平稳:

image.png

所以分析到此,可以得出:Load高跟磁盘、网络、压力都没啥关系

物理机上是跑的Docker,分析了一下CPUSet情况:

image.png

发现基本上所有容器都绑定在CPU1上(感谢 @辺客 发现这个问题)

进而检查top每个核的状态,果然CPU1 的idle一直为0

image.png

看到这里大致明白了,虽然CPU整体很闲但是因为很多进程都绑定在CPU1上,导致CPU1上排队很长,看前面tsar的–load负载截图的 等待运行进程排队长度(runq)确实也很长。

物理机有32个核,如果100个任务同时进来,Load大概是3,这是正常的。如果这100个任务都跑在CPU1上,Load还是3(因为Load是所有核的平均值)。但是如果有源源不断的100个任务进来,前面100个还没完后面又来了100个,这个时候CPU1前面队列很长,其它31个核没事做,这个时候整体Load就是6了,时间一长很快Load就能到几百。

这是典型的瓶颈导致积压进而高Load。

为什么会出现这种情况

检查Docker系统日志,发现同一时间点所有物理机同时批量执行docker update 把几百个容器都绑定到CPU1上,导致这个核忙死了,其它核闲得要死(所以看到整体CPU不忙,最忙的那个核被平均掩盖掉了),但是Load高(CPU1上排队太长,即使平均到32个核,这个队列还是长,这就是瓶颈啊)。

如下Docker日志,Load飙升的那个时间点有人批量调docker update 把所有容器都绑定到CPU1上:
image.png

检查Docker集群Swarm的日志,发现Swarm没有发起这样的update操作,似乎是每个Docker Daemon自己的行为,谁触发了这个CPU的绑定过程的原因还没找到,求指点。

手动执行docker update, 把容器打散到不同的cpu核上,恢复正常:

image.png

关于这个Case的总结

  • 技术拓展商业边界,同样技能、熟练能力能拓展解决问题的能力。 开始我注意到了Swarm集群显示的CPU绑定过多,同时也发现有些容器绑定在CPU1上。所以我尝试通过API: GET /containers/json 拿到了所有容器的参数,然后搜索里面的CPUSet,结果这个API返回来的参数不包含CPUSet,那我只能挨个 GET /containers/id/json, 要写个循环,偷懒没写,所以没发现这个问题。
  • 这种多个进程绑定到同一个核然后导致Load过高的情况确实很少见,也算是个教训
  • 自己观察top 单核的时候不够仔细,只是看到CPU1 的US 60%,没留意idle,同时以为这个60%就是偶尔一个进程在跑,耐心不够(主要也是没意识到这种极端情况,疏忽了)

关于Load高的总结

Linux 下load 高主要是因为R/D 两个状态的线程多了,排查套路:

img

参考文章

浅谈 Linux 高负载的系统化分析

部分机器网络不通

问题

应用机器: 10.100.10.201 这台机器抛502异常比较多,进一步诊断发现 ping youku.tddl.tbsite.net 的时候解析到 10.100.53.15/16就不通

直接ping 10.100.53.15/16 也不通,经过诊断发现是交换机上记录了两个 10.100.10.201的mac地址导致网络不通。

youku-mac-ip.gif

上图是不通的IP,下图是正常IP

经过调查发现是土豆业务也用了10.100.10.201这个IP导致交换机的ARP mac table冲突,土豆删除这个IP后故障就恢复了。

当时交换机上发现的两条记录:

00:18:51:38:b1:cd 10.100.10.201 
8c:dc:d4:b3:af:14 10.100.10.201

关于TCP连接的Keepalive和reset

先来看一个现象,下面是测试代码:

Server: socat -dd tcp-listen:2000,keepalive,keepidle=10,keepcnt=2,reuseaddr,keepintvl=1 -
Client: socat -dd - tcp:localhost:2000,keepalive,keepidle=10,keepcnt=2,keepintvl=1

Drop Connection (Unplug Cable, Shut down Link(WiFi/Interface)): sudo iptables -A INPUT -p tcp --dport 2000 -j DROP

server监听在2000端口,支持keepalive, client连接上server后每隔10秒发送一个keepalive包,一旦keepalive包得不对对方的响应,每隔1秒继续发送keepalive, 重试两次,如果一直得不到对方的响应那么这个时候client主动发送一个reset包,那么在client这边这个socket就断开了。server上会一直傻傻的等,直到真正要发送数据了才抛异常。

image.png

假如client连接层是一个Java应用的连接池,那么这个socket断开后Java能感知吗?

https://stackoverflow.com/questions/10240694/java-socket-api-how-to-tell-if-a-connection-has-been-closed

Java对Socket的控制比较弱,比如只能指定是否keepalive,不能用特定的keepalive参数(intvl/cnt等),除非走JNI,不推荐。

如下图(dup ack其实都是keepalive包,这是因为没有抓到握手包导致wireshark识别错误而已)
image.png

如上图,client 21512在多次keepalive server都不响应后,发送了reset断开这个连接(server没收到),server以为还连着,这个时候当server正常发数据给client,如果防火墙还在就丢掉,server不停地重传,如果防火墙不在,那么对方os收到这个包后知道21512这个端口对应的连接已经关闭了,再次发送reset给server,这时候server抛异常,中断这个连接。

image.png

os层面目前看起来除了用socket去读数据感知到内核已经reset了连接外也没什么好办法检测到。

如何徒手撕Bug

经常碰到bug,如果有源代码,或者源代码比较简单一般通过bug现象结合读源代码,基本能比较快解决掉。但是有些时候源代码过于复杂,比如linux kernel,比如 docker,复杂的另一方面是没法比较清晰地去理清源代码的结构。

所以不到万不得已不要碰复杂的源代码

问题

docker daemon重启,上面有几十个容器,重启后daemon基本上卡死不动了。 docker ps/exec 都没有任何响应,同时能看到很多这样的进程:

image.png

这个进程是docker daemon在启动的时候去设置每个容器的iptables,来实现dns解析。

这个时候执行 sudo iptables -L 也告诉你有其他应用锁死iptables了:
image.png

$sudo fuser /run/xtables.lock 
/run/xtables.lock:1203  5544 10161 14451 14482 14503 14511 14530 14576 14602 14617 14637 14659 14664 14680 14698 14706 14752 14757 14777 14807 14815 14826 14834 14858 14872 14889 14915 14972 14973 14979 14991 15006 15031 15067 15076 15104 15127 15155 15176 15178 15179 15180 16506 17656 17657 17660 21904 21910 24174 28424 29741 29839 29847 30018 32418 32424 32743 33056 33335 59949 64006

通过上面的命令基本可以看到哪些进程在等iptables这个锁,之所以有这么多进程在等这个锁,应该是拿到锁的进程执行比较慢所以导致后面的进程拿不到锁,卡在这里

跟踪具体拿到锁的进程

$sudo lsof  /run/xtables.lock | grep 3rW
iptables 36057 root3rW  REG   0,190 48341 /run/xtables.lock

通过strace这个拿到锁的进程可以看到:

image.png

发现在这个配置容器dns的进程同时还在执行一些dns查询任务(容器发起了dns查询),但是这个时候dns还没配置好,所以这个查询会超时

看看物理机上的dns服务器配置:

$cat /etc/resolv.conf   
options timeout:2 attempts:2   
nameserver 10.0.0.1  
nameserver 10.0.0.2
nameserver 10.0.0.3

尝试将 timeout 改到20秒、1秒分别验证一下,发现如果timeout改到20秒strace这里也会卡20秒,如果是1秒(这个时候attempts改成1,后面两个dns去掉),那么整体没有感知到任何卡顿,就是所有iptables修改的进程都很快执行完毕了

strace某个等锁的进程,拿到锁后非常快

image.png

拿到锁后如果这个时候没有收到 dns 查询,那么很快iptables修改完毕,也不会导致卡住

strace工作原理

strace -T -tt -ff -p pid -o strace.out

注意:对于多进线程序需要加-f 参数,这样会trace 进程下的所有线程,-t 表示打印时间精度默认为秒,-tt -ttt 分别表示ms us 的时间精度。

image.png

我们从图中可以看到,对于正在运行的进程而言,strace 可以 attach 到目标进程上,这是通过 ptrace 这个系统调用实现的(gdb 工具也是如此)。ptrace 的 PTRACE_SYSCALL 会去追踪目标进程的系统调用;目标进程被追踪后,每次进入 syscall,都会产生 SIGTRAP 信号并暂停执行;追踪者通过目标进程触发的 SIGTRAP 信号,就可以知道目标进程进入了系统调用,然后追踪者会去处理该系统调用,我们用 strace 命令观察到的信息输出就是该处理的结果;追踪者处理完该系统调用后,就会恢复目标进程的执行。被恢复的目标进程会一直执行下去,直到下一个系统调用。

你可以发现,目标进程每执行一次系统调用都会被打断,等 strace 处理完后,目标进程才能继续执行,这就会给目标进程带来比较明显的延迟。因此,在生产环境中我不建议使用该命令,如果你要使用该命令来追踪生产环境的问题,那就一定要做好预案。

假设我们使用 strace 跟踪到,线程延迟抖动是由某一个系统调用耗时长导致的,那么接下来我们该怎么继续追踪呢?这就到了应用开发者和运维人员需要拓展分析边界的时刻了,对内核开发者来说,这才算是分析问题的开始。

两个术语:

  1. tracer:跟踪(其他程序的)程序
  2. tracee:被跟踪程序

tracer 跟踪 tracee 的过程:

首先,attach 到 tracee 进程:调用 ptrace,带 PTRACE_ATTACH 及 tracee 进程 ID 作为参数。

之后当 tracee 运行到系统调用函数时就会被内核暂停;对 tracer 来说,就像 tracee 收到了 SIGTRAP 信号而停下来一样。接下来 tracer 就可以查看这次系统调 用的参数,打印相关的信息。

然后,恢复 tracee 执行:再次调用 ptrace,带 PTRACE_SYSCALL 和 tracee 进程 ID。 tracee 会继续运行,进入到系统调用;在退出系统调用之前,再次被内核暂停。

以上“暂停-采集-恢复执行”过程不断重复,tracer 就可以获取每次系统调用的信息,打印 出参数、返回值、时间等等。

strace 常用用法

  1. sudo strace -tt -e poll,select,connect,recvfrom,sendto nc www.baidu.com 80 //网络连接不上,卡在哪里

  2. 如何确认一个程序为什么卡住和停止在什么地方?

有些时候,某个进程看似不在做什么事情,也许它被停止在某个地方。

$ strace -p 22067 Process 22067 attached - interrupt to quit flock(3, LOCK_EX

这里我们看到,该进程在处理一个独占锁(LOCK_EX),且它的文件描述符为3,so 这是一个什么文件呢?

$ readlink /proc/22067/fd/3 /tmp/foobar.lock

aha, 原来是 /tmp/foobar.lock。可是为什么程序会被停止在这里呢?

$ lsof | grep /tmp/foobar.lock command 21856 price 3uW REG 253,88 0 34443743 /tmp/foobar.lock command 22067 price 3u REG 253,88 0 34443743 /tmp/foobar.lock

原来是进程 21856 hold住了锁。此时,真相大白 21856 和 22067 读到了相同的锁。

strace -cp // strace 可以按操作汇总时间

我的分析

docker启动的时候要修改每个容器的dns(iptables规则),如果这个时候又收到了dns查询,但是查询的时候dns还没配置好,所以只能等待dns默认超时,等到超时完了再往后执行修改dns动作然后释放iptables锁。这里会发生恶性循环,导致dns修改时占用iptables的时间非常长,进而看着像把物理机iptables锁死,同时docker daemon不响应任何请求。

这应该是docker daemon实现上的小bug,也就是改iptables这里没加锁,如果修改dns的时候同时收到了dns查询,要是让查询等锁的话就不至于出现这种恶性循环

总结

其实这个问题还是挺容易出现的,daemon重启,上面有很多容器,容器里面的任务启动的时候都要做dns解析,这个时候daemon还在修改dns,冲进来很多dns查询的话会导致修改进程变慢

这也跟物理机的 /etc/resolv.conf 配置有关

暂时先只留一个dns server,同时把timeout改成1秒(似乎没法改成比1秒更小),同时 attempts:1 ,也就是加快dns查询的失败,当然这会导致应用启动的时候dns解析失败,最终还是需要从docker的源代码修复这个问题。

解决过程中无数次想放弃,但是反复在那里strace,正是看到了有dns和没有dns查询的两个strace才想清楚这个问题,感谢自己的坚持和很多同事的帮助,手撕的过程中必然有很多不理解的东西,需要请教同事

参考资料

strace 是如何工作的(2016)

方舟域名和服务

服务发布

  • 通过Docker方式指定需要发布的服务名称和对应端口

~:docker run -d -it –name HTTP_Provider –net=vlan701 -l alimw.domain=chengji.test.com -l alimw.port=8090 reg.docker.alibaba-inc.com/middleware.udp

说明:这里docker容器的名称是HTTP_Provider ,通过alimw.domain=chengji.test.com -l alimw.port=8090 指定了服务名为:chengji.test.com,端口:8090

  • 启动后,进入VIPServer的OPS平台查询域名:chengji.test.com,可以看到注册的服务IP和端口,以及健康状态。
    说明:由于只是通过Docker方式注册了服务,但是内部服务并没有启动,可以看到健康程度标注为差,健康检查为false。
  • 部署相关的HTTP服务,再次进入VIPServer的OPS平台查询域名:chengji.test.com,将可以看到健康检查状态正常。

服务发现

1.VIPServer-Client方式

任意启动一个Docker环境,部署好HTTP服务的消费者,采用标准的VS的Client订阅方式即可

2.DNS-F方式(跨语言)

需要提前部署好DNS-F客户端,需要保证DNS-F服务高可用,可直接通过curl方式进行测试

3.方舟提供DNS Server,负责这些域名的解析

0%