404频道

学习笔记

之前购买的网件R6400V2路由器刷到了梅林系统,但一直以来信号都特别差,甚至都比不过最便宜的水星路由器,想重新刷回官方系统看下是否是梅林系统的问题。本文记录下重新刷回梅林系统的操作步骤。

刷机之路

从如下的地址下载固件。

1
链接:https://pan.baidu.com/s/1EBvUBlXozo_4zkXaeUMQng 密码:pqqv

在梅林系统的界面上找到固件升级的地方,将下载的固件上传。

image.png
结果悲剧的事情发生了,路由器出现了不断重启的状态,并且没有无线信号。猜测可能是因为固件用的是R6400,而非R6400V2导致的。或者是因为是用无线网络升级的原因,导致升级到一半网断掉了,从而导致失败了。

救砖之路

按照网件通用救砖,超详细教程文档中的网件通用救砖方法,将路由器的一个LAN口跟电脑的网卡用网线直接相连,并设置网卡的ip地址为192.168.1.3,网关为255.255.255.0。

在路由器启动的状态下,在命令行执行ping -t 192.168.1.1来验证路由器是否可以ping通,如果不通,可能是因为路由器的LAN段不是192.168.1.0,可能是192.168.50.0的。

在windows电脑上下载对应的文件,并保存到本地的F盘下。

手工安装winpcap,主要是给nmrpflash来使用。

以管理员身份运行命令行工具,执行nmrpflash.exe -L后找到本机的网卡net1。
image.png
执行nmrpflash命令后,立即重启路由器,如果第一次提示Timeout,可以立即执行该命令,如果出现下图的提示,说明命令执行成功。
image.png

参考文档

MTU

MTU是指一个以太网帧能够携带的最大数据部分的大小,并不包含以太网的头部部分。一般情况下MTU的值为1500字节。

当指定的数据包大小超过MTU值时,ip层会根据当前的mtu值对超过数据包进行分片,并会设置ip层头部的More Fragments标志位,并会设置Fragment offset属性,即分片的第二个以及后续的数据包会增加offset,第一个数据包的offset值为0。接收方会根据ip头部的More Fragment标志位和Fragment offset属性来进行切片的重组。

如果手工将发送方的MTU值设置为较大值,比如9000(巨型帧),如果发送方设置了不分片(ip头部的Don’t fragment),此时如果发送的链路上有地方不支持该MTU,报文就会被丢弃。

offload特性

执行 ethtool -k ${device} 可以看到很多跟网络接口相关的特性,这些特性的目的是为了提升网络的收发性能。TSO、UFO和GSO是对应网络发送,LRO、GRO对应网络接收。

执行ethtool -K ${device} gro off/on 来开启或者关闭相关的特性。

LRO(Large Receive Offload)

通过将接收的多个tcp segment聚合为一个大的tcp包,然后传送给网络协议栈处理,以减少上层网络协议栈的处理开销。

但由于tcp segment并不是在同一时刻到达网卡,因此组装起来就会变得比较困难。

由于LRO的一些局限性,在最新的网络上,该功能已经删除。

GRO(Generic Receive Offload)

GRO是LRO的升级版,正在逐渐取代LRO。运行与内核态,不再依赖于硬件。

RSS hash 特性

网卡可以根据数据包放到不同的网卡队列来处理,并可以根据不同的数据协议来设置不同的值。

注意:该特性并非所有的网卡都支持

下面命令为查询 udp 协议的设置,可以看到 hash 的策略为根据源 ip 地址和目的 ip 地址。

1
2
3
4
$ ethtool -n eth0 rx-flow-hash udp4
UDP over IPV4 flows use these fields for computing Hash flow key:
IP SA
IP DA

可以使用 ethtool -N eth0 rx-flow-hash udp4 sdfn来修改hash 策略,sdfn对应的含义如下:

1
2
3
4
5
6
7
8
9
m   Hash on the Layer 2 destination address of the rx packet.
v Hash on the VLAN tag of the rx packet.
t Hash on the Layer 3 protocol field of the rx packet.
s Hash on the IP source address of the rx packet.
d Hash on the IP destination address of the rx packet.
f Hash on bytes 0 and 1 of the Layer 4 header of the rx packet.
n Hash on bytes 2 and 3 of the Layer 4 header of the rx packet.
r Discard all packets of this flow type. When this option is
set, all other options are ignored.

修改完成后再查看网卡的 hash 策略如下:

1
2
3
4
5
6
$ ethtool -n eth0 rx-flow-hash udp4
UDP over IPV4 flows use these fields for computing Hash flow key:
IP SA
IP DA
L4 bytes 0 & 1 [TCP/UDP src port]
L4 bytes 2 & 3 [TCP/UDP dst port]

参考文章

望京SOHO

又是很长的一段时间没有更新,果然又是不定期更新,文章的有些内容也是很久以前积累的,并不是因为太懒,而是确实没有太多的精力。

题图为雨中的望京SOHO,今年全国的雨水特别多,北京亦是如此。南方的鱼米之乡地区出现了严重的洪灾,不知道今年的粮食产量会受多大影响。我们的地球在人类翻天覆地的变更后实在经受不了太多的hack,愿雨季早日过去。

