关于 Kubernetes 系列其他文章的传送门
-
《 k8s 中的网络入口 – Service 资源管理 》 您当前所在位置

一、Service 资源¶
为什么需要 Service 资源?¶
- 动态的 Pod 对象会给客户端带来的困扰有:
- Pod 资源对象存在生命周期且不可重现,必要时仅能创建一个新的替代者;
- Pod 对象在其控制器进行应用规模伸缩时,同一应用程序的 Pod 对象会增加或减少;
所以 Service 资源为动态管理的 Pod 对象添加一个固定访问入口的抽象层:
- Service 通过标签选择器关联至拥有相关标签的 Pod 对象;
- 客户端向 Service 进行请求,而非目标 Pod 对象;
每一个 Service 对应于 k8s 实际上来讲,应该是被转换为每一个节点上的 iptables/IPVS。当客户端访问 Service 的地址时,Service 会根据调度算法,从众多的后端 Pod 中挑选一个 Pod 出来,并把对应的 Endpoint 的地址返回给客户端,或者直接把流量代理到 Endpoint 上,因此就能使客户端与 Service 的后端之间进行通信了。
Service 的后端可能是集群内的 Pod,也有可能是集群外的某个具体的服务器提供的服务。如果双方都是在同个节点中的 Pod,之后就会直接使用 Pod IP 进行通信了。
关于 kube-proxy¶
Service 随时有可能会被创建、删除、修改,也就意味着每个节点上的 iptables/IPVS 规则也可能会随时发成变动,这个变动如何及时的反应到每一个节点上呢?所以每个节点上都会运行一个守护进程 kube-proxy,这个进程可能运行为一个系统级进程,也可能运行为一个 Pod,如果是使用 kubeadm 创建的集群,会被创建为一个 Pod:
1 | # kubectl get pods -n kube-system -o wide|grep proxy |
可以看到在每一个节点上都运行了一个 proxy 的 Pod,这是由 ds 类型的控制器所控制,因此每个节点上只能运行 1 个。而且会容忍主节点的污点,所以主节点上也会运行一个。
kube-proxy 其实就是运行在每一个节点之上的守护进程或 Pod,它是 API Server 的客户端,并监听着 API Server 上每一个 Service 资源的变动,一旦 API Server 上某一个 Service 发生变动,会立即通知到监视此资源的所有节点上的 kube-proxy,而后由 kube-proxy 转换为本地的 iptables/IPVS 规则。
自 1.11 版的 k8s 开始默认使用 IPVS 模型,如果在部署 k8s 时,节点上没有装载 IPVS 模块,会自动把 IPVS 模型降级为 iptables 模型。
- IPVS 与 iptables 规则的不同之处仅在于,IPVS 的请求流量的调度功能由 IPVS 实现,余下的其他功能仍由 iptables 来完成;
- 另外,使用 IPVS 可以支持多种调度算法,比如 rr、wrr、lc、dh、sh 等,但是所有节点的所有 Service 都必须使用同一种算法。
定义 Service 资源¶
ports:定义 Service 自己的端口和对端的目标端口;
name:定义一个名字,可省略;port:自己的端口;targetPort:对端的端口;protocol:定义使用哪种协议。默认TCP;
clusterIP:定义 Service 自己的 IP;
selector:使用标签选择器来关联对端是哪个;
示例:定义一个 Service 并关联至 pod¶
当前有 3 个 Pod,标签为 app:nginx:
1 | # kubectl get pods --show-labels |
创建 Service,配置文件如下:
1 | ~]# vim nginx-svc.yaml |
查看刚刚创建的 Service,被自动绑定在对应的 Endpoint 上:
1 | -> # kubectl describe svc nginx |
这个 Endpoint 是自动创建的,查看 Endpoint 中的 IP 地址就是 Pod 的 IP:
1 | # kubectl get endpoints |
查看此 Endpoint 的详细信息,因为 Endpoint 太简单了,所以一个没有 spec 字段的资源,但是有一个 subsets 字段,指定了绑定的 Pod:
1 | -> # kubectl get endpoints nginx -o yaml |
二、Service 的代理类型¶
定义 Service 如何代理到 Pod
ClusterIP (默认)¶
给一个 Service 分配一个当前集群上的,位于 Service 可用网段内的动态地址。这个地址使用私网地址,只在集群内可用。所以如果是 ClusterIP 类型,那么此 Service 就只能被集群内部的客户端所访问。
NodePort¶
对一个 Service 来说,它还是有 NodePort 的,但是会在集群的每一个节点上生成一个 iptables/IPVS 的 NAT 规则,这个规则可以把物理节点的某个端口,比如 3111,映射到 ClusterIP 的端口上。这样外部的客户端,通过去访问任一节点 IP 的 3111 端口,而后会被转发至集群内部的 ClusterIP 的端口上来,从而引入外部流量到 Service,然后 Service 把流量转发至 Pod。
示例:
1 | ~]# vim nginx-svc.yaml |
查看此 svc 的类型已经为 NodePort 了:
1 | # kubectl get svc |
之后就可以在集群外部通过访问任意一个物理节点的 IP 加端口,来访问这个 Pod 了:
1 | # curl 192.168.50.11:30080 |
注意:但是像 30080 这样的非标准端口,客户端是不知道怎么访问的,这时可以在外部使用一个负载均衡器来调度至此端口,且注意高可用。
ExternalName¶
如果某一个 Service 的后端不是集群内的资源时,就要使用此种 Service 类型。
类型为 ExternalName 的 Service 会将 Service 映射到其他的 DNS 名称上,而不是标签选择器:
1 | apiVersion: v1 |
需要注意的是:这个外部 DNS 必须是 CoreDNS 能够解析的。如果是公网的 DNS,需要 CoreDNS 能够访问互联网,能从根域名查询到相关的 DNS 信息才可以正常访问。
三、其他补充¶
没有 selector 的 Service¶
另一种方式是创建 Service 时不定义标签选择器,然后通过手动创建一个和 Service 同名的 Endpoint,这个 Endpoint 则指向外部网络中的地址。
示例:
1 | ~]# vim no-selector-svc.yaml |
由于此服务没有选择器,因此不会自动创建相应的 Endpoint 对象。可以通过手动添加 Endpoint 对象,将 Service 手动映射到运行该 Service 的网络地址和端口:
1 | ~]# vim mumnal-endpoint.yaml |
headless(无头) Service¶
正常情况下,客户端在通过域名访问时,会先从 CoreDNS 中获取到域名对应的 IP 地址,这个 IP 是绑定在 Service 上的,然后由这个 Service 代理至后端的 Endpoint 绑定的 Pod 上。
但如果创建 Service 时,指定不使用 IP,那么这样的 Service 就叫 无头 Service(headless Service),定义方式:
1 | ~]# vim headless-svc.yaml |
当客户端访问这样的无头 Service 时,因为 Service 上没有 IP,那么 CoreDNS 就会直接把域名绑定到 Pod 的 IP 上。
Service 会话粘性¶
Service 也支持会话粘性的特性,可以支持把同一组客户端 IP 地址的请求始终绑定到同一个后端的 Pod 上,这在定义 Service 时使用一个属性即可:
sessionAffinity:定义会话粘性的属性:none:默认没有会话粘性;ClientIP:使用 IPVS 中的 HS 调度算法;
启用 IPVS 类型的 Service¶
先加载 IPVS 模块¶
注意:在每个节点执行以下操作
创建内核模块载入相关的脚本文件 /etc/sysconfig/modules/ipvs.modules,设定开机自动载入的内核模块。文件内容如下:
1 | !/bin/bash |
修改文件权限,并手动为当前系统加载内核模块:
1 | chmod +x /etc/sysconfig/modules/ipvs.modules |
修改 kube-proxy 的配置文件¶
如果要启用 IPVS 类型的 Service,接下来要去改 kube-proxy 的 Pod 配置规则:
使用 kubectl get cm -n kube-system 可以查看到集中式的配置文件。集中式配置文件的特定是:例如修改这里边的 kube-proxy 的配置文件后,所有 kube-proxy 的 Pod 容器都会发生改变。
使用 kubectl edit cm kube-proxy -n kube-system 编辑 kube-proxy 的配置文件:
1 | mode: "" # 把这一行配置 |
因为此前 kube-proxy 已经创建并运行,那么现在根据环境的不同,可能需要等待其重启或者重建后配置才会生效。
如果一直没有生效,可能需要手动将 Pod 删除,等待其重建,不过生产环境不建议这么做,最好从一开始就定义好使用 ipvs 而不是 iptables:
1 | 定义删除标签是 k8s-app=kube-proxy 的 Pod |
之后使用 ipvsadm -Ln 可以看到配置就代表 ipvs 的配置生效了。
定义调度规则¶
使用 kubectl edit cm kube-proxy -n kube-system 修改配置文件
1 | schedeler: "" # 把这一行配置 |