不正宗 Docker 入门教程-使用 Docker-Compose (3/3)

本章通过一个具体的demo来了解 docker-compose

docker-compose 是做什么的

在构建一个完整的服务时,我们通常启动一个容器, 一旦出现多个容器需要同时启动的时候手打是下下之策, 因为时间一长难免会忘记细节,写脚本也不是不可以,但是大家没有达成共识时脚本也很难维护…

docker-compose 就是来解决这个痛点, 只需要按照统一的格式书写,那么大家生成的容器也都是一致的, 在团队开发的时候扔一个配置好的 docker-compose 能节省很多时间和口水

配置 docker-compose

这是我构建的一个开发环境的容器:dnmp

首先下载下来

1
git clone https://github.com/gaopengfei123123/dnmp.git && cd dnmp

我们第一件事就是先瞄一眼 .env 文件, 这里设置了很多常量,一会根据个人需求来调整

第二步才是打开 docker-compose.yml 文件, 看后缀都能猜到这是一个配置文件, 另外 docker-compose.yml 是根据缩进来进行分层的,注意书写格式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# docker-compose.yml
# 语法版本( 3 和 2 区别有点大, 比如 3 取消了 volume_from 的相关语法)
version: "3"

networks:
frontend:
driver: ${NETWORKS_DRIVER}
backend:
driver: ${NETWORKS_DRIVER}


volumes:
mysql_volume:
driver: ${VOLUMES_DRIVER}
redis_volume:
driver: ${VOLUMES_DRIVER}
rabbitmq_volume:
driver: ${VOLUMES_DRIVER}
# 服务编排
services:
# workspace:
# image: tianon/true
# container_name: dnmp-www
# volumes:
# - ./www:/usr/share/nginx/html

# NGINX #############################################
nginx:
container_name: dnmp-nginx
build:
context: ./nginx
args:
- PHP_UPSTREAM_CONTAINER=${NGINX_PHP_UPSTREAM_CONTAINER}
- PHP_UPSTREAM_PORT=${NGINX_PHP_UPSTREAM_PORT}
depends_on:
- php-fpm
ports:
- "${NGINX_HOST_HTTP_PORT}:80"
- "${NGINX_HOST_HTTPS_PORT}:443"
volumes:
# 没必要把配置文件用卷来挂载, 不然就算配置更新了 nginx 也是要重启的

# 挂载运行代码目录
- ${APP_CODE_PATH_HOST}:/var/www
# 挂载日志目录
- ${NGINX_HOST_LOG_PATH}:/var/log/nginx
# 使用 networks 取代 links 在同一个网络模式下的服务是互通的
# 在service 中使用其他的 service 就直接调用 service 名就行, 不用管 ip 地址, docker 会自己维护一套
networks:
- frontend
- backend


# PHP-FPM #############################################
php-fpm:
container_name: dnmp-php-fpm
# 这里的args 是属于 build 下面的,用于构建./php-fpm/Dockerfile 文件中 ARG 参数指定 php 版本
build:
context: ./php-fpm
args:
- PHP_VERSION=${PHP_VERSION}
volumes:
- ${APP_CODE_PATH_HOST}:/var/www
- ./php-fpm/php${PHP_VERSION}.ini:/usr/local/etc/php/php.ini
expose:
- "9000"
networks:
- backend

redis:
container_name: dnmp-redis
build:
context: ./redis
args:
- REDIS_SET_PASSWORD=${REDIS_SET_PASSWORD}
ports:
- ${REDIS_HOST_PORT}:6379
volumes:
# 这里卷挂载的是本地文件
# - ${DATA_PATH_HOST}/redis:/data
# 这里创建一个 redis_volume来存放数据
- redis_volume:/data

