从今天开始,打算整理一下k8s网络相关的知识和内容,这块的内容较多,将分多个章节进行学习、记录和分析,有纰漏的地方还希望读者指正。本文不会逐个介绍Service的基本功能,这里主要介绍一些跟网络相关的内容关心的内容。
前言
在早期运维时代,我们都是在物理机上部署程序,一般会选择一台主机,把要部署的程序安装到这台主机上,但是一般部署到这台机器后,我们一般认为这个进程存在与这台机器上这个状态是恒定的。当然这个进程有可能会挂掉,机器也有可能会重启,我们需要能够自动拉起相应的进程,拉起后它仍然在这台机器上。但是在程序挂或者机器重启的过程中,我们访问这个进程就会出错,会造成一段时间的服务不可用。
在微服务架构中,我们一般会把服务注册到一个注册中心,如果服务挂了,就会从注册中心摘掉。所以一般会通过部署多个服务进程,来保证服务整体的可用性,实现不间断的服务能力。
Pod简介
Kubernetes Pod是Kubernetes的一个最小部署单元,它由一个或者多个容器组成(这里一般指Docker容器),这里我们在物理机时代部署一个进程类似,但是在Kubernetes中,一般认为Pod是一个临时的实体,而不是永久性实体,Pod是有生命周期的,它们可以被创建,也可以被销毁,是一个临时实体,不是一个持久性的实体,每个Pod成功部署后,会有一个唯一的UID标识,一旦销毁后(不管什么原因),它就不能被恢复了。
所以一般我们会通过Deployment或者ReplicationController来管理Pod,实现Pod动态的创建和销毁,当一个Pod被销毁或者Pod所在的主机宕机之后,会自动重新调度创建出一个Pod,这个Pod与之前的Pod的名字可以相同,但是UID是不同的,而且它们的IP也会不同,那么当通过Deployment或者ReplicationController管理的一组Pod向其他的Pod提供服务时,如果保留自己呢?
当然这里也可以基于前面提到的微服务架构体系来实现,但是Kubernetes有自己的一套解决方案,那就是通过Service来提供统一的访问入口。
Service简介
Kubernetes Service与微服务类似,它提供了一套一组Pod的抽象,可以让其他的Pod(或者物理进程)访问到底层的一组Pod,实现负载均衡和高可用服务能力。
Kubernetes Service通常通过Label Selector来实现与底层的Pod的关联,并且会进行实时的关联。Service会基于Endpoints API实时检测到底层Pod的变化,并进行更新,与微服务的原理类似,唯一不同的是访问的方式不太一致。
- 一般的微服务需要实现一个富SDK,在SDK中实现对多个backend访问的高可用和负载均衡的能力,所以微服务体系在开发层面来讲,要做到语言无关性需要额外做出很多工作;
- k8s service一般提供了基于VIP代理的方式访问Service,首先访问到vip和对应的端口,再由Service重定向到管理的Pod中,保持了API的一致性;
kube-proxy代理模式
K8S Service的这种服务能力是在kube-proxy中的代理来实现的,kube-proxy在 Kubernetes v1.0 版本,代理完全在 userspace,在 Kubernetes v1.1 版本,新增了 iptables 代理,但并不是默认的运行模式。 从 Kubernetes v1.2 起,默认就是 iptables 代理,发展到v1.8版本开始引入ipvs模式,并在Kubernetes 1.11进入GA,在Kubernetes1.12成为kube-proxy的默认代理模式。
- userspace
这种模式,kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会在本地 Node 上打开一个端口(随机选择)。 任何连接到“代理端口”的请求,都会被代理到 Service 的backend Pods 中的某个上面(如 Endpoints 所报告的一样)。 使用哪个 backend Pod,是基于 Service 的 SessionAffinity 来确定的。 最后,它安装 iptables 规则,捕获到达该 Service 的 clusterIP(是虚拟 IP)和 Port 的请求,并重定向到代理端口,代理端口再代理请求到 backend Pod。
网络返回的结果是,任何到达 Service 的 IP:Port 的请求,都会被代理到一个合适的 backend,不需要客户端知道关于 Kubernetes、Service、或 Pod 的任何信息。
默认的策略是,通过 round-robin 算法来选择 backend Pod。 实现基于客户端 IP 的会话亲和性,可以通过设置 service.spec.sessionAffinity 的值为 "ClientIP" (默认值为 "None")。
- iptables
这种模式,kube-proxy 会监视 Kubernetes master 对Service
对象和Endpoints
对象的添加和移除。 对每个Service
,它会安装 iptables 规则,从而捕获到达该Service
的clusterIP
(虚拟 IP)和端口的请求,进而将请求重定向到Service
的一组 backend 中的某个上面。 对于每个Endpoints
对象,它也会安装 iptables 规则,这个规则会选择一个 backendPod
。
默认的策略是,随机选择一个 backend。 实现基于客户端 IP 的会话亲和性,可以将 service.spec.sessionAffinity
的值设置为 "ClientIP"
(默认值为 "None"
)。
和 userspace 代理类似,网络返回的结果是,任何到达 Service
的 IP:Port 的请求,都会被代理到一个合适的 backend,不需要客户端知道关于 Kubernetes、Service
、或 Pod
的任何信息。 这应该比 userspace 代理更快、更可靠。然而,不像 userspace 代理,如果初始选择的 Pod
没有响应,iptables 代理不能自动地重试另一个 Pod
,所以它需要依赖 readiness probes。
- ipvs
这种模式,kube-proxy会监视Kubernetes Service对象和Endpoints,调用netlink接口以相应地创建ipvs规则并定期与Kubernetes Service对象和Endpoints对象同步ipvs规则,以确保ipvs状态与期望一致。访问服务时,流量将被重定向到其中一个后端Pod。
与iptables类似,ipvs基于netfilter 的 hook 功能,但使用哈希表作为底层数据结构并在内核空间中工作。这意味着ipvs可以更快地重定向流量,并且在同步代理规则时具有更好的性能。此外,ipvs为负载均衡算法提供了更多选项,例如:
rr:轮询调度
lc:最小连接数
dh:目标哈希
sh:源哈希
sed:最短期望延迟
nq: 不排队调度
注意: ipvs模式假定在运行kube-proxy之前在节点上都已经安装了IPVS内核模块。当kube-proxy以ipvs代理模式启动时,kube-proxy将验证节点上是否安装了IPVS模块,如果未安装,则kube-proxy将回退到iptables代理模式。
K8S中的Pod信息
在K8s中有几个不同的port申明。
- 在Pod中就会有定义
pod.spec.containers[].ports,这个定义描述了pods的容器暴漏的端口,其实即使我们不在Pod中申明的暴漏的端口号,你的容器依然后占用相应的端口号,这里的端口号的申明另有意义。
- 在Service中的port、targetPort、nodePort
service中通过service.spec.ports列表配置了一个服务的端口以及代理的Pod的目的端口。
NodePort可以让我们可以通过集群外访问到主机IP和对应的端口号,也可以在集群内访问到服务的ClusterIP和端口号,最终被Pod的targetPort接受。
NodePort:该字段让服务在集群外部可见,通过暴漏节点上IP和定义的NodePort,服务必须是NodePort类型;
Port:通过指定的端口在集群内部暴漏服务的端口号;
targetPod:描述了Service映射到Pod的端口号,缺省不指定这个字段,那么它会与Port字段相同,当Port与targetPod不同时可以在这里指定。与此同时,这个字段还提供了更高的灵活性,它是一个字符串类型的,我们除了指定一个端口号以外,还可以指定前面我们提到的Pod申明暴漏的端口的名字,通过这种方式,我们可以不要求代理的Pod侦听的相同的端口号。譬如如果我们有两个版本的程序发布在K8S中,它们可以有不同的端口号,而对外仍然保持统一的服务,不影响外部应用的访问。
总结
本次希望逐步分析K8S网络相关的专题,所以没有完整介绍Service的概念,主要介绍了Service网络相同的基本知识,并把它与微服务进行了简单的对比。
从对比中,我们可以看出Service的独特之处在于它是基于代理的模式,实现了负载均衡和反向代理功能,这种服务的好处在于整个服务对外只需要暴漏API,这样不需要考虑高可用性、负载均衡等,可以通过自己熟悉的任何语言来访问服务。ServiceMesh在这方面做的更彻底,对外也是只暴漏原生的API,而把微服务的几乎所有特性,包括路由格则、服务发现和负载均衡、断路器、超时重试、错误注入、安全等都封装在底层。
后续的几个章节,会接下来会逐步记录学习Service的相关的笔记内容。