由浅入深 docker 系列: (3) docker-compose

在第二篇文章中,我们学会了使用 dockerfile 构建 docker 镜像,看起来已经能够满足我们的日常需求了。无论需要什么环境,在 dockerfile 里逐步构建,然后 build、run,就 ok 了,也满足了我们docker 隔离性、快速部署的要求,那为什么还会有这一节?
来看一个网站开发最常见的场景:我们要有数据库,网站应用,nginx,互相配合才是完整的环境。是的,我们完全可以以 ubuntu 为基础镜像,把这些一股脑全装进去,然后运行。但是这样有很多缺点,比如我们每次都要重新装 mysql 而不是直接利用 mysql 官方的基础镜像,升级维护不方便;如果我们的应用要扩展也很难,因为每个应用都连接的自己内部的数据库,无法共享数据;事实上,这种方式是典型的虚拟机的使用方式,不是 docker 的正确打开方式。
docker 是轻量化的应用程序,docker 官方推荐每个 docker 容器中只运行一个进程(下篇文章你将明白这是为什么),那么就是说,我们需要分别为我们的应用、数据库、nginx 创建单独的 docker 容器,然后分别启动它。想象一下,构建好 docker 之后,每次启动我们的网站,都要至少 docker run 三次,是不是很繁琐?而且此时这几个 docker 是分散独立的,很不方便管理。既然这几个 docker 都是为了同一个网站服务,是不是应该把它们放到一起?这就引出了 docker-compose 项目。
docker-compose是 docker 官方的开源项目,使用 python 编写,实现上调用了 Docker 服务的 API 进行容器管理。其官方定义为为 「定义和运行多个 Docker 容器的应用(Defining and running multi-container Docker applications)),其实就是上面所讲的功能。


一:安装
默认情况下,windows 和 mac 下的 docker 已经自带了 docker-compose 工具,可以使用 docker-compose -v 命令查看。

对于 linux 系统,需要自己手动安装,可以直接下载二进制文件安装,参考官方文档,更酷的是,可以直接使用 pip 安装,甚至可以使用 docker-compose 容器(比较复杂,不推荐~)。

二:简介
类似 docker 的Dockerfile文件,docker-compose使用 YAML 文件对容器进行管理。
对于 docker-compose 有两个基本的概念:
服务(service):一个应用容器,即 docker 容器,比如之前所说的mysql 容器、nginx 容器
项目(project):由一组关联的应用容器组成的一个完整业务单元,比如上面所讲的由 mysql、web app、nginx 容器组成的网站。docker-compose 面向项目进行管理。
再简单说下 YAML 文件格式。

  • 大小写敏感,缩进表示表示层级关系
  • 缩进空格数不重要,相同层级左侧对齐即可。(不允许使用 tab 缩进!)
  • 由冒号分隔的键值对表示对象;一组连词线开头的行,构成一个数组;字符串默认不使用引号
    这些基本够我们使用了,详细的格式说明可参考这篇YAML 语言教程-阮一峰。

