文章目录
Service
每个 Pod 都有自己的 IP 地址,但是在 Deployment 中,在同一时刻运行的 Pod 集合可能与稍后运行该应用程序的 Pod 集合不同。
这导致了一个问题: 如果一组 Pod(称为“后端”)为集群内的其他 Pod(称为“前端”)提供功能, 那么前端如何找出并跟踪要连接的 IP 地址,以便前端可以使用提供工作负载的后端部分?
在 K8s 里面,服务发现与负载均衡就是 K8s Service。上图就是在 K8s 里 Service 的架构,K8s Service 向上提供了外部网络以及 pod 网络的访问,即外部网络可以通过 service 去访问,pod 网络也可以通过 K8s Service 去访问。
向下,K8s 对接了另外一组 pod,即可以通过 K8s Service 的方式去负载均衡到一组 pod 上面去,这样相当于解决了前面所说的复发性问题,或者提供了统一的访问入口去做服务发现,然后又可以给外部网络访问,解决不同的 pod 之间的访问,提供统一的访问地址。
创建Service
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
上述配置创建一个名为 “my-service” 的 Service 对象,它会将请求代理到使用 TCP 端口 9376,并具有标签 "app=MyApp"
的 Pod 上。
Kubernetes 为该服务分配一个 IP 地址,该 IP 地址由服务代理使用。
服务选择算符的控制器不断扫描与其选择器匹配的 Pod,然后将所有更新发布到也称为 “my-service” 的 Endpoint 对象。
三种代理模式
userspace 代理模式
这种模式,kube-proxy 会监视 Kubernetes 控制平面对 Service 对象和 Endpoints 对象的添加和移除操作。 对每个 Service,它会在本地 Node 上打开一个端口(随机选择)。 任何连接到“代理端口”的请求,都会被代理到 Service 的后端 Pods
中的某个上面(如 Endpoints
所报告的一样)。 使用哪个后端 Pod,是 kube-proxy 基于 SessionAffinity
来确定的。
最后,它配置 iptables 规则,捕获到达该 Service 的 clusterIP
和 Port
的请求,并重定向到代理端口,代理端口再代理请求到后端Pod。
默认情况下,用户空间模式下的 kube-proxy 通过轮转算法选择后端。
iptables 代理模式
这种模式,kube-proxy
会监视 Kubernetes 控制节点对 Service 对象和 Endpoints 对象的添加和移除。 对每个 Service,它会配置 iptables 规则,从而捕获到达该 Service 的 clusterIP
和端口的请求,进而将请求重定向到 Service 的一组后端中的某个 Pod 上面。 对于每个 Endpoints 对象,它也会配置 iptables 规则,这个规则会选择一个后端组合。
默认的策略是,kube-proxy 在 iptables 模式下随机选择一个后端。
使用 iptables 处理流量具有较低的系统开销,因为流量由 Linux netfilter 处理, 而无需在用户空间和内核空间之间切换。 这种方法也可能更可靠。
如果 kube-proxy 在 iptables 模式下运行,并且所选的第一个 Pod 没有响应, 则连接失败。 这与用户空间模式不同:在用户空间模式下,kube-proxy 将检测到与第一个 Pod 的连接已失败, 并会自动使用其他后端 Pod 重试。
你可以使用 Pod 的 readinessProbe 验证后端 Pod 可以正常工作,以便 iptables 模式下的 kube-proxy 仅看到测试正常的后端。 这样做意味着你避免将流量通过 kube-proxy 发送到已知已失败的 Pod。
IPVS 代理模式
在 ipvs
模式下,kube-proxy 监视 Kubernetes 服务和端点,调用 netlink
接口相应地创建 IPVS 规则, 并定期将 IPVS 规则与 Kubernetes 服务和端点同步。 该控制循环可确保IPVS 状态与所需状态匹配。访问服务时,IPVS 将流量定向到后端Pod之一。
IPVS代理模式基于类似于 iptables 模式的 netfilter 挂钩函数, 但是使用哈希表作为基础数据结构,并且在内核空间中工作。 这意味着,与 iptables 模式下的 kube-proxy 相比,IPVS 模式下的 kube-proxy 重定向通信的延迟要短,并且在同步代理规则时具有更好的性能。 与其他代理模式相比,IPVS 模式还支持更高的网络流量吞吐量。
IPVS 提供了更多选项来平衡后端 Pod 的流量。 这些是:
rr
:轮替(Round-Robin)lc
:最少链接(Least Connection),即打开链接数量最少者优先dh
:目标地址哈希(Destination Hashing)sh
:源地址哈希(Source Hashing)sed
:最短预期延迟(Shortest Expected Delay)nq
:从不排队(Never Queue)
Service类型
Service可以通过.spec.type
指定你所需要的类型,默认是 ClusterIP
。
Type
的取值以及行为如下:
- ClusterIP:通过集群的内部 IP 暴露服务,选择该值时服务只能够在集群内部访问。 这也是默认的 Service类型。
- NodePort:通过每个节点上的 IP 和静态端口(
NodePort
)暴露服务。NodePort
服务会路由到自动创建的ClusterIP
服务。 通过请求<节点 IP>:<节点端口>
,你可以从集群的外部访问一个NodePort
服务。 - LoadBalancer:使用云提供商的负载均衡器向外部暴露服务。 外部负载均衡器可以将流量路由到自动创建的
NodePort
服务和ClusterIP
服务上。 - ExternalName:通过返回
CNAME
和对应值,可以将服务映射到externalName
字段的内容(例如,foo.bar.example.com
)。 无需创建任何类型代理。
Headless Service
有时不需要或不想要负载均衡,以及单独的 Service IP。 遇到这种情况,可以通过指定 Cluster IP(spec.clusterIP
)的值为 "None"
来创建 Headless
Service。
对这无头 Service 并不会分配 Cluster IP,kube-proxy 不会处理它们, 而且平台也不会为它们进行负载均衡和路由。 DNS 如何实现自动配置,依赖于 Service 是否定义了选择算符。
带选择算符的服务
对定义了选择算符的无头服务,Endpoint 控制器在 API 中创建了 Endpoints 记录, 并且修改 DNS 配置返回 A 记录(IP 地址),通过这个地址直接到达 Service
的后端 Pod 上。
无选择算符的服务
对没有定义选择算符的无头服务,Endpoint 控制器不会创建 Endpoints
记录。 然而 DNS 系统会查找和配置,无论是:
- 对于
ExternalName
类型的服务,查找其 CNAME 记录 - 对所有其他类型的服务,查找与 Service 名称相同的任何
Endpoints
的记录
Service DNS
A/AAAA 记录
“普通” 服务(除了无头服务)会以 my-svc.my-namespace.svc.cluster-domain.example
这种名字的形式被分配一个 DNS A 或 AAAA 记录,取决于服务的 IP 协议族。 该名称会解析成对应服务的集群 IP。
“无头(Headless)” 服务(没有Cluster IP)也会以 my-svc.my-namespace.svc.cluster-domain.example
这种名字的形式被指派一个 DNS A 或 AAAA 记录, 具体取决于服务的 IP 协议族。 与普通服务不同,这一记录会被解析成对应服务所选择的 Pod 集合的 IP。 客户端要能够使用这组 IP,或者使用标准的轮转策略从这组 IP 中进行选择。
SRV 记录
Kubernetes 会为命名端口创建 SRV 记录,这些端口是普通服务或 无头服务的一部分。 对每个命名端口,SRV 记录具有 _my-port-name._my-port-protocol.my-svc.my-namespace.svc.cluster-domain.example
这种形式。 对普通服务,该记录会被解析成端口号和域名:my-svc.my-namespace.svc.cluster-domain.example
。 对无头服务,该记录会被解析成多个结果,服务对应的每个后端 Pod 各一个; 其中包含 Pod 端口号和形为 auto-generated-name.my-svc.my-namespace.svc.cluster-domain.example
的域名。