构建一个简单的CaaS系统

在CaaS系统出现前企业应用架构基本被IaaS/SaaS/PaaS等模式垄断,直到Docker的出现为我们打开了另一个扇大门,废话不说了,我们直奔主题。

我们先了解下一个简单的CaaS系统是如何为用户提供服务的:

  1. 企业用户上传它的应用代码或其他代码托管方式,我们生成用户应用的镜像,或者用户直接上传镜像,或者用户直接使用我们提供的基础服务镜像
  2. 用户部署他的镜像应用,启动它的镜像容器
  3. 用户访问他的应用服务

OK,需求确定了,该搬砖了。

用户镜像制作

既然是一个简单的CaaS系统,我们就不让用户上传代码或者使用第三方代码托管了,直接让他们制作镜像后提交给我们,为此我们需要搭建一个Docker私服来让用户上传镜像,假设用户上传的镜像遵循这种格式 :docker 私服地址 /{appId}:{version} ,这对用户有一定要求,毕竟一些用户可能连Docker是啥都不知道就更别奢望让他们编写Dockerfile制作镜像交付给我们了。当然如果我们提供一些基础服务镜像(比如MySQL服务,Redis服务等)给用户那最好了。

启动用户镜像

有了用户制作的镜像,该是启动它的时候了。

docker pull docker私服地址/{appId}:{version}

docker run -d docker私服地址/{appId}:{version}

启动方式很简单,但这并不是我们想要的,毕竟我们是要让用户能够访问到他部署的服务的,假如用户的服务是一个Web服务,那你得暴露出用户的Web服务端口,这需要我们确定容器的通信方案:

  1. 跟宿主机共用一个网络空间
  2. 发布一个容器端口,让Docker随机选择一个未使用的高位端口
  3. 发布一个容器端口,并映射到宿主机上指定端口为外部路由服务
  4. 采用Docker的'links'来允许容器间通信。 如果一个新容器链接到一个已有容器,新容器将会通过环境变量获得已有容器的链接信息,一个关联的容器将会获得它的对应连接信息,在它处理了那些变量后允许它自动连接。这样就使得同一个宿主机上的容器不需要知道对应服务的端口和地址,就可以直接进行通信

我们简单的CaaS系统暂时还用不到容器间通信,如果跟宿主机共用一个网络空间即 --net="host" 模式启动的话,那么如果有多个用户上传了镜像,他们的WEB服务端口都是8080,显然宿主机上只能启动一个8080端口,只能有一个用户的容器启动成功,其他的因为端口已经被占用导致启动失败,在这里我们选择第三种模式,选择指定的端口映射来发布容器,这也方便我们后面管理宿主机上的端口资源。OK,启动方式改成下面:

docker run -d -p 25701:8080 docker私服地址/{appId}:{version}

为了不让某个用户的应用占用过多资源导致影响到整个宿主机上其他的应用,我们稍微对用户的资源进行下限制,比如限制用户应用容器的使用内存和CPU权重:

docker run -d -p 25701:8080 -m 512M -c 1024 docker私服地址/{appId}:{version}

为了能做到水平扩展,容器服务最好是无状态的的,这样能更好的实现负载均衡和水平扩容。

应用启动成功,我们可以通过在宿主机上访问25701即可访问容器的8080端口服务。

在写代码的时候我们通过 Docker Remote API client libraries 来启动卸载容器,具体代码实现就不多说了。

服务发现

容器启动成功后,用户该如何访问到他的容器服务呢,总不能提供宿主机IP给用户直接访问吧,这就需要我们构建一个服务发现组件了。

服务发现的工作方式

当每一个服务启动上线之后,他们通过发现工具来注册自身信息
服务的消费者能够在预设的终端查询该服务的相关信息,然后它就可以基于查到的信息与其需要的组件进行交互
为了简便,我们使用ZooKeeper来作为我们的服务发现工具。

首先在容器启动成功后我们将服务注册到zookeeper中,存储的path路径如下:/caas/service/address/{appId}/{version},存储的服务子节点为{containerId}->{宿主机IP}:{服务端口}。

