微服务动态路由实现:OpenResty+K8s

本文涉及的产品
云原生网关 MSE Higress,422元/月
注册配置 MSE Nacos/ZooKeeper,118元/月
服务治理 MSE Sentinel/OpenSergo,Agent数量 不受限
简介: 本文讲的是微服务动态路由实现:OpenResty+K8s,OpenResty是一个基于 Nginx 与Lua的高性能 Web 平台,其内部集成了大量精良的Lua库、第三方模块以及大多数的依赖项。

第一部分:OpenResty是什么

image

本文讲的是微服务动态路由实现:OpenResty+K8s,OpenResty是一个基于 Nginx 与Lua的高性能 Web 平台,其内部集成了大量精良的Lua库、第三方模块以及大多数的依赖项。用于方便地搭建能够处理超高并发、扩展性极高的动态Web 应用、Web 服务和动态网关。主要有章亦春维护。

OpenResty通过汇聚各种设计精良的 Nginx 模块(主要由OpenResty团队自主开发),从而将 Nginx 有效地变成一个强大的通用 Web 应用平台。右边的列表中的组件被用于构建OpenResty。

image

先通过一个hello world的例子,来对OpenResty有个大概了解。

编写一个nginx.conf,在nginx.conf使用了content_by_lua,主要是ngx.say(“

hello,world

”),然后使用该配置文件启动nginx。当浏览器访问 http://xxx.xxx.xxx.xxx时,会看到页面显示的是 hello,world。

通过这个例子大概可以看到OpenResty能做些什么事,可以直接在nginx.conf中通过编写Lua脚本,实现一些需要编写代码来完成的功能。后面我们会继续介绍如何使用OpenResty。

第二部分:为什么要需要OpenResty

image

先来看看遇到的问题,大家都知道K8s Service能够提供很强大的功能,通过提供ClusterIP可以作为Pod的对外访问接口,并提供软负载均衡。

但是Service的ClusterIP地址只能在集群内部访问,如果是集群外部的用户要如何访问Service呢?Kubernetes通过两种方式来实现上述需求,一个是“NodePort”,另一个是“LoadBalancer”。

我们现在用的是NodePort的方式来使得Service可以被外部用户访问,这样带来的问题是:
1.外部访问服务时需要带NodePort
2.每次部署服务后,NodePort端口会改变

对于这2个问题,我们选择的是使用Nginx做反向代理,给服务暴露的http的端口起一个端口名(如web),通过“http://web. svc01.tenant01.cluster01.devops.tp”来代替“http:// svc01.tenant01.cluster01.devops.tp:35089”去访问服务,这样对于用户就屏蔽NodePort,多次部署后用户也不需要知道新的NodePort。
image

前面介绍了遇到的问题:需要屏蔽NodePort,这里介绍下为什么需要OpenResty,引入了OpenResty后如何做动态路由。

按照设想希望用户通过输入“http://web.svc01.tenant01.cluster01.devops.tp”地址来访问服务,这样就可以对用户屏蔽NodePort。这样就需要一层host转换来实现动态路由,如果直接使用nginx,就需要动态的修改nginx.conf,这样带来的问题就是需要能够动态的对nginx.conf做内容增减(添加/删除服务时),以及需要同时修改多个nginx.conf。这个听起来好像也不算方便。

使用OpenResty的话,可以和Redis结合。Redis里保存了service的host和clusterip:port的映射,当用户访问“http://web.svc01.tenant01.cluster01.devops.tp”时请求会被OpenResty拦截,OpenResty根据请求的host到redis里查询对应的service的clusterip:port,根据clusterip:port再做一次upstream去访问K8s Service。如果没有找到host对应的value则会抛出相应的httpstatus code(500,400)。

image

前面介绍了OpenResty如何利用Redis中的数据做动态路由,那么Redis中的数据是在何时写进去的?
因为使用了Reids,服务信息维护也相对简单,只需要在服务有变更时去操作Redis的主结点进行信息的增/删即可。

现在在新一代里在以下几个时机会去操作Redis中的数据:

