5.3 将服务暴露给外部客户端
到目前为止,只讨论了集群内服务如何被pod使用;但是,还需要向外部公开某些服务。例如前端web服务器,以便外部客户端可以访问它们.
有几种方式可以在外部访问服务:
将服务的类型设置成 NodePort——每个集群节点都会在节点上打开一个端口,对于NodePort服务,每个集群节点在节点本身(因此得名叫NodePort)上打开一个端口,并将在该端口上接收到的流量重定向到基础服务。该服务仅在内部集群IP和端口上才可访问,但也可通过所有节点上的专用端口访问。
将服务的类型设置成 LoadBalance,NodePort类型的一种扩展——这使得服务可以通过一个专用的负载均衡器来访问,这是由Kubernetes中正在运行的云基础设施提供的。负载均衡器将流量重定向到跨所有节点的NodePort。客户端通过负载均衡器的IP连接到服务。
创建一个Ingress资源,这是一个完全不同的机制,通过一个IP地址公开多个服务——它运行在HTTP层(网络协议第7层)上,因此可以提供比工作在第4层的服务更多的功能。我们将在5.4节介绍Ingress资源。
5.3.1 使用NodePort类型的服务
将一组pod公开给外部客户端的第一种方法是创建一个服务并将其类型设置为NodePort。通过创建NodePort服务,可以让Kubernetes在其所有节点上保留一个端口(所有节点上都使用相同的端口号),并将传入的连接转发给作为服务部分的pod。
这与常规服务类似(它们的实际类型是ClusterIP),但是不仅可以通过服务的内部集群IP访问NodePort 服务,还可以通过任何节点的IP和预留端口访问 NodePort Service。
当尝试与NodePort服务交互时,意义更加重大。
创建NodePort类型的服务
现在将创建一个NodePort服务,以查看如何使用它。下面的代码清单显示了服务的YAML。
代码清单5.11 NodePort服务定义:kubia-svc-nodeport.yaml
apiVersion: v1
kind: Service
metadata:
name: kubia-nodeport
spec:
type: NodePort # 服务类型
ports:
- port: 80 # 服务集群IP的端口号
targetPort: 8080 # 背后pod的目标端口号
nodePort: 30123 # 通过集群节点的30123端口访问该服务
selector:
app: kubia
将类型设置为 NodePort
并指定该服务应该绑定到的所有集群节点的 NodePort Service。指定端口不是强制性的。如果忽略它,Kubernetes将选择一个随机端口。
注意 当在GKE中创建服务时,kubectl打印出一个关于必须配置防火墙规则的警告。接下来的章节将讲述如何处理。
查看NodePort类型的服务
查看该服务的基础信息:
$ kubectl get svc kubia-nodeport
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kubia-nodeport NodePort 10.102.31.94 <none> 80:30123/TCP 6m41s
看看EXTERNAL-IP列。它显示nodes,表明服务可通过任何集群节点的IP地址访问。PORT(S)列显示集群 IP(80
)的内部端口和 NodePort(30123
),可以通过以下地址访问该服务:
-
10.11.254.223:80
(集群内部) -
<节点1的IP>:30123
(集群外部,minikube单节点 使用minikube service kubia-nodeport -n <namespace>
获取) -
<节点2的IP>:30123
,等等
图5.6显示了服务暴露在两个集群节点的端口30123上(这适用于在GKE上运行的情况;Minikube只有一个节点,但原理相同)。到达任何一个端口的传入连接将被重定向到一个随机选择的pod,该pod是否位于接收到连接的节点上是不确定的。
外部客户端通过节点1或者节点2连接到NodePort服务,在第一个节点的端口30123
收到的连接,可以被重定向到第一节点个上运行的pod,也可能是第二个节点上运行的pod。
更改防火墙规则,让外部客户端访问我们的NodePort服务
使用JSONPath获取所有节点的IP
要了解有关kubectl使用JSONPath的更多信息,请参阅
可以在节点的JSON或YAML描述符中找到IP。但并不是在很大的JSON中筛选,而是可以利用kubectl只打印出节点IP而不是整个服务的定义。
$ kubectl get nodes -o=jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.status.addresses[0].address}{"\n"}{end}'
minikube 172.17.0.2
通过指定kubectl的JSONPath,使得其只输出需要的信息。你可能已经熟悉XPath,并且知道如何使用XML,JSONPath基本上是JSON的XPath。上例中的JSONPath指示kubectl执行以下操作:
浏览item属性中的所有元素。
对于每个元素,输出status属性。
一旦知道了节点的IP,就可以尝试通过以下方式访问服务:
$ curl http://172.17.0.2:30123
提示 使用Minikube时,可以运行 minikube sevrvice <service-name>[-n<namespace>]
命令,通过浏览器轻松访问NodePort服务。
正如所看到的,现在整个互联网可以通过任何节点上的30123端口访问到你的pod。客户端发送请求的节点并不重要。但是,如果只将客户端指向第一个节点,那么当该节点发生故障时,客户端无法再访问该服务。这就是为什么将负载均衡器放在节点前面以确保发送的请求传播到所有健康节点,并且从不将它们发送到当时处于脱机状态的节点的原因。
如果Kubernetes集群支持它(当Kubernetes部署在云基础设施上时,大多数情况都是如此),那么可以通过创建一个Load Badancer而不是NodePort服务自动生成负载均衡器。接下来介绍此部分。
5.3.2 通过负载均衡器将服务暴露出来
在云提供商上运行的Kubernetes集群通常支持从云基础架构自动提供负载平衡器。所有需要做的就是设置服务的类型为Load Badancer而不是NodePort。负载均衡器拥有自己独一无二的可公开访问的IP地址,并将所有连接重定向到服务。可以通过负载均衡器的IP地址访问服务。
如果Kubernetes在不支持Load Badancer服务的环境中运行,则不会调配负载平衡器,但该服务仍将表现得像一个NodePort服务。这是因为Load Badancer服务是NodePort服务的扩展。
创建LoadBalance服务
要使用服务前面的负载均衡器,请按照以下YAML manifest创建服务,代码清单如下所示。
代码清单5.12 Load Badancer类型的服务:kubia-svc-loadbalancer.yaml
apiVersion: v1
kind: Service
metadata:
name: kubia-loadbalancer
spec:
type: LoadBalancer #该服务从k8s的基础架构获取负载平衡器
ports:
- port: 80
targetPort: 8080
selector:
app: kubia
服务类型设置为LoadBalancer而不是NodePort。如果没有指定特定的 NodePort Service,Kubernetes将会选择一个端口。
通过负载均衡器连接服务
创建服务后,云基础架构需要一段时间才能创建负载均衡器并将其IP地址写入服务对象。一旦这样做了,IP地址将被列为服务的外部IP地址:
$ kubectl get svc kubia-loadbalancer
在这种情况下,负载均衡器的IP地址为130.211.53.173,因此现在可以通过该IP地址访问该服务:
$ curl http://130.211.53.173
成功了!可能像你已经注意到的那样,这次不需要像以前使用NodePort服务那样来关闭防火墙。
minikube获取服务地址
minikube service kubia-loadbalancer -n <namespace> --url
会话亲和性和Web浏览器
由于服务现在已暴露在外,因此可以尝试使用网络浏览器访问它。但是会看到一些可能觉得奇怪的东西——每次浏览器都会碰到同一个pod。此时服务的会话亲和性是否发生变化?使用kubectl explain,可以再次检查服务的会话亲缘性是否仍然设置为None,那么为什么不同的浏览器请求不会碰到不同的pod,就像使用curl时那样?
现在阐述为什么会这样。浏览器使用keep-alive连接,并通过单个连接发送所有请求,而curl每次都会打开一个新连接。服务在连接级别工作,所以当首次打开与服务的连接时,会选择一个随机集群,然后将属于该连接的所有网络数据包全部发送到单个集群。即使会话亲和性设置为None,用户也会始终使用相同的pod(直到连接关闭)。
请参阅图5.7,了解HTTP请求如何传递到该pod。外部客户端(可以使用curl)连接到负载均衡器的80端口,并路由到其中一个节点上的隐式分配节点端口。之后该连接被转发到一个pod实例。
如前所述,LoadBalancer类型的服务是一个具有额外的基础设施提供的负载平衡器NodePort服务。如果使用kubectl describe来显示有关该服务的其他信息,则会看到为该服务选择了一个 NodePort。如果要为此端口打开防火墙,就像在上一节中对NodePort服务所做的那样,也可以通过节点IP访问服务。
提示 如果使用的是Minikube,尽管负载平衡器不会被分配,仍然可以通过 NodePort(位于Minikube VM的IP地址)访问该服务。
5.3.3 了解外部连接的特性
你必须了解与服务的外部发起的连接有关的几件事情。
了解并防止不必要的网络跳数
当外部客户端通过 NodePort 连接到服务时(这也包括先通过负载均衡器时的情况),随机选择的pod并不一定在接收连接的同一节点上运行。可能需要额外的网络跳转才能到达pod,但这种行为并不符合期望。
可以通过将服务配置为仅将外部通信重定向到接收连接的节点上运行的pod来阻止此额外跳数。这是通过在服务的spec部分中设置 externalTrafficPolicy
字段来完成的:
spec:
externalTrafficPolicy: Local
...
如果服务定义包含此设置,并且通过服务的 NodePort 打开外部连接,则服务代理将选择本地运行的pod。如果没有本地pod存在,则连接将挂起(它不会像不使用注解那样,将其转发到随机的全局pod)。因此,需要确保负载平衡器将连接转发给至少具有一个pod的节点。
使用这个注解还有其他缺点。通常情况下,连接均匀分布在所有的pod上,但使用此注解时,情况就不再一样了。
想象一下两个节点有三个pod。假设节点A运行一个pod,节点B运行另外两个pod。如果负载平衡器在两个节点间均匀分布连接,则节点A上的pod将接收所有连接的50%,但节点B上的两个pod每个只能接收25%, 使用local外部流量策略的服务可能会导致跨pod的负载分布不均衡
记住客户端IP是不记录的
通常,当集群内的客户端连接到服务时,支持服务的pod可以获取客户端的IP地址。但是,当通过 NodePort接收到连接时,由于对数据包执行了源网络地址转换(SNAT),因此数据包的源IP将发生更改。
后端的pod无法看到实际的客户端IP,这对于某些需要了解客户端IP的应用程序来说可能是个问题。例如,对于Web服务器,这意味着访问日志无法显示浏览器的IP。
上一节中描述的local外部流量策略会影响客户端IP的保留,因为在接收连接的节点和托管目标pod的节点之间没有额外的跳跃(不执行SNAT)。
网友评论