序言
在Kubernetes应用编排的动态、弹性管理模型下,Service资源用于为Pod对象提供一个固定、统一的访问接口及负载均衡能力,并支持新一代DNS系统的服务发现功能,解决了客户端发现并访问容器化应用的难题。
Service对象的IP地址都仅在Kubernetes集群内可达,它们无法接入集群外部的访问流量。在解决此类问题时,除了可以在单一节点上做端口(hostPort)暴露及让Pod资源共享使用工作节点的网络名称空间(hostNetwork)之外,更推荐用户使用NodePort或LoadBalancer类型的Service资源,或者是有七层负载均衡能力的Ingress资源。
本质上来讲,一个Service对象对应于工作节点内核之中的一组iptables或/和ipvs规则,这些规则能够将到达Service对象的ClusterIP的流量调度转发至相应Endpoint对象指向的IP地址和端口之上。内核中的iptables或ipvs规则的作用域仅为其所在工作节点的一个主机,因而生效于集群范围内的Service对象就需要在每个工作节点上都生成相关规则,从而确保任一节点上发往该Service对象请求的流量都能被正确转发。
kube-proxy代理模型
Service对象的ClusterIP事实上是用于生成iptables或ipvs规则时使用的IP地址,它仅用于实现Kubernetes集群网络内部通信,且仅能够以规则中定义的转发服务的请求作为目标地址予以响应,这也是它之所以被称作虚拟IP的原因之一(iptables方式下clusterIP无法ping通,但是ipvs方式clusterIP可以ping通)。kube-proxy把请求代理至相应端点的方式有3种:userspace、iptables和ipvs
userspace代理模型
此处的userspace是指Linux操作系统的用户空间。在这种模型中,kube-proxy负责跟踪API Server上Service和Endpoints对象的变动(创建或移除),并据此调整Service资源的定义。对于每个Service对象,它会随机打开一个本地端口(运行于用户空间的kubeproxy进程负责监听),任何到达此代理端口的连接请求都将被代理至当前Service资源后端的各Pod对象,至于哪个Pod对象会被选中则取决于当前Service资源的调度方式,默认调度算法是轮询(round-robin),另外,此类Service对象还会创建iptables规则以捕获任何到达ClusterIP和端口的流量。在Kubernetes 1.1版本之前,userspace是默认的代理模型。
在这种代理模型中,请求流量到达内核空间后经由套接字送往用户空间中的kube-proxy进程,而后由该进程送回内核空间,发往调度分配的目标后端Pod对象。因请求报文在内核空间和用户空间来回转发,所以必然导致模型效率不高,因此现在已经被弃用。
iptables代理模型
创建Service对象的操作会触发集群中的每个kube-proxy并将其转换为定义在所属节点上的iptables规则,用于转发工作接口接收到的、与此Service资源ClusterIP和端口相关的流量。客户端发来请求将直接由相关的iptables规则进行目标地址转换(DNAT)后根据算法调度并转发至集群内的Pod对象之上,而无须再经由kube-proxy进程进行处理,因而称为iptables代理模型。对于每个Endpoints对象,Service资源会为其创建iptables规则并指向其iptables地址和端口,而流量转发到多个Endpoint对象之上的默认调度机制是随机算法。iptables代理模型由Kubernetes v1.1版本引入,并于v1.2版本成为默认的类型。
在iptables代理模型中,Service的服务发现和负载均衡功能都使用iptables规则实现,而无须将流量在用户空间和内核空间来回切换,因此更为高效和可靠,但是性能一般,而且受规模影响较大,仅适用于少量Service规模的集群。
ipvs代理模型
Kubernetes自v1.9版本起引入ipvs代理模型,且自v1.11版本起成为默认设置。在此种模型中,kube-proxy跟踪API Server上Service和Endpoints对象的变动,并据此来调用netlink接口创建或变更ipvs(NAT)规则。它与iptables规则的不同之处仅在于客户端请求流量的调度功能由ipvs实现,余下的其他功能仍由iptables完成。
ipvs代理模型中Service的服务发现和负载均衡功能均基于内核中的ipvs规则实现。类似于iptables,ipvs也构建于内核中的netfilter之上,但它使用hash表作为底层数据结构且工作于内核空间,因此具有流量转发速度快、规则同步性能好的特性,适用于存在大量Service资源且对性能要求较高的场景。ipvs代理模型支持rr、lc、dh、sh、sed和nq等多种调度算法。
修改代理模型
正常使用kubeadm部署的集群会在kube-system命名空间下生成一个kube-proxy的config,里面会记录代理模型。kubeadm部署默认是iptables,如需要修改可以编辑configmap并逐个重启kube-proxy的pod,当然建议在kubeadm init之前就定义为ipvs或者在集群刚部署完成的时候进行修改,修改完成之后再运行业务pod.
[root@k81 yaml]# kubectl -n kube-system edit configmaps kube-proxy Edit cancelled, no changes made. [root@k81 yaml]# kubectl -n kube-system get configmaps kube-proxy -oyaml|grep mode mode: ipvs [root@k81 yaml]# kubectl -n kube-system logs kube-proxy-9rrxd |grep Using I0222 09:56:25.969094 1 server_others.go:269] "Using ipvs Proxier"
Service资源类型
无论哪一种代理模型,Service资源都可统一根据其工作逻辑分为ClusterIP、NodePort、LoadBalancer和ExternalName这4种类型。
ClusterIP
通过集群内部IP地址暴露服务,ClusterIP地址仅在集群内部可达,因而无法被集群外部的客户端访问。此为默认的Service类型。
NodePort
NodePort类型是对ClusterIP类型Service资源的扩展,它支持通过特定的节点端口接入集群外部的请求流量,并分发给后端的ServerPod处理和响应。因此,这种类型的Service既可以被集群内部客户端通过ClusterIP直接访问,也可以通过套接字<NodeIP>: <NodePort>与集群外部客户端进行通信。显然,若集群外部的请求报文首先到的节点并非Service调度的目标Server Pod所在的节点,该请求必然因需要额外的转发过程(跃点)和更多的处理步骤而产生更多延迟。
另外,集群外部客户端对NodePort发起的请求报文源地址并非集群内部地址,而请求报文又可能被收到报文的节点转发至集群中的另一个节点上的Pod对象,因此,为避免X节点直接将响应报文发送给外部客户端,Y节点需要先将收到的报文的源地址转为请求报文的目标IP(自身的节点IP)后再进行后续处理过程。
LoadBalancer
这种类型的Service依赖于部署在IaaS云计算服务之上并且能够调用其API接口创建软件负载均衡器的Kubernetes集群环境。LoadBalancer Service构建在NodePort类型的基础上,通过云服务商提供的软负载均衡器将服务暴露到集群外部,因此它也会具有NodePort和ClusterIP。简言之,创建LoadBalancer类型的Service对象时会在集群上创建一个NodePort类型的Service,并额外触发Kubernetes调用底层的IaaS服务的API创建一个软件负载均衡器,而集群外部的请求流量会先路由至该负载均衡器,并由该负载均衡器调度至各节点上该Service对象的NodePort。该Service类型的优势在于,它能够把来自集群外部客户端的请求调度至所有节点(或部分节点)的NodePort之上,而不是让客户端自行决定连接哪个节点,也避免了因客户端指定的节点故障而导致的服务不可用。
ExternalName
通过将Service映射至由externalName字段的内容指定的主机名来暴露服务,此主机名需要被DNS服务解析至CNAME类型的记录中。换言之,此种类型不是定义由Kubernetes集群提供的服务,而是把集群外部的某服务以DNS CNAME记录的方式映射到集群内,从而让集群内的Pod资源能够访问外部服务的一种实现方式。因此,这种类型的Service没有ClusterIP和NodePort,没有标签选择器用于选择Pod资源,也不会有Endpoints存在。
总体来说,若需要将Service资源发布至集群外部,应该将其配置为NodePort或Load-Balancer类型,而若要把外部的服务发布于集群内部供Pod对象使用,则需要定义一个ExternalName类型的Service资源,只是这种类型的实现要依赖于v1.7及更高版本的Kubernetes。
应用Service资源
Service是Kubernetes核心API群组(core)中的标准资源类型之一,其管理操作的基本逻辑类似于Namespace和ConfigMap等资源,支持基于命令行和配置清单的管理方式,Service资源配置规范中常用的字段及意义如下所示。
apiVersion: v1 kind: Service metadata: name: … namespace: … labels:<map[string]string> xxx: xxxx spec: type <string> # Service类型,默认为ClusterIP selector <map[string]string> # 等值类型的标签选择器,内含“与”逻辑 ports: # Service的端口对象列表 - name <string> # 端口名称 protocol <string> # 协议,目前仅支持TCP、UDP和SCTP,默认为TCP port <integer> # Service的端口号 targetPort <string> # 后端目标进程的端口号或名称,名称需由Pod规范定义 nodePort <integer> # 节点端口号,仅适用于NodePort和LoadBalancer类型 clusterIP <string> # Service的集群IP,建议由系统自动分配避免冲突 externalTrafficPolicy <string> # 外部流量策略处理方式,Local表示由当前节点处理, Cluster表示向集群范围内调度 loadBalancerIP <string> # 外部负载均衡器使用的IP地址,仅适用于LoadBlancer externalName <string> # 外部服务名称,该名称将作为Service的DNS CNAME值
不同Service类型所支持使用的配置字段有着明显的区别,具体使用时应该根据计划使用的类型进行选择。
应用ClusterIP Service资源
创建Service对象的常用方法有两种:一是利用此前曾使用过的kubectl create service`或`kubectl expose命令创建,另一个则是利用资源配置清单创建。Service资源对象的期望状态定义在spec字段中,较为常用的内嵌字段为selector和ports,用于定义标签选择器和服务端口。下面的配置清单是定义在services-clusterip-demo.yaml中的一个Service资源示例:
kind: Service apiVersion: v1 metadata: name: demoapp-svc namespace: test spec: selector: app: demoapp ports: - name: http # 端口名称标识 protocol: TCP # 协议,支持TCP、UDP和SCTP port: 80 # Service自身的端口号 targetPort: 80 # 目标端口号,即Endpoint上定义的端口号
我们可以将示例中的Service对象创建于集群中,通过其详细描述了解其特性,如下面的命令及结果所示。
[root@k81 yaml]# kubectl apply -f services-clusterip-demo.yaml service/demoapp-svc created [root@k81 yaml]# kubectl -n test describe service demoapp-svc Name: demoapp-svc Namespace: test Labels: <none> Annotations: <none> Selector: app=demoapp Type: ClusterIP IP Family Policy: SingleStack IP Families: IPv4 IP: 192.168.209.4 IPs: 192.168.209.4 Port: http 80/TCP TargetPort: 80/TCP Endpoints: <none> Session Affinity: None Events: <none>
上面命令中的结果显示,demoapp-svc默认设定为ClusterIP类型,并得到一个自动分配的IP地址192.168.209.4。创建Service对象的同时会创建一个与之同名且拥有相同标签选择器的Endpoint对象,若该标签选择器无法匹配到任何Pod对象的标签,则Endpoint对象无任何可用端点数据,于是Service对象的Endpoints字段值便成了<none>。我们知道,Service对象自身只是iptables或ipvs规则,它并不能处理客户端的服务请求,而是需要把请求报文通过目标地址转换(DNAT)后转发至后端某个Server Pod,这意味着没有可用的后端端点的Service对象是无法响应客户端任何服务请求的,如下面从集群节点上发起的请求命令结果所示。
[root@k81 yaml]# curl 192.168.209.4 curl: (7) Failed connect to 192.168.209.4:80; Connection refused
下面使用命令式命令手动创建一个与该Service对象具有相同标签选择器的Deployment对象demoapp,它默认会自动创建一个拥有标签app:demoapp的Pod对象。
[root@k81 yaml]# kubectl create deploy demoapp --image=ikubernetes/demoapp:v1.0 -n test deployment.apps/demoapp created [root@k81 yaml]# kubectl -n test get pod -l app=demoapp -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES demoapp-5748b7ccfc-2vq8x 1/1 Running 0 3m20s 172.32.60.52 k82 <none> <none> [root@k81 yaml]# kubectl -n test get endpoints demoapp-svc NAME ENDPOINTS AGE demoapp-svc 172.32.60.52:80 5m58s [root@k81 yaml]# kubectl -n test describe service demoapp-svc |grep -i endpoints Endpoints: 172.32.60.52:80 [root@k81 yaml]# curl 192.168.209.4 iKubernetes demoapp v1.0 !! ClientIP: 172.32.219.0, ServerName: demoapp-5748b7ccfc-2vq8x, ServerIP: 172.32.60.52!
Service对象demoapp-svc通过API Server获知这种匹配变动后,会立即创建一个以该Pod对象的IP和端口为列表项的名为demoapp-svc的Endpoints对象,而该Service对象详细描述信息中的Endpoint字段便以此列表项为值。
扩展Deployment对象demoapp的应用规模引起的变动也将立即反映到相关的Endpoint和Service对象之上,例如将deployments/demoapp对象的副本扩展至3个,再来验证services/demoapp-svc的端点信息,如下面的命令及结果所示。
[root@k81 yaml]# kubectl -n test scale deployment demoapp --replicas=3 deployment.apps/demoapp scaled [root@k81 yaml]# kubectl -n test get endpoints demoapp-svc NAME ENDPOINTS AGE demoapp-svc 172.32.105.1:80,172.32.60.52:80,172.32.60.53:80 12m [root@k81 yaml]# kubectl -n test describe service demoapp-svc |grep -i endpoints Endpoints: 172.32.105.1:80,172.32.60.52:80,172.32.60.53:80
接下来可于集群中的某节点上再次向服务对象demoapp-svc发起访问请求以进行测试,多次的访问请求还可评估负载均衡算法的调度效果,如下面的命令及结果所示。
[root@k81 yaml]# while true; do curl -s 192.168.209.4:80/hostname; sleep 1;done ServerName: demoapp-5748b7ccfc-lmdtm ServerName: demoapp-5748b7ccfc-246kj ServerName: demoapp-5748b7ccfc-2vq8x ServerName: demoapp-5748b7ccfc-lmdtm ServerName: demoapp-5748b7ccfc-246kj ServerName: demoapp-5748b7ccfc-2vq8x ServerName: demoapp-5748b7ccfc-lmdtm
应用NodePort Service资源
部署Kubernetes集群系统时会预留一个端口范围,专用于分配给需要用到NodePort的Service对象,该端口范围默认为30000~32767。与Cluster类型的Service资源的一个显著不同之处在于,NodePort类型的Service资源需要显式定义.spec.type字段值为NodePort,必要时还可以手动指定具体的节点端口号。例如下面的配置清单(services-nodeport-demo.yaml)中定义的Service资源对象demoapp-nodeport-svc,它使用了NodePort类型,且人为指定了32223这个节点端口。
kind: Service apiVersion: v1 metadata: name: demoapp-nodeport-svc namespace: test spec: type: NodePort selector: app: demoapp ports: - name: http protocol: TCP port: 80 targetPort: 80 nodePort: 32223
将配置清单中定义的Service对象demoapp-nodeport-svc创建于集群之上,以便通过详细描述了解其状态细节。
[root@k81 yaml]# kubectl apply -f demoapp-nodeport-svc.yaml service/demoapp-nodeport-svc created [root@k81 yaml]# kubectl -n test describe service demoapp-nodeport-svc Name: demoapp-nodeport-svc Namespace: test Labels: <none> Annotations: <none> Selector: app=demoapp Type: NodePort IP Family Policy: SingleStack IP Families: IPv4 IP: 192.168.249.97 IPs: 192.168.249.97 Port: http 80/TCP TargetPort: 80/TCP NodePort: http 32223/TCP Endpoints: 172.32.105.1:80,172.32.60.52:80,172.32.60.53:80 Session Affinity: None External Traffic Policy: Cluster Events: <none>
命令结果显示,该Service对象用于调度集群外部流量时使用默认的Cluster策略,该策略优先考虑负载均衡效果,哪怕目标Pod对象位于另外的节点之上而带来额外的网络跃点,因而针对该NodePort的请求将会被分散调度至该Serivce对象关联的所有端点之上。可以在集群外的某节点上对任一工作节点的NodePort端口发起HTTP请求以进行测试。以节点k83为例,我们以如下命令向它的IP地址10.10.21.251的32223端口发起多次请求。
[root@k81 yaml]# while true ;do curl -s 10.10.21.251:32223 ;sleep 0.3 ;done iKubernetes demoapp v1.0 !! ClientIP: 172.32.105.0, ServerName: demoapp-5748b7ccfc-lmdtm, ServerIP: 172.32.60.53! iKubernetes demoapp v1.0 !! ClientIP: 172.32.105.0, ServerName: demoapp-5748b7ccfc-2vq8x, ServerIP: 172.32.60.52! iKubernetes demoapp v1.0 !! ClientIP: 10.10.21.251, ServerName: demoapp-5748b7ccfc-246kj, ServerIP: 172.32.105.1! iKubernetes demoapp v1.0 !! ClientIP: 172.32.105.0, ServerName: demoapp-5748b7ccfc-lmdtm, ServerIP: 172.32.60.53! iKubernetes demoapp v1.0 !! ClientIP: 172.32.105.0, ServerName: demoapp-5748b7ccfc-2vq8x, ServerIP: 172.32.60.52! iKubernetes demoapp v1.0 !! ClientIP: 10.10.21.251, ServerName: demoapp-5748b7ccfc-246kj, ServerIP: 172.32.105.1! iKubernetes demoapp v1.0 !! ClientIP: 172.32.105.0, ServerName: demoapp-5748b7ccfc-lmdtm, ServerIP: 172.32.60.53!
上面命令的结果显示出外部客户端的请求被调度至该Service对象的每一个后端Pod之上,而这些Pod对象可能会分散于集群中的不同节点。命令结果还显示,请求报文的客户端IP地址是最先接收到请求报文的节点上用于集群内部通信的IP地址,而非外部客户端地址,这也能在pod应用日志中进一步验证。
[root@k81 yaml]# kubectl -n test logs demoapp-5748b7ccfc-lmdtm |tail -n 5 172.32.219.0 - - [10/Mar/2023 13:26:57] "GET /hostname HTTP/1.1" 200 - 172.32.105.0 - - [10/Mar/2023 13:41:42] "GET / HTTP/1.1" 200 - 172.32.105.0 - - [10/Mar/2023 13:41:43] "GET / HTTP/1.1" 200 - 172.32.105.0 - - [10/Mar/2023 13:41:44] "GET / HTTP/1.1" 200 - 172.32.105.0 - - [10/Mar/2023 13:41:45] "GET / HTTP/1.1" 200 -
确保Server Pod的响应报文必须由最先接收到请求报文的节点进行响应,因此NodePort类型的Service对象会对请求报文同时进行源地址转换(SNAT)和目标地址转换(DNAT)操作。
另一个外部流量策略Local则仅会将流量调度至请求的目标节点本地运行的Pod对象之上,以减少网络跃点,降低网络延迟,但当请求报文指向的节点本地不存在目标Service相关的Pod对象时将直接丢弃该报文。下面先把demoapp-nodeport-svc的外部流量策略修改为Local,而后再进行访问测试。这里使用kubectl patch命令来修改Service对象的流量策略。
[root@k81 yaml]# kubectl -n test patch service demoapp-nodeport-svc -p '{"spec":{"externalTrafficPolicy": "Local"}}' service/demoapp-nodeport-svc patched [root@k81 yaml]# kubectl -n test get service demoapp-nodeport-svc -oyaml apiVersion: v1 kind: Service metadata: annotations: kubectl.kubernetes.io/last-applied-configuration: | {"apiVersion":"v1","kind":"Service","metadata":{"annotations":{},"name":"demoapp-nodeport-svc","namespace":"test"},"spec":{"ports":[{"name":"http","nodePort":32223,"port":80,"protocol":"TCP","targetPort":80}],"selector":{"app":"demoapp"},"type":"NodePort"}} creationTimestamp: "2023-03-10T13:38:30Z" name: demoapp-nodeport-svc namespace: test resourceVersion: "4083129" uid: 7121506b-8ef1-4735-a188-a1f2f22cf5ba spec: clusterIP: 192.168.249.97 clusterIPs: - 192.168.249.97 externalTrafficPolicy: Local # 这里已经通过patch改过来了,如果不习惯用这个命令直接edit也是一样的效果 internalTrafficPolicy: Cluster ipFamilies: - IPv4 ipFamilyPolicy: SingleStack ports: - name: http nodePort: 32223 port: 80 protocol: TCP targetPort: 80 selector: app: demoapp sessionAffinity: None type: NodePort status: loadBalancer: {}
配置完成后,我们再次发起测试请求时会看到,请求都被调度给了目标节点本地运行的Pod对象。另外,Local策略下无须在集群中转发流量至其他节点,也就不用再对请求报文进行源地址转换,Server Pod所看到的客户端IP就是外部客户端的真实地址。
[root@k81 yaml]# while true ;do curl -s 10.10.21.251:32223 ;sleep 0.3 ;done iKubernetes demoapp v1.0 !! ClientIP: 10.10.21.249, ServerName: demoapp-5748b7ccfc-246kj, ServerIP: 172.32.105.1! iKubernetes demoapp v1.0 !! ClientIP: 10.10.21.249, ServerName: demoapp-5748b7ccfc-246kj, ServerIP: 172.32.105.1! iKubernetes demoapp v1.0 !! ClientIP: 10.10.21.249, ServerName: demoapp-5748b7ccfc-246kj, ServerIP: 172.32.105.1! iKubernetes demoapp v1.0 !! ClientIP: 10.10.21.249, ServerName: demoapp-5748b7ccfc-246kj, ServerIP: 172.32.105.1! iKubernetes demoapp v1.0 !! ClientIP: 10.10.21.249, ServerName: demoapp-5748b7ccfc-246kj, ServerIP: 172.32.105.1! ^C [root@k81 yaml]# while true ;do curl -s 10.10.21.250:32223 ;sleep 0.3 ;done iKubernetes demoapp v1.0 !! ClientIP: 10.10.21.249, ServerName: demoapp-5748b7ccfc-lmdtm, ServerIP: 172.32.60.53! iKubernetes demoapp v1.0 !! ClientIP: 10.10.21.249, ServerName: demoapp-5748b7ccfc-2vq8x, ServerIP: 172.32.60.52! iKubernetes demoapp v1.0 !! ClientIP: 10.10.21.249, ServerName: demoapp-5748b7ccfc-lmdtm, ServerIP: 172.32.60.53! iKubernetes demoapp v1.0 !! ClientIP: 10.10.21.249, ServerName: demoapp-5748b7ccfc-2vq8x, ServerIP: 172.32.60.52! iKubernetes demoapp v1.0 !! ClientIP: 10.10.21.249, ServerName: demoapp-5748b7ccfc-lmdtm, ServerIP: 172.32.60.53! ^C [root@k81 yaml]# while true ;do curl -s 10.10.21.249:32223 ;sleep 0.3 ;done # 这里访问的是master节点的地址,由于污点存在,master节点未运行业务pod导致访问失败
应用LoadBalancer Service资源
NodePort类型的Service资源虽然能够在集群外部访问,但外部客户端必须事先得知NodePort和集群中至少一个节点IP地址,一旦被选定的节点发生故障,客户端还得自行选择请求访问其他的节点,因而一个有着固定IP地址的固定接入端点将是更好的选择。此外,集群节点很可能是某IaaS云环境中仅具有私有IP地址的虚拟主机,这类地址对互联网客户端不可达,为此类节点接入流量也要依赖于集群外部具有公网IP地址的负载均衡器,由其负责接入并调度外部客户端的服务请求至集群节点相应的NodePort之上。
Service对象的loadBalancerIP负责承接外部发来的流量,该IP地址通常由云服务商系统动态配置,或者借助.spec.loadBalancerIP字段显式指定,但有些云服务商不支持用户设定该IP地址,这种情况下,即便提供了也会被忽略。外部负载均衡器的流量会直接调度至Service后端的Pod对象之上,而如何调度流量则取决于云服务商,有些环境可能还需要为Service资源的配置定义添加注解,必要时请自行参考云服务商文档说明。另外,LoadBalancer Service还支持使用.spec.loadBalancerSourceRanges字段指定负载均衡器允许的客户端来源的地址范围。我这边由于暂时没有云厂商的IaaS环境就只能先写一个简单的demo了。
kind: Service apiVersion: v1 metadata: name: demoapp-lb-svc namespace: test spec: type: LoadBalancer selector: app: demoapp ports: - name: http protocol: TCP port: 80 targetPort: 80 [root@k81 yaml]# kubectl get svc demoapp-lb-svc -n test NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE demoapp-lb-svc LoadBalancer 192.168.101.64 <pending> 80:31816/TCP 3m4s
外部IP
若集群中部分或全部节点除了有用于集群通信的节点IP地址之外,还有可用于外部通信的IP地址(例如云厂商的EIP),那么我们还可以在Service资源上启用spec.externalIPs字段来基于这些外部IP地址向外发布服务。所有路由到指定的外部IP(externalIP)地址某端口的请求流量都可由该Service代理到后端Pod对象之上。从这个角度来说,请求流量到达外部IP与节点IP并没有本质区别,但外部IP却可能仅存在于一部分的集群节点之上,而且它不受Kubernetes集群管理,需要管理员手动介入其配置和回收等操作任务中。
外部IP地址可结合ClusterIP、NodePort或LoadBalancer任一类型的Service资源使用,而到达外部IP的请求流量会直接由相关联的Service调度转发至相应的后端Pod对象进行处理。假设示例Kubernetes集群中的k81节点上拥有一个可被路由到的IP地址10.10.21.249,我们期望能够将demoapp的服务通过该外部IP地址发布到集群外部,则可以使用下列配置清单(services-externalip-demo.yaml)中的Service资源实现。
kind: Service apiVersion: v1 metadata: name: demoapp-externalip-svc namespace: test spec: type: ClusterIP selector: app: demoapp ports: - name: http protocol: TCP port: 80 targetPort: 80 externalIPs: - 10.10.21.249
现在找集群外的一个节点进行访问测试
[zettakit@rocky1 ~]$ curl 10.10.21.249 iKubernetes demoapp v1.0 !! ClientIP: 172.32.219.0, ServerName: demoapp-5748b7ccfc-lmdtm, ServerIP: 172.32.60.53! [zettakit@rocky1 ~]$ curl 10.10.21.250 curl: (7) Failed to connect to 10.10.21.250 port 81: Connection refused [zettakit@rocky1 ~]$ curl 10.10.21.251 curl: (7) Failed to connect to 10.10.21.251 port 81: Connection refused
可以看到只有定义了的地址能成功访问,其他集群中未定义的节点是访问不了的。不难猜测,节点k81故障也必然导致该外部IP上公开的服务不再可达,除非该IP地址可以浮动到其他节点上。如今,大多数云服务商都支持浮动IP的功能,该IP地址可绑定在某个主机,并在其故障时通过某种触发机制自动迁移至其他主机。