- 前言
- K8S的三个网段
- node网段
- service网段
- pod网段
- 认识kubeube-proxy和flannel
- kube-proxy
- kube-proxy 实例探测
- flannel
- flannel实例探测
前言
本文主要以实例讲解kube-proxy和flannel,以此理解K8S网络。
K8S的三个网段
-
node网段
主要负责K8S集群中的各个node之间通信的网络;
root@zenlin:~# kubectl get nodes
NAME STATUS AGE VERSION
zenlin Ready 19d v1.7.3
zenlinnode1 Ready 19d v1.7.3
zenlinnode2 Ready 19d v1.7.3
#以下为各node的物理IP
root@zenlin:~# kubectl get nodes zenlin -o 'jsonpath={.status.addresses[0].address}'
10.229.43.65
root@zenlin:~# kubectl get nodes zenlinnode1 -o 'jsonpath={.status.addresses[0].address}'
10.229.53.146
root@zenlin:~# kubectl get nodes zenlinnode2 -o 'jsonpath={.status.addresses[0].address
10.229.45.161
root@zenlin:~# kubectl get nodes zenlin -o 'jsonpath={.spec.podCIDR}'
10.244.0.0/24
root@zenlin:~# kubectl get nodes zenlinnode1 -o 'jsonpath={.spec.podCIDR}'
10.244.1.0/24
root@zenlin:~# kubectl get nodes zenlinnode2 -o 'jsonpath={.spec.podCIDR}'
10.244.2.0/24
这是K8S的每个node上的podCIDR,用于给每个pod设置ip连接到node的网桥上,这将会在后面的章节中用到。
-
service网段
每个新创建的service都会分配到一个service的cluster IP,在笔者的集群中为10.0.0.0这个网段内分配。
root@zenlin:~# kubectl get svc
NAME CLUSTER-IP EXTERNAL-IP PORT(S) AGE
details 10.111.84.67 <none> 9080/TCP 3d
grafana 10.110.131.254 <none> 3000/TCP 7d
istio-egress 10.99.236.93 <none> 80/TCP 7d
kubernetes 10.96.0.1 <none> 443/TCP 19d
nginx-service 10.105.211.16 <none> 8000/TCP 9d
php-apache 10.106.156.64 <none> 80/TCP 13d
productpage 10.104.17.86 <none> 9080/TCP 3d
prometheus 10.109.48.180 <none> 9090/TCP 7d
ratings 10.100.168.14 <none> 9080/TCP 3d
reviews 10.110.130.11 <none> 9080/TCP 3d
servicegraph 10.104.205.192 <none> 8088/TCP 7d
zipkin 10.105.183.37 <nodes> 9411:30305/TCP 3d
-
pod网段
在笔者环境中,安装了fannal隧道网络,则pod网络其实就是fannel 网络,以帮助不同node之间的pod之间进行通信,fannel使整个K8S集群网络扁平化,不管是在node内还是node之间,pod之间的通信都可以通过pod IP进行。
可以看到,在笔者的环境中,pod的网络都位于10.244.0.0/16网段内,所有的pod将会被分配在这个网段。
root@zenlin:~# kubectl get po -owide
NAME READY STATUS RESTARTS AGE IP NODE
details-v1-3006205406-dnm69 2/2 Running 0 3d 10.244.1.137 zenlinnode1
grafana-1011650190-1f9h4 1/1 Running 0 7d 10.244.1.114 zenlinnode1
nginx-deployment-1885164871-dzls7 1/1 Running 0 9d 10.244.1.53 zenlinnode1
nginx-deployment-1885164871-q37zh 1/1 Running 0 9d 10.244.2.199 zenlinnode2
productpage-v1-4256385220-nvq0d 2/2 Running 0 3d 10.244.2.240 zenlinnode2
prometheus-4245872192-xz47s 1/1 Running 0 7d
##
认识kubeube-proxy和flannel
-
kube-proxy
kube-proxy就是一个简单的网络代理和负载均衡器,主要实现了内部从pod到service和外部从nodePort到service的访问。
kube-proxy主要有userspace和iptables两种模式,默认使用的是iptables这种模式,笔者的环境也是,故,我们只介绍iptables模式。
在这种模式下,kube-proxy监听kubernetes master添加和删除services和端点对象。对每个服务,kube-proxy安装iptalbes规则,用来捕获service的cluster IP和port的流量,并将重定向到serivce的后端集合之一(如pod),对于每个端点对象,它选择一个后端pod来安装iptables 规则。
默认情况下,后端的选择是随机的。可以通过将service.spec.sessionAffinity设置为“ClientIP”(默认为“无”)来选择基于客户端IP的会话关联。
由于iptables实际上是走的kernel态的Netflix,这会比userspace这种模式更快,更可靠。如果最初选择的pod不响应,则iptables代理不能自动重试。
具体关于kube-proxy的更多信息可参考官网:services-networking/service
-
kube-proxy 实例探测
通过K8S 创建nginx service,部署两个po,如下:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: nginx-deployment
spec:
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.8
ports:
- containerPort: 80
---
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
ports:
- port: 8000
targetPort: 80
protocol: TCP
selector:
app: nginx
通过K8S可以看到,创建了两个po和一个svc,两个pod分别位于两个node,cluster-ip分别为10.244.1.144 和 10.244.2.245,svc的cluster-ip为10.105.211.16
root@zenlin:~# kubectl get svc |grep nginx
nginx-service 10.105.211.16 <none> 8000/TCP 14d
root@zenlin:~# kubectl get po -owide -n zenlin
NAME READY STATUS RESTARTS AGE IP NODE
ubuntu-k8s-deployment-623915768-ctbd2 0/1 Running 0 7d 10.244.1.144 zenlinnode1
ubuntu-k8s-deployment-623915768-hsdrk 1/1 Running 0 7d 10.244.2.245 zenlinnode2
分别在三个节点上,查看kube-proxy下发的iptables规则:
root@zenlin:~# iptables-save |grep nginx
-A KUBE-SEP-RGXD32JDVTBAD2NU -s 10.244.1.53/32 -m comment --comment "default/nginx-service:" -j KUBE-MARK-MASQ
-A KUBE-SEP-RGXD32JDVTBAD2NU -p tcp -m comment --comment "default/nginx-service:" -m tcp -j DNAT --to-destination 10.244.1.53:80
# 先做mark,再做dnat,将serviceip(10.244.1.53/32)改为pod ip(10.244.1.53:80),送给pod。
-A KUBE-SEP-YEFU7DZX4NWUTRQS -s 10.244.2.199/32 -m comment --comment "default/nginx-service:" -j KUBE-MARK-MASQ
-A KUBE-SEP-YEFU7DZX4NWUTRQS -p tcp -m comment --comment "default/nginx-service:" -m tcp -j DNAT --to-destination 10.244.2.199:80
# 先做mark,再做dnat,将serviceip(10.244.1.53/32)改为pod ip(10.244.2.199:80),送给pod。
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.105.211.16/32 -p tcp -m comment --comment "default/nginx-service: cluster IP" -m tcp --dport 8000 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.105.211.16/32 -p tcp -m comment --comment "default/nginx-service: cluster IP" -m tcp --dport 8000 -j KUBE-SVC-GKN7Y2BSGW4NJTYL
-A KUBE-SVC-GKN7Y2BSGW4NJTYL -m comment --comment "default/nginx-service:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-RGXD32JDVTBAD2NU
-A KUBE-SVC-GKN7Y2BSGW4NJTYL -m comment --comment "default/nginx-service:" -j KUBE-SEP-YEFU7DZX4NWUTRQS
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.105.211.16/32 -p tcp -m comment –comment “default/nginx-service: cluster IP” -m tcp –dport 8000 -j KUBE-MARK-MASQ -A KUBE-SERVICES -d 10.105.211.16/32 -p tcp -m comment –comment “default/nginx-service: cluster IP” -m tcp –dport 8000 -j KUBE-SVC-GKN7Y2BSGW4NJTYL
iptables 在看到目的地为 10.105.211.16(nginx-service的cluster ip)的目的地端口80端口的tcp报文时,走KUBE-SVC-GKN7Y2BSGW4NJTYL 规则;
-A KUBE-SVC-GKN7Y2BSGW4NJTYL -m comment –comment “default/nginx-service:” -m statistic –mode random –probability 0.50000000000 -j KUBE-SEP-RGXD32JDVTBAD2NU -A KUBE-SVC-GKN7Y2BSGW4NJTYL -m comment –comment “default/nginx-service:” -j KUBE-SEP-YEFU7DZX4NWUTRQS
以上两条,实现按照50%的统计概率随机匹配到KUBE-SEP-RGXD32JDVTBAD2NU和KUBE-SEP-YEFU7DZX4NWUTRQS这两条规则。
-A KUBE-SEP-RGXD32JDVTBAD2NU -s 10.244.1.53/32 -m comment –comment “default/nginx-service:” -j KUBE-MARK-MASQ -A KUBE-SEP-RGXD32JDVTBAD2NU -p tcp -m comment –comment “default/nginx-service:” -m tcp -j DNAT –to-destination 10.244.1.53:80
先做mark,再做dnat,将serviceip(10.244.1.53/32)改为pod ip(10.244.1.53:80),送给pod。
-A KUBE-SEP-YEFU7DZX4NWUTRQS -s 10.244.2.199/32 -m comment –comment “default/nginx-service:” -j KUBE-MARK-MASQ -A KUBE-SEP-YEFU7DZX4NWUTRQS -p tcp -m comment –comment “default/nginx-service:” -m tcp -j DNAT –to-destination 10.244.2.199:80
先做mark,再做dnat,将serviceip(10.244.1.53/32)改为pod ip(10.244.2.199:80),送给pod。
-
flannel
默认的K8S网络中,集群中不同的node的docker container会被node上的docker daemon分配IP,这将直接导致不同node上的docker container IP存在重叠,但是并不是全集群唯一的IP,随着node数量的增加,一旦出现网络问题之后跟踪定位会显得复杂度大大增大。
Flannel属于隧道网络(Overlay Networking)的一种实现,可以让集群中的不同node创建的docker container 都被分配全集群唯一的Cluster IP,使能“同属同一个内网” 且 “IP地址不重复”,并且不同节点上的容器能够直接通过内网IP通信。
Flannel目前已经支持udp、vxlan、host-gw、aws-vpc、gce和alloc路由等数据转发方式,笔者的环境上使用的是默认的UDP,采用以下的通信方式进行通信:
源主机的flanneld服务将数据内容进行UDP封装后投递给目的节点的flanneld服务处理。
- Flannel通过etcd给每个节点分配可用的IP地址段,并让docker daemon感知,这样docker daemon只在该网段内为所在node节点上的contaienr分配IP。flannel通过保存在etcd中的记录确保分配给不同node的docker daemon的网段不会重复。
- 从源node到目的node的路由是如何处理,我们在后面的章节中,用实例来讲解。
-
flannel实例探测
我们通过K8S创建一个ubuntu镜像的deployment,两个pod:
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: ubuntu-deployment
namespace: zenlin
spec:
replicas: 2
template:
metadata:
creationTimestamp: null
labels:
app: ubuntu
spec:
containers:
- image: ubuntu:zenlin
imagePullPolicy: IfNotPresent
name: ubuntu
command: ["/bin/sh"]
args: ["-c", "while true; do echo hello; sleep 10;done"]
ports:
- containerPort: 80
protocol: TCP
resources: {}
securityContext:
privileged: true
dnsPolicy: ClusterFirst
restartPolicy: Always
schedulerName: default-scheduler
terminationGracePeriodSeconds: 30
root@zenlin:~# kubectl get po -owide -n zenlin |grep ubuntu
ubuntu-k8s-deployment-623915768-fnssv 1/1 Running 0 8h 10.244.1.145 zenlinnode1
ubuntu-k8s-deployment-623915768-nwzk9 1/1 Running 0 8h 10.244.2.246 zenlinnode2
可观测到,两个pod分为位于两个node上,并且cluster ip分别为10.244.1.145 和 10.244.2.246
我们进入node1上的pod ubuntu-k8s-deployment-623915768-nwzk9:
root@zenlin:~# kubectl exec -it ubuntu-k8s-deployment-623915768-fnssv /bin/bash -n zenlin
root@ubuntu-k8s-deployment-623915768-fnssv:/# ifconfig
eth0 Link encap:Ethernet HWaddr 9a:59:82:99:75:d6
inet addr:10.244.1.145 Bcast:0.0.0.0 Mask:255.255.255.255
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:12 errors:0 dropped:0 overruns:0 frame:0
TX packets:4 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:928 (928.0 B) TX bytes:280 (280.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:1 errors:0 dropped:0 overruns:0 frame:0
TX packets:1 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1
RX bytes:28 (28.0 B) TX bytes:28 (28.0 B)
root@ubuntu-k8s-deployment-623915768-fnssv:/#
再查看node1和node2节点上的路由分别为:
root@zenlinnode1:~# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.229.52.1 0.0.0.0 UG 0 0 0 eth0
10.229.52.0 0.0.0.0 255.255.254.0 U 0 0 0 eth0
10.244.0.0 0.0.0.0 255.255.0.0 U 0 0 0 flannel.1
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
root@zenlinnode1:~#
root@zenlinnode2:~# route -n
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 10.229.44.1 0.0.0.0 UG 0 0 0 eth0
10.229.44.0 0.0.0.0 255.255.254.0 U 0 0 0 eth0
10.244.0.0 0.0.0.0 255.255.0.0 U 0 0 0 flannel.1
172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0
当flannel被安装后,会在K8S的所有节点上安装上flanneld服务,各flanneld之间通过etcd查询各自分配的网段,并且彼此将进行通信。
以上,当一个数据包从ubuntu-k8s-deployment-623915768-fnssv 这个pod发往ubuntu-k8s-deployment-623915768-nwzk9 这个pod时,流程如下:
- 源数据包从容器内出发到达本机docker0网桥(本机docker通信请读者自行脑补哈),这个时候,根据发送节点的路由表,发现ip 10.244.1.145 只与 10.244.0.0(node1上的falnnel1.1)匹配,数据从docker0出来后被投递到flannel0;
- node1节点上的flanneld会对数据包进行UDP封装形成
playlod | Inner IP | UDP | Outer IP
的数据包; - node1的flanneld会根据Outer IP投递给node2的flanneld服务;
- node2上的flannld拿到数据后,进行解包,数据包进入node2上的flannel.1虚拟网卡;
- 数据包被转送给docker0虚拟网卡,剩下的部分就像本节点内的容器通信一样,达到目标容器。