• 服务创建:在服务创建后,如果服务的端口名带有web,则会向Redis写入服务的域名(key)以及对应的clusterip:port(value)。
• 服务销毁:在服务删除前,删除Redis中相应的服务的域名。
• 租户拉黑:查找租户相应的所有环境[开发、测试、…..],把这些环境里的所有服务在Redis里的key加上“$_.”前缀。
• 租户恢复:将租户拉黑时修改的key,去掉“$_.”的前缀。
• 租户销毁:查找租户相应的所有环境[开发、测试、…..],把这些环境里的所有服务在Redis里的key删除。

第三部分:如何在K8s上部署OpenResty

image

前面介绍完大致思路,接下来就进入实际操作阶段,第一步就是制作镜像。

使用到的镜像为:
OpenResty1.9.15.1
Redis3.2.1
phpRedisAdminmaster
镜像制作完成后提交到镜像私库供后续使用。

镜像制作时需要考虑镜像的配置可以通过配置文件,命令行参数和环境变量的组合配置来完成。这些配置应该从image内容中解耦,以此来保持容器化应用程序的便携性。

所以我们在制作镜像时将配置文件和启动脚本可以从外部mount,这样在调试时方便修改,不需要每次重新打镜像。

image

这里插播一下K8s ConfigMap,前面说了镜像制作时需要配置和镜像分离,那么在真正使用时,就需要将配置注入容器,这时候使用的就是K8s ConfigMap特性。

ConfigMap提供了将配置数据注入容器的方式,同时保持容器是不知道Kubernetes的。ConfigMap可以被用来保存单个属性,也可以用来保存整个配置文件或者JSON二进制大对象。

ConfigMap使用键-值对配置数据,这个数据可以在pods里使用。data 一栏包括了配置数据。就如同看到的那样,ConfigMap可以被用来保存单个属性,也可以用来保存一个配置文件。

配置数据可以通过很多种方式在Pods里被使用。ConfigMaps可以被用来:
• 设置环境变量的值
• 在容器里设置命令行参数
• 在数据卷里面创建config文件

在OpenResty部署中我们使用的是在数据卷里面创建config文件
image

先创建一个configmap目录,在configmap目录里有2个文件:
• redis.conf:保存的是reids的配置。
• run.sh:保存的是redis的启动脚本,根据环境变量来确定按那种模式启动redis。

通过使用”kubectl --namespace=euler-system createconfigmapsem-redis-configmap --from-file redis/configmap”可以将目录创建为ConfigMap。

ConfigMap里会有2个key,一个是”redis.conf”,一个是”run.sh”。 value分别对应的是文件内容。

创建完成后可以执行” kubectl --namespace=euler-system get configmapsem-redis-configmap-o yaml”查看ConfigMap的内容。

在部署时可以通过volume将ConfigMap的内容变成文件挂载到容器内。

image

Redis是按主从方式部署,主结点上还会安装phpRedisAdmin方便查看维护Redis的信息。从结点部署时需要指定需要关联主结点的服务名和端口号。

使用时需要注意volumes和volumeMounts。无论主从在部署时,都需要将ConfigMap作为一个volume,并且要将ConfigMap的key对应的内容保存成指定的文件名,如key=“redis.conf”,path=“redis.conf”表示将ConfigMap中key=”redis.conf”的内容保存到path=“redis.conf”的文件。

这个ConfigMap的volume会mount到容器内的一个目录”/app/configmap”。因为前面制作的镜像就会在/app/configmap目录下查找run.sh的启动脚本,并且脚本在启动时也使用到了/app/configmap/redis.conf的配置。这样就能正常启动。

这里没有使用Redis的sentinel,而是使用了K8s的RS来保证Redis主结点的可用性(Master停止后自动重启)。

image

步骤和创建Redis的ConfigMap一样,先创建一个configmap目录,在configmap目录里有2个文件:
• nginx.conf:保存的是nginx的配置。
• run.sh:保存的是nginx的启动脚本。

通过使用”kubectl --namespace=euler-system createconfigmapsem-openresty-configmap --from-file openresty/configmap”可以将目录创建为ConfigMap。

需要注意的是nginx.conf中的%resolver%,%redis_slave_svc_host%,%redis_slave_svc_port%
• resolver:是告诉nginx用哪个dns server去解析域名。在这里使用的是K8s集群里的skydns的地址。
• redis_slave_svc_host:指定需要连接到redis的slave的host地址。
• redis_slave_svc_port:指定需要连接到redis的slave的port。

