k8s 虽然设计了网络模型,然后将实现方式交给了 CNI 网络插件,而 CNI 网络插件的主要目的,就是实现 Pod 资源能够跨宿主机进行通信 常见的网络插件有
- Flannel
- Calico
- Canal
但是最简单的 Flannel 已经完全满足我们的要求,故不在考虑其他网络插件
网络插件 Flannel 介绍:https://www.kubernetes.org.cn/3682.html
1 Flannel 功能概述
1.1 Flannel 运转流程
-
首先 Flannel 利用 Kubernetes API 或者 etcd 用于存储整个集群的网络配置,其中最主要的内容为设置集群的网络地址空间。
例如,设定整个集群内所有容器的IP都取自网段 “10.1.0.0/16”。
-
接着 Flannel 在每个主机中运行 flanneld 作为 agent,它会为所在主机从集群的网络地址空间中,获取一个小的网段 subnet,本主机内所有容器的 IP 地址都将从中分配。
例如,设定本主机内所有容器的IP地址网段 “10.1.2.0/24”。
-
然后 flanneld 再将本主机获取的 subnet 以及用于主机间通信的 Public IP,同样通过 kubernetes API 或者 etcd 存储起来。
-
最后 Flannel 利用各种 backend mechanism,例如 udp,vxlan 等等,跨主机转发容器间的网络流量,完成容器间的跨主机通信。
1.2 Flannel 的网络模型
1.2.1 Flannel 支持 3 种网络模型
-
host-gw
网关模型{"Network": "xxx", "Backend": {"Type": "host-gw"}}
主要用于宿主机在同网段的情况下 Pod 间的通信,即不跨网段通信. 此时 flannel 的功能很简单,就是在每个宿主机上创建了一条通网其他宿主机的网关路由 完全没有性能损耗,效率极高
-
vxlan 隧道模型
{"Network": "xxx", "Backend": {"Type": "vxlan"}}
主要用于宿主机不在同网段的情况下 Pod 间通信,即跨网段通信. 此时 flannel 会在宿主机上创建一个
flannel.1
的虚拟网卡,用于和其他宿主机间建立 VXLAN 隧道 跨宿主机通信时,需要经由flannel.1
设备封包、解包,因此效率不高 -
混合模型
{"Network": "xxx", "Backend": {"Type": "vxlan","Directrouting": true}}
在既有同网段宿主机,又有跨网段宿主机的情况下,选择混合模式 flannel 会根据通信双方的网段情况,自动选择是走网关路由通信还是通过 VXLAN 隧道通信
1.2.2 实际工作中的模型选择
很多人不推荐部署 K8S 的使用的 flannel 做网络插件,不推荐的原因是是 flannel 性能不高,然而
- flannel 性能不高是指它的 VXLAN 隧道模型,而不是 gw 模型
- 规划 K8S 集群的时候,应规划多个 K8S 集群来管理不同的业务
- 同一个 K8S 集群的宿主机,就应该规划到同一个网段
- 既然是同一个网段的宿主机通信,使用的就应该是 gw 模型
- gw 模型只是创建了网关路由,通信效率极高
- 因此,建议工作中使用 flannel,且用
gw
模型
2. 部署 Flannel 插件
2.1 在 Etcd 中写入网络信息
以下操作在任意 etcd 节点中执行都可以
/opt/etcd/etcdctl set /coreos.com/network/config '{"Network": "172.7.0.0/16", "Backend": {"Type": "host-gw"}}'
# 查看结果
[root@hdss7-12 ~]# /opt/etcd/etcdctl get /coreos.com/network/config
{"Network": "172.7.0.0/16", "Backend": {"Type": "host-gw"}}
2.2 部署准备
2.2.1 下载软件
wget https://github.com/coreos/flannel/releases/download/v0.11.0/flannel-v0.11.0-linux-amd64.tar.gz
mkdir /opt/flannel-v0.11.0
tar xf flannel-v0.11.0-linux-amd64.tar.gz -C /opt/flannel-v0.11.0/
ln -s /opt/flannel-v0.11.0/ /opt/flannel
2.2.2 拷贝证书
因为要和 apiserver 通信,所以要配置 client 证书,当然 ca 公钥自不必说
cd /opt/flannel
mkdir cert
scp hdss7-200:/opt/certs/ca.pem cert/
scp hdss7-200:/opt/certs/client.pem cert/
scp hdss7-200:/opt/certs/client-key.pem cert/
2.2.3 配置子网信息
cat >/opt/flannel/subnet.env <<EOF
FLANNEL_NETWORK=172.7.0.0/16
FLANNEL_SUBNET=172.7.21.1/24
FLANNEL_MTU=1500
FLANNEL_IPMASQ=false
EOF
注意: subnet 子网网段信息,每个宿主机都要修改
2.3 启动 Flannel 服务
2.3.1 创建 Flannel 启动脚本
cat >/opt/flannel/flanneld.sh <<'EOF'
#!/bin/sh
./flanneld \
--public-ip=10.4.7.21 \
--etcd-endpoints=https://10.4.7.12:2379,https://10.4.7.21:2379,https://10.4.7.22:2379 \
--etcd-keyfile=./cert/client-key.pem \
--etcd-certfile=./cert/client.pem \
--etcd-cafile=./cert/ca.pem \
--iface=eth0 \
--subnet-file=./subnet.env \
--healthz-port=2401
EOF
# 授权
chmod u+x flanneld.sh
注意: public-ip 为节点 IP,注意按需修改 iface 为网卡,若本机网卡不是 eth0,注意修改
2.3.2 创建 supervisor 启动脚本
cat >/etc/supervisord.d/flannel.ini <<EOF
[program:flanneld]
command=sh /opt/flannel/flanneld.sh
numprocs=1
directory=/opt/flannel
autostart=true
autorestart=true
startsecs=30
startretries=3
exitcodes=0,2
stopsignal=QUIT
stopwaitsecs=10
user=root
redirect_stderr=true
stdout_logfile=/data/logs/flanneld/flanneld.stdout.log
stdout_logfile_maxbytes=64MB
stdout_logfile_backups=4
stdout_capture_maxbytes=1MB
;子进程还有子进程,需要添加这个参数,避免产生孤儿进程
killasgroup=true
stopasgroup=true
EOF
supervisor 的各项配置不再备注,有需要的看 K8S 二进制安装中的备注
2.3.3 启动 Flannel 服务并验证
启动服务
mkdir -p /data/logs/flanneld
supervisorctl update
supervisorctl status
验证路由
[root@hdss7-22 ~]# route -n|egrep -i '172.7|des'
Destination Gateway Genmask Flags Metric Ref Use Iface
172.7.21.0 10.4.7.21 255.255.255.0 UG 0 0 0 eth0
172.7.22.0 0.0.0.0 255.255.255.0 U 0 0 0 docker0
[root@hdss7-21 ~]# route -n|egrep -i '172.7|des'
Destination Gateway Genmask Flags Metric Ref Use Iface
172.7.21.0 0.0.0.0 255.255.255.0 U 0 0 0 docker0
172.7.22.0 10.4.7.22 255.255.255.0 UG 0 0 0 eth0
验证通信结果
[root@hdss7-21 ~]# ping 172.7.22.2
PING 172.7.22.2 (172.7.22.2) 56(84) bytes of data.
64 bytes from 172.7.22.2: icmp_seq=1 ttl=63 time=0.538 ms
64 bytes from 172.7.22.2: icmp_seq=2 ttl=63 time=0.896 ms
[root@hdss7-22 ~]# ping 172.7.21.2
PING 172.7.21.2 (172.7.21.2) 56(84) bytes of data.
64 bytes from 172.7.21.2: icmp_seq=1 ttl=63 time=0.805 ms
64 bytes from 172.7.21.2: icmp_seq=2 ttl=63 time=1.14 ms
3 优化 Iptables 规则
3.1 前因后果
3.1.1 优化原因说明
我们使用的是 gw
网络模型,而这个网络模型只是创建了一条到其他宿主机下 Pod 网络的路由信息.
因而我们可以猜想:
- 从外网访问到 B 宿主机中的 Pod,源 IP 应该是外网 IP
- 从 A 宿主机访问B宿主机中的 Pod,源 IP 应该是 A 宿主机的 IP
- 从 A 的 POD-A01 中,访问 B 中的 Pod,源 IP 应该是 POD-A01的容器IP 此情形可以想象是一个路由器下的 2 个不同网段的交换机下的设备通过路由器( gw )通信
然后遗憾的是:
- 前两条毫无疑问成立
- 第 3 条理应成立,但实际不成立
不成立的原因是:
-
Docker 容器的跨网络隔离与通信,借助了 iptables 的机制
-
因此虽然 K8S 我们使用了 ipvs 调度,但是宿主机上还是有 iptalbes 规则
-
而 docker 默认生成的 iptables 规则为: 若数据出网前,先判断出网设备是不是本机
docker0
设备(容器网络) 如果不是的话,则进行 SNAT 转换后再出网,具体规则如下[root@hdss7-21 ~]# iptables-save |grep -i postrouting|grep docker0 -A POSTROUTING -s 172.7.21.0/24 ! -o docker0 -j MASQUERADE
-
由于
gw
模式产生的数据,是从eth0
流出,因而不在此规则过滤范围内 -
就导致此跨宿主机之间的 Pod 通信,使用了该条 SNAT 规则
解决办法是:
- 修改此 Iptables 规则,增加过滤目标:过滤目的地是宿主机网段的流量
3.1.2 问题复现
-
在
7-21
宿主机中,访问172.7.22.2
curl -I 172.7.22.2
-
在
7-21
宿主机启动 busybox 容器,进入并访问172.7.22.2
docker pull busybox docker run --rm -it busybox bash / # wget 172.7.22.2 #或 docker run --rm -it busybox bash -c 'wget 172.7.22.2'
-
查看
7-22
宿主机上启动的 nginx 容器日志[root@hdss7-22 ~]# kubectl logs nginx-ds-j777c --tail=2 10.4.7.21 - - [xxx] "GET / HTTP/1.1" 200 612 "-" "curl/7.29.0" "-" 10.4.7.21 - - [xxx] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"
第一条日志为对端宿主机访问日志 第二条日志为对端容器访问日志 可以看出源 IP 都是宿主机的 Ip
3.2 具体优化过程
3.2.1 先查看 Iptables 规则
[root@hdss7-21 ~]# iptables-save |grep -i postrouting|grep docker0
-A POSTROUTING -s 172.7.21.0/24 ! -o docker0 -j MASQUERADE
3.2.2 安装 Iptables 并修改规则
yum install iptables-services -y
iptables -t nat -D POSTROUTING -s 172.7.21.0/24 ! -o docker0 -j MASQUERADE
iptables -t nat -I POSTROUTING -s 172.7.21.0/24 ! -d 172.7.0.0/16 ! -o docker0 -j MASQUERADE
# 验证规则并保存配置
[root@hdss7-21 ~]# iptables-save |grep -i postrouting|grep docker0
-A POSTROUTING -s 172.7.21.0/24 ! -d 172.7.0.0/16 ! -o docker0 -j MASQUERADE
[root@hdss7-21 ~]# iptables-save > /etc/sysconfig/iptables
3.2.3 注意 Docker 重启后操作
Docker 服务重启后,会再次增加该规则,要注意在每次重启 Docker 服务后,删除该规则 验证:
修改后会影响到 Docker 原本的 Iptables 链的规则,所以需要重启 Docker 服务
[root@hdss7-21 ~]# systemctl restart docker
[root@hdss7-21 ~]# iptables-save |grep -i postrouting|grep docker0
-A POSTROUTING -s 172.7.21.0/24 ! -o docker0 -j MASQUERADE
-A POSTROUTING -s 172.7.21.0/24 ! -d 172.7.0.0/16 ! -o docker0 -j MASQUERADE
# 可以用 iptables-restore 重新应用 iptables 规则,也可以直接再删
[root@hdss7-21 ~]# iptables-restore /etc/sysconfig/iptables
[root@hdss7-21 ~]# iptables-save |grep -i postrouting|grep docker0
-A POSTROUTING -s 172.7.21.0/24 ! -d 172.7.0.0/16 ! -o docker0 -j MASQUERADE
3.2.4 结果验证
# 对端启动容器并访问
[root@hdss7-21 ~]# docker run --rm -it busybox sh
/ # wget 172.7.22.2
# 或 docker run --rm -it busybox sh -c 'wget 172.7.22.2'
# 本端验证日志
[root@hdss7-22 ~]# kubectl logs nginx-ds-j777c --tail=1
172.7.21.3 - - [xxxx] "GET / HTTP/1.1" 200 612 "-" "Wget" "-"