资源

1.bocker

bocker=bash + docker,其利用100多行bash代码实现的简易版的docker,使用到的底层技术跟docker是一致的,包括chroot、namespace、cgroup。

2.kubectx

天天操作k8s的工程师一定少不了使用kubectl命令,而用kubectl命令的工程师一定会特别烦天天输入-n ${namespace}这样的操作,该工具可以省去输入namespace的操作。刚开始的时候不是太习惯该工具,直到近期才感知到该工具的价值。🤦‍♂️

3.KubeOperator

https://raw.githubusercontent.com/KubeOperator/website/master/images/kubeoperator-ui.jpg

k8s集群的安装操作基本上都是黑屏来完成的,同时集群规模较大时,还需要一些自动化的手段来解决安装和运维物理机的问题。KubeOperator提供了界面化的操作来完成k8s集群的配置、安装、升级等的操作,底层也是调用了ansible来作为自动化的工具。该项目已经加入CNCF,期望后面可以做的功能更加强大,给k8s集群的运维带来便利。

4.awesome-operators

k8s生态的operator非常火爆,作为k8s扩展能力的一个重要组成部分,该项目汇总了常见的operator项目。

5.chaos-mesh

pingcap开源的Kubernetes的混沌工程项目,可以使用CRD的方式来注入故障到Kubernetes集群中。

6.devops-exercises

DevOps相关的一些面试题,涉及到的方面还是比较全的。

7.shell2http

可以将shell脚本放到业务页面上执行的工具,在web页面上点击按钮后,会执行shell脚本,shell脚本的输出会在web页面上显示。

8.Google Shell 风格指南

Google编程规范还是比较有权威性的,此为Shell的编码规范。

9.shellcheck

Shell作为弱类型的编程语言,稍有不慎还是非常容易写错语法的,至少很多的语法我是记不住的,每次都是边查语法边写🤦‍。该项目为Shell的静态检查工具,用来检查可能的语法错误,在Github上的start数量还是非常高的。

不仅支持命令行工具检查,而且还可以跟常用的编辑器集成(比如vim、vscode),用来实现边写边检查的效果。还提供了web界面,可以将shell脚本输入到web界面上来在线检查。

10.teambition

阿里的一款的远程协作工具,类似于国外slack+trello的结合版,在产品设计上能看到太多地方借鉴了trello,非常像是trello的本土化版本,更贴近国人的使用习惯,可用于管理团队和个人的任务。

11.IcePanel

IcePanel为vscode的一款插件,提供了k8s一些基础对象的编辑生成器,通过ui的界面即可生成k8s的ConfigMap、Deployment、Service等对象。

  1. Play with Kubernetes

一个提供在线的kubernetes集群的工具,在界面上点一下按钮就可以创建一个k8s集群,不需要注册,非常方便,但创建的集群只有四个小时的使用时间。可以用来熟悉k8s的基本操作,或者试验一些功能。

精彩文章

1.腾讯自研业务上云:优化Kubernetes集群负载的技术方案探讨

k8s虽然在服务器的资源利用率上比起传统的物理机或虚拟机部署服务方式有了非常大的提升,本文结合实践经验,从pod、node、hpa等多个维护来优化以便进一步的压榨服务器的资源。

书籍

1.[Linux开源网络全栈详解:从DPDK到OpenFlow])(http://product.china-pub.com/8061094)

该书可以作为全面了解开源软件网络的相关技术,涉及到Linux虚拟网络、DPDK、OpenStack、容器相关网络等知识。

2.Kubernetes 网络权威指南:基础、原理与实践

该书可以作为全面了解k8s相关的容器网络的相关技术,如果对k8s周边的虚拟网络知识有所全面了解,该书籍还是比较适合的。

在k8s中的内置资源很多都有status部分,比如deployment,用来标识当前资源的状态信息。同样在CRD的体系中,也都有status部分。这些status部分信息,是由operator来负责维护的。

如果直接采用kubectl edit的方式来修改status部分信息,会发现是无法直接修改status部分的,因为status是无法修改成功的,因为status部分是CR的一个子资源。

可以通过如下的方式来完成修改

  1. 首先要准备一个完整的yaml文件,包含了status部分信息

这个的格式必须为json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"apiVersion": "kuring.me/v1alpha1",
"kind": "Certificate",
"metadata": {
"name": "test",
"namespace": "default"
},
"spec": {
"secretName": "test"
},
"status": {
"phase": "pending"
}
}
  1. 获取系统的TOKEN信息

通常在kube-system下会有admin的ServiceAccount,会有一个对应的Secret来存放该ServiceAccount的token信息。执行kubectl get secret -n kube-system admin-token-r2bvt -o yaml获取到token信息,并其中的token部分进行base64解码。

  1. 执行如下的脚本

需要将其中的变量信息修改一下

1
2
3
4
5
6
7
8
9
10
11
#!/bin/bash

