K8s四层网络
抽象成四层网络
Node节点网络
底层基础设施支持节点主机之间网络的互通
Pod网络
能够相互做IP寻址、相互通讯
同一节点上的Pod网络
大多数场景下一个pod下面有1个容器 也有一些场景下一个pod下有多个容器 这些容器共享pod中的网络栈
- eth0
是主机上的网卡 也是流量出入的设备 也支持k8s集群节点之间做网络寻址和互通的设备
- docker0
是一个虚拟网桥 简单理解为是一个虚拟交换机 支持节点上面或节点之间进行IP寻址和互通的设备
- veth0
是pod1上的虚拟网卡 支持pod内容器互通的设备 内部的容器可以通过localhost相互访问
- pause
pause是一个特殊的容器 为pod建立veth0网络共享的接口 通过docker ps就可以看到这个容器
- pod
pod的ip是docker0网桥分配的
不同节点上的Pod网络
pod1和pod2在同一个网桥dokcer0里 所以可以互联互通 节点内的pod网络在172.17.0.0/24这个地址空间内的 节点主机网络在10.100.0.0/24这个地址空间内的 也就是说 pod网络和节点网络不在同一个网络空间内
不同节点上的pod之间如何互通?
对于目标段172.17.1.0/24这个网段的pod转发到10.100.0.3这个主机 对于目标段172.17.0.0/24这个网段的pod转发到10.100.0.2这个主机 当主机eth0接受到pod网络的包就会向内部的pod网桥转发 这样不同节点的pod网络就可以IP寻址和通讯 这种方案引入了底层的网络设备 但是额外的网络开销比较少
覆盖网络方案(Overlay)
如果是公有云网络 就不支持路由方案 所以就可以用覆盖网络方案 所谓的覆盖网络方案就是在现有的网络基础上建立一个虚拟网络 所支持的技术很多 比如Flannel/Weavenet 大多采用隧道封包的技术
pod网络的数据包在出节点之前先被封装成节点网络的数据包 经过底层网络到达目标节点 就会被解封出来再转发给内部的pod网络 这种方案对底层网络没有太多依赖 但封包解包对性能有影响 考虑到pod网络实现技术众多 k8s为了简化集成 引入了CNI技术
CNI是一个Pod网络集成标准 简化K8s和不同Pod网络实现技术的集成 kubelet通过CNI插件去操作pod网络 而不需要关注底层的具体实现
Service
一个service背后往往是由多个pod组成的集群 由此引入了服务发现以及负载均衡等问题
服务发现与负载均衡
如何发现并且定位pod ip?
account-app pod集合中pod ip可能会变化 重新发布或自己挂了 k8s重新调度部署 也会变化
要解决服务发现问题 需要了解下技术演进的过程
DNS
最早是通过DNS来解决这个问题 K8s也支持DNS组件 运行的时候 K8s会把Pod集群中的Pod信息(ip和端口)注册到DNS上去 client应用通过查询DNS就可以发现目标Pod 就可以发起调用 对client无侵入 劣势: 1、有些客户端每次都会查询DNS服务 造成不必要的开销 2、有些客户端会缓存DNS信息 默认超时时间比较长 当目标Pod ip发生了变化 就会存在缓存刷新不及时问题 就会导致访问pod失效 3、大多数负载均衡策略比较简单 有些还不支持负载均衡 所以K8s并没有采用这种方式做服务发现 但实际上K8s还是引入了DNS组件来实现通过域名访问服务的
Service Registry+Client方案
这种方案典型的代表:Eureka+Ribbon/Consul/Nacos K8s 自带的分布式存储etcd 就可以用来实现Service Registry 1、k8s将pod集群中的pod信息自动注册到service registry 2、client应用可以通过servcie registry查询发现目标pod 然后可以发起调用 这种方案实现不复杂 client也可以实现灵活的负载均衡策略 但是对客户端应用有侵入性 所以k8s也没有直接采用这种方案
k8s service网络具体实现原理
Kube-Proxy、Kubelet、K8s Master 是K8s实现服务发现与注册到关键 "服务注册阶段" 1、服务Pod实例发布的时候 Kubelet会负责启动这些Pod实例 2、启动完之后 会把Pod实例列表注册到K8s Master上面去 3、在Servcie发布的时候 K8s会为Service分配ClusterIP 相关信息也会记录在Master节点上面 并且ClusterIP和PodIP有映射关系 "服务发现阶段" Kube-Proxy会监听Master并且发现Service的ClusterIp和Pod实例列表 并且修改本地的Linux Iptables转发机制 在接受到目标IP为某个ClusterIp的时候进行负载均衡并且转发到对应Pod上面去 "服务实际调用阶段" 当有消费者Pod需要访问某个服务的时候 先调用dns获取服务名 就通过clusterip发起调用 会被本地的iptables机制所截获 然后通过负载均衡转发到pod实例上面 dns组建也监听master节点并发现服务名称与clusterip之间的映射关系
k8s服务发现与eureka+ribbon方案对比
都是客户端代理的机制 负载均衡都是在客户端实现的 ribbon是以libary库的形式嵌入在客户端应用中的 对客户端应用是有侵入性的 而k8s的kube-proxy是独立的组件 每个docker节点上都有一个kube-proxy 对客户应用是无侵入的 该做法类似于ServiceMesh中的SideCar Ribbon转发是穿透的 K8s中的转发是通过iptables转发 kube-proxy只负责服务发现 修改iptables规则 请求是不穿透kube-proxy的 早期的k8s是穿kube-proxy的 考虑单点问题和性能损耗 新的版本不穿kube-proxy了 ribbon实现服务名到服务实例ip地址的映射 它只有一层映射 在k8s中是有两层映射的 kube-proxy是clusterip到podip的映射 kube-dns实现服务名到clusterip的映射 通过clusterip统一屏蔽服务发现和负载均衡问题