简介
移动互联网、物联网和大数据应用的快速发展极大地促进了人们对云计算的需求。但是让应用架构拥有良好的可伸缩性和高可用性并非易事,运维和管控庞大的基础架构更是极大的挑战。
近年来,一个新的架构风格Serverless成了热门话题。对于一个新的技术浪潮,不同人都会有不同解读。本文将分析Serverless的编程模型,并借力Docker容器技术来打造一个最简单的Serverless平台。
概念
Martin Fowler 在Serverless Architectures的描述了Serverless的不同定义。本文将关注于如下的定义:
Serverless can also mean applications where some amount of server-side logic is still written by the application developer but unlike traditional architectures is run in stateless compute containers that are event-triggered, ephemeral (may only last for one invocation), and fully managed by a 3rd party. One way to think of this is ‘Functions as a service / FaaS’ .
Serverless与跟传统架构不同,由开发者实现的服务端逻辑运行在无状态的计算容器中,它是由事件触发,短暂的(可能只存在于一次请求过程中),完全被第三方管理。另一种思考方式,这是函数服务‘Functions as a Service / FaaS’。
其实Serverless和FaaS是在不同维度概括述这个新架构的特性。Serverless从部署运维形态角度,强调其无需关注底层执行环境的优势;而FaaS则是描述是它以服务化的方式提供函数式计算能力。
在这个领域 AWS Lambda是先行者,随后其他厂商相继推出了自己的函数服务,比如Google Cloud Functions和阿里云函数服务等等。
Servrless 架构分析
Serverless应用通常基于的Event-driven编程范型。它的开发方式和经典的Event-condition-action (ECA)非常类似。其通常包含如下方面
- 事件(Event)的触发器:用于描述触发应用逻辑
- 事件处理器: 应该是无状态、原子化的任务,并能够从系统的上下文中进行数据交换。
- 事件的派发和调度:开发者可以声明事件处理器对底层计算资源需求,由系统根据需求自动分配计算资源并调度执行。
和过于技术范的“Functions as a Service”相比,Serverless显然更容易深入人心,然而这个名词也容易给人带来误解。Serverless应用并非不需要服务器作为计算资源,正确的理解是应用开发人员无需关注计算资源的获取和运维,由平台来按需分配计算资源,并保证应用执行的SLA。
正因为上述目标,Serverless/FaaS平台对底层计算环境的提出了特殊要求:
- 快速启动:需要对事件请求快速响应,能够在亚秒级完成启动
- 弹性扩展:可以按照应用需求,自动在群集上分配资源,按需伸缩,无需人工干预。
- 良好的隔离性:不同应用之间不相互干扰
- 健壮性:应用逻辑执行失败后,可以快速调度并重新执行
看到这里,我们可以看到容器技术(LXC, CGroup等)非常适合用于提供Serverless的计算环境。每次系统接收到事件,动态启动容器来执行业务逻辑即可。这也是为什么有人戏称 “Serverless” Is Basically CGI In Containers
然而在一个大规模分布式环境中支持Serverless/FaaS和在单机操作系统上调度执行一个CGI进程非常不同。我们需要能够标准化的方式来开发、交付和运维事件处理逻辑;并且需要能够在分布式环境中,有效管理各种资源并进行任务调度,同时还要处理各种失效情况来保证应用的高可用。
很多企业和方案,比如IBM OpenWhisk、Iron.io等等,都把Docker容器作为Serverless平台的基础技术。Docker也介绍了使用Docker容器作为Serverless实现技术的原型:(https://blog.docker.com/2016/06/building-serverless-apps-with-docker/)。 阿里云函数服务也和容器服务团队一起推动基于Docker的Serverless平台实现。
下面我会介绍一个基于Docker的Serverless平台的一个高层次的参考架构,其要点如下:
- Docker容器作为事件处理的计算环境运行
- 将函数化事件处理逻辑打包成为Docker镜像,利用镜像仓库进行管理和分发
- 事件调度器配合Docker集群来调度事件处理执行
利用Docker容器的方法实现Serverless平台,有如下优势:
- 利用Docker提供的轻量级OS虚拟化能力,能够敏捷地创建事件执行运行环境,并提供基本的资源、安全隔离能力
- 使用Docker镜像和镜像仓库,简单标准地对事件处理逻辑进行打包和在分布式环境下进行软件分发;通过Docker镜像的版本管理和追踪能力,可以管控应用发布,保证应用在大规模分布式环境中部署的一致性。
- 在Docker容器内部,开发人员可以自由选择使用不同的语言和框架进行事件处理,容器之间不会相互干扰。
- Docker容器提供了标准化的外界环境交互的能力,应用逻辑可以通过环境变量、文件卷或者网络来访问上下文状态信息。
- 对不同实现的Docker容器进行统一的标准化运维处理,大大减少运维的复杂性,可以更好地实现自动化运维
- 基于Docker编排技术(比如阿里云容器服务等)提供的集群管理和编排能力,可以大大简化事件调度器的实现
- 受益于成熟的Docker的DevOps流程,可以大大提升开发交付效率
一个简单的基于Docker的Servrless原型实现
下面我们就来基于阿里云容器服务来提供一个最简单的Servrless原型,应用架构如下
我们提供了一个最简单的Serverless Controller。它基于Flask框架实现,可以接受HTTP请求作为事件触发。它运行在Docker集群上,并通过容器服务的Cluster Manager来实现在Docker集群上的任务派发,动态地进行事件处理。
其实例代码可以从Github上获得
https://github.com/denverdino/docker-serverless-sample
我们可以分析它的实现代码:当请求者访问 /test1
路径时,controller会触发hello_from_docker事件。它会在Docker集群上自动选择一个节点并基于“hello-world”镜像创建一个容器并执行任务。
@app.route('/test1')
def hello_from_docker():
container = docker_client.create_container('hello-world')
docker_client.start(container)
return 'Hello World from container %s!' % container
由于阿里云容器服务完全兼容Docker Swarm,所以应用可以通过Docker原生API来创建容器来执行事件处理逻辑。整个逻辑非常简洁,也具有很好的灵活性,可以执行任何基于Docker镜像的执行逻辑,比如基于Python实现的爬虫、基于R的数据分析算法等等。
这个方法可以非常快速地上路,但是如果事件处理过程中需要支持任务水平伸缩,故障重试等等特性,还需要复杂的编码来在Docker集群上进行调度执行。由于Docker Swarm等编排管理工具更多地是考虑了长时间运行在线应用的调度算法,而对于Serverless这样的短生命周期或离线应用并没有内置支持。但是阿里云容器服务通过非常简单的方法提供了核心的离线任务调度和支持能力。我们可以方便地利用它来支持Serverless应用。
下面提供了一个简单的示例:当请求者访问 /test2
路径时,会触发hello_from_aliyun事件。controller会基于“test_template”模板中的定义在Docker集群上创建一个离线应用。在模板中利用通过声明的方式描述了基于容器的任务:test服务由“registry.cn-hangzhou.aliyuncs.com/denverdino/docker-serverless-sample” 镜像提供(它会动态地生成一个UUID);并且指明该服务需要有10个容器实例来执行,和其重试和释放策略;还可以指明任务执行需要的CPU share, Memory limites等资源需求。容器服务会根据这些声明条件自动化动态地在集群中调度任务。
test_template = '''
version: "2"
labels:
aliyun.project_type: "batch"
services:
test:
image: registry.cn-hangzhou.aliyuncs.com/denverdino/docker-serverless-sample
restart: no
cpu_shares: 10
mem_limit: 100000000
labels:
aliyun.scale: "10"
aliyun.retry_count: "20"
aliyun.remove_containers: "remove-all"
'''
@app.route('/test2')
def hello_from_aliyun():
project_name="hello%d" % time.time()
acs_client.create_project(project_name, template=test_template)
return 'Hello World from Aliyun %s!' % project_name
如果希望了解容器服务提供的离线计算原语,请参阅在阿里云容器服务中运行离线作业
基于容器服务提供的离线计算原语,可以大大简化事件调度,让开发人员关注与事件处理逻辑自身。同时针对业务需求您可以定制事件控制器的实现来控制数据处理流程,比如提供基于管道或者有向无环图的支持。
借力于容器服务提供的高可靠性,如果Docker节点失效会自动调度到其他节点执行;利用容器服务的弹性资源管理,平台可以动态调整集群规模,让其能够满足应用对资源的需求;阿里云容器服务还提供了集成的日志处理和应用监控能力,可以方便地诊断和管控任务执行。
在阿里云上部署
在阿里云上部署基于容器的Serverless管理应用是非常简单的。可以通过下面的 docker-compose模板实现一键部署:
controller:
image: registry.cn-hangzhou.aliyuncs.com/denverdino/docker-serverless-controller
environment:
- CLUSTER_URL=${CLUSTER_URL}
labels:
aliyun.addon: "serverless"
aliyun.routing.port_5000: serverless
由于需要让事件调度器访问容器集群的manager节点,我们需要配置相应的访问证书和集群访问端点。相关信息可以通过选择集群的连接信息来获得:
在容器服务中为了保证安全访问,集群manager需要通过HTTPS来进行访问,并且通过证书方式进行认证。每个集群的证书不同,而且可以动态地吊销和重新生成证书。在每个节点上的/etc/docker/
目录下都会包含针对这个节点的集群的访问证书。在节点上容器应用中一个更加简单获得证书的方法是通过label将其标识为aliyun.addon
这样,容器就可以在其内部的/etc/docker/
目录下获得集群的访问证书了,具体实现示例请参见Github中代码
这样我们可以方便地在容器服务上部署上述模板,部署时只需填写集群的访问端点即可。
这样一个Serverless控制器就部署完成了
访问不同的访问端点就可以触发相应的事件处理逻辑
总结
Serverless 是一种构建分布式计算的应用的方法,而 Docker 是完美的实现Serverless的基础技术。阿里云容器服务在兼容Docker Swarm集群管理和容器调度的同时提供了针对离线计算,服务管控等众多扩展,使得构建一个无需运维(NoOps)的Serverless应用环境非常简单。
如果需要构建一个真正意义上的Serverless平台,当然还有很多需要考虑的事情:比如提供多租户环境的安全隔离;优化调度逻辑来重用容器,避免每次创建等等。这些问题阿里云函数服务等商业化的Serverless平台都会有系统化的解决之道。
想了解更多容器服务内容,请访问 https://www.aliyun.com/product/containerservice