这3个变量在容器启动时会由run.sh先进行变量替换,再启动ngixn
image

这里先介绍一下K8s Daemon Set,因为OpenResty的部署用到了Daemon Set,而不是Deployment。Daemon Set可确保所有的节点运行一个Pod。有新的节点添加到群集时,Pod会被被添加到其中。当节点从群集中移除,Pod会被删除。

DaemonSet的一些典型的用途是︰
• 在每个节点上运行群集存储守护进程,如 glusterd,ceph。
• 在每个节点上运行日志收集守护进程,如 fluentd ,logstash。
• 在每个节点上运行监控守护进程,如collectd,gmond。

可以看到主要用途是在每个节点上装一些守护进程,而我们的需求正好是在每个节点上都装一个OpenResty,这样经过前端DNS解析后可以转到任意一个节点的OpenResty。

本来打算是在每个节点上通过systemd管理这些服务,然后发现不是很方便,而K8s正好提供了Daemon Set,就用了Daemon Set。

image

OpenResty是按DaemonSet方式部署,注意kind是DaemonSet,然后需要设置REDIS_HOST和REDIS_PORT,告诉OpenResty需要连接哪的Redis。

需要注意volumes和volumeMounts。将ConfigMap作为一个volume,并且将ConfigMap的key对应的内容保存成指定的文件名,如key=“nginx.conf”,path=“nginx.conf”表示将ConfigMap中key=”nginx.conf”的内容保存到path=“ngixn.conf”的文件。

这个ConfigMap的volume会mount到容器内的一个目录”/app/configmap”。
因为前面制作的镜像就会在/app/configmap目录下查找run.sh的启动脚本,并且脚本在启动时也使用到了/app/configmap/nginx.conf的配置。这样就能正常启动。
image

到了这里OpenResty就部署完成了,可以看到在整个K8s集群中的每个monion节点上都部署了一个OpenResty的Pod,并在集群里部署了1个Redismaster Pod,2个Redis slave Pod。可以执行kubectl --namespace=euler-system get pod 查看namespace下的所有Pod。

当有K8s的Service被创建后,SEM会向Redis Master注册服务域名和clusterip:port的键值对。

这样用户就可以通过如“http://web.svc01.tenant01.cluster01.devops.tp”的url访问到服务了。

第四部分:新的选择Ingress

image

说是新的选择,不是指它是个新特性,是我自己知道的比较晚,原本以为ingress只能用于GCE/GKE环境,经我司春龙、潇男提醒,也可以用于本地环境。

一个Ingress(入口)是一系列允许访问集群服务的连接规则. 它可以为服务配置一个外部访问 url,负载均衡,SSL,以及提供基于名称的虚拟主机等。用户通过将入口资源发布到 API 服务器请求入口。进入控制器(Ingress Controller)负责履行入口,通常与一个负载均衡器一起工作。如在GoogleGCE上的Http Load Balancer,或者本地的Nginx。

IngressController 的大概工作流程是监控Ingress的变化,并将变化写Load Balancer的配置。在Nginx的Ingress Controller实现中会监听Ingress、Service、Endpoints、Secret对象的变化,并将变化写入nginx.conf文件,并重新加载nginx.conf。

上面的示例就是创建了一个Ingress,按照hostname和path可以将请求路由到K8s Service对应的Pod上。

image

今天的分享就到这里,谢谢大家。

原文发布时间为:2016-08-18
本文作者: 王文斌
本文来自云栖社区合作伙伴EAWorld,了解相关信息可以关注EAWorld。

