前言
任何相对完整的应用服务都不可能是由单一的程序来完成支持, 计划使用 Docker 来部署的服务更是如此大型服务需要进行拆分, 形成微服务集群方能增强其稳定性和可维护性本篇随笔将对 Docker Compose 和 Docker Swarm 的原理和配置做整理归纳, 并分享其使用经验
1. YAML 简介
Docker Compose 的配置文件采用 YAML 格式, 因此有必要在正文之前简要说明下 YAML 是一门专门用来写配置文件的语言, 设计目标就是方便读写, 其实质上是一种通用的数据串行化格式, 基本语法规则如下:
大小写敏感
# 表示注释
使用缩进表示层级关系
缩进时不允许使用 Tab 键, 只允许使用空格
缩进的空格数目不重要, 只要相同层级的元素左侧对齐即可
YAML 支持的数据结构有三种:
对象: animal:cat
数组: 一组中划线开头的行, 例如:
- # ex1
- - cat
- - dog
- - bird
- # ex2
- -
- - cat
- - dog
- - bird
- # ex3
- animal: [cat, dog, bird]
值类型和字符串
2. Docker Compose
2.1 安装与简介
Docker 可以极为方便地部署单个服务, 但这时候我们需要一个工具来整合 Docker 的功能, 使之能够更便捷地去管理整个微服务集群的部署和迁移, Docker Compose 正是应此而生他是由 Python 编写的程序, 能够根据指令结合配置文件转换成对应的 Docker API 的操作, 并直接体现到 Docker Daemon 中, 这就代替我们完成了重复输入复杂指令的过程, 主要功能可分为以下两点:
Service: 代表的是运行同种应用程序的一个或多个相同容器的抽象定义, 也是我们在 Docker Compose 中配置的主要对象在每个 Docker Compose 的配置文件中, 我们可以定义多个服务, 并定义服务的配置, 以及服务于服务之间的以来关系
Project: 代表的是由多个服务所组成的一个相对完整的业务单元
安装命令:
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
2.2 配置参数
Docker Compose 的核心就是其配置文件, 采用 YAML 格式, 默认为 docker-compose.yml , 参数详解可查阅官方文档, 以下只做一个常规摘要
services
所有服务的根节点
image
指定服务的镜像名, 若本地不存在, 则 Compose 会去仓库拉取这个镜像:
- services:
- web:
- image: nginx
- ports
端口映射, 例:
- ports:
- - "80:80"
- - "81:81"
- volumes
挂载主机目录, 其中 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
- networks
配置服务间的网路互通与隔离, 例:
- services:
- web:
- image: nginx
- networks:
- - proxy
- - youclk
- networks:
- youclk:
- external: true
- proxy:
- external: true
- secrets
配置服务密码访问, 例:
- 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
健康检查, 这个非常有必要, 等服务准备好以后再上线, 避免更新过程中出现短暂的无法访问
- 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
依赖的服务, 优先启动, 例:
- depends_on:
- - redis
- environment & env_file
设置环境变量和指定环境变量的文件, 例:
- environment:
- - VIRTUAL_HOST="test.youclk.com"
- env_file:
- - ./common.env
- deploy
部署相关的配置都在这个节点下, 例:
- 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]
若非特殊服务, 以上各节点的配置能够满足大部分部署场景了
3. Swarm
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 脱离集群
以下各节点常规操作命令, 比较简单, 就不解释了:
- docker node demote [NODE]
- docker node inspect [NODE]
- docker node ls
- docker node promote [NODE]
- docker node ps [NODE]
- docker node rm [NODE]
- docker node update [OPTIONS] NODE
4. 应用案例
集群最擅长的就是解决多服务问题, 只要在同一 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 还有一个默认的监控服务, 显示如下:
不过数据没有统一收集, 因此意义不大, 看看就好除此之外就是真正需要部署的应用了, 只要服务器性能足够, 随便想来几个来几个
5. 部署与维护
docker stack
部署命令:
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
- docker service
我使用 Compose 的场景一般都结合 Swarm, 因此很少去记手动创建或者更改配置的命令了, 意义也不大除了查看移除等与上文相似以外, 此处还应记两个:
- docker service logs --tail 10 youclk_proxy
- docker service update --force youclk_proxy
分别是查看日志和服务异常后强制重启
结语
到此为止写了蛮多了, 其余还有一些比较重要内容的后续有空再整理一篇总结一下, 开头我放的那张图其实很形象: Docker 可以看做集装箱把杂乱的货物一个个整理归类, Compose 则是用于编排这些集装箱, 最后 Swarm 就是多提供几条船, 挂掉一两条还能继续走, 提高稳定性
不知为何此刻我会突然想到一句诗: 天苍苍野茫茫风吹草低见牛羊, 有关联吗? 没关联, 想到就写了, 晚安 :)
来源: https://www.cnblogs.com/youclk/p/8453526.html