# Mysql #############################################
mysql:
container_name: dnmp-mysql
# 镜像来源: https://github.com/docker-library/mysql/blob/fc3e856313423dc2d6a8d74cfd6b678582090fc7/5.7/Dockerfile
image: mysql:${MYSQL_VERSION}
volumes:
# - ${DATA_PATH_HOST}/mysql:/var/lib/mysql
- mysql_volume:/var/lib/mysql
# 容器只要停止就会重启
restart: always
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_USER: ${MYSQL_USER}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
ports:
- ${MYSQL_HOST_PORT}:3306

接下来看看它的关键词都起着什么作用:

version

这个规定了文件的版本, 既然有 3 就肯定不用 2 啊, 虽然两者没冲突,但是我喜欢, 2 和 3 版本之间有轻微的变动,具体区别你可以在写配置文件时产生的报错信息来体验一下

network
1
2
3
4
5
networks:
frontend:
driver: ${NETWORKS_DRIVER}
backend:
driver: ${NETWORKS_DRIVER}

${NETWORKS_DRIVER} 是从 .env 文件中取的值, 下面的同理

这一块就相当于执行 docker network create -d bridge frontend && docker network create -d bridge backend
在本地持久化的建立一个网络配置,稍后方便容器进行连接, 当然这里也不止是一个 driver 参数,具体配置情况还是参考docker network inspect dnmp_frontend 来看一下

没有设置名字的配置当需要名字的时候会 {当前docker-compose.yml文件名}_{key} 这种格式

有了 network 配置就极大的简化了老版的 --links 命令, 只要属于同一个 network 就能互相访问到, 而不是每新增一个服务就要把原来的服务都 link 一遍

volume
1
2
3
4
5
6
7
volumes:
mysql_volume:
driver: ${VOLUMES_DRIVER}
redis_volume:
driver: ${VOLUMES_DRIVER}
rabbitmq_volume:
driver: ${VOLUMES_DRIVER}

network 部分一样, 持久化的创建几个 volume, 相当于命令 docker network create mysql_volume等等

这算是 v3 的一个新特性, 在 v2 的时候, 为了共享数据大家会创建一个什么镜像都不继承的image, 所有容器的 volume 都会和它连接, 现在有了 volume 就没必要这么搞了

service

这个是本章的重点, 我们来看下面的例子中的注释, 按序号来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
services:

#1 创建一个服务叫做nginx服务
nginx:
#2 为了显得个性化一点,我们指定这个容器的名字叫做 dnmp-nginx
container_name: dnmp-nginx
#3 标明这个服务的 Dockerfile 的地址,用相对路径方便项目迁移
build:
#3.1 相当于命令:
# docker build ./nginx -t dnmp-nginx \
# --build-arg PHP_UPSTREAM_CONTAINER=xxx \
# --build-arg PHP_UPSTREAM_PORT=zzz
context: ./nginx
#3.2 这里 ${NGINX_PHP_UPSTREAM_PORT} 的值是从 .env 文件中取的, args 属于构建时传入的参数
args:
- PHP_UPSTREAM_CONTAINER=${NGINX_PHP_UPSTREAM_CONTAINER}
- PHP_UPSTREAM_PORT=${NGINX_PHP_UPSTREAM_PORT}
#4 在启动这个容器之前先启动 php-fpm 这个容器
depends_on:
- php-fpm
#5 将本地端口和容器端口绑定, 本地哪个端口就看 .env 里怎么写的
ports:
- "${NGINX_HOST_HTTP_PORT}:80"
- "${NGINX_HOST_HTTPS_PORT}:443"

#6 设置需要挂载的卷, 这里时将本地目录和容器绑定, 也可以像 services.redis 那样和创建好的卷绑定
volumes:
# 没必要把配置文件用卷来挂载, 不然就算配置更新了 nginx 也是要重启的

# 挂载运行代码目录
- ${APP_CODE_PATH_HOST}:/var/www
# 挂载日志目录
- ${NGINX_HOST_LOG_PATH}:/var/log/nginx
# 使用 networks 取代 links 在同一个网络模式下的服务是互通的
# 在service 中使用其他的 service 就直接调用 service 名就行, 不用管 ip 地址, docker 会自己维护一套