obj_file=$1
kind=certificates
APISERVER=https://10.0.0.100:6443
namespace=default
TOKEN=eyJhbGciOiJSUzI1NiIsImtpZCI6IiJ9.eyJpc3MiOiJrdWJlcm5ldGVzL3NlcnZpY2VhY2NvdW50Iiwia3ViZXJuZXRlcy5pby9zZXJ2aWNlYWNjb3VudC9uYW1lc3BhY2UiOiJrdWJlLXN5c3RlbSIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VjcmV0Lm5hbWUiOiJhZG1pbi10b2tlbi1yMmJ2dCIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50Lm5hbWUiOiJhZG1pbiIsImt1YmVybmV0ZXMuaW8vc2VydmljZWFjY291bnQvc2VydmljZS1hY2NvdW50LnVpZCI6ImRiY2IyNzUzLWE5OGMtMTFlYS04NGVjLTAwMTYzZTAwOGU3MCIsInN1YiI6InN5c3RlbTpzZXJ2aWNlYWNjb3VudDprdWJlLXN5c3RlbTphZG1pbiJ9.tX3jyNh-GEuZQg-hmy7igqh9vpTAz8Jh9uEv-diZ5XWjX9JYhxwD9nxTQvCcvzY7iPIbvxQfW2GHDZISPoopX0vQy9mQ7npVitrOvFovk06plefI5Gxjdft6vdpt-ArsGTpm7-s9G-3aBg5x41h3Cdgyv-W-ypFlCr9dKu9K7BcRIXSq_GQlq5TBmd-LKFXoer4QGwkn7geq5-ziMk_lY21jIGVdIkq9IRiH8NWuCl7l8i6nQESQDUUpMyKDCqkJqUFV8UkrQL7TfqurFP36_TUAQTh2ZAE8nFnrKRoa09BnjT-FoPO6Jnq6COQjk3PGDHV8LKNDAjCCrs0A53IYGw
obj=nginx-test

echo "begin to patch $obj the file "${obj_file}
curl -XPATCH -H "Accept: application/json" -H "Content-Type: application/merge-patch+json" --header "Authorization: Bearer $TOKEN" --insecure -d @${obj_file} $APISERVER/apis/kuring.me/v1alpha1/namespaces/${namespace}/${kind}/$obj/status

查看网桥设备以及端口

使用brctl show可以查看本地上的所有的网桥设备以及接到网桥设备上的所有网络设备。

查看网桥设备的mac地址表

执行brctl showmacs ${dev},常用来排查一些包丢在网桥上的场景。
其中port no为网桥通过mac地址学习到的某个mac地址所在的网桥端口号。

1
2
3
4
5
6
7
8
9
10
$ brctl showmacs br0
port no mac addr is local? ageing timer
1 02:50:89:59:ac:4b no 3.96
69 02:e2:14:78:d7:92 no 0.57
1 0a:1e:01:dc:67:87 no 10.23
1 0a:60:3c:ca:a8:85 no 6.04
1 0e:01:ce:d6:fc:66 no 8.36
1 0e:0c:f8:6c:08:75 no 56.73
58 0e:49:85:f6:a1:40 no 1.30
22 0e:c0:99:b0:d9:f9 no 0.85

查看网桥设备的某个端口的挂载设备

在上文中中可以获取到某个mac地址对应的网桥设备的端口号,要想知道某个网桥设备的端口号对应的设备可以使用brctl showstp ${dev}命令。

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
brctl showstp br0
br0
bridge id 8000.ae90501b5b47
designated root 8000.ae90501b5b47
root port 0 path cost 0
max age 20.00 bridge max age 20.00
hello time 2.00 bridge hello time 2.00
forward delay 15.00 bridge forward delay 15.00
ageing time 300.00
hello timer 0.03 tcn timer 0.00
topology change timer 0.00 gc timer 62.37
flags
bond0.11 (1)
port id 8001 state forwarding
designated root 8000.ae90501b5b47 path cost 100
designated bridge 8000.ae90501b5b47 message age timer 0.00
designated port 8001 forward delay timer 0.00
designated cost 0 hold timer 0.00
flags
veth02b41ce8 (20)
port id 8014 state forwarding
designated root 8000.ae90501b5b47 path cost 2
designated bridge 8000.ae90501b5b47 message age timer 0.00
designated port 8014 forward delay timer 0.00
designated cost 0 hold timer 0.00
flags
hairpin mode 1

在日常开发的过程中,经常会需要在本地开发的程序需要在k8s中调试的场景,比如,写了一个operator。如果此时,本地又没有可以直接可达的k8s集群,比如k8s是在公有云的vpc环境内,外面无法直接访问。此时,ssh又是可以直接通过公网vip访问的vpc的网络内的。为了满足此类需要,可以采用ssh tunnel的方式来打通本地跟远程的k8s集群。

1. 本地建立ssh tunnel到远程集群网络

mac用户可以通过SSH TUNNEL这个软件来在界面上自动化配置,具体的配置方式如下:

需要增加一个ssh的动态代理,监听本地的9909端口号。

2. 将远程集群的kubeconfig文件复制到本地

将k8s集群中~/.kube/config文件复制到本地的~/.kube/config目录下

3. 在命令行中执行kubectl命令

在命令行中配置http和https代理

