关于 Kubernetes 下的服务发布
与传统网络比,Kubernetes 下的网络设计高度模块化,且分散,大体来说容器环境中的网络分下面几部分:
- 容器网络置备:为 Pod 分配 IP 地址,并连接到 Bridge 中,与其他位置互联互通;
- 东西向连接和访问控制:控制不同 Pod 间的连接和访问控制,包含主机内不同 Pod 间和跨主机 Pod 间;
- 集群内容器到 Service 的访问:Kubernetes 有种 ClusterIP 类型的 Service,它是 Kubernetes 中非常重要的对象,等同于传统环境下的 DNS 解析+负载均衡,集群内服务间的调用通常使用该 Service 来完成;
- 容器出向访问:Pod 访问 Kubernetes 集群外的服务;
- 容器入向访问:外部访问 Pod 提供的服务。
本文重点关注容器入向访问。
关于 Kubernetes 的 Service
Kubernetes 原生的资源对象中有一种 API 对象叫 Service,Service 类似于传统架构中的负载均衡服务,一般包含下列几个部分:
- 虚拟 IP:供用户(调用端)访问的 IP;
- 后端池:最终提供服务的节点集合,在 Kubernetes 下称作 Endpoints;
- 域名:虚拟 IP 对应的域名,在 Kubernetes 下名称是必选的,一般称为 Service Name。
Service 的类型很多,按照 Kubernetes 的官方文档,概述如下:
- ClusterIP:默认的 Service 类型,该 Service 仅供集群内 Pod 访问。每个服务发布后会有一个固定的 ClusterIP,其他 Pod 可以通过 Service 名称或者 ClusterIP 来访问到服务,其中 Service 名称到 ClusterIP 的域名解析通常由 CoreDNS 来完成;
- NodePort:将 Service 映射到 Kubernetes Worker 节点的某个端口上,对外提供服务。每个 NodePort 服务实际上也会创建 ClusterIP,所以实际上集群内的 Pod 也可以访问此服务;
- LoadBalancer:借助外部负载均衡器来实现 L4 服务的对外发布,Kubernetes 本身只提供接口,具体功能由第三方组件实现;
- externalName:将本 Service 映射到外部域名,通过 DNS CNAME 记录形式实现。
除此之外,Kubernetes 还有一种特殊的 Service 叫 Headless Service,其工作原理是不设置 ClusterIP(clusterIP 设置为 None),当用户访问该 Service 时,CoreDNS 直接返回 Pod 的 IP 地址。某些状态化服务例如 kafka 会使用这类 Service。
对外服务发布的几种方式
上一章节提到 Service Type NodePort 及 LoadBalancer 可以用于将 Kubernetes 服务发布到外部,除此之外还有一种方式叫 Ingress,我们将这三种常见的对外发布方式做一比较:
Service type NodePort
NodePort 的工作原理是,每发布一个 NodePort 服务,Kubernetes 会自动分配一个高位端口(比如 31432),每个 Worker 监听此端口,当接到外部的访问请求时,将流量通过 IPtables/IPVS 转发给相关的 Pod。
这是 Kubernetes 最简单的一种对外发布服务方式,一般集群装好 CNI 即可使用,但伴随着简单,其缺点就是功能过于单一:
- 无高可用性:在 NodePort 下每个 Worker 节点均是独立的流量入口,Worker 之间不会提供高可用,一旦某个节点故障,需要用户自行修改访问 IP。这个缺点使得 NodePort 只适合于不在乎 SLA 的非生产环境中使用;
- 无主动监控:不会像传统负载均衡器一样主动监控 Pod 运行状态,被动依赖 Kubernetes Pod Ready 状态来做故障切换。另外当出现 Worker 故障时,系统只能被动等待 Worker 连接状态超时后将相关 Pod 从服务池中踢出,这可能会造成分钟级别的服务降级;
- 端口数量限制:每个 Worker 节点的可用端口数量有限,这使得集群最大 NodePort 数量受限;
- 流量不优化:在 NodePort 下,访问并不会以最短的路径进入 Pod,存在流量跨节点横向穿越的问题,造成访问延迟增高;
- 无持久性:没有策略保证来自于同一个用户的请求在一段时间内固定发给某个 Pod;
- 负载均衡策略缺失:与传统负载均衡比,基本只提供 Round Robin 这一种负载均衡策略,难以满足应用的需求。
以上缺点中,高可用问题可以借助外部负载均衡器来解决,但其他问题无太好的办法。
Service type LoadBalancer
前面提到这类 Service Kubernetes 仅提供接口,具体实现要依靠第三方组件。开源社区里这类方案并不多,更多都是由企业级 ADC 或者 Public Cloud 的云负载来实现,VMware Avi 就是支持这类 Service 的一个企业级 ADC 产品。
Loadbalancer 的实现方面各个厂家的方案不尽一致,笔者尝试从大类来做下对比:
控制面实现:
- External Cloud Provider 直接与 Kubernetes 集群对接,监听 Kubernetes 资源对象变更事件:这种方式要求 Cloud Provider 本身支持与 Kubernetes 环境对接,兼容性和灵活性上略差;
- 使用 Operator 在集群内监听 Kubernetes 事件:Kubernetes 原生建议的对接方式,集群内 Operator 可以理解为 Kubernetes 与外部负载均衡器的桥梁,可以做 API 转换等工作,灵活性很强。
数据面对比:
- 在集群内部署 Pod 实现负载均衡服务:将 LB 数据面直接放在 Pod 中运行,这样的好处是 LB Pod 到业务 Pod 流量路径短,理论上也支持 ClusterIP 类型服务的实现。但缺点是复杂,需要考虑 Kubernetes 兼容性、网络如何暴露、资源争夺等诸多问题;
- 外部负载均衡器+NodePort:一种云厂商使用较多的方式,这种实现可以理解为 NodePort 的优化,但继承了 NodePort 的诸多缺点;
- 外部负载均衡器直连 Pod:外部负载均衡器直接通过 Pod IP 访问 Pod,中间不会经过 NodePort,这种实现性能更优,而且流量路径更简化,便于排错,Avi 通常使用这种方式。
Ingress
Ingress 用于将 HTTP/HTTPS 服务暴露到集群外部,可以认为是 Kubernetes 集群内的 L7 Service。在 Kubernetes 中 Ingress 也由外部组件实现,统称为 Ingress Controller。
相比 Service,Ingress 支持的访问规则则要多很多,可以基于域名+Path 进行请求负载,下面是一个基础的 Ingress 配置示例:
在配置层面,Ingress 对象需要挂在 Service (类型为 ClusterIP)前面,在数据层面,Ingress Controller 可以将请求发给 Pod,不经过 Service。
通常社区开源方案中,Ingress Controller 会以 Pod 形式部署在集群内,然后对外通过 NodePort 或者 Host-Port 发布,为了保证跨节点高可用性,在外部需要再加一个四层负载均衡器。
这种架构中使用了 NodePort,因此又继承了之前提到的 NodePort 的诸多缺点,那有没有更简化的方式呢?
答案就是 VMware 的 Avi:在 Avi 架构下,可以通过一套平台同时实现 LoadBalancer 服务和 Ingress 服务,且数据面都做到“极简”,不依赖 NodePort 等组件,实现 LB 一跳直达 Pod:
除了数据平面的简化,Avi 也支持常见的负载均衡算法、持久性、转发策略、安全策略等,为容器环境提供与传统环境等同的 ADC 功能。
通过 Avi 发布 Loadbalancer 类型的 Service
接着我们来讲下如何通过 Avi(NSX Advanced Loadbalancer)来实现。
写着写着就成了科普,真想吐槽下 Kubernetes 的命名和大小写,真的一言难尽。