从零开始入门 | Kubernetes 中的服务发现与负载均衡

做者 | 阿里巴巴技术专家  溪恒前端

1、需求来源

为何须要服务发现

在 K8s 集群里面会经过 pod 去部署应用,与传统的应用部署不一样,传统应用部署在给定的机器上面去部署,咱们知道怎么去调用别的机器的 IP 地址。可是在 K8s 集群里面应用是经过 pod 去部署的, 而 pod 生命周期是短暂的。在 pod 的生命周期过程当中,好比它建立或销毁,它的 IP 地址都会发生变化,这样就不能使用传统的部署方式,不能指定 IP 去访问指定的应用。node

另外在 K8s 的应用部署里,以前虽然学习了 deployment 的应用部署模式,但仍是须要建立一个 pod 组,而后这些 pod 组须要提供一个统一的访问入口,以及怎么去控制流量负载均衡到这个组里面。好比说测试环境、预发环境和线上环境,其实在部署的过程当中须要保持一样的一个部署模板以及访问方式。由于这样就能够用同一套应用的模板在不一样的环境中直接发布。nginx

Service:Kubernetes 中的服务发现与负载均衡

最后应用服务须要暴露到外部去访问,须要提供给外部的用户去调用的。咱们上节了解到 pod 的网络跟机器不是同一个段的网络,那怎么让 pod 网络暴露到去给外部访问呢?这时就须要服务发现。vim

file

在 K8s 里面,服务发现与负载均衡就是 K8s Service。上图就是在 K8s 里 Service 的架构,K8s Service 向上提供了外部网络以及 pod 网络的访问,即外部网络能够经过 service 去访问,pod 网络也能够经过 K8s Service 去访问。后端

向下,K8s 对接了另一组 pod,便可以经过 K8s Service 的方式去负载均衡到一组 pod 上面去,这样至关于解决了前面所说的复发性问题,或者提供了统一的访问入口去作服务发现,而后又能够给外部网络访问,解决不一样的 pod 之间的访问,提供统一的访问地址。微信

2、用例解读

下面进行实际的一个用例解读,看 pod K8s 的 service 要怎么去声明、怎么去使用?网络

Service 语法

file

首先来看 K8s Service 的一个语法,上图实际就是 K8s 的一个声明结构。这个结构里有不少语法,跟以前所介绍的 K8s 的一些标准对象有不少类似之处。好比说标签 label 去作一些选择、selector 去作一些选择、label 去声明它的一些 label 标签等。架构

这里有一个新的知识点,就是定义了用于 K8s Service 服务发现的一个协议以及端口。继续来看这个模板,声明了一个名叫 my-service 的一个 K8s Service,它有一个 app:my-service 的 label,它选择了 app:MyApp 这样一个 label 的 pod 做为它的后端。app

最后是定义的服务发现的协议以及端口,这个示例中咱们定义的是 TCP 协议,端口是 80,目的端口是 9376,效果是访问到这个 service 80 端口会被路由到后端的 targetPort,就是只要访问到这个 service 80 端口的都会负载均衡到后端 app:MyApp 这种 label 的 pod 的 9376 端口。负载均衡

建立和查看 Service

如何去建立刚才声明的这个 service 对象,以及它建立以后是什么样的效果呢?经过简单的命令:

  • kubectl apply -f service.yaml

或者是

  • kubectl created -f service.yaml

上面的命令能够简单地去建立这样一个 service。建立好以后,能够经过:

  • kubectl discribe service

去查看 service 建立以后的一个结果。

file

service 建立好以后,你能够看到它的名字是 my-service。Namespace、Label、Selector 这些都跟咱们以前声明的同样,这里声明完以后会生成一个 IP 地址,这个 IP 地址就是 service 的 IP 地址,这个 IP 地址在集群里面能够被其它 pod 所访问,至关于经过这个 IP 地址提供了统一的一个 pod 的访问入口,以及服务发现。

这里还有一个 Endpoints 的属性,就是咱们经过 Endpoints 能够看到:经过前面所声明的 selector 去选择了哪些 pod?以及这些 pod 都是什么样一个状态?好比说经过 selector,咱们看到它选择了这些 pod 的一个 IP,以及这些 pod 所声明的 targetPort 的一个端口。

file

实际的架构如上图所示。在 service 建立以后,它会在集群里面建立一个虚拟的 IP 地址以及端口,在集群里,全部的 pod 和 node 均可以经过这样一个 IP 地址和端口去访问到这个 service。这个 service 会把它选择的 pod 及其 IP 地址都挂载到后端。这样经过 service 的 IP 地址访问时,就能够负载均衡到后端这些 pod 上面去。

