当前位置: 首页 > 科技观察

本文详细讲解了Kubernetes中的服务发现,请收藏

时间:2023-03-20 13:09:28 科技观察

K8S服务发现之旅Kubernetes服务发现是经常让我困惑的话题之一。本文分为两部分:网络背景知识深入理解Kubernetes服务发现要理解服务发现,首先要了解其背后的网络知识。这部分比较简单。如果读者对这部分比较熟悉,可以跳过,直接阅读服务发现部分。在开始之前需要提醒的另一件事是,这篇文章稍微长一些,以便详细描述这个过程。K8S网络基础在开始探索服务发现之前,您需要了解以下内容:Kubernetes应用程序运行在容器中,容器在Pod中。每个Pod都连接到同一个大型扁平IP网络,称为Pod网络(通常是VXLAN覆盖网络)。每个Pod都有自己唯一的IP地址,可在Pod网络内路由。以上三个因素结合在一起,使得各个应用程序(应用程序的组件和服务)可以直接通信,而无需经过NAT等网络进程。当动态网络横向扩展应用时,Pod网络中会加入新的Pod,新的Pod自然会伴随新的IP地址;如果应用程序缩小,旧的Pod及其IP将被删除。这个过程看起来很混乱。这同样适用于应用程序的滚动更新和回滚——为新版本添加新Pod,或为旧版本删除旧Pod。新的Pod将向Pod网络添加新的IP,而终止的旧Pod将删除其现有IP。如果不出意外,每个应用程序服务都需要监控网络并管理健康pod列表。这个过程会很痛苦,而且在每个应用程序中都写这个逻辑是很低效的。幸运的是,Kubernetes用一个对象——Service来完成这个过程。将此对象称为服务不是一个好主意,我们已经使用这个词来描述应用程序的流程或组件。还有一件事值得注意:Kubernetes执行IP地址管理(IPAM)职责,跟踪pod网络上已用和可用的IP地址。服务带来稳定性Kubernetes服务对象在一组提供服务的Pod之间创建稳定的网络端点,并在这些Pod之间分配负载。通常,一个Service对象被放置在一组执行相同工作的Pod之前。例如,您可以在Web前端Pod前面有一个Service,在AuthenticationServicePod前面有另一个。执行不同职责的Pod之前不应使用单个Service。客户端与Service通信,Service负责均衡流量负载到Pod。上图中,最下面的Pod会因为伸缩、更新、故障等发生变化,Service会跟踪这些变化。同时,Service的名称、IP和端口不会改变。K8SService分析可以将KubernetesService理解为前端和后端两部分:前端:名称、IP、端口等常量部分。Backend:满足一定标签选择标准的Pod集合。前端稳定可靠,其名称、IP和端口在服务的整个生命周期内不会发生变化。前端的稳定性意味着无需担心客户端DNS缓存超时等问题。后端是高度动态的,由一组符合标签选择条件的Pod组成,这些Pod以负载均衡的方式访问。这里的负载均衡是一个简单的第4层循环。它在连接级别工作,因此在同一个连接中发起的所有请求都将转到同一个Pod。因为它在第4层工作,所以它不知道第7层HTTP标头或cookie。摘要应用程序在容器中运行,容器在Kubernetes中表示为pod。Kubernetes集群中的所有Pod都在同一个平面Pod网络中,并有自己的IP地址。这意味着所有Pod都直接相互连接。然而,Pod是不稳定的,可能会由于各种因素而被创建和销毁。Kubernetes提供了一个稳定的网络端点,称为服务,一个位于一组类似Pod前面的对象,提供稳定的名称、IP和端口。客户端连接到服务,服务负载平衡到Pod的流量。接下来,让我们谈谈服务发现。深入了解K8S服务发现深入了解Kubernetes服务发现服务发现其实包括两个功能点:服务注册服务发现服务注册服务注册的过程是指将一个服务注册到服务注册中心,以便其他服务被发现。Kubernetes使用DNS作为服务注册表。为了满足这种需求,每个Kubernetes集群在kube-system命名空间中运行一个DNS服务作为Pod,通常称为集群DNS。每个Kubernetes服务都会自动注册到集群DNS。注册流程大致如下:通过POST方式向APIServer提交一个新的Service定义;只有通过身份验证、授权和其他访问策略检查后,该请求才会被释放;Service获取一个ClusterIP(虚拟IP地址),并保存到集群数据仓库;在集群内传播服务配置;集群DNS服务知道服务的创建,并相应地创建必要的DNSA记录。在上述过程中,第五步是关键环节。集群DNS使用CoreDNS,作为Kubernetes原生应用程序运行。CoreDNS实现了一个监控APIServer的控制器。一旦找到一个新的服务对象,它就会创建一个从服务名称映射到ClusterIP的域名记录。这样Service就不需要自己去DNS注册了,CoreDNS控制器会关注到新创建的Service对象并执行后续的DNS流程。在DNS中注册的名字是metadata.name,ClusterIP是Kubernetes自己分配的。Service对象在集群DNS中注册后,就可以被集群中运行的其他Pod发现。前端的Endpoint对象Service创建成功并注册到服务注册中心(DNS)后,剩下的就是后端的工作了。后端包含一个Pod列表,Service对象会将流量分配给这些Pod。毫无疑问,这个Pod列表需要是最新的。Service对象有一个LabelSelector字段,它是一个标签列表,满足列表条件的Pod会被服务纳入服务的负载均衡范围。见下图:Kubernetes自动为每个Service创建一个Endpoints对象。Endpoints对象负责保存一个Pod列表,这些Pod与服务标签选择器标准相匹配,这些Pod将从服务接收流量。下图中,Service会选择两个Pod,同时也展示了Service的Endpoints对象,其中包含两个符合Service选择条件的Pod的IP。后面我们会讲解网络是如何将ClusterIP流量转发给PodIP的,同时也会提到Endpoints对象。服务发现假设我们在Kubernetes集群中有两个应用程序,my-app和your-app。my-app的Pod前端是一个名为my-app-svc的Service对象;your-appPod之前的Service是your-app-svc。这两个Service对象对应的DNS记录为:my-app-svc:10.0.0.10your-app-svc:10.0.0.20要使用服务发现,每个Pod需要知道集群DNS的位置才能使用它.所以每个容器在每个Pod中的/etc/resolv.conf文件都配置为使用集群DNS进行解析。如果my-app中的pod想要连接到your-app中的pod,它必须向DNS服务器查询域名your-app-svc。假设他们的本地DNS解析缓存中没有这条记录,他们需要将查询提交到集群DNS服务器。您将获得you-app-svc的ClusterIP(VIP)。这里的前提是my-app需要知道目标服务的名称。至此,my-app中的Pod有了目标IP地址,但这只是一个虚拟IP,在转移到目标Pod之前还需要做一些网络工作。网络上的Pod获取Service的ClusterIP后,它会尝试向该IP发送流量。不过ClusterIP所在的网络叫做ServiceNetwork,这个网络有点特殊——没有路由指向它。由于没有路由,所有发现此类地址的容器都会将流量发送到默认网关(名为CBR0的网桥)。这些流量会被转发到Pod所在节点的网卡上。节点的网络栈也没有到达ServiceNetwork的路由,只能发送到自己的默认网关。路由到节点默认网关的数据包通过节点核心-这里有一个变化。回顾之前的内容。首先Service对象的配置是对整个集群有效的,后面会再提到Endpoints对象。我们会回过头来发现他们在这个过程中各自的责任。每个Kubernetes节点都运行一个名为kube-proxy的系统服务。这是一个基于Pod的Kubernetes原生应用。它实现的controller会监听APIServer上Service的变化,并据此创建iptables或IPVS规则。转发到PodIP。有趣的是,kube-proxy并不是一般意义上的代理。它的工作只不过是创建和管理iptables/IPVS规则。这个名字的原因是它曾经使用非服务空间模式代理。每个新服务对象的配置(包含其ClusterIP)和端点对象(包含健康Pod列表)被发送到每个节点上的kube-proxy进程。kube-proxy会创建iptables或者IPVS规则,告诉节点抓取去往ServiceClusterIP的流量,并根据Endpoints对象的内容转发给对应的Pod。也就是说,节点内核每次处理一个目的地为Service网络的数据包时,都会重写数据包的包头,将目的地IP改为ServiceEndpoints对象中健康Pod的IP。原来的iptables正在被IPVS取代(Kubernetes1.11进入稳定期)。长话短说,iptables是一个数据包过滤器,不是为负载平衡而设计的。IPVS是一个四层负载均衡器,它的性能和实现比iptables更适合这种使用场景。总结要消化的内容很多,我们简单回顾一下。当创建一个新的Service对象时,它会得到一个虚拟IP,称为ClusterIP。服务名及其ClusterIP会自动注册到集群DNS中,并创建相关的Endpoints对象保存符合标签条件的健康Pod列表,Service对象将流量转发给列表中的Pod。同时,集群中的所有节点都会配置相应的iptables/IPVS规则来监听去往ClusterIP的流量,并转发给真实的PodIP。这个过程如下图所示:一个Pod需要使用一个Service去连接其他的Pod。它首先向集群DNS发出查询,将Service名称解析为ClusterIP,然后将流量发送到Service网络上的ClusterIP。但是,没有到服务网络的路由,因此Pod将流量发送到其默认网关。这种行为会导致流量被转发到Pod所在节点的NIC,然后再转发到该节点的默认网关。在这个操作中,节点的内核修改数据包头中的目标IP,将其重定向到一个健康的Pod。