服务发现
服务(Service)
将在集群中运行的应用通过同一个面向外界的端点公开出去,即使工作负载分散于多个后端也完全可行。
Kubernetes 中 Service 是 将运行在一个或一组 Pod上的网络应用程序公开为网络服务的方法。
Kubernetes 中 Service 的一个关键目标是让你无需修改现有应用以使用某种不熟悉的服务发现机制。 你可以在 Pod 集合中运行代码,无论该代码是为云原生环境设计的,还是被容器化的老应用。 你可以使用 Service 让一组 Pod 可在网络上访问,这样客户端就能与之交互。
如果你使用 Deployment来运行你的应用, Deployment 可以动态地创建和销毁 Pod。 在任何时刻,你都不知道有多少个这样的 Pod 正在工作以及它们健康与否; 你可能甚至不知道如何辨别健康的 Pod。 Kubernetes Pod 的创建和销毁是为了匹配集群的预期状态。 Pod 是临时资源(你不应该期待单个 Pod 既可靠又耐用)。
每个 Pod 会获得属于自己的 IP 地址(Kubernetes 期待网络插件来保证这一点)。 对于集群中给定的某个 Deployment,这一刻运行的 Pod 集合可能不同于下一刻运行该应用的 Pod 集合。
这就带来了一个问题:如果某组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”) 集合提供功能,前端要如何发现并跟踪要连接的 IP 地址,以便其使用负载的后端组件呢?
这样我们可以通过下图大概了解,下图中可以看出我们的客户端以及api-server并没有通过直接对接后端Pod的方式进行连接访问的,而是通过一个叫做ClusterIP作为一个统一入口进行代理访问,这个ClusterIP就是我们本章节要介绍的Service,同时ClusterIP是通过集群中的iptables进行实现的。这样就能做到不管Pod是如何删除又重建的,我们始终访问的是这个ClusterIP这个是不会变动的。
那么我们的集群中会有很多个Pod,service又是如何能找到例如Mysql这个后端的Pod是哪些呢?service是通过一个名为标签选择器的配置Yaml进行筛选的,这个之前我们在控制器章节也是有见过这个参数的,最终我们就可以实现给临时的Pod绑定上一个持久的service网络,所有的不管客户端也好,集群内部的api也好都可以通过持久的service找到我们对应的Pod并且通过service内部的算法合理的将流量分配到我们的每一个Pod身上。

Kubernetes 中的 Service
Service API 是 Kubernetes 的组成部分,它是一种抽象,帮助你将 Pod 集合在网络上公开出去。 每个 Service 对象定义端点的一个逻辑集合(通常这些端点就是 Pod)以及如何访问到这些 Pod 的策略。
例如,考虑一个无状态的图像处理后端,其中运行 3 个副本(Replicas)。 这些副本是可互换的 —— 前端不需要关心它们调用的是哪个后端。 即便构成后端集合的实际 Pod 可能会发生变化,前端客户端不应该也没必要知道这些, 而且它们也不必亲自跟踪后端的状态变化。
Service 抽象使这种解耦成为可能。
Service 所对应的 Pod 集合通常由你定义的选择算符来确定。 若想了解定义 Service 端点的其他方式,可以查阅后续的不带选择算符的 Service。
如果你的工作负载使用 HTTP 通信,你可能会选择使用 Ingress 来控制 Web 流量如何到达该工作负载。Ingress 不是一种 Service,但它可用作集群的入口点。 Ingress 能让你将路由规则整合到同一个资源内,这样你就能将工作负载的多个组件公开出去, 这些组件使用同一个侦听器,但各自独立地运行在集群中。
用于 Kubernetes 的 Gateway API 能够提供 Ingress 和 Service 所不具备的一些额外能力。 Gateway 是使用 CustomResourceDefinitions 实现的一系列扩展 API。 你可以添加 Gateway 到你的集群中,之后就可以使用它们配置如何访问集群中运行的网络服务。
service的类型
type: ClusterIP
指定该类型时,服务只能够在集群内部访问,同时此为默认 Service 类型,从你的集群中为此预留的 IP 地址池中分配一个 IP 地址。
其他几种 Service 类型在 ClusterIP 类型的基础上进行构建。
如果你定义的 Service 将 .spec.clusterIP 设置为 "None",则 Kubernetes 不会为其分配 IP 地址。其被称为无头服务。
选择自己的 IP 地址
在创建 Service 的请求中,你可以通过设置 spec.clusterIP 字段来指定自己的集群 IP 地址。 比如,希望复用一个已存在的 DNS 条目,或者遗留系统已经配置了一个固定的 IP 且很难重新配置。
你所选择的 IP 地址必须是合法的 IPv4 或者 IPv6 地址,并且这个 IP 地址在 API 服务器上所配置的 service-cluster-ip-range CIDR 范围内。 如果你尝试创建一个带有非法 clusterIP 地址值的 Service,API 服务器会返回 HTTP 状态码 422, 表示值不合法。

