plantegg

java tcp mysql performance network docker Linux

定制 Linux Kernel

Linux 里面有一个工具,叫 Grub2,全称 Grand Unified Bootloader Version 2。顾名思义,就是搞系统启动的。

修改启动参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$cat change_kernel_parameter.sh 
#cat /sys/devices/system/cpu/vulnerabilities/*
#grep '' /sys/devices/system/cpu/vulnerabilities/*
#https://help.aliyun.com/document_detail/102087.html?spm=a2c4g.11186623.6.721.4a732223pEfyNC

#cat /sys/kernel/mm/transparent_hugepage/enabled
#transparent_hugepage=always
#noibrs noibpb nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off mitigations=off
#追加nopti nospectre_v2到内核启动参数中
sudo sed -i 's/\(GRUB_CMDLINE_LINUX=".*\)"/\1 nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off mitigations=off transparent_hugepage=always"/' /etc/default/grub

//从修改的 /etc/default/grub 生成 /boot/grub2/grub.cfg 配置
//如果是uefi引导,则是 /boot/efi/EFI/redhat/grub.cfg
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

#limit the journald log to 500M
sed -i 's/^#SystemMaxUse=$/SystemMaxUse=500M/g' /etc/systemd/journald.conf
#重启系统
#sudo reboot

## 选择不同的kernel启动
#sudo grep "menuentry " /boot/grub2/grub.cfg | grep -n menu
##grub认的index从0开始数的
#sudo grub2-reboot 0; sudo reboot
or
#grub2-set-default "CentOS Linux (3.10.0-1160.66.1.el7.x86_64) 7 (Core)" ; sudo reboot

GRUB 2 reads its configuration from the /boot/grub2/grub.cfg file on traditional BIOS-based machines and from the /boot/efi/EFI/redhat/grub.cfg file on UEFI machines. This file contains menu information.

The GRUB 2 configuration file, grub.cfg, is generated during installation, or by invoking the /usr/sbin/grub2-mkconfig utility, and is automatically updated by grubby each time a new kernel is installed. When regenerated manually using grub2-mkconfig, the file is generated according to the template files located in /etc/grub.d/, and custom settings in the /etc/default/grub file. Edits of grub.cfg will be lost any time grub2-mkconfig is used to regenerate the file, so care must be taken to reflect any manual changes in /etc/default/grub as well.

查看kernel编译参数

一般在 /boot/config-** 文件内放置所有内核编译参数

1
2
3
4
5
6
7
//启用 tcp_rt 模块
cat /boot/config-4.19.91-24.8.an8.x86_64 |grep TCP_RT
CONFIG_TCP_RT=y

//启用 RPS
cat /boot/config-4.19.91-24.8.an8.x86_64 |grep RPS
CONFIG_RPS=y

修改是否启用透明大页

1
2
$cat /sys/kernel/mm/transparent_hugepage/enabled
always [madvise] never

制作启动盘

Windows 上用 UltraISO、rufus 烧制,macOS 上就比较简单了,直接用 dd 就可以做好:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ diskutil list
/dev/disk6 (external, physical):
#: TYPE NAME SIZE IDENTIFIER
0: *31.5 GB disk6

# 找到 U 盘的那个设备,umount
$ diskutil unmountDisk /dev/disk3

# 用 dd 把 ISO 文件写进设备,注意这里是 rdisk3 而不是 disk3,在 BSD 中 r(IDENTIFIER)
# 代表了 raw device,会快很多
$ sudo dd if=/path/image.iso of=/dev/rdisk3 bs=1m

# 弹出 U 盘
$ sudo diskutil eject /dev/disk3

Linux 下制作步骤

1
2
3
umount /dev/sdn1
sudo mkfs.vfat /dev/sdn1
dd if=/data/uniontechos-server-20-1040d-amd64.iso of=/dev/sdn1 status=progress

iommu passthrough

在内核参数中加上iommu.passthrough=1 可以关闭iommu,iommu.strict=0是nostrict模式,iommu.strict=1是strict模式(这种性能较差),也是默认的模式。Strict和nostrict主要是处理 无效TLB中缓存的页表项 的方法不同, 一种是批量处理, 一种是一次处理一个。

在X86中加 intel_iommu=off 去关闭的。

IOMMU 硬件单元

DMA Remapping Feature 的工作是通过 CPU 硬件平台的 IOMMU(I/O MMU,Input/Output Memory Management Unit,I/O 内存管理硬件单元)来完成的。IOMMU 的出现,实现了地址空间上的隔离,使设备只能访问规定的内存区域。

image-20220718111233654

参考资料:https://lenovopress.lenovo.com/lp1467.pdf

image-20220729162624318

1
2
3
4
5
6
7
/*
* This variable becomes 1 if iommu=pt is passed on the kernel command line.
* If this variable is 1, IOMMU implementations do no DMA translation for
* devices and allow every device to access to whole physical memory. This is
* useful if a user wants to use an IOMMU only for KVM device assignment to
* guests and not for driver dma translation.
*/

说明配置了iommu=pt 的话函数iommu_no_mapping返回1,那么驱动就直接return paddr,并不会真正调用到domain_pfn_mapping,直接用了物理地址少了一次映射性能当然会高一些。如果是跑KVM建议 passthrough=0,物理机场景 passthrough=1

