返回 登录
0

在Docker Swarm上部署Apache Storm

原文地址:The Joy Of Deploying Apache Storm On Docker Swarm
翻译:王鹏,OneAPM工程师
责编:陈晨

本文来自Baqend Tech Blog,描述了如何在Docker Swarm,而不是在虚拟机上部署和调配Apache Storm集群。这个题目很有意思,Wolfram Wingerath将之描述为“真正有趣”的体验,在Tech上你很少能听见这种话。我好奇地问他是什么让使用容器比使用虚拟机更棒?他回答说:

作为一名Docker和Docker Swarm的新手,我肯定还有很多不知道的事。不过,在我看来,在Docker上部署(及一般操作)比在虚拟机甚至裸机上更有趣,因为Docker剥离了异构性和许多问题。一旦运行了Docker,你就可以用一行声明语句来启动MongoDB或者Redis的服务器等东西。Docker Swarm集群可以帮你做同样的事,而且Docker还会帮你把启动的东西分发给集群中的某个服务器。Docker甚至会帮你下载正确的镜像,如果你本地没有的话。你也不用解决连接问题,因为只要在同一Docker网络中,任一台机器都与所有其他的机器互联互通。正如在本教程中所提到的,只要你使用了overlay网络,分布式安装也能实现。

你在邮件中引用到了我的一些话,当我在写它们的时候,我的脑海深处想起了几个月前,我需要安装和运行一个拥有超过16个节点的Apache Storm集群的事。当时有好几个问题,比如,我对AWS并不太熟悉(以前是用OpenStack的),还有与(Storm使用的)Netty的连接性问题,以及AWS的主机名解析问题。这些问题在我设置OpenStack的时候从没出现过。最终我们花费了数天及数百美元去解决它们。我真心认为,如果你使用Docker,你就不会遇到这些麻烦,因为你的环境始终如一:即Docker。

回到教程上来

Bagend Cloud即将支持查询缓存和连续查询的功能,我们将依靠Apache Storm来处理低延迟的数据。已经有好几个项目都致力于实现在Docker上部署多服务器Storm (例如wurstmeister/storm-dockerviki-org/storm-docker),但是越过服务器数量的限制似乎会使事情变得复杂。既然可扩展性和易操作性是我们部署的关键,我们从一开始就使用Dock Swarm,也很高兴地看到事情进展的如此顺利。我们希望通过这篇教程来分享我们的经历,提升你对即将发布的Baqend实时API的兴趣,最终宣传一下Dock Swarm(因为它真的很牛!):-)

如果你是Swarm新手,请看我们的AWS Meetup Docker幻灯片

接下来的计划

概述

首先,我们将介绍一个简单的部署范例,并解释其中的每一部分。接着,我们会告诉你在TL;DR中所需做的最简单的准备工作(使用事先准备好的实用脚本程序)。然后,我们会来到本教程的核心部分,一步步向你展示Docker Swarm集群及多节点Apache Storm集群的部署过程。当然,我们也会做一些与Storm(特别是部署和终止远程服务器上的拓扑)以及Swarm(例如重启manager节点和终止整个Storm集群)都涉及到的常规工作。

概述:部署

下图是部署的架构图:

enter image description here

有三台运行Ubuntu Server 14.04的机器,每台都会运行一个Docker守护进程,同时每台都装有几个容器。经过初始设置,你只能访问其中一台机器(Ubuntu 1),很大程度上来说,会觉得只有一个Docker守护进程。

安装Swarm之后,你会创建一个覆盖网络(stormnet),这样不同Swarm节点间的Docker容器就可以相互通信了。最终,你将建立起一个成熟的Storm集群,这个集群使用现有的 ZooKeeper 共同协调,并通过stormnet实现节点间的通信。虽然监管容器将每一台服务器策略分发,Nimbus和UI容器则会安装在manager节点上(Ubuntu 1)。

必须允许对Ubuntu 1机器的公共访问(即分配一个公共IP和开放端口8080!)。否则,你就看不到Storm美丽的UI了。

详细教程