type: NodePort
如果你将 type 字段设置为 NodePort,则 Kubernetes 控制平面将在 --service-node-port-range 标志所指定的范围内分配端口(默认值:30000-32767)。 每个节点将该端口(每个节点上的相同端口号)上的流量代理到你的 Service。 你的 Service 在其 .spec.ports[*].nodePort 字段中报告已分配的端口。
通过每个节点的IP和静态端口暴露访问。NodePort访问会路由到自动创建的ClusterIP服务。通过请求<节点IP>:<节点端口>,不过您可以从集群外部进行访问服务,因此即使是创建了NodePort,他也会为您自动创建出一个clusterIP,通过节点IP路由到您的clusterIP,从而实现了从外部访问到集群内部。
对于 NodePort 类型 Service,Kubernetes 额外分配一个端口(TCP、UDP 或 SCTP 以匹配 Service 的协议)。 集群中的每个节点都将自己配置为监听所分配的端口,并将流量转发到与该 Service 关联的某个就绪端点。 通过使用合适的协议(例如 TCP)和适当的端口(分配给该 Service)连接到任何一个节点, 你就能够从集群外部访问 type: NodePort 服务。
选择你自己的端口
如果需要特定的端口号,你可以在 nodePort 字段中指定一个值。 控制平面将或者为你分配该端口,或者报告 API 事务失败。 这意味着你需要自行注意可能发生的端口冲突。 你还必须使用有效的端口号,该端口号在配置用于 NodePort 的范围内。
由于ClusterIP仅限于集群内部访问,无法让需要局域网内的客户端直接进行访问,对于这样的业务,我们可以使用NodePort,可以解决集群外部的访问需求。

type: LoadBalancer
在使用云提供商的负载均衡器向外暴露访问,如果将 type 设置为 "LoadBalancer", 则平台会为 Service 提供负载均衡器。 负载均衡器的实际创建过程是异步进行的,关于所制备的负载均衡器的信息将会通过 Service 的 status.loadBalancer 字段公开出来,外部负载均衡器可以将流量路由到自动创建的NodePort服务和ClusterIP服务上。
由于NodePort类型他仅限于局域网,并且他所使用的端口是(30000-32767)的高端口并且提出的服务数量有限制,所以如果您对该服务具有公网访问、低端口等需求,我们可以使用LoadBalancer不仅可以满足公网服务访问,并且可以将高端口转换为低端口,便于记忆。
但是由于这需要用到云服务器和公网IP和负载均衡器,这东西是需要收费的,这是无法避免的,当然您如果没有公网需求只需要一个低端口访问,您可以使用***(负载均衡器)来实现这一效果。

ExternalName 类型
类型为 ExternalName 的 Service 将 Service 映射到 DNS 名称,而不是典型的选择算符, 例如 my-service 或者 cassandra。你可以使用 spec.externalName 参数指定这些服务。
iptables代理模式的Service
kube-proxy会监视kubernetes控制节点对Service对象和Endpoints对象的添加和移除。对每个Service,他会配置iptables规则,从而捕获到达该Service的ClusterIP和端口的请求,进而将请求重定向到Service的一组后端中的某个Pod上。对于每个Endpoints对象,它也会配置iptables规则,这个规则会选择一个后端组合。
默认的策略是,kube-proxy在iptables模式下随机选择一个后端。
使用iptables处理六里昂具有较低的系统开销,因为流量由Linux netfilter处理,而无需在用户空间和内核空间之间来回切换。这种方法也可能更可靠。
IPVS代理模式的Service
在ipvs模式下,kube-proxy监控kubernetes服务和端点,调用netlink接口相应地创建IPVS规则,并定期将IPVS规则与kubernetes服务和端点同步。该控制循环可确保IPVS状态与所需状态匹配。访问服务时,IPVS将流量定向到后端的Pod之一。
IPVS代理模式基于类似iptables模式的netfilter挂钩函数,但是使用哈希表作为基础数据结构,并且在内核空间中工作。这意味着,与iptables模式下的kube-proxy相比,IPVS模式下的kube-proxy重定向通信的延迟要短,并且在同步代理规则时具有更好的性能。与其他代理模式相比,IPVS模式还支持更高的网络流量吞吐。
因为当您的规则有上百万条的时候,如果您使用的是iptables那么它得挨个扫描一变去匹配规则,那这样可能会出现几秒或是十几秒的延迟,如果当您使用的是IPVS,由于它是基于哈希表进行存储的,每个哈希都是一个固定值,这样相对而言IPVS性能更好。
这样看的话是不是用IPVS要更好,iptables没用?并不是这样的,具体得根据业务体量来进行判断,如果您的业务场景仅是内部使用,物理机数量也就几台或者几十台,你在使用iptables和IPVS上感受不到什么区别的,只有当您的物理机数量达到一定量,假定体量很大,物理机数量有5000台,那么用IPVS会感受到一些区别。
命令生成NodePort类型的Service
接下来我们开始了解,如何创建和使用service。
我们可以通过命令行的方式进行创建service服务,以下解析各命令含义:
- expose:表示service服务暴露的命令及含义。
- deployment:选定我们要暴露的服务类型为deployment。
- nginx-dlt:选定我们要暴露的deployment服务中的nginx-dlt服务。
- –type=NodePort:表示我们创建的service类型为NodePort。
- –name=nginx-service: 表示创建的service资源类型名称为nginx-service。
- –port 9000:表示将要创建的ClusterIP的端口为9000。
- –target-port 80:表示流量到达ClusterIP的9000端口后,指定找到我们pod中的80端口服务进行暴露。
1 | [root@k8s-master-01 ~]# kubectl expose deployment nginx-dlt --type=NodePort --name=nginx-service --port 9000 --target-port 80 |
1、创建service完成后,我们可以通过如下命令查看到创建的service信息。
2、同时iptables为我们自动从IP池中获取到了一个clusterIP及端口为:10.96.226.228:9000
3、由于我们创建的service类型是NodePort,同时为我们获取到了外部访问的高端口为:30790
1 | [root@k8s-master-01 ~]# kubectl get service |
我们可以先验证一下clusterIP在集群内是否能访问到这个nginx服务。
1 | [root@k8s-master-01 ~]# curl 10.96.226.228:9000 |
最后我们在使用节点IP加上NodePort获取到的高端口通过外部客户端的浏览器进行访问,访问通过。