iommu=pt并不会影响kvm/dpdk/spdk的性能,这三者本质上都是用户态驱动,iommu=pt只会影响内核驱动,能让内核驱动设备性能更高。

SMMU:

ChatGPT:SMMU代表的是”System MMU”,是一种硬件单元,通常用于处理设备DMA(直接内存访问)请求,以允许安全而有效地使用设备,同时保护系统内存不受意外访问和恶意攻击。SMMU的主要功能是将设备发出的DMA请求映射到正确的物理内存地址,同时确保设备无法访问不属于其权限范围的内存区域。SMMU通常与ARM和其他芯片架构一起使用,以提高系统安全性和性能。

Google: SMMU(System Memory Management Unit)是Arm平台的IOMMU, SMMU为设备提供用设备可见的IOVA地址来访问物理内存的能力,体系结构中可能存在多个设备使用IOVA经过IOMMU来访问物理内存,IOMMU需要能够区分不同的设备,从而为每个设备引入了一个Stream ID,指向对应的STE(Stream Table Entry),所有的STE在内存中以数组的形式存在,SMMU记录STE数组的首地址。在操作系统扫描设备的时候会为其分配独有的Stream ID简称sid,设备通过IOMMU进行访存的所有配置都写在对应sid的STE中。

在非虚拟化场景下使能IOMMU/SMMU会带来性能衰减,主要是因为在DMA场景下要iova 到 pa的翻译,带来开销。当前集团的ARM机型,在非云化环境下都是SMMU OFF的,云化机型才是开启SMMU。

定制内存

物理内存700多G,要求OS只能用512G:

1
2
3
4
5
6
7
8
9
10
11
12
24条32G的内存条,总内存768G
# dmidecode -t memory |grep "Size: 32 GB"
Size: 32 GB
…………
Size: 32 GB
Size: 32 GB
root@uos15:/etc# dmidecode -t memory |grep "Size: 32 GB" | wc -l
24

# cat /boot/grub/grub.cfg |grep 512
linux /vmlinuz-4.19.0-arm64-server root=UUID=dbc68010-8c36-40bf-b794-271e59ff5727 ro splash quiet console=tty video=VGA-1:1280x1024@60 mem=512G DEEPIN_GFXMODE=$DEEPIN_GFXMODE
linux /vmlinuz-4.19.0-arm64-server root=UUID=dbc68010-8c36-40bf-b794-271e59ff5727 ro splash quiet console=tty video=VGA-1:1280x1024@60 mem=512G DEEPIN_GFXMODE=$DEEPIN_GFXMODE

高级版 按numa限制内存

每个numa 128G内存,总共1024G(32条*32G),8个numa node,需要将每个numa node内存限制在64G

在grub中cmdline中加入如下配置,每个node只用64G内存:

1
memmap=64G\$64G memmap=64G\$192G memmap=64G\$320G memmap=64G\$448G memmap=64G\$576G memmap=64G\$704G memmap=64G\$832G memmap=64G\$960G

或者:

1
2
3
4
5
6
7
8
9
10
11
#cat /etc/default/grub
GRUB_TIMEOUT=5
GRUB_DISTRIBUTOR="$(sed 's, release .*$,,g' /etc/system-release)"
GRUB_DEFAULT=saved
GRUB_DISABLE_SUBMENU=true
GRUB_TERMINAL_OUTPUT="console"
GRUB_CMDLINE_LINUX="crashkernel=1024M,high resume=/dev/mapper/klas-swap rd.lvm.lv=klas/root rd.lvm.lv=klas/swap video=efifb:on rhgb quiet quiet noibrs noibpb nopti nospectre_v2 nospectre_v1 l1tf=off nospec_store_bypass_disable no_stf_barrier mds=off tsx=on tsx_async_abort=off mitigations=off iommu.passthrough=1 memmap=64G\\\$64G memmap=64G\\\$192G memmap=64G\\\$320G memmap=64G\\\$448G memmap=64G\\\$576G memmap=64G\\\$704G memmap=64G\\\$832G memmap=64G\\\$960G"
GRUB_DISABLE_RECOVERY="true"

然后执行生成最终grub启动参数
sudo grub2-mkconfig -o /boot/grub2/grub.cfg

比如在一个4node的机器上,总共768G内存(32G*24),每个node使用64G内存

1
linux16 /vmlinuz-0-rescue-e91413f0be2c4c239b4aa0451489ae01 root=/dev/mapper/centos-root ro crashkernel=auto rd.lvm.lv=centos/root rd.lvm.lv=centos/swap rhgb quiet memmap=128G\$64G memmap=128G\$256G memmap=128G\$448G memmap=128G\$640G

128G表示相对地址,$64G是绝对地址,128G\$64G 的意思是屏蔽64G到(64+128)G的地址对应的内存

检查

检查正在运行的系统使用的grub参数:

1
cat /proc/cmdline

内存信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#dmidecode -t memory
# dmidecode 3.2
Getting SMBIOS data from sysfs.
SMBIOS 3.2.1 present.
# SMBIOS implementations newer than version 3.2.0 are not
# fully supported by this version of dmidecode.

Handle 0x0033, DMI type 16, 23 bytes
Physical Memory Array
Location: System Board Or Motherboard
Use: System Memory
Error Correction Type: Multi-bit ECC
Maximum Capacity: 2 TB //最大支持2T
Error Information Handle: 0x0032
Number Of Devices: 32 //32个插槽