1
2
export http_proxy=127.0.0.1:9909
export https_proxy=127.0.0.1:9909

然后至此就可以通过kubectl命令来访问远程的k8s集群了。

conntrack是netfilter提供的连接跟踪机制,允许内核识别出数据包属于哪个连接。是iptables实现状态匹配(-m state)以及nat的基础,由单独的内核模块nf_conntrack实现。

conntrack在图中有两处,一处位于prerouting,一处位于output。主机自身进程产生的数据包会经过output链的conntrack,主机的网络设备接收到的数据包会通过prerouting链的conntrack。

每个通过conntrack的数据包,内核会判断是否为新的连接。如果是新的连接,则在连接跟踪表中插入一条记录。如果是已有连接,会更新连接跟踪表中的记录。

需要特别注意的是,conntrack并不会修改数据包,如dnat、snat,而仅仅是维护连接跟踪表。

连接跟踪表的内容

/proc/net/nf_conntrack可以看到连接跟踪表的所有内容,通过hash表来实现。

1
ipv4     2 tcp      6 117 TIME_WAIT src=10.45.4.124 dst=10.45.8.10 sport=36903 dport=8080 src=10.45.8.10 dst=10.45.4.124 sport=8080 dport=36903 [ASSURED] mark=0 zone=0 use=2
  • 117: 该连接的生存时间,每个连接都有一个timeout值,可以通过内核参数进行设置,如果超过该时间还没有报文到达,该连接将会删除。如果有新的数据到达,该计数会被重置。
  • TIME_WAIT: 当前该连接的最新状态

涉及到的内核参数

  • net.netfilter.nf_conntrack_buckets: 用来设置hash表的大小
  • net.netfilter.nf_conntrack_max: 用来设置连接跟踪表的数据条数上限

iptables与conntrack的关系

iptables使用-m state模块来从连接跟踪表查找数据包的状态,上面例子中的TIME_WAIT即为连接跟踪表中的状态,但这些状态对应到iptable中就只有五种状态。特别需要注意的是,这五种状态是跟具体的协议是tcp、udp无关的。

状态 含义
NEW 匹配连接的第一个包
ESTABLISHED NEW状态后,如果对端有回复包,此时连接状态为NEW
RELATED 不是太好理解,当已经有一个状态为ESTABLISHED连接后,如果又产生了一个新的连接并且跟此时关联的,那么该连接就是RELATED状态的。对于ftp协议而言,有控制连接和数据连接,控制连接要先建立为ESTABLISHED,数据连接就变为控制连接的RELATED。那么conntrack怎么能够识别到两个连接是有关联的呢,即能够识别出协议相关的内容,这就需要通过扩展模块来完成了,比如ftp就需要nf_conntrack_ftp
INVALID 无法识别的或者有状态的数据包
UNTRACKED 匹配带有NOTRACK标签的数据包,raw表可以将数据包标记为NOTRACK,这种数据包的连接状态为NOTRACK

conntrack-tools

1
2
# 查看连接跟踪表
conntrack -L

reference

控制器向上提供接口,用来供应用程序调用,此接口成为北向接口;控制器向下调用接口,控制网络设备,此接口成为南向接口。

OpenFlow是控制器和网络设备之间互通的南向协议,OpenvSwitch 用于创建软件的虚拟交换机。

原理

https://static001.geekbang.org/resource/image/d8/14/d870e5bfcad8ec45d146c3226cdccb14.jpg

用户态进程

  • ovsdb:本地数据库,存储ovs的配置信息
  • vswitchd:ovs-ofctl用来跟该命令通讯,下发流表规则

install OpenvSwitch on CentOS 7

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 安装依赖
yum install openssl-devel python-sphinx gcc make python-devel openssl-devel kernel-devel graphviz kernel-debug-devel autoconf automake rpm-build redhat-rpm-config libtool python-twisted-core python-zope-interface PyQt4 desktop-file-utils libcap-ng-devel groff checkpolicy selinux-policy-devel gcc-c++ python-six unbound unbound-devel -y

mkdir -p ~/rpmbuild/SOURCES && cd ~/rpmbuild/SOURCES
# 下载ovs源码
wget https://www.openvswitch.org/releases/openvswitch-2.12.0.tar.gz

tar zvxf openvswitch-2.12.0.tar.gz
# 构建rpm包
rpmbuild -bb --nocheck openvswitch-2.12.0/rhel/openvswitch-fedora.spec

# 安装rpm包
yum localinstall /root/rpmbuild/RPMS/x86_64/openvswitch-2.12.0-1.el7.x86_64.rpm

systemctl start openvswitch.service

在编译的时候有如下报错:

1
2
3
  File "/usr/lib64/python2.7/site-packages/jinja2/sandbox.py", line 22, in <module>
from markupsafe import EscapeFormatter
ImportError: cannot import name EscapeFormatter

是因为markupsafe的版本不对导致的,解决方法为安装合适的版本:

1
2
pip uninstall markupsafe
pip install markupsafe==0.23

vlan实验

1
2
3
# 创建虚拟交换机ovs_br
ovs-vsctl add-br ovs_br
ovs-vsctl add-port ovs_br first_port

