k8s网络系列学习笔记之一-----Service简介

从今天开始,打算整理一下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")。

services-userspace-overview.jpg
  • iptables
    这种模式,kube-proxy 会监视 Kubernetes master 对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会安装 iptables 规则,从而捕获到达该 ServiceclusterIP(虚拟 IP)和端口的请求,进而将请求重定向到 Service 的一组 backend 中的某个上面。 对于每个 Endpoints 对象,它也会安装 iptables 规则,这个规则会选择一个 backend Pod

默认的策略是,随机选择一个 backend。 实现基于客户端 IP 的会话亲和性,可以将 service.spec.sessionAffinity 的值设置为 "ClientIP" (默认值为 "None")。

和 userspace 代理类似,网络返回的结果是,任何到达 Service 的 IP:Port 的请求,都会被代理到一个合适的 backend,不需要客户端知道关于 Kubernetes、Service、或 Pod 的任何信息。 这应该比 userspace 代理更快、更可靠。然而,不像 userspace 代理,如果初始选择的 Pod 没有响应,iptables 代理不能自动地重试另一个 Pod,所以它需要依赖 readiness probes

services-iptables-overview.jpg
  • 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代理模式。

service-ipvs-overview.png

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的相关的笔记内容。

推荐阅读更多精彩内容