任何相对完整的应用服务都不多是由单一的程序来完成支持,计划使用 Docker 来部署的服务更是如此。大型服务须要进行拆分,造成微服务集群方能加强其稳定性和可维护性。本篇随笔将对 Docker Compose 和 Docker Swarm 的原理和配置作整理概括,并分享其使用经验。
html
Docker Compose 的配置文件采用 YAML 格式,所以有必要在正文以前简要说明下。YAML 是一门专门用来写配置文件的语言,设计目标就是方便读写,其实质上是一种通用的数据串行化格式,基本语法规则以下:node
#
表示注释。YAML 支持的数据结构有三种:nginx
animal:cat
。# ex1 - cat - dog - bird # ex2 - - cat - dog - bird # ex3 animal: [cat, dog, bird]
Docker 能够极为方便地部署单个服务,但这时候咱们须要一个工具来整合 Docker 的功能,使之可以更便捷地去管理整个微服务集群的部署和迁移,Docker Compose 正是应此而生。他是由 Python 编写的程序,可以根据指令结合配置文件转换成对应的 Docker API 的操做,并直接体现到 Docker Daemon 中,这就代替咱们完成了重复输入复杂指令的过程,主要功能可分为如下两点:git
安装命令:github
curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose && chmod +x /usr/local/bin/docker-compose
Docker Compose 的核心就是其配置文件,采用 YAML 格式,默认为 docker-compose.yml
,参数详解可查阅“官方文档”,如下只作一个常规摘要。web
全部服务的根节点。redis
指定服务的镜像名,若本地不存在,则 Compose 会去仓库拉取这个镜像:docker
services: web: image: nginx
端口映射,例:json
ports: - "80:80" - "81:81"
挂载主机目录,其中 ro 表示只读,例:后端
volumes: - "/etc/nginx/www:/www" - "/var/run/docker.sock:/tmp/docker.sock:ro"
大多数状况下集群中部署的应该都是无状态服务,服务可复制且不固定在某一台宿主机,因此挂载的数据卷最好应当与宿主机脱离关系,例:
web: services: image: nginx volumes: - type: volume source: logs target: /mnt volume: nocopy: true volumes: logs: driver_opts: type: nfs o: addr=***.cn-hangzhou.nas.aliyuncs.com,rw device: ":/"
固然,这种状况下最好是优先建立数据卷,后在配置文件中引用,例:
docker volume create --driver local \ --opt type=nfs \ --opt o=addr=***.cn-hangzhou.nas.aliyuncs.com,rw \ --opt device=:/ \ logs
volumes: logs: external: true
若必须挂载集群中一台宿主机的目录做为数据卷,则要安装一个 docker 插件:
docker plugin install vieux/sshfs # 若配置了密钥对则可省略 password 参数 docker volume create \ -d vieux/sshfs \ --name sshvolume \ -o "sshcmd=user@1.2.3.4:/remote" \ -o "password=$(cat file_containing_password_for_remote_host)\ sshvolume
配置服务间的网路互通与隔离,例:
services: web: image: nginx networks: - proxy - youclk networks: youclk: external: true proxy: external: true
配置服务密码访问,例:
services: redis: image: redis:latest deploy: replicas: 1 secrets: - my_secret - my_other_secret secrets: my_secret: file: "./my_secret.txt" my_other_secret: external: true
docker secret create [OPTIONS] SECRET [file|-] echo "admin:password" | docker secret create my_secret - docker secret create my_secret ./secret.json
健康检查,这个很是有必要,等服务准备好之后再上线,避免更新过程当中出现短暂的没法访问。
healthcheck: test: ["CMD", "curl", "-f", "http://localhost/alive"] interval: 5s timeout: 3s
其实大多数状况下健康检查的规则都会写在 Dockerfile 中:
FROM nginx RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* HEALTHCHECK --interval=5s --timeout=3s CMD curl -f http://localhost/alive || exit 1
依赖的服务,优先启动,例:
depends_on: - redis
设置环境变量和指定环境变量的文件,例:
environment: - VIRTUAL_HOST=test.youclk.com env_file: - ./common.env
部署相关的配置都在这个节点下,例:
deploy: mode: replicated replicas: 2 restart_policy: condition: on-failure max_attempts: 3 update_config: delay: 5s order: start-first # 默认为 stop-first,推荐设置先启动新服务再终止旧的 resources: limits: cpus: "0.50" memory: 1g
deploy: mode: global # 不推荐全局模式(仅我的意见)。 placement: constraints: [node.role == manager]
若非特殊服务,以上各节点的配置可以知足大部分部署场景了。
Docker 默认包含了 Swarm,所以能够直接使用,初始化命令:docker swarm init
,此时将会默认当前节点为 Leader,如下命令为查看 token:docker swarm join-token (worker|manager)
,其余节点能够用 manager 或者 worker 的身份加入到当前集群,例:
docker swarm join --token SWMTKN-1-3pu6hszjas19xyp7ghgosyx9k8atbfcr8p2is99znpy26u2lkl-1awxwuwd3z9j1z3puu7rcgdbx 172.17.0.2:2377
执行 docker swarm leave
脱离集群。
如下各节点常规操做命令,比较简单,就不解释了:
集群最擅长的就是解决多服务问题,只要在同一 network 之下,服务之间默承认以直接经过 service_name 互通有无。但为了不混乱,各服务与外部的通讯最好统一交给一个反向代理服务转发。因对 nginx 比较熟悉,因此我最初选择的代理是“jwilder/nginx-proxy”:
server { listen 80; server_name localhost; location /alive { return 200; } } server { listen 81; return 301 https://$host$request_uri; }
FROM jwilder/nginx-proxy ADD ./src /etc/nginx/conf.d ADD https://gitee.com/youclk/entry/raw/master/debian/sources-vpc.list /etc/apt/sources.list RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/* HEALTHCHECK --interval=5s --timeout=3s CMD curl -f http://localhost/alive || exit 1
version: "3.5" services: proxy: image: $REGISTRY/proxy ports: - "80:80" - "81:81" volumes: - "/var/run/docker.sock:/tmp/docker.sock:ro" deploy: placement: constraints: [node.role == manager] restart_policy: condition: on-failure max_attempts: 3 update_config: delay: 5s order: start-first resources: limits: cpus: "0.50" memory: 1g
负载均衡使用的是阿里云的 SLB,监听 80 -> 81, 443 -> 80
,这样一个服务就实现了节点检查、代理和 https 重定向为一身。拖 nginx 的福,反正用起来就是爽,点击“Nginx 原理解析和配置摘要”进一步了解。
正所谓乐极生悲,某一次我在扩展 Swarm 集群的时候提高了部分 work 节点为 manager, 而且扩展了代理的数量,这让不少服务频繁出现 503,找来找去我发现问题出在 nginx-proxy 代理上。当服务在各节点分布不均的时候,非 leader 节点上的那个代理没法找到服务,废了老大的劲儿也没找到合理的解决方案。
最后我决定选择“Docker Flow Proxy”做为新的代理(好家伙,这一看文档吓我一跳,我仍是第一次看到私人的开源项目能把参考文档写得这么详细,做者的细腻程度“使人发指”,小弟顶礼膜拜之),如下是个人案例:
version: "3.5" services: proxy: image: vfarcic/docker-flow-proxy ports: - "80:80" networks: - proxy environment: - LISTENER_ADDRESS=swarm-listener - MODE=swarm secrets: - dfp_users_admin deploy: replicas: 2 labels: - com.df.notify=true - com.df.port=8080 - com.df.serviceDomain=localhost - com.df.reqPathSearchReplace=/alive,/v1/docker-flow-proxy/ping swarm-listener: image: vfarcic/docker-flow-swarm-listener networks: - proxy volumes: - /var/run/docker.sock:/var/run/docker.sock environment: - DF_NOTIFY_CREATE_SERVICE_URL=http://proxy:8080/v1/docker-flow-proxy/reconfigure - DF_NOTIFY_REMOVE_SERVICE_URL=http://proxy:8080/v1/docker-flow-proxy/remove deploy: placement: constraints: [node.role == manager] networks: proxy: external: true secrets: dfp_users_admin: external: true
更换代理的过程也并不是一路顺风,我在 https 重定向这个问题浪费了好多时间,最后也没在代理中解决。做者固然是考虑到了这个问题,经典的解决方案应以下:
services: proxy: image: vfarcic/docker-flow-proxy ports: - "80:80" - "443:443" networks: - proxy environment: - LISTENER_ADDRESS=swarm-listener - MODE=swarm deploy: replicas: 2 labels: - com.df.notify=true - com.df.httpsOnly=true - com.df.httpsRedirectCode=301
但奈何哥哥“非经典”呀,个人 https 证书和负载均衡都委托给阿里云的 SLB 了,SLB 代理的后端请求只能限定 http。个人想法仍是监听全部请求 443 端口的域名并返回 301,但如下方案并无成功:
labels: - com.df.notify=true - com.df.httpsRedirectCode=301 - com.df.serviceDomainAlgo=hdr_dom(host) - com.df.srcPort.1=80 - com.df.port.1=8080 - com.df.serviceDomain.1=localhost - com.df.reqPathSearchReplace.1=/alive,/v1/docker-flow-proxy/ping - com.df.srcPort.2=443 - com.df.port.2=8080 - com.df.serviceDomain.2=youclk.com,localhost - com.df.httpsOnly.2=true
固然重定向能够在各服务内部实现,但我不认为这是个好的解决方案。最后的最后,我想反正早晚都要上 CND,因而就在 CND 中加了 https 重定向(哎,就是带宽的费用要 double 咯...):
除了代理,最好再加一个监控服务,我选择了官方案例中的 visualizer ,配合 proxy 示例:
services: visualizer: image: dockersamples/visualizer networks: - proxy volumes: - "/var/run/docker.sock:/var/run/docker.sock" deploy: placement: constraints: [node.role == manager] labels: - com.df.notify=true - com.df.serviceDomain=visualizer.youclk.com - com.df.port=8080 - com.df.usersSecret=admin
visualizer 算是敏感服务了,通常须要用密码保护,这里经过 com.df.usersSecret
指定了密码文件,密码已写入 secrets dfp_users_admin
中。注意,com.df.usersSecret 的值与 dfp_users_* 必须相同,示例已在上文。部署后显示以下:
docker-flow-proxy 还有一个默认的监控服务,显示以下:
不过数据没有统一收集,所以意义不大,看看就好。除此以外就是真正须要部署的应用了,只要服务器性能足够,随便想来几个来几个。
部署命令:docker stack deploy -c docker-compose.yml --with-registry-auth youclk
,私有仓库必须加 --with-registry-auth
才能下载镜像。除此以外经常使用的以下:
# network volume service secret 用法都相似,同出一系嘛... docker stack ls docker stack ps youclk docker stack rm youclk
我使用 Compose 的场景通常都结合 Swarm,所以不多去记手动建立或者更改配置的命令了,意义也不大。除了查看移除等与上文类似之外,此处还应记两个:
docker service logs --tail 10 youclk_proxy docker service update --force youclk_proxy
分别是查看日志和服务异常后强制重启。
到此为止写了蛮多了,其他还有一些比较重要内容的后续有空再整理一篇。总结一下,开头我放的那张图其实很形象:Docker 能够看作集装箱把杂乱的货物一个个整理归类, Compose 则是用于编排这些集装箱,最后 Swarm 就是多提供几条船,挂掉一两条还能继续走,提升稳定性。
不知为什么此刻我会忽然想到一句诗:“天苍苍野茫茫风吹草低见牛羊”,有关联吗?没关联,想到就写了,晚安:)
个人公众号《有刻》,咱们共同成长!