本文由 CNCF + Alibaba 云原生技术公开课 整理而来
Service:服务发现与负载均衡
- 为什么需要服务发现?
在 Kubernetes 集群里面会通过 Pod
去部署应用,与传统的应用部署不同,传统应用部署在给定的机器上面去部署,去调用别的机器的 IP 地址非常简单。
在 Kubernetes 集群里面应用是通过 Pod
去部署的, 而 Pod
的生命周期是短暂的。在 Pod 的生命周期过程中,比如它创建或销毁,它的 IP 地址都会发生变化,这样就不能使用传统的部署方式,不能指定 Pod IP
去访问指定的应用。
另外在 Kubernetes 中,虽然有 Deployment
的应用部署模式,但还是需要创建一个 Pod
组,然后这些 Pod
组需要提供一个统一的访问入口,以及怎么去控制流量负载均衡到这个组里面。比如说测试环境、预发布环境和线上环境,其实在部署的过程中需要保持同样的一个部署模板以及访问方式。因为这样就可以用同一套应用的模板在不同的环境中直接发布。
最后应用服务需要暴露到外部去访问,需要提供给外部的用户去调用或访问的。那怎么让 Pod
网络暴露到去给外部访问呢?此时就需要服务发现。
Service
-服务发现与负载均衡 :
在 Kubernetes 里面,应用程序的服务发现与负载均衡就是通过 Service
来完成的。向上,Service
提供了外部网络以及 Pod
网络的访问,即外部网络可以通过 Service
去访问,Pod
网络也可以通过 Service
去访问。
向下,Kubernetes 对接了另外一组 Pod
,即可以通过 Service
的方式去负载均衡到一组 Pod
上面去,这样相当于解决了前面所说的问题。而且提供了统一的访问入口去做服务发现,然后又可以给外部网络访问,解决不同的 Pod 之间的访问,提供统一的访问地址。
Service 用法
下面简单介绍 Service
的用法。
Service
语法:
my-service Service
yaml 文件示例:
apiVersion: v1
kind: Service
metadata:
name: my-service
labels:
app: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
文件解析:
apiVersion: v1 表示 Service 的 API 版本是 v1
kind 表示 Kubernetes 资源类型是 Service
metadata 表示 Service 的元数据,元数据通常包含 name、namespace、labels、annotations 等
spec 表示 Service 的期望状态,selector 表示 Pod 选择器,选择带相同 label 的 Pod;
ports 表示 Service 的协议和端口,并指定 Pod 的端口
- 创建和查看
Service
:
kubectl apply -f <yaml-file> #创建
kubectl describe service <service-name> #查看
在 kubectl describe service
时,能够看到一个 Endpoints
属性,该属性的值就是通过 selector
匹配到的后端 Pod 的 IP 和 targetPort
组成的一组地址的列表;而 IP
属性的值就是 Kubernetes 自动为 Service
分配的 ClusterIP
,ClusterIP
是虚拟 IP,在集群内唯一。
在集群里,所有的 Pod 和 Node 都可以通过 ClusterIP
和 yaml 中指定的 port
去访问到这个 Service
。Service
会把它选择的 Pod
及其 IP:Port 都挂载到后端,这样一来,当访问 Service
的 ClusterIP
时,就可以负载均衡到后端的这些 Pod
上面去。
当 Pod
的生命周期有变化时,比如说其中一个 Pod
销毁,Service
就会自动从后端移除这个 Pod
。即使 Pod
的生命周期有变化,也和 Service
无关,用户访问的地址并不需要改变。
- 集群内访问
Service
:
在 Kubernetes 集群里面,其他 Pod
要怎么访问到 Service
呢?有 3 种方式:
1. 可以通过 Service 的 ClusterIP 去访问
2. 可以直接访问服务名,依靠 DNS 解析。同一个 Namespace 里 Pod 可以直接通过 Service 的名字去访问到 Service;
不同的 Namespace 里面,可以通过 <service-name>.<namespace-name> 去访问 Service
3. 通过环境变量访问,在同一个 Namespace 里的 Pod 启动时,Kubernetes 会把 Service 的一些简单的配置(如 IP 地址、端口等)通过环境变量的方式放到 Pod 里面
Headless Service
:
Headless Service
就是没有 ClusterIP
的 Service
。Service
在创建时可以指定 clusterIP: None
,这样 Kubernetes 就不会分配 ClusterIP
给 Service
。Headless Service
没有 ClusterIP
怎么做到负载均衡以及统一的访问入口呢?
Pod
可以直接通过 <service-name>
以 DNS A 记录的方式解析到所有后端 Pod
的 IP 地址,由客户端选择一个后端的 IP 地址,这个 A 记录会随着 Pod
的生命周期变化,返回的 A 记录列表也发生变化,这样就要求客户端应用要从 A 记录把所有 DNS 返回到 A 记录的列表里面 IP 地址中,客户端自己去选择一个合适的地址去访问 Pod
。
- 向集群外暴露
Service
:
怎么把集群的应用暴露给外部去访问呢?Kubernetes 中提供了 3 种方式:
NodePort,将 ClusterIP 映射到集群的 Node 上的一个端口,这样就可以直接访问 Node 的这个端口访问到 ClusterIP
LoadBalancer,在 NodePort 上面做一层转换,NodePort 其实是集群里面每个 Node 上的同一端口,
LoadBalancer 是在所有的 Node 前挂上一个负载均衡,这个负载均衡会提供一个统一的入口
Ingress,在集群中创建 Ingress 对象,用户访问 Ingress 对象中指定的域名 和 url,然后 Ingress 会将所有流量转发到对应 Service 的 ClusterIP
架构设计
- Kubernetes 服务发现架构:
Kubernetes 分为 Master
节点和 Node
节点:
Master 负责维护集群的目标状态
Node 负责运行实际的业务负载
Master
节点控制所有 Node
节点。Master
节点上的 APIServer
统一管理 Kubernetes 所有对象,所有的组件都会注册到 APIServer
上面去监听这个对象的变化,例如 Pod
生命周期发生变化等事件。
这里面最关键的有三个组件:
Cloud Controller Manager,负责去配置 LoadBalancer 给外部去访问
Coredns,通过 Coredns 去观测 APIServer 里面的 Service 后端 Pod 的变化,去配置 Service 的 DNS 解析,实现可以通过 Service 的名字直接访问到 Service 的 ClusterIP,或者是 Headless Service 中的 IP 列表的解析
kube-proxy,位于 Node 节点上,它通过监听 Service 以及 Pod 变化,然后实际去配置集群里面的 Pod 或者 ClusterIP 的访问
实际的访问链路是什么样的呢?从集群内部的一个 Pod
去访问 Service
,Pod
首先通过 Coredns
这里去解析出 ClusterIP
,Pod
会拿这个 ClusterIP
去做请求,它的请求到 Node
的网络之后,就会被 kube-proxy
所配置的 iptables 或者 IPVS 去做一层拦截处理,之后去负载均衡到每一个实际的后端 Pod
上面去,这样就实现了一个负载均衡以及服务发现。
对于外部的流量,通过公网访问的一个请求,它是通过外部的一个负载均衡器 Cloud Controller Manager
去监听 Service
的变化之后,去配置的一个负载均衡器,然后转发到节点上的一个 NodePort
上面去,NodePort
也会经过 kube-proxy
配置的一个 iptables,把 NodePort
的流量转换成 ClusterIP
,紧接着转换成后端的一个 Pod
的 IP 地址,去做负载均衡以及服务发现。
这就是整个 Kubernetes 服务发现以及 Kubernetes Service
整体的结构。