Docker大家都很熟悉用Docker部署应用很方便但是当应用需要多个容器协同工作的时候比如Web应用需要Nginx容器PHP容器MySQL容器Redis容器等等手动一个个启动配置网络挂载很麻烦也容易出错这时候就需要Docker Compose它能用一个配置文件定义多个容器然后一键启动所有容器非常方便。

我用Docker Compose很久了部署了很多应用踩了一些坑也总结了一些经验今天就来分享一下Docker Compose的实战经验从入门到进阶全面分享。

一、Docker Compose是什么

先简单说说Docker Compose是什么。

Docker Compose是Docker官方的容器编排工具用来定义和运行多容器Docker应用它用一个YAML格式的配置文件docker-compose.yml来定义应用需要的所有服务(容器)网络卷等等然后用一个命令就能启动所有服务非常方便。

简单来说Docker Compose能把多个容器的启动命令配置都写在一个文件里然后一键启动不用手动一个个docker run很方便也不容易出错。

Docker Compose适合开发环境测试环境和简单的生产环境对于复杂的大规模的生产环境可能需要Kubernetes这样更强大的容器编排工具但是对于大部分中小应用Docker Compose完全够用了而且简单易用。

二、Docker Compose的安装

Docker Compose的安装很简单。

如果你安装的是Docker Desktop(Windows/Mac)那么Docker Compose已经自带了不用单独安装。

如果是Linux需要单独安装Docker Compose方法也很简单:

# 下载Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

# 添加执行权限
sudo chmod +x /usr/local/bin/docker-compose

# 验证安装
docker-compose --version

这样就安装好了很简单。

现在Docker Compose也有V2版本是Docker的插件命令是docker compose(没有横杠)和V1版本的docker-compose(有横杠)用法基本一样V2版本更新更推荐不过V1版本也还能用。

三、Docker Compose的基本使用

下面用一个简单的例子来说明Docker Compose的基本使用。

假设我们要部署一个简单的Web应用需要Nginx和MySQL两个容器。

首先创建一个项目目录比如myapp:

mkdir myapp
cd myapp

然后在目录里创建docker-compose.yml文件:

version: '3'

services:
  nginx:
    image: nginx:latest
    ports:
      - "80:80"
    volumes:
      - ./html:/usr/share/nginx/html
    depends_on:
      - mysql
    restart: always

  mysql:
    image: mysql:5.7
    environment:
      MYSQL_ROOT_PASSWORD: root
      MYSQL_DATABASE: myapp
      MYSQL_USER: myapp
      MYSQL_PASSWORD: myapp
    volumes:
      - mysql_data:/var/lib/mysql
    restart: always

volumes:
  mysql_data:

这个配置文件定义了两个服务nginx和mysql:

  • nginx服务: 用nginx:latest镜像映射80端口挂载本地的html目录到容器的网页根目录依赖mysql服务总是自动重启。
  • mysql服务: 用mysql:5.7镜像设置了环境变量root密码数据库用户名密码挂载了数据卷持久化MySQL数据总是自动重启。

然后创建html目录和一个index.html:

mkdir html
echo "<h1>Hello Docker Compose!</h1>" > html/index.html

然后一键启动所有服务:

docker-compose up -d

这里-d表示后台运行执行这个命令Docker Compose就会自动拉取镜像创建网络卷启动所有容器非常方便。

启动之后可以查看运行状态:

docker-compose ps

可以看到nginx和mysql两个容器都在运行了。

然后访问http://localhost就能看到"Hello Docker Compose!"说明部署成功了。

查看日志:

docker-compose logs
# 查看某个服务的日志
docker-compose logs nginx

停止服务:

docker-compose stop

启动服务:

docker-compose start

重启服务:

docker-compose restart

停止并删除容器网络:

docker-compose down

如果要同时删除数据卷:

docker-compose down -v

是不是很简单很方便?用一个配置文件一个命令就能部署多容器应用不用手动一个个docker run很省心。

四、docker-compose.yml配置详解

docker-compose.yml是Docker Compose的核心下面详细说说常用的配置项。

1. version

指定Docker Compose的版本目前常用的是'3'或者'3.8'等等版本不同支持的配置项可能不同一般用'3'就够了。

