从一个网络工程师的角度出发,观察 K8S 的世界。
开篇
在传统业务场景下,我们知道一般情况业务流量访问时,会从客户端发起 --> DNS 解析 --> LB --> 防火墙 --> 服务集群 --> 服务集群内部负载 --> 真实服务器,最终对外提供服务访问。而在 K8S 的系统环境下,基本的流量访问是类似的,只是在其中和传统网络又有什么地方不同呢?这个是我们这次探索之路的主要目的。
接下来,就以访问一个搭建好的 eureka-web 为例子,一起来看看业务访问过程中 K8S 的不同。
K8S 业务访问方式
在开始之前,我们需要先了解 K8S 的几种服务暴露的方式,掌握基本的规则。
NodePort
不知道你是否使用过才 API 或者用 docker 直接启动一个服务端口,然后通过本地直接调用 IP+Port 的方式来测试你的代码。没错,这个就是最基本的访问场景,也是我们大家应该比较熟悉的方式。而 K8S 也支持这种方式实现业务访问。
这种方式就是 NodePort,在 K8S 之中一个 Service 的 Pod 启动之后,会暴露一个 Port,将这个 Port 直接映射到当前 Node 的一个指定 Port 上,那么此时通过访问 NodeIP + Port 的方式就能实现业务访问的最基本的需求。
这种方式简单粗暴,但是存在很多问题:
- Node 端口有限,默认设置如下图 20000 - 65000
- Pod 会在 Node 上面迁移,如果 Pod 被迁移了,对外暴露的地址就需要变更
- 不利于管理
- 端口直接暴露不合规
所以这种方式不是主要的提供服务的方式,只有服务比较稳定,或者在测试环境为了方便才会这么干。实际业务场景下极其不推荐这种方法。
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
app: MyApp
ports:
# By default and for convenience, the `targetPort` is set to the same value as the `port` field.
- port: 80
targetPort: 80
# Optional field
# By default and for convenience, the Kubernetes control plane will allocate a port from a range (default: 30000-32767)
nodePort: 30007
Loadbalance
NodePort 可以在内部直接提供服务访问,但是如果有个公网的请求呢?那私网地址肯定无法被直接访问到。所以在在 K8S 请求层面需要创建一个负载均衡器(loadbalance),loadbalance 通过将每个 Node 作为后端,而 Service 挂在 NodePort 之后。以此对外提供服务访问。如下图参考:
apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
selector:
app: MyApp
ports:
- protocol: TCP
port: 80
targetPort: 9376
clusterIP: 10.0.171.239
type: LoadBalancer
status:
loadBalancer:
ingress:
- ip: 192.0.2.127
当需要设置公网的 load balancer 时,每个云厂商的声明方式不同,比如 alibaba cloud 和 AWS
metadata:
annotations:
service.beta.kubernetes.io/alibaba-cloud-loadbalancer-address-type: "intranet"
metadata:
name: my-service
annotations:
service.beta.kubernetes.io/aws-load-balancer-internal: "true"
ClusterIP
K8S 主要的方式,Pod 在 K8S 环境中可能会经常发生变化,被驱逐、被重拉、被删除,因为它本身也是一个 container。当 Pod 发生了改变时,Pod 的地址都和所在的 Node 都有可能会变化,所以很难提供一个稳定的访问。所以 K8S 提供了一个解决方案,把每个 Service 固定在一个虚拟 IP 地址上(ClusterIP),自动且动态绑定后面的 Pod。而其底层依然是依赖 kube-proxy 来实现。
是默认的服务类型。
ClusterIP 的特点:
- 仅用于 kubernetes Service 这个对象,并由 Kubernetes 管理和分配 IP 地址;
- cluster ip 无法被ping ,它没有一个“实体网络对象”;
- cluster ip 只能结合 Service Port 组合成一个具体的通信端口,单独 ClusterIP 不具备通信的基础,并且他们属于 kubernetes 集群这样一个封闭的空间。
ClusterIP 的方式:
- 普通 Service : 通过为 Kubernetes 的 Service 分配一个集群内部可访问的固定虚拟 IP,实现集群内的访问;
- Headless Service:不分配 cluster ip,通过 dns 提供稳定的网络 ID 来访问,dns 会将 headless service 的后端直接解析为 pod IP,主要供 statefulSet 使用。
ExternalName
ExternaleName 是 K8S 中一个特殊的 service 类型, 通过 CNAME 的方式匹配一个 Service 到一个 DNS 域名,而不是通过匹配 Selector的方式,可以通过 spec.externalName 的参数来声明。
apiVersion: v1
kind: Service
metadata:
name: my-service
namespace: prod
spec:
type: ExternalName
externalName: my.database.example.com
访问流程
通过上面对 K8S 的访问学习,我们基本了解到,要想成功访问 K8S 的业务有四种方式:NodePort、Loadbalance、ClusterIp、ExternalName,而 ClusterIP 是 K8S 内部访问的,不可以被外部直接访问,所以选择 NodePort 的方式是常规操作了。但是这种方式也存在很多弊端,在前面介绍 NodePort 已经提到了。而 ExternalName 的方式就很直接,而它更多适合将外部服务映射到内部服务。
在 k8s 官网上有个知识点,它就是 Ingress,从 K8S 官网摘录以下内容,关于 Ingress 的定义(出处kubernetes.io/docs/concep… )
An API object that manages external access to the services in a cluster, typically HTTP.Ingress may provide load balancing, SSL termination and name-based virtual hosting.
直白地翻译一下:Ingress 是一个管理外部访问集群内服务的 API 对象。Ingress 可以提供 LB、 SSL 认证、基于名称的虚拟主机。
Ingress 可以更简单的理解为是暴露 HTTP 和 HTTPS 路由到外部集群,提供外部访问,非常类似 Nginx。访问流量的路由信息,通过 Ingress 资源定义的规则来控制,如下图中,外部客户端访问资源,先通过 Ingress-managed 访问到集群内的 Ingress,通过路由规则对应到了相应的 service,再基于 kube-proxy 将 Pod 的IP 和端口,以 endpoints 的方式暴露出来。所以如果在业务访问中发现没有 endpoints,那实际业务访问肯定也会出现问题。接下来我们看看具体的流程是如何一步一步关联起来的。
-
Ingress : 在大致了解流程的情况下, 我们先查看 Ingress 的信息, 命令如下:
kubectl get ingressroute -n {namespace} eureka-web -o yaml结果如下图,我们可以关注到这几个信息
API Version: traefik.containo.us/v1a1pha1 # Ingress 无法独自完成工作,需要 IngressController 来配合,这里使用的是 traefik ,还有其他很多类型的 Ingress Controller。 Spec.Routes.Match :Host('xxx.xxx.com') && PathPrefix('/eureka-web') # 定义了路由信息,即访问时的请求地址 Spec.Routes.Services.Name :eureka-server # 关联后端服务名称,是下一步我们要查的依据 -
svc
通过上一步的查看,我们找到的 Ingress 是如何对应 Service 的,那 Service 的信息如何查看?
kubectl get svc -n {namespace} eureka-web查询结果如下图
在这个图中出现了比较多的关键字段,我们先关注下面这些字段,这里终于出现了 ClusterIP 以及一些 Port。
Labels: xxx # 用于和后端 Pod 绑定 Selector: xxx # 用于告知 endpoint Type: ClusterIP # 这是一个内部访问 IP 地址,也可以是 NodePort、lb、externalName IP : 10.x.x.x # CLusterIP 地址 Port: 80/TCP # 映射出的端口 TargetPort: 20001/TCP # 实际后端的端口 Endpoints: 172.25.x.x:20001 # endpoints 记录 -
Endpoints
Endpoint 是 K8S 集群资源对象,用来表示一个 service 对应的所有 Pod 的访问地址。service 只有配置 selector,endpoint 才会自动创建对应的 endpoint 对象。而 Endpoints Controller 负责生成和维护所有 Endpoints 对象的控制器。
查看命令
kubectl describe endpoints -n {namespace} eureka-serverSubsets.addresses: 172.25.x.x # 是后端 Pod 的地址,这里查看到的信息和 SVC 中看到的信息是一样的
-
Pod
SVC 中定义的 Labels 前后关联,把 pod 和 endpoints 整个串起来。
而 Pod 可以先粗略地理解为一个 sandbox,它框住了一些资源,在 Pod 下启动的 docker 可以共享存储、网络、进程等信息,而一个或多个 Pod 可以共同为一个 service 提供服务。
在 Service 和 Pod 绑定且启动之后,就会生成 Endpoint,以此对外提供服务,这里记录的是实际后端 PodIP 以及对外提供的服务端口。
总结
通过目前的梳理,已经大致完成业务访问流程的摸索,但未谈及具体的流量转发和控制的细节,毕竟 K8S 的精髓在于调度控制,后面会继续详细介绍每一步的细节。