CNI网络插件bridge plugin
CNI网络插件bridge plugin
Bridge插件是典型的CNI基础插件,其工作原理类似物理交换机,通过创建虚拟网桥将所有容器连接到一个二层网络,从而实现容器间的通信。
Bridge插件使用了Linux 原生网桥技术,功能单一结构简单,拥有较高的可靠性,在故障排查上也比较容易。Bridge对比其他网络插件,一方面更容易上手,有利于通过简单实验来揭示cni工作原理;另一方面被很多上层插件依赖,对理解和学习其他插件也有帮助;下面将循序渐进的展开说明该插件的原理和使用方法。
本文首先结合nginx应用来熟悉bridge插件使用,然后通过cnitool观察插件与外部交互行为,再通过模拟试验进一步剥开插件机制,最后分析源码阐述原理和实现;建议先上手操作后再带着问题分析原理,效果会更好。
下面将依次介绍:
- Bridge插件是什么
- Bridge插件安装配置
- Bridge结合ningx应用
- 通过cnitool验证Bridge
- Bridge原理和试验
- Bridge代码分析
Bridge插件概念
Bridge作为最基础的CNI网络插件,它的作用是将容器依附到网桥上实现与宿主机的通信,通过网桥机制建立连接是kubernetes实现容器通信的一种典型方式。bridge插件结合IPAM等插件来管理pod的网络设备和地址配置,在pod创建和删除过程中发挥作用。
如图所示,当容器创建时会建立一个虚拟网桥,可类比物理交换机,刚创建的网桥只接入了协议栈,其他端口未被使用;要让网桥用起来还需要生成一个虚拟网卡对veth pair,它相当于一条网线,其一端接在容器内另一端连到虚拟网桥上,以此来将容器接入网络。
veth pair设备总是成对出现,两个设备彼此相连,一个设备上收到的数据会被转发到另一个设备上,效果类似于管道,在这里配合网桥作用,就是把一个 namespace 发出的数据包转发到另一个 namespace,实现容器内外交互。
bridge与macvlan等插件属于同一层级,下图展示了相关插件与CNI的关系。
Bridge功能使用
这里先结合nginx说明bridge插件使用方法,按以下步骤操作,并确保每个阶段的状态检查正常后再进入下一步。
准备Kubernetes环境
(以下步骤略去细节,详细内容可以参考kubernetes快速安装部署实践)
-
启动docker和kubelet服务
systemctl start docker
systemctl start kubelet
确保所有节点均启用服务,这里为了保证后续操作不受干扰,对于已经安装过k8s环境的机器需要通过kubeadm reset命令重置。 -
master节点初始化
kubeadm init --pod-network-cidr=192.166.0.0/16
注意分配给容器使用的虚拟地址不能和node地址同网段,此后所有pod都会从该地址池分配IP。 -
添加工作节点
kubeadm join 192.168.122.17:6443 --token xxx(需使用上一步init返回的结果)
所有工作节点直接使用master节点上kubeadm init执行后返回的join命令串。 -
检查各node状态
在Master节点运行命令,确保所有节点处于ready状态,至此k8s环境生效。
kubectl get node -o wide
NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME
k8s-nfv-master Ready master 47h v1.14.3 192.168.122.17 <none> CentOS Linux 7 (Core) 3.10.0-957.el7.x86_64 docker://18.9.2
k8s-nfv-node1 Ready <none> 47h v1.14.3 192.168.122.18 <none> CentOS Linux 7 (Core) 3.10.0-957.el7.x86_64 docker://18.9.2
k8s-nfv-node2 Ready <none> 47h v1.14.3 192.168.122.19 <none> CentOS Linux 7 (Core) 3.10.0-957.el7.x86_64 docker://18.9.2
安装配置bridge插件
- 准备二进制文件
kubernetes版本1.14.3安装后默认包含bridge插件,其二进制可执行文件位于主节点的/opt/cni/bin目录中;如需通过源码编译生成可以按如下命令操作。
git clone https://github.com/containernetworking/plugins.git
cd plugins
./build_linux.sh
- 准备配置文件
在/etc/cni/net.d中增加cni配置文件10-bgnet.conf,指定使用的cni组件及参数;需要在所有节点配置该文件,各节点分配的IP范围可通过kubectl describe 查看 ;配置文件更新后,kubenet将会自动调用组件配置网络参数;也可以重启服务使配置立即生效。
systemctl restart docker
systemctl restart kubelet
Master配置:
{
"cniVersion":"0.3.1",
"name": "bgnet",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"forceAddress": false,
"ipMasq": true,
"hairpinMode": true,
"ipam": {
"type": "host-local",
"subnet": "192.166.0.0/24",
"rangeStart": "192.166.0.100", //可选
"rangeEnd": "192.166.0.200", //可选
"gateway": "192.166.0.1", //可选
"routes": [
{ "dst": "0.0.0.0/0" }
]
}
}
配置字段详细说明:
- cniVersion: 使用的CNI版本 name: 网络名称,在管理域中唯一 type: 插件类型,即插件名称,这里是bridge
- bridge:网桥接口名称,如cni0 isGateway:是否为网桥分配IP地址,以便连到网桥的容器可以将其用作网关
- isDefaultGateway: 设置为true时将网桥配置为虚拟网络的默认网关,默认值为false
- forceAddress:如果先前的IP已更改,则要求插件分配一个新的IP,默认为false ipMasq:是否创建IP
- masquerade,即为出口流量启用SNAT将源地址改为网桥IP,默认为false mtu: 设置MTU为指定值,默认使用内核数值
- hairpinMode:为网桥接口设置反射中继,打开则允许网桥通过接收了以太网帧的虚拟端口将其重新发送回去,即让POD是否可以通过Service访问自己,默认为false
- promiscMode: 设置网桥的混杂模式,默认为false vlan: 分配Vlan标签,默认为none
- ipam:
设置ip分配方案,对于L2-only模式,创建为空即可 type: 指定使用的方案,对应IPAM插件名称,这里是host-local - subnet: 分配的子网范围 rangeStart:开始的地址 rangeEnd:结束的地址 gateway: 容器内部网络的网关
- routes: 路由
Node1配置:
{
"cniVersion":"0.3.1",
"name": "bgnet",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "192.166.1.0/24",
"rangeStart": "192.166.1.100",
"rangeEnd": "192.166.1.200",
"gateway": "192.166.1.1",
"routes": [
{
"dst": "0.0.0.0/0" }
]
}
}
Node2配置:
{
"cniVersion":"0.3.1",
"name": "bgnet",
"type": "bridge",
"bridge": "cni0",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "192.166.2.0/24",
"rangeStart": "192.166.2.100",
"rangeEnd": "192.166.2.200",
"gateway": "192.166.2.1",
"routes": [
{
"dst": "0.0.0.0/0" }
]
}
}
- 检查pod状态
在主节点输入命令,查询所有pod均为Running状态,且有分配到地址;比如其中coredns的pod分配到地址192.166.0.105,状态为Running,至此cni插件已生效。
kubectl get pod -n kube-system -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
coredns-fb8b8dccf-r4hkc 1/1 Running 2 47h 192.166.0.104 k8s-nfv-master <none> <none>
coredns-fb8b8dccf-v8zc6 1/1 Running 2 47h 192.166.0.105 k8s-nfv-master <none> <none>
etcd-k8s-nfv-master 1/1 Running 2 47h 192.168.122.17 k8s-nfv-master <none> <none>
kube-apiserver-k8s-nfv-master 1/1 Running 2 47h 192.168.122.17 k8s-nfv-master <none> <none>
kube-controller-manager-k8s-nfv-master 1/1 Running 2 47h 192.168.122.17 k8s-nfv-master <none> <none>
kube-proxy-4f7vr 1/1 Running 2 47h 192.168.122.17 k8s-nfv-master <none> <none>
kube-proxy-6gb2d 1/1 Running 0 47h 192.168.122.19 k8s-nfv-node2 <none> <none>
kube-proxy-n8wxq 1/1 Running 0 47h 192.168.122.18 k8s-nfv-node1 <none> <none>
kube-scheduler-k8s-nfv-master 1/1 Running 2 47h 192.168.122.17 k8s-nfv-master <none> <none>
安装ningx容器验证
- 创建configMap
ConfigMap提供了向容器中注入配置文件的功能,目的就是为了让镜像和配置文件解耦,以便实现镜像的可移植性和可复用性。注入方式有两种,一种将configMap做为存储卷,一种是将configMap通过env中configMapKeyRef注入到容器中,这里用前者。
以下是nginx.conf配置文件:
user nginx;
worker_processes 1;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;
events {
worker_connections 1024;
}
http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
#tcp_nopush on;
keepalive_timeout 65;
#gzip on;
#include /etc/nginx/conf.d/*.conf;
server {
listen 80;
server_name localhost;
root /home/wwwroot/test;
index index.html;
}
}
在默认ns下,运行创建命令
kubectl create configmap confnginx --from-file nginx.conf
查看创建结果
kubectl get configmap
NAME DATA AGE
confnginx 1 41h
- 创建Replication Controller
RC可以保证在任意时间运行Pod的副本数量,保证Pod总是可用的。如果实际Pod数量比指定的多就结束掉多余的,如果实际数量比指定的少就新创建一些Pod,当Pod失败、被删除或者挂掉后,RC会去自动创建新的Pod来保证副本数量,这里使用RC来管理ngnix Pod。
apiVersion: v1
kind: ReplicationController //指明RC类型
metadata: //设置元数据
name: nginx-controller //RC自身命名
spec: //定义RC具体配置
replicas: 2 //设置pod数量维持在2个
selector: //通过selector来匹配相应pod的标签
name: nginx //服务名称
template: //设置pod模板
metadata:
labels: //设置标签
name: nginx
spec:
containers:
- name: nginx //容器名称
image: docker.io/nginx:alpine //使用的镜像
ports:
- containerPort: 80 //容器端口配置
volumeMounts:
- mountPath: /etc/nginx/nginx.conf //容器内部的配置目录
name: nginx-config
subPath: nginx.conf
- mountPath: /home/wwwroot/test //挂载节点主机目录
name: nginx-data
volumes:
- name: nginx-config //通过挂载存储卷注入configMap
configMap:
name: confnginx
- name: nginx-data
hostPath:
path: /home/wwwroot/test
创建nginx的RC pod,后续RC将按配置自动运行相应数量的ngnix容器
kubectl create -f nginx-rc.yaml
查看创建结果:
kubectl get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-controller-ggg2z 1/1 Running 0 30h 192.166.1.100 k8s-nfv-node1 <none> <none>
nginx-controller-s84rt 1/1 Running 0 30h 192.166.2.100 k8s-nfv-node2 <none> <none>
- 创建nginx的Service
Service可以看作是一组提供相同服务的Pod对外的访问接口,借助Service应用可以方便地实现服务发现和负载均衡。
nginx-svc.yaml如下:
apiVersion: v1
kind: Service // 指明service类型
metadata:
name: nginx-service-nodeport //指定服务名称
spec:
ports:
- port: 8000 //Service将会监听8000端口,并将所有监听到的请求转发给其管理的Pod
targetPort: 80 //Service监听到8000端口的请求会被转发给其管理的Pod的80端口
protocol: TCP
nodePort: 30080 //Service将通过Node上的30080端口暴露给外部
type: NodePort //此模式下访问任意一个NodeIP:nodePort都将路由到ClusterIP
selector:
name: nginx //指定service将要使用的标签,即会管理所有ngnix标签的pod
通过命令创建服务
kubectl create -f nginx-svc.yaml
查看服务状态:
kubectl get svc -A
NAMESPACE NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
default kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 2d
default nginx-service-nodeport NodePort 10.103.145.249 <none> 8000:30080/TCP 31h
kube-system kube-dns ClusterIP 10.96.0.10 <none> 53/UDP,53/TCP,9153/TCP 2d
其中10.103.145.249为服务对外提供的地址,端口为8000
-
验证连通性
1)Node1
通过node地址访问:wget http://192.168.122.18:30080/index.html
通过pod地址访问:wget http://192.166.1.100:80/index.html
通过service地址访问:wget http://10.103.145.249:8000/index.html
2)Node2
通过node地址访问:wget 192.168.122.19:30080
通过pod地址访问:wget http://192.166.2.100:80/index.html
通过service地址访问:wget http://10.103.145.249:8000/index.html验证节点内部访问正常,但节点之间无法访问,还需配置路由和转发规则
-
配置iptable规则
1)增加路由实现不同网段的跨节点访问
node1:
ip route add 192.166.2.0/24 via 192.168.122.19 dev ens3
node2:
ip route add 192.166.1.0/24 via 192.168.122.18 dev ens32)增加转发规则实现同节点上容器间和网桥的通讯
node1 & node2:
iptables -t filter -A FORWARD -s 192.166.0.0/16 -j ACCEPT
iptables -t filter -A FORWARD -d 192.166.0.0/16 -j ACCEPT3)增加NAT规则实现容器内部网络与外部网络的通讯
node1:
iptables -t nat -A POSTROUTING -s 192.166.1.0/24 ! -o cni0 -j MASQUERADE
node2:
iptables -t nat -A POSTROUTING -s 192.166.2.0/24 ! -o cni0 -j MASQUERADE此时在各节点使用wget测试,节点之间能相互连通,wget验证外网地址正常;由于service有进行负载均衡处理,在wget中可能散列到不同节点上获取文件。
-
进入容器验证
使用contain2ns.sh enter_ns.sh工具进入容器内部终端
1)查看接口:
Node1
ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.166.1.100 netmask 255.255.255.0 broadcast 192.166.1.255
ether 2e:ab:58:fc:45:3e txqueuelen 0 (Ethernet)
RX packets 365 bytes 29668 (28.9 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 288 bytes 31042 (30.3 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
Node2
ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 192.166.2.100 netmask 255.255.255.0 broadcast 192.166.2.255
ether 9e:ef:6f:db:d3:a6 txqueuelen 0 (Ethernet)
RX packets 192 bytes 14800 (14.4 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 146 bytes 14159 (13.8 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0
2)测试容器到容器连通性
在node1上ping node2:ping 192.166.2.100
tcpdump抓包显示:
14:36:32.233236 IP 192.166.1.100 > 192.166.2.100: ICMP echo request, id 30854, seq 1, length 64
14:36:32.234221 IP 192.166.2.100 > 192.166.1.100: ICMP echo reply, id 30854, seq 1, length 64
Node2:
14:35:07.696705 IP 192.168.122.18 > 192.166.2.100: ICMP echo request, id 30768, seq 1, length 64
14:35:07.696800 IP 192.166.2.100 > 192.