使用Docker编写一个PHP应用

・16 分钟阅读

PHP应用程序通常由web服务器,关系数据库系统和语言解释器本身组成,在本教程中,我们会利用Docker实现一个完整的PHP应用程序栈,这是一个深度教程,我们为Nginx (web服务器),MySQL (数据库系统)和PHP构建和编排容器。

为了本教程,我们会编写一个简单的应用程序,从数据库中读取城市列表。

本指南假定你已经安装了docker ce,并且至少对Docker有最少的工作知识,因此,你可以查看以下教程:

配置我们的工作环境

实际的基于docker的应用程序通常由几个容器组成,手工管理这些操作很容易变得混乱和繁琐,这就是docker-compose进场的地方,它可以帮助你通过简单的yaml配置文件管理许多容器。

安装docker-compose 。


curl -L https://github.com/docker/compose/releases/download/1.19.0/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
chmod +x /usr/local/bin/docker-compose

创建一个文件夹以保存此例子的所有必需文件,然后会cd放入其中,从现在开始,这是我们的工作目录,每个命令会在这个文件夹中执行,并且每个路径都会相对于它,此文件夹稍后可能被引用为WORKING_DIR


mkdir ~/docker
cd ~/docker

现在再创建三个文件夹。


mkdir php nginx app

php文件夹是我们自定义的PHP镜像,nginx文件夹会包含自定义Nginx镜像和app文件夹的源代码。

配置PHP容器

在本例中,我们会使用php-fpm连接Nginx服务器,我们将使用官方的PHP基础镜像,但是,我们还需要安装,并且启用一些扩展,以便我们可以访问数据库,在php文件夹内创建一个名为Dockerfile的文件,并会以下内容放入其中。


FROM php:7.1-fpm-alpine3.4
RUN apk update --no-cache 
 && apk add --no-cache $PHPIZE_DEPS 
 && apk add --no-cache mysql-dev 
 && docker-php-ext-install pdo pdo_mysql

请注意,我们使用的是官方PHP镜像的Alpine版本,Alpine是一个非常小的面向容器的分布,提供更小的足迹,另外,注意命令docker-php-ext-install的使用,官方PHP镜像提供这个命令来简化安装和配置PHP扩展的过程。

现在,让我们通过发出以下(在我们的WORKING_DIR里面)来构建此Docker镜像:


docker build -t vultr-php php/

docker-compose.yml文件

如上所述,docker-compose允许你通过简单的配置文件管理许多容器,此配置文件通常命名为docker-compose.yml ,在app文件夹中创建此文件。


touch app/docker-compose.yml

现在将以下内容放入此文件中。


version: '2'
services:
 php:
 image: vultr-php
 volumes:
 - ./:/app
 working_dir: /app

我们将解释这个语法,首先,注意第一行。

 
version:'2'

 

这会指定使用的docker-compose.yml配置文件的版本,下一行指定服务,换句话说,指定要配置的容器。


services:
 php:
 image: vultr-php
 volumes:
 - ./:/app
 working_dir: /app

请注意,每个服务在services块内都有一个特定的键,这里指定的名称会用于以后引用此特定容器,还要注意在php配置中,我们定义用于运行容器(这是我们之前构建的镜像)的镜像,我们还定义了一个卷映射。


volumes:
 - ./:/app

这告诉docker-compose会当前目录(./ )映射到容器内的/app目录,最后一行会容器内的/app文件夹设置为工作目录,这意味着容器中的所有命令未来都是默认执行。

我们现在可以协调我们的容器了。


cd ~/docker/app
docker-compose up -d

你可以运行以下命令来确保执行了PHP容器:

 
docker ps

 

如何在容器内执行命令

我们还可以在app文件夹内使用docker-compose命令在已定义的服务容器内运行任何命令。


docker-compose exec [service] [command]

[service]占位符指的是服务密钥,在我们的例子中,这是php ,让我们在容器内运行一个命令来检查PHP版本。


docker-compose exec php php -v

你将看到以下输出。


PHP 7.1.14 (cli) (built: Feb 7 2018 00:40:45) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.1.0, Copyright (c) 1998-2018 Zend Technologies

配置Nginx容器

就像PHP容器一样,我们需要为web服务器创建一个定制的镜像,但是在这种情况下,我们只需要为virtual host提供一个配置,确保你位于WORKING_DIR内部,并在nginx文件夹内创建一个Dockerfile


cd ~/docker
touch nginx/Dockerfile

现在会以下内容放入此Dockerfile


FROM nginx:1.13.8-alpine
COPY ./default.conf /etc/nginx/conf.d/default.conf

我们正在使用基于Alpine的默认Nginx镜像,在这个Docker文件上,我们只是会一个配置文件复制到应用程序设置中,在生成此镜像之前,请创建配置文件。


touch nginx/default.conf

现在用这个内容填充它。


server {
 listen 80 default_server;
 listen [::]:80 default_server ipv6only=on;

 root /app;
 index index.php;

 #server_name server_domain_or_IP;

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

 location ~ .php$ {
 try_files $uri /index.php =404;
 fastcgi_split_path_info ^(.+.php)(/.+)$;
 fastcgi_pass php:9000;
 fastcgi_index index.php;
 fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
 include fastcgi_params;
 }
}

注意,在fastcgi_pass php:9000行,我们通过docker-compose.yml配置文件的service块中的名称引用PHP容器,内部docker-compose创建一个网络,并会服务名作为主机名分配给所定义的每个服务,我们现在可以构建Nginx镜像。


docker build -t vultr-nginx nginx/

更新docker-compose.yml

