Kubernetes-服务
五、服务
假设现在通过Deployment部署了一些pod,这些pod是一些网络服务,即它们需要根据外部请求做出响应(比如这些pod提供了网上商城的服务)。如果是传统部署,只需要在配置文件中明确指出服务器的IP地址或者域名即可被外界访问,但是在k8s中不可以,因为:
- pod存在生命周期,它们是短暂的,它们会根据系统的各种情况被删除,然后再次重建
- k8s在启动前会给调度到节点上的pod分配IP地址,因此客户端不能提前知道pod的IP地址
- 多个pod可能提供同一个服务,每个pod都有自己的IP地址,但客户端的访问只需要一个固定的IP地址
为了解决以上问题,k8s提供了一种资源——Service(服务),简称为svc。
1 基础概念
Kubernetes的服务是一种为一组功能相同的pod提供单一不变的接入点的资源。当服务存在时,它的IP地址和端口不会改变。客户端通过这个IP地址和端口号建立连接,这些连接会被路由到提供该服务的任意一个pod上。前端客户端不应该也没必要知道每个单独的提供服务的pod的地址,这样这些pod就可以在集群中随时被创建或移除。
举个例子,一个应用分为两部分,有很多pod提供前端服务,而只有一个pod提供后台数据库服务。此时需要解决两个问题才能使系统正常工作:
- 外部客户端无须关心服务器数量而连接到前端pod上。
- 前端的pod需要连接后端的数据库。由于数据库运行在pod中,它可能会在集群中移来移去,导致IP地址变化。当后台数据库被移动时,无须对前端pod重新配置。
可以为前端pod创建服务,并且将其配置成可以在集群外部访问,暴露一个单一不变的IP地址让外部的客户端连接pod。同理,为后台数据库pod创建服务,并为其分配一个固定的IP地址。尽管pod的IP地址可能会改变,但是服务的IP地址固定不变。另外,通过创建服务,能够让前端的pod通过环境变量或DNS以及服务名来访问后端服务。整个系统如图所示:
2 创建服务
通过前面的例子可以看出,由于pod可能不止一个,因此服务需要使用标签选择器来准确地管理这些pod,并对它们进行负载均衡。下面是一个服务的yaml资源清单示例:
1 | apiVersion: v1 |
上述配置创建一个名称为 “my-service” 的 Service 对象,它会将请求代理到使用 TCP 端口 9376,并且具有标签 "app=MyApp"
的 Pod 上。使用kubectl create -f
发布文件创建服务。
2.1 查看服务
创建完svc后,可以在命名空间下查看所有的服务:
1 | $ kubectl get svc |
这里的CLUSTER-IP
就是Kubernetes为该服务分配的IP地址(也称为“集群IP”),这里是10.98.146.16
,因为只是集群的IP地址, 只
能在集群内部可以被访问。服务的主要目标就是使集群内部的其他pod可以访问当前这组pod。至于如何对外暴露服务后续讲解。
2.2 同—个服务暴露多个端口
在上一小节,注意到字段PORT(S)
,说明暴露的端口可以是一个,也可以是多个:比如,你的pod需要监听两个端口,HTTP监听8080端口、HTTPS监听8443 端口,可以使用一个服务从端口80和443转发至pod端口8080和8443。在这种情况下,无须创建两个不同的服务。通过一个集群IP,使用一个服务就可以将多个端口全部暴露出来。但需要注意,在创建一个有多个瑞口的服务的时候,必须给每个瑞口指定名字。举例:
1 | apiVersion: v1 |
2.3 使用命名的端口
也可以使用命名的端口,假设你的pod资源清单如下所示:
1 | kind: Pod |
接下来可以在服务spec中按名称引用这些端口:
1 | apiVersion: v1 |
采用端口命名的方式有一个好处就是:即使更换端口号也无须更改服务spec,比如后续你的pod将8080端口更改为8888端口,你只需要更改pod的端口,而不用修改服务的targetPort
。
与一般的Kubernetes名称一样,端口名称只能包含小写字母数字字符 和
-
。 端口名称还必须以字母数字字符开头和结尾。例如,名称
123-abc
和web
有效,但是123_abc
和-web
无效。
3 服务发现
通过创建服务,现在就可以通过一个单一稳定的IP地址访问到pod 。在服务整个生命周期内这个地址保持不变。在服务后面的pod可能删除重建,它们的IP地址可能改变,数量也会增减,但是始终可以通过服务的单一不变的IP地址访问到这些pod。
但客户端pod如何知道服务的IP和端口?是否需要先创建服务,然后手动kubectl get svc
获得其IP地址并将IP传递给客户端pod的配置选项?当然不是。Kubernetes支持两种基本的服务发现模式——环境变量和DNS。
3.1 通过环境变量发现服务
在pod开始运行的时候,Kubernetes会初始化一系列的环境变量指向现在存在的服务。但要注意,你创建的服务必须早于客户端pod的创建,否则,这些客户端pod将不会设定其环境变量。如果你的集群中存在pod比svc提前创建的情况,在查看服务的环境变量之前,首先需要删除所有的这些pod使得RC创建新的pod。
举个例子,一个名称为 redis-master
的 Service 暴露了 TCP 端口 6379, 同时给它分配了 Cluster IP 地址 10.0.0.11,这个 Service 生成了如下环境变量:
1 | kubectl exec kubia-3inly env |
名称的创建规则为: {SVCNAME}_SERVICE_HOST
和 {SVCNAME}_SERVICE_PORT
变量。 这里 Service 的名称需大写,横线被转换成下划线。这样,其他pod就可以通过环境变量获取服务的内部集群IP地址和端口。
3.2 通过DNS发现服务
Kubernetes DNS在集群上调度DNS Pod和服务,并配置kubelet以告知各个容器使用DNS服务的IP来解析DNS名称。客户端的pod在知道服务名称的情况下可以通过全限定域名来访问,而不是诉诸于环境变量。
DNS 查询可能因为执行查询的 Pod 所在的名称空间而返回不同的结果。 不指定名称空间的 DNS 查询会被限制在 Pod 所在的名称空间内。 要访问其他名称空间中的服务,需要在 DNS 查询中给出名称空间。
例如,假定名称空间 test
中存在一个 Pod,prod
名称空间中存在一个服务 data
。Pod 查询服务 data
时没有返回结果,因为使用的是 Pod 的名称空间 test
,在这个名称空间下没有data
服务。Pod 查询 data.prod
时则会返回预期的结果,因为查询中指定了名称空间。
kubelet 会为每个pod容器内生成/etc/resolv.conf
文件,例如,对 data
的查询可能被展开为 data.prod.svc.cluster.local
。 其中,data
是服务名称,prod
表示服务所在的名称空间,而svc.cluster.local
是在所有集群本地服务名称中使用的可配置集群域后缀。如果发出查询的pod和服务pod在同一个名称空间下,则svc.cluster.local
可以省略,甚至名称空间也可以省略,只需要使用data
即可指代服务。至于为什么可以省略,可以查看容器内的/etc/resolv.conf
文件
1 | search <namespace>.svc.cluster.local svc.cluster.local cluster.local |
其中的search
选项的取值会被用来展开查询。要进一步了解 DNS 查询,可参阅 resolv.conf
手册页面。
关于DNS记录的更多细节,可参阅官方文档。
4 将服务暴露给外部
到目前为止, 只讨论了集群内服务如何被pod使用;但是,有时需要将服务暴露给外部访问。例如运行web服务,以便外部客户端可以访问它们。可以通过更改Service类型来实现。Service通过ServiceTypes
字段允许指定你所需要的 Service 类型,有四种类型
- ClusterIP:通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。 这也是默认的
ServiceType
。 - NodePort:通过每个节点上的 IP 和静态端口(
NodePort
)暴露服务。NodePort
服务会路由到自动创建的ClusterIP
服务。 通过请求<节点 IP>:<节点端口>
,你可以从集群的外部访问一个NodePort
服务。 - LoadBalancer:使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的
NodePort
服务和ClusterIP
服务上。 - ExternalName:通过返回
CNAME
和对应值,可以将服务映射到externalName
字段的内容(例如,foo.bar.example.com
)。 无需创建任何类型代理。注意,你需要使用 kube-dns 1.7 及以上版本或者 CoreDNS 0.0.8 及以上版本才能使用ExternalName
类型。
除此之外,还有一种叫做Ingress
的资源将服务暴露给外部,在下一节会具体介绍。
4.1 NodePort
将一组pod公开暴露给外部客户端的一种方法是创建一个服务并将其类型设置为NodePort。通过创建NodePort服务,可以让Kubernetes在其所有节点上保留一个端口(所有节点上都使用相同的端口号),并将传入的连接转发给作为服务部分的pod。
现在创建一个NodePort服务,如下面的资源清单所示:
1 | apiVersion: v1 |
创建这个服务,然后通过kubectl get svc my-service
查看这个服务的信息:
1 | $ kubectl get svc my-service |
这里的PORT(S)
列显示集群IP的内部端口(80)和节点端口(30000),即可以通过以下地址访问到服务pod:
- 集群内部IP地址:80,比如这里是10.96.235.13:80
- 节点1的IP地址:30000,这里是192.168.142.21:30000
- 节点2的IP地址:30000,这里是192.168.142.22:30000
但是,如果只将客户端指向一个节点,那么当该节点发生故障时,客户端无法再访问该服务。因此,还需要手动做负载均衡,比如将nginx放在节点前面。
4.2 LoadBalancer
在使用支持外部负载均衡器的云提供商的服务时,设置 type
的值为 "LoadBalancer"
, 将为 Service 提供负载均衡器。当然,每个用到的 LoadBalancer 都需要付费。
下面是一个LoadBalancer类型的服务资源清单:
1 | apiVersion: v1 |
关于被提供的负载均衡器的信息将会通过 Service 的 status.loadBalancer
字段发布出去。有关更多信息,参阅官方文档。
5 Ingress
ingress /ˈɪnɡres/
n. 进入;入口;准许进入;入境
Ingress
也可以将服务暴露给外部,LoadBalancer需要自己的负载均衡器,以及独有的公有IP地址,而Ingress只需要一个公网IP就能为许多服务提供访问:
为了让Ingress资源工作,就相当于k8s的插件,集群必须有一个正在运行的Ingress控制器。你可能需要部署Ingress控制器,例如ingress-nginx。你可以从许多Ingress 控制器中进行选择。
下面是一个Ingress最简单的资源清单:
1 | apiVersion: networking.k8s.io/v1 |
Ingress经常使用注解(annotations)来配置一些选项,具体取决于不同的Ingress控制器,不同的Ingress控制器支持不同的注解。查看文档以供你选择Ingress控制器,以了解支持哪些注解。
关于Ingress的更多信息,参阅官方文档。