Docker之MNMP配置PHP Web开发环境

Docker 安装,参考Docker之开始

相关文件

➜  www tree -L 2
.
├── default.conf
├── html
│   ├── connect_mysql.php
│   └── index.php
├── mysql
├── php.ini
└── www.conf

这里的目录与文件,会在下文中都有介绍,先不用手动创建

mysql

Docker Hub 中拉取镜像

docker pull mysql:5.7 

最新版本的mysql 8.0 会有一些新特性,与最新的php-fpm 配合需要一些特殊处理,这里采用 mysql:5.7 先做上手。

实例容器,启动数据库

docker run -p 3306:3306 --name mysql -v ~/www/mysql/:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=123456 -d --privileged=true mysql

说明:

  • -p 3306:3306:将容器的3306端口映射到主机的3306端口
  • -v ~/www/mysql/:/var/lib/mysql:将主机当前用户目录下的mysql文件夹挂载到容器的/var/lib/mysql 下,在mysql容器中产生的数据就会保存在本机mysql目录下
  • -e MYSQL_ROOT_PASSWORD=123456:初始化root用户的密码
  • -d 后台运行容器
  • --name 给容器指定别名
  • --privileged=true 可能会碰到权限问题,需要加参数;否则的话 访问 mysql 会提示无访问权限

php-fpm

拉取镜像

docker pull php:7.2-fpm

实例容器,启动PHP

docker run --name php-fpm -p 9000:9000 -d php:7.2-fpm

复制配置文件至本地

docker cp php-fpm:/usr/local/etc/php-fpm.d/www.conf www.conf
docker cp php-fpm:/usr/src/php/php.ini-production php.ini

这里要特别注意一下,php-fpm:/usr/src/php/php.ini-production,在实例出的容器中,不一定是路径src/php,拉取的php:fpm版本镜像不同,php.ini路径不同。

可以这样查看php.ini路径

# 先进入容器
$ docker exec -it php-fpm bash
$ cd /usr/src/ && ls
# 有以下两个文件
php.tar.xz   php.tar.xz.asc
# 这里我们需要解压php.tar.xz文件,因为php.ini-production就在其中
//先解压xz
 xz -d php.tar.xz  
//再解压tar
# tar -xvf  php.tar

解压完毕后, php.ini-production便出现了,我当时的路径是/usr/src/php-7.1.9/php.ini-production。

即,前文中的
$ docker cp php-fpm:/usr/src/php/php.ini-production php.ini
改为
$ docker cp php-fpm:/usr/src/php-7.1.9/php.ini-production php.ini

==在本地服务器修改 php.ini 的内容,设置 cgi.fix_pathinfo=1(要先删除前面的;注释符)。==

前面关于php-fpm的一系列操作主要是为了获得配置文件,并没有挂载本地目录到容器中,所以接下来需要删除容器,重新实例一个容器出来

$ docker stop php-fpm
$ docker rm php-fpm
$ docker run --name php-fpm -p 9000:9000 --link mysql:mysql -v ~/www/html:/var/www/html -v ~/www/www.conf:/usr/local/etc/php-fpm.d/www.conf -v ~/www/php.ini:/usr/local/etc/php/php.ini -d php:7.2-fpm

上述操作,从博客中直接复制过来,实操过程基本一致。php.ini的获取,我使用的是php-7.2.12所以路径也有细微不同。按照上述操作获取即可。

Nginx

拉取镜像

docker pull nginx

实例容器

docker run --name nginx -p 80:80 -d nginx

通过浏览器:localhost 就会看到Nginx默认的欢迎界面:Welcome to Nginx

映射HTML路径

默认情况下,Docker nginx服务器的HTML路径(网站根目录)在容器/usr/share/nginx/html目录下,现在需要把这个目录映射到本地服务器的~/www/html目录。在上面命令的基础上加上-v参数,具体如下:

## 先删除之前的容器
$ docker rm nginx    
$ docker run --name nginx -p 80:80 -d -v ~/www/html:/usr/share/nginx/html nginx

-v的参数格式为:<local-volumes>:<container-volumes>

在~/www/html下创建一个index.html文件,内容随意

比如 hello world

在浏览器上访问 http://localhost,刷新一下就可以看到新的内容了。

配置Nginx

Nginx的强大很大部分体现在配置文件上,对于一些高级的应用来说,自定义Nginx非常重要。所以,我们需要把Nginx的配置文件复制到本地服务器目录:

$ cd ~/www
$ docker cp nginx:/etc/nginx/conf.d/default.conf default.conf