例如用户appId01和appId02分别部署了各自的应用版本容器containerId01和containerId02,对应的服务端口分别为25701和25702,那么zk里存储的注册表信息为下:

/caas/service/address/appId01/app01Version/containerId01 -> {宿主机IP}:25701

/caas/service/address/appId02/app02Version/containerId02 -> {宿主机IP}:25702

如果一个用户部署了多个容器实例,对应的zk注册表信息类似下面:

/caas/service/address/{appId}/{version}/containerId01 -> {宿主机IP}:25701

/caas/service/address/{appId}/{version}/containerId02 -> {宿主机IP}:25702

/caas/service/address/{appId}/{version}/containerId03 -> {宿主机IP}:25703

/caas/service/address/{appId}/{version}/containerId04 -> {宿主机IP}:25704

故障检测

以上我们完成了服务的注册,注册完服务后为了实现应用的高可用,我们应该还需要对容器进行故障检测,故障检测的方案通常有下面2种:

  • 组件主动请求服务发现心跳方式:组件可以设置一个超时时间,并能定期去请求服务发现来重置超时时间,超时时间达到阀值更新注册表
  • 服务发现主动请求组件心跳方式:服务发现定期的健康检查组件以及当组件出现故障时更新注册表

通常内部自己的服务可以使用第一种方式让组件主动请求服务发现,用户自己写的服务一般不可能费劲的去实现心跳来访问服务发现组件,所以通常会要求用户实现一个服务发现组件能访问的心跳接口,让服务发现组件去主动请求用户的应用,一旦访问失败在重试一定次数后会认为该应用已经出现故障无法继续提供服务,这时可以根据策略来选择直接停止删除该用户容器或者重新启动。

比如服务发现的健康检查组件可以每隔一定时间来访问用户的心跳接口,类似{宿主机IP}:25701/_ping。

注册表安全访问

基于安全方面考虑,通常情况下我们需要对服务发现做相应的访问控制,以便对注册表中的存储信息实现安全访问,可能有以下几种方案可供参考:

  1. 服务发现工具可以采用SSL/TLS加密链接
  2. 对写入数据进行加密,使用者使用的信息必须用相应的密钥解码从服务发现中获取
  3. 服务发现实现访问控制,将不同的键值切分到不同的分组中,根据访问的需要来制定不同的秘钥从而访问相应的分组

这里我们就不说具体的安全方面的实现了,谁让我们是简易版CaaS系统呢。

分布式配置存储和负载均衡

其实服务发现的注册表存储访问地址只是其中的一个方面,你可以用它来存其他的信息,比如存应用的配置,你可以通过配置动态的调整应用,也可以存容器的相关指标,负载均衡就是一个很好的例子,它可以通过查询服务发现得到各个后端节点承受的流量数,然后根据这个信息来调整配置。具体的负载均衡算法可以根据需求来选择,我们就使用最简单的round bobin算法,即轮询方式访问。这方面的实现涉及到CaaS系统的另一个组件:路由网关,具体后面介绍。

上面我们一直都是使用了ZooKeeper来作为服务发现工具的,除了ZooKeeper,我们还可以使用其他的服务发现工具:etcd、Consul、crypt、Confd,大家有兴趣可以了解下,最重要的是能保证注册表信息的数据一致性。

调度编排

通过上面几步你的CaaS系统基本小有所成了,但这还不够。我们在生产环境里随着用户应用容器的数量增加需要增加宿主机来支撑避免资源不足,或者将某些用户的实例单独部署在指定的宿主机上,这就需要我们实现一个调度器组件。

宿主选择

CaaS系统是一个分布式系统,在多个宿主机的环境里,我们需要知道用户的应用该部署在哪台宿主机上,如果单机的话那就不需要选择了,直接指定就好了。具体该如何调度需要考虑以下几点:

  1. 需要一个默认的调度策略,比如选择可用内存最多的宿主机部署服务或选择CPU最空闲的宿主机部署服务
  2. 调度器需要提供覆盖机制,比如2个容器必须部署在同一个宿主机上作为一个单元来运行,比如同一个服务的2个实例容器必须部署在不同机器上来达到高可用
  3. 调度器需要满足限制条件,比如给特定的宿主机打标签,比如一些服务需要部署在集群中的每一台宿主机上
  4. 多容器部署调度