使用Yaml的方式生成NodePort类型的Service
前面一直提到,命令行创建的资源建议通常用于临时性的任务,当在企业级的正式环境中,还是建议使用yaml的方式进行创建便于管理维护,接下来我们来应用下前面控制器的知识,结合service创建一个较为完善的服务访问。
Deployment部分就不在过度解析了,主要关注spec下的selector(标签选择器)及template下的labels标签两个保持一致,避免控制器所筛选的标签与pod创建的标签不同,这里主要描述service资源创建部分。
- spec.selector: app: nginx:这里的标签选择器要与Deployment的metadata.labels要一致,避免service无法筛选到需要暴露的Deployment服务。
- spec.type: NodePort :这里表示您要创建的service类型。
- spec.ports.targetPort: 80:这里表示pod服务暴露出来的端口,与Deployment部分的ports一致。
- spec.ports.port: 9000:这里表示自动创建的ClusterIP的端口。
1 | cat > service-nodeport.yaml << EOF |
可以使用如下命令,执行yaml文件,进行资源创建。
1 | [root@k8s-master-01 service]# kubectl create -f service-nodeport.yaml |
验证资源创建情况。
1 | [root@k8s-master-01 service]# kubectl get -f service-nodeport.yaml |
验证CLUSTERIP是否访问成功。
1 | [root@k8s-master-01 service]# curl 10.97.51.154:9000 |
验证NodePort节点IP加高端口访问。

