过去几年,利用容器打包和部署代码的方式日益流行,越来越多企业开始测试或是已经在生产环境中运行了微服务架构应用,开始直接面对和解决分布式服务化架构演变中出现的各种问题。
在这样的趋势和大环境下,无服务器 PaaS **Rainbond**围绕着服务的拓展、监控、治理等角度,进行了一系列思考和尝试,插件体系正是其中的重要一环。
Rainbond 的插件体系抽象集中在平台的业务层面,理论基础源于 Kubernetes 的 pod 机制和一部分容器概念。针对平台业务层面对 kubernetes 容器编排进行抽象,转变为一个对用户体验友善的 Rainbond 插件产品的过程,方便用户在不需要懂 Kubernetes 原理的情况下使用。
Rainbond 插件体系的设计遵循易于理解和易于使用的原则:
在 Rainbond 插件体系中,插件使用的过程即主容器与 init 或 sidecar 等容器结合的过程,原理是将插件容器以 sidecar 容器(大部分)的形式编排至主应用的 pod 中,共享主应用容器的网络和环境变量,因此可以插件化实现某些附加功能,例如对主应用进行流量分析等。
Pod 是 Kubernetes 中模块化容器服务的实例,由一个或多个共享资源的容器组合而成,共享包括文件系统、内核命名空间和 IP 地址等资源。它是 Kubernetes 集群中调度的原子单元,通过提供更高层次的抽象,实现灵活的部署和管理模式。
在以下 Rainbond ( www.rainbond.com )部署 pod 描述文件片段中,我们可以看到该 pod 中包含两个 containers:394d2f238a603bf01eb5215e23237691
(主容器)与22dc8b12aeaf417fa7bd6466c136b9f4
(副容器),两者通过 pod 机制捆绑在一起,共同完成该 server 提供的服务。
kubectl describe pod -n b314b3e7e44e45a082a0d00e125e88bf b314b3e7e44e45a082a0d00e125e88bf
Containers:
394d2f238a603bf01eb5215e23237691:
Container ID: docker://44018fa19a268c1f9ea5b20e9793290cbd89b167bd1fc9017a04ecbd9606d379
Image: goodrain.me/tomcat:latest_gr237691
Image ID: docker-pullable://goodrain.me/tomcat@sha256:701d36cde0b55d07f59a65e525e258523aae46b033297f80b44b0b6c07fc0277
Port: 8080/TCP
State: Running
Started: Sun, 21 Jan 2018 14:56:35 +0800
Ready: True
Restart Count: 0
Limits:
cpu: 640m
memory: 512Mi
Requests:
cpu: 120m
memory: 512Mi
22dc8b12aeaf417fa7bd6466c136b9f4:
Container ID: docker://4c8bd31c2ec2200ee323fb1abdd7b652544ce3d110b1621c36acab4bae434c77
Image: goodrain.me/tcm_20180117175939
Image ID: docker-pullable://goodrain.me/tcm_20180117175939@sha256:d2b20d7eec4da05d953fb7862b9c9ead76797ea5542bff4b93cf2bc98331d279
Port:
State: Running
Started: Sun, 21 Jan 2018 14:56:36 +0800
Ready: True
Restart Count: 0
Limits:
memory: 64Mi
Requests:
memory: 64Mi
一个 pod 可以封装多个容器,应用运行在这些容器之中;同时,pod 可以有一个或者多个 init 容器,init 容器在应用容器启动之前启动。如果某个 pod 的 init 容器启动运行失败,Kubernetes 将不断重启 pod,直到 init 容器启动运行成功为止。当然,我们可以设定 pod restartPolicy 值为 Never,阻止它重复启动。
利用 pod 中容器可以共享存储和网络的能力,sidecar 容器得以扩展并增强“主要”容器,与之共存并使其工作得更好。在上面 pod 描述文件片段中,22dc8b12aeaf417fa7bd6466c136b9f4
就是一个 sidecar 类型的容器,用来协助分析主容器的一些性能指标。
Rainbond 插件体系易于使用的原则体现在类应用化
、绑定使用
、独有的变量作用域
等方面。
Rainbond 插件体系为插件设计了与应用类似的生命周期,包含创建、启用、关闭等模式,与 Rainbond 平台用户操作应用的习惯保持一致。同时,Rainbond 插件体系简化了插件创建类型,支持基于 docker image 和 dockerfile 创建,创建插件比创建应用更加简单。
插件创建流程设计如下图所示:
需要注意的是,当一个插件版本固定后,其内存、版本信息、插件变量无法再做修改,这些元素仅作用于当前插件版本。需要修改插件变量等元素时,对插件进行重新构建
,重复创建流程即可。
插件的创建和使用过程步骤相对独立,用户可以使用当前租户下创建的插件和其他团队(或租户)分享到云市的插件(初期 Rainbond 将陆续为用户提供数款插件)。
如果用户没有创建自己的插件,在使用插件前,需要先将他人分享在云市的插件安装至本地。这个过程会将分享出来的插件元数据存储至用户租户下,相当于用户“创建”了这个插件(并没有耗时的构建过程)。
创建完成后,用户可以对插件进行针对性设置,目前可以设置变量和插件生效与否(后续会增加内存设定,满足主应用复杂情况下附加功能对应内存的需求)。内存的限制将在 pod 创建时进行限制,插件变量生效与实时修改在下文中会继续介绍。
注入到容器内的变量设计为有两类:共用变量
与插件变量
。
共用变量就是主容器的变量,为使插件参与甚至扩展主应用的功能,在 pod 创建过程中将主应用的环境变量注入到了插件容器中;插件变量则仅作用在该插件容器内部,防止插件间的变量重复与混用。
Rainbond 目前默认为用户提供性能分析和服务治理两款插件,详情访问 http://www.rainbond.com/docs/stable/user-app-docs/myapps/myapp-platform-plugin.html 查看。
以下我们以网络代理插件为例,介绍 Rainbond 插件体系的工作过程。
用户填入插件相关信息后,Rainbond 将根据这些信息生成插件创建任务,发送至 Rainbond 消息组件中,由任务发现器处理该任务消息。Rainbond 的 builder 组件接受任务后,将会对插件进行构建,生成插件容器镜像。一个插件容器镜像对应一个构建版本,关联其相应的功能特色。在 Rainbond 中,插件将以一个构建完成后的镜像来进行流通。类似于应用,插件也可以在 Rainbond 及云市中进行分享。
参照插件使用文档,在应用的插件 tab 中点击安装后,会对插件的当前最新版本与应用标记为关联。此时重启应用,在 pod 创建时,会对插件进行判断,若存在插件,则进行PluginContainerCreate
,pod 的 container list 中将会包含主容器与插件容器。
网络代理插件在 Rainbond 中又名 servicemesh, 是一个功能增强容器,在 pod 中与主容器共享网络。
网络代理插件要完成其功能需要完全代理主容器的网络, 接管主容器的出入口。结构如图:
网络代理分为出口网络模式
、入口网络模式
两种。
出口网络模式(示意图中访问 tomcat 应用为出口网络模式),图中 service mesh1 plugin 通过discover_service
获取 tomcat 应用的网络信息,包括 listen ports,routers,endpoints 等。这些信息由 discover_service 通过 watching kubernetes 集群中 tomcat 应用的 services 和 endpoints 资源获取。discover_service API 相关请查看相关代码 https://github.com/goodrain/rainbond/blob/master/pkg/node/api/router/discoverRouter.go
在 service mesh1 plugin 获取所需的下游应用信息后则开始监听本地对应 tomcat 的端口,代理当前应用访问 tomcat 的请求,例如curl http://tomcat
。
为何上述请求可以由 pod 内的容器代理呢?这是由于dns_service
将 tomcat 这类规则域名解析至本地,路由及负载均衡则全权交由 service mesh1 plugin 进行,即客户端负载均衡的模式。
入口网络模式较之于出口模式类似(示意图中由外网访问进入 main container 为入口网络模式),复杂之处在于是对当前应用用户设置端口的转发,由于本地监听所以无法和主容器监听相同的端口。
在处理这种场景时,将 service mesh1 plugin 的监听端口进行了转化,在开启插件时会随机生成一个不重复的端口port_outer1
供给外层监听,service mesh1 plugin 继续转发主应用的原端口,在生成 k8s 的 service 和 pod 资源时由新端口替代原端口,并标注对应关系。
在外网负载均衡注册这个应用的外网端口访问时会使用port_outer1
来进行注册。分配给用户使用的域名则基于原端口与新端口的映射规则保持不变,用户并无感知
相关组件discover_service
下图为 Rainbond 提供的服务治理插件配置:
用户在控制台将应用与插件首次关联(安装)后,插件就会对应当前应用产生配置。经由 region 端存储至数据中心的 etcd 中。修改配置相应的设置,进行更新操作,则会修改对应服务的插件资源。
分布式服务化架构面临的问题很多,想要实现服务化,服务治理是一个比较关键的点。在提供治理服务的基础上,配置则需要实现实时生效和联动。因此在 rainbond 设计中将插件的配置资源放置在了discover_service
中,由该发现服务来支持动态配置。
在 Kubernetes 创建 pod 时,插件容器的 env 中注入了一个相关的环境变量DISCOVER_URL
,该变量的值为插件可以通过 GET 请求获取资源的 url。后续用户在使用平台创建自己所需的功能插件时,这个变量也是你的插件获取资源所必须的。
相关代码请查看 https://github.com/goodrain/rainbond/blob/master/pkg/worker/appm/pod.go
Rainbond是国内首个开源的无服务器 PaaS,深度整合基于 Kubernetes 的容器管理、多类型 CI/CD 应用构建与交付、多数据中心的资源管理等技术,提供云原生应用全生命周期解决方案,构建应用与基础设施、应用之间及基础设施之间的互联互通生态体系。