现在更新app/docker-compose.yml文件。


version: '2'
services:
 php:
 image: vultr-php
 volumes:
 - ./:/app
 working_dir: /app
 web:
 image: vultr-nginx
 volumes:
 - ./:/app
 depends_on:
 - php
 ports:
 - 80:80

我们只添加了一个新的服务,配置几乎是相同的,除了下面的。


depends_on:
 - php
ports:
 - 80:80

一旦Nginx容器需要完全初始化PHP服务,我们就在depends_on选项中强制这个需求,ports配置键会主端口映射到容器端口,这里我们会主机中的端口80映射到容器中的端口80

现在在app文件夹内创建一个名为index.php的文件,并会其放入其中。

 
<?php phpinfo();

 

确保端口80可以通过防火墙访问,并且执行以下操作。


cd ~/docker/app
docker-compose up -d

再次检查服务是否已启动。

 
docker ps

 

打开浏览器,并且访问[vultr-instance-ip] ,你可以通过运行以下命令找到你的实例IP地址。

 
hostname -I

 

你将看到PHP信息页面。

配置MySQL容器

官方的MySQL镜像允许你通过简单的环境变量配置容器,这可以在服务块定义中使用environment选项完成,会~/docker/app/docker-compose.yml文件更新为下列内容。


version: '2'
services:
 php:
 image: vultr-php
 volumes:
 - ./:/app
 working_dir: /app
 web:
 image: vultr-nginx
 volumes:
 - ./:/app
 depends_on:
 - php
 ports:
 - 80:80
 mysql:
 image: mysql:5.7.21
 volumes:
 - ./:/app
 - dbdata:/var/lib/mysql
 environment:
 - MYSQL_DATABASE=world
 - MYSQL_ROOT_PASSWORD=root
 working_dir: /app
volumes:
 dbdata:

现在我们为数据库定义了一个新服务,注意行dbdata:/var/lib/mysql,这会容器/var/lib/mysql上的路径挂载到由Docker管理的持久卷,这样数据库数据在容器中继续,需要在顶层块中定义这个卷,就像在文件的末尾看到的一样。

在安排新配置之前,让我们先下载一个例子MySQL数据库,官方MySQL文档提供了一些例子数据库,我们将使用已知的世界数据库,这个数据库提供了国家和城市的列表,若要下载此例子,请在我们的应用程序文件夹中执行以下操作。


curl -L http://downloads.mysql.com/docs/world.sql.gz -o world.sql.gz
gunzip world.sql.gz

现在让我们整理容器。

 
docker-compose up -d

 

如你所见,docker-compose up命令只启动还没有启动的容器,它检查docker-compose.yml文件与正在运行的容器的当前配置之间的差异。

再一次,检查MySQL容器是否已启动。

 
docker ps

 

现在填充世界数据库。


docker-compose exec -T mysql mysql -uroot -proot world < world.sql

您可以通过直接从数据库中选择数据来验证数据库是否已填充。首先在容器内访问MySQL提示符。


docker-compose exec mysql mysql -uroot -proot world

在MySQL提示符中,运行以下命令。


select * from city limit 10;

你会看到一个城市的列表,现在退出MySQL提示符。

 
mysql> exit

 

构建我们的应用程序

现在所有必要的容器都启动,并且运行了,我们可以集中于例子应用程序,会app/index.php文件更新为下列内容。


<?php

$pdo = new PDO('mysql:host=mysql;dbname=world;charset=utf8', 'root', 'root');

$stmt = $pdo->prepare("
 select city.Name, city.District, country.Name as Country, city.Population
 from city
 left join country on city.CountryCode = country.Code
 order by Population desc
 limit 10
");
$stmt->execute();
$cities = $stmt->fetchAll(PDO::FETCH_ASSOC);

?>

<!doctype html>
<html>
<head>
 <meta charset="UTF-8">
 <title> Rocks!</title>
</head>
<body>
 <h2>Most Populous Cities In The World</h2>
 <table>
 <thead>
 <tr>
 <th>Name</th>
 <th>Country</th>
 <th>District</th>
 <th>Population</th>
 </tr>
 </thead>
 <tbody>
 <?php foreach($cities as $city): ?>
 <tr>
 <td><?=$city['Name']?></td>
 <td><?=$city['Country']?></td>
 <td><?=$city['District']?></td>
 <td><?=number_format($city['Population'], 0)?></td>
 </tr>
 <?php endforeach ?>
 </tbody>
 </table>
</body>
</html>

如果你在web浏览器中访问[vultr-instance-ip],你会看到世界上最大的城市的列表,祝贺你,你已经使用Docker部署了一个完全。

结束语

在本教程中,我演示了如何配置一个完全工作的PHP应用程序,我们为PHP和Nginx构建定制镜像,并配置docker-compose来编排容器,尽管这个设置非常基本和简单,但是它反映了真实的生活场景。

在本指南中,我们在本地构建,并且标记了镜像。对于更灵活的设置,可以会这些镜像推送到docker registry ,你可以向官方docker registry 推送,甚至设置你自己的docker registry ,无论如何,这会允许你在一个主机上构建镜像,并且在另一个主机上使用它们。

有关docker-compose的更详细用法,你应该参阅正式文档

根据你的应用程序需求和使用的PHP框架,你可能需要添加更多的扩展,这可以通过修改用于构建定制PHP镜像的Dockerfile来轻松完成,但是,有些扩展需要在容器中安装额外的依赖项,你应该参考PHP官方文档中的扩展列表,以查看每个扩展的基本要求。

讨论
Huangzhongbang profile image