flow table试验 1

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
# 创建namespace
ip netns add ns1
ip netns add ns2

# 创建veth pair设备
ip link add veth1 type veth peer name veth1_br
ip link add veth2 type veth peer name veth2_br

# 设置veth pair设备的namespace
ip link set veth1 netns ns1
ip link set veth2 netns ns2

# 创建OVS网桥
ovs-vsctl add-br ovs1

# 将veth pair设备另一端绑到网桥ovs1
ovs-vsctl add-port ovs1 veth1_br
ovs-vsctl add-port ovs1 veth2_br

# 启动veth pair
ip netns exec ns1 ip link set veth1 up
ip netns exec ns2 ip link set veth2 up
ip link set veth1_br up
ip link set veth2_br up

# 设置veth1和veth2的ip地址
ip netns exec ns1 ip addr add 192.168.1.100 dev veth1
ip netns exec ns2 ip addr add 192.168.1.200 dev veth2

# 配置路由
ip netns exec ns1 route add -net 192.168.1.0 netmask 255.255.255.0 dev veth1
ip netns exec ns2 route add -net 192.168.1.0 netmask 255.255.255.0 dev veth2

可以使用如下命令来查看刚才的操作:

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
# 可以看到刚才创建的网桥
$ ovs-vsctl list-br
ovs1

# 查看网桥的端口
$ ovs-vsctl list-ports ovs1
veth1_br
veth2_br

# 查看网桥的状态
$ ovs-vsctl show
4ec35070-a763-4748-878a-c3784b5938a4
Bridge "ovs1"
Port "veth1_br"
Interface "veth1_br"
Port "ovs1"
Interface "ovs1"
type: internal
Port "veth2_br"
Interface "veth2_br"
ovs_version: "2.12.0"

# 查看interface的状态,跟port是一一对应的
$ ovs-vsctl list interface veth1_br
...
mac : []
mac_in_use : "32:13:6b:99:91:2b"
mtu : 1500
mtu_request : []
name : "veth1_br"
ofport : 1 # ovs port编号
...

接下来测试一下网络的连通性是没问题的。

1
ip netns exec ns1 ping 192.168.1.200

查看当前流表,可以看到有一条默认的规则,该条规则用来实现交换机的基本动作。

1
2
$ ovs-ofctl dump-flows ovs1
cookie=0x0, duration=1428.955s, table=0, n_packets=22, n_bytes=1676, priority=0 actions=NORMAL

将上述规则删除,再执行ping命令发现已经不通。说明该默认规则会将流量在端口之间进行转发。

1
ovs-ofctl del-flows ovs1

新增加如下两条规则,用来表示将port 1的流量转发到port 3,将port 3的流量转发到port 1。其中的1和3分别为port编号,使用ovs-vsctl list interface veth1_br命令中的ofport可以看到。

1
2
3
4
5
6
ovs-ofctl add-flow ovs1 "priority=1,in_port=1,actions=output:3"
ovs-ofctl add-flow ovs1 "priority=2,in_port=3,actions=output:1"

$ ovs-ofctl dump-flows ovs1
cookie=0x0, duration=69.378s, table=0, n_packets=4, n_bytes=280, priority=1,in_port="veth1_br" actions=output:"veth2_br"
cookie=0x0, duration=69.063s, table=0, n_packets=4, n_bytes=280, priority=2,in_port="veth2_br" actions=output:"veth1_br"

再执行ping命令,发现可以ping通了。

重新增加一条优先级更高的规则,将port 1的数据drop掉。此时再ping发现已经不通了。

1
ovs-ofctl add-flow ovs1 "priority=3,in_port=1,actions=drop"

多table

接下来清理掉规则,并将规则重新写入到table1中,默认规则是写入到table0中的

1
2
3
ovs-ofctl del-flows ovs1
ovs-ofctl add-flow ovs1 "table=1,priority=1,in_port=1,actions=output:3"
ovs-ofctl add-flow ovs1 "table=1,priority=2,in_port=3,actions=output:1"

此时再执行ping命令,发现网络是不通的。因为table0中没有匹配成功,包被drop掉了。

再增加如下规则,即将table 0的规则发送到table 1处理,此时可以ping通。

1
ovs-ofctl add-flow ovs1 "table=0,actions=goto_table=1"

group table

执行ovs-ofctl del-flows ovs1重新清理掉规则,执行下面命令查看group table内容,可以看到内容为空。

1
2
# ovs-ofctl -O OpenFlow13 dump-groups ovs1
OFPST_GROUP_DESC reply (OF1.3) (xid=0x2):

执行如下命令,完成数据包从table0 -> group table -> table1的过程,真正数据处理在table1中。

1
2
3
4
5
6
7
8
9
10
# 创建一个group table,其作用为将数据包发送到table 1
ovs-ofctl add-group ovs1 "group_id=1,type=select,bucket=resubmit(,1)"

# 将port 1和3 的数据发往group table 1
ovs-ofctl add-flow ovs1 "table=0,in_port=1,actions=group:1"
ovs-ofctl add-flow ovs1 "table=0,in_port=3,actions=group:1"