随着业务的扩展,我们可能需要提供分组容器管理,将一个集合的容器(通常是有相互依赖关系紧密关联的组件)作为一个单独应用来处理,比如一个Web服务容器再加上后端的数据库服务容器组合成一个project来发布。这里就不多做讨论了,我们的简易版系统还没考虑到这步。

供应

供应是指将一个新主机上线并完成基本配置使得它们能够工作的一个过程,通常在集群管理里用来自动扩展宿主机,管理工具来定义需求额外主机的过程以及自动触发的条件,例如,如果你的应用的负载很高,你可能希望让你的系统增加额外的机器并水平扩展容器以缓解负载,这里我们同样不做实现,简易版就直接手动增加宿主机就好了嘛。

我们在这里举个实现调度器的相对简陋的方案:

主要使用关系型数据库如MySQL来存储宿主机信息,调度器查询宿主机的相关指标信息根据调度算法选择相应的宿主机来部署,利用乐观锁来保证并发操作时的数据一致性,利用事务来保证部署和卸载等操作的原子性。这里面可能坑比较多,大家也可以使用现在比较流行的调度器,常用的调度器有:Fleet、Marathon、Swarm、Mesos、Kubernetes、Compose,大家有兴趣可以了解下。

网关

上面我们在服务发现的负载均衡方面介绍到了网关,我们把它作为CaaS系统中重要的一个组件,他主要是负责用户请求的转发,举个例子用户部署了容器想要访问它的容器服务,这个请求到达网关后网关根据策略选择相应的后端容器服务然后转发请求。根据用户的设定,动态路由请求到对应容器实例,这相当于一个代理服务器。具体如何选择容器实例服务转发就需要实现负载均衡器,我们可以通过查询服务发现组件来获取相应容器信息来完成。既然是代理服务,我们在中间可以对用户的请求做其他处理,比如做黑名单过滤,做流量统计,做CNames路由等等

假设我们的CaaS网关访问域名是 mycaas.gateway.cn ,用户在我们后台部署了一个WEB应用容器实例,调度器将他部署在了10.10.10.101宿主机上,容器服务端口映射为25701,用户请求mycaas.gateway.cn到达网关后,网关根据请求信息识别用户查询该用户所有的应用容器信息,得到所有的容器服务地址,根据负载均衡规则代理转发到目标容器服务上。这个查询服务发现的过程中最好实现本地缓存,比如使用zookeeper的缓存减少和避免每次请求都访问服务发现组件,同时代理转发中尽量使用连接池减少开销。

总结

至此我们简单的CaaS系统就架构设计好了,在整个系统中有服务发现/调度器/网关等多个组件协调配合。

时间: 2016-10-03

使用MongoDB和JSP实现一个简单的购物车系统实例

