继续向 Kubernetes 进发,上一篇 Docker Swarm 集群模式实操 了解完 Swarm 后,有必要对 Docker Compose 了解一番。Docker Swarm 是把 Docker 宿主机组成集群,部署服务时只要告知 Manager 节点,它就会自动找到相应节点去运行相应的容器。Compose 完全是另一个概念,它把相关联的多个容器组织成一个整体来部署,如由负载容器,多个 Web 容器和一个 Redis 缓存容器构成一个整体。
由其名 Compose 正式有了容器编排这么一个概念,后来将要学的 Kubernetes 是更高级别的组织,运行,管理容器的工具。Compose 由于把容器组织起来了,所以能够一条命令启动多个相关联的容器,而无需单独启动一个一个的容器。
关于 Docker Compose 的安装,在 Mac OS X 下的 Docker Desktop 自带了 docker-compose; 由于 Docker Compose 是用 Python 编写的,我们可以用 pip install docker-compose
的安装,安装后使用它的命令就是 docker-compose
。
下方的 Docker Container, Swarm, Compose 三者之间的关系图很形像
Compose 由多个容器组成,它可以以一个整体部署到单个 Docker 宿主机或 Swarm 宿主机集群当中。
下面亲自来体验一下 Docker Compose 的功能,设计了一个服务含有如下容器
- 前端负载均衡服务器,由 HAProxy 来扮演,请求转发到以下两个 Web 容器
- 两个 Web 容器,用 Python Flask 来演示
- 一个 Redis 缓存容器,Web 将会访问该容器中的缓存数据
定义 Docker Compose 涉及到的文件有 Dockerfile 和 docker-compose.yml,需要为每一个自定义的 Docker 容器创建一个 Dockerfile,某些容器能直接使用 Docker 镜像仓库的就不用单独的 Dockerfile。也就是说一个 Compose 中可有一个或多个 Dockerfile, 如果是一个 Dockerfile 的情况时,目录结构可以是
myapp/
- Dockerfile
- docker-compose.yml
如果需要用到多个 Dockerfile 的情况时,Dockerfile 必须要放在不同的目录中,这时目录结构需要调整为如下(并加上后面将要用到的几个辅助文件)
myapp/
- proxy/
- Dockerfile
- haproxy.cfg
- web/
- Dockerfile
- app.py
- requirements.txt
- docker-compose.yml
接下来看每一个文件的内容
myapp/proxy/Dockerfile
1 2 |
FROM haproxy:2.1.3 COPY haproxy.cfg /usr/local/etc/haproxy/haproxy.cfg |
myapp/proxy/haproxy.cfg
1 2 3 4 5 6 7 |
frontend myweb bind *:80 default_backend realserver backend realserver balance roundrobin server web1 web_a:5000 check server web2 web_b:5000 check |
这是一个最简单的 haproxy.cfg 配置文件,只是把请求轮转的转发到 web_a 和 web_b, 将会在后面的 docker-compose.yml 文件中看到 web_a 和 web_b。
myapp/web/Dockerfile
1 2 3 4 |
FROM python:3.7-alpine ADD app.py requirements.txt ./ RUN pip install -r requirements.txt CMD ["python", "app.py"] |
myapp/web/requirements.txt
1 2 |
flask redis |
myapp/web/app.py
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import redis import socket from flask import Flask cache = redis.Redis('redis') app = Flask(__name__) def hit_count(): return cache.incr('hits') @app.route('/') def index(): count = hit_count() return 'Served by <b>{}</b>, count: <b>{}</b>\n'.format(socket.gethostname(), count) if __name__ == '__main__': app.run(host='0.0.0.0') |
用简单的 Flask 来演示一个 Web 应用,访问计数保存在 Redis 中,并显示当前处理请处的机器名。
myapp/docker-compose.yml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
version: '3.7' services: web_a: build: ./web ports: - 5000 web_b: build: ./web ports: - 5000 proxy: build: ./proxy ports: - "80:80" redis: image: "redis:alpine" |
自已构建两个 Docker 镜像,Redis 的镜像直接用 redis:alpine 的,在这里定义了端口及映射,所以在 Dockerfile 中不用 EXPOSE 来定义端口号。我们将启动两个 Web 服务,分别为 web_a, web_b, 在前方的 haproxy.cfg 转发请求就是到这两个容器中去的。
现在我们可以来到命令行下的 myapp 目录处,执行命令
$ docker-compose up -d
此时,如果本地没有相应的镜像文件将会构建 myapp_proxy, myapp_web_a, myapp_web_b 镜像,然后启动它们。如果本地已有那些镜像则直接启动那些容器。启动完成后,用 docker ps
查看
1 2 3 4 5 6 |
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e5d918e0ae2a myapp_proxy "/docker-entrypoint.…" About a minute ago Up About a minute 0.0.0.0:80->80/tcp myapp_proxy_1 56819534cfa6 myapp_web_a "python app.py" About a minute ago Up About a minute 0.0.0.0:32777->5000/tcp myapp_web_a_1 a2125855e55a myapp_web_b "python app.py" About a minute ago Up About a minute 0.0.0.0:32778->5000/tcp myapp_web_b_1 f216a7b5f95b redis:alpine "docker-entrypoint.s…" About a minute ago Up About a minute 6379/tcp myapp_redis_1 |
启动了四个容器,一个 haproxy, 两个 web, 一个 redis,正是我们所需要的。再来验证一下服务,是否能共同完成请求:
1 2 3 4 5 6 |
$ curl http://localhost Served by <b>a2125855e55a</b>, count: <b>1</b> $ curl http://localhost Served by <b>56819534cfa6</b>, count: <b>2</b> $ curl http://localhost Served by <b>a2125855e55a</b>, count: <b>3</b> |
负载均衡,Web 服务,Redis 正在协同无误的工作
如果此时用 docker kill 56
杀掉任何一个容器,该容器不会自动恢复起来,但再次运行 docker-compose run -d
后,只是刚刚被杀的容器又起来了,其他的容器没变化。
1 2 3 4 |
$ docker-compose up -d Starting myapp_web_a_1 ... myapp_redis_1 is up-to-date Starting myapp_web_a_1 ... done |
请用 docker-compose -h
查看更丰富的命令参数,不少与 docker
命令参数是一样的,只是针对 docker-compose
而已,例如下面常用的
- docker-compose images: 显示当前 compose 所用到的 docker 镜像
- docker-compose logs: 显示当前 compose 的运行日志
- docker-compose down: 停掉 compose 涉及到的所有容器
- docker-compose kill: 杀掉 compose 涉及的所有容器,而无须用 docker kill 逐个杀
- docker-compose restart: 重启整个 compose 服务(所有容器)
还有更简单的支持 HAProxy + 多个动态的 Web 容器的方式
修改前面的 docker-compose.yml
文件内容如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
version: '3.7' services: web: build: ./web ports: - 5000 proxy: image: dockercloud/haproxy links: - web ports: - "80:80" volumes: - /var/run/docker.sock:/var/run/docker.sock redis: image: "redis:alpine" |
proxy 选用了 dockercloud/haproxy
镜像,自己构建用的 myapp/proxy
目录及其中的 Dockerfile
和 haproxy.cfg
文件也不用了。
现在用同样的命令启动 docker-compose up -d
1 2 3 4 5 |
$ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES 3ce8fcd986ac dockercloud/haproxy "/sbin/tini -- docke…" 6 minutes ago Up 4 minutes 443/tcp, 0.0.0.0:80->80/tcp, 1936/tcp myapp_proxy_1 c73be3d3f795 myapp_web "python app.py" 6 minutes ago Up 4 minutes 0.0.0.0:32793->5000/tcp myapp_web_1 0fabf6c6e81f redis:alpine "docker-entrypoint.s…" 16 minutes ago Up 4 minutes 6379/tcp myapp_redis_1 |
只有一个 web 容器,现在还能动态扩容,用 docker-compose up --scale web=d -d
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ docker-compose up --scale web=3 -d Starting myapp_web_1 ... Starting myapp_web_1 ... done Creating myapp_web_2 ... done Creating myapp_web_3 ... done myapp_proxy_1 is up-to-date $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES e3b4db8db92d myapp_web "python app.py" 7 seconds ago Up 5 seconds 0.0.0.0:32796->5000/tcp myapp_web_2 f1ba850865e0 myapp_web "python app.py" 7 seconds ago Up 5 seconds 0.0.0.0:32795->5000/tcp myapp_web_3 3ce8fcd986ac dockercloud/haproxy "/sbin/tini -- docke…" 8 minutes ago Up 6 minutes 443/tcp, 0.0.0.0:80->80/tcp, 1936/tcp myapp_proxy_1 c73be3d3f795 myapp_web "python app.py" 8 minutes ago Up 6 minutes 0.0.0.0:32793->5000/tcp myapp_web_1 0fabf6c6e81f redis:alpine "docker-entrypoint.s…" 17 minutes ago Up 6 minutes 6379/tcp myapp_redis_1 |
来测试一下他们是否能正常工作
1 2 3 4 5 6 |
$ curl http://localhost Served by <b>c73be3d3f795</b>, count: <b>20</b> $ curl http://localhost Served by <b>e3b4db8db92d</b>, count: <b>21</b> $ curl http://localhost Served by <b>f1ba850865e0</b>, count: <b>22</b> |
三次请求分别被三个不同的 Web 容器处理。
注:
docker-compose scale web=2
命令不推荐使用,而应用docker-compose up --scale web=2 -d
docker-compose.yml
中的 deploy 配置只能工作于 Swarm 模式,而deploy
下的replicas
是工作于 docker stack deploy 命令的,在docker-compose up
或docker-compose run
中被忽略
关于在 Swarm 集群之上运行 Compose
看起来有点麻烦,见官方的 Use Compose with Swarm. 需要用到 docker stack 来部署 Compose。自己就不作实际操作了,也不知道产品中这种结合情景多不多,关键了解一下部署后容器是如何在 Swarm 节点中分配的,是把 Compose 当作一个整体呢?还是以 Compose 中的容器为粒度来部署。
从 Deploy Docker Compose (v3) to Swarm (mode) Cluster 中的一张图片来看
Swarm 还是能把 Compose 中定义的容器拆分出来,单独的部署到 Swarm 集群中的节点上,而不是把 Compose 当作一个整体来看待,表现的不错。
链接:
- How to use Docker Compose to run complex multi container apps on your Raspberry Pi
- Docker Compose 多容器部署 (五)
- Docker-Compose简介安装使用
本文链接 https://yanbin.blog/docker-compose-in-action/, 来自 隔叶黄莺 Yanbin Blog
[版权声明] 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。