在生产环境实践中,我们发现使用单个docker节点是远远不够的,搭建docker集群势在必行。然而,面对Kubernetes、Mesos以及Swarm等众多容器集群系统(或者叫容器编排工具),我们该如何选择呢?通过对比发现,Swarm是docker原生的,同时也是最简单的、最易学的、最节省资源的,值得我们多了解一下。本文将给出一些实用的建议。
如果你在单个生产节点上用过docker,然后发现单个节点的资源不够用,那你会怎么做?关于在生产环境中使用Docker Swarm,我会为你提供一些建议,这些都是我一年来积累的经验。如果你对Docker Swam不熟悉,可以参考我之前的博客。
我并不打算重复官方文档。尽管文档非常短,但是通过它可以了解Swarm的基本知识。另外,我也不会写如何搭建Swarm集群,这方面的资料太多了,你可以查看Digital Ocean或者自己谷歌,而我是使用Ansible搭建Swarm集群的。
据我观察,Swarm进行调度和通信的CPU负载非常低。因此,Swarm的管理节点(Manager)同时可以作为工作节点(Worker)。如果你需要搭建一个非常大的集群(1000+节点),管理节点需要更多资源,但是对于中小型集群来说,管理节点需要的资源可以忽略不计。另外,这篇博客介绍了Swarm3k(一个拥有4700节点的Swarm集群),不妨了解一下。
当你开启一个服务的端口之后,在Swarm集群中的任何一个节点都可以访问它。负载均衡也是由Swarm提供。后文会提到一些之前遇到的问题,但是Docker 1.13之后,这些问题都解决了。
下面是我每天需要用到的一些命令
// 查看所有服务
# docker service ls
// 创建服务
# docker service create \
--image nginx \
--replicas 2 \
nginx
// 查看服务的详细信息
# docker service inspect nginx
// 查看服务的容器状态
# docker service ps nginx
// 更新服务
# docker service update \
--image nginx:alpine \
nginx
// 增加服务实例
# docker service scale nginx=5
// 减少服务实例(这比直接删除服务要好)
# docker service scale nginx=0
// 删除服务
# docker service rm nginx
// 构建新镜像
# docker build -t hub.docker.com/image .
// 将新镜像上传到Docker仓库
# docker push hub.docker.com/image
// 更新服务的镜像
# docker service update --image hub.docker.com/image service
这样可以方便地实现持续部署
分布式系统通常是非常复杂的。与其他容器集群系统(Mesos, Kubernetes)相比,Swarm的学习曲线最低。在没有任何Swarm知识的情况下,我只花了一周时间,就把服务从单个Docker主机迁移到20个节点的Docker集群上。
你的容器同时运行在多个主机上。更新服务时,只需要更新Docker镜像。合理的测试和部署流程是保证成功的关键。
并非所有的服务都应该部署在Swarm集群内。
理论上,你可以通过使用labels将容器部署到特定节点上,但是这样的话,Swarm集群外的节点就很难访问它们了(Docker 1.12没有很好的方法,但是1.13之后可以使用attachable network);如果你允许集群外的节点访问数据库,则所有节点都可以访问它,这显然不符合你的需求。另外,Docker Swarm的跨节点数据卷(cross-host mounted volumes)并不可靠,一个简单的文件上传都可能引起问题。
无状态应用可以用环境变量进行配置(使用ENV指令),或者通过volume映射本地配置文件到容器配置目录。建议为开源工具构建镜像,例如我们可以把Nginx放到Docker镜像中。
下面是我部署在Swarm集群内的服务
下面是我部署在Swarm集群外的服务
由于一个获取真正IP的问题,我很可能会把Nginx运行在Swarm集群之外,或者采用host模式。
Docker仓库,你值得拥有!你可以自己搭建一个,或者使用Docker仓库服务,比如DockerHub或者GitLab Container Registry。不要在服务器上直接构建Docker镜像,因为你有多个节点(在每个节点上构建镜像非常麻烦),而且在创建服务时,你需要指定镜像(这个镜像所有节点都应该可以下载)。如果你配置了私有Docker仓库,则需要指定--with-registry-auth
,否则这些节点将无法下载镜像。
另外,你应该为Docker镜像设置tag版本,这样易于回滚。
所谓半无状态服务,就是容器需要依赖一些不太重要的外部文件。你可以使用数据卷(volume),但是更好的选择是使用ceph或者其他云存储服务。记住,想要获得扩展性,云是最好的选择。
例如我构建Nginx镜像,把配置文件放到镜像中。使用数据卷挂载Nginx配置文件不是很方便。
使用分布式系统时,集中管理日志是非常必要的。我们有很多方案,包括开源工具或者SaaS服务,比如ELK,Grafana, Graylog,自己搭建完整的系统非常复杂,所以我建议搭建先使用SaaS服务(比如Loggly, Logentries ),当费用太高时,则自己搭建一个ELK系统。ELK可以这样配置:
# docker service update \
--log-driver gelf \
--log-opt gelf-address=udp://monitoring.example.com:12201 \
--log-opt tag=example-tag \
example-service
attachable network是一个非常重要的特性。你最好使用它,否则docker run
创建的容器将无法接入Swarm集群的网络。这是Docker 1.13之后的版本才有的功能,也许你需要升级。创建attachable network的命令如下:
# docker network create --driver=overlay --attachable core
// core是overlay网络名称
如果你按照How to write excellent Dockerfiles构建Docker镜像,你很可能会使用环境变量去配置很多东西。如果你这样做的话,则迁移到Swarm集群时问题会少很多。示例命令如下:
创建服务时指定环境变量
# docker service create \
--env VAR=VALUE \
--env-file FILENAME \
// 增加、删除环境变量
# docker service update \
--env-add VAR=NEW_VALUE \
--env-rm VAR \
下一步是使用Secrets API。简单地说,你可以将私密数据(比如密码,SSL证书等)以文件的形式挂载到容器中。虽然我还没有用过Secrets API,但是我觉得值得尝试。
一方面,你需要保证足够多的容器数来处理负载以及作为灾备;另一方面,太多的容器会导致主机的CPU和内存资源不足。因此,你需要配置合理的应用容器个数,也就是说,某个服务,需要运行合理个数的容器。
另外,默认的update-parallelism值是1,这就意味着更新服务时,每次只更新1个容器。这个值太小了,我的建议是将它设为服务容器数/2
。相关命令
// 将同时更新的容器数设为10
# docker service update \
--update-parallelism 10 \
webapp
// 同时增加多个服务的容器数
# docker service scale redis=1 nginx=4 webapp=20
// 查看服务状态
# docker service ls
// 查看服务的详情(排除关闭的容器)
# docker service ps webapp | grep -v "Shutdown"
最佳方式是使用Docker compose file v3语法,这样可以把服务的所有配置选项代码化。我把docker-compose.yml
用于开发环境,docker-compose.prod.yml
用于生产环境。如果使用docker-compose文件部署服务,需要docker stack deploy
命令(参考docker stack)。docker-compose文件示例:docker-compose.prod.yml
version: '3'
services:
webapp:
image: registry.example.com/webapp
networks:
- core
deploy:
replicas: ${WEBAPP_REPLICAS}
mode: replicated
restart_policy:
condition: on-failure
proxy:
image: registry.example.com/webapp-nginx-proxy
networks:
- core
ports:
- 80:80
- 443:443
deploy:
replicas: ${NGINX_REPLICAS}
mode: replicated
restart_policy:
condition: on-failure
networks:
core:
external: true
部署命令
# export NGINX_REPLICAS=2 WEBAPP_REPLICAS=5 //为compose文件指定环境变量
# docker login registry.example.com
# docker stack deploy \
-c docker-compose.prod.yml\
--with-registry-auth \
frontend
另外,docker-compse文件支持环境变量(${VARIABLE}),这样你可以动态地调整配置。
根据我的经验,你需要限制所有服务的CPU使用。这样可以防止单个容器占用主机的所有的CPU资源。当我希望平均地把所有容器部署到各个docker节点时,我会使用--reserve-cpu
选项,它可以保证每个容器都有足够地CPU资源。示例:
// 限制webapp服务占用的CPU资源,配额0.1核,浮动上限0.25核
# docker service update \
--limit-cpu 0.25 \
--reserve-cpu 0.1 \
webapp
我遇到过Swarm网络方面的问题:有时候所有的请求都被转发到某一个容器,然而还有9个其他容器正在运行。这时,我们可以尝试减少/增加实例个数,或者改变路由类型(使用–endpoint-mode选项)
如果没有监控日志的话,这样的问题很难被发现。因此,搭建监控系统是非常必要的。
Tips for using Docker Swarm mode in production