注意最新的Docker Compose V2已经不需要version字段了但是加上也没问题兼容。

2. services

services是最核心的配置定义应用的所有服务(容器)每个服务就是一个容器。

每个服务下面有很多配置项:

  • image: 指定容器用的镜像比如nginx:latestmysql:5.7等等。
  • build: 如果不用现成的镜像要自己构建镜像可以用build指定Dockerfile所在的目录比如build: ./php这样Docker Compose会自动构建镜像。
  • ports: 端口映射格式是"主机端口:容器端口"比如"80:80""3306:3306"等等可以映射多个端口。
  • expose: 暴露端口但是不映射到主机只有同一个网络的其他容器能访问比如expose: - "3306"这样MySQL只对内可见不对外暴露更安全。
  • volumes: 数据卷挂载格式是"主机路径:容器路径"或者"数据卷名:容器路径"比如./html:/usr/share/nginx/htmlmysql_data:/var/lib/mysql等等用来持久化数据或者共享文件。
  • environment: 环境变量用来设置容器里的环境变量比如MySQL的root密码数据库名等等可以用键值对的形式也可以用数组的形式。
  • envfile: 从文件读取环境变量比如envfile: - .env这样环境变量写在.env文件里不用写在docker-compose.yml里更方便也更安全。
  • depends_on: 服务依赖指定这个服务依赖哪些服务启动的时候会先启动依赖的服务再启动这个服务比如nginx依赖mysql就先启动mysql再启动nginx。

注意depends_on只是保证启动顺序不会等依赖的服务完全就绪比如MySQL启动了但是还没完全准备好这时候应用连接可能会失败所以需要在应用里做重试或者用healthcheck健康检查。

  • restart: 重启策略比如always总是自动重启unless-stopped除非手动停止否则自动重启on-failure只有失败的时候才重启等等生产环境建议设置always或者unless-stopped保证服务挂了能自动重启。
  • networks: 指定服务加入哪些网络同一个网络的服务可以互相用服务名访问比如nginx容器可以用mysql作为主机名连接MySQL很方便。
  • command: 覆盖容器默认的启动命令比如command: php-fpm等等。
  • entrypoint: 覆盖容器默认的入口点。
  • working_dir: 设置工作目录。
  • user: 设置运行用户。
  • healthcheck: 健康检查配置健康检查的命令间隔超时等等用来判断服务是否正常运行。
  • logging: 日志配置比如日志驱动大小数量等等避免日志太大占满磁盘。
  • deploy: 部署配置用于Docker Swarm模式一般Docker Compose单机用不到。

3. networks

networks定义应用的网络默认Docker Compose会创建一个默认的网络所有服务都加入这个网络也可以自定义网络比如:

networks:
  frontend:
  backend:

然后在服务里指定加入哪个网络比如nginx加入frontend和backendmysql只加入backend这样nginx能访问mysql但是外部不能直接访问mysql更安全。

4. volumes

volumes定义数据卷用来持久化数据比如:

volumes:
  mysql_data:
  redis_data:

然后在服务里挂载这些数据卷比如mysql_data:/var/lib/mysql这样MySQL的数据就持久化了容器删除重建数据也不会丢。

也可以用主机路径挂载比如./mysql:/var/lib/mysql这样数据存在本地目录里也能持久化。

五、实战案例:部署LNMP环境

下面用一个更复杂的实战案例来说明Docker Compose的使用部署一个LNMP环境Linux + Nginx + MySQL + PHP还有Redis。

项目目录结构:

myblog/
├── docker-compose.yml
├── .env
├── nginx/
│   ├── conf.d/
│   │   └── default.conf
│   └── nginx.conf
├── php/
│   └── Dockerfile
├── mysql/
│   └── my.cnf
├── redis/
│   └── redis.conf
└── www/
    └── index.php

docker-compose.yml:

version: '3'