三:构建
接下来我们使用 docker-compose 构建一个php 网站项目,并逐步讲解其使用。
我们需要一个网站项目,这里以 summerblue 的larabbs论坛系统为例,这个项目使用了 mysql、redis,我们可以验证搭建是否成功。

  1. 从远程仓库把其克隆到本地,然后开始我们今天的工作。

  2. 在项目文件夹下创建 docker-compose.yml文件。

  3. 先在 docker-compose.yml 文件里添加如下代码,构建我们的 php 应用。

    version: '2'
    services:
    
        # our web application
        app:
            build:
                context: ./
                dockerfile: app.dockerfile
            volumes:
                - ./:/var/www
            working_dir: /var/www
            environment:
                - DB_HOST=database
                - REDIS_HOST=redis

    下面解释下我们的代码。

    • version: 表示我们的compose文件的版本,目前有1,2,3,每个版本语法不尽相同,这里以版本2为例。
    • services: 即我们要开始定义服务,每个docker容器为一个服务。
    • app: 这里我们定义了第一个服务,app 为其名字
    • build: 指定该容器构建参数
    • volumes: 与 dockerfile 中 -v 参数相似,这里是将当前文件夹挂载到容器的/var/www 目录下
    • working_dir: 指定容器工作目录
    • environment: 设置环境变量。由于 laravel 框架在环境变量已有值的情况下不会加载.env 配置,这里 DB_HOST和 REDIS_HOST 就是.env 文件中配置数据库连接的参数,我们设置它以便连接docker 的数据库,database 和 redis 是接下来定义的服务名称。

    这是 app.dockerfile 文件的内容,之前都讲过,不再细说。

    FROM php:7.1.22-fpm
    
    # 安装必要的 php 依赖包
    RUN apt-get update \
        && apt-get install -qq git curl libmcrypt-dev libjpeg-dev libpng-dev libfreetype6-dev libbz2-dev \
        && apt-get clean
    
    # 安装 php 扩展
    RUN docker-php-ext-install pdo pdo_mysql mcrypt zip gd
  4. 创建一个 composer 服务以安装 composer 依赖,docker-compose.yml 文件添加以下内容

    # install dependencies
        composer:
            image: prooph/composer:7.1
            volumes:
                - ./:/var/www
            working_dir: /var/www
            command: install

注意这里同样将当前文件夹挂载到了容器中,因此对文件所做的更改都直接作用于本地文件,而我们的 app 容器也挂载了当前文件夹,这里安装的包文件都能生效。

  1. 创建一个 nginx 服务,docker-compose.yml 文件添加以下内容
    # web server
        nginx:
            build:
                context: ./
                dockerfile: nginx.dockerfile
            volumes:
                - ./public:/var/www/public
            ports:
                - 80:80
* 这里将 public 文件夹挂载到了容器中, nginx 直接返回了静态文件,否则你将看到网站格式乱了,因为获取不到 css 等文件,如果是单纯后端可以不挂载此文件夹
* ports 将nginx容器的80端口映射到本机80端口

nginx.dockerfile 文件内容,为了添加默认配置文件
    FROM nginx:1.10
    ADD vhost.conf /etc/nginx/conf.d/default.conf
vhost.conf 文件,nginx 配置文件
    server {
        listen 80;
        server_name www.larabbs.test
        index index.php index.html;
        root /var/www/public;

        error_log /var/log/nginx/error.log notice;
        access_log /var/log/nginx/access.log main;

        location / {
            try_files $uri /index.php?$args;
        }

        location ~ \.php$ {
            fastcgi_split_path_info ^(.+\.php)(/.+)$;
            fastcgi_pass app:9000;
            fastcgi_index index.php;
            include fastcgi_params;
            fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
            fastcgi_param PATH_INFO $fastcgi_path_info;
        }
    }
* 这里配置了 server_name,因为 larabbs 使用了sudo-su包,不配置域名访问会有权限问题。
* fastcgi_pass app:9000 这里将动态请求转发给了 app 容器的9000端口,即 php-fpm 服务的端口,默认 docker-compose 同一项目下的容器是在同一个网络中,无需映射可以直接互相访问。
  1. 创建数据库服务,docker-compose.yml 文件添加以下内容

    # mysql database
        database:
            image: mysql:5.7
            environment:
              - "MYSQL_ROOT_PASSWORD=root"
              - "MYSQL_DATABASE=larabbs"
              - "MYSQL_USER=homestead"
              - "MYSQL_PASSWORD=secret"
    
        # redis database
        redis:
            image: redis:5
    • 这里 database 和 redis 是服务名,也即在 app 服务的 environment 变量设置的参数,因此 app 才能访问到我们容器中的数据库,当然你也可以使用其它名字。
    • environment 这里我们设置了 mysql 的数据库及用户名密码,即是 env 文件中配置的用户名密码,mysql 容器会自动初始化,然后应用才有权限连接。

    到目前为止配置文件已经写完了,完整的文件可以在我的 github 看到。