本文介绍了JSP编程技术实现一个简单的购物车程序,具体如下: 1 问题描述 利用JSP编程技术实现一个简单的购物车程序,具体要求如下. (1)用JSP编写一个登录页面,登录信息中有用户名和密码,分别用两个按钮来提交和重置登录信息. (2)编写一个JSP程序来获取用户提交的登录信息并查询数据库,如果用户名为本小组成员的名字且密码为对应的学号时,采用JSP内置对象的方法跳转到订购页面(显示店中商品的种类和单价等目录信息):否则采用JSP动作提示用户重新登录(注:此页面上要包含前面的登录界面). (3

MyBatis Plus构建一个简单的项目的实现

开始吧 1.首先准备一张表"users"表. 2.创建一个springboot工程,"可以使用 Spring Initializer 快速初始化一个 Spring Boot 工程",具体工程的创建这里就不说了.大家都懂的. 3.添加相关依赖,如下: <!-- 数据库依赖--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-

用Swift构建一个简单的iOS邮件应用的方法

在前几个月内,我一直在做InboxKit的研究,它是关于Inbox平台的IOS SDK.Inbox为和邮件数据的交互提供高层API,使得你可以忽略IMAP,Exchange,MIME的解析以及thread探测(当然还有很多其他事情...),并使你致力于完成富有创意的APP的创作上.我们的目标很简单:尽可能地打造一个优雅的,跨提供商的邮件应用.毕竟,它很难. 在Objective-C中,InboxKit使得创建邮件体验变得很轻松,那么,Swift又如何呢?Swift在WWDC后已正式被IOS社区所

Vue 2.0+Vue-router构建一个简单的单页应用(附源码)

一.介绍 vue.js 是 目前 最火的前端框架,vue.js 兼具 angular.js 和 react.js 的优点,并剔除它们的缺点,并且提供了很多的周边配套工具 如vue-router .vue-resource .vuex等等 ,通过他们我们可以很轻松的构建一个大型单页应用. 目前Vue版本为:Vue2.0 官网地址:http://vuejs.org.cn/ 查看API文档:https://vuefe.cn/v2/api/ 对比其他框架:http://vuejs.org.cn/guid

利用Python的Flask框架来构建一个简单的数字商品支付解决方案

作为一个程序员,我有时候忘了自己所具有的能力.当事情没有按照你想要的方式发展时,却很容易忘记你有能力去改变它.昨天,我意识到,我已经对我所出售的书的付款处理方式感到忍无可忍了.我的书完成后,我使用了三个不同的数字商品支付处理器,在对它们三个都感到不满后,我用Python和Flask,两个小时的时间写出了我自己的解决方案.没错!两个小时!现在,这个系统支撑着我的书籍付费流程,整个过程难以置信的简单,你可以在20秒内购买书籍并开始阅读. 往下看,看我是如何在一夜之间完成我自己的数字商品支付解决方案的

vue2.0+vue-router构建一个简单的列表页的示例代码

一: 环境搭建 使用vue-cli脚手架工具构建 安装 vue-cli npm install -g vue-cli 使用vue-cli初始化项目 vue init demo1 进到目录 cd demo1 安装依赖 npm install 开始运行 npm run dev 浏览器访问http://localhost:8080 1.首先会打开首页 也就是我们看到的index.html文件 2.使用webpack打包之后默认加载main.js文件并将其引入到index.html文件中 二: 开发 在

一个jsp+AJAX评论系统第1/2页

这是一个简单的评论系统,使用了JDOM(这边使用Jdom-b9),实例使用JSP作为视图,结合使用AJAX(用到prototype-1.4),Servlet和JavaBean作为后台处理,使用xml文件存储数据. 1.应用目录结构如下: data   |--comment.xml js   |--prototype.js   |--ufo.js(UTF-8格式)                                                                    

基于Python实现一个简单的银行转账操作

前言 在进行一个应用系统的开发过程中,从上到下一般需要四个构件:客户端-业务逻辑层-数据访问层-数据库,其中数据访问层是一个底层.核心的技术.而且在实际开发中,数据库的操作也就是说数据访问层都是嵌套在其他语言中的,其是编程的核心.本文面向的是python语言,即通过python操作数据库来实现简单的银行转账操作. 工具 python提供了python DB API用来统一操作数据库,使访问数据库的接口规范化,在没有python DB API之前,接口程序十分混乱,不同的数据库需要不同的操作接口,

用Javascript轻松制作一套简单的抽奖系统

作者:jegg  年底将至,许多公司忙着搞年会,会上一般都会有一些抽奖活动,下面的程序就是用javascript 写的一个简单的抽奖系统与大家共享. 此代码借鉴了网上的一些网友的代码,又加上了一些诸如不重复抽奖之类的改进.大概思路如下: 1.将所有的的抽奖数据(这里为手机号码)存入数组中. 2.使用random 函数随机产生该数组的INDEX 3.使用setInterval 函数以极短的时间间隔产生该数组随机INDEX所对应的手机号码,并显示.       4.使用removeEleAt(ind