再加一个-v参数,把本地的配置文件映射到容器上,在重启容器:

$ docker stop nginx
$ docker rm nginx
$ docker run --name nginx -p 80:80 -v ~/www/html:/usr/share/nginx/html -v ~/www/default.conf:/etc/nginx/conf.d/default.conf -d nginx

如果配置文件有修改,需要重启容器生效:

$ docker restart nginx

这样就可以直接在本地修改配置文件了。

修改Nginx配置

就是前面我们从容器中复制出来的 default.conf

server {
    listen       80;
    server_name  _;
    root   /usr/share/nginx/html;
    index  index.html index.htm;

    #charset koi8-r;
    #access_log  /var/log/nginx/host.access.log  main;

    location / {
        #root   /usr/share/nginx/html;
        #index  index.html index.htm;
        try_files $uri $uri/ =404;
    }

    error_page  404              /404.html;
    location = /40x.html {
        root    /user/share/nginx/html;
    }    

    # redirect server error pages to the static page /50x.html
    #
    error_page   500 502 503 504  /50x.html;
    location = /50x.html {
        root   /usr/share/nginx/html;
    }

    # proxy the PHP scripts to Apache listening on 127.0.0.1:80
    #
    #location ~ \.php$ {
    #    proxy_pass   http://127.0.0.1;
    #}

    # pass the PHP scripts to FastCGI server listening on 127.0.0.1:9000
    #
    location ~ \.php$ {
        root           /var/www/html/;
        fastcgi_pass   php-fpm:9000;
        fastcgi_index  index.php;
        include        fastcgi_params;
    #    fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        fastcgi_param  SCRIPT_FILENAME  $document_root$fastcgi_script_name;        
    }

    # deny access to .htaccess files, if Apache's document root
    # concurs with nginx's one
    #
    location ~ /\.ht {
        deny  all;
    }
}

关于配置文件,有个需要注意的地方

location ~ \.php$ {
        root           /var/www/html/;
        fastcgi_pass   php-fpm:9000;
        fastcgi_index  index.php;
#        fastcgi_param  SCRIPT_FILENAME  /scripts$fastcgi_script_name;
        include        fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
    }
    
include        fastcgi_params;  
要放在
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
的前面

fastcgi_pass:其中配置的 php-fpm:9000,就是之前实例PHP的容器名

删除刚才的nginx容器,重新生成一个

$ docker stop nginx
$ docker rm nginx
$ docker run --name nginx -p 80:80 --link php-fpm -v ~/www/html:/usr/share/nginx/html -v ~/www/default.conf:/etc/nginx/conf.d/default.conf -d nginx

测试 PHP与Nginx的连接

~/www/html 下创建 index.php

<?php
    phpinfo();

浏览器访问 localhost

会以长表格形式展现当前 PHP的版本信息。

测试PHP与MySQL的连接

PHP中与MySQL建立连接 还需要我们安装 PHP的扩展:mysqli

# 进入 php-fpm 容器中
docker exec -it php-fpm bash
# 执行php mysqli 的扩展安装
docker-php-ext-install mysqli
# 退出容器
exit

重启容器

docker restart php-fpm

~/www/html 中创建 connect_mysql.php

<?php
// mysqli_connect参数:ip:port、账号、密码、数据库
$link = mysqli_connect("10.10.10.1:3306", "root", "123456");

if (!$link) {
    echo "Error: Unable to connect to MySQL." . PHP_EOL . "</br>";
    echo "Debugging errno: " . mysqli_connect_errno() . PHP_EOL. "</br>";
    echo "Debugging error: " . mysqli_connect_error() . PHP_EOL. "</br>";
    echo "string";
    // exit;
}

echo "Success: A proper connection to MySQL was made! The my_db database is great." . PHP_EOL. "</br>";
echo "Host information: " . mysqli_get_host_info($link) . PHP_EOL;

mysqli_close($link);
?>

在浏览器中输入:http://localhost/connect_mysql.php,展示如下:

Success: A proper connection to MySQL was made! The my_db database is great. 
Host information: 10.10.10.1:3306 via TCP/IP

则说明php与MySQL连接已建立

配置Dockerfile

上述中,在容器 php-fpm 中手动安装的扩展,在容器被删除后,就会被删除了,所以再次实例 php-fpm 时,还要再次手动安装扩展。是不是很麻烦,很不自动化?

so Dockerfile 就出场了,通过Dockerfile来定制镜像,只需要安装一次扩展以后实例化容器,就自动拥有这些扩展了。

先看目录层级

www
├── default.conf 
├── Dockerfile
├── html
│   ├── index.php
│   └── connect_mysql.php
├── mysql
├── php.ini
└── www.conf

