5.2 连接集群外部的服务
5.2 连接集群外部的服务
到现在为止,我们已经讨论了后端是集群中运行的一个或多个pod的服务。但也存在希望通过Kubernetes服务特性暴露外部服务的情况。不要让服务将连接重定向到集群中的pod,而是让它重定向到外部IP和端口。
这样做可以让你充分利用服务负载平衡和服务发现。在集群中运行的客户端pod可以像连接到内部服务一样连接到外部服务。
5.2.1 介绍服务endpoint
在进入如何做到这一点之前,先阐述一下服务。服务并不是和pod直接相连的。相反,有一种资源介于两者之间——它就是Endpoint资源。如果之前在服务上运行过kubectl describe,可能已经注意到了endpoint,如下面的代码清单所示。
代码清单5.7 用kubectl describe展示服务的全部细节
# kubectl describe svc kubia
Name: kubia
Namespace: custom
Labels: <none>
Annotations: <none>
Selector: app=kubia #用于创建endpoint列表的服务pod选择器
Type: ClusterIP
IP Family Policy: SingleStack
IP Families: IPv4
IP: 10.111.56.158
IPs: 10.111.56.158
Port: <unset> 80/TCP
TargetPort: 8080/TCP
Endpoints: 172.18.0.2:8080,172.18.0.3:8080,172.18.0.4:8080 # 代表服务endpoint的pod的IP和端口列表
Session Affinity: None
Events: <none>
Endpoint资源就是暴露一个服务的IP地址和端口的列表,Endpoint资源和其他Kubernetes资源一样,所以可以使用 kubectl info
来获取它的基本信息。
$ kubectl get endpoint kubia
尽管在 spec服务中定义了pod选择器,但在重定向传入连接时不会直接使用它。相反,选择器用于构建IP和端口列表,然后存储在Endpoint资源中。当客户端连接到服务时,服务代理选择这些IP和端口对中的一个,并将传入连接重定向到在该位置监听的服务器。
5.2.2 手动配置服务的endpoint
或许已经意识到这一点,服务的 endpoint与服务解耦后,可以分别手动配置和更新它们。
如果创建了不包含pod选择器的服务,Kubernetes将不会创建Endpoint资源(毕竟,缺少选择器,将不会知道服务中包含哪些pod)。这样就需要创建Endpoint资源来指定该服务的endpoint列表。
要使用手动配置endpoint的方式创建服务,需要创建服务和Endpoint资源。
创建没有选择器的服务
首先为服务创建一个YAML文件,如下面的代码清单所示。
代码清单5.8 不含pod选择器的服务:external-service.yaml
apiVersion: v1
kind: Service
metadata:
name: external-service # 服务的名字必须和endpoint对象的名字相匹配
spec:
ports:
- port: 80
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- name: http
protocol: TCP
port: 80
targetPort: 9376
externalIPs:
- 80.11.12.10
定义一个名为external-service的服务,它将接收端口80上的传入连接。并没有为服务定义一个pod选择器。
为没有选择器的服务创建Endpoint资源
Endpoint是一个单独的资源并不是服务的一个属性。由于创建的资源中并不包含选择器,相关的Endpoints资源并没有自动创建,所以必须手动创建。如下所示的代码清单中列出了YAML manifest。
k get pod -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
kubia-7d5b548867-4qn2x 1/1 Running 1 23h 172.18.0.2 minikube <none> <none>
kubia-7d5b548867-8wnls 1/1 Running 1 23h 172.18.0.3 minikube <none> <none>
kubia-7d5b548867-m4bfz 1/1 Running 1 23h 172.18.0.4 minikube <none> <none>
代码清单5.9 手动创建Endpoint资源:external-service-endpoints.yaml
apiVersion: v1
kind: Endpoints
metadata:
name: external-service # endpoint的名称必须和服务相匹配
subsets:
- addresses:
- ip: 172.18.0.2 #服务将连接重定向到endpoint的ip地址
- ip: 192.168.0.105
- ip: 119.147.33.43
ports:
- port: 8080 #endpoint的目标端口
验证一下
$ k exec -it kubia-7d5b548867-4qn2x -- bash
$ curl http://external-service
Endpoint对象需要与服务具有相同的名称,并包含该服务的目标IP地址和端口列表。服务和Endpoint资源都发布到服务器后,这样服务就可以像具有pod选择器那样的服务正常使用。在服务创建后创建的容器将包含服务的环境变量,并且与其IP:port对的所有连接都将在服务端点之间进行负载均衡。
图5.4显示了三个pod连接到具有外部endpoint的服务。
如果稍后决定将外部服务迁移到Kubernetes中运行的pod,可以为服务添加选择器,从而对Endpoint进行自动管理。反过来也是一样的——将选择器从服务中移除,Kubernetes将停止更新Endpoints。这意味着服务的IP地址可以保持不变,同时服务的实际实现却发生了改变。
5.2.3 为外部服务创建别名
除了手动配置服务的Endpoint来代替公开外部服务方法,有一种更简单的方法,就是通过其完全限定域名(FQDN)访问外部服务
创建ExternalName类型的服务
要创建一个具有别名的外部服务的服务时,要将创建服务资源的一个type字段设置为ExternalName。例如,设想一下在http://api.somecompany.com上有公共可用的API,可以定义一个指向它的服务,如下面的代码清单所示。
代码清单5.10 ExternalName类型的服务:external-service-externalname.yaml\
apiVersion: v1
kind: Service
metadata:
name: external-service
spec:
type: ExternalName
externalName: www.baidu.com #实际服务的域名
ports:
- port: 80
服务创建完成后,pod可以通过 external-service.default.svc.cluster.local
域名(甚至是 external-service
)连接到外部服务,而不是使用服务的实际FQDN。这隐藏了实际的服务名称及其使用该服务的pod的位置,允许修改服务定义,并且在以后如果将其指向不同的服务,只需简单地修改externalName属性,或者将类型重新变回ClusterIP并为服务创建Endpoint——无论是手动创建,还是对服务上指定标签选择器使其自动创建。
ExternalName 服务仅在DNS级别实施——为服务创建了简单的CNAME DNS记录。因此,连接到服务的客户端将直接连接到外部服务,完全绕过服务代理。
出于这个原因,这些类型的服务甚至不会获得集群IP。
注意 CNAME记录指向完全限定的域名而不是数字IP地址。