对Kurbernetes中服务暴露方法的一些理解和说明(201001)
由于最近在进一步整理和学习云原生解决方案的相关材料,原来一直没太理解清楚的就是kurbernetes中的网络和服务暴露方式。最近又查找资料进一步学习了下。
业务场景说明
在前面谈DevOps解决方案的时候就谈到,一个完整的DevOps持续集成和交付过程,需要和容器云集成,来实现自动化部署,动态弹性伸缩,环境迁移等能力。
一个DevOps支撑平台离不开和容器化PaaS平台的集成,即最终的编译构建完成的内容形成镜像并放到镜像仓库,后续部署,环境迁移,资源扩展基于镜像仓库进行快速的拷贝和复制。对于Docker容器一般会和K8S结合来实现资源的动态调度,集群管理能力。
在原来谈的时候仅仅谈到通过K8s来完成部署和资源动态扩展的时候会从此一个VIP虚拟地址提供给应用模块访问使用,而这里没有进行展开,今天主要是结合场景进一步展开说明。
场景说明:
我们以整个应用实际有两个微服务模块来举例,一个是UserMgr微服务,一个是OrderMgr订单管理微服务,这个两个微服务都通过k8s自动化部署到容器云环境。同时我们假设,每个微服务都动态扩展了2个副本Pod,即形成了三个Pod节点。
在这种情况下,我们不可能直接去访问Pod IP,一个是Pod IP本身就会动态变化,一个是集群扩展后本身同一个微服务已经存在多个副本Pod IP。
因此我们需要通过Service来访问。
Kubernetes Service 定义了这样一种抽象:一个 Pod 的逻辑分组,一种可以访问它们的策略 —— 通常称为微服务。 这一组 Pod 能够被 Service 访问到,通常是通过 Label Selector实现的。比如上面的UserMgr微服务,我们可以给它打一个UserMgr的标签,然后相同的标签自动聚合到一个Service逻辑分组上面。
内部模块间服务访问-ClusterIP
刚才我们谈到,整个业务场景里面有UserMgr和OrderMgr两个微服务,那么这两个微服务之间的访问属于Kurbernetes集群内部的访问。
在这种集群内部访问场景下,即通过Service的ClusterIP即可。
注意ClusterIP本身是一个虚拟IP,无法Ping通,对于该IP的访问请求实际是基于IPTables路由表和KubeProxy最终路由到具体的Pod实例节点上面。即:
Request-》ClusterIP-》IPTables+KubeProxy-》Pod Instance
如下图:
在iptables代理模式下,对每个Service,它会安装iptables规则,从而捕获到达该Service的clusterIP(虚拟IP)和端口的请求,进而将请求重定向到Service的一组backend中某个上面。对于每个Endpoints对象,它也会安装iptables规则,这个规则会选择一个backend Pod。默认的策略是,随机选择一个backend。
对外提供服务-NodePort方式
如果需要对外提供服务,实际上有NodePort,LoadBalancer和Ingress多种方式。下面分别来对这几种方式做下说明。
NodePort方式主要通过每个节点IP加端口的形式暴露端口,访问任意一个node ip都可以访问到(前提没有指定node调度策略),其中端口可以通过apiserver的配置文件可以看到端口暴露范围。
比如还是上面的两个微服务模块部署下去后,对于8001端口可以配置为访问UserMgr这个微服务模块。即:10.0.0.1:8001, 10.0.0.1:8002,10.0.0.1:8003。
对于NodePort这种模式,实际上仍然是将请求转发到Service上面,再通过Service路由到具体的Pod实例节点上面。唯一差异在于NodeIP是可以访问到的IP地址。
这三个地址都可以访问到用户管理这个微服务。注意一个port端口映射到一个微服务上面,比如8001映射到UserMgr微服务,8002映射到8002微服务。上面三个地址都可以外部访问到,如果客户端要统一访问,统一接入到类似Ngnix反向代理就可以了。
但是这种方式存在问题即如果新增加了Node节点,我们需要在集群或负载均衡上新增加配置信息,其次就是Node本身是附属在虚拟机上面,如果整个IaaS环境的虚拟机重启后IP地址可能发生变化,那么这个时候又需要手工进行配置。
对外提供服务-LoadBalancer方式
这种方式主要是利用其他第三方的LB暴露服务的,阿里云或者亚马逊的LB等等。在这种方式下注意对于每一个微服务都会消耗一个IP,因此可能带来公有云费用的问题。其次,也不容易形成了要给统一的服务访问出口。
在这种方式下,来自外部负载均衡器的流量将直接达到 backend Pod 上,不过实际它们是如何工作的,这要依赖于云提供商。 在这些情况下,将根据用户设置的 loadBalancerIP 来创建负载均衡器。
对外提供服务-Ingress方式
Ingress资源对象,用于将不同URL的访问请求转发到后端不同的Service,以实现HTTP层的业务路由机制。Kubernetes使用一个Ingress策略定义和一个具体的Ingress Controller,两者结合并实现了一个完整的Ingress负载均衡器。
Ingress Controller将基于Ingress规则将客户请求直接转发到Service对应的后端Endpoint上,这样会跳过kube-proxy的转发功能,kube-proxy 不再起作用。
对于Ingress完全可以理解为整个Kurbernetes集群对外的一个网关或代理出口。把它理解为一个对外的API网关也没有问题。通过Ingress可以接入和注册各个微服务,微服务的IP访问地址意义,通过后面不同的路径和url来区分具体路由到哪个微服务上面。
对于Ingress网关的选型
可以参考:https://www.cnblogs.com/upyun/p/12372107.html
该篇文章给出了一个对比图如下:
可以看到,当前Kong API网关本身也有了Kurbernetes插件后,形成了Kong Ingress,即既满足了集群节点的对外暴露,同时又包括了Kong网关的一些核心能力,包括服务注册发现,限流熔断,安全等能力都可以满足日常对API管理的需要。
简单来说,如果你是将内部微服务的API接口暴露出去给前端APP用,那么采用Kong Ingress应该是一个不错的选择。同时Kong ingress 还有一个非常大的优点,他提供了一些 API、服务的定义,去抽象成 K8S 的 CRD,所以可以很方便地通过 K8S ingress 配置,同步到 Kong 的集群。
参考:https://github.com/Kong/kubernetes-ingress-controller/tree/main/docs
在DevOps集成中能做什么?
对于API网关和DevOps的协同,我在前面做过思考和整理如下。
我们首先看下什么时候需要涉及到API网关,在我们最初的概念里面是当一个业务应用需要对外发布API接口服务能力,这个对外发布可能是外部其它合作伙伴使用,也可能是我们自己的APP前端使用,只要存在这种场景往往就涉及到API网关的使用。
在一个大型项目的多团队协同下可以看到,如果都采用微服务架构,我们实际建议的是每个团队都是自己独立的微服务注册中心,负责团队内部多个微服务模块之间的API接口调用,这些API接口调用走注册中心即可。但是涉及到跨团队协同的API接口服务,那么就需要注册到API网关进行统一管理。
简单来说就是,对外发布API或者跨团队API接口调用都需要涉及将API注册接入到网关管理。
对于一个微服务模块和API网关的协同,包括了提供API接口服务注册和接入到网关,也包括了从网关调用API接口服务消费。因此需要从API注册接入和API消费调用两个方面来谈协同。
API注册接入
对于整个DevOps过程可以看到,底层是Docker容器+K8s资源调度,在我们编排流水线的时候涉及到编译构建和打包,部署等各个动作。实际上可以看到在完成自动部署后接口服务会暴露一个k8s提供出来的动态ip访问地址。而我们需要做的是将这个ip地址提供出来的访问接口,注册和接入到网关。
在整个过程搞清楚后,实际上我可以有两种方式来处理API注册接入。
- 在部署节点,增加自定义脚本编写,通过自定义运行的脚本来完成API接口服务的注册。
- 增加接口注册流水线编排节点,在部署节点完成后,编排注册节点,在API注册节点定义接口注册内容。
由于整个DevOps流水线设计和执行偏开发人员使用,可以看到,采用第一种方式往往更加灵活。唯一的就是在定义某一个流水线的时候,需要预先规划好需要接入和注册的接口内容。
而在DevOps支撑平台虽然不需要完整的API网关管控功能,但是最好还是增加一个功能,就是能够在DevOps支撑平台查询到当前已经注册和接入了哪些接口服务,注册接入后提供的代理服务地址是什么,是哪个微服务模块注册接入的该服务等基本服务目录信息。
基于前面思考,后续我们考虑就是实现Kong Ingress和K8s集群的集成,对于需要要注册的接口服务先写入配置文件,然后在K8s进行微服务部署或动态节点扩展的时候,通过API调用,将接口服务自动注册到API网关上面,实现对外访问。
API消费调用
注意在采用了API网关后带来的一个好处就是,API网关本身提供出来的API访问地址的IP是固定的,不会随着每次微服务模块的自动构建和部署动态变化。对于API网关我们会提前先部署到测试环境和生产环境,在网关部署完成后再开始进行各个微服务模块的持续集成和部署操作。
因此一个微服务模块需要访问其它微服务模块哪些接口,一个方法是每次都调用服务注册中心去查询具体的服务访问地址,一个方法就是本身要将访问地址存在在本地配置文件。更好的方法是:
- 首先调用先访问服务注册中心,获取服务访问地址,并存在到本地配置文件
- 在发现本地配置文件已经有服务访问地址后,不再从服务注册中心调用,除非得到地址变更消息通知
在这个确定后,微服务模块本身的构建打包和部署,实际上和原来没有和API网关协同是完全一样的,只是配置文件访问地址固定为了API网关提供的地址而已。如何知道API网关提供了哪些地址,即我们谈到的可以在API网关的管控平台查询,也可以在DevOps平台提供的服务目录查询功能上进行查询。