我们给三个Ubuntu机器的域名分别是zk1.cloud、zk2.cloud和zk3.cloud。由于ZooKeeper服务器和manager节点从概念上说是两个不同的角色,我们使用manager.swarm和manager.swarm.baqend.com作为manager节点的私有IP地址和公共IP地址。尽管本教程中,Ubuntu 1实际上是扮演了ZooKeeper 1和管理者两个角色,但你可以在自己部署的时候使用不同两个服务器来完成。在Github上check out这个教程,在readme.me中,根据你自己的域名,查找并替换我们的原有域名,然后你就可以把我们的大部分语句复制粘贴到我们将要使用的外壳程序中了。

太长不看?

对于那些急着看结果的人来说,我们也准备了一些脚本!这些是部署Swarm和Storm所需的全部脚本了。但是,为了便于理解,后面还是会有一个详细的步骤描述的。

所以,在讨论细节之前,这有一份快速指南:

1.创建一个Ubuntu 14.04服务器 – 我们称之为Ubuntu 1 – 然后通过SSH连接它。然后执行以下语句check out指南中的脚本来安装Docker:

    sudo apt-get install git -y && \
    cd /home/ubuntu/ && \
    git clone https://github.com/Baqend/tutorial-swarm-storm.git && \
    chmod +x tutorial-swarm-storm/scripts/* && \
    cd tutorial-swarm-storm/scripts/ && \
    sudo bash installDocker.sh && \
    sudo usermod -aG docker ubuntu && \
    sudo shutdown -h now

2.机器会自动关机。关机的时候,生成快照。

3.启动两台你刚刚快照过的机器(Ubuntu 2和Ubuntu 3),使用一下自定义脚本把它们做成Swarm worker节点:

    #!/bin/bash
    cd /home/ubuntu/ && rm -rf tutorial-swarm-storm && \
    git clone https://github.com/Baqend/tutorial-swarm-storm.git && \
    cd tutorial-swarm-storm/scripts/ && \
    chmod +x ./* && \
    ./init.sh zk1.cloud,zk2.cloud,zk3.cloud

注意:你需要把逗号分隔开的主机名替换成你自己的主机名。

4.对域名服务器做如下设置:把列表中的第一台主机zk1.cloud指向Ubuntu1,剩下的zk2.cloud和zk3.cloud分别指向Ubuntu 2和Ubuntu 3。另外要确保manager.swarm.baqend.com和manager.swarm分别被解析为Ubuntu 1的公开IP地址和私有IP地址。

5.确保主机之间可以互相访问:需要打开端口2181、2888、3888 (ZooKeeper)、2375 (Docker Swarm)和6627 (Storm,远程拓扑部署)。为了保证能从外部访问Storm UI,还必须公开manager.swarm.baqend.com:8080。

6.最后,启动Ubuntu 1同时运行下列代码,配置ZooKeeper ensemble、Swarm和Storm:

    cd /home/ubuntu/tutorial-swarm-storm/scripts/ && \
    ZOOKEEPER=zk1.cloud,zk2.cloud,zk3.cloud && \
    sudo bash init.sh $ZOOKEEPER manager && \
    . swarm.sh $ZOOKEEPER && \
    . storm.sh $ZOOKEEPER 3

再次提醒:记得把其中的主机名替换为你自己的。

你现在应该可以访问http://manager.swarm.baqend.com:8080下的Storm UI了。

另外,当你在manager节点上输入

    docker info

时,你将会看见UI和Nimbus容器在同一台机器上运行,而Swarm管理器和监控容器则在不同的机器上运行。

再做一次:准备一个镜像

好,现在让我们来看一遍详细的步骤。为了避免重复的步骤,我们只在一台机器上进行这些准备工作,然后关机并快照。接着我们通过这个快照创建其它机器。

让我们开始吧:

1.创建Ubuntu 1,作为Ubuntu 14.04服务器,然后通过SSH连接它,执行下列语句安装Docker:

    sudo apt-get update && sudo apt-get install apt-transport-https ca-certificates && sudo apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D \
    && echo "deb https://apt.dockerproject.org/repo ubuntu-trusty main" | sudo tee -a /etc/apt/sources.list.d/docker.list \
    && sudo apt-get update && sudo apt-get purge lxc-docker && sudo apt-cache policy docker-engine \
    && sudo apt-get update -y && sudo  apt-get install -y linux-image-extra-$(uname -r) apparmor docker-engine git make \
    && sudo usermod -aG docker $(whoami)

(关于Docker安装的细则可以看这里

由于Docker是通过一个key文件去识别不同的docker守护进程的,因此在快照之前,我们要停止docker守护进程,删掉这个key文件(重启Docker后会生成一个新的Key文件),关机之前记得快照。

    sudo service docker stop \
    && sudo rm /etc/docker/key.json

注意:如果你不在快照之前删掉这个Key文件,则所有根据这个镜像生成的机器都会拥有同样的识别符,最终你的Swarm集群一片狼藉。

3.最后,我们只 需要用同一个方法准备一个机器,这个机器在下次boot的时候会成为Swarm worker。为此,我们用文本编辑器,例如nano,创建一个文件 /etc/init.sh。

    sudo nano /etc/init.sh

接着,我们复制下列代码,并保存:

    #!/bin/bash
    # first script argument: the servers in the ZooKeeper ensemble:
    ZOOKEEPER_SERVERS=$1

    # second script argument: the role of this node:
    # ("manager" for the Swarm manager node; leave empty else)
    ROLE=$2

    # the IP address of this machine:
    PRIVATE_IP=$(/sbin/ifconfig eth0 | grep 'inet addr:' | cut -d: -f2 | awk '{ print $1}')

    # define label for the manager node:
    if [[ $ROLE == "manager" ]];then LABELS="--label server=manager";else LABELS="";fi
    # define default options for Docker Swarm:
    echo "DOCKER_OPTS=\"-H tcp://0.0.0.0:2375 \
        -H unix:///var/run/docker.sock \
        --cluster-advertise eth0:2375 \
        $LABELS \
        --cluster-store \
        zk://$ZOOKEEPER_SERVERS\"" \
    | sudo tee /etc/default/docker

    # restart the service to apply new options:

    sudo service docker restart

    echo "let's wait a little..."
    sleep 30

    # make this machine join the Docker Swarm cluster:
    docker run -d --restart=always swarm join --advertise=$PRIVATE_IP:2375 zk://$ZOOKEEPER_SERVERS

4.现在,我们要关机了。

    sudo shutdown -h now

接着快照一下。

5.现在,通过快照的镜像,启动另外两台机器(Ubuntu 2和Ubuntu 3)。使用下面的语句作为初始/自定义脚本:

    #!/bin/bash
    /bin/bash /etc/init.sh \
        zk1.cloud,zk2.cloud,zk3.cloud

注意:如果你使用的是OpenStack,上述脚本可以加为自定义脚本。但如果是AWS,则应加为用户数据。

6.重启已经快照过的机器(Ubuntu 1),连接机器,接着执行以下代码:

    /bin/bash /etc/init.sh \
        zk1.cloud,zk2.cloud,zk3.cloud \
        manager

这会在机器上建立一个Swarm worker,并标之为Swarm管理器。

7.对域名服务器做如下设置:将列表中的第一个域名(zk1…)指向Ubuntu1上的管理器,剩下的两个域名(zk2…和zk3…)指向另外两台刚刚启动的机器,即Ubuntu 2和Ubuntu 3。另外要确保manager.swarm.baqend.com和manager.swarm分别被解析为Ubuntu的公开IP地址和私有IP地址。

8.最后,完成安全设置,使端口2181、2888、3888 (ZooKeeper)、2375 (Docker Swarm)和6627 (Storm, 远程拓扑部署)上的机器可以互相访问。如果你希望可以实现从外部访问Storm UI,那么还需要公开manager.swarm.baqend.com:8080。

见证奇迹的时刻到了!

创建Swarm集群

如果一切顺利,那么你现在已经有了三台Ubuntu服务器,每个上面都运行了一个Docker守护进程。可以通过私有网络中的zk1.cloud和manager.swarm访问Ubuntu 1,或者也可以从外部通过manager.swarm.baqend.com(至少在8080端口)访问。我们一直在这台机器上折腾,并且,从现在开始,这是我们唯一需要访问的机器。为了保证Swarm节点之间的协调通畅,我们需要配置ZooKeeper ensemble和Swarm管理器。

1.通过SSH连接Ubuntu1,接着快速检查一遍。如果Docker安装正确,运行下列代码可以显示出一个所有运行中的Docker容器的列表(只有针对Swarm的):

    docker ps

2.现在我们可以按下面的方法在每台机器上启动一个ZooKeeper节点:

    docker -H tcp://zk1.cloud:2375 run -d --restart=always \
          -p 2181:2181 \
          -p 2888:2888 \
          -p 3888:3888 \
          -v /var/lib/zookeeper:/var/lib/zookeeper \
          -v /var/log/zookeeper:/var/log/zookeeper  \
          --name zk1 \
          baqend/zookeeper zk1.cloud,zk2.cloud,zk3.cloud 1
    docker -H tcp://zk2.cloud:2375 run -d --restart=always \
          -p 2181:2181 \
          -p 2888:2888 \
          -p 3888:3888 \
          -v /var/lib/zookeeper:/var/lib/zookeeper \
          -v /var/log/zookeeper:/var/log/zookeeper  \
          --name zk2 \
          baqend/zookeeper zk1.cloud,zk2.cloud,zk3.cloud 2
    docker -H tcp://zk3.cloud:2375 run -d --restart=always \
          -p 2181:2181 \
          -p 2888:2888 \
          -p 3888:3888 \
          -v /var/lib/zookeeper:/var/lib/zookeeper \
          -v /var/log/zookeeper:/var/log/zookeeper  \
          --name zk3 \
          baqend/zookeeper zk1.cloud,zk2.cloud,zk3.cloud 3

通过明确说明-H……参数,我们可以在不同的主机上启动ZooKeeper容器。-p命令打开ZooKeeper默认需要的那些端口。两个-v命令通过将ZooKeeper使用的文件夹映射到对应的主机文件夹,可以在发生容器错误的情况仍然保持连贯性。以逗号分隔的主机名列表通知ZooKeeper这一集合中有哪些服务器。集合中的每个节点都不例外。唯一的变量就是ZooKeeper的ID(第二个参数),因为它对每个容器都是不一样的。

你可以使用以下命令行来检查ZooKeeper是否一切正常:

    docker -H tcp://zk1.cloud:2375 exec -it zk1 bin/zkServer.sh status && \
    docker -H tcp://zk2.cloud:2375 exec -it zk2 bin/zkServer.sh status && \
    docker -H tcp://zk3.cloud:2375 exec -it zk3 bin/zkServer.sh status

如果你的集群一切正常,每个节点都会汇报它们是主节点还是从节点。

Now it’s time to start the Swarm manager:

现在是开启Swarm管理器的时候了:

    docker run -d --restart=always \
          --label role=manager \
          -p 2376:2375 \
          swarm manage zk://zk1.cloud,zk2.cloud,zk3.cloud

现在Swarm集群正在运行。但是我们必须把这一点告诉Docker客户端。最后你只须保证之后所有的Docker运行语句都指向Swarm管理器的容器(它会负责排程),并且不违反本地Docker守护进程。

    cat << EOF | tee -a ~/.bash_profile
        # this node is the master and therefore should be able to talk to the Swarm cluster:
        export DOCKER_HOST=tcp://127.0.0.1:2376
    EOF
```    export DOCKER_HOST=tcp://127.0.0.1:2376


上面这段会立即执行,并且保证下次我们登录机器的时候被再次执行。






<div class="se-preview-section-delimiter"></div>

##健康度检查


现在一切都被启动运行了。键入




<div class="se-preview-section-delimiter"></div>
docker info

检查一下manager节点上的集群状态。你会看到3个运行中的worker,类似这样:




<div class="se-preview-section-delimiter"></div>
Nodes: 3
 docker1: zk1.cloud:2375
  └ Status: Healthy
  └ Containers: 3
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 2.053 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=3.13.0-40-generic, operatingsystem=Ubuntu 14.04.1 LTS, server=manager, storagedriver=devicemapper
  └ Error: (none)
  └ UpdatedAt: 2016-04-03T15:39:59Z
 docker2: zk2.cloud:2375
  └ Status: Healthy
  └ Containers: 2
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 2.053 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=3.13.0-40-generic, operatingsystem=Ubuntu 14.04.1 LTS, storagedriver=devicemapper
  └ Error: (none)
  └ UpdatedAt: 2016-04-03T15:39:45Z
 docker3: zk3.cloud:2375
  └ Status: Healthy
  └ Containers: 2
  └ Reserved CPUs: 0 / 1
  └ Reserved Memory: 0 B / 2.053 GiB
  └ Labels: executiondriver=native-0.2, kernelversion=3.13.0-40-generic, operatingsystem=Ubuntu 14.04.1 LTS, storagedriver=devicemapper
  └ Error: (none)
  └ UpdatedAt: 2016-04-03T15:40:15Z
最重要的是每个节点的Status: Healthy那一行。如果你发现出现了其它的状态,例如Status: Pending,或者有的节点没有显示出来,那么即使其它地方还没有报错,也应该用以下命令试着重启管理容器,




<div class="se-preview-section-delimiter"></div>
docker restart $(docker ps -a --no-trunc --filter "label=role=manager" | awk '{if(NR>1)print $1;}')

然后再检查一次(这个操作可能会引发一个错误信息;无视它)。






<div class="se-preview-section-delimiter"></div>

##创建Storm集群


现在Swarm已经运行起来了,你将要创建一个覆盖网络来跨越所有的Swarm节点,为单独的Storm组建运转多个容器。尽管Supervisor节点(即Storm workers)散布于Swarm集群中的所有节点,Nimbus和UI会在manager节点上,每台服务器上一个。


1.先创建覆盖网络stormnet:




<div class="se-preview-section-delimiter"></div>
docker network create --driver overlay stormnet

然后通过Docker来检查stormnet是否存在:




<div class="se-preview-section-delimiter"></div>
docker network ls

2.现在一个一个地启动Storm组件。每个Storm相关的容器会有一个cluster=storm标记,这样你在后面杀死整个Storm集群时,不会错杀其它容器。


首先,启动UI




<div class="se-preview-section-delimiter"></div>
docker run \
    -d \
    --label cluster=storm \
    --label role=ui \
    -e constraint:server==manager \
    -e STORM_ZOOKEEPER_SERVERS=zk1.cloud,zk2.cloud,zk3.cloud \
    --net stormnet \
    --restart=always \
    --name ui \
    -p 8080:8080 \
    baqend/storm ui \
      -c nimbus.host=nimbus

接下来是Nimbus:




<div class="se-preview-section-delimiter"></div>
docker run \
    -d \
    --label cluster=storm \
    --label role=nimbus \
    -e constraint:server==manager \
    -e STORM_ZOOKEEPER_SERVERS=zk1.cloud,zk2.cloud,zk3.cloud \
    --net stormnet \
    --restart=always \
    --name nimbus \
    -p 6627:6627 \
    baqend/storm nimbus \
      -c nimbus.host=nimbus

为了确保这些在manager节点上运行,我们加入一个限制条件:constraint:server==manager。


3.你现在可以访问Storm UI了,就好像它是在manager节点上运行一样:假如你的manager节点有个公开IP并且其端口8080开放,你就可以用链接[http://manager.swarm.baqend.com:8080](http://t.umblr.com/redirect?z=http://manager.swarm.baqend.com:8080&t=YWFkOTgxYWRjYzIwYTllYTJkNjA0NTcxYjU4MWZmMWY4NzExMGNmMSxoVEVjU3FkZA==)通过浏览器来访问Storm集群。但是目前还没有运行任何supervisor节点。


4.运行以下语句三次,来启动三个supervisor:




<div class="se-preview-section-delimiter"></div>
docker run \
    -d \
    --label cluster=storm \
    --label role=supervisor \
    -e affinity:role!=supervisor \
    -e STORM_ZOOKEEPER_SERVERS=zk1.cloud,zk2.cloud,zk3.cloud \
    --net stormnet \
    --restart=always \
    baqend/storm supervisor \
     -c nimbus.host=nimbus \
     -c supervisor.slots.ports=[6700,6701,6702,6703]

因为我们无所谓具体启动哪里的supervisor节点,这里我们不用加入任何限制条件或容器名。但是,为了防止同一台机器上的有两个supervisor被启动,我们用了一个**affinity标记**:affinity:role!=supervisor。如果你要用更多的supervior容器,就得添加更多的Swarm worker节点(Ubuntu 4、Ubuntu 5等等)。


5.看一眼Storm UI,确保有三个Supervisor在运行。






<div class="se-preview-section-delimiter"></div>

##(远程)拓扑部署


可以通过与manager主机在同一网络中的任意一台装有Docker守护进程的服务器来部署拓扑网络。在下面的代码中,假设你目前使用的目录中,拓扑fatjar是一个名为topology.jar的文件。




<div class="se-preview-section-delimiter"></div>
docker -H tcp://127.0.0.1:2375 run \
   -it \
   --rm \
   -v $(readlink -m topology.jar):/topology.jar \
   baqend/storm \
     -c nimbus.host=manager.swarm \
     jar /topology.jar \
       main.class \
       topologyArgument1 \
       topologyArgument2

**注意:**这个命令会生出一个Docker容器,部署拓扑,接着删除容器。我们用-H tcp://127.0.0.1:2375参数来确保容器是在你当前使用的机器上启动的。如果让Docker Swarm自己来编排,部署就可能会失败,因为在生成容器的主机上可能找不到必要的拓扑文件。


另外,readlink -m topology.jar会为topology.jar生成一个绝对路径,因为并不支持相对路径。但是你也可以直接提供一个绝对路径。





<div class="se-preview-section-delimiter"></div>

##Killing A Topology





<div class="se-preview-section-delimiter"></div>

##终止一个拓扑


可以通过与Storm web UI交互从而终止一个拓扑,或者也可以通过下面的方式,假设正在运行的拓扑叫做runningTopology:




<div class="se-preview-section-delimiter"></div>
docker run \
   -it \
   --rm \
   baqend/storm \
     -c nimbus.host=manager.swarm \
     kill runningTopology
此处并不需要主机参数-H ...,因为上述命令是独立的,并不依赖任何文件。






<div class="se-preview-section-delimiter"></div>

##关掉Storm集群


由于每个与Storm相关的容器都有一个cluster=storm标签,你可以用下述语句终止所有的容器:




<div class="se-preview-section-delimiter"></div>
docker rm -f $(docker ps -a --no-trunc --filter "label=cluster=storm" | awk '{if(NR>1)print $1;}')

“`

不要忘记安全性!

我们在此教程中示范了如何在Docker上用多节点的ZooKeeper集为了高可用性和容错性运行一个分布式Storm集群。为了不让这个教程过分复杂,我们跳过了针对TLS配置Docker Swarm的部分。如果你打算把Docker Swarm用于关键的业务应用中,你必须在这个方面多下些功夫。

Baqend是怎样使用Apache Storm的?

我们利用Storm的能力来提供低延迟的流查询和查询缓存:

连续查询

如果你的APP允许连续查询,Apache Storm能接受你的连续查询和所有你的写入操作,并且将它们相比对:每当写一个对象时,所有注册的连续查询都与之比对。既然Storm一直在跟踪所有查询的结果,它就能发现新的主机或者新的匹配或不匹配的情况,并且在变更刚发生的时候通知你。

查询缓存

对于查询缓存来说,我们用这些通知来主动让缓存失效:Baqend Cloud用一段合理长度的TTL来缓存一个查询结果,一旦发生变更就自动让缓存的查询结果无效。得益于Storm,我们的匹配网络延迟仅为几毫秒,相较以往,数据的获取速度显著提高。

Baqend Cloud将很快发布这两大功能,近期,我们也会在Baqend Tech博客发表更多有关架构的细则和标杆性测试结果。敬请期待!

结束语

本教程是为1.10.3版本的Docker和1.1.3的Swarm所做,并在OpenStack和AWS上测试。本教程及我们的Storm和ZooKeeper Docker镜像文件都在Chicken Dance License v0.2的保护下有效。

  • tutorial at GitHub
  • baqend/storm Docker image at Docker Hub and GitHub
  • baqend/zookeeper Docker image at Docker Hub and GitHub
  • Github获取教程
  • Docker HubGitHub获取baqend/storm Docker镜像文件
  • Docker HubGitHub获取baqend/zookeeper Docker镜像文件

你可以在GitHub上复制我们的项目,如有任何改进,也欢迎发起pull请求!

希望你能喜欢这篇教程,并在下面的评论部分给我们留言!

评论