在了解了k8s的基本原理以后,还是有一些疑惑,就是k8s 中部署的服务如何对外提供服务,pod与pod是如何通信的,k8s内部的服务又是如何做负载均衡的。一个pod挂了,再重启拉起来一个pod,是如何加入负载均衡中的,为什么它拉起来就能提供服务?
直到我了解了k8s中的service,这一切才得到答案。
service是什么
其实就是k8s中的服务注册与负载均衡。
最终能够实现,提供一个唯一的地址,供我们来访问地址,而不需要具体的去了解,这个服务起的Pod的ip是什么。
怎么去使用 service呢
其实 k8s 其实可以这样理解,它就是依赖 yaml的,基本上你能想到的所有的问题,它都是通过配置文件来完成的,学习k8s的使用,其实就是学习这些yaml配置的含义。就这么简单。
看一下对一个service的声明:其中粉色的框,就就已经说明了这是一个service,然后红色框,就是通过selector的方式,选出来,名字叫做MyApp的Pod。然后绿色框,代表的是对外提供的端口号。然后整体都有依赖k8s的域名解析的模块。用户想要访问这个服务,访问 my-service 就可以访问到服务。9376就是MyApp这个pod提供服务的端口,其实这里就像是nginx的一个路由映射。你想要访问9376这个端口的服务,并没有直接访问9376这个端口,而是走了service这一层类似与流量网关做了端口映射。
具体的应用是通过,一个包含上边内容的 yaml文件,然后使用命令创建这个网关服务;
- kubectl apply -f service.yaml
或者是用这个命令来创建,结果都一样
- kubectl created -f service.yaml
查看创建的结果
- kubectl discribe service
结果如下:
注意看红框的ip地址,其实就是通过我们上边的配置的service的yaml,去查找了名字叫做 MyApp的Pod,并且找到了三个。
最后是一个这样的关系:其中172.29.3.27 是service 的ip,这是被自动分配的一个ip。另外service关注的是叫做 MyApp的Pod,而不是具体ip是哪个的pod,这样pod并没有和service绑定,它销毁了,没有关系,那么service只看到另外两个pod ,如果再加进来两个pod,也一样。
如何访问service呢
经过上边的步骤,其实一个service已经被创建出来了,但是具体如何去访问它呢?方式有三种
- 上边创建的service其实是被分配了一个ip的,同一个集群内,可以通过这个ip起访问到。
- 第二种方式直接访问服务名,依靠 DNS 解析,就是同一个 namespace 里 pod 可以直接通过 service 的名字去访问到刚才所声明的这个 service。不同的 namespace 里面,我们可以通过 service 名字加“.”,然后加 service 所在的哪个 namespace 去访问这个 service,例如我们直接用 curl 去访问,就是 my-service:80 就可以访问到这个 service。
- 第三种是通过环境变量访问,在同一个 namespace 里的 pod 启动时,K8s 会把 service 的一些 IP 地址、端口,以及一些简单的配置,通过环境变量的方式放到 K8s 的 pod 里面。在 K8s pod 的容器启动之后,通过读取系统的环境变量比读取到 namespace 里面其他 service 配置的一个地址,或者是它的端口号等等。比如在集群的某一个 pod 里面,可以直接通过 curl $ 取到一个环境变量的值,比如取到 MY_SERVICE_SERVICE_HOST 就是它的一个 IP 地址,MY_SERVICE 就是刚才我们声明的 MY_SERVICE,SERVICE_PORT 就是它的端口号,这样也可以请求到集群里面的 MY_SERVICE 这个 service。
我你家青睐于第二种方式。第二种方式是依赖k8s的域名解析的。而第一种banding了ip,service一旦挂掉了,就很麻烦了。
## 可以告诉k8s不用给service分配ip,这样就只能走域名解析了
向集群外暴露 Service
前面介绍的都是在集群里面 node 或者 pod 去访问 service,service 怎么去向外暴露呢?怎么把应用实际暴露给公网去访问呢?这里 service 也有两种类型去解决这个问题,一个是 NodePort,一个是 LoadBalancer。
- NodePort 的方式就是在集群的 node 上面(即集群的节点的宿主机上面)去暴露节点上的一个端口,这样相当于在节点的一个端口上面访问到之后就会再去做一层转发,转发到虚拟的 IP 地址上面,就是刚刚宿主机上面 service 虚拟 IP 地址。
- LoadBalancer 类型就是在 NodePort 上面又做了一层转换,刚才所说的 NodePort 其实是集群里面每个节点上面一个端口,LoadBalancer 是在所有的节点前又挂一个负载均衡。比如在阿里云上挂一个 SLB,这个负载均衡会提供一个统一的入口,并把所有它接触到的流量负载均衡到每一个集群节点的 node pod 上面去。然后 node pod 再转化成 ClusterIP,去访问到实际的 pod 上面。
service的架构设计
最后是对 K8s 设计的一个简单的分析以及实现的一些原理。
我先用一句话来描述,一个关键点,其实用到了k8s的一个核心点DNS,DNS去关注Pod 的生命状态。可以这样理解,就像是美团,会有很多商户入住,然后你打开美团,想要订酒店,然后美团孤给你提供符合你意向的酒店,美团会主动的维护这个关系,定时的问,你们这家酒店还开不开了,倒闭了,美团就会把这个酒店下架。如果酒店又开了分店,那就同样添加到列表中来。
Kubernetes 服务发现架构
如上图所示,K8s 服务发现以及 K8s Service 是这样整体的一个架构。
K8s 分为 master 节点和 worker 节点:
- master 里面主要是 K8s 管控的内容;
- worker 节点里面是实际跑用户应用的一个地方。
在 K8s master 节点里面有 APIServer,就是统一管理 K8s 所有对象的地方,所有的组件都会注册到 APIServer 上面去监听这个对象的变化,比如说我们刚才的组件 pod 生命周期发生变化,这些事件。
这里面最关键的有三个组件:
- 一个是 Cloud Controller Manager,负责去配置 LoadBalancer 的一个负载均衡器给外部去访问;
- 另外一个就是 Coredns,就是通过 Coredns 去观测 APIServer 里面的 service 后端 pod 的一个变化,去配置 service 的 DNS 解析,实现可以通过 service 的名字直接访问到 service 的虚拟 IP,或者是 Headless 类型的 Service 中的 IP 列表的解析;
- 然后在每个 node 里面会有 kube-proxy 这个组件,它通过监听 service 以及 pod 变化,然后实际去配置集群里面的 node pod 或者是虚拟 IP 地址的一个访问。
实际访问链路是什么样的呢?比如说从集群内部的一个 Client Pod3 去访问 Service,就类似于刚才所演示的一个效果。Client Pod3 首先通过 Coredns 这里去解析出 ServiceIP,Coredns 会返回给它 ServiceName 所对应的 service IP 是什么,这个 Client Pod3 就会拿这个 Service IP 去做请求,它的请求到宿主机的网络之后,就会被 kube-proxy 所配置的 iptables 或者 IPVS 去做一层拦截处理,之后去负载均衡到每一个实际的后端 pod 上面去,这样就实现了一个负载均衡以及服务发现。
对于外部的流量,比如说刚才通过公网访问的一个请求。它是通过外部的一个负载均衡器 Cloud Controller Manager 去监听 service 的变化之后,去配置的一个负载均衡器,然后转发到节点上的一个 NodePort 上面去,NodePort 也会经过 kube-proxy 的一个配置的一个 iptables,把 NodePort 的流量转换成 ClusterIP,紧接着转换成后端的一个 pod 的 IP 地址,去做负载均衡以及服务发现。这就是整个 K8s 服务发现以及 K8s Service 整体的结构。