services:
  nginx:
    image: nginx:1.21
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./www:/var/www/html
      - ./nginx/conf.d:/etc/nginx/conf.d
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
      - nginx_logs:/var/log/nginx
    depends_on:
      - php
      - mysql
      - redis
    networks:
      - frontend
      - backend
    restart: unless-stopped
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  php:
    build: ./php
    volumes:
      - ./www:/var/www/html
      - ./php/php.ini:/usr/local/etc/php/php.ini
    networks:
      - backend
    restart: unless-stopped
    depends_on:
      - mysql
      - redis
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  mysql:
    image: mysql:5.7
    ports:
      - "127.0.0.1:3306:3306"
    environment:
      MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
      MYSQL_DATABASE: ${MYSQL_DATABASE}
      MYSQL_USER: ${MYSQL_USER}
      MYSQL_PASSWORD: ${MYSQL_PASSWORD}
    volumes:
      - mysql_data:/var/lib/mysql
      - ./mysql/my.cnf:/etc/mysql/my.cnf
    networks:
      - backend
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${MYSQL_ROOT_PASSWORD}"]
      interval: 10s
      timeout: 5s
      retries: 5
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

  redis:
    image: redis:6
    ports:
      - "127.0.0.1:6379:6379"
    command: redis-server /etc/redis/redis.conf
    volumes:
      - redis_data:/data
      - ./redis/redis.conf:/etc/redis/redis.conf
    networks:
      - backend
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"

networks:
  frontend:
  backend:

volumes:
  mysql_data:
  redis_data:
  nginx_logs:

.env文件:

MYSQL_ROOT_PASSWORD=your_root_password
MYSQL_DATABASE=myblog
MYSQL_USER=myblog
MYSQL_PASSWORD=your_password

这个配置定义了4个服务nginxphpmysqlredis:

  • nginx: 映射80和443端口挂载网站代码Nginx配置日志加入frontend和backend网络依赖phpmysqlredis。
  • php: 用自己构建的镜像(Dockerfile里安装PHP和扩展比如pdo_mysqlredisgd等等)挂载网站代码php.ini只加入backend网络不对外暴露端口。
  • mysql: 映射3306端口但是只绑定127.0.0.1只有本机能访问更安全设置了环境变量(从.env读取)挂载数据卷配置文件有健康检查。
  • redis: 映射6379端口也是只绑定127.0.0.1挂载数据卷配置文件有健康检查。

网络分了frontend和backendnginx在两个网络mysql和redis只在backend这样外部不能直接访问mysql和redis只能通过nginx或者本机访问更安全。

日志配置了大小限制每个日志最大10M保留3个避免日志太大占满磁盘。

然后php/Dockerfile大概这样:

FROM php:7.4-fpm

# 安装依赖
RUN apt-get update && apt-get install -y \
    libfreetype6-dev \
    libjpeg62-turbo-dev \
    libpng-dev \
    libzip-dev \
    && docker-php-ext-configure gd --with-freetype --with-jpeg \
    && docker-php-ext-install -j$(nproc) gd pdo_mysql zip opcache

# 安装redis扩展
RUN pecl install redis && docker-php-ext-enable redis

# 设置工作目录
WORKDIR /var/www/html

# 暴露端口
EXPOSE 9000

# 启动php-fpm
CMD ["php-fpm"]

然后www/index.php写一个phpinfo或者连接MySQLRedis的测试代码。

然后一键启动:

docker-compose up -d --build

这里--build表示启动前先构建镜像(php服务需要构建)。

启动之后访问http://localhost就能看到PHP的页面了非常方便一个命令就部署了完整的LNMP + Redis环境。

六、常用命令和技巧

下面分享一些Docker Compose的常用命令和技巧。

1. 常用命令

# 启动所有服务
docker-compose up -d

# 启动并构建镜像
docker-compose up -d --build

# 查看服务状态
docker-compose ps

# 查看日志
docker-compose logs
docker-compose logs -f  # 实时查看
docker-compose logs nginx  # 查看某个服务的日志

# 进入某个容器
docker-compose exec nginx bash
docker-compose exec mysql mysql -u root -p

# 重启服务
docker-compose restart
docker-compose restart nginx  # 重启某个服务

# 停止服务
docker-compose stop
docker-compose stop nginx  # 停止某个服务

# 启动已停止的服务
docker-compose start
docker-compose start nginx

# 停止并删除容器、网络
docker-compose down

# 停止并删除容器、网络、数据卷
docker-compose down -v

# 拉取最新镜像
docker-compose pull

# 重新构建镜像
docker-compose build
docker-compose build --no-cache  # 不使用缓存重新构建