Handle 0x0041, DMI type 17, 84 bytes
Memory Device
Array Handle: 0x0033
Error Information Handle: 0x0040
Total Width: 72 bits
Data Width: 64 bits
Size: 32 GB
Form Factor: DIMM
Set: None
Locator: CPU0_DIMMA0
Bank Locator: P0 CHANNEL A
Type: DDR4
Type Detail: Synchronous Registered (Buffered)
Speed: 2933 MT/s //内存最大频率
Manufacturer: SK Hynix
Serial Number: 220F9EC0
Asset Tag: Not Specified
Part Number: HMAA4GR7AJR8N-WM
Rank: 2
Configured Memory Speed: 2400 MT/s //内存实际运行速度--比如内存条数插太多会给内存降频
Minimum Voltage: 1.2 V
Maximum Voltage: 1.2 V
Configured Voltage: 1.2 V
Memory Technology: DRAM
Memory Operating Mode Capability: Volatile memory
Module Manufacturer ID: Bank 1, Hex 0xAD
Non-Volatile Size: None
Volatile Size: 32 GB

#lshw
*-bank:19
description: DIMM DDR4 Synchronous Registered (Buffered) 2933 MHz (0.3 ns) //内存最大频率
product: HMAA4GR7AJR8N-WM
vendor: SK Hynix
physical id: 13
serial: 220F9F63
slot: CPU1_DIMMB0
size: 32GiB //实际所插内存大小
width: 64 bits
clock: 2933MHz (0.3ns)

内存速度对延迟的影响

左边两列是同一种机型和CPU、内存,只是最左边的开了numa,他们的内存Speed: 2400 MT/s,但是实际运行速度是2133;最右边的是另外一种CPU,内存速度更快,用mlc测试他们的延时、带宽。可以看到V52机型带宽能力提升特别大,时延变化不大

image-20220123094155595

image-20220123094928794

image-20220123100052242

对比一下V62,intel8269 机型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
./Linux/mlc
Intel(R) Memory Latency Checker - v3.9
Measuring idle latencies (in ns)...
Numa node
Numa node 0 1
0 77.9 143.2
1 144.4 78.4

Measuring Peak Injection Memory Bandwidths for the system
Bandwidths are in MB/sec (1 MB/sec = 1,000,000 Bytes/sec)
Using all the threads from each core if Hyper-threading is enabled
Using traffic with the following read-write ratios
ALL Reads : 225097.1
3:1 Reads-Writes : 212457.8
2:1 Reads-Writes : 210628.1
1:1 Reads-Writes : 199315.4
Stream-triad like: 190341.4

Measuring Memory Bandwidths between nodes within system
Bandwidths are in MB/sec (1 MB/sec = 1,000,000 Bytes/sec)
Using all the threads from each core if Hyper-threading is enabled
Using Read-only traffic type
Numa node
Numa node 0 1
0 113139.4 50923.4
1 50916.6 113249.2

Measuring Loaded Latencies for the system
Using all the threads from each core if Hyper-threading is enabled
Using Read-only traffic type
Inject Latency Bandwidth
Delay (ns) MB/sec
==========================
00000 261.50 225452.5
00002 263.79 225291.6
00008 269.02 225184.1
00015 261.96 225757.6
00050 260.56 226013.2
00100 264.27 225660.1
00200 130.61 195882.4
00300 102.65 133820.1
00400 95.04 101353.2
00500 91.56 81585.9
00700 87.94 58819.1
01000 85.54 41551.3
01300 84.70 32213.6
01700 83.14 24872.5
02500 81.74 17194.3
03500 81.14 12524.2
05000 80.74 9013.2
09000 80.09 5370.0
20000 78.92 2867.2

Measuring cache-to-cache transfer latency (in ns)...
Local Socket L2->L2 HIT latency 51.6
Local Socket L2->L2 HITM latency 51.7
Remote Socket L2->L2 HITM latency (data address homed in writer socket)
Reader Numa Node
Writer Numa Node 0 1
0 - 111.3
1 111.1 -
Remote Socket L2->L2 HITM latency (data address homed in reader socket)
Reader Numa Node
Writer Numa Node 0 1
0 - 175.8
1 176.7 -

[root@numaopen.cloud.et93 /home/admin]
#lscpu
Architecture: x86_64
CPU op-mode(s): 32-bit, 64-bit
Byte Order: Little Endian
CPU(s): 104
On-line CPU(s) list: 0-103
Thread(s) per core: 2
Core(s) per socket: 26
Socket(s): 2
NUMA node(s): 2
Vendor ID: GenuineIntel
CPU family: 6
Model: 85
Model name: Intel(R) Xeon(R) Platinum 8269CY CPU @ 2.50GHz
Stepping: 7
CPU MHz: 3199.902
CPU max MHz: 3800.0000
CPU min MHz: 1200.0000
BogoMIPS: 4998.89
Virtualization: VT-x
L1d cache: 32K
L1i cache: 32K
L2 cache: 1024K
L3 cache: 36608K
NUMA node0 CPU(s): 0-25,52-77
NUMA node1 CPU(s): 26-51,78-103

