在CAAS系统出现之前,企业应用架构基本被IAAS/SAAS/PAAS等模式所垄断,直到docker的出现为我们打开了另一扇门,让我们直奔题目我们先了解一个简单的CAAS系统是如何为用户提供服务的。企业用户上传他们的应用代码或其他代码托管方式。我们生成用户应用的镜像,或者用户直接上传镜像,或者用户直接使用我们提供的基础服务镜像用户部署他的镜像应用,启动它的镜像容器用户访问他的应用服务。OK,需求确定了,该搬砖了。1.由于用户图片制作是一个简单的CAAS系统,我们不允许用户上传代码或使用第三方代码托管,而是让用户制作图片并提交给我们。为此,我们需要搭建一个docker私服,让用户上传Image,假设用户上传的图片是这样的格式:docker私服地址/{appId}:{version},这对用户有一定的要求.毕竟有的用户可能连docker是什么都不知道,更别说让他们写dockerfile生产镜像交付给我们了。当然,如果我们提供一些基础的服务镜像(比如mysql服务,redis服务等)给用户就完美了。2.启动用户镜像有了用户制作的镜像,就可以启动了。这不是我们想要的。毕竟,我们希望用户能够访问他们部署的服务。如果用户的服务是web服务,那你就得暴露用户的web服务端口,这就需要我们确定容器的通信方案。:与宿主机共享网络空间发布容器端口,让docker随机选择一个未使用的高阶端口发布容器端口,映射到宿主机上的指定端口,对外提供路由服务。使用docker的“链接”允许容器间通信。如果一个新的容器链接到一个已有的容器,新的容器会通过环境变量获取已有容器的链接信息,关联的容器会获取其对应的连接信息,使其在处理完这些变量后自动连接。这样同一主机上的容器就可以直接通信,而无需知道对应服务的端口和地址。我们简单的CAAS系统暂时不使用容器间通信。如果我们和宿主机共享一个网络空间,也就是——如果启动了net="host"模式,那么如果有多个用户上传图片,他们的WEB服务端口都是8080,显然只能启动一个8080端口在主机上,只能成功启动一个用户的容器。因为端口已经被占用,所以启动失败。这里我们选择第三种模式,选择指定的端口映射来发布容器,也方便我们后面管理宿主机上的端口资源。OK,将启动方式改成如下:dockerrun-d-p25701:8080docker私服地址/{appId}:{version}为了防止某个用户的应用占用太多资源,影响整个主机上的其他应用,我们稍微限制用户的资源,比如限制用户的应用容器使用的内存和CPU权重**是无状态的,可以更好的实现负载均衡和横向扩展。应用程序启动成功。我们在宿主机上访问25701就可以访问容器的8080端口服务了。在编写代码时,我们使用DockerRemoteAPI客户端库来启动和卸载容器。具体的代码实现我就不多说了。3、服务发现容器启动成功后,用户应该如何访问其容器服务?无法提供主机IP供用户直接访问。这就需要我们构建一个服务发现组件。3.1服务发现的工作方式:当每个服务启动上线后,通过发现工具注册自己的信息服务。服务的消费者可以在预设的终端查询服务的相关信息,然后它可以根据查询到的信息与自己需要的组件进行交互。为简单起见,我们使用zookeeper作为我们的服务发现工具。首先,容器启动成功后,我们在zookeeper中注册服务。存储路径如下:/caas/service/address/{appId}/{version},存储服务子节点对于{containerId}->{hostIP}:{serviceport},例如用户appId01和appId02部署了各自的应用版本容器containerId01和containerId02,对应的服务端口分别为25701和25702,那么zk中存储的registry信息如下:/caas/service/address/appId01/app01Version/containerId01->{hostIP}:25701/caas/service/address/appId02/app02Version/containerId02->{hostIP}:25702如果用户部署创建了多个容器实例,对应的zkregistry信息类似如下:/caas/service/address/{appId}/{version}/containerId01->{hostIP}:25701/caas/service/address/{appId}/{version}/containerId02->{hostIP}:25702/caas/service/address/{appId}/{version}/containerId03->{hostIP}:25703/caas/service/address/{appId}/{version}/containerId04->{hostIP}:257043.2故障检测我们已完成上述服务的注册。注册服务后,为了实现应用的高可用,我们还需要检测容器的故障,故障检测的解决方案通常有两种:组件主动请求服务发现心跳方式:组件可以设置超时时间,可以周期性请求服务发现重置超时时间,超时时间达到阈值更新注册中心服务发现主动请求组件心跳方式:服务发现定期健康检查组件和当组件失败时更新注册表。通常,内部服务可以使用第一种方式让组件主动请求服务发现。用户编写的服务一般不可能实现心跳来访问服务发现组件,所以通常需要用户实现一个服务发现组件可以访问的心跳接口,让服务发现组件主动请求用户的应用。一旦访问失败,在重试一定次数后,会认为应用失败,无法继续提供服务。这时候可以根据策略选择直接停止删除用户容器或者重启。比如服务发现的健康检查组件,可以定时访问用户的心跳接口,类似{宿主机IP}:25701/_ping3.3Registry安全访问是基于安全的考虑,通常在某些情况下,我们需要对服务发现进行相应的访问控制,以实现对注册中心存储信息的安全访问。可能有几种方案可以参考:服务发现工具可以使用SSL/TLS加密链接进行数据写入。加密,用户使用的信息必须用相应的密钥解码才能从服务发现中得到服务发现,实现访问控制,将不同的密钥值分成不同的组,根据访问的需要制定不同的秘钥进行访问对应的分组这里就不讲具体安全方面的实现了,谁给我们做了一个简易版的CAAS系统。3.4分布式配置存储和负载均衡其实服务发现的注册中心存储访问地址只是一方面。您可以使用它来存储其他信息,例如存储应用程序配置。您可以通过配置动态调整应用程序,也可以负载均衡是存储容器相关指标的一个很好的例子。它可以通过查询服务了解每个后端节点所承载的流量大小,然后根据这些信息调整配置。可以根据需要选择具体的负载均衡算法。我们将使用最简单的roundbobin算法,即round-robinaccess。这方面的实现涉及到CAAS系统的另一个组成部分:路由网关,后面会介绍。上面我们一直使用zookeeper作为服务发现工具。除了zk,我们还可以使用其他的服务发现工具:etcd、consul、crypt、confd。如果你有兴趣,你可以了解更多。最重要的是保证注册表信息的数据一致性。4、调度编排通过以上步骤,你的CAAS系统基本完成,但这还不够。在生产环境中,随着用户应用容器数量的增加,我们需要增加支持的主机数量来避免资源短缺,或者将部分用户实例单独部署在指定的主机上,这就需要我们实现一个调度器组件。4.1主机选择CAAS系统是一个分布式系统。在多主机的环境中,我们需要知道用户的应用应该部署在哪个主机上。如果是单机就不用选了,指定即可。如何调度需要考虑以下几点:需要一个默认的调度策略,比如选择可用内存最多的主机部署服务,或者选择空闲CPU最多的主机部署服务。调度器需要提供覆盖机制,比如两个容器必须部署在同一台主机上作为一个单元运行。比如同一个服务的两个实例容器必须部署在不同的机器上才能实现高可用。调度器需要满足限制,比如给特定的主机打标签,比如有些服务需要部署在集群中的每台主机上4.2多容器部署调度随着业务的扩展,我们可能需要提供组容器管理,把一个集合容器(通常是相互关系密切的组件)作为一个单一的,比如一个web服务容器加上一个后端数据库服务容器组合成一个项目发布。这里就不用讨论了,我们简易版的系统没有考虑这一步。4.3ProvisioningProvisioning是指将一台新主机上线并完成基本配置,使其能够正常工作的过程。通常用于集群管理,自动扩容主机。管理工具定义了需要额外主机的过程和自动触发的条件,例如,如果你的应用程序负载很高,你可能想在你的系统中添加额外的机器并水平扩展容器以减轻负载。我们也不会在这里这样做。对于简单版本,只需手动添加主机。这里我们给出一个比较简单的方案来实现调度器:主要是使用mysql等关系型数据库来存储主机信息,调度器根据调度算法查询主机的相关索引信息来选择对应的主机进行部署,使用乐观锁为了保证并发操作时的数据一致性,使用事务来保证部署、卸载等操作的原子性。这里可能有很多陷阱,你也可以使用比较流行的调度器。常用的调度器包括:fleet、marathon、Swarm、mesos、Kubernetes、compose。如果你有兴趣,你可以了解更多。5.网关上面我们在服务发现的负载均衡方面介绍了网关。我们将其视为CAAS系统中的重要组成部分。主要负责转发用户请求。比如用户部署了一个容器,想要访问它的容器,请求到达网关后,网关根据策略选择对应的后端容器服务,然后转发请求。根据用户的设置,将请求动态路由到对应的容器实例,相当于一个代理服务器。具体如何选择容器实例服务转发,需要负载均衡器的实现。我们可以通过查询服务发现组件来获取对应的容器信息。既然是代理服务,我们可以在中间对用户请求做其他处理,比如黑名单过滤、流量统计、CNames路由等。假设我们的CAAS网关访问域名是mycaas.gateway.cn,用户在我们的后台部署了一个WEB应用容器实例,调度器将其部署在10.10.10.101宿主机上。容器服务端口映射到25701,用户请求mycaas.gateway.cn到达网关后,网关根据请求信息识别用户,查询用户的所有应用容器信息,得到所有容器服务地址得到,并根据负载均衡规则转发给目标容器服务。在查询服务发现的过程中,最好实现本地缓存,比如使用zookeeper的缓存来减少和避免每次请求访问服务发现组件,同时尽量使用连接池来减少代理转发的开销.6.总结至此,我们简单的CAAS系统的架构已经设计完成。在整个系统中,服务发现/调度器/网关等多个组件相互协调配合。
