Kubernetes服务发布基础
Service 的定义
service 是 kubernetes 中的一种抽象,用于定义一组 pod 以及访问这一组 pod 的策略、service
的作用是将一组 pod 封装为一个虚拟的服务,并提供一个统一的入口,供客户端访问。service 支持负载
均衡、服务发现、服务暴露等功能。
Service 用于为一组提供服务的 Pod 抽象一个稳定的网络访问地址,是 k8s 实现微服务的核心概念。
通过 Service 的定义设置的访问地址是 DNS 域名格式的服务名称,对于客户端应用来说,网络访问方式
并没有改变。Service 还提供了负载均衡器的功能,将客户端请求负载分发到后端提供具体服务的各个
Pod 上。
Service 主要用于提供网络服务,通过 Service 的定义,能够为客户端应用提供稳定的访问地址(域
名或 IP 地址)和负载均衡功能,以及屏蔽后端 EndPoint 的变化,是 Kubernetes 实现微服务的核心资
源。
总之,service 是 kubernetes 中一个非常重要的概念,service 用于将外部请求代理到内部 pod
上,提供 4 层负载均衡和服务发现的功能,使得我们可以构建高可用和可扩展的应用程序。
Service 工作原理
service 基本原理介绍
在 kubernetes 中,pod 的 IP 地址是动态变化的,因此无法直接通过 pod 的 IP 地址进行访问。service
的出现正是为了解决这个问题的。service 会为一组 pod 创建一个虚拟的 IP 地址,通过这个 IP 地址可
以访问这组 pod 中的任意一个 pod。当客户端请求这个虚拟 IP 地址时,请求会被负载均衡到一组 pod 中
的某一个 pod 上,从而完成对 pod 的访问。
service 的实现依赖于 kube-proxy 组件。kube-proxy 会在每个节点上监听 service 的变化,一旦
有 service 发生变化,kube-proxy 会更新本地的 iptables 规则,从而实现流量的转发和负载均衡。
另外,service 还与 CoreDNS 有关。CoreDNS 是 kubernetes 集群中的 DNS 解析服务。在 kubernetes
中 service 的虚拟 IP 地址还会注册到 CoreDNS 中,从而使得客户端还可以通过 service 名称访问
service 的虚拟 IP 地址。在 service 的定义中,可以通过 spec.selector 字段指定哪些 pod 属于这个
service,这样,就可以将请求负载均衡到这些 pod 中。
总之,service 是 kubernetes 中一种非常重要的资源对象,它可以让 pod 对外提供服务,并提供负
载均衡、服务发现等功能。service 的实现依赖于 kube-proxy 和 CoreDNS 组件,他们共同协作,将 service
与 pod 连接起来,实现对 pod 的代理访问,如下图所示。
Service 的负载均衡机制
当一个 Service 对象在 Kubernetes 集群中被定义出来时,集群内的客户端应用就可以通过服务 IP
访问到具体的 Pod 容器提供的服务了。从服务 IP 到后端 Pod 的负载均衡机制,则是由每个 node 上的kube-proxy 代理来负责实现的。
kubeproxy 的代理模式有:userspace、iptables、ipvs 和 kernelspace。
(1) userspace
起初,kube-proxy 进程是一个真实的 TCP/UDP 代理,当某个 pod 以 clusterIP 方式访问某个 service
的时候,这个流量会被 pod 所在的本机的 iptables 转发到本机的 kube-proxy 进程,然后将请求转发到
后端某个 pod 上。具体过程为:
- kube-proxy 为每个 service 在 node 上打开一个随机端口作为代理端口
- 建立 iptables 规则,将 clusterip 的请求重定向到代理端口(用户空间)
- 到达代理端口的请求再由 kubeproxy 转发到后端
clusterip 重定向到 kube-proxy 服务的过程存在内核态到用户态的切换,开销很大,因此有了 iptables 模式,而 userspace 模式也被废弃了。
(2) iptabels
kubernets 从 1.2 版本开始将 iptabels 模式作为默认模式,这种模式下 kube-proxy 不再起到 proxy
的作用。其核心功能:通过 API Server 的 Watch 接口实时跟踪 Service 和 Endpoint 的变更信息,并
更新对应的 iptables 规则,Client 的请求流量通过 iptables 的 NAT 机制 “直接路由” 到目标 Pod。
不同于 userspace,在 iptables 的模式中,kube-proxy 不再负责转发数据包,kube-proxy 主要
完成对 iptables 策略的动态管理。数据包的走向完全由 iptables 规则决定,这样的过程不存在内核态
到用户态的切换,效率明显会高很多。但是随着 service 的增加,iptables 规则会不断增加,导致内核
十分繁忙(等于在读一张很大的没建索引的表)。
(3) ipvs
从 kubernetes 1.8 版本开始引入第三代的 IPVS 模式,它也是基于 netfilter 实现的,但定位不同:
iptables 是为防火墙设计的,IPVS 则专门用于高性能负载均衡,并使用高效的数据结构 Hash 表,允许
几乎无限的规模扩张。
ipvs 为负载均衡提供了更多的算法:
- rr:轮训
- lc:最小连接数
- df:目标哈希
- sh:源哈希
- sed:预计延迟最短
- nq:从不排队
一句话说明:ipvs 使用 ipset 存储 iptables 规则,在查找时类似 hash 表查找,时间复杂度为 O (1),而 iptables 时间复杂度则为 O (n)。时间复杂度用字母 O 表示。
可以将 ipset 简单理解为 ip 集合,这个集合的内容可以是 IP 地址、IP 网段、端口等,iptabels
可以直接添加规则对这个可变集合进行操作,这样做的好处可以大大减少 iptables 规则的数量,从而减少性能损耗。
如果操作系统没有启用 IPVS 内核模块,kube-proxy 会自动运行为 iptables 模式,如果操作系统启
用了 ipvs 模块,则 kube-proxy 会运行为 ipvs 模式。查看系统是否开启了 ipvs 模块,可以使用命令:
lsmod | grep ip_vs
(4) kernelspace
Windows Server 上的代理模式,此处不作介绍。
service 的 4 种类型
kubernetes 支持 4 中 service 类型。
(1) ClusterIP
这是最常用的 Service 类型,它为 Pod 提供了一个虚拟的 IP 地址。当其他 Pod 需要访问该 Service
时,它们只需要使用该虚拟 IP 地址即可。Kubernetes 会自动将请求路由到相应的 Pod 上
(2) NodePort
这种 Service 类型将 Pod 公开为集群中所有节点上的某个端口。当外部请求到达任何一个节点上的
该端口时,Kubernetes 会将请求路由到相应的 Pod 上。
(3) LoadBalancer
LoadBalancer Service:这种 Service 类型使用云提供商的负载均衡器将请求路由到后端 Pod。
Kubernetes 会自动创建和配置负载均衡器,并将其绑定到 Service 上。
(4) externalName
ExternalName Service:这种 Service 类型允许你将 Service 映射到集群外部的某个名称。当 Pod
需要访问该 Service 时,它们将使用该名称来解析出相应的 IP 地址。
生成用于测试 service 的 Deployment
编写 Deployment,用于各种 service 的验证。
在应用 Service 概念之前,先创建一个提供 web 服务的 Pod 集合,有两个 Tomcat 容器副本组成,
每个容器提供的服务端口都为 8080。
编辑 deployment
selector(选择器)主要用于资源的匹配,只有符合条件的资源才会被调用或使用,可以使用该方式对集群中的各类资源进行分配。Kubernetes 和核心资源 Deployment、StatefulSet 管理的 Pod 是通过选择器字段决定的,通过 Service 访问这些后端 Pod 也是通过选择器字段决定的。
label(标签)可以对 Kubernetes 的其他资源进行配置,当 Kubernetes 对系统的任何 API 对象如 Pod 进行分组时,会对其添加标签,用以精准的选择对应的 API 对象。
创建该 Deployment
查看 pod 创建情况
查看各个 pod 的 IP 地址
访问这两个地址
service 的创建
创建一个 ClusterIP 类型 service
描述:ClusterIP 是默认的 Service 类型。它将创建一个虚拟的 ClusterIP 地址,用于在集群内部访问 Service。
使用场景:适用于集群内部的服务通信,例如将前端服务和后端服务连接起来,供内部其他服务使用。
方法一:通过 expose 命令创建 ClusterIP 类型的 Service
为了让客户端应用能够访问到前面创建的两个 Tomcat Pod 实例,需要创建一个 Service 来提供服务。
k8s 提供了一种快速地方法,即通过 kubectl expose 命令来创建 Service。
(1) 执行 expose 命令暴露端口
(2) 查看创建的 Service
这里可以看到,系统为他分配了一个虚拟 IP 地址,Service 的端口号,从 pod 中复制而来。我们就可以通过这个虚拟 IP 地址,和端口号来访问这个 Service 了。
(3) 访问测试
端应用获知后端服务实例列表和变化的复杂度。
(4) 删除此 Service
方法二:使用 yaml 文件创建 service
除了使用 expose 的命令创建 Service,更便于管理的方式是 yaml 文件来创建 Service。
(1) 编辑价 service 文件
本案例中的 ports 定义的是 Service 本身的端口号 8080(用于提供给外部用户访问的端口),targetPort 则用来指定后端 Pod 的容器端口号,selector 定义的是后端 Pod 所拥有的 label。
(2) 利用 yaml 创建此服务
(3) 查看创建的 Service,并访问测试
(4) 查看 Endpoint 列表
一个 Service 对应的后端由 Pod 的 IP 地址何容器的端口号组成,即一个完成的 IP:Port 访问地址,这在 k8s 中叫做 EndPoint。通过查看 Service 的详细信息,可以看到其后端的 EndPoint 列表
(5) 查看 Endpoint 资源对象
实际上,k8s 自动创建了与 Service 关联的 EndPoint 资源对象,这可以通过 EndPoint 对象进行查看
(6) 删除这个 Service
或
创建 NodePort 类型的 service
NodePort 将在每个节点上公开一个端口,并将流量转发到 Service。它会创建一个 ClusterIP,并将指定的端口映射到每个节点上的相同端口。
这种 service 适用于需要从外部访问集群中的服务时,可以通过节点的 IP 地址和映射的端口进行访问。这对于开发和测试环境非常有用。
(1) 创建 Service 文件
设置 Service 类型为 NodePort,并设置具体的 nodePort 端口号为 30008
各关键字的含义如下:
- port: 8080:这是 Service 的内部端口,即在 Service 内部暴露的端口。客户端请求会发送到这个端口,并由 Kubernetes 负载均衡到后端的 Pods 上。
- targetPort: 8080:这是指向后端 Pods 上的实际容器端口。当流量通过 Service 到达后端 Pods 时,将被转发到这个端口。
- nodePort: 30008:这是在每个节点上打开的端口,用于外部流量进入集群。外部客户端可以通过
<node-ip>:<node-port>
访问该 Service。
nodePort 端口的范围在 1.23.6 的版本中为 30000-32767
(2) 创建此 Service
继续使用刚才的 webapp 的 Deployment,为此 Deployment 创建一个 NodePort 类型的 service。
(3) 查看创建的 Service
(4) 在 windows 宿主机上用浏览器测试访问
http://192.168.10.101:30008
因为 NodePort 的方式可以在每个 Node 节点创建出这个端口,所以在任何一个节点使用 netstat -anpt | grep 30008
命令查询,都可以看到此端口,在访问的时候,IP 地址可以使用任何一个主节点的 IP 地址。
(5) 删除该 Service
创建 LoadBalancer 类型的 service
通常在公有云的环境中会使用 LoadBalancer 的类型,可以将 Service 映射到公有云提供的某个负载均衡器的 IP 地址上,客户端通过负载均衡器的 IP 和 Service 的端口号就可以访问到具体的服务。
描述:LoadBalancer 为 Service 创建一个外部负载均衡器,并分配一个外部 IP 地址。它通常由云提供商的负载均衡服务实现。
使用场景:适用于需要将流量从外部负载均衡器分发到集群内部的服务,例如在生产环境中暴露 Web 应用程序。
(1) 编写 LoadBalancer 类型的 Service 文件
这个 Service 创建好后,云服务商在 Service 定义中补充 LoadBalancer 的 IP 地址,之后就可以在客户端访问该 Service 了。nodePort 的端口范围在 30000-32767 之间。
(2) 创建此 Service
(3) 查看创建结果
(4) 在 windows 宿主机上访问
(5) 删除此 Service
创建 ExternalName 类型的 service
ExternalName 允许 Service 通过返回 CNAME 记录来引用集群外部的服务。它没有 ClusterIP,NodePort 或 LoadBalancer。
适用于需要将 K8s 内部的服务与集群外的现有服务进行关联,例如连接到外部的数据库或其他资源。
ExternalName 类型是 Service 的特例,用于将集群外的服务定义为 Kubernetes 的集群 Service,并通过 ExternalName 字段指定外部服务的地址,可以使用域名或 IP 格式。集群内客户端应用通过访问这个 Service 就能访问外部服务了。他没有选择器,没有定义任何端口和 EndPoint,他通过返回该外部服务的别名来提供服务。
也可以用于两个不同的 namespace 之间的不同 pod 可以通过 name 的形式访问。
两个不同的命名空间中的 pod,可以直接使用他们的 IP 地址进行通信,但是 pod 的 IP 地址是随机分配的,重建 pod 后其 IP 地址就改变了,因此我们要用 pod 对应的 service 名称进行通信。但是跨命名空间是不能解析其他命名空间中创建的 service 名称的。这个时候就可以使用 ExternalName 实现两个不同命名空间中的各个 pod 间的通信。接下来,我们以两个命名空间内 Pod 之间的访问为例,讲解 ExternalName 的使用方法。
(1) 创建案例所需的两个命名空间
(2)第一个pod
1、创建命名空间为test01的pod
2、创建一个无头(headless)Service
(Headless Service) 无头 Service:这种服务没有入口地址(没有 ClusterIP),kube-proxy 也不会为其创建负载转发的规则,服务名的解析机制取决于这个无头 Service 是否设置了标签选择器。
无头服务创建出来之后,对于其他外部的 pod 来讲,该 pod 对应的名字就确定了下来,其固定的格式是:
无头服务名。命名空间.svc.cluster.local
3、创建extername
关键字段的含义:
- name: myapp-svcname02:指的是外部的服务名称
- myapp-svc02.test02.svc.cluster.local:对方无头服务的名字。对方命名空间的名字.svc.cluster.local
(3)创建第二个pod
1、创建命名空间trst02的pod
2、创建一个无头(headless)Service
3、创建extername
备注:
name: myapp-svcname01 指的是外部的服务名称
(4)验证pod间的通信
查看命名空间为test01的pod
查看命名空间为test02的pod
service 的其他应用
Service 的多端口设置
一个容器应用可以提供多个端口的服务,在 Service 的定义中也可以相应的设置多个端口号。
(1) 创建 service 文件
注意:
selector 中的 app:webapp 的标签对应的是 webapp-deployment.yaml 中的 pod 的标签
(2) 创建 service
(3) 查看 Endpoint 列表
(4) 查看创建的 Service
(5) 删除这个 Service
或
Kubernetes 服务发现
服务发现机制是指客户端如何在一个 kubernetes 集群中获知后端服务的访问地址。
Kubernetes 的服务发现有两种方式:环境变量和 DNS
(1) 基于环境变量的服务发现
当 Pod 部署到一个 node 节点后,该 node 节点上的 kubelet 会在该 pod 内部设置一组环境变量,这些环境变量是根据活跃的 Service 生成的,所以,要使用基于环境变量的服务发现,需要先创建对应的 Service 后再创建所需的 Pod。在一个 Pod 运行起来的时候,系统会自动为其容器运行环境注入所有集群中有效 Service 的信息,Service 的相关信息包括服务 IP、服务端口号、各端口号相关的协议等。然后,客户端应用就能够根据 Service 相关环境变量的命名规则,从环境变量中获取需要访问的目标服务(Pod)的地址了。
先创建出之前用的 service
任意的 Pod 都会根据这些 Service 创建一组变量用于服务发现。
(2) 基于 DNS 的服务发现
Kubernetes 进行服务发现的另一种方式是基于 Kubernetes 内部的 DNS 记录,新版的 Kubernetes 默认使用 CoreDNS 为集群的内部 DNS,一般 CoreDNS 的 Service 地址为 Service 网段的低 10 个地址,比如 10.96.0.10,端口为 53,集群内的 Pod 可以通过该地址和端口进行 Kubernetes 内部的服务解析。DNS 服务器监听 Kubernetes 创建的 Service,然后给每个 Service 添加一组 DNS 记录,集群中的 Pod 就能通过 Kubernetes 内部的 DNS 解析到 Service 的 IP,也就是 Service 的 ClusterIP。
例如:创建一个含有 nslookup 命令的容器,测试解析 kube-dns 的 Service。