# 查看配置
docker-compose config

# 查看镜像
docker-compose images

2. 环境变量和.env文件

Docker Compose支持用环境变量替换配置里的变量比如${MYSQLROOTPASSWORD}可以把环境变量写在.env文件里Docker Compose会自动读取这样敏感信息比如密码不用写在docker-compose.yml里更安全也更方便不同环境用不同的.env文件。

3. 多个配置文件

可以用多个配置文件比如基础配置docker-compose.yml和生产环境覆盖配置docker-compose.prod.yml然后启动的时候指定:

docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d

这样不同环境用不同的配置很方便。

4. 健康检查

前面提到了dependson只是保证启动顺序不会等服务完全就绪所以可以用healthcheck健康检查配置服务的健康检查然后dependson可以指定condition: service_healthy这样会等依赖的服务健康了再启动当前服务更可靠。

不过这个需要Docker Compose版本支持配置稍微复杂一点。

5. 日志限制

容器的日志默认会越来越大占满磁盘所以要配置日志大小限制比如前面例子里的:

logging:
  driver: "json-file"
  options:
    max-size: "10m"
    max-file: "3"

这样每个日志最大10M保留3个不会无限增长。

6. 不要把数据存在容器里

重要的数据比如MySQL数据Redis数据上传的文件等等一定要用数据卷或者主机路径挂载持久化不要存在容器里不然容器删除重建数据就丢了这是很多新手容易犯的错误。

7. 端口不要随便暴露

不需要对外暴露的端口不要映射到主机比如MySQLRedis如果只有容器内需要访问就不要映射端口或者只绑定127.0.0.1只有本机能访问减少攻击面更安全。

七、踩坑经验

最后分享一些我踩过的坑和经验。

1. 容器时区不对

很多Docker镜像默认时区是UTC和国内差8小时会导致日志时间不对应用时间不对所以要设置时区可以挂载主机的/etc/localtime到容器或者设置环境变量TZ=Asia/Shanghai。

volumes:
  - /etc/localtime:/etc/localtime:ro
environment:
  - TZ=Asia/Shanghai

2. 文件权限问题

挂载主机目录到容器可能会有文件权限问题比如容器里的用户和主机的用户UID不一样导致没有权限读写文件这时候要注意用户权限或者在Dockerfile里指定用户UID和主机一致或者修改主机目录的权限。

3. MySQL数据损坏

MySQL容器强制删除或者异常关闭可能会导致数据损坏所以要正确停止容器用docker-compose stop不要直接kill而且要定期备份MySQL数据不要只靠数据卷。

4. 镜像版本不要用latest

不要用latest标签的镜像因为latest会变化可能今天是一个版本明天就变成另一个版本可能不兼容导致问题要用具体的版本号比如nginx:1.21mysql:5.7php:7.4-fpm等等这样版本固定稳定不会意外变化。

5. 构建镜像太慢

构建Docker镜像有时候很慢特别是国内下载依赖慢可以用国内的镜像源比如apt用阿里云的源pecl用国内的源等等加快构建速度也可以用构建缓存不要随便--no-cache。

6. 端口冲突

启动容器的时候可能会遇到端口冲突比如80端口已经被占用了这时候要检查哪个程序占用了端口停掉或者换一个端口映射。

7. 容器启动失败

容器启动失败要看日志docker-compose logs看错误信息一般都是配置不对或者依赖没就绪或者权限问题等等根据错误信息排查。

八、写在最后

Docker Compose实战:一键部署多容器应用。

Docker Compose是一个非常好用的容器编排工具能用一个配置文件一个命令部署多容器应用非常方便大大简化了部署流程提高了效率对于开发环境测试环境和中小规模的生产环境都非常适用。

当然Docker Compose也有局限性比如不适合大规模的集群复杂的编排这时候需要Kubernetes这样更强大的工具但是对于大部分中小应用Docker Compose完全够用了而且简单易用学习成本低。

希望这篇分享能帮大家更好地使用Docker Compose简化部署提高效率。

最后用一句话结尾:

"Docker Compose让多容器部署变得简单高效一个文件一个命令搞定一切是开发者的好帮手。"

祝大家都能用好Docker Compose部署顺利!