删除资源可以使用如下命令。
1 | [root@k8s-master-01 service]# kubectl delete -f service-nodeport.yaml |
使用Yaml的方式生成ClusterIP类型的Service
同样沿用之前的yaml,只需要稍加改动即可,在创建前,需先将上面创建的NodePort删除,否则会资源名称重复。
这里也是没什么改动,由于kubernetes模式就是ClusterIP类型,所以我们只需要把type删除即可,或者把type的值改为ClusterIP。
1 | cat > service-nodeport.yaml << EOF |
可以使用如下命令,执行yaml文件,进行资源创建。
1 | [root@k8s-master-01 service]# kubectl create -f service-nodeport.yaml |
验证资源创建情况,这里我们可以看到service资源有些变化,type类似变为:ClusterIP,PORT(S)中也少了NodePort的端口。
1 | [root@k8s-master-01 service]# kubectl get -f service-nodeport.yaml |
由于CLUSTERIP只能集群内访问,所以我们只能在K8s集群中的物理机进行验证CLUSTERIP是否访问成功。
1 | [root@k8s-master-01 service]# curl 10.109.116.255:9000 |
那有小伙伴可以要问,这是怎么实现的访问ClusterIP就能找到我对应的Pod后端服务,在这ClusterIP后面其实还有一个endpoints的资源类型,kube-porxy会实时扫描控制节点的变化,如果您的Pod删除了等操作导致IP变动了,kube-porxy会对endpoints记录的PodIP进行更新。
1 | [root@k8s-master-01 service]# kubectl get endpoints |
这里我们删除一个Pod,模拟故障重启的情况,这时我们发现Pod的IP进行了变动。
1 | [root@k8s-master-01 service]# kubectl get pods |
同时我们endpoints的记录IP也进行了秒级更新,所以我们的service能保证任何时候都能找到临时存在的Pod。
1 | [root@k8s-master-01 service]# kubectl get endpoints |
使用Yaml的方式生成Headless类型的Service(无头服务)
什么是Headless?前面我们提到过一下无头服务,这个Headless就是无头服务,下面我们看下如何创建和使用这个Headless。
这里镜像换成了busybox:1.28,因为节约时间这个镜像有需要用到的命令。采用的类型还是默认的ClusterIP,只不过多加了一个参数spec.clusterIP: None,这个表示没有clusterIP。
在创建前,需先将上面创建的NodePort删除,否则会资源名称重复。
1 | cat > service-nodeport.yaml << EOF |
可以使用如下命令,执行yaml文件,进行资源创建。
1 | [root@k8s-master-01 service]# kubectl create -f service-nodeport.yaml |
验证资源创建情况,这里我们可以看到service资源有些变化,type依然是为:ClusterIP没问题。
但是CLUSTER-IP字段缺没了IP,service宣称提供的是持久可靠的统一IP访问入口,但是你都没有IP了,如何保证持久。
1 | [root@k8s-master-01 service]# kubectl get -f service-nodeport.yaml |
这里我们要验证的话需要进入到Pod容器中,我们通过nslookup这个命令扫描我们创建service时的资源名称,可以发现这返回的于之前的截然不同,他将所有的Pod的Ip进行了一个返回以及他的一个地址。
1 | [root@k8s-master-01 service]# kubectl exec -it deployments/del-busybox -- /bin/sh |
前面我们介绍过,headless他是通过DNS去进行记录条目的,所以我们可以通过域名的方式去进行一个访问。
但是访问出来的结果是各个Pod的IP,这就衍生到我可以用headless做些什么了?
我们在使用不管是NodePort还是ClusterIIP,他中间都是带有负载均衡的,由它来为您自动选择这个流量要进去到哪个Pod中,所以headless通常是在您对它这个负载均衡不满意的情况下,并且您拥有一个自己的负载均衡器,想用自己的负载均衡算法去分配流量的时候,您就可以使用headless(无头服务)去满足您个性化的需求。
Ingress
什么是ingress?它是基于上述说的service中优化衍生出来的产品,拥有了它您不在需要考虑什么高端口、什么只能集群内部访问等等,使用一种能感知协议配置的机制来解析 URI、主机名称、路径等 Web 概念, 让你的 HTTP(或 HTTPS)网络服务可被访问。 Ingress 概念允许你通过 Kubernetes API 定义的规则将流量映射到不同后端。
- Ingress 是对集群中服务的外部访问进行管理的 API 对象,典型的访问方式是 HTTP。
- Ingress 可以提供负载均衡、SSL 终结和基于名称的虚拟托管。
它是可以通过同一个公网IP:10.10.10.10及域名解析的方式,将您访问的host(域名)对应的转到相应的Pod服务中去。

这里可以参考下实力,浏览器访问百度,使用F12您可以看到,您每次的访问都会带有一个host,ingress就是通过这个host去分析路由规则,同时每一个service都绑定这唯一的host,当遇到相匹配的host请求,则就转发到相应的service中去。

环境准备
要使用ingress首先需要安装ingress控制器,这里我采用的是ingress-nginx。
ingress-nginx离线下载地址:https://github.com/kubernetes/ingress-nginx

具体安装就不具体演示了,如遇到问题可咨询博主。
最终可使用如下命令,查看是否安装完成,主要是看ingress-nginx-controller。
1 | [root@k8s-master-01 service]# kubectl get pods -A | grep ingress |
使用ingress
1 | cat > backend.yaml << EOF |
可以使用如下命令,执行yaml文件,进行资源创建。
1 | [root@k8s-master-01 service]# kubectl create -f backend.yaml |
创建完成后,需先检查验证ingress分配的IP是多少,并将其加入hosts用于域名解析。
1 | # 两台node节点均已加入。 |
验证正常访问。
1 | [root@k8s-master-01 service]# curl www.liweihu.com |
同时包括在我们的客户端进行访问也是一样的,需要在本机电脑上的hosts文件内同样添加两个节点的域名解析。

即也可以正常外部域名访问。

即使当您使用了不存在的后缀,也同样能锁定到默认的service,这样即便不管您有多少个服务,统一通过统一域名不同后缀的方式进行访问,大大减少的前面繁琐的管理和维护工作。






