Nginx在微服务中的应用
导航: 这里将Nginx的一些配置进行整合。根据导航比较容易找到对应的文档。资料来自于weixueyuan 5.Nginx 缓存 |
近几年微服务(Microservices)技术迅猛发展,以 Spring Cloud 为架构方案、Kubernetes 为支撑平台成为微服务架构的主流实践方案。
微服务是以多个独立的个体部署的,因此微服务架构给运维工作带来了诸多挑战,相对于单体应用,微服务的业务调用链更长、调用关系更复杂、故障点更多。运维人员需要考虑更多运行可用性、连续性、容量伸缩及响应速度方面的工作。
Kubernetes 被认为是目前最成功、影响也最大的微服务支撑方案,它提供了资源调度、弹性伸缩、自动化部署等功能,并完美解决了负载均衡、集群管理、有状态数据的管理等微服务面临的问题,成为企业容器化微服务的首选解决方案。
在微服务架构体系中,Nginx 也基于其自身优势不仅在 Kubernetes 系统中以 Ingress 的方式提供服务入口应用,更在微服务网关等核心组件中发挥着重要的作用。
1.微服务是什么?
计算机自诞生以来,极大地影响了人类的生产和社会活动,软件生产以一种生产活动的方式进入了人们的生活。软件生产是知识密集型的智力活动,生产过程仍以手工劳动为主。随着软件生产活动的发展,不同时期生产力的需求促进着生产方式的变革,表现形式从程序设计方式逐渐转变为应用架构的创新,微服务便以应用架构的创新形式随着软件生产的发展逐渐演变而来,成为软件生产发展的必然产物,同时也是软件开发过程中的必然需求。
1.1 微服务是软件生产发展的必然产物
软件生产以程序设计为生产方式的表现形式发展经历了 3 个时期,分别是程序设计时代、软件系统时代和软件工程时代。
1) 程序设计时代(20世纪50年代~20世纪70年代)
程序设计时代的软件生产仅是程序设计,主要是按照需求编写用来计算的数学程序,编写者和使用者通常为同一人或同一组人,是一种自给自足、个体手工劳动的生产活动。编程语言主要以早期的命令式程序设计语言为表现方式。机器语言及汇编语言是最早的命令式语言,最早诞生的高级语言 Fortran I,也是一种命令式语言,命令式语言是基于动作的语言。
以冯·诺依曼计算机体系结构为背景,计算机被看作是动作的序列,程序就是用语言提供的操作命令书写的一个操作序列。命令式语言支持自然公式语法,如使用几条命令让计算机完成数学计算等,现在的高级语言都支持这种命令式语言的设计风格。
2) 软件系统时代(20世纪70年代~20世纪90年代)
软件系统时代有了数据结构的概念,程序与数据构成了软件,同时也产生了职业的软件开发人员,并由个体手工劳作逐渐转变成作坊式合作的真正软件生产活动。由于高级语言的诞生和 20 世纪 60 年代中期计算机硬件的飞速发展,计算机的应用领域及需求不断扩大,软件成为一种商品。这一时期由于生产力需求的增加,落后的程序设计方法严重阻碍了生产力的发展,导致了第一次软件危机的爆发。
为解决这一问题,人们提出了结构化程序设计方法,结构化程序设计约定软件开发者采用自上而下、逐步求精、模块化的方式进行程序设计,整个程序的各个模块通过顺序、选择、循环的控制结构进行连接,只有一个入口和一个出口。模块化的设计实现了有效的工作分工,每个模块可以被不同的人编写、重用并独立测试,使生产力得到巨大提升。结构化程序设计的典型代表莫过于C语言,每个程序由多个源文件构成,每个源文件就是一个模块,不同的源文件被入口文件
main.c 引入后,通过控制结构实现模块功能的调用。
3) 软件工程时代(20世纪90年代至今)
软件工程时代的软件生产引入了软件工程的概念,软件生产被定义了生命周期,程序开发也被要求遵守系统化、规范化、数量化的工程原则。随着社会的发展,人们对软件的需求量剧增,软件的复杂度也越来越高,大规模软件常常由数百万行代码组成,参与程序开发的人员数以百计,结构化程序设计的问题日益凸显。此时,以面向对象程序设计为代表的新的生产方式适应了生产力发展的需求,成为人们的新选择。
面向对象程序设计是将结构化程序设计中的数据及与数据有关的函数集成在一起,形成对象,而对象的类型就是类,类中可以定义方法和属性等,并将结构化程序设计中主程序与子程序间的从属关系,变为对象间相互发送消息的平等关系。现今流行的开发语言大多是面向对象的程序设计语言,采用面向对象程序设计思想开发的外部文件里可以有更加复杂的方法。通过类、方法、属性的定义,使其可以处理更加复杂的场景。
早期,软件生产方式变革的重点都在数据结构和算法的选择上,随着软件系统规模的变大及处理的场景越发复杂,软件生产进入软件工程时代,整个软件系统的架构设计和规范变得越来越重要,数据库、网络、分布式等应用架构技术也成为软件设计的重要组成部分。软件生产方式的表现形式逐渐由内在的程序设计向外在的应用架构转变,曾被使用的应用架构有垂直应用架构和面向服务架构。
① 垂直应用架构
最早的 LAMP(Linux、Apache、MySQL、PHP)是一种非常原始的垂直架构模式,由于早期互联网公司的业务规模小,LAMP 在很长一段时间内十分流行。随着互联网应用规模的增长,分层模式的垂直应用架构得到了广泛应用。
分层模式的最典型模型就是
MVC(Model-View-Controller)模型,MVC
模型充分利用面向对象的封装、继承及多态特性,把代码结构分为展示层(View)、控制层(Controller)和模型层(Model),控制层负责处理用户请求,通过模型层获取数据,并经过展示层渲染后展示给用户。MVC模型下的应用代码通常打包在一个发布包中进行部署。在高并发场景下,会使用 Nginx
等负载均衡对部署在多个机器上的应用进行负载分流。
② 面向服务架构
面向服务架构(Service Oriented Architecture,SOA)是一种松耦合、粗粒度的以服务为中心的架构,以服务为基本的业务功能单元,由平台接口契约来定义,将业务系统服务化。按照这种方式,可以将程序代码中的不同模块解耦,并通过网络实现服务调用、消息交换和资源共享。它的关注点是服务,注重服务的可用性、松耦合的独立性、可任意组合编排、无状态且可被自动发现,所有服务间可以通过网络、注册中心或企业服务总线(ESB)等技术方式进行通信。
上述两种应用架构下的每个功能应用仍以单体应用的方式存在,当软件规模复杂时,代码的复杂性、维护性、创新性、可扩展性等问题依然存在。
微服务架构基于面向服务架构,既在程序设计方法上限制了对象类模块的无限增长,使代码的复杂性得到有效控制,便于开发人员维护和扩展;同时又以服务化的形式增强应用架构的整合,使不同的应用以服务化的形式更易相互调用以实现不同的功能。微服务架构使软件生产方式再次发生变化,提升了软件生产效率,成为软件生产发展中的必然产物。
1.2 微服务是软件开发过程的必然需求
用户需求不明确是软件生产供需矛盾的一个重要表现形式,自软件诞生以来就一直存在于软件开发过程中。用户在见到开发出来的软件前,通常自己也不清楚软件的具体需求,对软件需求的描述也不够精确,甚至可能存在很多错误的描述。用户在软件开发期间也会有变更需求的情况发生,甚至因与开发人员所处的业务领域不同导致彼此间对需求的理解存在很大的差异。
首先,我们要认识到这种需求不明确存在的客观性,因为它不只存在于软件生产活动中,还是一种客观的社会环境特点。这一社会环境特点被称为 VUCA,VUCA 是易变性(Volatility)、不确定性(Uncertainty)、复杂性(Complexity)、模糊性(Ambiguity)的缩写。
VUCA 是如今整个社会环境的特点,尤其是信息科技方面,随着科技进步及互联网的快速发展,社会正处于信息化爆炸的年代,大数据、云计算、物联网、人工智能快速发展,人们对未来充满未知和疑惑,各种需求更加模糊、复杂且具有极大的不确定性。
在软件开发过程中,人们一直以不同的软件过程模型进行开发过程革新。最早出现的软件开发模型是瀑布模型,它以一种预见式的方式向用户确认需求,将开发周期从一个阶段向下个阶段逐级过渡。瀑布式的软件开发过程缺乏灵活性,当遇到用户需求不明确的问题时这一缺点最为突出。
敏捷开发模型顺应时代的发展成为人们的首选,在它的迭代式开发模式下,开发工作被组织为一系列称为迭代的实现周期短小、固定长度的小项目,每次迭代都包含需求分析、开发、测试等一系列动作,通过迭代开发的方式可以在每次迭代时向客户细化需求,并不断调整开发过程,使其更接近用户需求。
敏捷开发下的迭代周期都很短,通常在两周或三周左右。对于每次需求的变化,软件开发都要以最快的速度去调整。为适应这种变化,软件在程序设计上需要有更多可被重用的模块,应用架构方面也要能够应对其不断拆分或重组带来的变化。
微服务应用架构下,构成服务的粒度较小,代码逻辑简单、易维护、可替换性强。每个微服务都是独立运行的,每个产品功能由一个或多个微服务共同实现,对功能需求的变更只需增加或修改对应的微服务即可,其完全满足了敏捷式开发的需求,成为软件开发过程的必然选择。
1.3 微服务的技术特点
微服务是独立运行的、可被访问的服务单元。微服务架构是一种应用架构,架构中每个微服务可以独立部署,彼此之间是松耦合的。它集成了面向服务架构的诸多优点,且更注重以服务为单元的低复杂度、小体积形态,每个微服务代表一个较小的业务能力,多个不同的微服务可以被组织成可实现更复杂功能的集合。
微服务适应了客观社会环境,能够有效满足敏捷开发的需求。Spring Cloud 是一套完整的微服务架构实践方案,它利用 Java 语言 Spring Boot 框架的开发便利性,使开发人员的开发项目与其提供的各微服务组件可以很方便地进行集成,使微服务架构的项目可以快速实施。本节便以 Spring Cloud 集成的常见的微服务架构组件为例,介绍微服务架构的技术特点。微服务架构的主要技术特点如下。
1) 服务注册发现
微服务架构中,为确保每个服务的高可用性,每个微服务都由多个部署相同代码的节点构成,每个微服务都会把自己的所有节点注册到注册中心。对于服务调用方,可以通过注册中心查询并发现期望调用服务的节点调用地址,以实现服务访问通信。注册中心会提供相应的检测机制,以确保被发现的节点地址是可用的。常用的服务注册与发现组件有 Spring Cloud Euraka、ZooKeeper、etcd、Consul。
2) 服务网关
微服务架构中,每个微服务都提供了一个小的应用功能,对于客户端来讲,要想完成一个较复杂的功能需要调用不同的微服务。为了便于客户端的访问及访问管理,在客户端和服务端之间增加了服务网关组件。
服务网关为所有的微服务提供了一个唯一的入口,通过不同的路径将客户端的请求路由到不同的内部服务。通过服务网关还可以提供统一的用户鉴权、跨域访问、流量管控及数据整形等功能,既方便了微服务之间的访问,又减轻了开发工程师的工作量。常见的服务网关组件有 Spring Cloud Zuul、Kong(基于 OpenResty)及 Gravitee。
3) 配置中心
在传统模式下,每个应用都会存在对运行时的数据库、Redis等组件或不同硬件配置下的运行参数进行修改的需求,这些修改都以配置文件的形式保存在代码包中。当每个微服务被拆分为更小的体积并独立部署时,部署节点的数量急剧增加,每个节点配置的修改也变得非常复杂。为了方便配置的修改,配置中心提供了一种配置文件与应用代码分离、集中修改的方法实现配置修改操作。每个服务将配置存储在配置中心,在每次启动时按需读取配置内容,完成配置加载的需求。常见的组件有 Spring Cloud Config、Apollo 及 Disconf。
4) 服务容错保护
微服务架构将原有的单体应用拆分为多个可独立运行的服务,使很多以前在单应用内存级的调用变成了网络调用。由于网络调用的不确定性或被调用方的可用性等因素极大地增加了访问响应延迟等问题的发生,相应地,调用方自身在等待期间无法响应上级服务的当前调用,若此时仍不断有相同的请求被发送过来,便会造成请求积压,甚至导致服务瘫痪。
基于这种考量,Spring
Cloud 在微服务架构中提供了断路器、线程隔离等一系列服务容错保护机制,以对调用的请求进行监控,当下游请求出错达到阈值时,将自动启动熔断,不再调用下游服务直接返回错误信息,当检测到下游服务器恢复时,则继续向下游服务器发送请求。常见的服务容错保护组件有 Spring Cloud Hystrix、Linkerd、Istio。
5) 分布式链路跟踪
在单体应用拆分为多个可独立运行服务的微服务架构中,服务节点不断增加,服务间的调用关系变得越发复杂。通常一个客户端请求会引发多个及多层级服务的调用,期间除了需要对容错保护机制进行监控,还需要对因调用关系而引发的链路性能进行分析监控。分布式链路跟踪会对客户端访问的每个请求创建一个唯一的跟踪标识,当请求在访问链路中流转时,跟踪系统将根据该跟踪标识实现对每个请求链路的监控。这些监控信息可以包括访问路径中的服务名称、请求耗时、方法错误等。常见的分布式链路跟踪组件有 Spring Cloud Sleuth、Jaeger和Zipkin。
6) 微服务进程间的通信
微服务是通过网络实现通信的,服务的相互调用是进程间的通信调用。对于进程间的通信在通信机制上有两种,一种是 IPC(Inter-Process Communication)机制,其以 REST 风格为代表,并完全通过 HTTP 协议实现,相对更加通用、规范;另一种是 RPC(Remote Procedure Call)机制,典型应用是 Google 开源的 gRPC 框架,它基于 HTTP/2 协议,使不同服务间的进程可以像调用本地方法一样调用远程方法。
很多语言都支持这两种机制的实现,不同语言编写的服务都可以实现跨语言的进程间通信。在通信模式上有同步和异步之分,在同步模式下,服务间调用需要被调用方即时响应,在高并发场景下会出现阻塞;在异步模式下,服务间通过消息组件实现间接通信,可有效避免阻塞,同时还支持一对多的通信实现,常用的消息组件有 RabbitMQ、Kafka。
7) 支撑平台
碎片化是微服务的主要特征,因而微服务及微服务架构的运维变得更加复杂。容器化技术以进程级别虚拟化使每个微服务运行在传统物理机上,基于容器的管理系统 Kubernetes 为微服务提供了自动化的管理解决方案。Kubernetes 提供了包括自动化部署、运维、监控、负载均衡、灰度访问等功能,有效解决了碎片化微服务的运维管理问题。
1.4 微服务的进化
微服务架构技术仍在不断创新,人们围绕微服务不断提出不同的部署和使用方式,使得微服务架构技术不断进化。
1) 服务网格
服务网格(Service Mesh)是一种微服务架构形式,它将微服务独立运行时所依赖的服务组件功能与业务进程分离,使其作为一种可配置的基础设施层存在,每个微服务都包含一个基础设施,并在微服务间为业务进程提供快速、可靠、安全的通信保障。
被分离的基础设施叫作 Sidecar,它实现了服务发现、负载均衡、链路跟踪、访问日志、身份验证、授权及容错保护等功能,使业务进程只关注于具体业务的实现即可。服务网格起源于开源项目 Linkerd,并因 Google 联合 IBM、Lyft 发起的 Istio 项目得到广泛推广。
Istio 是基于 Kubernetes 容器管理框架实现的,并与 Kubernetes 系统实现了紧密的结合,它使用了 Kubernetes 的服务名及服务发现机制。Istio的Sidecar 可实现自动注入 Pod,并使集群内服务间的通信完全可被 Istio 监控。
-
- Istio 分为控制面板(Control Plane)和数据面板(Data Plane)。
- 控制面板负责实现与用户间的交互,实现监控数据的展示和数据面板相关配置的修改及存储。
- 数据面板由每个微服务的基础设施(Sidecar)组成,其负责与控制面板间的通信及具体微服务进程间的通信基础功能的实现,Istio 的 Sidecar 是通过 Envoy 实现的。
- 在 Kubernetes 中部署 Istio 后,Service 间的通信将不再通过 Kube-proxy,而是被 Istio 通过 iptables 规则转由 Sidecar 接管。
服务网格将 Spring
Cloud 微服务架构中诸多组件通过基础设施层利用 Kubernetes 系统的特点注入微服务每个节点的 Pod 中,该方式对业务代码无侵入性,使开发工程师可以更专注于业务功能的实现,极大地减轻了进行软件开发的工作量,提高了软件生产效率。
2) 无服务器化
无服务器化(Serverless)并不代表没有服务器,服务器作为底层资源仍是软件运行的基础,它并不是不需要服务器,而是共享服务器资源。每个用户只需要考虑自己业务应用所需要的计算资源,而不需要关心其运行在什么样的服务器上。无服务器化是公有云产品的一个延伸,它极大地改变了程序设计的方法,对于非无服务器化下的程序开发,开发工程师需要对实现的业务代码加载诸多基础函数、进行打包编译和部署发布等一系列的操作。
无服务器化则使开发工程师只需考虑具体代码的实现,甚至可以仅提供一段函数代码,就可以由无服务器化云平台完成一系列部署、发布、运行等操作。开源无服务器化应用 Kubeless 是基于 Kubernetes 系统实现的,它支持 Python、Node.js、Ruby、PHP、Go、.NET 等语言的运行时(runtime),也支持自定义运行时的方法。
当用户提交一段函数代码或文件后,它会将这段函数与其依赖的运行时封装成可运行的服务,并以 Pod 的形式运行在 Kubernetes 集群中,调用方只需要通过 Kubernetes 提供的 Service 及 Ingress 提供的端口,使用基于 HTTP 的 REST 方式即可实现相应函数方法的调用。
无服务器化架构方式更细粒度地拆解了微服务,它使每个函数都可成为一个微服务的最小功能单元,极大减少了开发工程师所需考虑的非业务类额外因素,更包括代码可复用的公共组件等,使开发工程师们更专注于业务功能的实现,可以更快速地完成开发任务。
3) 持续进化
微服务概念自出现以来,大家一直在思考什么是“微”,就是微服务到底有多小、如何对现有的单体应用进行拆分。这个问题似乎很复杂,也让初识微服务的人对其望而却步,但无论是 Spring Cloud 架构、服务网格还是无服务器化都是将软件生产过程中可被重用的部分与业务代码分离,其本质上仍是结构化程序设计思想的延续,就是将复杂任务按照功能进行拆分,逐步细化并通过模块化的方式提高代码的可重用性,可将这类微服务架构统一称为结构化微服务架构。
在我们的认知中,我们周围所有客观存在的都是物质,每个物质都有它的物理属性和化学属性,分子、原子、离子是构成物质最基本的微粒。在自然界,物质的种类形态万千,物质的性质多种多样,但它们都有其特性,那就是客观存在,并能够被观测。我们可以将微服务架构中的微服务看作一个物质对象,微服务的名称、分类、接口地址、参数说明被定义为它的物理属性,微服务的接口被传递不同参数时产生的不同返回结果被定义为它的化学属性。
对象类是组成微服务物质的分子,具有网络服务接口能力的一个或多个对象类构成一个微服务。这是以面向对象的思想构建微服务架构,多个对象类达到一定的规模就变成了单体应用,多个微服务之间被按照微服务架构的规则自动注册、彼此发现、共享数据、进行统一路由管理等则构成了更复杂的服务。
微服务在我们的现实世界里还需要不断进化,它已经变成客观存在,但以面向对象微服务架构的思想来看,它还不具备可被观测的特性,每个微服务的物理及化学属性应该形成一种标准和规范,可以在一定的授权范围内被用户观测和使用。例如 REST 风格或 gRPC 都是微服务化学反应的一种进程通信机制,无论使用哪一种,都应是物理属性中被声明的一部分,可以被外部用户直接观测。
人工智能技术已渗透到我们每个人的生活之中,未来计算机科学的各种应用都将以人工智能技术的方式体现。可被观测是微服务的一种基本特性,能够主动交流才是智能的体现,在具有智能特征的微服务架构体系中,每个微服务都应该可以智能地告诉服务中心:我是谁、我能做什么、如何和我交流并产生化学反应以及我的进化史。
以公司为实体范围的内部用户将共享每个微服务提供的功能,用户通过服务中心检索每个分类的微服务,并按照自己的需求组装更复杂的功能。当网络中不存在符合功能的微服务时,工程师们可以根据需求添加新的微服务或对相似的微服务进行升级。服务中心管理着每个微服务的版本,并根据智能算法和微服务提供的测试声明确保其化学属性的可用性。
每个微服务均以对象类为最小粒度进行构建,当功能扩展的版本升级后,被智能中心扫描发现所包含的对象类达到技术体系约定的数量时,便会被要求拆分为多个独立的微服务。由于微服务的体量足够小、更加便于阅读,所以每个工程师将不再受传统部门或项目组的约束,其可自由地添加或更改自己所需要的微服务版本,包括更换为自己熟悉的编程语言。每个微服务接口名称将像物质分类一样被社会标准统一制定,即便开发人员遇到跨领域的开发需求,也只需在服务中心检索通用类目获得相应解释和定义,并按照约定的名称定义接口。
总之,自然界中的物质形态万千,同样,微服务应用的功能也是无穷无尽的,所以按照应用的功能进行拆分是无法找到固定拆分方法的,只要以对象类为最小维度构建,并确保其有物理和化学属性的特征,就可以构建一个微服务。笔者认为面向对象的微服务架构将是微服务进化的方向,微服务的粒度也只应与包含对象类的数量有关。
2.Nginx Kong:微服务网关简述
Kong 是一款开源的 API 平台,它是基于 Nginx 扩展版 OpenResty 的 Lua 应用,其将 Nginx 的配置解构成多个 Lua 应用模块,通过 Lua 应用实现了 Nginx 中各请求阶段的操作。Kong 把 Nginx 操作的配置存储在外部数据库中,并提供了 REST 风格的管理接口,用户可以通过管理接口实现 Kong 所有功能的动态操作。
Kong 支持 PostgreSQL 和 Cassandra 两种数据库,可以通过数据库的主从同步或分布式部署实现配置数据的高可用,多台 Kong 服务器通过数据库共享配置数据,实现对多台 Kong 服务器的统一配置管理。Kong 提供了基于 Lua 脚本实现的多种功能插件,在将用户请求转发给后端服务之前,用户可使用这些插件实现用户请求的认证、访问限流、链路跟踪、日志处理等各种操作。
Kong 是一个微服务网关平台,它作为微服务 API 的统一入口对外提供服务,为方便 API 的管理,定义了如下术语。
2.1 消费者
Kong 系统中,把访问微服务 API 的用户定义为消费者,用户可以通过消费者对象定义消费者身份,并可通过相关插件实现消费者访问路由规则或服务的授权。
2.2 消费者接口
消费者接口是消费者访问微服务
API 的接口,用于实现后端被代理目标的访问转发。
2.3 管理接口
管理接口是进行 Kong 功能配置的接口,可通过管理接口对操作对象进行配置,其约定了 REST 风格的语法,用户可以很容易地通过管理接口实现对 Kong 的功能配置。
2.4 操作对象
Kong 为方便实现 Nginx 配置的动态管理,定义了多个操作对象和对象参数,通过管理接口对不同的操作对象按照该对象的对象参数进行配置,可以非常快速地完成 Kong 的管理操作。Kong 常用的操作对象有目标(target)对象、上游(upstream)对象、服务(service)对象、路由(route)对象、消费者(consumer)对象、插件(plugin)对象、证书(certificate)对象、CA 证书(CA Certificate)对象、SNI 对象。
目标对象和上游对象构成真实的被访问服务器集群,可通过上游对象实现目标对象的负载均衡、会话保持等配置。路由对象和服务对象构成了 Nginx 虚拟主机的访问入口路由和转发目标的配置,服务对象可以直接代理一个外部主机域名,也可以直接关联上游对象实现用户请求的转发。
插件对象由不同的功能插件脚本组成,其可以与路由对象、服务对象及消费者对象关联,实现消费者对象请求转发给后端服务之前的各种功能操作。消费者对象用于描述客户端标识,通过认证及 ACL 插件可以对其进行访问认证和访问路由对象或服务对象的授权。证书对象、CA 证书对象、SNI 对象均用于 SSL 相关配置。
由于管理工具 Konga 基于管理接口提供了更加方便的 Web 化操作方式,这里为方便读者理解和操作,便直接使用 Konga 配置界面的对象参数介绍 Kong 的相关操作对象和对象参数。
1) 目标对象
目标对象等同于 Nginx 配置中上游服务器的主机,一个上游对象可以关联多个目标对象,目标对象的配置是动态即时生效的。由于上游对象需要维护目标对象的变更记录,因此目标对象只能手动或通过管理接口 DELETE 方法设置权重为 0。目标对象的对象参数说明如下表所示。
参数名 | 参数说明 |
Target | 被访问的真实服务器,可以是 IP:Port,默认端口为 8000,也可以是域名 |
Weight | 当前目标对象在上游对象中的权重,默认为 100,取值范围为 0~1000 |
2) 上游对象
Kong 的上游对象用于描述 Nginx 配置指令域 upstream 的配置内容,Kong 支持对其所关联的目标对象进行主动或被动健康检测的设置。Kong 为方便上游对象及其关联目标对象的管理,通过 Lua 脚本实现了加权轮询(round-robin)、一致性哈希(consistent-hashing)、最少连接(least-connections)负载均衡算法,默认为轮询。
一个上游对象由多个目标对象组成,可以通过管理接口实现目标对象的动态变更。通常在一致性哈希算法和加权轮询负载策略下,目标对象数量的动态变化会引起负载策略的重新计算,虽然这种影响无法避免,但为了降低因负载算法重新计算产生的影响,Kong 为每个上游对象定义了一个环平衡器(ring-balancer),每个环平衡器有预先定义好数量的插槽(slot),上游对象中的每个目标对象将根据其权重被分配到相应数量的插槽,当目标对象数量变化时,只需对部分目标对象重新分配插槽而不需要负载策略的重新计算。
环平衡器只有在上游对象更改总插槽数时才会进行负载策略的重新计算,目标对象初始分配的插槽数官方建议至少为 100 个,当上游对象预期为 8 个时,即使初始时为两个目标也至少应将总插槽数定义为 800。上游对象的对象参数说明如下表所示。
参数名 | 参数说明 |
Name | 上游对象实例的名称 |
Hash on | 设置是否启用一致性哈希负载,选项为 none 及哈希类型 consumer、ip、header 或 cookie,默认为 none,即仅使用加权轮询对目标对象实现负载 |
Hash on header | 当哈希类型被设置为 header 时,此参数用于指定请求头字段的名称 |
Hash on cookie | 当哈希类型被设置为 cookie 时,此参数用于指定 cookie 字段的名称,如果请求的 cookie 中无此字段,Kong 将为该字段生成一个值并在响应头中设置客户端 cookie |
Hash on cookie path | 当哈希类型被设置为 cookie 时,此参数用于指定 cookie 路径的值,默认为 / |
Hash fallback | 如果启用一致性哈希负载策略,若用户的请求无法计算哈希值时,此处可指定一个备份哈希类型,选项为 none、consumer、ip、header 或 cookie,默认为 none。如果哈希类型设置为 cookie,则参数无效 |
Hash fallback header | 当备份哈希类型被设置为 header 时,此参数用于指定请求头字段的名称 |
Slots | 上游对象环平衡器的总插槽数,取值范围为 10-65536,默认值为 1000 |
3) 服务对象
Kong 中的服务(Service)对象是指被代理的服务目标,既可以是一个域名,也可以是一个上游对象的名称,区别在于是否由 Kong 实现负载均衡。每个服务对象可以关联多个路由对象。一个服务对象只能关联一个上游对象或被代理的主机域名。服务对象的对象参数说明如下表所示。
参数名 | 参数说明 |
Name | 服务对象实例名称 |
Description | 服务对象实例描述 |
Tags | 服务对象实例标签 |
Url | 为方便配置,通过 URL 形式描述被代理主机的通信协议(Protocol)、主机(Host)、端口(Port)及路径(Path)的配置 |
Protocol | 与被代理服务目标的通信协议。可选项为 http 或 https |
Host | 上游对象名或被代理的主机域名 |
Port | 当前主机端口。默认为 80 |
Path | 被代理主机的访问路径。默认为空 |
Retries | 访问失败时执行重试的次数。默认为 5 |
Connect timeout | 与被代理主机的连接超时时间,单位为毫秒。默认为 60000 |
Write timeout | 与被代理主机的连续写超时时间,单位为毫秘。默认为 60000 |
Read timeout | 与被代理主机的连续读超时时间,单位为毫秒。默认为 60000 |
4)路由对象
路由(Route)对象用于表示 Nginx 配置中虚拟主机的配置,对应 Nginx 的指令域 Server 及其包含的 location 配置。Kong 配置结构中,因为服务对象用于关联被代理的目标,而路由对象单独存在没有意义,所以其必须与服务对象关联使用。路由对象的对象参数说明如下表所示。
参数名 | 参数说明 |
Name | 路由规则名称 |
Hosts | 当前路由规则匹配的虚拟主机名称,同 Nginx 配置指令 server name 的设置。该指令值是个列表值,可以输入多个 |
Paths | 当前路由规则匹配的路径,同 Nginx 配置指令 location 的配置。该指令值是个列表值,可以输入多个 |
Regex priority | 在同一虚拟主机下,当用户请求的路径被正则匹配到多个时,将只匹配该设定值最低的路由规则,默认值为0 |
Methods | 当前路由规则允许的 HTTP 请求方法。它是个列表值,可以输人多个 |
Strip Path |
当用户请求匹配当前规则时,设置是否在转发给服务对象的请求 URL 中包含当前路由规则匹配的路径。默认值为 false, 表示保持用户请求的 URL 不变。当指令值为 true 时,相当于把用户请求 URL 中的当前路由规则匹配的路径替换为空。 当代理协议为 GRPC 或 GRPCS 时不能使用该参数 |
Preserve Host |
设置是否将用户请求当前虚拟主机的域名作为转发给服务对象的请求头中 Host 的值。默认为 false,当设置为 true 时, Kong 发送给服务对象请求头中 Host 的值为当前用户请求的域名 |
Protocols |
当前路由规则支持的用户访问协议,该参数选项是个列表值,默认为 ["http","https"],可以输入选项为 http 和 https, 当只选 https 时,所有 HTTP 请求都将跳转到 HTTPS |
5) 插件对象
插件对象用于对用户在消费接口的请求/响应闭环中的不同插件执行方法进行配置,不同的插件与路由对象、服务对象及消费者对象关联,实现对消费者对象在 Nginx 中各请求阶段的相关操作。
Kong 的插件对象既可以关联到服务对象,实现所有该服务的请求控制,也可以关联到路由对象,仅对某些路由接口的请求进行控制,甚至是更细粒度的,仅对指定的消费者进行控制。一个插件在一个请求的生命周期中只运行一次,当一个插件被与多个操作对象关联时,与路由对象、服务对象及消费者对象这3个对象关联的越具体则执行优先级最高,插件的全局配置优先级最低。
6) 消费者对象
消费者对象是描述用户身份的对象,通过认证及 ACL 插件可以对其进行访问认证和访问路由对象或服务对象的授权。
7) 证书对象
证书对象表示 HTTPS 域名关联的证书,证书对象用于存储 SSL 证书的公共证书/私钥对。Kong
使用这些对象来处理加密请求的 SSL 终止。
8) CA证书对象
CA 证书对象表示受信任的 CA。Kong
使用这些对象来验证客户端或服务器证书的有效性。
9) SNI对象
Kong 的 SNI(Server Name Indication)对象可与证书对象进行关联,将证书/密钥对绑定到一个或多个域名。SNI 是一种改善 SSL/TLS 的技术,用于对客户端请头中 Host 字段进行处理,通过对 Host 字段的识别解决了当一个服务器绑定多个域名时 SSL 证书选择的问题,服务器将根据 Host 字段的域名返回该域名的 SSL 证书。
3.Nginx Kong(微服务网关)安装部署
Kong 可以灵活地部署在用户的局域网中,其同样支持多种部署方式,官方在 DockerHub 上提供了 Docker 镜像,方便用户快速实现 Kong 的 Docker 化部署。部署步骤如下。
3.1 初始化系统环境并安装 Docker 应用
配置样例如下:
# 安装yum工具 yum install -y yum-utils # 安装Docker官方yum源 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo # 安装Docker及docker-compose应用 yum install -y docker-ce docker-compose # 设置Docker服务开机自启动 systemctl enable docker # 启动Docker服务 systemctl start docker
3.2 Kong 应用部署
Kong 将 Nginx 的配置存储在外部数据库,可以通过自带的数据库初始化命令自动完成数据库表结构的创建和初始数据的添加,为方便一次性创建,该脚本会启动独立的容器 kong-migrations 来完成此项操作。此处脚本创建的 Kong 规划为主管理服务器,管理接口不提供外部访问,仅提供在同一虚拟网络内的 Web 工具的访问,因此设置为固定 IP。
Docker-compose 脚本内容如下:
version: '2.1' # 创建名为kong-net的虚拟网络 networks: kong-net: ipam: config: - subnet: 172.19.0.0/24 gateway: 172.19.0.1 name: kong-net services: # 创建用于数据库初始化的独立容器 kong-migrations: hostname: kong-migrations container_name: kong-migrations image: kong:latest command: kong migrations bootstrap depends_on: db: condition: service_healthy env_file: - .env_kong links: - db:db networks: - kong-net restart: on-failure # 创建Kong容器 kong: hostname: kong-nginx container_name: kong-nginx image: kong:latest depends_on: db: condition: service_healthy env_file: - .env_kong networks: kong-net: ipv4_address: 172.19.0.201 ports: - "8000:8000/tcp" # 用于监听HTTP协议的消费接口,实现用户请求的接入 # - "8001:8001/tcp" # 用于监听HTTP协议的管理接口,此处关闭外部访问 - "8443:8443/tcp" # 用于监听HTTPS协议的消费接口,实现用户请求的接入 # - "8444:8444/tcp" # 用于监听HTTPS协议的管理接口,此处关闭外部访问 # network_mode: host # 在高并发应用场景下,可以将Docker容器以host模式运行, # 提高传输效率 healthcheck: test: ["CMD", "kong", "health"] interval: 10s timeout: 10s retries: 10 restart: on-failure # 创建Kong的postgreSQL数据库容器 db: hostname: kong-postgres container_name: kong-postgres image: postgres:9.5 env_file: - .env_postgress healthcheck: test: ["CMD", "pg_isready", "-U", "kong"] interval: 30s timeout: 30s retries: 3 restart: on-failure stdin_open: true tty: true networks: - kong-net ipv4_address: 172.19.0.202 volumes: - /opt/data/apps/kong/postgresql/data:/var/lib/postgresql/data
环境变量文件内容如下:
cat>.env_kong<<EOF KONG_ADMIN_ACCESS_LOG=/dev/stdout KONG_ADMIN_ERROR_LOG=/dev/stderr KONG_ADMIN_LISTEN=0.0.0.0:8001 KONG_CASSANDRA_CONTACT_POINTS=db KONG_PROXY_ACCESS_LOG=/dev/stdout KONG_PROXY_ERROR_LOG=/dev/stderr KONG_DATABASE=postgres KONG_PG_DATABASE=kong-data KONG_PG_HOST=db KONG_PG_PASSWORD=kong KONG_PG_USER=kong EOF cat>.env_postgress<<EOF POSTGRES_DB=kong-data POSTGRES_PASSWORD=kong POSTGRES_USER=kong EOF
3.3 Kong 的 Web 管理工具 Konga
Konga 是基于 Node.js 开发的 Kong 开源管理工具,它不仅提供了 Kong 管理接口的全部操作对象的管理功能,同时还可以对多个 Kong 节点进行管理,包括 Kong 节点的备份、还原、健康监测等,还提供了多用户的功能,让 Kong 的日常管理操作可以更加方便灵活。
Konga 通过数据存储操作用户及 Kong 管理相关的配置,此处与 Kong 共用 PostgreSQL 数据库,可通过如下命令创建并初始化数据库实例 konga:
docker run --network kong-net --rm pantsel/konga -c prepare -a postgres -u postgresql://kong:kong@172.19.0.202:5432/konga
编写 docker-compose 脚本,脚本内容如下:
version: '2.1' services: # 创建konga容器 konga: hostname: konga container_name: konga image: pantsel/konga env_file: - .env_konga external_links: - kong-postgres:db ports: - "1337:1337/tcp" networks: - kong-net # 加入名为kong-net的虚拟网络 networks: kong-net: external: true name: kong-net
环境变量文件内容如下:
cat>.env_konga<<EOF DB_ADAPTER=postgres DB_HOST=db DB_USER=kong DB_PASSWORD=kong DB_DATABASE=konga NODE_ENV=production EOF
Kong 集群只需在其他服务器部署 Kong 节点并连接到同一个 PostgreSQL 数据库即可,Kong 为避免频繁地进行数据库连接,会将数据库的内容缓存在本机内存中,管理接口修改数据库配置后,Kong 的配置会在同步周期下一次开始时生效,同步周期可以通过配置文件 kong.conf 中的配置参数 db_update_frequency 进行修改,默认时间为 5 秒。
4.Nginx Kong(微服务网关)应用实例
作为一款微服务网关应用,Kong 通过插件功能实现了微服务网关的多种功能,此处分别以访问认证、请求终止、数据整形为例,为了方便读者理解和应用,此处均使用管理接口直接操作,功能参数仍以 Konga 页面显示的名称进行说明。
4.1 访问认证
Kong 提供了基本认证、密钥认证、OAuth2 认证、HMAC 认证、JWT 认证、LDAP 认证等多种方式的认证插件,此处列举常见的密钥认证方式配置。密钥认证插件参数说明如下表所示。
参数名 | 参数说明 |
consumer | 消费者 ID,如果为空则对所有消费者有效 |
key names | 密钥名称,用户可在请求头或请求参数中使用该字段提交密钥,该值为数组,可以添加多个名称。密钥名只能包含 [a-2]1a-2][0-91[uu] 和 [-] |
hide credentials | 设置是否将密钥名称字段传递给被代理服务器,指令值为 true 时将不向被代理服务器传递该字段 |
anonymous | 如果身份验证失败,则用作 anonymous 消费者的可选字符串(消费者对象实例 uuid)值。默认值为空,表示直接返回验证失败状态码 4××。此值必须引用 Kong 内的消费者 ID 属性,而不是 "custom id" |
key in body | 如果设置为 true,Kong 插件将尝试从请求体中读取密钥名称,支持请求体的 MIME 类型有 application/www-form-urlencoded、application/json 和 multipart/form-data |
run on preflight | 如果设置为 true,该插件将在请求之前运行。否则在请求任何阶段均被允许 |
接口认证是服务开发中常见的功能,Kong 插件的认证功能可以让开发工程师不必单独开发此功能,仅需选择使用 Kong 的认证机制或通过认证转发使用内部的认证服务器,让所有的接口服务很容易地实现统一认证的功能。在下面的配置样例中,在 Konga 中按照参数配置添加密钥认证插件,认证密钥名称为 apikey。
# 创建服务 curl -i -X POST \ --url http://10.10.4.8:8001/services/ \ --data 'name=baidu' \ --data 'url=https://www.baidu.com' # 创建路由 curl -i -X POST \ --url http://10.10.4.8:8001/services/baidu/routes \ --data 'name=baidu' \ --data 'paths[]=/v1/baidu' # 访问测试,确认路由规则 curl -i -X GET \ --url http://10.10.4.8:8000/v1/baidu # 关联插件到路由对象实例baidu curl -i -X POST \ --url http://10.10.4.8:8001/routes/baidu/plugins \ --data "name=key-auth" \ --data "config.key_names=apikey" \ # 创建消费者 curl -d "username=test123" http://10.10.4.8:8001/consumers/ # 创建消费者密钥 curl -X POST http://10.10.4.8:8001/consumers/test123/key-auth -d '' # 查看并获得密钥 curl http://10.10.4.8:8001/consumers/test123/key-auth # 消费者使用密钥访问 curl -i -X GET \ --url http://10.10.4.8:8000 \ --header "apikey: xKgpAM6qBQE3e8nrR51dIrK89ggRdelf"
4.2 请求终止
请求终止(request-termination)插件原设计场景是进行请求熔断等安全管理,但其同样适用于做依赖该接口的测试桩场景,通过 Kong 的请求终止插件可以非常快速地实现该功能,而且不需要做任何代码改动,测试桩的创建和撤销也非常简单。插件参数说明如下表所示。
参数名 | 参数说明 |
consumer | 消费者 ID,如果为空则对所有消费者有效 |
status code | 返回响应状态码 |
content type | 相应数据 MIEM 类型 |
body | 置响应数据内容 |
下面是一个测试桩的样例,该插件可以对当前接口的请求返回固定格式的 JSON 数据,该场景可以满足不同团队合作时在真实业务 API 代码开发完毕前,让合作方、前端及测试人员进行代码升级或测试。
# 创建服务 curl -i -X POST \ --url http://10.10.4.8:8001/services/ \ --data 'name=baidu' \ --data 'url=https://www.baidu.com' # 创建路由 curl -i -X POST \ --url http://10.10.4.8:8001/services/baidu/routes \ --data 'name=baidu2' \ --data 'paths[]=/v2/baidu' # 访问测试,确认路由规则 curl -i -X GET \ --url http://10.10.4.8:8000/v2/baidu # 关联插件到路由对象实例baidu curl -i -X POST \ --url http://10.10.4.8:8001/routes/baidu2/plugins \ --data "name=request-termination" \ --data "config.status_code=200" \ --data "config.content_type=application/json; charset=utf-8" \ --data "config.body={\"status\": 200, \"data\": {\"status_code\": 403, \"message\": \"测试数据\"}, \"message\": \"专业测试桩\"}" # 测试结果 curl -i -X GET \ --url http://10.10.4.8:8000/v2/baidu
4.3 数据整形
通常一套服务提供的 JSON 格式数据是固定的,但在多个团队的开发合作中,可能需要对接口数据返回格式有不同的需求,以往大家都希望用一个统一的标准进行规范化的 JSON 数据格式输出,但执行起来则会遇到诸多现实问题。
通过 Kong 的插件可以让使用方和供给方不必再为这种标准而纠结,开发人员不需要修改代码,仅需要简单进行 Lua 脚本编写就可以实现现有服务的供给或使用需求,这里使用 Kong 的第三方插件 API 转换(API Transformer)插件做样例在中间进行数据整形,大家也可以根据实际需求定制自己的 Kong 插件。API 转换插件功能参数如下表所示。
参数名 | 参数说明 |
consumer | 消费者 ID,如果为空则对所有消费者有效 |
request transformer | 请求数据整形脚本路径,必选项 |
response transformer | 响应数据整形脚本路径,必选项 |
http 200 always | HTTP 响应码总为 200 |
此处演示将管理接口返回的 JSON 数据格式修改为前端 jQuery 插件 DataTables 的数据格式。因 API 转换插件的 request_transformer 参数为必选项,即便不需要请求阶段数据整形,也要为此参数指定文件,如下样例中将创建一个返回空数据的 req.lua 文件。
# 安装插件 git clone https://github.com/qnap-dev/kong-plugin-api-transformer.git cd kong-plugin-api-transformer luarocks make # 启用插件,需要重启Kong才可生效 sed -i "/\"session\",/a\ \"api-transformer\"" /usr/local/share/lua/5.1/kong/constants.lua # 创建服务,代理目标为管理接口 curl -i -X POST \ --url http://10.10.4.8:8001/services/ \ --data 'name=adminapi' \ --data 'url=http://10.10.4.8:8001' # 创建路由 curl -i -X POST \ --url http://10.10.4.8:8001/services/adminapi/routes \ --data 'name=adminapi' \ --data 'paths[]=/adminapi' # 访问测试,确认路由策略 curl -i -X GET \ --url http://10.10.4.8:8000/adminapi # 关联插件到路由adminapi curl -X POST http://10.10.4.8:8001/routes/adminapi/plugins \ --data "name=api-transformer" \ --data "config.request_transformer=/etc/kong/scripts/req.lua" \ --data "config.response_transformer=/etc/kong/scripts/datatables.lua" \ --data "config.http_200_always=true" # 创建req.lua,此处需要在Kong系统中进行操作 mkdir -p /etc/kong/scripts echo "return true, \"\"" > /etc/kong/scripts/req.lua # 创建响应数据整形脚本datatables.lua,此处需要在Kong系统中进行操作 cat>/etc/kong/scripts/datatables.lua<<EOF local return_body = { data = {} } local _resp_json_body = ngx.ctx.resp_json_body return_body.data = _resp_json_body.data local i = 0; for _, obj in pairs(return_body.data) do # 此处可进行相关字段的变更或过滤 i = i + 1; end return_body["page_size"] = i return_body["recordsFiltered"] = i return_body["recordsTotal"] = i return true, _cjson_encode_(return_body) EOF # 访问测试,确认返回数据 curl -i -X GET \ --url http://10.10.4.8:8000/adminapi/routes
Kong 的插件都是基于 Lua 脚本实现的,通过 Nginx 可以实现用户请求过程中各阶段的数据操作,此处不再举例,大家可以根据实际需求灵活使用 Kong 的功能。