#dmidecode -t memory
Handle 0x003C, DMI type 17, 40 bytes
Memory Device
Array Handle: 0x0026
Error Information Handle: Not Provided
Total Width: 72 bits
Data Width: 64 bits
Size: 32 GB
Form Factor: DIMM
Set: None
Locator: CPU1_DIMM_E1
Bank Locator: NODE 2
Type: DDR4
Type Detail: Synchronous
Speed: 2666 MHz
Manufacturer: Samsung
Serial Number: 14998029
Asset Tag: CPU1_DIMM_E1_AssetTag
Part Number: M393A4K40BB2-CTD
Rank: 2
Configured Clock Speed: 2666 MHz
Minimum Voltage: 1.2 V
Maximum Voltage: 1.2 V
Configured Voltage: 1.2 V

BIOS定制

ipmitool

直接在linux内设置主板bios,然后重启就可以生效:

1
2
3
4
5
6
7
//Hygon C86 7260 24-core Processor 设置numa node(hygon 7280 就不行了)
ipmitool raw 0x3e 0x5c 0x00 0x01 0x81
ipmitool raw 0x3e 0x5c 0x05 0x01 0x81

//查询是否生效, 注意是 0x5d
#ipmitool raw 0x3e 0x5d 0x00 0x01
01 81

ipmitool使用

基本语法:ipmitool raw 0x3e 0x5c index 0x01 value

raw 0x3e 0x5c 固定不需要改,

Index表示需要修改的配置项, 接下来的 0x01 固定不需要改

value 表示值,0x81表示enable; 0x80表示disable

中间件的vipclient服务在centos7上域名解析失败

我们申请了一批ECS,操作系统是centos7,这些ECS部署了中间件的DNS服务(vipclient),但是发现这个时候域名解析失败,而同样的配置在centos6上就运行正确

抓包分析

分别在centos6、centos7上nslookup通过同一个DNS Server解析同一个域名,并抓包比较得到如下截图(为了方便我将centos6、7抓包做到了一张图上):

image.png

绿色部分是正常的解析(centos6),红色部分是解析,多了一个OPT(centos7)

赶紧Google一下OPT,原来DNS协议还有一个extention,参考这里

而centos7默认启用edns,但是vipclient实现的时候没有支持edns,所以 centos7 解析域名就出了问题

通过 dig 命令来查看dns解析过程

在centos7上,通过命令 dig edas.console.cztest.com 解析失败,但是改用这个命令禁用edns后就解析正常了:dig +noedns edas.console.cztest.com

vipclient会启动一个53端口,在上面监听dns query,也就是自己就是一个DNS Service

img

分析vipclient域名解析返回的包内容

image.png

把上图中最后4个16进制翻译成10进制IP地址,这个IP地址正是域名所对应的IP,可见vipclient收到域名解析后,因为看不懂edns协议,就按照自己的理解返回了结果,客户端收到这个结果后按照edns协议解析不出来IP,也就是两个的协议不对等导致了问题

总结

centos7之前默认都不启用edns,centos7后默认启用edns,但是vipclient目前不支持edns
通过命令:dig +noedns edas.console.cztest.com 能解析到域名所对应的IP
但是命令:dig edas.console.cztest.com 解析不到IP,因为vipclient(相当于这里的dns server)没有兼容edns,实际返回的结果带了IP但是客户端不支持edns协议所以解析不到(vipclient返回的格式、规范不对)

Docker中的DNS解析过程

问题描述

同一个Docker集群中两个容器中通过 nslookup 同一个域名,这个域名是容器启动的时候通过net alias指定的,但是返回来的IP不一样

如图所示:

image.png

图中上面的容器中 nslookup 返回来了两个IP,但是只有146是正确的,155是多出来,不对的。

要想解决这个问题抓包就没用了,因为Docker 中的net alias 域名是 Docker Daemon自己来解析的,也就是在容器中做域名解析(nslookup、ping)的时候,Docker Daemon先看这个域名是不是net alias的,是的话返回对应的ip,如果不是(比如 www.baidu.com) ,那么Docker Daemon再把这个域名丢到宿主机上去解析,在宿主机上的解析过程就是标准的DNS,可以抓包分析。但是Docker Daemon内部的解析过程没有走DNS协议,不好分析,所以得先了解一下 Docker Daemon的域名解析原理

具体参考文章: http://www.jianshu.com/p/4433f4c70cf0 http://www.bijishequ.com/detail/261401?p=70-67

继续分析所有容器对这个域名的解析

继续分析所有容器对这个域名的解析发现只有某一台宿主机上的有这个问题,而且这台宿主机上所有容器都有这个问题,结合上面的文章,那么这个问题比较明朗了,这台有问题的宿主机的Docker Daemon中残留了一个net alias,你可以理解成cache中有脏数据没有清理。

进而跟业务的同学们沟通,确实155这个IP的容器做过升级,改动过配置,可能升级前这个155也绑定过这个域名,但是升级后绑到146这个容器上去了,但是Docker Daemon中还残留这条记录。

重启Docker Daemon后问题解决(容器不需要重启)

重启Docker Daemon的时候容器还在正常运行,只是这段时间的域名解析会不正常,其它业务长连接都能正常运行,在Docker Daemon重启的时候它会去检查所有容器的endpoint、重建sandbox、清理network等等各种事情,所以就把这个脏数据修复掉了。

在Docker Daemon重启过程中,会给每个容器构建DNS Resovler(setup-resolver),如果构建DNS Resovler这个过程中容器发送了大量域名查询过来同时这些域名又查询不到的话Docker Daemon在重启过程中需要等待这个查询超时,然后才能继续往下走重启流程,所以导致启动流程拉长问题严重的时候导致Docker Daemon长时间无法启动