# table 1为真正要处理数据的逻辑
ovs-ofctl add-flow ovs1 "table=1,priority=1,in_port=1,actions=output:3"
ovs-ofctl add-flow ovs1 "table=1,priority=2,in_port=3,actions=output:1"

此时再执行ping命令,发现是可以ping通的。

清理操作

1
2
3
4
5
6
# 删除网桥
ovs-vsctl del-br ovs1
ip link delete veth1_br
ip link delete veth2_br
ip netns del ns1
ip netns del ns2

常用操作

  • ovs-appctl fdb/show ovs1: 查看mac地址表
  • ovs-ofctl show ovs1: 可以查看网桥的端口号
  • ovs-vsctl set bridge ovs1 stp_enable=false: 开启网桥的生成树协议
  • ovs-appctl ofproto/trace ovs1 in_port=1,dl_dst=7a:42:0a:ca:04:65: 可用来验证一个包到达网桥后的处理流程

reference

kube-proxy默认使用iptables规则来做k8s集群内部的负载均衡,本文通过例子来分析创建的iptabels规则。

主要的自定义链涉及到:

  • KUBE-SERVICES: 访问集群内服务的CLusterIP数据包入口,根据匹配到的目标ip+port将数据包分发到相应的KUBE-SVC-xxx链上。一个Service对应一条规则。由OUTPUT链调用。
  • KUBE-NODEPORTS: 用来匹配nodeport端口号,并将规则转发到KUBE-SVC-xxx。一个NodePort类型的Service一条。在KUBE-SERVICES链的最后被调用
  • KUBE-SVC-xxx:相当于是负载均衡,将流量利用random模块均分到KUBE-SEP-xxx链上。
  • KUBE-SEP-xxx:通过dnat规则将连接的目的地址和端口号做dnat,从Service的ClusterIP或者NodePort转换为后端的pod ip
  • KUBE-MARK-MASQ: 使用mark命令,对数据包设置标记0x4000/0x4000。在KUBE-POSTROUTING链上有MARK标记的数据包进行一次MASQUERADE,即SNAT,会用节点ip替换源ip地址。

环境准备

创建nginx deployment

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
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: nginx-svc
version: nginx
name: nginx
namespace: default
spec:
replicas: 2
selector:
matchLabels:
app: nginx-svc
template:
metadata:
labels:
app: nginx-svc
version: nginx
spec:
containers:
- image: 'nginx:1.9.0'
name: nginx
ports:
- containerPort: 443
protocol: TCP
- containerPort: 80
protocol: TCP

创建service对象

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
apiVersion: v1
kind: Service
metadata:
name: nginx-svc
namespace: default
spec:
ports:
- name: '80'
port: 8000
protocol: TCP
targetPort: 80
nodePort: 30080
selector:
app: nginx-svc
sessionAffinity: None
type: NodePort

环境信息如下:

  • 容器网段:172.20.0.0/16
  • Service ClusterIP cidr: 192.168.0.0/20
  • k8s版本:

提交后创建出来的信息如下:

  • Service ClusterIP:192.168.103.148
  • nginx pod的两个ip地址:172.16.3.3 172.16.4.4

从宿主机上访问ClusterIP

从本机请求ClusterIP的数据包会经过iptables的链:OUTPUT -> POSTROUTING

要想详细知道iptabels的执行情况,可以通过iptables的trace功能。如何开启trace功能可以参考:http://kuring.me/post/iptables/。

image

执行 iptables -nvL OUTPUT -t nat 可以看到如下的iptables规则命令

1
2
pkts bytes target         prot opt in     out     source               destination         
17M 1150M KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */

执行 iptables -nvL KUBE-SERVICES -t nat 可以查看自定义链的具体内容,里面包含了多条规则,其中跟当前Service相关的规则如下。

1
2
pkts bytes target                     prot opt in     out     source               destination
1 60 KUBE-SVC-Y5VDFIEGM3DY2PZE tcp -- * * 0.0.0.0/0 192.168.103.148 /* default/nginx-svc:80 cluster IP */ tcp dpt:8000

执行 iptables -nvL KUBE-SVC-Y5VDFIEGM3DY2PZE -t nat 查看自定义链的具体规则

1
2
3
pkts  bytes target                     prot opt in     out     source               destination
0 0 KUBE-SEP-IFV44I3EMZAL3LH3 all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */ statistic mode random probability 0.50000000000
1 60 KUBE-SEP-6PNQETFAD2JPG53P all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */

上述规则会按照特定的概率将流量均等的执行自定义链的规则,两个自定义的链的规则跟endpoint相关,执行 iptables -nvL KUBE-SEP-IFV44I3EMZAL3LH3 -t nat可查看endpoint级别的iptabels规则。dnat操作会修改数据包的目的地址和端口,从clusterip+service port修改为访问pod ip+pod端口。

1
2
3
pkts bytes target          prot opt in     out     source               destination
0 0 KUBE-MARK-MASQ all -- * * 172.16.3.3 0.0.0.0/0 /* default/nginx-svc:80 */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */ tcp to:172.16.3.3:80

