Docker 重要性不用多做解释,我们天天吵着云原生,最重要的一门技术,你是否掌握了呢?从今天,一起来Docker吧!

1. 目标

1、利用 docker-compose 编排一个PHP开发环境;
2、PHP 常见扩展安装;
3、Nginx php-fpm Mysql Redis等容器互联;
4、PHP 程序能够操作 Mysql Redis
2、搜集相关日志便于排查;


2. 工具

1、操作系统:Ubuntu
2、编辑器:VSCode
3、Docker
4、Docker Compose 官方文档
5、Compose 中文手册
6、如果Docker镜像拉取慢,可以尝试这个 https://www.daocloud.io/mirror


3. 步骤

3.1 检查软件

确保 Docker 和 Docker Compose 已安装好
比如

docker -v Docker version 19.03.1, build 74b1e89

docker-compose -v

docker-compose version 1.17.1, build 6d101fb

3.2 创建 docker-compose.yml 文件

在你的文件夹中新增这个文件,这是我们编排的模板文件,格式如下

  1. version: '3'
  2. services:
  3. php-workspace:
  4. image: php:7.3-fpm-alpine
  5. nginx:
  6. image: nginx:alpine

注意点:
1、模板中定义的 version 字段声明了模板的格式和支持声明字段,3 是目前比较推荐也用的比较多的的版本
2、注意 key 的层级和空格,我们可以使用命令 docker-compose config 命令检查格式是否正确
3、使用 VSCode方便的一点是可以看到可以代码可以根据层级折叠
image.png
4、alpine是一种小型轻量的Linux,我们使用基于 alpine 的镜像是因为做出的镜像体积比较小,关于后期容器构建的优化,这个是另外一个话题,这里不展开阐述。

3.3 “让子弹飞起来”

docker-compose up --build 构建镜像并启动容器

这时能看到有两个已经在运行的容器和镜像
image.png

3.4 让 Nginx 工作起来

刚刚启动的容器可以使用 docker-compose down 来关闭项目,还会自动删除容器。
现在,我们要指定端口让 Nginx 能处理请求,可以参考 Compose 中文手册的模板文件的语法。
image.png

3.4.1 增加 ports 参数

指定宿主机端口和容器端口的映射关系,如下;

version: '3'
services:
  php-workspace:
    image: php:7.3-fpm-alpine
  nginx:
    image: nginx:alpine
    ports:
      - "8080:8080"

3.4.2 执行 docker-compose up

去浏览器请求 localhost:8080
image.png

3.5 PHP文件交给 PHP-FPM 处理

image.png

其实选中容器右键能管理容器,Attach Shell 就能快速进入容器,我们进入容器就能看到内部情况,比如,nginx 的www目录和配置目录,php-fpm 的默认配置等等。

3.5.1 增加 nginx 的站点配置

新增文件 nginx/conf.d/site.conf文件,内容如下,下面的思路也可以参考上篇《利用Docker搭建PHP开发环境》做法。
image.png

server {
    listen 8080;
    root /usr/share/nginx/html;
    index index.php index.html;
    server_name localhost;
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    location ~ \.php$ {
        root /var/www/html; # 这里指向的php容器的项目根目录
        fastcgi_pass php-workspace:9000; #php-workspace是模板文件中的php-fpm服务名
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_param PATH_INFO $fastcgi_path_info;
    }
}

3.5.2 修改 docker-compose.yml 文件

让 nginx 和 php-fpm 服务基于Dockerfile 构建

version: '3'
services:
  php-workspace:
    build: ./php # 这里php指我们的创建的目录
  nginx:
    build: ./nginx # 这里nginx指我们的创建的目录
    ports:
      - "8080:8080"

3.5.3 新增 nginx/Dockerfile 文件

FROM nginx:alpine

COPY conf.d /etc/nginx/conf.d

3.5.4 新增 php/Dockerfile 文件

FROM php:7.3-fpm-alpine

3.5.5 增加code文件夹作为php项目,新增index.php文件

<?php

phpinfo();

3.5.6 重新修改 docker-compose.yml 文件

让项目文件夹以挂载的形式,让php-fpm容器可以直接读取宿主机文件目录,这样我们可以很方便的修改代码调试

version: '3'
services:
  php-workspace:
    build: ./php
    volumes: # 这里指定数据卷,可以指定多个, 中划线代表值为数组的一个成员
      - ./code:/var/www/html
  nginx:
    build: ./nginx #这里nginx指我们的创建的目录
    ports:
      - "8080:8080"

3.5.7 走一下

重启之前,再确定下代码文件大概位置
image.png

docker-compose up --build 重新启动项目,我们看到在前台控制器的一些日志
image.png

两个容器都构建好了,php也启动了。如果我们现在请求 localhost:8080,还能看到nginx的请求日志
image.png

3.6 连接Mysql

用法我们依然参考了镜像的介绍 >> https://hub.docker.com/_/mysql

3.6.1 添加 db 的服务

version: '3'
services:
  php-workspace:
    build: ./php
    volumes:
      - ./code:/var/www/html
  nginx:
    build: ./nginx
    ports:
      - "8080:8080"
  db:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment: # 设定环境变量,只给定名称的变量会自动获取运行 Compose 主机上对应变量的值
      MYSQL_ROOT_PASSWORD: root

3.6.2 重新构建

docker-compose up --build
image.png

VSCode 容器管理中右键连接shell中进入容器执行 mysql -u root -p,确认密码 root,确定我们的数据库可用
image.png

3.6.3 安装 pdo mysql

在操作之前我们确定下pdo是不支持mysql驱动的
image.png
修改 php 的 Dockerfile 文件如下
RUN 的第一条命令是修改了 alpine 系统的软件源,方便后续安装会更快点