Dockerfile文件如下:

FROM php:7.2-fpm
ADD www.conf   /usr/local/etc/php-fpm.d/www.conf
ADD php.ini    /usr/src/php/php.ini
RUN docker-php-ext-install mysqli pdo_mysql \
    && docker-php-ext-enable mysqli
EXPOSE 9000

依旧先删除之前的php-fpm容器:

$ docker stop php-fpm
$ docker rm php-fpm
# 构建 运行
# 进入项目的目录,比如我的www文件夹就是建在/home/username下的
$ cd ~/www
$ docker build -t php-fpm:v1 ./
$ docker run --name php-fpm -p 9000:9000 --link mysql:mysql -v ~/www/html:/var/www/html -v ~/www/www.conf:/usr/local/etc/php-fpm.d/www.conf -v ~/www/php.ini:/usr/local/etc/php/php.ini -d php:7.2-fpm
即可

当然具体需要哪些扩展还是根据自己的需求来编写Dockerfile

docker-compose

上述中,每次都要输入那么多的shell命令,每个容器都要一个个的启动,还要加那么多的参数,是不是感觉很烦?

so docker-compose 就出场了

先看目录层级

.
├── app
│   ├── connect_mysql.php
│   └── index.php
├── docker-compose.yml
└── services
    ├── mysql
    │   └── data
    ├── nginx
    │   ├── config
    │   │   └── default.conf
    │   └── logs
    └── php
        ├── Dockerfile
        └── config
            ├── php.ini
            └── www.conf

docker-compose.yml 文件如下:

version: '3'
services:
    nginx:
        image: nginx:latest
        # 端口映射
        ports:
            - "80:80"
        # 依赖关系 先跑php
        depends_on:
            - "php"
        # 数据卷
        volumes:
            # 映射主机config目录到容器/etc/nginx/conf.d目录
            - "$PWD/services/nginx/config:/etc/nginx/conf.d"
            - "$PWD/app:/usr/share/nginx/html"
        networks:
            - app_net
        # 容器名称
        container_name: "compose-nginx"
    php:
        build: ./services/php
        # image指定build Dockerfile生成镜像的名称
        image: php:7.2-fpm-pdo
        ports:
            - "9000:9000"
        volumes:
            - "$PWD/app:/var/www/html"
        networks:
            - app_net
        container_name: "compose-php"
    mysql:
        image: mysql:5.7
        ports:
            - "3306:3306"
        volumes:
            - "$PWD/services/mysql/data:/var/lib/mysql"            
        # 环境变量
        environment:
            MYSQL_ROOT_PASSWORD: "123456"
        # 允许运行特权命令    
        privileged: true
        networks:
            app_net:
                # 固定子网ip,网段必须在子网络10.10.*.*
                ipv4_address: 10.10.10.1
        container_name: "compose-mysql"
networks:
    # 配置docker network
    app_net:
        driver: bridge
        ipam:
            config:
                # 子网络
                - subnet: 10.10.0.0/16

按照如上目录层级,做好文件配置,其中相关文件配置内容,同名参考前面步骤就OK,直接复制过来就行。

实际上到了这一步,基本就完成了一个docker-compose 项目。

启动项目

docker-compose up -d

实际上,在这里启动的时候,如果本地镜像不存在,就会去Docker Hub 中去拉取;如果是定制镜像且在本地未曾构建过,就会自动去构建新的镜像;如果是定制镜像,但本地已经存在构建过的同名镜像,就会跳过构建这一步,直接启动同名镜像去实例化容器。

停止项目服务并删除所有容器

docekr-compose down

测试Sequel Pro 连接 MySQL

host:127.0.0.1
username:root
password:123456
port:3306

登录即可

关于端口

port:MySQL容器的导出端口,即启动时配置的宿主机端口与容器端口的映射,默认是3306

  • 例如:

    ports:
        - 3306:3306
    

    则Sequel Pro 中通过 127.0.0.1:3306连接。

    ports:
        - 3316:3306
    

    则Sequel Pro 中通过 127.0.0.1:3316连接,而docker-compose容器互联则要通过 3306端口来访问MySQL Server

关于一些坑

  • php最新版(实操时:7.2.12-fpm),与最新版 MySQL (8.0),由于默认的加密机制不同,导致无法连接成功,不利于新手上手实操。故本文中直接采用 mysql:5.7
  • 启动 mysql 需要 添加 privileged=true 参数,否则连接数据库时,无访问权限

参考文献

推荐阅读更多精彩内容