转载自深入理解kubernetes(k8s)网络原理之一-pod连接主机

关于Linux网络的知识

向外发送一个数据包,执行步骤:

1、查找该数据包的目的地的路由信息,如果是直连,则在邻居表中查找该目的地的Mac地址
2、如果非直连路由,则在邻居表中查找下一跳的Mac地址
3、如果找不到对应的路由,则报"network is unreachable"
4、如果在邻居表中没有查到相应的MAC地址信息,则向外发送ARP请求询问
5、发送出去的数据帧,源MAC地址为发送网卡的MAC地址,目标MAC则是下一跳的MAC,只要不经过NAT,那么源目的IP全程不会变化,而MAC地址则每一跳都会变化

收到数据帧,执行步骤

1、如果数据帧目标MAC地址不是收包网卡的MAC,也不是ARP广播地址,且网卡未开启混杂模式,则拒绝收包
2、如果数据帧目标MACff:ff:ff:ff:ff:ff,则进入ARP请求处理流程
3、如果数据帧目标MAC地址是收包网卡的MAC,且是IP包则:
1、目标IP地址在本机,则上送到上一层协议继续处理
2、目标IP地址不在本机,则看net.ipv4.ip_forward是否为1,若是1,则查找目标IP的路由信息,进行转发
3、目标IP不在本机,且net.ipv4.ip_forward为0,则丢弃

常见命令

1
2
3
4
5
6
7
8
# 查看网卡信息
ip link
# 查看网卡ip地址
ip addr
# 查看邻居表信息
ip neigh
# 查看所有iptables规则
iptables-save

为了让多个进程高效互不影响地运行,衍生出容器技术,其中以Docker最为流行:
1、资源隔离: 使用linux control group 解决各种进程CPUMemoryio的资源分配问题
2、网络隔离: 使用linux network group让各个进程运行在独立的网络命名空间,使各个进程运行在独立的网络命名空间
3、文件系统隔离:使用union fs,让各个进程运行在独立的根文件系统中

POD即共享同一个ns的多个容器

示例

docker 运行一个容器时,都会为当前容器创建一个ns,多个容器只能相互访问对方的ip地址

1
2
docker run -itd --name=pause busybox
docker run --name=nginx -d nginx

此时要在 pause 中访问 nginx,先查找下nginx容器的ip地址

1
2
3
4
docker inspect nginx | grep IPAddress
"SecondaryIPAddresses": null,
"IPAddress": "172.17.0.3",
"IPAddress": "172.17.0.3",

然后在pause容器中用刚查到的ip地址进行访问

1
2
3
4
5
6
7
8
9
docker exec -it pause curl 172.17.0.8

<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
....
</body>
</html>

这里可以让 nginx 容器加入pause容器的ns,用下面的命令可以模拟:

1
2
docker run -itd --name=pause busybox
docker run --name=nginx --network=container:pause -d nginx

此时pause容器和nginx容器是在相同的ns中,相互间访问就可以使用`localhost进行访问了,可以用下面的命令进行验证:

1
docker exec -it pause culr localhost

pause容器和nginx容器就是共享一个ns的两个容器,所以pausenginx两个容器加起来就是k8spod

k8s集群的节点中使用docker ps,总会发现一堆名为pause的容器,pause是为多个业务容器提供共享的ns的。

1、进入docker创建的pause容器的ns 先获取pause容器的pid`

1
docker inspect pause | grep Pid

2、进入指定pid的`ns

1
nsenter --net=/proc/3083138/ns/net

3、此时已经在pause容器的ns中了,可以查看该ns的网卡,路由表,邻居表等信息了

1
2
ip addr show
ip route

认识ns

影响网络方面的配置主要有以下几个:
— 网卡:启动时初始化,后期可以添加虚拟设备

  • 端口:1到65535,所有进程共享
  • iptables规则: 配置进出主机的防火墙策略和NAT规则
  • 路由表:到目标地址的路由信息
  • 邻居表:与主机在同个二层网络的其他主机的MAC地址与IP地址的映射关系

示例

1、创建新的ns

1
ip netns add ns1

然后可以使用ip netns exec ns1前缀来执行命令,这样显示的结果就都是ns1的网络相关的配置了.

1
ip netns exec ns1 ip link show

2、主机与pod相互访问
首先给ns1正价一张与主机相连的网卡,这里用到linux虚拟网络设备veth网卡对,对于veth,基本可以理解为中间连着线的两张网卡:

1
2
3
4
5
6
7
8
9
10
# 增加一对veth网卡,名为 ns1-eth0 和 veth-ns1
ip link add ns1-eth0 type veth peer name veth-ns1
# 其中一端挪到刚才创建的ns1中,另一端留在主机端,这样主机和ns就连接起来了
ip link set ns1-eth0 netns ns1
# 启动主机端的网卡veth-ns1
ip link set veth-ns1 up
# 执行设置网卡的ip
ip netns exec ns1 ip addr add 172.20.1.10/24 dev ns1-eth0
# 启动ns1端的网卡ns1-eth0
ip netns exec ns1 ip link set ns1-eth0 up

3、测试与主机ip是否能ping

1
ip netns exec ns1 ping xxx.xxx.xxx.xxx

此时发现不能ping通主机,这是因为没有到目的地的路由,所以在这里给ns1增加一条默认路由

1
ip netns exec ns1 ip route add default via 172.20.1.1. dev ns-eth0

通过

1
ip netsns exec ns1 ip route

查看路由信息
此时去ping发现还是不行,这是因为如果是非直连路由,会先去拿下一跳的mac地址,下一跳是172..20.1.1,能获取到它的MAC地址吗?
用如下命令查一下路由表:

1
ip netns exec ns1 ip neigh

会发现获取不到,以为网关ip地址确实是个不存在的地址,网关IP是不会出现在pod发送的数据包中的,真正需要用的是网关的mac地址,我们的目的是要得到主机端veth-ns1mac地址,有两个方法:
·、设置对端的网卡apr代答,ns1-eth0的对端是主机上的veth-ns1网卡

1
2
# 这样就开启了veth-ns1的arp代答,只要收到arp请求,不管目标IP是什么,veth-ns1网卡都会把自己MAC地址回复回去
echo 1 > /proc/sys/net/ipv4/conf/veth-ns1/proxy_arp

或者把网关地址设置在对端的网卡上
4、此时拿到网关的mac地址但是ping之后发现还是不行。这是因为主机上没有添加到pod的直连路由

1
ip route add 172.20.1.10 dev veth-ns1

此时只能保证主机与pod进行互通,此时pod是没法访问外网的,这个时候需要做原地址转换,所以我们需要在主机上也要配置针对刚才创建的pod的原地址转换规则。

pod访问外网

  • 首先第一步需要打开本机的ip转发功能
    1
    echo 1 > /proc/sys/net/ipv4/ip_forward
  • 设置snat规则
    1
    iptables -A POSTROUTING -t nat -s 172.20.1.10 -j MASQUERADE
    此时发现可以ping通百度