用 Docker 来运行 PHP 网站

使用 Docker 有一段时间,用 Docker 搭建开发环境才发现这货真是好东西,能方便提供一致的开发环境和生产环境,还能轻松部署多个不同版本的环境,互不干扰。

基本环境架设

MySQL

这里用 busybox 作为 base image

1
docker run --name=mysql_data -v /var/lib/mysql -d busybox echo MySQL Data

运行 docker ps -a 可以看到我们创建的 mysql_data 实例。

1
2
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS                         PORTS               NAMES
846d094b0309 busybox "echo MySQL Data" About an hour ago Exited (0) About an hour ago mysql_data

接下来创建 MySQL 实例,这里我使用了 tommylau/mysql,更多的信息可以参考 Docker Hub 上的说明:https://hub.docker.com/r/tommylau/mysql/

开发或者测试环境,使用 -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
2
3
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS                         PORTS               NAMES
3838c805e9dd tommylau/mysql "/entrypoint.sh mysq 39 minutes ago Up 39 minutes 3306/tcp mysql_server
846d094b0309 busybox "echo MySQL Data" About an hour ago Exited (0) About an hour ago mysql_data

创建 wwwroot 存储

新建一个 wwwroot 的实例,用于映射实例里面的 /var/www/html 路径,因为 NginxPHP 需要同时访问到这些文件。

1
docker run --name=wwwroot -v /home/user/www:/var/www/html -d busybox echo wwwroot

通过上面的命令就把本地路径 /home/user/www 映射到了 /var/www/html。注意:请用你本机的实际地址替换相应的路径。至此,我们就完成了 wwwroot 的准备工作。可以再次运行 docker ps -a 检查一下:

1
2
3
4
CONTAINER ID        IMAGE               COMMAND                CREATED             STATUS                         PORTS               NAMES
6e0fed55437c busybox "echo wwwroot" 35 seconds ago Exited (0) 34 seconds ago wwwroot
3838c805e9dd tommylau/mysql "/entrypoint.sh mysq 48 minutes ago Up 48 minutes 3306/tcp mysql_server
846d094b0309 busybox "echo MySQL Data" About an hour ago Exited (0) About an hour ago mysql_data

PHP-FPM

现在就要用到我们之前所创建的 mysql_server 实例和 wwwroot 存储了。

1
docker run --name=php-fpm --volumes-from wwwroot --link mysql_server:mysql -d tommylau/php

新创建的这个 php-fpm 实例中,如果打开 /etc/hosts 会发现里面有一条域名记录,指向 mysql_server 实例。

1
2
3
4
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
d6a7ffb010a7 tommylau/php "php-fpm" 7 minutes ago Up 7 minutes 9000/tcp php-fpm
3838c805e9dd tommylau/mysql "/entrypoint.sh mysq About an hour ago Up About an hour 3306/tcp mysql_server
1
2
3
4
5
6
7
8
9
$ docker exec -ti php-fpm cat /etc/hosts
172.17.0.6 d6a7ffb010a7
127.0.0.1 localhost
::1 localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.4 mysql 3838c805e9dd mysql_server

我们可以看到在 hosts 文件最后有一条记录 172.17.0.4 mysql,这个就是 mysql_server 实例在虚拟环境中的 IP 地址,我们在 php-fpm 实例中,就是通过 mysql 这个名字与 mysql_server 实例进行通信的,我们可以 ping 一下看看。

1
2
3
4
5
$ docker exec -ti php-fpm ping -c 3 mysql
PING mysql (172.17.0.4): 56 data bytes
64 bytes from 172.17.0.4: icmp_seq=0 ttl=64 time=0.084 ms
64 bytes from 172.17.0.4: icmp_seq=1 ttl=64 time=0.158 ms
64 bytes from 172.17.0.4: icmp_seq=3 ttl=64 time=0.054 ms

Nginx

启动 Nginx 容器可以使用 Dockerfile 生成一个新的镜像,也可以使用 -v 挂在一个配置文件映射到实例中。

Nginx 配置文件