会在dnat操作之前为对数据包执行打标签操作。KUBE-MARK-MASQ 自定义链为对数据包打标记的自定义规则,执行 iptables -nvL KUBE-MARK-MASQ -t nat

1
2
pkts bytes target     prot opt in     out     source               destination         
1 60 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000

接下来看一下POSTROUTING链上的规则,iptables -nvL POSTROUTING -t nat

1
2
pkts bytes target            prot opt in     out     source               destination         
205K 13M KUBE-POSTROUTING all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes postrouting rules */

继续看一下KUBE-POSTROUTING链的内容,iptables -nvL KUBE-POSTROUTING -t nat,其中最后一条的MASQUERADE指令的操作实际上为SNAT操作。

1
2
3
4
5
Chain KUBE-POSTROUTING (1 references)
pkts bytes target prot opt in out source destination
6499 398K RETURN all -- * * 0.0.0.0/0 0.0.0.0/0 mark match ! 0x4000/0x4000
1 60 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK xor 0x4000
1 60 MASQUERADE all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service traffic requiring SNAT */

即从本机访问service clusterip的数据包,在output链上经过了dnat操作,在postrouting链上经过了snat操作后,最终会发往目标pod。pod在处理完请求后,回的数据包最终会经过nat的逆过程返回到本机。

外部访问nodeport

从外部访问本机的nodeport数据包会经过iptables的链:PREROUTING -> FORWARD -> POSTROUTING

image

nodeport都是被外部访问的情况,入口位于PREROUTING链上。执行 iptables -nvL PREROUTING -t nat

1
2
pkts bytes target         prot opt in     out     source               destination         
349K 21M KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */

在KUBE-SERVICES链的最后一条规则为跳转到KUBE-NODEPORTS链

1
4079  246K KUBE-NODEPORTS  all  --  *      *       0.0.0.0/0            0.0.0.0/0            /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL

执行iptables -nvL KUBE-NODEPORTS -t nat, 查看KUBE-NODEPORTS链

1
2
3
pkts bytes target                     prot opt in     out     source               destination         
0 0 KUBE-MARK-MASQ tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */ tcp dpt:30080
0 0 KUBE-SVC-Y5VDFIEGM3DY2PZE tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */ tcp dpt:30080

其中KUBE-MARK-MASQ链只有一条规则,即打上0x4000的标签。

1
2
pkts bytes target     prot opt in     out     source               destination         
0 0 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000

自定义链KUBE-SVC-Y5VDFIEGM3DY2PZE的内容如下,跟clusterip的规则是重叠的:

1
2
3
pkts bytes target     prot opt in     out     source               destination         
0 0 KUBE-SEP-IFV44I3EMZAL3LH3 all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */ statistic mode random probability 0.50000000000
0 0 KUBE-SEP-6PNQETFAD2JPG53P all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */

KUBE-SEP-IFV44I3EMZAL3LH3的内容为,会经过一次DNAT操作:

1
2
3
pkts bytes target          prot opt in     out     source               destination         
0 0 KUBE-MARK-MASQ all -- * * 172.16.3.3 0.0.0.0/0 /* default/nginx-svc:80 */
0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */ tcp to:172.16.3.3:80

在经过了PREROUTING链后,接下来会判断目的ip地址不是本机的ip地址,接下来会经过FORWARD链。在FORWARD链上,仅做了一件事情,就是将前面大了0x4000的数据包允许转发。

1
2
3
4
pkts bytes target              prot opt in     out     source               destination         
0 0 KUBE-FORWARD all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding rules */
0 0 KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate NEW /* kubernetes service portals */
0 0 KUBE-EXTERNAL-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate NEW /* kubernetes externally-visible service portals */

KUBE-FORWARD的内容如下:

1
2
3
4
5
pkts bytes target     prot opt in     out     source               destination         
0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate INVALID
0 0 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding rules */ mark match 0x4000/0x4000
0 0 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding conntrack pod source rule */ ctstate RELATED,ESTABLISHED
0 0 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding conntrack pod destination rule */ ctstate RELATED,ESTABLISHED

跟clusterip一样,会在POSTROUTING阶段匹配mark为0x4000/0x4000的数据包,并进行一次MASQUERADE转换,将ip包替换为宿主上的ip地址。

加入这里不做MASQUERADE,流量发到目的的pod后,pod回包时目的地址为发起端的源地址,而发起端的源地址很可能是在k8s集群外部的,此时pod发回的包是不能回到发起端的。NodePort跟ClusterIP的最大不同就是NodePort的发起端很可能是在集群外部的,从而这里必须做一层SNAT转换。

在上述分析中,访问NodePort类型的Service会经过snat,从而服务端的pod不能获取到正确的客户端ip。可以设置Service的spec.externalTrafficPolicy为Local,此时iptables规则只会将ip包转发给运行在这台宿主机上的pod,而不需要经过snat。pod回包时,直接回复源ip地址即可,此时源ip地址是可达的,因为源ip地址跟宿主机是可达的。如果所在的宿主机上没有pod,那么此时流量就不可以转发,此为限制。