四:使用

  1. 启动项目
    docker-compose 最常用的命令就是 docker-compose up 了,该命令十分强大,它将尝试自动完成包括构建镜像,(重新)创建服务,启动服务,并关联服务相关容器的一系列操作。
    因此,在项目文件夹下执行此命令。注意要在项目文件夹下,否则 docker-compose 找不到 docker-compose.yml 文件,也不知道你是如何配置的。
    时间可能较长,请耐心等待。如果遇到网络故障,可以重试。
    简单介绍下输出:


可以看到 此时app 服务已启动,等待连接。


redis 服务已启动。


database 服务已启动,在3306端口等待连接。


composer 服务准备开始安装依赖。


composer 服务完成任务退出。

当然顺序可能不同,但正常情况下nginx、mysql、redis、app 服务都已经启动,运行正常。
另外,你可以给命令加上-d 参数,以忽略输出,当然第一次运行还是仔细观察输出为好,将来使用时可以这么做。
  1. 项目使用
    首先执行下 docker ps,可以看到目前有4个容器,容器名都加上了项目前缀。

    此时你可以使用 docker exec 命令进入相应容器执行初始化操作,因为 docker-compose 本身也是调用的 docker api,但是既然使用了 docker-compose 来管理项目,我们必然有更方便的方式。

    执行 docker-compose ps,可以至查看当前项目的容器状态。

    可以看到,四个服务正在运行,而 composer 服务已经执行完任务退出。
    如果要容器执行命令,直接 docker-compose exec service_name command 更方便。
    比如,进入我们的 nginx 容器,nginx 即是 YAML 文件里定义的服务名。

    接下来,我们进行网站的初始化工作。即生成秘钥、初始化数据库等。
    app 也是 YAML 文件里定义的服务名。

    一切都顺利进行。
    对了,因为上面提到 sudo-su的原因,你必须使用域名访问网站,在 host 文件添加 larabbs.test到127.0.0.1的映射即可。
    然后,你应该就可以正常访问 http://larabbs.test 网站了。

  2. 容器的停止
    直接 control-c 或者 docker-compose stop 即可。
    注意下次docker-compose up 默认仍会继续使用之前的容器和数据。
    必要时你可以添加--build 参数重新构建镜像,或者--force-recreate参数重新创建容器。


更多资料:
docker 三剑客-compose
docker-compose 官方文档


闲言
这个系列写了三篇了,算是把 docker 的基本使用介绍完了,更准确的说,应该是把我会用的介绍完了。因为本身是开发工程师,更着重于日常开发过程中使用,经验不多,对于运维视角的 docker不甚了解,而 docker 更大的意义应该是在运维层,所以有介绍的不合适甚至错误的地方,欢迎指出,或者分享你对 docker 的理解与使用。
另外,接下来会介绍 docker 与虚拟机的区别,docker 的原理等,写起来会比较难,但我会尽快完成。相信看了之后,会对 docker 的实质有更深的理解,就算是吹牛逼也会更有自信,欢迎关注~

原文见我的知乎专栏

本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 3年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《L02 从零构建论坛系统》
以构建论坛项目 LaraBBS 为线索,展开对 Laravel 框架的全面学习。应用程序架构思路贴近 Laravel 框架的设计哲学。
讨论数量: 7

图片又挂了些 :joy_cat::joy_cat::joy_cat:

3年前 评论

如果修改了yml里面的配置 是不是要重新composer up?

3年前 评论

图片挂了,有图片就完美了

3年前 评论
lixiang9194 (楼主) 3年前

windows中怎么整啊? 我把docker-composer复制到我得项目下,但是一运行就报错:ERROR: SSL error: [SSL: TLSV1_ALERT_PROTOCOL_VERSION] tlsv1 alert protocol version (_ssl.c:661)

3年前 评论
matteao 2年前

感谢大佬,
完整的文件可以在我的 github 看到。这个github链接应该贴的有点问题。
有需要的直接拿走:https://github.com/lixiang9194/laravel-doc...

2年前 评论

file 为什么我composer安装失败啊

2年前 评论

为什么我的php-fpm好像没有启动

1年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!