k8s有哪些负载均衡方案?优缺点有哪些??
12 个回答
NodePort、LoadBalancer、Ingress controller(Ingress 控制器) ……,Kubernetes 组件简直令人眼花缭乱。
当我们与客户和社区讨论生产级 Kubernetes 部署时,他们经常会问的一个问题是:我需要 Ingress controller 吗?这个问题不能简单地用“是”或“否”来回答,我们要先了解将流量路由到 pod 的几种不同方式。本文介绍了 Kubernetes 网络的基础知识,可帮助您就是否以及何时需要 Ingress controller 做出明智的决策。
Kubernetes 提供了多种方法和层级用于将外部流量路由到 pod —— 但它们各有不同。默认的模型是 kube-proxy ,不过它既不是代理,也不是为实施流量负载均衡、控制 API 或监控 service 行为而设计。
幸运的是,我们还可以使用其他方法来管理外部流量。但在展开讨论之前,我们先来快速回顾一下 Kubernetes 组件:
- Kubernetes 部署由 节点(node) 组成,这些节点或为物理机或为虚拟机。
- 节点相互连接构成一个 集群(cluster) 。
- 每个集群都会管理 pod 。从 Kubernetes 网络和基础架构级别来看,pod 是可部署的最小计算单元。一个或多个 pod 可构成一个 service。
- 每个 pod 内部都有一个或多个容器(取决于应用的大小)。
Kubernetes 负责监测构成 service 的 pod,并根据需要对其进行扩展以满足应用的需求。但是如何将流量路由到 pod 呢?这就要用到两种类型的 Kubernetes 对象:service 和 Ingress controller。
什么是 Kubernetes Service?
根据 Kubernetes 文档 ,一个 service 是“用于暴露运行应用的一组 pod的一种抽象方式”。service 连接着一个集群或一个容器网络中的所有 pod,这使得 pod 在任意节点都不会有影响。也就是说即使它们的位置发生变化,甚至被销毁或重启,外部流量也可以被路由到特定的 pod。可以说 service 就像一个具有最基本功能的反向代理。
Kubernetes 中有多种类型的 service,而service 对象的类型与将外部流量路由到 Kubernetes 相关。不同类型的 service 对象经常被混淆,但实际上它们的功能大不相同,因此我们有必要回顾一下它们的功能、用途和缺点。
ClusterIP
ClusterIP 是默认的 service,它在 Kubernetes 内提供了集群内其他 service 可以访问的 service。ClusterIP 不支持从集群外部访问。暴露 ClusterIP service 的唯一方法是使用 kube-proxy 之类的模型,但有必要这样做的场景不多。少数几个这样的情况包括访问笔记本电脑上的 service、调试 service 或查看一些监控和指标。
NodePort
NodePort service 会在集群中的每个节点上打开一个特定端口,并将发送到该端口节点的任何流量转发到相应的应用。这是将流量路由到应用的一个非常基本的方法,但在实际的流量管理用例中,这种方法存在许多局限性。比如每个 NodePort 只能对应一个 service,并且只能使用 30000 到 32767 范围的端口。2768 个端口虽然听起来很多,但大规模运行 Kubernetes 的企业很快就能用完。此外,NodePort 使用四层路由规则和 Linux iptables 实用程序,七层应用路由受限。
除了路由限制之外,使用 NodePort 还有三大缺点:
- 下游客户端必须知道节点的 IP 地址才能与其连接 —— 如果节点的 IP 地址或虚拟机主机发生变化,则无法建立连接。
- NodePort 无法将流量转发到多个 IP 地址。
- 如下图所示,NodePort 没有在 Kubernetes 集群中提供负载均衡,因此流量会被随机分发给各个 service。这可能会导致 service 过载和端口耗尽。
Exposing Services with NodePort | 使用 NodePort 暴露 service |
Client
Request to http:// pine.color.com on port 30001 |
客户端
向 http:// pine.color.com 的 30001 端口发送请求 |
DNS:port | DNS:端口 |
1 The client is programmed with the service IP address and the DNS name and port. | 1 客户端使用右侧图中 service 的 IP 地址以及 DNS 名称和端口进行编程。 |
2 DNS can round robin load balance traffic to nodes. | 2 对去向节点的流量,DNS 能够以轮询的方式实施负载均衡。 |
3 Load balancing within Kubemetes is not possible so traffic is distributed randomly, causing service overload and port exhaustion. | 3在 Kubernetes 内部是无法进行负载均衡的,因此流量会被随机分发,而这可能会导致 service 过载和端口耗尽。 |
Kubernetes cluster | Kubernetes 集群 |
Node 1 | 节点 1 |
NodePort Service
Pine port: 30001 Lagoon port: 30002 Deep Lake port: 30003 |
NodePort Service
Pine 端口:30001 Lagoon 端口:30002 Deep Lake 端口:30003 |
Service: Pine
Service: Lagoon Service: Deep Lake |
Service:Pine
Service:Lagoon Service:Deep Lake |
Node 2 | 节点 2 |
Node 3 | 节点 3 |
LoadBalancer
LoadBalancer service 能够接受外部流量,但需要使用外部负载均衡器作为该流量的接口。在外部负载均衡器经过正确调试和重新配置,可映射到正在运行的 pod的前提下,LoadBalancer service 支持七层路由(即路由流量到 pod 的 IP 地址)。LoadBalancer 是最受欢迎的对外暴露 service 的方式之一。它在云平台中的使用最为广泛,对于小型静态部署环境来说是不错的选择。
如果您使用的是托管 Kubernetes service,那么您会自动获得云提供商选择的负载均衡器。您暴露的每个 service 都有自己的公共 IP 地址来转发所有流量,但这些流量并没有经过任何过滤或路由,这意味着您可以发送几乎任何类型的流量(HTTP、TCP/UDP、WebSocket 等)。
如果您不想使用云提供商的工具(例如,如果您需要功能更强大或独立于平台的工具),那么您可以换成类似于 F5 BIG-IP (作为外部负载均衡器)和 F5 Container Ingress Services (作为执行 LoadBalancer 功能的 operator)这样的工具。有关此模式的进一步讨论,请参阅我们的博文 《在同一架构中部署 BIG-IP 和 NGINX Ingress Controller》 。
在动态多变的环境中,应用的 pod 需要通过扩展来满足不断变化的需求。在这种情况下使用 LoadBalancer 暴露应用就变得有挑战性了。由于每个 service 都有自己的 IP 地址,一个热门应用可能需要管理数百甚至数千个 IP 地址。在大多数情况下,外部负载均衡器可通过 NodePort 连接到 service(如下图所示)——虽然这可以保证流量均匀地分发到各个节点上,但仍然无法对 service 进行负载均衡,因此仍然会出现 service 过载和端口耗尽的问题。
Exposing Services with a Load Balancer and NodePort | 使用负载均衡器和 NodePort 暴露 service |
Client
Request to http:// pine.color.com on port 80 |
客户端
向 http:// pine.color.com 的 80 端口发送请求 |
Load Balancer port 80 | 负载均衡器 80 端口 |
1 The client is programmed with the service IP address and the load balancer port. | 1 客户端使用右侧图中 service 的 IP 地址和负载均衡器端口进行编程。 |
2 The load balancer guarantees traffic distributes across nodes. | 2 负载均衡器保证流量分发到各个节点。 |
3 Load balancing within Kubemetes is not possible so traffic is distributed randomly, causing service overload and port exhaustion. | 3 在 Kubernetes 内部进行负载均衡是无法实现的,因此流量会被随机分发,导致 service 过载和端口耗尽。 |
Kubernetes cluster | Kubernetes 集群 |
Node 1 | 节点 1 |
NodePort Service
Pine port: 30001 Lagoon port: 30002 Deep Lake port: 30003 Service: Pine Service: Lagoon Service: Deep Lake |
NodePort Service
Pine 端口:30001 Lagoon 端口:30002 Deep Lake 端口:30003 Service:Pine Service:Lagoon Service:Deep Lake |
Node 2 | 节点 2 |
Node 3 | 节点 3 |
什么是 Kubernetes Ingress Controller?
根据 Kubernetes 文档 ,“控制器 (controller) 是监控集群状态的控制回路,能够在需要时做出更改或请求做出更改。每个控制器都会努力让当前集群状态接近所期望的状态。”控制器用于管理 Kubernetes 中许多种任务的状态,包括正确分配资源、指定持久化存储和管理 cron 作业。
在路由的上下文中, Ingress controller 能够消除 NodePort 和 LoadBalancer 的局限性。
针对特定 service 的 pod,Ingress controller 用于配置和管理其外部交互。Ingress controller 将动态七层路由视为“一等公民”。这意味着 Ingress controller 能够更轻松地提供更细粒度的控制和管理。借助 Ingress controller,您既可以轻松地控制入向流量,也可以提供 service 级的性能指标,并将其作为安全策略的一部分。Ingress controller 还具有传统外部负载均衡器的许多特性,例如 TLS 终止、处理多个域和命名空间,当然还有负载均衡流量。Ingress controller 可以按请求而不是按 service 对流量进行负载均衡,因此能够支持您更有效地监看七层流量并且更好地实施 SLA。
Ingress controller 还有一个优势!它还可以执行出向规则,这些规则可以做到只允许来自某些 pod 的流量传输到特定的外部 service,或者确保使用 mTLS 对流量进行相互加密。mTLS 加密对于在医疗、金融、电信和政府等行业提供受监管的服务至关重要,这也是端到端加密 (E2EE) 策略的关键组成部分。控制来自同一工具的出站流量还能够简化业务逻辑在 service的应用。当入向和出向可以在同一个控制平面中统一调配时,设置适当的资源规则就容易得多了。
下图展示了 Ingress controller 是如何降低客户端的复杂性的——客户端不再需要知道 service 的 IP 地址或端口。不同 service 之间的流量分发得到了保障。一些 Ingress controller 支持多个负载均衡算法,以获得更好的灵活性和控制性。
Exposing Services with an Ingress Controller | 使用 Ingress Controller 暴露 service |
Client
Request to http:// pine.color.com on port 80 |
客户端
向 http:// pine.color.com 的 80 端口发送请求 |
Load Balancer port 80
-pine.color -lagoon.color -deeplake.color |
负载均衡器 80 端口
-pine.color -lagoon.color -deeplake.color |
1 The client is programmed with the service IP address and the load balancer port. | 1 客户端使用 service IP 地址和负载均衡器端口进行编程。 |
2 The Ingress controller guarantees traffic distributes across nodes and services. | 2 Ingress controller 保证流量分发到各个节点和 service。 |
Kubernetes cluster | Kubernetes 集群 |
Node 1 | 节点 1 |
Service: Pine
Service: Lagoon Service: Deep Lake |
Service:Pine
Service:Lagoon Service:Deep Lake |
Node 2 | 节点 2 |
Node 3 | 节点 3 |
部署带有 Ingress controller 的负载均衡器
正如我们在 《在同一架构中部署 BIG-IP 和 NGINX Ingress Controller》 中所讨论的,许多企业的用例都会因部署带 Ingress controller(或在大多数情况下,多个 Ingress controller 实例)的外部负载均衡器而受益。当企业需要扩展 Kubernetes 或在高度合规环境中运营时,这种部署尤为常见。这些工具通常由不同的团队管理并用于不同的目的:
- 负载均衡器(或称 ADC):
- 所有者:NetOps(也可能是 SecOps)团队
- 用例:位于 Kubernetes 外部,作为唯一面向公众的终端,为集群之外的用户提供服务和应用。作为一种更通用的应用,旨在提高安全性,并促进交付更高级别的网络管理。
- Ingress controller:
- 所有者:Platform Ops 或 DevOps 团队
- 用例:位于 Kubernetes 内部,支持细粒度的南北流量(HTTP2、HTTP/HTTPS、SSL/TLS 终止、TCP/UDP、WebSocket、gRPC)负载均衡、API 网关功能以及集中式安全防护和身份验证。
下图展示了负载均衡器处理跨多个集群流量分发的过程,同时集群部署了 Ingress controllers 来确保对 service 的平均分发。
Deploying a Load Balancer in Front of Ingress Controllers | 在 Ingress Controller 前面部署负载均衡器 |
Client
Request to http:// pine.color.com on port 80 |
客户端
向 http:// pine.color.com 的 80 端口发送请求 |
Load Balancer port 80 | 负载均衡器 80 端口 |
Ingress Controller port 80
-pine.color -lagoon.color -deeplake.color |
Ingress Controller 80 端口
-pine.color -lagoon.color -deeplake.color |
1 The load balancer distributes traffic across Ingress pods. | 1 负载均衡器将流量分发到各个 Ingress pod。 |
2 The Ingress controller guarantees traffic distributes across nodes and services. | 2 Ingress controller 保证流量分发到各个节点和 service。 |
Kubernetes cluster | Kubernetes 集群 |
Node 1 | 节点 1 |
Service: Pine
Service: Lagoon Service: Deep Lake |
Service:Pine
Service:Lagoon Service:Deep Lake |
Node 2 | 节点 2 |
Node 3 | 节点 3 |
后续步骤
如果本文没有完全解答您的疑问, 您可以点击文章《 Kubernetes 网络入门 》更多关于NGINX 的相关知识,您可以进入 NGINX 官方社区 了解。
本文主要介绍了K8S集群中的服务发现和流量暴露机制,包括K8S中的workload类型、service类型、DNS解析原理以及四层服务暴露和七层服务暴露的规则。
1、云原生基础概念
1.1 K8S架构
下图为K8S
官方文档
中对K8S架构设计的一个简要介绍示意图,这个架构图侧重于从云厂商的角度展示了
云厂商的API
、
K8S集群中控制面(Control Plane)
和
工作节点(Node)
之间的关系,但是将留给第三方实现的如
CRI、CNI、CSI
等从中剥离出去了。
在官方架构图的基础上我们将CRI和CNI引入到架构图中,可以得到下面的这个模型:
-
kube-apiserver
对外暴露了Kubernetes API。它是的 Kubernetes 前端控制层。它被设计为水平扩展,即通过部署更多实例来缩放。 -
etcd
用于 Kubernetes 的后端存储。etcd 负责保存Kubernetes Cluster的配置信息和各种资源的状态信息,始终为 Kubernetes 集群的 etcd 数据提供备份计划。当数据发生变化时,etcd 会快速地通知Kubernetes相关组件。 -
kube-scheduler
主要的工作就是调度新创建的Pod
,当集群中出现了新的Pod还没有确定分配到哪一个Node节点的时候,kube-scheduler
会根据各个节点的负载,以及应用对高可用、性能、数据亲和性的需求等各个方面进行分析并将其分配到最合适的节点上。 -
kube-controller-manager
运行控制器,它们是处理集群中常规任务的后台线程。逻辑上,每个控制器是一个单独的进程,但为了降低复杂性,它们都被编译成独立的可执行文件,并在单个进程中运行。这些控制器包括:节点控制器(Node Controller
)、副本控制器(Replication Controller
)、端点控制器(Endpoints Controller
)、服务帐户和令牌控制器(Service Account & Token Controllers
)等 -
kube-proxy
是集群中每个节点上运行的网络代理, kube-proxy通过维护主机上的网络规则并执行连接转发,实现了Kubernetes服务抽象。service在逻辑上代表了后端的多个Pod,外界通过service访问Pod。service接收到的请求就是通过kube-proxy转发到Pod上的,kube-proxy服务负责将访问service的TCP/UDP数据流转发到后端的容器。 如果有多个副本,kube-proxy会实现负载均衡。 -
K8S的
三大插件
分别管控
运行时
、
网络
和
存储
,即
Container Runtime Interface (CRI)
、Container Network Interface (CNI)
和Container-Storage-Interface (CSI)
。注意CRI和CNI是每个K8S集群都必须要部署的基础组件,而CSI则不一定,一般来说只有在我们需要运行有状态服务的时候才需要用到CSI。
1.2 CNI基础
K8S本身不实现集群内的网络模型,而是通过将其抽象出来提供了CNI接口给第三方实现,这样一来节省了开发资源可以集中精力到K8S本身,二来可以利用开源社区的力量打造一整个丰富的生态,CNI的一些实现细节和要求我们都可以在 github 上面找到,我们这里暂不深入解析。
重点来看一下K8S对集群内的网络模型定义:
- K8S集群中任意两个POD可以直接通信,并且不需要进行NAT
- K8S集群中的每个Pod都必须要有自己的唯一、独立且可被访问的IP(IP-per-Pod)
K8S并不关心各个CNI如何具体实现上述基础规则,只要最终的网络模型符合标准即可。因此我们可以确保不论使用什么CNI,K8S集群内的Pod网络都是一张巨大的平面网络,每个Pod在这张网络中地位是平等的,这种设计对于集群内的 服务发现 、 负载均衡 、 服务迁移 、 应用配置 等诸多场景都带来了极大的便利。
1.3 Overlay networks
Overlay网络可以理解为建立在另一个网络之上的虚拟网络,这个概念在SDN里面里面经常出现。和虚拟网卡需要依赖实际存在的物理网卡才能通信类似,Overlay网络也不能凭空出现,它需要依赖的底层网络通常被称为Underlay网络。Underlay 网络是专门用来承载用户 IP 流量的基础架构层,它与 Overlay 网络之间的关系有点类似物理机和虚拟机。Underlay 网络和物理机都是真正存在的实体,它们分别对应着真实存在的网络设备和计算设备,而 Overlay 网络和虚拟机都是依托在下层实体使用软件虚拟出来的层级。
在使用了Overlay网络的K8S集群中,我们可以把 底层的Underlay网络看作是K8S集群的Node节点所在的网络,而上层的Overlay网络一般用来处理Pod之间的网络通信 。正常情况下,Underlay网络和Overlay网络之间互不干扰,两者并不知道对方的网络情况。但是由于Overlay网络是需要依赖Underlay网络进行传输数据的,因此在Overlay网络的数据发送到Underlay网络进行传输的时候,需要进行数据包的封装,将其变为Underlay网络可以理解的数据包;反之当数据从Underlay网络传送回Overlay网络的时候需要进行数据包的解封。在K8S的Overlay网络实现中,用于封装的两种常见网络协议是 VXLAN 和 IP-in-IP。
使用Overlay网络的主要优点是:
- 高度灵活性,Overlay网络和底层硬件网络设施分离,因此在跨机房、跨数据中心等场景有着传统的Underlay网络无法比拟的优势
使用Overlay网络的主要缺点是:
- 轻微的性能影响。封装数据包的过程占用少量 CPU,数据包中用于编码封装(VXLAN 或 IP-in-IP 标头)所需的额外字节减少了可以发送的内部数据包的最大大小,进而可以意味着需要为相同数量的总数据发送更多数据包。
- Pod IP 地址不可在集群外路由。
1.4 边界网关协议(BGP)
BGP(Border Gateway Protocol/边界网关协议)是一种基于标准的网络协议,用于在网络中共享路由。它是互联网的基本组成部分之一,具有出色的扩展特性。在K8S中,BGP是出场率很高的一个路由协议,有很多相关的CNI或者是LoadBalancer都会使用BGP协议来实现诸如路由可达或者是ECMP等特性。
目前对BGP协议支持最好、使用最广泛的CNI应该是Calico,另外Cilium也有仍处于beta阶段的BGP模式的支持。
1.5 可路由性(routability)
不同的K8S集群网络的一个重要区别就是Pod的IP在K8S集群外的可路由性。
由于K8S集群内的Pod之间必然是路由可达的,因此这里探讨的是集群外的服务到集群内的Pod之间的路由可达。
路由不可达
所谓路由不可达,即K8S集群外的机器没办法和集群内的Pod直接建立连接,集群外的服务器不知道如何将数据包路由到 Pod IP。
这种情况下 当集群内的Pod需要主动和集群外的服务建立连接的时候 ,会通过K8S进行SNAT(Source Network Address Translation)。此时在集群外的服务器看到的连接对端IP是这个Pod所在的K8S集群节点的Node IP而不是Pod自身的IP,对于集群外的服务器发送返回数据的目的IP也永远都是这个K8S集群节点的Node IP,数据在Node IP上面再转换发送回Pod。这种情况下,集群外的服务器是无法得知Pod的IP,也无法直接获取真实的请求IP。
反之则更复杂,因为集群外的服务器不知道如何将数据包路由到 Pod IP ,所以也没办法主动去请求这些Pod, 此时只能通过K8S的services(NodePort、LoadBalancer、Ingress)来将服务暴露到集群外 ,此时集群外的服务器的访问对象是某个K8S的服务,而不是具体的某个Pod。
路由可达
如果 Pod IP 地址可在集群外部路由,则 pod 可以在没有 SNAT 的情况下直接连接到集群外的服务器,而集群外的服务器也可以直接连接到 pod,而无需通过 通过K8S的services(NodePort、LoadBalancer、Ingress)。
可在集群外路由的 Pod IP 地址的优点是:
- 减少网络层级、降低网络层面架构复杂性、降低使用人员的理解成本、维护成本和Debug成本等
- 针对一些特殊的应用场景(如集群外的机器需要直接和Pod进行连接),在这种架构中实现更加简单
可在集群外路由的 Pod IP 地址的主要缺点是:
- Pod IP 在集群外的网络中也必须要唯一。如果有多个K8S集群都需要实现集群外路由可达,那么就需要给每个集群的Pod使用不同的CIDR。这对内部IP的使用规划有一定的要求,并且当集群足够大的时候,还需要考虑内网IP耗尽的可能。
可路由性的决定因素
- 如果集群使用的是overlay网络,一般来说Pod IP无法在集群外部路由
- 如果不使用overlay网络,则取决于部署的环境(云厂商/本地部署)、使用的CNI(Calico-BGP、Cilium-BGP等)以及实际网络规划等
- 目前K8S网络的集群外可路由性实现一般都是通过BGP协议
2、K8S服务暴露
正常情况下,我们部署在K8S集群中的工作负载是需要对外提供服务的。这里的 “对外” 指的是对该负载以外的所有外部服务器提供服务,而根据这些外部服务器是否位于K8S集群中,我们可以分为K8S集群内部流量和K8S集群外流量。
2.1 Workload与SVC
开始之前,我们要明确几个点:
-
K8S中的工作负载(Workload)一般指的是集群中的真实工作任务。比如无状态服务常用的
deployments
、有状态服务常用的statefulsets
、一些特殊用途的daemonsets
、定时服务常用的cronjobs
等,这些都属于是K8S中的工作负载(Workload)。 - K8S中的service(SVC)更多倾向于是一种规则集合,将符合某些特定条件的pod全部归属到一个Service中,然后组成一个特定的Service。注意这些pod是可以属于不同的工作负载(Workload)。
-
K8S中的每个SVC都会有一个对应的域名,域名的组成格式为
$service_name.$namespace_name.svc.$cluster_name
,一般来说k8s集群中的$cluster_name
就是cluster.local
,这个字段一般在集群创建的时候就会设定好,之后想要再更改会十分麻烦。
综上所诉,我们可以得出以下结论:
-
K8S中的工作负载(Workload)和服务暴露(service)是相互隔离开来的,分别交给不同的
api
来实现 -
每个SVC都会有一个
服务名+命名空间+svc+集群名
/$service_name.$namespace_name.svc.$cluster_name
的域名可以用来访问(如app.namespace.svc.cluster.local
) - K8S集群内的服务之间访问主要就是通过这个域名来实现的
2.2 SVC的种类
一般来说svc可以分为四类:
Headless
、
ClusterIP
、
NodePort
、
LoadBalancer
。四者之间的关系并非是完全互斥,具体如下:
Headless Services
-
Headless
类型服务和其他三者完全互斥,可以通过指定 Cluster IP(spec.clusterIP
)的值为"None"
来创建Headless
Service; - 此时该服务的域名解析的结果就是这个服务关联的所有Pod IP,使用该域名访问的时候请求会直接到达pod;
- 这时的负载均衡策略相当于是仅使用了DNS解析做负载均衡,并没有使用k8s内置的kube-proxy进行负载均衡;
-
Headless
类型服务不会创建对应域名所属的SRV记录;
Headless Services
这种方式优点在于足够简单、请求的链路短,但是缺点也很明显,就是DNS的缓存问题带来的不可控。
很多程序查询DNS并不会参考规范的TTL值,要么频繁的查询给DNS服务器带来巨大的压力,要么查询之后一直缓存导致服务变更了还在请求旧的IP。
ClusterIP Services
在 Kubernetes 集群中,每个 Node 运行一个
kube-proxy
进程。
kube-proxy
负责为 Service 实现了一种 VIP(虚拟 IP)的形式,一般称之为
ClusterIP Services
。
-
ClusterIP
是最常用的服务类型,也是默认的服务类型,同时也是NodePort
、LoadBalancer
这两个服务的基础; -
对于ClusterIP类型的服务,K8S会给该服务分配一个称为
CLUSTER-IP
的VIP; - ClusterIP是单独的IP网段,区别于K8S的宿主机节点IP网段和Pod IP网段,也是在集群初始化的时候定义的;
-
ClusterIP可以在每一台k8s宿主机节点上面的
kube-ipvs0
网卡里面看到; - ClusterIP类型的服务的域名解析的结果就是这个VIP,请求会先经过VIP,再由kube-proxy分发到各个pod上面;
- 如果k8s使用了ipvs,可以在K8S宿主机节点上面使用ipvsadm命令来查看这些负载均衡的转发规则;
-
ClusterIP
类型服务还会创建对应域名所属的SRV记录,SRV记录中的端口为ClusterIP的端口
ClusterIP Services
这种方式的
优点是有VIP位于Pod前面,可以有效避免前面提及的直接DNS解析带来的各类问题;缺点也很明显,当请求量大的时候,kube-proxy组件的处理性能会首先成为整个请求链路的瓶颈。
NodePort Service
- 从NodePort开始,服务就不仅局限于在K8S集群内暴露,开始可以对集群外提供服务
- NodePort类型会从K8S的宿主机节点上面挑选一个端口分配给某个服务(默认范围是30000-32767),用户可以通过请求任意一个K8S节点IP的该指定端口来访问这个服务
-
NodePort服务域名解析的解析结果是一个
CLUSTER-IP
,在集群内部请求的负载均衡逻辑和实现与ClusterIP Service
是一致的 -
NodePort服务的请求路径是
从K8S节点IP直接到Pod
,并不会经过ClusterIP,但是这个转发逻辑依旧是由
kube-proxy
实现
NodePort Service
这种方式的
优点是非常简单的就能把服务通过K8S自带的功能暴露到集群外部;缺点也很明显:NodePort本身的端口限制(数量和选择范围都有限)以及请求量大时的kube-proxy组件的性能瓶颈问题。
LoadBalancer Service
- LoadBalancer服务类型是K8S对集群外服务暴露的最高级最优雅的方式,同时也是门槛最高的方式;
- LoadBalancer服务类型需要K8S集群支持一个云原生的LoadBalancer,这部分功能K8S本身没有实现,而是将其交给云厂商/第三方,因此对于云环境的K8S集群可以直接使用云厂商提供的LoadBalancer,当然也有一些开源的云原生LoadBalancer,如MetalLB、OpenELB、PureLB等;
-
LoadBalancer服务域名解析的解析结果是一个
CLUSTER-IP
; -
LoadBalancer服务同时会分配一个
EXTERNAL-IP
,集群外的机器可以通过这个EXTERNAL-IP
来访问服务; -
LoadBalancer服务默认情况下会同时创建NodePort,也就是说一个LoadBalancer类型的服务同时是一个NodePort服务,同时也是一个clusterIP服务;一些云原生LoadBalancer可以通过指定
allocateLoadBalancerNodePorts: false
来拒绝创建NodePort服务;
我们还是借用 OpenELB官网的图 来解释一下这个流程,注意这里为BGP模式。
LoadBalancer Service
这种方式的
优点是方便、高效、适用场景广泛,几乎可以覆盖所有对外的服务暴露;缺点则是成熟可用的云原生LoadBalancer选择不多,实现门槛较高。
2.3 Port概念辨析
在我们进行SVC和Workload部署配置的时候,经常会碰到各种名字中带有
Port
的配置项,这也是K8S中容易让人混淆的几个概念之一,这里主要介绍一下
NodePort
、
Port
、
targetPort
和
containerPort
这个四个概念。四者的关系我们可以通过下面这种图比较清晰地区分出来:
-
nodePort
: 只存在于Loadbalancer服务和NodePort服务中,用于指定K8S集群的宿主机节点的端口,默认范围是30000-32767
,K8S集群外部可以通过NodeIP:nodePort
来访问某个service; -
port
: 只作用于CLUSTER-IP
和EXTERNAL-IP
,也就是对Loadbalancer服务、NodePort服务和ClusterIP服务均有作用,K8S集群内部可以通过CLUSTER-IP:port
来访问,K8S集群外部可以通过EXTERNAL-IP:port
来访问; -
targetPort
: Pod的外部访问端口,port和nodePort的流量会参照对应的ipvs规则转发到Pod上面的这个端口,也就是说数据的转发路径是NodeIP:nodePort -> PodIP:targetPort
、CLUSTER-IP:port -> PodIP:targetPort
、EXTERNAL-IP:port -> PodIP:targetPort
-
containerPort
:和其余三个概念不属于同一个维度,containerPort
主要是在工作负载(Workload)
中配置,其余三者均是在service
中配置。containerPort
主要作用在Pod内部的container,用来告知K8S这个container内部提供服务的端口,因此理论上containerPort
应该要和container内部实际监听的端口一致,这样才能确保服务正常;但是实际上由于各个CNI的实现不通以及K8S配置的网络策略差异,containerPort
的作用并不明显,很多时候配置错误或者是不配置也能正常工作;
综上所述,我们可以得知四者的主要区别,那么我们在实际使用的时候,最好就需要确保
targetPort
、
containerPort
和Pod里面运行程序实际监听的端口三者保持一致,即可确保请求的数据转发链路正常。
3、K8S中的DNS服务
众所周知,在K8S中,IP是随时会发生变化的,变化最频繁的就是
Pod IP
,
Cluster IP
也并不是一定不会发生变化,
EXTERNAL-IP
虽然可以手动指定静态IP保持不变,但是主要面向的是集群外部的服务;
因此在K8S集群中,最好的服务之间相互访问的方式就是通过域名。
3.1 DNS创建规则
在K8S集群中,Kubernetes 为 Service 和 Pod 创建 DNS 记录。
前面我们介绍了K8S中的每个SVC都会有一个对应的域名,域名的组成格式为
$service_name.$namespace_name.svc.$cluster_name
,同时也会给这个SVC下的所有Pod都创建一个
$pod_name.$service_name.$namespace_name.svc.$cluster_name
的这种域名,这个域名的解析结果就是Pod IP。
Pod域名有两个比较明显的特征:
- 一是域名的组成比较特殊,因为域名中使用了Pod的名称,而pod名称在K8S中是会发生变化的(例如在服务更新或者滚动重启时),同时由于默认情况下Pod的命名是没有太明显的规律(大部分名字中会包含一串随机UUID)
- 二是域名的解析结果特殊,相较于集群内的其他类型域名,Pod域名的解析是可以精确到特定的某个Pod,因此一些特殊的需要点对点通信的服务可以使用这类Pod域名
3.2 DNS策略配置
DNS 策略可以逐个 Pod 来设定。目前 Kubernetes 支持以下特定 Pod 的 DNS 策略。 这些策略可以在 Pod 规约中的
dnsPolicy
字段设置:
-
Default
: Pod 从运行所在的K8S宿主机节点继承域名解析配置; -
ClusterFirst
: 不指定任何dnsPolicy
配置情况下的默认选项 ,所有查询的域名都会根据生成的集群的K8S域名等信息生成的/etc/resolv.conf
配置进行解析和转发到集群内部的DNS服务进行解析; -
ClusterFirstWithHostNet
:主要用于以hostNetwork
方式运行的 Pod,如果这些pod想要使用K8S集群内的DNS服务,则可以配置为这个字段; -
None
: 此设置允许 Pod 忽略 Kubernetes 环境中的 DNS 设置,Pod 会使用其dnsConfig
字段 所配置的 DNS 设置;
说明:
下面主要介绍
ClusterFirst
模式
3.3 DNS解析规则
DNS 查询参照 Pod 中的
/etc/resolv.conf
配置,kubelet 会为每个 Pod 生成此文件。因此在每个pod里面都有一个类似下面这样的
/etc/resolv.conf
文件,通过修改其中的配置可以更改DNS的查询规则:
nameserver 10.32.0.10
search <namespace>.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
这里的配置有几个需要注意的点:
-
nameserver
:集群中的DNS服务器IP,一般来说就是CoreDNS
的ClusterIP
-
search
:需要搜索的域,默认情况下会从该pod所属的namespace开始逐级补充 -
options ndots
:触发上面的search
的域名点数,默认为1,上限15,在K8S中一般为5;例如在Linux中tinychen.com
这个域名的ndots
是1,tinychen.com.
这个域名的ndots
才是2(需要注意所有域名其实都有一个根域.
,因此tinychen.com
的全称应该是tinychen.com.
)
这是一个比较通用的案例,我们再来看一个比较特殊的配置
# 首先进入一个pod查看里面的DNS解析配置
[root@tiny-calico-master-88-1 tiny-calico]# kubectl exec -it -n ngx-system ngx-ex-deploy-6bf6c99d95-5qh2w /bin/bash
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl exec [POD] -- [COMMAND] instead.
[root@ngx-ex-deploy-6bf6c99d95-5qh2w /]# cat /etc/resolv.conf
nameserver 10.88.0.10
search ngx-system.svc.cali-cluster.tclocal svc.cali-cluster.tclocal cali-cluster.tclocal k8s.tcinternal
options ndots:5
[root@ngx-ex-deploy-6bf6c99d95-5qh2w /]# exit
这个pod里面的
/etc/resolv.conf
配置文件有两个和前面不同的地方:
-
cluster.local
变成了cali-cluster.tclocal
这里我们可以看到coredns的配置中就是配置的
cali-cluster.tclocal
,也就是说
/etc/resolv.conf
中的配置其实是和
coredns
中的配置一样,更准确的说是和该K8S集群初始化时配置的集群名一样
# 再查看K8S集群中的coredns的configmap
[root@tiny-calico-master-88-1 tiny-calico]# kubectl get configmaps -n kube-system coredns -oyaml
apiVersion: v1
data:
Corefile: |
.:53 {
errors
health {
lameduck 5s
ready
kubernetes cali-cluster.tclocal in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
prometheus :9153
forward . 10.31.100.100 {
max_concurrent 1000
cache 30
reload
loadbalance
kind: ConfigMap
metadata:
creationTimestamp: "2022-05-06T05:19:08Z"
name: coredns
namespace: kube-system
resourceVersion: "3986029"
uid: 54f5f803-a5ab-4c77-b149-f02229bcad0a
-
search
新增了一个k8s.tcinternal
实际上我们再查看K8S的宿主机节点的DNS配置规则时会发现这个
k8s.tcinternal
是从宿主机上面继承而来的
# 最后查看宿主机节点上面的DNS解析配置