使用LoadBalancer类型访问的情况

externalTrafficPolicy为local

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
-A KUBE-SERVICES -d 10.149.30.186/32 -p tcp -m comment --comment "acs-system/nginx-ingress-lb-cloudbiz:http loadbalancer IP" -m tcp --dport 80 -j KUBE-FW-76HLDRT5IPNSMPF5
-A KUBE-FW-76HLDRT5IPNSMPF5 -m comment --comment "acs-system/nginx-ingress-lb-cloudbiz:http loadbalancer IP" -j KUBE-XLB-76HLDRT5IPNSMPF5
-A KUBE-FW-76HLDRT5IPNSMPF5 -m comment --comment "acs-system/nginx-ingress-lb-cloudbiz:http loadbalancer IP" -j KUBE-MARK-DROP

# 10.149.112.0/23为pod网段
-A KUBE-XLB-76HLDRT5IPNSMPF5 -s 10.149.112.0/23 -m comment --comment "Redirect pods trying to reach external loadbalancer VIP to clusterIP" -j KUBE-SVC-76HLDRT5IPNSMPF5
-A KUBE-XLB-76HLDRT5IPNSMPF5 -m comment --comment "Balancing rule 0 for acs-system/nginx-ingress-lb-cloudbiz:http" -j KUBE-SEP-XZXLBWOKJBSJBGVU

-A KUBE-SVC-76HLDRT5IPNSMPF5 -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-XZXLBWOKJBSJBGVU
-A KUBE-SVC-76HLDRT5IPNSMPF5 -j KUBE-SEP-GP4UCOZEF3X7PGLR

-A KUBE-SEP-XZXLBWOKJBSJBGVU -s 10.149.112.45/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-XZXLBWOKJBSJBGVU -p tcp -m tcp -j DNAT --to-destination 10.149.112.45:80
-A KUBE-SEP-GP4UCOZEF3X7PGLR -s 10.149.112.46/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-GP4UCOZEF3X7PGLR -p tcp -m tcp -j DNAT --to-destination 10.149.112.46:80

缺点

  1. iptables规则特别乱,一旦出现问题非常难以排查
  2. 由于iptables规则是串行执行,算法复杂度为O(n),一旦iptables规则多了后,性能将非常差。
  3. iptables规则提供的负载均衡功能非常有限,不支持较为复杂的负载均衡算法。

docker bridge是默认的网络模式

容器访问外网

默认情况下,容器即可访问外网。

启动一个容器:docker run -d nginx

容器中访问外网的请求如www.baidu.com,内核协议栈根据路由信息,会选择默认路由,将请求发送到容器中的eth0网卡,目的mac地址为网关172.17.0.1的mac地址。

1
2
3
4
5
$ route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 172.17.0.1 0.0.0.0 UG 0 0 0 eth0
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 eth0

eth0网卡接收到数据包后,会将数据包转发到veth pair的另外一端,即宿主机网络中的veth6b173fd设备。

veth6b173fd设备是挂在网桥上的,会将数据包转发到网桥br0,br0即为网关172.17.0.1。

br0接收到数据包后,会将数据包转发给内核协议栈。

宿主机上的/proc/sys/net/ipv4/ip_forward为1,表示转发功能开启,即目的ip不是本机的会根据路由规则进行转发,而不是丢弃。

仅在宿主机上开启了ip_forward后,包即使转发了,还是无法回来的,因为包中的源ip地址为172.17.0.1,是私有网段的ip地址。需要做一次SNAT才可以,docker会在iptabels的nat表中的postrouting链中增加SNAT规则,下面规则的意思是源地址为172.17.0.0/16的会做一次地址伪装,即SNAT。

1
2
3
4
5
# iptables -nL -t nat

Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0

在eth0网卡上抓包,可以发现源ip已经是eth0的网卡ip地址。

端口映射

命令格式:-p ${host_port}:${container_port}

启动一个容器:docker run -d -p 8080:80 nginx

查看本地的iptables规则

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
$ iptables -nL -t nat
Chain PREROUTING (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 0.0.0.0/0 ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target prot opt source destination

Chain OUTPUT (policy ACCEPT)
target prot opt source destination
DOCKER all -- 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target prot opt source destination
MASQUERADE all -- 172.17.0.0/16 0.0.0.0/0
MASQUERADE tcp -- 172.17.0.2 172.17.0.2 tcp dpt:80

Chain DOCKER (2 references)
target prot opt source destination
RETURN all -- 0.0.0.0/0 0.0.0.0/0
## prerouting链引用,外面发往本机的8080端口的数据包,会dnat为172.17.0.2:80
DNAT tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:8080 to:172.17.0.2:80

丢包问题

SNAT在并发比较高的情况下,会存在少量的丢包现象,具体原因跟conntrack模式的实现有关。conntrack在SNAT端口的分配和插入conntrack表之间有个延时,如果在这中间存在冲突的话会导致插入失败,从而出现丢包的问题。

该问题没有根治的解决办法,能大大缓解的解决办法为使用iptabels的–random-fully选项,SNAT选择端口为随机,大大降低出现冲突的概率。

0%