准备好 Nginx 的配置文件 default.conf,记下该文件的路径

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server {
listen 80;

root /var/www/html;
index index.html index.htm index.php;

server_name localhost;

location / {
true# First attempt to serve request as file, then
true# as directory, then fall back to displaying a 404.
truetry_files $uri $uri/ =404;
true# Uncomment to enable naxsi on this location
true# include /etc/nginx/naxsi.rules
true}

location ~ \.php$ {
truefastcgi_split_path_info ^(.+\.php)(/.+)$;
truefastcgi_pass php:9000;
truefastcgi_index index.php;
trueinclude fastcgi_params;
true}
}

创建 Web 页面

本地路径是 /home/user/www,在这个目录创建2个新文件,分别是 index.htmlinfo.php

index.html

1
2
3
4
5
6
7
8
9
<html>
<head>
true<title>Test</title>
</head>
true
<body>
trueHello,World!
</body>
</html>

info.php

1
2
<?php
truephpinfo();

Dockerfile 方式运行 Nginx

在刚才创建 default.conf 的目录内(/home/user/docker),创建一个文件名为 Dockerfile 的文件(Mac 和 Linux 下请注意首字母的大写),其内容如下:

1
2
FROM tommylau/nginx
COPY default.conf /etc/nginx/conf.d/

打开终端或者命令行并进入到 Dockerfile 所在目录,运行 docker build 命令来生成一个新的镜像。注意:本命令必须在 Dockerfiledefault.conf 所在目录执行,否则 Docker 会提示找不到 Dockerfile

1
2
3
4
5
6
7
8
9
$ docker build -t local/nginx .
Sending build context to Docker daemon 5.632 kB
Sending build context to Docker daemon
Step 0 : FROM tommylau/nginx
---> 923ac8f880f4
Step 1 : COPY default.conf /etc/nginx/conf.d/
---> f5cc770ff88f
Removing intermediate container 25c8d14259e4
Successfully built f5cc770ff88f
1
2
3
$ docker images
REPOSITORY TAG IMAGE ID CREATED VIRTUAL SIZE
local/nginx latest f5cc770ff88f 6 minutes ago 132.9 MB

这个命令会生成一个新的名为 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/info.php 来查看 PHP 版本信息。

挂载方式运行 Nginx

1
docker run --name=nginx --volumes-from wwwroot --link php-fpm:php -v /home/user/default.conf:/etc/nginx/conf.d/default.conf -p 80:80 -d tommylau/nginx

如果运行上述命令,提示如下错误,我们可以使用 docker rm -f nginx 命令来删除旧的实例,再重新执行该命令便可。

Error response from daemon: Conflict. The name “nginx” is already in use by container 16aaaf7c5bfd. You have to delete (or rename) that container to be able to reuse that name.

测试 MySQL 连接

在的 wwwroot 目录(/home/user/www)中新建一个 mysql.php 的文件:

mysql.php

1
2
3
4
5
6
7
8
9
<?php
$connect = mysql_connect("mysql", "root", "Passw0rd") or die("Unable to connect to MySQL.");
mysql_select_db("mysql") or die("Could not open the database.");
$showtablequery = "SHOW TABLES FROM mysql;";
$query_result = mysql_query($showtablequery);

while ($row = mysql_fetch_array($query_result)) {
trueecho $row[0] . "<br />\n";
}

访问:http://localhost/mysql.php ,应该会打印出类似如下的 mysql 数据库表名列表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
columns_priv
db
event
func
general_log
help_category
help_keyword
help_relation
help_topic
innodb_index_stats
innodb_table_stats
ndb_binlog_index
plugin
proc
procs_priv
proxies_priv
servers
slave_master_info
slave_relay_log_info
slave_worker_info
slow_log
tables_priv
time_zone
time_zone_leap_second
time_zone_name
time_zone_transition
time_zone_transition_type
user

到此为止,Docker 已经能够完整运行一个 PHP 网站了。

参考:https://tommy.net.cn/2015/02/13/run-and-debug-php-website-with-docker-part-1

(完)