当 pod 的生命周期有变化时,好比说其中一个 pod 销毁,service 就会自动从后端摘除这个 pod。这样实现了:就算 pod 的生命周期有变化,它访问的端点是不会发生变化的。

集群内访问 Service

在集群里面,其余 pod 要怎么访问到咱们所建立的这个 service 呢?有三种方式:

  • 首先咱们能够经过 service 的虚拟 IP 去访问,好比说刚建立的 my-service 这个服务,经过 kubectl get svc 或者 kubectl discribe service 均可以看到它的虚拟 IP 地址是 172.29.3.27,端口是 80,而后就能够经过这个虚拟 IP 及端口在 pod 里面直接访问到这个 service 的地址。

  • 第二种方式直接访问服务名,依靠 DNS 解析,就是同一个 namespace 里 pod 能够直接经过 service 的名字去访问到刚才所声明的这个 service。不一样的 namespace 里面,咱们能够经过 service 名字加“.”,而后加 service 所在的哪一个 namespace 去访问这个 service,例如咱们直接用 curl 去访问,就是 my-service:80 就能够访问到这个 service。

  • 第三种是经过环境变量访问,在同一个 namespace 里的 pod 启动时,K8s 会把 service 的一些 IP 地址、端口,以及一些简单的配置,经过环境变量的方式放到 K8s 的 pod 里面。在 K8s pod 的容器启动以后,经过读取系统的环境变量比读取到 namespace 里面其余 service 配置的一个地址,或者是它的端口号等等。好比在集群的某一个 pod 里面,能够直接经过 curl $ 取到一个环境变量的值,好比取到 MY_SERVICE_SERVICE_HOST 就是它的一个 IP 地址,MY_SERVICE 就是刚才咱们声明的 MY_SERVICE,SERVICE_PORT 就是它的端口号,这样也能够请求到集群里面的 MY_SERVICE 这个 service。

Headless Service

service 有一个特别的形态就是 Headless Service。service 建立的时候能够指定 clusterIP:None,告诉 K8s 说我不须要 clusterIP(就是刚才所说的集群里面的一个虚拟 IP),而后 K8s 就不会分配给这个 service 一个虚拟 IP 地址,它没有虚拟 IP 地址怎么作到负载均衡以及统一的访问入口呢?

它是这样来操做的:pod 能够直接经过 service_name 用 DNS 的方式解析到全部后端 pod 的 IP 地址,经过 DNS 的 A 记录的方式会解析到全部后端的 Pod 的地址,由客户端选择一个后端的 IP 地址,这个 A 记录会随着 pod 的生命周期变化,返回的 A 记录列表也发生变化,这样就要求客户端应用要从 A 记录把全部 DNS 返回到 A 记录的列表里面 IP 地址中,客户端本身去选择一个合适的地址去访问 pod。

file

能够从上图看一下跟刚才咱们声明的模板的区别,就是在中间加了一个 clusterIP:None,即代表不须要虚拟 IP。实际效果就是集群的 pod 访问 my-service 时,会直接解析到全部的 service 对应 pod 的 IP 地址,返回给 pod,而后 pod 里面本身去选择一个 IP 地址去直接访问。

向集群外暴露 Service

前面介绍的都是在集群里面 node 或者 pod 去访问 service,service 怎么去向外暴露呢?怎么把应用实际暴露给公网去访问呢?这里 service 也有两种类型去解决这个问题,一个是 NodePort,一个是 LoadBalancer。

  • NodePort 的方式就是在集群的 node 上面(即集群的节点的宿主机上面)去暴露节点上的一个端口,这样至关于在节点的一个端口上面访问到以后就会再去作一层转发,转发到虚拟的 IP 地址上面,就是刚刚宿主机上面 service 虚拟 IP 地址。

  • LoadBalancer 类型就是在 NodePort 上面又作了一层转换,刚才所说的 NodePort 实际上是集群里面每一个节点上面一个端口,LoadBalancer 是在全部的节点前又挂一个负载均衡。好比在阿里云上挂一个 SLB,这个负载均衡会提供一个统一的入口,并把全部它接触到的流量负载均衡到每个集群节点的 node pod 上面去。而后 node pod 再转化成 ClusterIP,去访问到实际的 pod 上面。

3、操做演示

下面进行实际操做演示,在阿里云的容器服务上面进去体验一下如何使用 K8s Service。

