最近在公司尝试将测试环境 docker 化,由于组件比较多,考虑引入容器编排方案。首先尝试使用 docker-compose 来管理各个组件实例。可以跑通基本逻辑,但是目前没有服务发现框架,只能使用 docker 内置网络中的 DNS 发现其它服务,这限定了测试环境只能跑在单机环境上,时间久了各种 OOM。最近看到 docker 新版本附带的 swarm mode,尝试搭建了一套多机 docker 环境,感觉很满足当前的需求,并且对代码基本零侵入。简单的记录了一下搭建过程以及一些注意事项。
Swarm mode 简介
背景
- docker swarm 是 docker 官方编排项目之一,提供将多个 docker engine 实例变成一台虚拟 docker engine 实例的能力,便于 docker 容器集群管理
- docker 1.12 版本推出了 swarm mode 功能,将 docker swarm 深度集成到 docker engine 中,提供了整套更为简洁的 api 对 swarm 集群进行管理。相对于原生的 docker swarm,swarm mode 部署更为简单,无需外置的服务发现框架,并且提供了容器路由服务
概念
- Node: node 是 swarm 集群中一个 docker engine 实例
- Manager node: 对整体集群进行管理和编排,并维护整个集群
- Worker node: 接收 manager node 分配的 tasks,并运行,默认情况下,manager node 本身也作为一个 worker node 运行 task,可以通过改变配置让 manager node 变成一个 manager-only 的纯管理节点
- Task: swarm 集群任务的基本单元,通常是一个 docker container 的实例
- Service: 由一组同类型 task 聚合而成,是 swarm 集群中主要操作对象
- Load balancing: swarm 集群有一套内置的负载均衡策略提供外部访问到目标容器的路由,在创建 Service 时可以发布端口到外部网络上,访问 swarm 集群任意节点该端口的请求都会被路由到该 Service
Swarm mode quick start
初始化 swarm 集群
- 初始化机器,安装 docker-engine (版本需要高于 1.12)
选择一台机器作为 swarm manager 节点,在 console 中运行
docker swarm init
创建 swarm 集群,命令会返回加入集群的命令Swarm initialized: current node (c8dzd8xek2c9csoxj27ycl1nh) is now a manager. To add a worker to this swarm, run the following command: docker swarm join \ --token SWMTKN-1-07bc8loetz13sy5eqv2t1uycqfr9dqabsd7x08gr22w7gj79fc-eiz5ljflasv65rnoq36aj2mfc \ 192.168.65.2:2377 To add a manager to this swarm, run 'docker swarm join-token manager' and follow the instructions.
在另一台机器上,运行提示中的命令加入集群,如果没有保存之前的提示,可以通过
docker swarm join-token worker
查询docker swarm join \ --token SWMTKN-1-07bc8loetz13sy5eqv2t1uycqfr9dqabsd7x08gr22w7gj79fc-eiz5ljflasv65rnoq36aj2mfc \ 192.168.65.2:2377
在 manager 节点上运行
docker info
可以看到 swarm 集群的信息Swarm: active NodeID: c8dzd8xek2c9csoxj27ycl1nh Is Manager: true ClusterID: cj75fozp5effsiyuuq2dixv95 Managers: 1 Nodes: 2 Orchestration: Task History Retention Limit: 5 Raft: Snapshot Interval: 10000 Heartbeat Tick: 1 Election Tick: 3 Dispatcher: Heartbeat Period: 5 seconds CA Configuration: Expiry Duration: 3 months Node Address: 192.168.65.2
在节点上运行
docker swarm leave
可以让当前节点离开集群,如果是管理节点,需要增加--force
参数
创建一个 service
- 在 manager 节点上运行命令
docker service create --replicas 1 --name redis docker-registry-cn.easemob.com/redis
创建 redis 服务,replicas
参数指定该服务中的 task 数量- 如果是私有 registry,需要在 manager 节点上执行
docker login
,然后创建服务时,增加--with-registry-auth
参数将认证信息发送到 worker 节点
- 如果是私有 registry,需要在 manager 节点上执行
在 manager 节点上运行
docker service ls
查看所有服务状态ID NAME REPLICAS IMAGE COMMAND 0nqfp11n2xgb redis 1/1 docker-registry-cn.easemob. com/redis
在 manager 节点上运行
docker service ps redis
查看 redis 服务状态ID NAME IMAGE NODE DESIRED STATE CURRENT STATE ERROR 3qpkmxptn8o336h161183a0vz redis.1 docker-registry-cn.easemob.com/redis moby Running Running 17 hours ago
在 manager 节点上运行
docker service update redis
更新服务。例如可以用docker service update redis --publish-add 6379:6379
发布 redis 服务到 swarm 集群 6379 端口上(也可以在创建服务时使用 –publish 参数发布),在 swarm 任意节点上运行redis-cli
都可以连接到 redis 服务- 在 manager 节点上运行
docker service rm redis
可以删除服务
Service 间网络调用
如果 service publish 了端口,可以通过任意 swarm 节点 publish 端口进行调用
➜ ~ docker service update --publish-add 6379:6379 redis1 redis1 ➜ ~ telnet localhost 6379 Trying ::1... Connected to localhost. Escape character is '^]'. set test aaa +OK
创建一个 overlay 的网络,创建服务时,通过
--network
参数连接到 overlay 网络上。通过内建的 DNS 服务,可以通过 service name 解析出 task IP➜ ~ docker network create --driver overlay --subnet 10.0.10.0/24 sandbox e7q4c68ybbw7xhrkb0at47k00 ➜ ~ docker service create --with-registry-auth --replicas 1 --network sandbox --name redis1 docker-registry-cn.easemob.com/redis 0dfqszxp925id4ma9blolyjmz ➜ ~ docker service create --with-registry-auth --replicas 1 --network sandbox --name redis2 docker-registry-cn.easemob.com/redis c3khd0snpu13ixx8popuoludx ➜ ~ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 4c92e01d25fd docker-registry-cn.easemob.com/redis:latest "/entrypoint.sh redis" 12 seconds ago Up 9 seconds 6379/tcp redis2.1.4qbw5c0hawat5rr50hgzpxj7l c60b4659e768 docker-registry-cn.easemob.com/redis:latest "/entrypoint.sh redis" 6 minutes ago Up 6 minutes 6379/tcp redis1.1.4pvn93a6i2n5teou41mddmlxr ➜ ~ docker exec -it c60b4659e768 /bin/bash root@c60b4659e768:/data# ping redis2 PING redis2 (10.0.10.4): 56 data bytes 64 bytes from 10.0.10.4: icmp_seq=0 ttl=64 time=0.109 ms
一些坑点
- 截止 docker 最新版本(1.12.3),在 overlay 网络中,以 VIP 方式启动的服务,可以解析 DNS,但是无法
ping
IP,某些依赖ping
检查网络连通的程序会有问题,见 https://github.com/docker/docker/issues/25497。解决方案是启动模式换成 dnsrr (创建服务时增加参数 –endpoint-mode dnsrr),或者使用 tasks.${service_name} 的方式调用 - 在某些操作系统(例如 Suse)上,service publish 的端口无法连接,原因不明,可能和缺少某些内核模块有关。建议使用 Centos7
- swarm manager 会在 service 中 task 退出时重新提交 task,某些需要一次性运行的 docker image (例如环境初始化脚本的执行)endpoint 或者 cmd 需要修改成 block 形式,以免退出后被重复提交。Docker 1.13 版本允许
docker run
启动的容器 attach 到 swarm service 使用的 network 上,可以将一次性运行的 image 直接以docker run
的方式启动 docker service update --image myimage:latest
不会拉取新镜像,即使远端有更新的版本,只能通过手动拉取镜像后重启 service 来更新服务。这个 bug 会在 docker 1.13 上被修复
Have fun :)