Apache Storm是一个非常常用的实时流计算框架。最近有客户来咨询如何在Docker中运行Apache Storm的问题。我之前读过一篇文章介绍Apache Storm在Docker环境的部署,The Joy Of Deploying Apache Storm On Docker Swarm。文章写的很好,但是整个过程需要从手工构建Docker集群环境开始,再一步步把Storm配置起来,虽然作者提到整个过程是"a real joy",估计绝大多数用户依然会望而生畏。
利用Docker Compose模板,我们可以在本地单机Docker环境快速地搭建一个Apache Storm集群,进行应用开发测试。
基于阿里云容器服务,我们可以在公共云上轻松搭建一个分布式Apache Storm集群,进一步简化部署复杂性并提供生产级别的高可用性,让您感受到穿越风暴(storm)的快乐。
除此之外,本文还会介绍Docker Compose开发和云端部署经验,以及容器部署的调度约束。
本文的Storm示例部署架构如下:
其中包含如下容器
这里使用到的Storm Docker镜像"registry.aliyuncs.com/denverdino/baqend-storm",来自于Baqend Techt提供的baqend/storm镜像。您也可以利用它的Github项目自己构建。
本文的Docker Compose文档,和示例应用等可以从https://github.com/denverdino/docker-storm获得。
如果您希望在直接云端部署验证,可以直接跳过这一节。
在Mac/Windows上,开发者只需利用Docker Toolbox就可安装配置本地的Docker环境。它已经包括了Docker和Docker Compose等工具可以方便地进行容器镜像和编排模板开发。
首先我们下载代码
git clone https://github.com/denverdino/docker-storm.git
cd docker-swarm/local
在Docker Compose模板“docker-compose.yml”中,描述了上图所示的Storm应用架构。其具体内容如下:
version: '2'
services:
zookeeper1:
image: registry.aliyuncs.com/denverdino/zookeeper:3.4.8
container_name: zk1.cloud
environment:
- SERVER_ID=1
- ADDITIONAL_ZOOKEEPER_1=server.1=0.0.0.0:2888:3888
- ADDITIONAL_ZOOKEEPER_2=server.2=zk2.cloud:2888:3888
- ADDITIONAL_ZOOKEEPER_3=server.3=zk3.cloud:2888:3888
zookeeper2:
image: registry.aliyuncs.com/denverdino/zookeeper:3.4.8
container_name: zk2.cloud
environment:
- SERVER_ID=2
- ADDITIONAL_ZOOKEEPER_1=server.1=zk1.cloud:2888:3888
- ADDITIONAL_ZOOKEEPER_2=server.2=0.0.0.0:2888:3888
- ADDITIONAL_ZOOKEEPER_3=server.3=zk3.cloud:2888:3888
zookeeper3:
image: registry.aliyuncs.com/denverdino/zookeeper:3.4.8
container_name: zk3.cloud
environment:
- SERVER_ID=3
- ADDITIONAL_ZOOKEEPER_1=server.1=zk1.cloud:2888:3888
- ADDITIONAL_ZOOKEEPER_2=server.2=zk2.cloud:2888:3888
- ADDITIONAL_ZOOKEEPER_3=server.3=0.0.0.0:2888:3888
ui:
image: registry.aliyuncs.com/denverdino/baqend-storm:1.0.0
command: ui -c nimbus.host=nimbus
environment:
- STORM_ZOOKEEPER_SERVERS=zk1.cloud,zk2.cloud,zk3.cloud
restart: always
container_name: ui
ports:
- 8080:8080
depends_on:
- nimbus
nimbus:
image: registry.aliyuncs.com/denverdino/baqend-storm:1.0.0
command: nimbus -c nimbus.host=nimbus
restart: always
environment:
- STORM_ZOOKEEPER_SERVERS=zk1.cloud,zk2.cloud,zk3.cloud
container_name: nimbus
ports:
- 6627:6627
supervisor:
image: registry.aliyuncs.com/denverdino/baqend-storm:1.0.0
command: supervisor -c nimbus.host=nimbus -c supervisor.slots.ports=[6700,6701,6702,6703]
restart: always
environment:
- affinity:role!=supervisor
- STORM_ZOOKEEPER_SERVERS=zk1.cloud,zk2.cloud,zk3.cloud
depends_on:
- nimbus
topology:
build: ../storm-starter
command: -c nimbus.host=nimbus jar /topology.jar org.apache.storm.starter.RollingTopWords production-topology remote
depends_on:
- nimbus
networks:
default:
external:
name: test-storm
首先我们来构建所需的topology容器镜像,它会利用"../storm-starter"中定义的Dockerfile来构建镜像。如果您需要测试自己的应用,只需要更新其中的jar文件即可。
我们可以运行下列命令构建测试镜像:
docker-compose build
为了充分利用Docker网络的能力,我们需要为 Apache Storm 应用创建一个自定义的bridge网络
docker network create test-storm
提示: 关于容器网络的介绍,请参见之前的博文学习Docker容器网络模型 - 搭建分布式Zookeeper集群。
现在我们可以用下面的命令来一键部署Storm应用
docker-compose up -d
部署完毕,检查Storm应用状态
docker-compose ps
当UI容器启动后,我们可以利用下面的URL在浏览器中来访问Storm UI, http://192.168.99.100:8080/index.html
注意: 如果您本地Docker环境配置与本文不一致,相应的Docker虚机IP地址可能有所不同。您可以通过docker-machine ip default
来获取相应的IP地址,并替换上面URL中的"192.168.99.100"
利用如下命令,您可以伸缩supervisor的数量,比如伸缩到3个实例
docker-compose scale supervisor=3
您也许会发现Web界面中并没有运行中的topology。我们不是已经在Compose模板中定义“topology”容器依赖于“nimbus”服务容器吗?这个原因是Docker Compose只能保证容器的启动顺序,但是无法确保所依赖容器中的应用已经完全启动并可以被正常访问了。
所以,这个我们需要运行下面的命令来再次启动“topolgoy”服务应用来提交拓扑
docker-compose start topology
稍后刷新Storm UI,我们可以发现Storm应用已经部署成功了
在本地Docker单机环境只能做些简单的开发测试。而利用阿里云容器服务,我们可以在一组虚拟机部署和运行分布式Apache Storm。
为了确保应用的可用性,我们需要将Zookeeper的三个容器实例部署到不同的ECS虚拟机上。同理,也需要将supervisor的容器实例部署到不同的ECS虚拟机上。
注意:
首先,我们对之前的Docker Compose模板做一些调整,来适合云端部署。更新版本如下:
version: '2'
services:
zookeeper1:
image: registry.aliyuncs.com/denverdino/zookeeper:3.4.8
container_name: zk1.cloud
environment:
- SERVER_ID=1
- ADDITIONAL_ZOOKEEPER_1=server.1=0.0.0.0:2888:3888
- ADDITIONAL_ZOOKEEPER_2=server.2=zk2.cloud:2888:3888
- ADDITIONAL_ZOOKEEPER_3=server.3=zk3.cloud:2888:3888
- affinity:role!=zookeeper
labels:
role: zookeeper
zookeeper2:
image: registry.aliyuncs.com/denverdino/zookeeper:3.4.8
container_name: zk2.cloud
environment:
- SERVER_ID=2
- ADDITIONAL_ZOOKEEPER_1=server.1=zk1.cloud:2888:3888
- ADDITIONAL_ZOOKEEPER_2=server.2=0.0.0.0:2888:3888
- ADDITIONAL_ZOOKEEPER_3=server.3=zk3.cloud:2888:3888
- affinity:role!=zookeeper
labels:
role: zookeeper
zookeeper3:
image: registry.aliyuncs.com/denverdino/zookeeper:3.4.8
container_name: zk3.cloud
environment:
- SERVER_ID=3
- ADDITIONAL_ZOOKEEPER_1=server.1=zk1.cloud:2888:3888
- ADDITIONAL_ZOOKEEPER_2=server.2=zk2.cloud:2888:3888
- ADDITIONAL_ZOOKEEPER_3=server.3=0.0.0.0:2888:3888
- affinity:role!=zookeeper
labels:
role: zookeeper
ui:
image: registry.aliyuncs.com/denverdino/baqend-storm:1.0.0
command: ui -c nimbus.host=nimbus
environment:
- STORM_ZOOKEEPER_SERVERS=zk1.cloud,zk2.cloud,zk3.cloud
restart: always
container_name: ui
labels:
aliyun.routing.port_8080: storm-ui
ports:
- 8080:8080
depends_on:
- nimbus
nimbus:
image: registry.aliyuncs.com/denverdino/baqend-storm:1.0.0
command: nimbus -c nimbus.host=nimbus
restart: always
environment:
- STORM_ZOOKEEPER_SERVERS=zk1.cloud,zk2.cloud,zk3.cloud
container_name: nimbus
ports:
- 6627:6627
labels:
aliyun.probe.url: tcp://container:6627
aliyun.probe.initial_delay_seconds: "10"
supervisor:
image: registry.aliyuncs.com/denverdino/baqend-storm:1.0.0
command: supervisor -c nimbus.host=nimbus -c supervisor.slots.ports=[6700,6701,6702,6703]
restart: always
environment:
- affinity:role!=supervisor
- STORM_ZOOKEEPER_SERVERS=zk1.cloud,zk2.cloud,zk3.cloud
labels:
role: supervisor
aliyun.scale: "3"
depends_on:
- nimbus
topology:
image: registry.aliyuncs.com/denverdino/storm-starter:1.0.0
command: -c nimbus.host=nimbus jar /topology.jar org.apache.storm.starter.RollingTopWords production-topology remote
labels:
aliyun.depends: nimbus
depends_on:
- nimbus
这个模板只是针对本地的compose模板做了几个简单的调整和增强。其中变动的部分如下:
除此之外,模板中利用了下列阿里云扩展来简化容器部署
为了保证“zookeeper”服务的高可用性,我们需要描述服务的“anti-affinity”(反亲和性),也就是不允许任意两个zookeeper容器实例部署在同一个VM上。因为容器服务提供了兼容“Docker Swarm”的服务部署约束策略,我们可以利用容器的标签过滤器(label filter)来支持这个场景
为每个“zookeeper”服务,定义 role: zookeeper
标签,并用环境变量定义部署约束 affinity:role!=zookeeper
。这样集群不会把zookeeper容器调度到已经拥有role: zookeeper
标签的节点上
同样,“supervisor”服务容器也利用了反亲和性约束将容器部署到不同节点上。而且"supervisor"服务的aliyun.scale: "3"
指明了容器的伸缩数量,所以系统会自动部署3个Supervisor容器到3个不同的ECS节点上。
另外,上节谈到了docker-compose无法解决“topology”和“nimbus”容器之间的依赖次序,而容器服务提供了一个更加优雅的方案来保证容器之间的正确依赖
aliyun.probe.url: tcp://container:6627
可以通过tcp端口6627的可用性来判断容器的健康状态aliyun.depends: nimbus
,让其等待"nimbus"进入健康状态之后再启动。这就确保了容器应用之间的正确的时序依赖。我们还利用aliyun.routing.port_8080: storm-ui
标签,这样可以为“ui”服务提供虚拟域名“storm-ui”来直接访问Storm UI,而无需关系Storm UI容器具体部署在集群中的哪一个节点。
通过容器服务控制台,基于以上面的模板之间部署一个容器应用,几分钟以后一个分布式Storm应用已经在云端运行起来!我们点击“ui”服务端点,就可以打开Storm UI。
检查应用的容器列表,这里我们可以发现不同的zookeeper容器被部署到不同的ESC实例上。
Topoloy也已经成功提交,这个过程是不是很简单 :-)
上文中,利用简单的调度约束可以让系统在集群中自动挑选合适的节点部署容器。但在真实的场景中,很多用户希望能够把容器部署到指定的一个或多个节点之上。这时应该如何做呢?
对于这类需求,我们可以利用节点过滤器(Node filters)来解决。
当节点加入集群之后,容器服务会为每个节点提供一些预定义的标签。
此外,用户还可以为节点编辑自定义标签。在控制台集群列表页面,选择集群并点击更多操作,我们可以看见如下操作菜单:
点击“用户标签管理”,就可以方便地编辑节点标签
提示: 与Docker的社区实现不同,在容器服务中为指定Docker节点添加标签,无需编辑配置文件并重启Docker Engine即可生效。
下面的示例,我们希望把三个zookeeper节点依次部署到第一、第二和第三号节点。另外把“ui”和“nimbus”容器部署到包含“server:manager”标签的节点上。这时候Compose模板如下:
version: '2'
services:
zookeeper1:
image: registry.aliyuncs.com/denverdino/zookeeper:3.4.8
container_name: zk1.cloud
environment:
- SERVER_ID=1
- ADDITIONAL_ZOOKEEPER_1=server.1=0.0.0.0:2888:3888
- ADDITIONAL_ZOOKEEPER_2=server.2=zk2.cloud:2888:3888
- ADDITIONAL_ZOOKEEPER_3=server.3=zk3.cloud:2888:3888
- constraint:aliyun.node_index==1
zookeeper2:
image: registry.aliyuncs.com/denverdino/zookeeper:3.4.8
container_name: zk2.cloud
environment:
- SERVER_ID=2
- ADDITIONAL_ZOOKEEPER_1=server.1=zk1.cloud:2888:3888
- ADDITIONAL_ZOOKEEPER_2=server.2=0.0.0.0:2888:3888
- ADDITIONAL_ZOOKEEPER_3=server.3=zk3.cloud:2888:3888
- constraint:aliyun.node_index==2
zookeeper3:
image: registry.aliyuncs.com/denverdino/zookeeper:3.4.8
container_name: zk3.cloud
environment:
- SERVER_ID=3
- ADDITIONAL_ZOOKEEPER_1=server.1=zk1.cloud:2888:3888
- ADDITIONAL_ZOOKEEPER_2=server.2=zk2.cloud:2888:3888
- ADDITIONAL_ZOOKEEPER_3=server.3=0.0.0.0:2888:3888
- constraint:aliyun.node_index==3
ui:
image: registry.aliyuncs.com/denverdino/baqend-storm:1.0.0
command: ui -c nimbus.host=nimbus
environment:
- STORM_ZOOKEEPER_SERVERS=zk1.cloud,zk2.cloud,zk3.cloud
- constraint:server==manager
restart: always
container_name: ui
labels:
aliyun.routing.port_8080: storm-ui
ports:
- 8080:8080
depends_on:
- nimbus
nimbus:
image: registry.aliyuncs.com/denverdino/baqend-storm:1.0.0
command: nimbus -c nimbus.host=nimbus
restart: always
environment:
- STORM_ZOOKEEPER_SERVERS=zk1.cloud,zk2.cloud,zk3.cloud
- constraint:server==manager
container_name: nimbus
ports:
- 6627:6627
labels:
aliyun.probe.url: tcp://container:6627
aliyun.probe.initial_delay_seconds: "10"
supervisor:
image: registry.aliyuncs.com/denverdino/baqend-storm:1.0.0
command: supervisor -c nimbus.host=nimbus -c supervisor.slots.ports=[6700,6701,6702,6703]
restart: always
environment:
- affinity:role!=supervisor
- STORM_ZOOKEEPER_SERVERS=zk1.cloud,zk2.cloud,zk3.cloud
labels:
role: supervisor
aliyun.scale: "3"
depends_on:
- nimbus
topology:
image: registry.aliyuncs.com/denverdino/storm-starter:1.0.0
command: -c nimbus.host=nimbus jar /topology.jar org.apache.storm.starter.RollingTopWords production-topology remote
labels:
aliyun.depends: nimbus
depends_on:
- nimbus
我们可以非常方便地利用下面约束来选择包含指定标签的节点
constraint:aliyun.node_index==1
等等constraint:server==manager
当Storm部署成功后,再查看相应的容器部署。它们是不是完全依照您的指令来部署到指定节点上了呢?:-)
针对节点的容器调度约束,不但可以让你更好地对集群内节点进行分类管理,还非常适合使用host网络的应用场景。
如果您希望进一步了解容器服务支持的调度和编排能力,您可以查看参考文档
利用容器和容器编排技术可以大大简化大数据框架的部署复杂性,提升部署效率。可以让数据工程师轻松在本地和云端搭建自己的计算环境,进行开发、测试和生产部署。
利用阿里云容器服务可以极大简化Docker集群的创建和管理。容器服务不但完全拥抱社区生态,让本地开发的Docker镜像和Compose模板平滑地迁移到云端;更在编排、调度等方面提供了增强,让您的应用部署运维更加简单,并充分利用阿里云的能力。工程师可以在Compose模板中结合灵活的容器部署约束,随心控制容器部署位置,方便地利用Docker技术一键搭建完备的分布式应用并满足其SLA。
再次感谢Baqend Techt提供的baqend/storm镜像。
想了解更多容器服务的内容,请点击 https://www.aliyun.com/product/containerservice