基于Docker Swarm的JStorm集群实践
现在是9102年, 虽然JStorm已经过时了, Docker Swarm也是明日黄花, 但是这一点也不妨碍我写下这篇文章 ????
基本概念
Swarm
Docker Swarm是Docker原生的集群管理工具, 用于管理多台服务器上的Docker容器集群, 并提供扩容, 缩容, 滚动更新等多种功能, 简单易用好上手.Swarm集群上适合部署无状态服务.
在Swarm集群中,节点有两种角色,manager和worker,Swarm命令基本只能在manager节点执行, 一个集群可以有多个manager节点, 但只有一个节点可以成为leader manager node,视服务器具体数量分配manager和worker.
Swarm中task是一个单一的Docker容器, service是指一组task的集合, 即service包含了多个相同的Docker容器, 同Docker-Compose中的service是相同的含义, service定义了这些task的属性,services有两种模式:
- replicated services:只运行指定个数的tasks
- global services:每个节点上运行一个此task
Swarm中有两种网络模式: vip, dnsrr
- vip(默认): 每一个service对应一个vip(虚拟ip), service的vip对应映射多个具体容器的ip, service的ip与容器ip隔离保持不变 , 在vip网络模式下, 可以映射端口到宿主机, 这样可以通过宿主机IP+端口的形式访问到服务
- dnsrr: 每一个service对应其所有容器ip的列表, 通过service name访问时将轮询各个容器, 这种网络模式下不可映射端口到宿主机
当前Swarm的网络可能会踩到某些坑, 不太好用
JStorm
JStorm是一个分布式实时计算引擎
JStorm集群包含两类节点: 主控节点(Nimbus)和工作节点(Supervisor). 其分别对应的角色如下:
主控节点(Nimbus)上运行Nimbus Daemon. Nimbus负责接收Client提交的Topology, 分发代码, 分配任务给工作节点, 监控集群中运行任务的状态等工作
工作节点(Supervisor)上运行Supervisor Daemon, Supervisor通过subscribe Zookeeper相关数据监听Nimbus分配过来任务, 据此启动或停止Worker工作进程. 每个Worker工作进程执行一个Topology任务的子集; 单个Topology的任务由分布在多个工作节点上的Worker工作进程协同处理.
Nimbus和Supervisor节点之间的协调工作通过Zookeeper实现. 此外, Nimbus和Supervisor本身均为无状态进程, 支持Fail Fast; JStorm集群节点的状态信息或存储在Zookeeper, 或持久化到本地, 这意味着即使Nimbus/Supervisor宕机, 重启后即可继续工作. 这个设计使得JStorm集群具有非常好的稳定性.
在部署上, Nimbus节点和Supervisor节点两者的区别就是启动命令不同jstorm nimbus
和jstorm supervisor
集群搭建
准备
- 一台及以上的同一内网服务器
- Swarm需要高版本的Docker(17,18)
- 每台服务器上需要导入docker swarm镜像
$ docker run --rm swarm -v
查看swarm版本, 若本机不存在会直接去拉swarm镜像, 每台服务的docker版本和swarm需要统一 - Swarm集群中各个节点的机器上都要有相应的镜像(启动时不会自动pull镜像), 如果容器需要volume外部目录也要手动创建, 这个不像Docker容器本身一样会自己去创建
初始化Swarm集群
选择一台机器作为manage节点初始化Swarm集群, 执行如下命令:
$ docker swarm init --advertise-addr 本机ip # 执行之后,出现worker的join-token,比如下面示例:
[[email protected] ~]$ docker swarm init --advertise-addr 10.0.0.203
Swarm initialized: current node (dfsazah8sv5rlerkkiqmaj1xk) is now a manager.
To add a worker to this swarm, run the following command:
docker swarm join --token SWMTKN-1-476ch09ncm7isnl7ydyxpkgidr5x33p7p5n861emt924hs00ue-6lz0sdviclopxq94mkjcpf8td 10.0.0.203:2377
To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
复制该命令后在其他机器上执行即以worker身份加入集群
也可以通过如下命令获得join-token:
$ docker swarm join-token worker # 获取manager的token,以woker加入
$ docker swarm join-token manager # 获取manager的token,以manager加入
$ docker swarm inspect # 查看集群详细信息
$ docker node ls # 查看集群节点列表
执行docker info
可以检查到swarm相关的信息, 如下所示
Swarm: active
NodeID: 571mtvouc9jqml6s8gxb8ae5l
Is Manager: true
ClusterID: u79o25zg5ra856r21v2atj53k
Managers: 1
Nodes: 3
Default Address Pool: 10.0.0.0/8 # 此处需要注意
SubnetSize: 24
Orchestration:
Task History Retention Limit: 5
Raft:
Snapshot Interval: 10000
Number of Old Snapshots to Retain: 0
Heartbeat Tick: 1
Election Tick: 10
Dispatcher:
Heartbeat Period: 5 seconds
CA Configuration:
Expiry Duration: 3 months
Force Rotate: 0
Autolock Managers: false
Root Rotation In Progress: false
Node Address: 10.0.0.203
Manager Addresses:
10.0.0.203:2377
# work节点
Swarm: active
NodeID: wam5ww8oxa55v6bhka40yeay1
Is Manager: false
Node Address: 10.1.2.7
Manager Addresses:
10.0.0.203:2377
- 在manager节点安装swarm可视化界面 , 用于查看集群中各个容器的状态
$ docker pull dockersamples/visualizer
$ docker run -it -d -p 8080:8080 --name swarm-status -v /var/run/docker.sock:/var/run/docker.sock dockersamples/visualizer
网络初始化
预先创建一个overlay网络(跨主机的网络管理 )
docker network create --driver overlay net_swarm_test
overlay网络的网段即在docker info
的信息中显示的Default Address Pool
这一值, 表示swarm所创建的容器ip网段, 默认值为10.0.0.0/8, 如果我们的宿主机也是这个网段, 那么在Swarm集群中的服务想访问overly网络之外的地址的时候, 就会出不去, 被overlay的网关转发后无法转发到宿主机的网关上, 此时需要修改overlay网络的网段, 添加参数--subnet
, 例如:--subnet=192.168.0.0/16
执行docker network ls
就可以看到创建的该网络
Compose file v3 参考
在Swarm集群上启动服务可以使用两种方式
- 使用docker service create 命令, 类似于docker run, 以纯命令行的形式启动服务
- 使用compose yaml文件编排服务, 然后使用
docker stack deploy -c swarm-compose.yaml servicePrefixName
启动服务, 类似与docker-compose
推荐使用第2种, 具备更好的维护性
Swarm基于Compose yaml 文件的编排需要使用Compose v3的配置
示例如下:
version: "3.2"
services:
nginx: # swarm service name中下划线的后面部分
#build: ./web/nginx # 不执行build, 在swarm集群中,通过集群分发应用的镜像,可以将镜像推送到Registry仓库
image: nginx:1.13.1 # 指定应用所需的镜像,服务器需要预先准备导入该镜像
ports: # 当endpoint_mode 为vip时才能映射service端口到宿主机上
- "18080:80"
networks:
- overlay # 这里的overlay可以理解为是一个变量名, 指向下面定义的overlay网络
volumes: # 创建容器的服务器上需要有volumes参数所指定的文件及文件夹,swarm不会自己去创建
- ./logs/nginx:/var/log/nginx
# - ./web/nginx/nginx.conf:/etc/nginx/nginx.conf # 对于配置, 推荐做在镜像中
deploy:
# endpoint_mode: dnsrr # 默认是vip, 3.2开始支持这个参数
replicas: 2 # replicas模式,指定集群中实例的个数
#mode: global # global模式
update_config: # 这个选项用于告诉 Compose 使用怎样的方式升级, 以及升级失败后怎样回滚原来的服务。
parallelism: 1 # parallelism: 服务中多个容器同时更新
delay: 10s # delay: 设置每组容器更新之间的延迟时间
failure_action: continue # failure_action: 设置更新失败时的动作, 可选值有 continue 与 pause (默认是:pause)。
monitor: 60s # monitor: 每次任务更新时,监视是否故障的持续时间 (ns|us|ms|s|m|h) (默认:0s)。
max_failure_ratio: 0.3 # max_failure_ratio: 更新期间容忍的失败率
order: start-first # 更新期间的操作顺序 stop-first(旧的task在新的启动前先停止), start-first(启动新的, 务, 并且运行的任务将短暂地重叠), default(stop-first) V3.4支持
restart_policy: # 重启策略
condition: on-failure # 重启条件none, on-failure, any (default: any).
delay: 5s # 在重新启动尝试之间等待多长时间, 指定为持续时间(默认值:0)
#max_attempts: 3 # 设置最大的重启尝试次数, 默认是永不放弃
window: 120s # 在决定重新启动是否成功之前要等待多长时间, 默认是立刻判断, 有些容器启动时间比较, , 指定一个“窗口期”非常重要
placement:
constraints: [node.role == manager] # Constraint 过滤器是绑定到节点的键值对(https://docs.docker.com/engine/reference/commandline/service_create/#specify-service-constraints-constraint)
#environment:
# - "affinity:image==redis" # 调度已经拉取的'redis'镜像的容器,也可以通过容器环境变量指定容器所在的位置(https://docs.docker.com/compose/swarm/#manual-scheduling)
networks:
overlay: # 定义网络, overlay, 被上面的overlay引用
external:
name: ctuswarm_overlay # 使用外部名为ctuswarm_overlay的overlay网络
当我们使用上述这一份yaml文件创建service时, 使用命令 docker stack deploy -c swarm-compose.yaml swarm
, 会创建一个名为swarm_nginx
的服务, 命令中的swarm和yaml中的nginx共同组成了service的名字, 并映射了18080端口到宿主机上, 基于docker swarm的routing mesh可以通过任意一台机器的ip:port访问这个nginx应用
容器(服务)之间的访问可以通过服务名称:端口
直接访问, 例如:nginx需要转发某些请求到其他web服务上可以在nginx.conf中直接配置service_name:port
进行转发, service_name的解析即 vip与dnsrr 这两种网络模式的实现.
部署JStrom集群
定义JStrom service的yaml文件, 保存为jstorm-docker-compose.yml
文件
version: '3.2'
services:
nimbus:
image: timestatic/jstorm:2.2.1
deploy:
replicas: 1
update_config:
parallelism: 1
delay: 10s
failure_action: continue
monitor: 60s
max_failure_ratio: 0.3
restart_policy:
condition: on-failure
delay: 5s
window: 60s
volumes:
- /mydata/jstorm/nimbus/log:/opt/jstorm/logs # mkdir -p /mydata/jstorm/nimbus/log
environment:
- storm_zookeeper_servers=10.1.2.7 # zookeeper address
- nimbus_seeds=jstormswarm_nimbus # nimbus service name
networks:
- default
command: jstorm nimbus
supervisor:
image: timestatic/jstorm:2.2.1
deploy:
replicas: 1
update_config:
parallelism: 1
delay: 10s
failure_action: continue
monitor: 60s
max_failure_ratio: 0.3
restart_policy:
condition: on-failure
delay: 5s
window: 60s
volumes:
- /mydata/jstorm/supervisor/log:/opt/jstorm/logs # mkdir -p /mydata/jstorm/supervisor/log
environment:
- storm_zookeeper_servers=10.1.2.7 # zookeeper address
- nimbus_seeds=jstormswarm_nimbus # nimbus service name
networks:
- default
command: jstorm supervisor
ui:
image: timestatic/jstorm-ui:2.2.1
ports:
- "8080:8080"
deploy:
replicas: 1
restart_policy:
condition: on-failure
delay: 5s
window: 60s
environment:
- storm_zookeeper_servers=10.1.2.7 # zookeeper address
- nimbus_seeds=jstormswarm_nimbus # nimbus service name
volumes:
- /mydata/jstorm/ui/log:/usr/local/tomcat/logs # mkdir -p /mydata/jstorm/ui/log
networks:
- default
networks:
default:
external:
name: net_swarm_test
执行启动命令docker stack deploy -c jstorm-docker-compose.yml jstormswarm
后即创建如下三个服务:
[[email protected] ~]$ docker service ls
ID NAME MODE REPLICAS IMAGE PORTS
8mkllcx4pmtu jstormswarm_nimbus replicated 1/1 timestatic/jstorm:2.2.1
qtv0d1mujyww jstormswarm_supervisor replicated 1/1 timestatic/jstorm:2.2.1
quftb2thxn5p jstormswarm_ui replicated 1/1 timestatic/jstorm-ui:2.2.1 *:8080->8080/tcp
可以使用docker service scale
对Nimbus或Supervisor进行扩容, 例如: docker service scale jstormswarm_supervisor=3
, 将supervisor的实例数变为3个
提交JStorm计算任务
启动
一个JStorm任务(即Topology)的内容通常都是打在一个Jar包中, 通过jstorm jar xx.jar xxx.MainClass topologyName
命令提交到集群中. 同时, 这个任务可能会更新、删除. 因此, 我们可以将这个任务jar包作为一个Docker镜像, 基础镜像即JStorm镜像, 提交任务即启动这个容器同时连接到相应的Nimbus和ZooKeeper地址, 当这个任务提交完之后, 这个容器的使命也就可以认为是结束了.
将一个Topology定义为Swarm的service, 如下, 保存为demo.yaml
文件:
version: "3.2"
services:
demo:
# image: timestatic/jstorm:2.2.1
image: jstrom_demo:1.0
networks:
- default
environment:
- storm_zookeeper_servers=10.1.2.7
- nimbus_seeds=jstormswarm_nimbus
# volumes:
# - ./jstorm-demo-jar-with-dependencies.jar:/jstorm-demo.jar
deploy:
replicas: 1
update_config:
parallelism: 1
delay: 10s
failure_action: continue
monitor: 60s
max_failure_ratio: 0.3
restart_policy:
condition: on-failure
delay: 5s
window: 60s
command: jstorm jar /jstorm-demo.jar com.github.timestatic.jstormdemo.Application wordCountDemo
networks:
default:
external:
name: net_swarm
启动docker stack deploy -c demo.yaml jstorm
, 这样其实就启动了一个只有一个容器的service, 而在容器的启动过程中即提交了Topology, 提交完之后, 即使这个容器挂掉也没有关系. 当然, 这个容器本身不作为集群中的一个节点.
任务更新
对于任务的更新, 需要先到任意一个JStorm容器中kill掉原先的Topology, 然后build一个新的任务镜像, 基于Swarm的更新机制, 可以使用docker service update --image imageName:versionNo serviceName
命令进行更新.
当然也可以直接volume任务jar包然后直接重启
总结
Swarm给我们带来了什么?
因为Nimbus和Supervisor均为无状态进程, 所以在扩容缩容上非常方便, 基于Swarm service的重启机制可以在故障时进行Nimbus和Supervisor的自动重启, 对于任务的提交和更新, 由于JStorm的本身机制实际上大同小异.