FROM php:7.3-fpm-alpine

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories \ 
    && docker-php-ext-install pdo_mysql

重新构建项目 docker-compose up --build
image.png

3.6.4 修改 index.php 文件,代码如下

<?php

$dsn = 'mysql:host=db;port=3306'; // host=db,这里的db其实是mysql的服务名
$user = 'root';
$password = 'root';

try {
    $dbh = new PDO($dsn, $user, $password);
    print_r($dbh);
} catch (PDOException $e) {
    echo 'Connection failed: '.$e->getMessage();
}

3.6.5 测试 Mysql

image.png

3.7 连接 Redis

3.7.1 容器安装PHP扩展的两种方式

在Docker Hub 中 php镜像文档中说的很清楚

  • 第一种是PHP核心的扩展可以通过 docker-php-ext-install ext_name 安装
  • 第二种是PECL
  • 第三种通过源码包编译

Redis的扩展,我们用的是第二种。

3.7.2 修改 docker-compose.yml 文件,增加redis服务

version: '3'
services:
  php-workspace:
    build: ./php
    volumes:
      - ./code:/var/www/html
  nginx:
    build: ./nginx
    ports:
      - "8080:8080"
  db:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
  redis:
    image: redis:5.0.5-alpine

3.7.3 重新构建

docker-compose up --build
构建后,我们同样可以进入容器,使用 redis-cli 命令确定redis是否可用
image.png

3.7.4 修改 php Dockerfile

因为pecl在alpine容器中安装扩展会少一些东西,所以我们先安装了一个phpize_deps,用后删除即可,保持镜像瘦小

FROM php:7.3-fpm-alpine

RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories \ 
    && docker-php-ext-install pdo_mysql \
    && apk add ${PHPIZE_DEPS} \
    && pecl install redis-4.0.1 \
    && docker-php-ext-enable redis \
    && apk del ${PHPIZE_DEPS}

重新构建,我们修改index.php 代码 phpinfo();能看到 redis扩展已打开
image.png

3.7.5 连接 Redis

修改 index.php 文件内容如下测试

<?php

$redis = new Redis();
$redis->pconnect('redis', 6379);

$key = 'first';
$redis->incr($key);

echo '页面浏览了:'.$redis->get($key); die;

image.png

3.8 日志搜集

我们回顾一下 nginx/conf.d/site.conf 的配置

server {
    listen 8080;
    root /usr/share/nginx/html;
    index index.php index.html;
    server_name localhost;
    # 我们已经把nginx相关日志写到这里了,如何放到宿主机呢?
    error_log  /var/log/nginx/error.log;
    access_log /var/log/nginx/access.log;
    # ... more something
}

docker日志采集固然有很多方案,但是暂时不考虑集群管理工具的日志搜集方案,这里以数据挂载的形式,把 nginx 请求日志落地到宿主机。

3.8.1 修改 docker-compose.yml 文件如下

version: '3'
services:
  php-workspace:
    build: ./php
    volumes:
      - ./code:/var/www/html
  nginx:
    build: ./nginx
    volumes: # 新增这两行
      - ./log/nginx:/var/log/nginx/
    ports:
      - "8080:8080"
  db:
    image: mysql
    command: --default-authentication-plugin=mysql_native_password
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: root
  redis:
    image: redis:5.0.5-alpine

3.8.2 重启项目

docker-compose up 执行后,就可以看到我们的文件夹多了两个目录,还有两个文件
image.png

3.9 优化

depends_on

可以指定依赖项,解决容器启动先后问题,比如一个应用如果依赖数据库,要让数据库容器先启动可以设置

depends_on:
      - db
      - redis

还有个别情况下,nginx启动时 找不到 php-workspace,我们在nginx服务下方也增加一个依赖

nginx:
 depends_on: 
 - php-workspace

环境变量

当然其实还有其他很多有用的参数设置,比如感敏数据读取,环境变量,配置host等等,后续都可以在开发中尝试使用。


4. 总结

以上思路可以解决本地开发中的很多问题了,如果项目中有使用 memcached 或者其他应用,做法都可以参考Docker Hub中的镜像文档说明中引入。
除此之外,在Laravel 开发中之前早就有一个开源项目叫做 Laradock 来搭建Docker化的PHP应用,不仅如此,还有很多中间件都可以利用 docker-compose 启动,可以参看 >> http://laradock.io/ ,但是里边有很多很多东西,因为要满足大部分人的扩展性,加了很多环境变量,if else 判断,让原本的 Dockerfile 更为复杂,增加了很多逻辑。但是思路基本上是相同的。
另外还有几点要点出:
1、docker-compose.yml 定义的一个项目会以所在的文件夹名为项目名。
2、一个项目有一个默认网络,项目中的服务彼此可以互通,所以我们刚刚在 site.conf 转发php请求可以直接用 php-workspace:9000 服务名来转发,连接 数据库也是可以用 db redis 等他们的服务名来连接。
image.png


5. 完整代码

https://github.com/baiyutang/demo-docker-compose


6. 遗留问题

1、指定数据库挂载的数据卷,比如Mysql,我们可以把其他环境的数据库直接以文件的形式拷贝,然后直接利用。这样比我们手动导入sql会更快迁移数据。

2、Mysql 镜像如何瘦身,现在Mysql镜像文件最大,如图有445M
image.png


7. 下篇预告

其实PHP的Docker环境已经有一个开源项目 Laradock,使用方便 易于扩展,但是最大的问题是 感觉臃肿 有时候安装特别慢,特别是要考虑太多扩展插件等等制作出来镜像特别大。所以下一篇的思路可能会围绕如何优化今天做的这个 demo-docker-compose,或者做 Laradock 本地化工作?
其他两个思路:
1、直接进入到 k8s 课题
2、Docker化后如何做CI/CD


8. 扩展