K8S容器网络
阅读原文时间:2021年07月15日阅读:1

1. 一个主机上多个容器通信

问题等同于:这个被隔离的容器进程,该如何跟其他 Network Namespace 里的容器进程进行交互呢?

1、容器直接使用宿主机网络栈的方式,虽然简单且能提供比较好的网络性能,但是不可避免地引入了共享网络资源的问题,比如端口冲突。

2、被限制在 Network Namespace 里的容器进程,实际上是通过 Veth Pair 设备 + 宿主机网桥的方式,实现了跟同其他容器的数据交换。

3、把这些容器“连接”到 docker0 网桥上呢?通过Veth Pair设备。

备注:

  • Docker 项目会默认在宿主机上创建一个名叫 docker0 的网桥,凡是连接在 docker0 网桥上的容器,就可以通过它来进行通信;

Veth Pair 设备的特点是:它被创建出来后,总是以两张虚拟网卡(Veth Peer)的形式成对出现的。并且,从其中一个“网卡”发出的数据包,可以直接出现在与它对应的另一张“网卡”上,哪怕这两个“网卡”在不同的 Network Namespace 里。

这个设备的一段在容器里面,叫做eth0网卡,另一端在宿主机上,是个虚拟网卡,插在了docker0上了。

  • 当你遇到容器连不通“外网”的时候,你都应该先试试 docker0 网桥能不能 ping 通,然后查看一下跟 docker0 和 Veth Pair 设备相关的 iptables 规则是不是有异常,往往就能够找到问题的答案了

 

2. 跨主机容器通信

用户的容器都连接在 docker0 网桥上。而网络插件则在宿主机上创建了一个特殊的设备(UDP 模式创建的是 TUN 设备,VXLAN 模式创建的则是 VTEP 设备),docker0 与这个设备之间,通过 IP 转发(路由表)进行协作。

网络插件真正要做的事情,则是通过某种方法,把不同宿主机上的特殊设备连通,从而达到容器跨主机通信的目的。

Kubernetes 是通过一个叫作 CNI 的接口,维护了一个单独的网桥来代替 docker0。这个网桥的名字就叫作:CNI 网桥,它在宿主机上的设备名称默认是:cni0。

Kubernetes 之所以要设置这样一个与 docker0 网桥功能几乎一样的 CNI 网桥:

l 一方面,Kubernetes 项目并没有使用 Docker 的网络模型(CNM),所以它并不希望、也不具备配置 docker0 网桥的能力;

l 另一方面,这还与 Kubernetes 如何配置 Pod,也就是 Infra 容器的 Network Namespace 密切相关。

Kubernetes 创建一个 Pod 的第一步,就是创建并启动一个 Infra 容器,用来“hold”住这个 Pod 的 Network Namespace。

CNI 的设计思想,就是:Kubernetes 在启动 Infra 容器之后,就可以直接调用 CNI 网络插件,为这个 Infra 容器的 Network Namespace,配置符合预期的网络栈。

3. CNI网络

问题牵引:
网络方案是谁?它和“CNI标准”的关系(实现)是?kubernetes网络配置由谁来完成?(或者说我要怎么做才能实现它??)

核心支撑点:
1、flannel网络方案本身
2、CNI插件,这里是内置的Flannel插件
3、dockershim(DRI)

两个背景知识:
1、CNI 的设计思想:Kubernetes 在启动 Infra 容器之后,就可以直接调用 CNI 网络插件,为这个 Infra 容器的 Network Namespace,配置符合预期的网络栈。
2、建立网络的“三类”基础组件/可执行文件。

串线(着重描述三个核心点之间的串联关系):
kubelet 创建 Pod ->创建 Infra 容器。主要是由(CRI)**dockershim **调用 Docker API 创建并启动 Infra 容器-> SetUpPod方法。方法的作用是:1.为 CNI 插件准备参数,2.然后调用 CNI 插件为 Infra 容器配置网络。
1.所需参数->实现ADD/DEL方法->CNI插件(*flannel插件*)实现。:
1.1参数一:由 dockershim 设置的一组 CNI 环境变量,ADD/DEL方法参数。
1.2参数二:是 dockershim 从 CNI “配置文件”里加载到的、默认插件的配置信息;由*flannel网络方案本身*安装时生成。
2.调用 CNI 插件:
引:"dockershim 对 *Flannel CNI 插件*的调用,其实就是走了个过场。Flannel CNI 插件唯一需要做的,就是对 dockershim 传来的 Network Configuration (CNI配置文件)进行补充。"
接下来,Flannel CNI 插件->调用 CNI bridge 插件(参数一:“CNI环境变量/ADD", 参数二:”Network Confiuration/Delegate"),-->“代表”Flannel,将容器加入CNI网络(cni0网桥)。