建立 Service

咱们已经建立好了一个阿里云的容器集群,而后而且配置好本地终端到阿里云容器集群的一个链接。

首先能够经过 kubectl get cs ,能够看到咱们已经正常链接到了阿里云容器服务的集群上面去。

file

今天将经过这些模板实际去体验阿里云服务上面去使用 K8s Service。有三个模板,首先是 client,就是用来模拟经过 service 去访问 K8s 的 service,而后负载均衡到咱们的 service 里面去声明的一组 pod 上。

file

K8s Service 的上面,跟刚才介绍同样,咱们建立了一个 K8s Service 模板,里面 pod,K8s Service 会经过前端指定的 80 端口负载均衡到后端 pod 的 80 端口上面,而后 selector 选择到 run:nginx 这样标签的一些 pod 去做为它的后端。

file

而后去建立带有这样标签的一组 pod,经过什么去建立 pod 呢?就是以前所介绍的 K8s deployment,经过 deployment 咱们能够轻松建立出一组 pod,而后上面声明 run:nginx 这样一个label,而且它有两个副本,会同时跑出来两个 pod。

file

先建立一组 pod,就是建立这个 K8s deployment,经过 kubectl create -f service.yaml。这个 deployment 也建立好了,再看一下 pod 有没有建立出来。以下图看到这个 deployment 所建立的两个 pod 都已经在 running 了。经过 kubectl get pod -o wide 能够看到 IP 地址。经过 -l,即 label 去作筛选,run=nginx。以下图所示能够看到,这两个 pod 分别是 10.0.0.135 和 10.0.0.12 这样一个 IP 地址,而且都是带 run=nginx 这个 label 的。

file

下面咱们去建立 K8s service,就是刚才介绍的经过 service 去选择这两个 pod。这个 service 已经建立好了。

file

根据刚才介绍,经过 kubectl describe svc 能够看到这个 service 实际的一个状态。以下图所示,刚才建立的 nginx service,它的选择器是 run=nginx,经过 run=nginx 这个选择器选择到后端的 pod 地址,就是刚才所看到那两个 pod 的地址:10.0.0.12 和 10.0.0.135。这里能够看到 K8s 为它生成了集群里面一个虚拟 IP 地址,经过这个虚拟 IP 地址,它就能够负载均衡到后面的两个 pod 上面去。

file

如今去建立一个客户端的 pod 实际去感觉一下如何去访问这个 K8s Service,咱们经过 client.yaml 去建立客户端的 pod,kubectl get pod 能够看到客户端 pod 已经建立好而且已经在运行中了。

file

经过 kubectl exec 到这个 pod 里面,进入这个 pod 去感觉一下刚才所说的三种访问方式,首先能够直接去访问这个 K8s 为它生成的这个 ClusterIP,就是虚拟 IP 地址,经过 curl 访问这个 IP 地址,这个 pod 里面没有装 curl。经过 wget 这个 IP 地址,输入进去测试一下。能够看到经过这个去访问到实际的 IP 地址是能够访问到后端的 nginx 上面的,这个虚拟是一个统一的入口。

file

第二种方式,能够经过直接 service 名字的方式去访问到这个 service。一样经过 wget,访问咱们刚才所建立的 service 名 nginx,能够发现跟刚才看到的结果是同样的。

file

在不一样的 namespace 时,也能够经过加上 namespace 的一个名字去访问到 service,好比这里的 namespace 为 default。

file

最后咱们介绍的访问方式里面还能够经过环境变量去访问,在这个 pod 里面直接经过执行 env 命令看一下它实际注入的环境变量的状况。看一下 nginx 的 service 的各类配置已经注册进来了。

file

能够经过 wget 一样去访问这样一个环境变量,而后能够访问到咱们的一个 service。

file

介绍完这三种访问方式,再看一下如何经过 service 外部的网络去访问。咱们 vim 直接修改一些刚才所建立的 service。

file

最后咱们添加一个 type,就是 LoadBalancer,就是咱们前面所介绍的外部访问的方式。

file

而后经过 kubectl apply,这样就把刚刚修改的内容直接生效在所建立的 service 里面。

file

如今看一下 service 会有哪些变化呢?经过 kubectl get svc -o wide,咱们发现刚刚建立的 nginx service 多了一个 EXTERNAL-IP,就是外部访问的一个 IP 地址,刚才咱们所访问的都是 CLUSTER-IP,就是在集群里面的一个虚拟 IP 地址。

file

