基于 Kubernetes 1.22 版本
一、基础概念
Service 是 Kubemetes 最核心的概念,它定义了一组逻辑 Pod 和访问它们的策略(有时这种模式称为微服务)。即通过创建 Service ,可以为一组相同功能的 Pod 提供一个统一的入口地址,井且将请求负载均衡分发到后端的各个 Pod 上。Service 所管理的 Pod 集通常由 Label Selector 确定。
运行在每个 Node 上的 kube-proxy 进程其实就是一个智能的软件负载均衡器,它负责把对 Service 的请求转发到后端的某个 Pod 上,井在内部实现服务的负载均衡与会话保持机制。每个 Service 会分配到一个全局唯一的虚拟 IP 地址,这个虚拟 IP 被称为 Cluster IP,这个虚拟 IP 在 Service 的整个生命周期内,它是不会发生改变的。
要知道 Pod 的 Endpoint 地址随着其销毁和重建而发生改变,像使用 Deployment 来运行应用程序,它可以动态地创建和销毁 Pod。那么这样前端如何去找到后端呢,此时我们的 Servcie 就派上用场了,它通过 Label Selector 去对接后端 Pod,前端只需访问 Servcie 定义的服务入口地址即可。
官方文档:https://kubernetes.io/docs/concepts/services-networking/service/
二、代理模式
我们知道,Kubernetes 集群中的每个节点都运行一个 kube-proxy 进程,kube-proxy 负责为除 ExternalName 类型以外的 Service 实现虚拟 IP。
在 Kubernetes 中,kube-proxy 有三种代理模式,分别是 Userspace、Iptables 和 Ipvs。官方推荐使用 Ipvs,不过当集群不支持 Ipvs 的时候,集群会自动使用 Iptables。
1、Userspace
在这种模式下,kube-proxy 监控着 Service 和 Endpoint。对于每个 Service,它在本地节点上打开一个端口(随机选择),任何到此代理端口的连接都将代理到 Service 的一个后端 Pod。kube-proxy 在决定使用哪个后端 Pod 时会考虑 Service 的 SessionAffinity 设置。
最后,Userspace 代理模式会创建 iptables 规则,用于捕获到服务的 ClusterIP 和端口的流量,规则将该流量重定向到代理后端 Pod 的代理端口。
默认情况下,Userspace 代理模式下的 kube-proxy 通过轮询算法选择后端。
例如,当创建 Service 时,Kubernetes 会分配一个虚拟 IP 地址(ClusterIP),例如 10.0.0.1。假设 Service 端口为 1234,集群中的所有 kube-proxy 实例都会观察到该 Service,当它看到一个新 Service 时,它会打开一个新的随机端口,建立一个从虚拟 IP 地址到这个新端口的 iptables 重定向,并开始接受该端口上的连接。当客户端连接到 Service 的虚拟 IP 地址时,iptables 规则生效,并将数据包重定向到代理自己的端口。然还代理将选择后端,并开始将流量从客户端代理到后端。
2、Iptables
在这种模式下,kube-proxy 监控着 Service 和 Endpoint。对于每个 Service,它都创建 iptables 规则,这些规则捕获到 Service 的 ClusterIP 和端口的流量,并将该流量重定向到 Service 的一个后端集。对于每个端点对象,它创建 iptables 规则,这些规则会选择一个后端 Pod。
默认情况下,iptables 代理模式下的 kube-proxy 随机选择后端。
使用 iptables 处理流量具有较低的系统开销,因为流量由 Linux netfilter 处理,无需在用户空间和内核空间之间切换,这种方法也更加可靠。
如果 kube-proxy 在 iptables 代理模式下运行,并且选择的第一个 Pod 没有响应,则连接失败。这与 Userspace 代理模式不同的是:在这种情况下,kube-proxy 将检测到与第一个 Pod 的连接失败,并将自动使用不同的后端 Pod 重试。
我们也可以使用 Pod 就绪探测来验证后端 Pod 是否正常工作,这样在 iptables 代理模式下的 kube-proxy 只会看到测试正常的后端。这样做意味着可以避免通过 kube-proxy 将流量发送到已知失败的 Pod。
3、Ipvs
在这种模式下,kube-proxy 监控着 Service 和 Endpoint,调用 netlink 接口创建相应的 ipvs 规则,并定期将 ipvs 规则与 Service 和 Endpoint 同步。该控制循环可确保 ipvs 状态与所需状态匹配。访问 Service 时,ipvs 将流量定向到后端 Pod 之一。
Ipvs 代理模式基于 netfilter hook 函数,该函数类似于 iptables 模式,但使用哈希表作为底层数据结构,并在内核空间中工作。这意味着 ipvs 模式下的 kube-proxy 重定向流量的延迟比iptables 模式下的kube-proxy 低,在同步代理规则时具有更好的性能。与其他代理模式相比,ipvs 模式还支持更高的网络流量吞吐量。像 iptables 在大规模集群(例如10000个服务)中的运行速度显著降低。
Ipvs 为平衡后端 POD 的流量提供了更多选项,几种调度算法如下:
• rr:轮询,即依次循环分配流量。
• lc:最少打开连接数,即优先分配流量给打开连接数最少的后端。
• dh:目标哈希,即流量按照目标 IP 的 hash 结果分配。
• sh:源哈希,即流量按照源 IP 的 hash 结果分配,这样会使得来自同一 IP 的客户端固定访问一个后端。
• sed:最短的预期延迟,即响应时间越短的后端优先分配流量
• nq:从不排队,即当后端有空闲时,先调度到空闲的上,否则依据 sed 算法调度。
要在 IPVS 模式下运行 kube-proxy,必须在启动 kube-proxy 之前保证节点上的 IPVS 可用。当 kube-proxy 在 IPVS 代理模式下启动时,它会验证 IPVS 内核模块是否可用。如果未检测到 IPVS 内核模块,则 kube-proxy 将回退到以 iptables 代理模式运行。
以上不论哪种模式,kube-proxy 都监控着 kube-apiserver 写入 etcd 中关于 Pod 的最新状态信息,它一旦检查到那个 Pod 资源添加或者删除,它会立即将这些变化同步到 iptables 或 ipvs 规则中,以便 iptables 和 ipvs 在调度客户端请求到后端 Pod 时,不会出现 Pod 不存在的情况。
三、定义详解
Service 定义官方文档:https://kubernetes.io/docs/reference/kubernetes-api/service-resources/
yaml 格式的 Service 定义文件常用内容如下:
apiVersion: v1 # 必选,API版本号
kind: Service # 必选,类型为Service
metadata: # 必选,元数据
name: string # 必选,Service名称
namespace: string # 命名空间,默认为default
labels: # 标签列表
name: string
annotations: # 注释列表
name: string
spec: # 必选,定义Service的行为
selector: # 标签选择器,选择具有指定标签的Pod进行管理
name: string
type: string # Service的类型,用于指定Service的访问方式,默认为CusterIP
ports: # 必选,定义Servcie端口相关信息
- name: string # 端口名称
port: int # 必选,端口,Service将公开的端口
targetPort: int # 目标端口,目标Pod的容器暴露端口,默认和port相同。
protocol: string # 端口协议,支持TCP、UDP和SCTP,默认为TCP
nodePort: int # 当type为NodePort或LoadBalancer时,Servic在宿主机节点上公开的端口,不指定随机分配
clusterIP: string # Service的IP地址,默认随机分配
sessionAffinity: string # Session亲和,即基于客户端IP地址进行会话保持的模式,可选值为ClientIP和None(默认)
# ClientIP表示将同一个客户端(IP)的访问请求都转发到同一个后端Pod上
Service 的类型(spec.type):
• ClusterIP:在集群内部 IP 上公开此 Service,此类型服务只能从集群内部访问,这是默认的 Service 类型。
• NodePort:在宿主机节点 IP 的端口上公开此 Service,此类型将能够通过 NodePort 从集群外部访问服 Service。
• LoadBalancer:使用外部负载均衡器对外公开 Service,用于公有云环境。
• ExternalName:通过返回 CNAME 记录及其值,将 Service 映射到 externalName 字段的内容。
除了文件定义,我们还可以使用命令快速创建 Service:
[root@master ~]# kubectl expose deployment nginx-deployment --port=80 --type=NodePort
service/nginx-deployment exposed
[root@master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-deployment NodePort 10.1.82.56 <none> 80:32080/TCP 11s
[root@master ~]# curl -s localhost:32080 | head -n4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
四、基本使用
1、ClusterIP
下面是一个简单的示例,创建了一个 Deployment,并定义了一个名为 "nginx-service" 的 Service ,它的服务端口为80 ,将对接拥有 "app: nginx" 这个 label 的所有 Pod。
[root@master ~]# vim nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
labels:
app: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.18.0
ports:
- containerPort: 80
[root@master ~]# vim nginx-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service
spec:
selector:
app: nginx
ports:
- port: 80
targetPort: 80
protocol: TCP
我们来简单解读 Service 的定义文件 nginx-service.yaml:spec.selector 定义选择具有指定标签的 Pod 进行管理;在 spec.ports 的定义中, port 定义了 Service 的端口,targetPort 则定义了该服务的容器所暴露(EXPOSE)的端口,如果没有指定 targetPort,则默认 targetPort 和 port 相同。
[root@master ~]# kubectl apply -f nginx-deployment.yaml,nginx-service.yaml
deployment.apps/nginx-deployment created
service/nginx-service created
[root@master ~]# kubectl get endpoints
NAME ENDPOINTS AGE
kubernetes 10.0.0.100:6443 23d
nginx-service 10.244.2.120:80,10.244.2.121:80,10.244.2.122:80 12s
[root@master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubernetes ClusterIP 10.1.0.1 <none> 443/TCP 23d
nginx-service ClusterIP 10.1.10.223 <none> 80/TCP 18s
[root@master ~]# curl -s 10.1.10.223 | head -n4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
2、NodePort
现在将 nginx-service 改为 NodePort 模式,以使得能从集群外部访问:
# 将spec.type改为NodePort
[root@master ~]# kubectl edit service nginx-service
service/nginx-service edited
[root@master ~]# kubectl get svc nginx-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service NodePort 10.1.10.223 <none> 80:30577/TCP 68m
[root@master ~]# curl -s localhost:30577 | head -n4
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
3、多端口
对于某些服务,是需要公开多个端口。Kubernetes 允许给 Service 定义多个端口。为一个 Service 使用多个端口时,必须配置所有端口的名称,例如:
[root@master ~]# cat nginx-service-port.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-service-port
spec:
selector:
app: nginx
ports:
- name: http
protocol: TCP
port: 80
- name: https
protocol: TCP
port: 443
[root@master ~]# kubectl apply -f nginx-service-port.yaml
service/nginx-service-port created
[root@master ~]# kubectl get svc nginx-service-port
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service-port ClusterIP 10.1.92.51 <none> 80/TCP,443/TCP 6s
五、外部服务
Service 最常见的用法是抽象对 Pod 的访问,但它们也可以抽象其它类型的后端,即定义一个没有选择器的服务。
例如以下场景:
• 将外部数据库作为后端服务进行连接。
• 将另一个集群或 Namespace 中的服务作为服务的后端。
例如下例,10.0.0.100 上有一个 web 服务,我们创建一个Service指向它。
[root@master ~]# curl -s 10.0.0.100 | head -n4
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Welcome to CentOS</title>
[root@master ~]# vim ext-service.yaml
apiVersion: v1
kind: Service
metadata:
name: ext-service
spec:
ports:
- port: 80
protocol: TCP
targetPort: 80
[root@master ~]# kubectl apply -f ext-service.yaml
service/ext-service created
[root@master ~]# kubectl get svc ext-service
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
ext-service ClusterIP 10.1.179.200 <none> 80/TCP 8s
[root@master ~]# kubectl describe svc ext-service | grep -i endpoint
Endpoints: <none>
由于此 Service 没有选择器,无法选择后端 Pod,因此是不会自动创建相应的 Endpoint 记录的。因此需要手动创建一个和该 Service 同名的 Endpoint ,用于指向实际的后端访问地址。
[root@master ~]# vim ext-service-endpoint.yaml
apiVersion: v1
kind: Endpoints
metadata:
name: ext-service
subsets:
- addresses:
- ip: 10.0.0.100
ports:
- port: 80
[root@master ~]# kubectl apply -f ext-service-endpoint.yaml
endpoints/ext-service created
[root@master ~]# kubectl get endpoints ext-service
NAME ENDPOINTS AGE
ext-service 10.0.0.100:80 34s
[root@master ~]# kubectl describe svc ext-service | grep -i endpoint
Endpoints: 10.0.0.100:80
[root@master ~]# curl -s 10.1.179.200 | head -n4
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<title>Welcome to CentOS</title>
注:端点地址不能是环回(127.0.0.0/8)、私有保留地址(169.254.0.0/16 )、组播地址(224.0.0.0/24)和其它 Kubernetes 服务的集群IP。
六、Headless Service
Headless Service(无头服务),即不为 Service 设置 ClusterIP 入口地址 ,仅通 Label Selector 将后端的 Pod 列表返回给调用的客户端。对于 headless Services,kube-proxy 是不处理这些服务,也不会为它们做负载均衡或代理。
例如以下场景:
• 不使用 Service 提供的默认负载均衡的功能,想自己控制负载均衡策略
• 不通过 Service 转发,想直接访问 Pod 等场景。
• 配合其它服务发现,而不受 Kubernetes 中实现的束缚。
对于 Headless Services,DNS 的自动配置方式取决于 Service 是否定义了选择器:
• 有选择器:对于定义选择器的 Headless Service,端点控制器在 API 中创建 Endpoints 记录,并修改 DNS 配置以返回直接指向支持 Service 的 POD 的记录(IP地址)。
• 无选择器:对于未定义选择器的 Headless Service,端点控制器不会创建 Endpoints 记录。但是,DNS 系统会查找并配置 ExternalName 类型服务的 CNAME 记录和A记录与服务共享名称的所有其他类型的端点。
例如,下面示例:
[root@master ~]# vim nginx-svc-headless.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-svc-headless
labels:
app: nginx
spec:
selector:
app: nginx
ports:
- port: 80
clusterIP: None
[root@master ~]# kubectl apply -f nginx-svc-headless.yaml
service/nginx-svc-headless created
[root@master ~]# kubectl get svc nginx-svc-headless
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-svc-headless ClusterIP None <none> 80/TCP 16s
[root@master ~]# kubectl describe service nginx-svc-headless | grep Endpoints
Endpoints: 10.244.2.120:80,10.244.2.121:80,10.244.2.122:80
# Service DNS 命名一般是为:<服务名>.<命名空间>.svc.cluster.local
# 下面通过 DNS 查询一下
[root@master ~]# kubectl run test -it --rm --image=busybox
If you don't see a command prompt, try pressing enter.
/ # nslookup nginx-svc-haeadless.default.svc.cluster.local
Server: 10.1.0.10
Address: 10.1.0.10:53
Name: nginx-svc-haeadless.default.svc.cluster.local
Address: 10.244.2.121
Name: nginx-svc-haeadless.default.svc.cluster.local
Address: 10.244.2.122
Name: nginx-svc-haeadless.default.svc.cluster.local
Address: 10.244.2.120
这样 Service 就不再具有一个特定 ClusterIP,当客户端对其进行访问将获得包含 Label "app=nginx" 的全部 Pod 。
像 StatefulSet 就是使用 Headless Service 来指定具体的 pod。
参考文章:
https://kubernetes.io/docs/
《Kubernetes 权威指南》