#7 设置容器从属的网络, 同一个网络下可互相访问
networks:
- frontend
- backend

在上文的 #3 步骤看其他的service也有直接使用image的, 这是直接从远程获取镜像的方式

配置文件写完了, 我们看下nginx的构建文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
# in file ./nginx/Dockerfile

#1 选择继承的镜像
FROM nginx:1.13.1-alpine
#2 各种标签
LABEL maintainer="GPF <5173180@qq.com>"

#3 容器中执行命令, 且把本地的配置文件添加进去
#https://yeasy.gitbooks.io/docker_practice/content/image/build.html
RUN mkdir -p /etc/nginx/cert \
&& mkdir -p /etc/nginx/conf.d \
&& mkdir -p /etc/nginx/sites

COPY ./nginx.conf /etc/ngixn/nginx.conf
COPY ./conf.d/ /etc/nginx/conf.d/
COPY ./cert/ /etc/nginx/cert/

COPY ./sites /etc/nginx/sites/


#4 这里也是设置构建参数, 不过相同 key 值会被 docker-compose 中的给覆盖掉
ARG PHP_UPSTREAM_CONTAINER=php-fpm
ARG PHP_UPSTREAM_PORT=9000
#5 ${PHP_UPSTREAM_CONTAINER} 就在构建时的参数使用方式
RUN echo "upstream php-upstream { server ${PHP_UPSTREAM_CONTAINER}:${PHP_UPSTREAM_PORT}; }" > /etc/nginx/conf.d/upstream.conf

#6 设置挂载的目录, 该目录下文件变化不会影响到容器
VOLUME ["/var/log/nginx", "/var/www"]

#7 设置目录运行时所处在容器中的目录地址
WORKDIR /usr/share/nginx/html

#5 就是显示了在 nginx 容器中怎么去访问 php-fpm 这个容器, 直接调用 service 名称就行

这里需要注意的时 ARGENV 的区别, 参考这篇文章: Docker中 Arg 和 Env 的区别

启动docker-compse

在配置好 .env 文件和 docker-compose.yml 配置文件后就可以启动它了, 命令也很简单,在同级目录下运行:

1
docker-compose up -d

它会自动创建volumenetworkservices, 而且相关的运行参数都是按着配置文件来的, 这样一来每个完整docker-compose.yml中的service就相当于时一个整体,每个服务又属于各自的容器,这样操控是不是节省了很多代码呢?

查看这些容器的运行状况也很是简单

1
2
docker-compose ps
# 或者使用更方便的一个工具: ctop , github地址: https://github.com/bcicen/ctop

可操控单一容器一样, 但是它会把这一组容器都囊括了进去,操控起来只需要知道操控哪个服务,而一些参数就写在配置文件当中已经默认添加了

一些常用的命令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 终止整个服务集合
docker-compose stop

# 终止指定的服务 (这有个点就是启动的时候会先启动 depond_on 中的容器,关闭的时候不会影响到 depond_on 中的)
docker-compose stop nginx

# 查看容器的输出日志
docker-compose logs -f [services...]

# 构建镜像时不使用缓存(能避免很多因为缓存造成的问题)
docker-compose build --no-cache --force-rm

# 移除指定的容器
docker-compose rm nginx

本来熟悉命令的最好方式就是 用->犯错->排错->用 这种循环, 有什么不懂的 谷歌bing 都能查到, 直接 docker-compose --help 也能猜出命令的大概作用, 这里就不细说了

还有个很不错的 docker-compose 项目就是 laradock, dnmp 就是仿照着它写的, 不过网络不好的情况下别运行 laradock, 它现在做的太臃肿了。。。。 看看它里面的镜像是怎么写的还是很有收获的

相关

  1. 不正宗 Docker 入门教程-启动一个容器(1/3)
  2. 不正宗 Docker 入门教程-构建一个镜像(2/3)
  3. 不正宗 Docker 入门教程-使用 Docker-Compose (3/3)