Kubernetest网络
各容器之间的网络交互
一个容器想要与外界做到互通就需要一套网络栈也就是它发出、响应网络请求的基本环境,这其中就包括网卡、回环设备、路由表和iptables规则。
交互方式
本机容器通信
- Docker容器通过在宿主机上创建一个名字是docker0的网桥,来使连接到网桥上的容器进行通信
- 主要是通过一个叫Veth Pair的虚拟设备实现(这个虚拟设备总是成对出现的,当其中一个网卡发出请求时,另外一个就能接受到请求)
- 无论这两个设备是否在同一个network space,就相当于网线连接
- 原理无外乎使用etho网卡做ARP广播,从而通过IP找到Mac地址
跨主通信
- 本机交互通信不过是让宿主机作媒介连接多个容器网络,那跨多个宿主机呢?
- 很容易想到的是在容器网络的再搭建一层网络,只要实现这层网络的相互通信也就解决了各个宿主机容器间的网络通信问题。
跨主通信实现方式
- 举个例子比如当前宿主机
A
上有容器C1
想要访问到宿主机B
上的容器C2
- 首先
C1
发出请求,需要访问IP为1.1.1.2的C2
容器 - 请求先是到达
A
,A
上的docker0网桥没有这个网段,从而把这个请求的选择权交给宿主机A
的iptables - 这个时候
我们在iptables中预先创建好一系列路由规则
然后将这个请求发往C2
所在的宿主机就好了 - 那么如何
预先创建好路由规则呢?
,在K8s中所有容器其实都是其宿主机的一个子网,比如A的子网是1.1.2.0/24
,C1
的IP就是1.1.2.1
这样子,所以就可以通过IP找到宿主机B
,从而找到对应的C2
容器 - 而这个关系又保存在
Etcd
中,我们只需要监听Etcd
中IP规则的变化,实时更新iptables就好了
下面是 Flannel UDP模式的跨主通信的原理图
- 虽然UDP方案的跨主通信因为性能问题已经被抛弃(用户态内核态切换过多、需要三次数据来回拷贝),但却值得学习
- 除此之外Flannel项目还有两个跨主通信的解决方案
VXLAN
(virtual Extensible LAN)、host-gw
(host-gateway)
- 除此之外Flannel项目还有两个跨主通信的解决方案
K8s网络模型与CNI
- K8s中使用CNI网桥来替代了docker0网桥,替换的原因有两点:
- K8s没有使用Docker的网络模型,也就没有docker0网桥
- CNI与K8s配置pod的网络环境密切相关(即Infra 容器的 network namespace)
- 通过CNI样式的插件构建K8s网络能实现
各个容器
、宿主机
之间直接使用IP通信,实现的效果是在容器内访问容器的IP与其他容器访问容器的IP都是一模一样的,总之实现的就是实现互通
。
容器之间的网络隔离 - NetworkPolicy
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: test-network-policy
namespace: default
spec:
# 定义NetworkPolicy限制范围 --黑名单,所有role是xxx1的Pod都被拒绝,如果留空就是这个ns下的所有Pod
podSelector:
matchLabels:
role: xxx1
# 限制类型是流入或流出,下面定义的都是白名单
policyTypes:
- Ingress
- Egress
# 接受下面定义流入的ip比如1.1.0.0/16,拒绝1.1.1.0/24以及其他所有请求
# 流入
ingress:
- from:
- ipBlock:
cidr: 1.1.0.0/16
except:
- 1.1.1.0/24
- namespaceSelector:
matchLabels:
project: testPro
- podSelector:
matchLabels:
role: test
ports:
- protocol: TCP
port: 6379
# 流入
egress:
- to:
- ipBlock:
cidr: 100.0.0.0/24
ports:
- protocol: TCP
port: 5978
NetworkPolicy实现原理
- 实际上就是在宿主机上生成一系列特定iptables规则。这和安全组非常类似
- 一般来说,我们哪些地方需要NetworkPolicy,比如:job,cronjob这种计算型Pod不需要也不应该对外提供服务,所以就需要拒绝掉所有访问它们的流量,从而提升系统安全。
Service实现原理
kube-proxy + iptables
- 当Service被添加到K8s时,kube-proxy就会在宿主机上创建一条iptables规则集合
- 这个集合中就包含了Service所代理的所有Pod
- 同样Service的负载均衡也是在这里实现的
- 在访问Service的请求到来后,路由前,K8s会将流入的IP和端口通过iptables上的信息改成真是的目的IP与端口,也就是真是Pod的IP与Port
ipvs
- iptables维护路由规则的方式实际上有很大的性能开销,当有大量Pod的时候,需要去维护很多的iptables规则,不断刷新这些规则
- ipvc则不需要维护如此多的iptables规则,而是将规则的处理移动到了内核态,从而降低了开销
- 虽然其也需要iptables来做包过滤等操作,但所需的规则数量都是有限的,而不是随着Pod增加而增加。时刻保持访问Pod iptables规则的最新状态
如何访问Service - NodePort、LoadBalancer service、ExternalName
NodePort
apiVersion: v1
kind: Service
metadata:
name: test
labels:
run: test
spec:
type: NodePort
ports:
# 声明8080代理Pod的80端口
- nodePort: 8080
targetPort: 80
protocol: TCP
name: http
- nodePort: 443
protocol: TCP
name: https
selector:
run: test-pod
- 这个时候使用
宿主机IP
+8080
就可以访问了 - 当定义了这样一个文件后,kube-proxy同样也是生成一条iptables规则,让它能路由到具体Service上
- 这里需要注意的是在请求结束后,kube-proxy会给这个IP包进行一次
SNAT
操作,他会将这个IP包的源地址替换成当前宿主机的CNI网桥地址 - 这里为什么会有这一次
SNAT
操作呢?比如外部请求访问了node1
,然后这个请求又被路由到node2
上,当node2
处理完成后开始进行回复,那回复肯定要先回复到1上,然后1在回复到客户端(收到回复是不同主机,客户端可能会报错)
LoadBalancer类型的 Service
kind: Service
apiVersion: v1
metadata:
name: test-service
spec:
ports:
- port: 8765
targetPort: 9376
selector:
app: test-app
type: LoadBalancer
对于LoadBalancer类型的 Service,Kubernetes 会调用 CloudProvider 在公有云上创建一个负载均衡服务,并且把被代理的 Pod 的 IP 地址配置给负载均衡服务做后端。
ExternalName
kind: Service
apiVersion: v1
metadata:
name: test-service
spec:
type: ExternalName
externalName: my.test.com
这个时候访问test-service.default.svc.cluster.local
的时候返回的就是my.test.com
万变不离其宗
- Service其实就是 Kubernetes 为 Pod 分配的、固定的、基于 iptables(或者 IPVS)的访问入口。而这些访问入口代理的 Pod 信息,则来自于 Etcd,由 kube-proxy 通过控制循环来维护。
- 所以通过分析Service在宿主机上的
iptables
就能解决大多数问题(比如当service没法通过DNS访问时,就可以检查K8smaster节点和Service DNS是否异常)
访问Service的Service - Ingress
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: test-ingress
spec:
tls:
- hosts:
- test.com
secretName: test-secret
rules:
- host: test.com
http:
paths:
- path: /path1
backend:
serviceName: svc1
servicePort: 80
- path: /path2
backend:
serviceName: svc2
servicePort: 80
- 其实Ingress就是对K8s做的一个反向代理。
- 上面的Ingress定义的文件就把
svc1
与svc2
进行了代理,当访问path1
时,能路由到svc1
上 - 比如常用的nginx,官方维护的nginx-ingress-controller能根据Ingress与service的变化动态更新Nginx负载均衡器
- 当然请求的时候,如果没有任何一个IngressRule被匹配到,拿nginx-ingress-controller来说就会返回一个Nginx的404页面(也可以自己配置,比如专门启一个Pod用来返回404页面)