而后如今实际去访问一下这个外部 IP 地址 39.98.21.187,感觉一下如何经过 service 去暴露咱们的应用服务,直接在终端里面点一下,这里能够看到咱们直接经过这个应用的外部访问端点,能够访问到这个 service,是否是很简单?

file

咱们最后再看一下用 service 去实现了 K8s 的服务发现,就是 service 的访问地址跟 pod 的生命周期没有关系。咱们先看一下如今的 service 后面选择的是这两个 pod IP 地址。

file

咱们如今把其中的一个 pod 删掉,经过 kubectl delete 的方式把前面一个 pod 删掉。

file

咱们知道 deployment 会让它自动生成一个新的 pod,如今看 IP 地址已经变成 137。

file

如今再去 describe 一下刚才的 service,以下图,看到前面访问端点就是集群的 IP 地址没有发生变化,对外的 LoadBalancer 的 IP 地址也没有发生变化。在全部不影响客户端的访问状况下,后端的一个 pod IP 已经自动放到了 service 后端里面。

file

这样就至关于在应用的组件调用的时候能够不用关心 pod 在生命周期的一个变化。

以上就是全部演示。

4、架构设计

最后是对 K8s 设计的一个简单的分析以及实现的一些原理。

Kubernetes 服务发现架构

file

如上图所示,K8s 服务发现以及 K8s Service 是这样总体的一个架构。

K8s 分为 master 节点和 worker 节点:

  • master 里面主要是 K8s 管控的内容;
  • worker 节点里面是实际跑用户应用的一个地方。

在 K8s master 节点里面有 APIServer,就是统一管理 K8s 全部对象的地方,全部的组件都会注册到 APIServer 上面去监听这个对象的变化,好比说咱们刚才的组件 pod 生命周期发生变化,这些事件。

这里面最关键的有三个组件:

  • 一个是 Cloud Controller Manager,负责去配置 LoadBalancer 的一个负载均衡器给外部去访问;
  • 另一个就是 Coredns,就是经过 Coredns 去观测 APIServer 里面的 service 后端 pod 的一个变化,去配置 service 的 DNS 解析,实现能够经过 service 的名字直接访问到 service 的虚拟 IP,或者是 Headless 类型的 Service 中的 IP 列表的解析;
  • 而后在每一个 node 里面会有 kube-proxy 这个组件,它经过监听 service 以及 pod 变化,而后实际去配置集群里面的 node pod 或者是虚拟 IP 地址的一个访问。

实际访问链路是什么样的呢?好比说从集群内部的一个 Client Pod3 去访问 Service,就相似于刚才所演示的一个效果。Client Pod3 首先经过 Coredns 这里去解析出 ServiceIP,Coredns 会返回给它 ServiceName 所对应的 service IP 是什么,这个 Client Pod3 就会拿这个 Service IP 去作请求,它的请求到宿主机的网络以后,就会被 kube-proxy 所配置的 iptables 或者 IPVS 去作一层拦截处理,以后去负载均衡到每个实际的后端 pod 上面去,这样就实现了一个负载均衡以及服务发现。

对于外部的流量,好比说刚才经过公网访问的一个请求。它是经过外部的一个负载均衡器 Cloud Controller Manager 去监听 service 的变化以后,去配置的一个负载均衡器,而后转发到节点上的一个 NodePort 上面去,NodePort 也会通过 kube-proxy 的一个配置的一个 iptables,把 NodePort 的流量转换成 ClusterIP,紧接着转换成后端的一个 pod 的 IP 地址,去作负载均衡以及服务发现。这就是整个 K8s 服务发现以及 K8s Service 总体的结构。

后续进阶

后续再进阶部分咱们还会更加深刻地去讲解 K8s Service 的实现原理,以及在 service 网络出问题以后,如何去诊断以及去修复的技巧。

本文总结

本文的主要内容就到此为止了,这里为你们简单总结一下:

  1. 为何云原生的场景须要服务发现和负载均衡,
  2. 在 Kubernetes 中如何使用 Kubernetes 的 Service 作服务发现和负载均衡
  3. Kubernetes 集群中 Service 涉及到的组件和大概实现原理

相信通过本文的学习与把握,你们可以经过 Kubernetes Service 将复杂的企业级应用快速并标准地编排起来。

“阿里巴巴云原生微信公众号(ID:Alicloudnative)关注微服务、Serverless、容器、Service Mesh等技术领域、聚焦云原生流行技术趋势、云原生大规模的落地实践,作最懂云原生开发者的技术公众号。”

相关文章
相关标签/搜索