用 Docker 来运行和调试 PHP 网站(一)
前言
为什么要写这篇博文
因为,作为一个伪运维工程师
,我已经被架设各种 Web 服务器环境折磨得体无完肤了。直到我发现了 Docker 这货,才有一种相见恨晚的赶脚!懂我的同学你们懂的,如果你不懂的话,你可以直接关闭这个页面了。
那么用 Docker 的好处是什么呢?最重要的,就是可以快速搭建统一的 PHP 开发和生产环境。你的开发环境就是你的生产环境,本地测试通过,代表着部署到服务器也可以完全正常运行。而且还可以部署多个测试环境,让一套代码同时跑在 PHP 5.2, 5.3, 5.4, 5.5, 5.6 系统上测试兼容性,而不需要把本地开发环境弄得一塌糊涂!
准备工作
要想体验 Docker 的好处以及更好的理解本文,你必须要做好以下准备工作:
- 了解 Docker 的基本原理和操作,起码知道怎么拉镜像和创建实例吧?
- 基本的 Linux 命令行操作能力
- 熟悉 PHP 开发和相关的知识
基本环境架设
运行 MySQL
因为要经常升级 MySQL 到最新版本,所以,我们不想每升级一次数据库,就重新导出、导入一次数据,因为这样感觉实在是太土了,不够高大上。为了达到这个目的,我们在创建 MySQL 实例之前需要先创建一个 Volume 用于保存 MySQL 的数据。我个人喜欢用 busybox 作为 base image,当然,你也可以根据自己的喜好来做。
1 | docker run --name=mysql_data -v /var/lib/mysql -d busybox echo MySQL Data |
这个命令,会创建一个包含 /var/lib/mysql
的 volume,以便我们后续挂载到 MySQL 实例上。运行 docker ps -a
可以看到我们创建的 mysql_data
实例。
1 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
接下来创建 MySQL 实例,这里我使用了自己制作的 MySQL 镜像 tommylau/mysql,你可以根据自己的使用情况进行调整,更多的信息可以参考 Docker Hub 上的说明:https://registry.hub.docker.com/u/tommylau/mysql/
1 | docker run --name=mysql_server --volumes-from mysql_data -e MYSQL_ROOT_PASSWORD=Passw0rd -d tommylau/mysql |
--volumes-from
命令表示挂载名为 mysql_data
的 volume 到即将创建的 mysql_server
实例,也就是说,在 mysql_server
实例中对 /var/lib/mysql
目录进行读写操作时,实际的文件是存储在 mysql_data
实例中的。是不是有点儿难理解?没关系,你只要知道 MySQL 的数据都保存在 mysql_data
实例中就可以了。
-e MYSQL_ROOT_PASSWORD
参数用于设置环境变量,实例初始化的时候会用这个环境变量来设置 root
用户的密码,我们这里使用的密码为 Passw0rd
。如果是开发或者测试环境的话,你也可以使用 -e MYSQL_ALLOW_EMPTY_PASSWORD=1
参数,这样的话 root
密码为空。
1 | docker run --name=mysql_server --volumes-from mysql_data -e MYSQL_ALLOW_EMPTY_PASSWORD=1 -d tommylau/mysql |
查看一下我们刚刚创建的 MySQL 实例(docker ps -a
)。
1 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
嗯,不错,没有报任何错误,而且也是正在运行的状态。基本上 MySQL 的配置到这里就结束了。
创建 wwwroot 存储
其实这步是可选的,如果你只运行很简单的站点的话,直接使用 Docker 自带的 -v
将本地路径映射到实例里面就可以了,比如:
1 | docker run -v /path/to/web:/var/www/html -p 80:80 -d tommylau/apache |
这样你访问 http://localhost 的时候,访问的就是你本地磁盘的 /path/to/web
。但是本地保存的文件位置可能会有变化,为了以后更好地维护和管理,我们新建一个 wwwroot
的实例,用于映射实例里面的 /var/www/html
路径,因为 Nginx 和 PHP 需要同时访问到这些文件。
1 | docker run --name=wwwroot -v /Users/tommy/www:/var/www/html -d busybox echo wwwroot |
通过上面的命令我们就把我们的本地路径 /Users/tommy/www
映射到了 /var/www/html
。注意:请用你本机的实际地址替换相应的路径,不要照抄例子。至此,我们就完成了 wwwroot
的准备工作。可以再次运行 docker ps -a
检查一下:
1 | CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES |
运行 PHP-FPM
好像讲了这么久,终于到正题了。现在就要用到我们之前所创建的 mysql_server
实例和 wwwroot
存储了。不管三七二十一,先来个命令嗨一下再说。
1 | docker run --name=php-fpm --volumes-from wwwroot --link mysql_server:mysql -d tommylau/php |
与 mysql_server
类似,这里再次使用了 --volumes-from
参数,这样在我们新建的 php-fpm
实例里面访问 /var/www/html
就会引用 wwwroot
实例,又因为 wwwroot
实例映射到了 /Users/tommy/www
,所以实际访问地址是本机的 /Users/tommy/www
。如果你还是无法理解的话,请再重温一下上述两个小节。
--link
参数表示连接另外一个实例,这里我们连接了之前创建的 mysql_server
实例,并将它命名为 mysql
。注意,这里所谓的命名,你可以理解为一个主机名或者别名,在我们新创建的这个 php-fpm
实例中,如果你打开 /etc/hosts
会发现里面有一条域名记录,指向 mysql_server
实例。
1 | $ docker ps |
这次,我使用了 docker ps
命令,没有 -a
,表示,只列出正在运行的实例。我们创建的 mysql_data
和 wwwroot
都是完全不需要占用 CPU 的,它们只是负责存储而已。第二条命令表示在 php-fpm
实例中执行 cat /etc/hosts
。-ti
是 -t
, -i
的合体,分别表示伪 TTY 和命令行交互,想深入了解的请参考 Docker 官方文档,或运行 docker run --help
。
我们可以看到在 hosts
文件最后有一条记录 172.17.0.3 mysql
,这个就是 mysql_server
实例在虚拟环境中的 IP 地址,我们在 php-fpm
实例中,就是通过 mysql
这个名字与 mysql_server
实例进行通信的,我们可以 ping 一下看看。
1 | $ docker exec -ti php-fpm ping -c 3 mysql |
嗯,看起来一切正常,PHP-FPM 的配置就告一段落了。如果你已经看到想睡觉了,那么休息一会儿,喝杯咖啡什么的,回来再继续。
运行 Nginx
好了,我们的 Web 服务终于要跑起来了,把最后一个 Nginx 跑起来就算大功告成了。
运行 Nginx 可以使用 Dockerfile
生成一个新的镜像,也可以使用 -v
挂在一个配置文件映射到实例中。
Nginx 配置文件
在介绍两种方式之前,我们先准备好 Nginx 的配置文件 default.conf
。随便找一个你自己喜欢的目录,并创建该文件,但是请记下该文件的路径,比如 /Users/tommy/default.conf
。下面是参考的配置,你也可以根据自己的喜好进行调整。
1 | server { |
这里有几点需要做适当调整的:
root
需要根据实际情况调整,如果你都是按照这个教程操作的话,可以不用修改server_name
需要根据实际情况进行调整,如果只运行一个默认网站的话,可以不用修改fastcgi_pass
后面的php
是要连接的实例名称,稍后我们在命令行中会用到,先配置好
创建 Web 页面
上面我们曾经提到过,我的本地路径是 /Users/tommy/www
,所以我在这个目录创建2个新文件,分别是 index.html
和 phpinfo.php
。用你喜欢的工具在你本地的路径创建这2个文件就好,或者是其他你想要写的内容,你可以任意发挥你的想象力。
1 | <html> |
1 |
|
Dockerfile 方式运行 Nginx
好消息是我们马上就可以看到我们的劳动成果了,坏消息是我们又要折腾 Dockerfile
了。
在刚才创建 default.conf
的目录内(/Users/tommy
),创建一个文件名为 Dockerfile
的文件(Mac 和 Linux 下请注意首字母的大写),其内容如下:
1 | FROM tommylau/nginx |
打开终端或者命令行并进入到 Dockerfile
所在目录,运行 Docker build 命令来生成一个新的镜像。注意:本命令必须在 Dockerfile
和 default.conf
所在目录执行,否则 Docker 会提示找不到 Dockerfile
。
1 | $ docker build -t local/nginx . |
这个命令会生成一个新的名为 local/nginx
的镜像,当然你也可以按照你自己的喜好给它重新起个名字。不过你必须记住这个名字,因为稍后我们还要召唤它来提供 Web 服务。最后,整合我们之前启动的 PHP-FPM 实例 php-fpm
。
1 | docker run --name=nginx --volumes-from wwwroot --link php-fpm:php -p 80:80 -d local/nginx |
同样的,我们需要加载 wwwroot
实例,以便实例可以正确的访问 /var/www/html
目录。这里将实例 php-fpm
映射成别名 php
,这里必须要与我们之前修改的 Nginx 配置文件 default.conf
中的名字相匹配(fastcgi_pass
后面的服务器名)。-p 80:80
表示将实例内的 80 端口暴露给 Host 主机。
这个时候,我们已经可以通过 http://localhost 来访问 Nginx 实例了。你会看到一个大大的 Hello world
,当然我们也可以访问 http://localhost/phpinfo.php 来查看 PHP 版本信息。
注意:使用 boot2docker 的用户,需要将 localhost
修改为 Guest 的 IP 地址。如果你的 VirtualBox 只有 boot2docker 一个虚拟机的话,则 boot2docker 默认的 IP 地址是:192.168.59.103。所以,你可以尝试一下访问:http://192.168.59.103 。如果不行的话,你可以运行 boot2docker ip
来获得 guest 的 IP 地址。
1 | $ boot2docker ip |
挂载方式运行 Nginx
挂载配置文件方式启动 Nginx 跟使用 Dockerfile
方式类似,不过更简单一点,这种方式比较适合配置简单的情况。
记得我们之前保存 default.conf
的路径么?我的是 /Users/tommy/default.conf
。实现同上述同样的启动效果,我们可以使用下面的命令。
1 | docker run --name=nginx --volumes-from wwwroot --link php-fpm:php -v /Users/tommy/default.conf:/etc/nginx/conf.d/default.conf -p 80:80 -d tommylau/nginx |
如果运行上述命令,提示如下错误,我们可以使用 docker rm -f nginx
命令来删除旧的实例,再重新执行该命令便可。
1 | FATA[0000] Error response from daemon: Conflict. The name "nginx" is already in use by container 6f021cff90ef. You have to delete (or rename) that container to be able to reuse that name. |
打开网页检查一下,是不是和使用 Dockerfile
的方式一样?:)
还有一种情况是,我们有多个配置文件,如果一一指定的话会变得相当的痛苦,这个时候我们就会使用目录映射的方式来替代文件映射。我们先来创建一个包含配置的文件夹 conf.d
并把 default.conf
移动到该目录中。
1 | $ cd /Users/tommy |
然后使用如下命令,就可以把整个配置文件的目录映射到实例中。
1 | docker run --name=nginx --volumes-from wwwroot --link php-fpm:php -v /Users/tommy/conf.d:/etc/nginx/conf.d -p 80:80 -d tommylau/nginx |
再打开浏览器检查一下,依然可以正常运行。
测试 MySQL 连接
在我们的 wwwroot
目录(/Users/tommy/www
)中新建一个 mysql.php
的文件:
1 |
|
访问:http://localhost/mysql.php ,应该会打印出类似如下的 mysql
数据库表名列表:
1 | columns_priv |
总结
总的来说,架设的步骤如下
- 架设 MySQL 数据库,因为 PHP 需要连接到 MySQL
- 架设 PHP 服务器,同时连接到 MySQL Server
- 架设 Nginx 服务器,连接 PHP 服务器
因为 PHP 访问数据库,所以 Nginx 是不需要连接 MySQL 的,Nginx 只需要做代理服务器,当有 PHP 请求的时候转发给 PHP 服务器处理就好了。
这里的架设范例都是基于单个主机进行的,实例之间会自动存在于一个虚拟的 docker0
的交换机内。所以实例之间就好像在同一个局域网一样,如果你要跨主机进行数据的访问,那么你可能需要暴露部分服务的端口。比如 MySQL
的 3306
端口,PHP
的 9000
端口等。更多的网络设置可以参考 Docker 官方的 Network Configuration。
至此,PHP 服务器的架设就告一段落了,稍后会更新如何搭建基于 Apache 和 Xdebug 的 PHP 调试环境。