其实本章难点在于实现网络方案对应的CNI插件,即配置Infra容器的网络栈,并连到网桥上。整体流程是:kubelet创建Pod->创建Infra容器->调用SetUpPod()方法,该方法需要为CNI准备参数,然后调用CNI插件(flannel)为Infra配置网络;其中参数来源于1、dockershim设置的一组CNI环境变量;2、dockershim从CNI配置文件里(有flanneld启动后生成,类型为configmap)加载到的、默认插件的配置信息(network configuration),这里对CNI插件的调用,实际是network configuration进行补充。参数准备好后,调用Flannel CNI->调用CNI bridge(所需参数即为上面:设置的CNI环境变量和补充的network configuation)来执行具体的操作流程。

CNI 插件所需的基础可执行文件:

/opt/cni/bin目录下:

4. K8S集群网络要解决的问题

Kubernetes中的网络要解决的核心问题就是每台主机的IP地址网段划分,以及单个容器的IP地址分配。概括为:

l 保证每个Pod拥有一个集群内唯一的IP地址

l 保证不同节点的IP地址划分不会重复

l 保证跨节点的Pod可以互相通信

l 保证不同节点的Pod可以与跨节点的主机互相通信

5. Service

如何访问我的容器

1、为什么需要Service?

一方面是因为 Pod 的 IP 不是固定的;

另一方面则是因为一组 Pod 实例之间总会有负载均衡的需求。

2、Endpoints?

被Selector选中的Pod就是Service的Endpoints;

只有处于 Running 状态,且 readinessProbe 检查通过的 Pod,才会出现在 Service 的 Endpoints 列表里。

3、如何工作?

  • Service 是由 kube-proxy 组件,加上 iptables 来共同实现的。
  • Service 来说,一旦它被提交给 Kubernetes,那么 kube-proxy 就可以通过 Service 的 Informer 感知到这样一个 Service 对象的添加。而作为对这个事件的响应,它会在宿主机上创建这样一条 iptables 规则(你可以通过 iptables-save 看到它);
  • 这些 Endpoints 对应的 iptables 规则,正是 kube-proxy 通过监听 Pod 的变化事件,在宿主机上生成并维护的

 

备注:

1、Pod跟Service是通过Label确定的

2、iptables处理Service

kube-proxy 通过 iptables 处理 Service 的过程,其实需要在宿主机上设置相当多的 iptables 规则。而且,kube-proxy 还需要在控制循环里不断地刷新这些规则来确保它们始终是正确的。

当你的宿主机上有大量 Pod 的时候,成百上千条 iptables 规则不断地被刷新,会大量占用该宿主机的 CPU 资源,甚至会让宿主机“卡”在这个过程中。所以说,一直以来,基于 iptables 的 Service 实现,都是制约 Kubernetes 项目承载更多量级的 Pod 的主要障碍

6. 集群外部访问Service

1、NodePort

 

我在 ports 字段里声明了 Service 的 8080 端口代理 Pod 的 80 端口,Service 的 443 端口代理 Pod 的 443 端口。

client --> nodeIP:nodePort --> serviceVIP:port --> podIp:targetPort

2、LoadBalancer

3、ExternalName

4、通用排查分析方法

问题:Service没法通过DNS访问到

1、区分是Service的问题还是集群的DNS出了问题

可以通过检查K8S的master节点的Service DNS是否正常:

2、Service的Cluster IP无法访问到

#1 检查这个Service是否有对应的EndPoints;

Pod 的 readniessProbe 没通过,它也不会出现在 Endpoints 列表里。

#2  需要确认 kube-proxy 是否在正确运行

#3 仔细看宿主机上的iptables规则

备注:

Service本质:

所谓 Service,其实就是 Kubernetes 为 Pod 分配的、固定的、基于 iptables(或者 IPVS)的访问入口。而这些访问入口代理的 Pod 信息,则来自于 Etcd,由 kube-proxy 通过控制循环来维护。

7. Ingress

1、为什么需要Ingress?

由于每个 Service 都要有一个负载均衡服务,更希望看到 Kubernetes 为我内置一个全局的负载均衡器。然后,通过我访问的 URL,把请求转发给不同的后端 Service。

2、什么是Ingress?

这种全局的、为了代理不同后端 Service 而设置的负载均衡服务,就是 Kubernetes 里的 Ingress 服务。

所谓 Ingress,就是 Service 的“Service”。

3、怎么工作的?

首先:有个ingress对象;

 

rules字段,在K8S里称为IngressRule。

IngressRule 的 Key,就叫做:host。它必须是一个标准的域名格式(Fully Qualified Domain Name)的字符串,而不能是 IP 地址

当一个新的 Ingress 对象由用户创建后,nginx-ingress-controller 就会根据 Ingress 对象里定义的内容,生成一份对应的 Nginx 配置文件(/etc/nginx/nginx.conf),并使用这个配置文件启动一个 Nginx 服务。

一个 Nginx Ingress Controller 为你提供的服务,其实是一个可以根据 Ingress 对象和被代理后端 Service 的变化,来自动进行更新的 Nginx 负载均衡器。