K8S(03)核心插件-Flannel 网络插件

2022/04/17 k8s,docker

k8s 虽然设计了网络模型,然后将实现方式交给了 CNI 网络插件,而 CNI 网络插件的主要目的,就是实现 Pod 资源能够跨宿主机进行通信 常见的网络插件有

  • Flannel
  • Calico
  • Canal

但是最简单的 Flannel 已经完全满足我们的要求,故不在考虑其他网络插件

网络插件 Flannel 介绍:https://www.kubernetes.org.cn/3682.html

1 Flannel 功能概述

1.1 Flannel 运转流程

  1. 首先 Flannel 利用 Kubernetes API 或者 etcd 用于存储整个集群的网络配置,其中最主要的内容为设置集群的网络地址空间。

    例如,设定整个集群内所有容器的IP都取自网段 “10.1.0.0/16”。

  2. 接着 Flannel 在每个主机中运行 flanneld 作为 agent,它会为所在主机从集群的网络地址空间中,获取一个小的网段 subnet,本主机内所有容器的 IP 地址都将从中分配。

    例如,设定本主机内所有容器的IP地址网段 “10.1.2.0/24”。

  3. 然后 flanneld 再将本主机获取的 subnet 以及用于主机间通信的 Public IP,同样通过 kubernetes API 或者 etcd 存储起来。

  4. 最后 Flannel 利用各种 backend mechanism,例如 udp,vxlan 等等,跨主机转发容器间的网络流量,完成容器间的跨主机通信。

1.2 Flannel 的网络模型

1.2.1 Flannel 支持 3 种网络模型

  1. host-gw 网关模型

    {"Network": "xxx", "Backend": {"Type": "host-gw"}}
    

    主要用于宿主机在同网段的情况下 Pod 间的通信,即不跨网段通信. 此时 flannel 的功能很简单,就是在每个宿主机上创建了一条通网其他宿主机的网关路由 完全没有性能损耗,效率极高

  2. vxlan 隧道模型

    {"Network": "xxx", "Backend": {"Type": "vxlan"}}
    

    主要用于宿主机不在同网段的情况下 Pod 间通信,即跨网段通信. 此时 flannel 会在宿主机上创建一个 flannel.1 的虚拟网卡,用于和其他宿主机间建立 VXLAN 隧道 跨宿主机通信时,需要经由 flannel.1 设备封包、解包,因此效率不高

  3. 混合模型

    {"Network": "xxx", "Backend": {"Type": "vxlan","Directrouting": true}}
    

    在既有同网段宿主机,又有跨网段宿主机的情况下,选择混合模式 flannel 会根据通信双方的网段情况,自动选择是走网关路由通信还是通过 VXLAN 隧道通信

1.2.2 实际工作中的模型选择

很多人不推荐部署 K8S 的使用的 flannel 做网络插件,不推荐的原因是是 flannel 性能不高,然而

  1. flannel 性能不高是指它的 VXLAN 隧道模型,而不是 gw 模型
  2. 规划 K8S 集群的时候,应规划多个 K8S 集群来管理不同的业务
  3. 同一个 K8S 集群的宿主机,就应该规划到同一个网段
  4. 既然是同一个网段的宿主机通信,使用的就应该是 gw 模型
  5. gw 模型只是创建了网关路由,通信效率极高
  6. 因此,建议工作中使用 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 网络的路由信息.

因而我们可以猜想:

  1. 从外网访问到 B 宿主机中的 Pod,源 IP 应该是外网 IP
  2. 从 A 宿主机访问B宿主机中的 Pod,源 IP 应该是 A 宿主机的 IP
  3. 从 A 的 POD-A01 中,访问 B 中的 Pod,源 IP 应该是 POD-A01的容器IP 此情形可以想象是一个路由器下的 2 个不同网段的交换机下的设备通过路由器( gw )通信

然后遗憾的是:

  • 前两条毫无疑问成立
  • 第 3 条理应成立,但实际不成立

不成立的原因是:

  1. Docker 容器的跨网络隔离与通信,借助了 iptables 的机制

  2. 因此虽然 K8S 我们使用了 ipvs 调度,但是宿主机上还是有 iptalbes 规则

  3. 而 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
    
  4. 由于 gw 模式产生的数据,是从 eth0 流出,因而不在此规则过滤范围内

  5. 就导致此跨宿主机之间的 Pod 通信,使用了该条 SNAT 规则

解决办法是:

  • 修改此 Iptables 规则,增加过滤目标:过滤目的地是宿主机网段的流量

3.1.2 问题复现

  1. 7-21 宿主机中,访问 172.7.22.2

    curl -I 172.7.22.2
    
  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'
    
  3. 查看 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" "-"

原文链接:https://www.cnblogs.com/noah-luo/p/13345177.html

Search

    Table of Contents