相关实践学习
通过Ingress进行灰度发布
本场景您将运行一个简单的应用,部署一个新的应用用于新的发布,并通过Ingress能力实现灰度发布。
容器应用与集群管理
欢迎来到《容器应用与集群管理》课程,本课程是“云原生容器Clouder认证“系列中的第二阶段。课程将向您介绍与容器集群相关的概念和技术,这些概念和技术可以帮助您了解阿里云容器服务ACK/ACK Serverless的使用。同时,本课程也会向您介绍可以采取的工具、方法和可操作步骤,以帮助您了解如何基于容器服务ACK Serverless构建和管理企业级应用。 学习完本课程后,您将能够: 掌握容器集群、容器编排的基本概念 掌握Kubernetes的基础概念及核心思想 掌握阿里云容器服务ACK/ACK Serverless概念及使用方法 基于容器服务ACK Serverless搭建和管理企业级网站应用
相关文章
|
7天前
|
运维 Kubernetes Docker
利用Docker和Kubernetes构建微服务架构
利用Docker和Kubernetes构建微服务架构
|
5天前
|
监控 持续交付 Docker
Docker 容器化部署在微服务架构中的应用有哪些?
Docker 容器化部署在微服务架构中的应用有哪些?
|
5天前
|
监控 持续交付 Docker
Docker容器化部署在微服务架构中的应用
Docker容器化部署在微服务架构中的应用
|
5天前
|
安全 持续交付 Docker
微服务架构和 Docker 容器化部署的优点是什么?
微服务架构和 Docker 容器化部署的优点是什么?
|
6天前
|
存储 监控 Docker
探索微服务架构下的容器化部署
本文旨在深入探讨微服务架构下容器化部署的关键技术与实践,通过分析Docker容器技术如何促进微服务的灵活部署和高效管理,揭示其在现代软件开发中的重要性。文章将重点讨论容器化技术的优势、面临的挑战以及最佳实践策略,为读者提供一套完整的理论与实践相结合的指导方案。
|
13天前
|
JavaScript 持续交付 Docker
解锁新技能:Docker容器化部署在微服务架构中的应用
【10月更文挑战第29天】在数字化转型中,微服务架构因灵活性和可扩展性成为企业首选。Docker容器化技术为微服务的部署和管理带来革命性变化。本文探讨Docker在微服务架构中的应用,包括隔离性、可移植性、扩展性、版本控制等方面,并提供代码示例。
51 1
|
21天前
|
Kubernetes 负载均衡 Docker
构建高效微服务架构:Docker与Kubernetes的完美搭档
本文介绍了Docker和Kubernetes在构建高效微服务架构中的应用,涵盖基本概念、在微服务架构中的作用及其实现方法。通过具体实例,如用户服务、商品服务和订单服务,展示了如何利用Docker和Kubernetes实现服务的打包、部署、扩展及管理,确保微服务架构的稳定性和可靠性。
74 7
|
17天前
|
Kubernetes 关系型数据库 MySQL
Kubernetes入门:搭建高可用微服务架构
【10月更文挑战第25天】在快速发展的云计算时代,微服务架构因其灵活性和可扩展性备受青睐。本文通过一个案例分析,展示了如何使用Kubernetes将传统Java Web应用迁移到Kubernetes平台并改造成微服务架构。通过定义Kubernetes服务、创建MySQL的Deployment/RC、改造Web应用以及部署Web应用,最终实现了高可用的微服务架构。Kubernetes不仅提供了服务发现和负载均衡的能力,还通过各种资源管理工具,提升了系统的可扩展性和容错性。
51 3
|
20天前
|
Kubernetes 负载均衡 Docker
构建高效微服务架构:Docker与Kubernetes的完美搭档
【10月更文挑战第22天】随着云计算和容器技术的快速发展,微服务架构逐渐成为现代企业级应用的首选架构。微服务架构将一个大型应用程序拆分为多个小型、独立的服务,每个服务负责完成一个特定的功能。这种架构具有灵活性、可扩展性和易于维护的特点。在构建微服务架构时,Docker和Kubernetes是两个不可或缺的工具,它们可以完美搭档,为微服务架构提供高效的支持。本文将从三个方面探讨Docker和Kubernetes在构建高效微服务架构中的应用:一是Docker和Kubernetes的基本概念;二是它们在微服务架构中的作用;三是通过实例讲解如何使用Docker和Kubernetes构建微服务架构。
55 6
|
23天前
|
缓存 监控 API
微服务架构下RESTful风格api实践中,我为何抛弃了路由参数 - 用简单设计来提速
本文探讨了 RESTful API 设计中的两种路径方案:动态路径和固定路径。动态路径通过路径参数实现资源的 CRUD 操作,而固定路径则通过查询参数和不同的 HTTP 方法实现相同功能。固定路径设计提高了安全性、路由匹配速度和 API 的可维护性,但也可能增加 URL 长度并降低表达灵活性。通过对比测试,固定路径在性能上表现更优,适合微服务架构下的 API 设计。