Docker的域名解析为什么要这么做,是因为容器中有两种域名解析需求:

  1. 容器启动时通过 net alias 命名的域名
  2. 容器中正常对外网各种域名的解析(比如 baidu.com/api.taobao.com)

对于第一种只能由docker daemon来解析了,所以容器中碰到的任何域名解析都会丢给 docker daemon(127.0.0.11), 如果 docker daemon 发现这个域名不认识,也就是不是net alias命名的域名,那么docker就会把这个域名解析丢给宿主机配置的 nameserver 来解析【非常非常像 dns-f/vipclient 的解析原理】

容器中的域名解析

容器启动的时候读取宿主机的 /etc/resolv.conf (去掉127.0.0.1/16 的nameserver)然后当成容器的 /etc/resolv.conf, 但是实际在容器中看到的 /etc/resolve.conf 中的nameserver只有一个:127.0.0.11,因为如上描述nameserver都被代理掉了

容器 -> docker daemon(127.0.0.11) -> 宿主机中的/etc/resolv.conf 中的nameserver

如果宿主机中的/etc/resolv.conf 中的nameserver没有,那么daemon默认会用8.8.8.8/8.8.4.4来做下一级dns server,如果在一些隔离网络中(跟外部不通),那么域名解析就会超时,因为一直无法连接到 8.8.8.8/8.8.4.4 ,进而导致故障。

比如 vipserver 中需要解析 armory的域名,如果这个时候在私有云环境,宿主机又没有配置 nameserver, 那么这个域名解析会发送给 8.8.8.8/8.8.4.4 ,长时间没有响应,超时后 vipserver 会关闭自己的探活功能,从而导致 vipserver 基本不可用一样。

修改 宿主机的/etc/resolv.conf后 重新启动、创建的容器才会load新的nameserver

如果容器中需要解析vipserver中的域名

  1. 容器中安装vipclient,同时容器的 /etc/resolv.conf 配置 nameserver 127.0.0.1
  2. 要保证vipclient起来之后才能启动业务

kubernetes中dns解析偶尔5秒钟超时

dns解析默认会发出ipv4和ipv6,一般dns没有配置ipv6,会导致ipv6解析等待5秒超时后再发出ipv4解析得到正确结果。应用表现出来就是偶尔卡顿了5秒

img

(高亮行delay 5秒才发出查询,是因为高亮前一行等待5秒都没有等到查询结果)

解析异常的strace栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
不正常解析的strace日志:
1596601737.655724 socket(PF_INET, SOCK_DGRAM|SOCK_NONBLOCK, IPPROTO_IP) = 5
1596601737.655784 connect(5, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.68.0.2")}, 16) = 0
1596601737.655869 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
1596601737.655968 sendmmsg(5, {{{msg_name(0)=NULL, msg_iov(1)=[{"\20\v\1\0\0\1\0\0\0\0\0\0\20redis-7164-b5lzv\7cluster\5local\0\0\1\0\1", 48}], msg_controllen=0, msg_flags=MSG_TRUNC|MSG_EOR|MSG_FIN|MSG_RST|MSG_ERRQUEUE|MSG_NOSIGNAL|MSG_MORE|MSG_WAITFORONE|MSG_FASTOPEN|0x1e340010}, 48}, {{msg_name(0)=NULL, msg_iov(1)=[{"\207\250\1\0\0\1\0\0\0\0\0\0\20redis-7164-b5lzv\7cluster\5local\0\0\34\0\1", 48}], msg_controllen=0, msg_flags=MSG_WAITALL|MSG_FIN|MSG_ERRQUEUE|MSG_NOSIGNAL|MSG_FASTOPEN|MSG_CMSG_CLOEXEC|0x156c0000}, 48}}, 2, MSG_NOSIGNAL) = 2
1596601737.656113 poll([{fd=5, events=POLLIN}], 1, 5000) = 1 ([{fd=5, revents=POLLIN}])
1596601737.659251 ioctl(5, FIONREAD, [141]) = 0
1596601737.659330 recvfrom(5, "\207\250\201\203\0\1\0\0\0\1\0\0\20redis-7164-b5lzv\7cluster\5local\0\0\34\0\1\7cluster\5local\0\0\6\0\1\0\0\0\10\0D\2ns\3dns\7cluster\5local\0\nhostmaster\7cluster\5local\0_*5T\0\0\34 \0\0\7\10\0\1Q\200\0\0\0\36", 2048, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.68.0.2")}, [16]) = 141
=========
1596601737.659421 poll([{fd=5, events=POLLIN}], 1, 4996) = 0 (Timeout) //这里就是问题所在
=========
1596601742.657639 poll([{fd=5, events=POLLOUT}], 1, 0) = 1 ([{fd=5, revents=POLLOUT}])
1596601742.657735 sendto(5, "\20\v\1\0\0\1\0\0\0\0\0\0\20redis-7164-b5lzv\7cluster\5local\0\0\1\0\1", 48, MSG_NOSIGNAL, NULL, 0) = 48
1596601742.657837 poll([{fd=5, events=POLLIN}], 1, 5000) = 1 ([{fd=5, revents=POLLIN}])
1596601742.660929 ioctl(5, FIONREAD, [141]) = 0
1596601742.661038 recvfrom(5, "\20\v\201\203\0\1\0\0\0\1\0\0\20redis-7164-b5lzv\7cluster\5local\0\0\1\0\1\7cluster\5local\0\0\6\0\1\0\0\0\3\0D\2ns\3dns\7cluster\5local\0\nhostmaster\7cluster\5local\0_*5T\0\0\34 \0\0\7\10\0\1Q\200\0\0\0\36", 2048, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.68.0.2")}, [16]) = 141
1596601742.661129 poll([{fd=5, events=POLLOUT}], 1, 4996) = 1 ([{fd=5, revents=POLLOUT}])
1596601742.661204 sendto(5, "\207\250\1\0\0\1\0\0\0\0\0\0\20redis-7164-b5lzv\7cluster\5local\0\0\34\0\1", 48, MSG_NOSIGNAL, NULL, 0) = 48
1596601742.661313 poll([{fd=5, events=POLLIN}], 1, 4996) = 1 ([{fd=5, revents=POLLIN}])
1596601742.664443 ioctl(5, FIONREAD, [141]) = 0
1596601742.664519 recvfrom(5, "\207\250\201\203\0\1\0\0\0\1\0\0\20redis-7164-b5lzv\7cluster\5local\0\0\34\0\1\7cluster\5local\0\0\6\0\1\0\0\0\3\0D\2ns\3dns\7cluster\5local\0\nhostmaster\7cluster\5local\0_*5T\0\0\34 \0\0\7\10\0\1Q\200\0\0\0\36", 65536, 0, {sa_family=AF_INET, sin_port=htons(53), sin_addr=inet_addr("10.68.0.2")}, [16]) = 141
1596601742.664600 close(5) = 0

原因分析

DNS client (glibc 或 musl libc) 会并发请求 A 和 AAAA 记录,跟 DNS Server 通信自然会先 connect (建立fd),后面请求报文使用这个 fd 来发送,由于 UDP 是无状态协议, connect 时并不会发包,也就不会创建 conntrack 表项, 而并发请求的 A 和 AAAA 记录默认使用同一个 fd 发包,send 时各自发的包它们源 Port 相同(因为用的同一个socket发送),当并发发包时,两个包都还没有被插入 conntrack 表项,所以 netfilter 会为它们分别创建 conntrack 表项,而集群内请求 kube-dns 或 coredns 都是访问的CLUSTER-IP,报文最终会被 DNAT 成一个 endpoint 的 POD IP,当两个包恰好又被 DNAT 成同一个 POD IP时,它们的五元组就相同了,在最终插入的时候后面那个包就会被丢掉,而single-request-reopen的选项设置为俩请求被丢了一个,会等待超时再重发 ,这个就解释了为什么还存在调整成2s就是2s的异常比较多 ,因此这种场景下调整成single-request是比较好的方式,同时k8s那边给的dns缓存方案是 nodelocaldns组件可以考虑用一下

关于recolv的选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
single-request (since glibc 2.10) 串行解析,
Sets RES_SNGLKUP in _res.options. By default, glibc
performs IPv4 and IPv6 lookups in parallel since
version 2.9. Some appliance DNS servers cannot handle
these queries properly and make the requests time out.
This option disables the behavior and makes glibc
perform the IPv6 and IPv4 requests sequentially (at the
cost of some slowdown of the resolving process).
single-request-reopen (since glibc 2.9) 并行解析,少收到一个解析回复后,再开一个socket重新发起解析,因此看到了前面调整timeout是1s后,还是有挺多1s的解析
Sets RES_SNGLKUPREOP in _res.options. The resolver
uses the same socket for the A and AAAA requests. Some
hardware mistakenly sends back only one reply. When
that happens the client system will sit and wait for
the second reply. Turning this option on changes this
behavior so that if two requests from the same port are
not handled correctly it will close the socket and open
a new one before sending the second request.

getaddrinfo 关闭ipv6的解析

基本上所有测试下来,网上那些通过修改配置的基本都不能关闭ipv6的解析,只有通过在代码中指定

hints.ai_family = AF_INET; /* or AF_INET6 for ipv6 addresses */

来只做ipv4的解析

Prefer A (IPv4) DNS lookups before AAAA(IPv6) lookups

https://man7.org/linux/man-pages/man3/getaddrinfo.3.html:

1
2
3
4
5
6
7
8
9
If hints.ai_flags includes the AI_ADDRCONFIG flag, then IPv4
addresses are returned in the list pointed to by res only if the
local system has at least one IPv4 address configured, and IPv6
addresses are returned only if the local system has at least one
IPv6 address configured. The loopback address is not considered
for this case as valid as a configured address. This flag is
useful on, for example, IPv4-only systems, to ensure that
getaddrinfo() does not return IPv6 socket addresses that would
always fail in connect(2) or bind(2).

c code demo:

1
2
3
4
5
6
7
struct addrinfo hints, *result;
int s;

memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_INET; /* or AF_INET6 for ipv6 addresses */
s = getaddrinfo(NULL, "ftp", &hints, &result);
...

or

In the Wireshark capture, 172.25.50.3 is the local DNS resolver; the capture was taken there, so you also see its outgoing queries and responses. Note that only an A record was requested. No AAAA lookup was ever done.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <stdlib.h>
#include <netdb.h>
#include <stdio.h>

int main(void) {
struct addrinfo hints;
struct addrinfo *result, *rp;
int s;
char host[256];

memset(&hints, 0, sizeof(struct addrinfo));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = 0;

s = getaddrinfo("www.facebook.com", NULL, &hints, &result);
if (s != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s));
exit(EXIT_FAILURE);
}

for (rp = result; rp != NULL; rp = rp->ai_next) {
getnameinfo(rp->ai_addr, rp->ai_addrlen, host, sizeof(host), NULL, 0, NI_NUMERICHOST);
printf("%s\n", host);
}
freeaddrinfo(result);
}

or:https://unix.stackexchange.com/questions/9940/convince-apt-get-not-to-use-ipv6-method

/etc/gai.conf getaddrinfo的配置文件

Prefix Precedence Label Usage
::1/128 50 0 Localhost
::/0 40 1 Default unicast
::ffff:0:0/96 35 4 IPv4-mapped IPv6 address
2002::/16 30 2 6to4
2001::/32 5 5 Teredo tunneling
fc00::/7 3 13 Unique local address
::/96 1 3 IPv4-compatible addresses (deprecated)
fec0::/10 1 11 Site-local address (deprecated)
3ffe::/16 1 12 6bone (returned)

来源于维基百科

0:0:0:0:0:ffff:0:0/96 10 4 IPv4映射地址(这个地址网络上信息较少,地址范围::: ffff:0.0.0.0~:: ffff:255.255.255.255 地址数量2 128−96 = 2 32 = 4 294 967 296,用于软件,目的是IPv4映射的地址。 )

参考资料

Kubernetes >= 1.13 + kube-proxy IPVS mode 服务部署不平滑

linux ipv4 ipv6双栈 (优先ipv4而不使用ipv6配置)

windows7的wifi总是报DNS域名异常无法上网

Windows7笔记本+公司wifi(dhcp)环境下,用着用着dns服务不可用(无法通过域名上网,通过IP地址可以访问),这里有个一模一样的Case了:https://superuser.com/questions/629559/why-is-my-computer-suddenly-using-nbns-instead-of-dns 一样的环境,看来这个问题也不只是我一个人碰到了。

其实之前一直有,一个月偶尔出来一两次,以为是其他原因就没管,这次换了新电脑还是这个毛病有点不能忍,于是决定彻底解决一下。

这个问题出现后,通过下面三个办法都可以让DNS恢复正常:

  1. 重启系统大法,恢复正常
  2. 禁用wifi驱动再启用,恢复正常
  3. 不用DHCP,而是手工填入一个DNS服务器,比如114.114.114.114【公司域名就无法解析了】

如果只是停用一下wifi再启用问题还在。

找IT升级了网卡驱动不管用

重现的时候抓包看看

image.png

这肯定不对了,254上根本就没有跑DNS服务,可是当时没有检查 ipconfig,看看是否将网关IP动态配置到dns server里面去了,等下次重现后再确认吧。

第二次重现后抓包,发现不一样了:

image.png

出来一个 NBNS 的鬼东西,赶紧查了一下,把它禁掉,如下图所示:

image.png

把NBNS服务关了就能上网了,同时也能抓到各种DNS Query包

事情没有这么简单

过一段时间后还是会出现上面的症状,但是因为NBNS关闭了,所以这次 ping www.baidu.com 的时候没有任何包了,没有DNS Query包,也没有NBNS包,这下好尴尬。

尝试Enable NBNS,又恢复了正常,看来开关 NBNS 仍然只是一个workaround,他不是导致问题的根因,开关一下没有真正解决问题,只是临时相当于重启了dns修复了问题而已。

继续在网络不通的时候尝试直接ping dns server ip,发现一个奇怪的现象,丢包很多,丢包的时候还总是从 192.168.0.11返回来的,这就奇怪了,我的笔记本基本IP是30开头的,dns server ip也是30开头的,route 路由表也是对的,怎么就走到 192.168.0.11 上了啊(参考我的另外一篇文章,网络到底通不通),赶紧 ipconfig /all | grep 192

image.png

发现这个IP是VirtualBox虚拟机在笔记本上虚拟出来的网卡IP,这下我倒是能理解为啥总是我碰到这个问题了,因为我的工作笔记本一拿到后第一件事情就是装VirtualBox 跑虚拟机。

VirtualBox为啥导致了这个问题就是一个很偏的方向,我实在无能为力了,尝试找到了一个和VirtualBox的DNS相关的开关命令,只能死马当活马医了(像极了算命大师和老中医)

./VBoxManage.exe  modifyvm "ubuntu" --natdnshostresolver1 on

执行完上面的命令观察了3个月了,暂时没有再出现这个问题,相对于以前轻则一个月2、3次,重则一天出现5、6次,应该算是解决了,同时升级 VirtualBox 也无法解决这个问题。

route 信息:

$route PRINT -4
===========================================================================
接口列表
 23...00 ff c1 57 7f 12 ......Sangfor SSL VPN CS Support System VNIC
 18...f6 96 34 38 76 06 ......Microsoft Virtual WiFi Miniport Adapter #2
 17...f6 96 34 38 76 07 ......Microsoft Virtual WiFi Miniport Adapter
 15...00 ff 1f 24 e6 6c ......Sophos SSL VPN Adapter
 12...f4 96 34 38 76 06 ......Intel(R) Dual Band Wireless-AC 8260
 11...54 ee 75 d4 99 ae ......Intel(R) Ethernet Connection I219-V
 14...0a 00 27 00 00 0e ......VirtualBox Host-Only Ethernet Adapter
  1...........................Software Loopback Interface 1
 25...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter
 19...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter #9
 26...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter #2
 27...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter #3
 22...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter #7
 21...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter #5
 20...00 00 00 00 00 00 00 e0 Microsoft 6to4 Adapter #2
 16...00 00 00 00 00 00 00 e0 Teredo Tunneling Pseudo-Interface
 24...00 00 00 00 00 00 00 e0 Microsoft ISATAP Adapter #8
===========================================================================

IPv4 路由表
===========================================================================
活动路由:
网络目标网络掩码  网关   接口   跃点数
  0.0.0.0  0.0.0.0192.168.0.250169.254.24.89266
  0.0.0.0  0.0.0.030.27.115.254 30.27.112.21 20
  30.27.112.0255.255.252.0在链路上  30.27.112.21276
 30.27.112.21  255.255.255.255在链路上  30.27.112.21276
30.27.115.255  255.255.255.255在链路上  30.27.112.21276
127.0.0.0255.0.0.0在链路上 127.0.0.1306
127.0.0.1  255.255.255.255在链路上 127.0.0.1306
  127.255.255.255  255.255.255.255在链路上 127.0.0.1306
  169.254.0.0  255.255.0.0在链路上 169.254.24.89266
169.254.24.89  255.255.255.255在链路上 169.254.24.89266
  169.254.255.255  255.255.255.255在链路上 169.254.24.89266
224.0.0.0240.0.0.0在链路上 127.0.0.1306
224.0.0.0240.0.0.0在链路上 169.254.24.89266
224.0.0.0240.0.0.0在链路上  30.27.112.21276
  255.255.255.255  255.255.255.255在链路上 127.0.0.1306
  255.255.255.255  255.255.255.255在链路上 169.254.24.89266
  255.255.255.255  255.255.255.255在链路上  30.27.112.21276
===========================================================================
永久路由:
  网络地址  网络掩码  网关地址  跃点数
  0.0.0.0  0.0.0.0192.168.0.250 默认
  0.0.0.0  0.0.0.0192.168.0.250 默认
===========================================================================

另外DHCP也许可以做一些事情,至少同样的用法在以前的公司网络环境没有出过问题

下面是来自微软官方的建议:

One big advise – do not disable the DHCP Client service on any server, whether the machine is a DHCP client or statically configured. Somewhat of a misnomer, this service performs Dynamic DNS registration and is tied in with the client resolver service. If disabled on a DC, you’ll get a slew of errors, and no DNS queries will get resolved.

No DNS Name Resolution If DHCP Client Service Is Not Running. When you try to resolve a host name using Domain Name Service (DNS), the attempt is unsuccessful. Communication by Internet Protocol (IP) address (even to …

http://support.microsoft.com/kb/268674

from: https://blogs.msmvps.com/acefekay/2009/11/29/dns-wins-netbios-amp-the-client-side-resolver-browser-service-disabling-netbios-direct-hosted-smb-directsmb-if-one-dc-is-down-does-a-client-logon-to-another-dc-and-dns-forwarders-algorithm/#section4

NBNS也许会导致nslookup OK but ping fail的问题

https://www.experts-exchange.com/questions/28894006/NetBios-name-resolution-instead-of-DNS.html

The Windows Client Resolver(ping dns流程)

  1. Windows checks whether the host name is the same as the local host name.
  2. If the host name and local host name are not the same, Windows searches the DNS client resolver cache.
  3. If the host name cannot be resolved using the DNS client resolver cache, Windows sends DNS Name Query Request messages to its configured DNS servers.
  4. If the host name is a single-label name (such as server1) and cannot be resolved using the configured DNS servers, Windows converts the host name to a NetBIOS name and checks its local NetBIOS name cache.
  5. If Windows cannot find the NetBIOS name in the NetBIOS name cache, Windows contacts its configured WINS servers.
  6. If Windows cannot resolve the NetBIOS name by querying its configured WINS servers, Windows broadcasts as many as three NetBIOS Name Query Request messages on the directly attached subnet.
  7. If there is no reply to the NetBIOS Name Query Request messages, Windows searches the local Lmhosts file.
    Ping

windows下nslookup 流程

  1. Check the DNS resolver cache. This is true for records that were cached via a previous name query or records that are cached as part of a pre-load operation from updating the hosts file.
  2. Attempt NetBIOS name resolution.
  3. Append all suffixes from the suffix search list.
  4. When a Primary Domain Suffix is used, nslookup will only take devolution 3 levels.

总结

碰到问题绕过去也不是长久之计,还是要从根本上了解问题的本质,这个问题在其它公司没有碰到过,我觉得跟公司的DNS、DHCP的配置也有点关系吧,但是这个我不好确认,应该还有好多用Windows本本的同学同样会碰到这个问题的,希望对你们有些帮助

https://support.microsoft.com/en-us/help/172218/microsoft-tcp-ip-host-name-resolution-order

http://www.man7.org/linux/man-pages/man5/resolv.conf.5.html


本文附带鸡汤:

有些技能初学很难,大家水平都差不多,但是日积月累就会形成极强的优势,而且一旦突破某个临界点,它就会突飞猛进,这种技能叫指数型技能,是值得长期投资的,比如物理学就是一种指数型技能。

那么抓包算不算呢?​​

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 高负载的系统化分析

0%