在 Alpine 容器中使用 PHP8.0 运行 Laravel

717 阅读7分钟

部署 Laravel 应用程序时,我们希望部署过程尽可能快速和简单。实现这一目标的一个重要部分是选择正确的 Linux 基础镜像来启动一个容器镜像来运行我们的应用程序。

Alpine Linux 就是启动容器最快的发行版。自 Docker 首次发布以来,Alpine 发行版的流行度不断增长,因为它是一个小型、容器和以安全为中心的发行版。

为了能够运行一个应用程序,只有 PHP 和 Composer 是不够的,NGINX 和 Supervisor 也是必需的。这些看起来很复杂,但是通过 Dockerfile 文件就能实现。

1. Dockerfile

下面是一个在本地和生产环境中用于为 Laravel 应用程序提供服务的完整的 Dockerfile 。需要注意的是,它并没有进行最少层数的优化,这是有目的的,因为我们要了解每个部分的作用。

 FROM alpine:latest
 ​
 WORKDIR /var/www/html/
 ​
 # Essentials
 RUN echo "UTC" > /etc/timezone
 RUN apk add --no-cache zip unzip curl sqlite nginx supervisor
 ​
 # Installing bash
 RUN apk add bash
 RUN sed -i 's/bin/ash/bin/bash/g' /etc/passwd
 ​
 # Installing PHP
 RUN apk add --no-cache php8 \
     php8-common \
     php8-fpm \
     php8-pdo \
     php8-opcache \
     php8-zip \
     php8-phar \
     php8-iconv \
     php8-cli \
     php8-curl \
     php8-openssl \
     php8-mbstring \
     php8-tokenizer \
     php8-fileinfo \
     php8-json \
     php8-xml \
     php8-xmlwriter \
     php8-simplexml \
     php8-dom \
     php8-pdo_mysql \
     php8-pdo_sqlite \
     php8-tokenizer \
     php8-pecl-redis
 ​
 # Installing composer
 RUN curl -sS https://getcomposer.org/installer -o composer-setup.php
 RUN php composer-setup.php --install-dir=/usr/local/bin --filename=composer
 RUN rm -rf composer-setup.php
 ​
 # Configure supervisor
 RUN mkdir -p /etc/supervisor.d/
 COPY .docker/supervisord.ini /etc/supervisor.d/supervisord.ini
 ​
 # Configure PHP
 RUN mkdir -p /run/php/
 RUN touch /run/php/php8.0-fpm.pid
 ​
 COPY .docker/php-fpm.conf /etc/php8/php-fpm.conf
 COPY .docker/php.ini-production /etc/php8/php.ini
 ​
 # Configure nginx
 COPY .docker/nginx.conf /etc/nginx/
 COPY .docker/nginx-laravel.conf /etc/nginx/modules/
 ​
 RUN mkdir -p /run/nginx/
 RUN touch /run/nginx/nginx.pid
 ​
 RUN ln -sf /dev/stdout /var/log/nginx/access.log
 RUN ln -sf /dev/stderr /var/log/nginx/error.log
 ​
 # Building process
 COPY . .
 RUN composer install --no-dev
 RUN chown -R nobody:nobody /var/www/html/storage
 ​
 EXPOSE 80
 CMD ["supervisord", "-c", "/etc/supervisor.d/supervisord.ini"]

1.1 定义基础镜像

构建 Dockerfile 的第一步是创建文件本身并定义 Linux 发行版及其版本号。完成后,您就可以开始使用构建容器镜像所需的指令编写 Dockerfile。

 FROM alpine:latest
 ​
 WORKDIR /var/www/html/

FROM 指令设置基本镜像。请注意,已定义 alpine:latest,它将 alpine 设置为基本 Linux 镜像。在发行版名称之后,有一个: 用于指定标签或版本的符号,因此在FROM alpine:latest解释该指令时,它会将最新版本的 alpine 设置为基础镜像。

WORKDIR 指令为 Dockerfile 中跟在它后面的任何 RUN、CMD、ENTRYPOINT、COPY 和 ADD 指令设置工作目录。因此,当指令WORKDIR /var/www/html/ 被解释时,Dockerfile 中的每个命令执行都将发生在 /var/www/html/ 中。

1.2 软件安装

现在容器镜像库已经定义好了,是时候开始研究运行应用程序需要安装的软件了。如前所述,PHP、Composer、NGINX 和 Supervisor 都是要安装的软件程序,但这还不是全部。由于该软件具有依赖性,因此也必须安装它们。下面是分解成几段解释的安装过程,便于你理解它。

 RUN echo "UTC" > /etc/timezone
 RUN apk add --no-cache zip unzip curl sqlite nginx supervisor

第一个 RUN 指令将在当前镜像之上的新层中执行命令并提交结果。因此,在 RUN echo "UTC" > /etc/timezone 解释时,echo 命令会将 UTC 字符串打印到 /etc/timezone文件中。命令执行的结果是:UTC 成为标准时区。

在第二条 RUN 指令中,出现了一个 apk 命令,apk 是 Alpine 包管理器,另一个著名的包管理器是来自 Ubuntu 的 apt。当RUN apk add --no-cache zip unzip curl sqlite nginx supervisor被解释时,它会将这些软件程序安装在基本镜像中。

 RUN apk add bash
 RUN sed -i 's/bin/ash/bin/bash/g' /etc/passwd

第一个 RUN 指令是安装 bash。第二条指令通过将字符串 /bin/ash替换为 /bin/bash,将其设置为标准 shell。此更改是因为 Alpine 标准 shell是ash,工作方式不同,当您或您的团队需要在容器中执行 shell 脚本时,可能不兼容。

 RUN apk add --no-cache php8 \
     php8-common \
     php8-fpm \
     php8-pdo \
     php8-opcache \
     php8-zip \
     php8-phar \
     php8-iconv \
     php8-cli \
     php8-curl \
     php8-openssl \
     php8-mbstring \
     php8-tokenizer \
     php8-fileinfo \
     php8-json \
     php8-xml \
     php8-xmlwriter \
     php8-simplexml \
     php8-dom \
     php8-pdo_mysql \
     php8-pdo_sqlite \
     php8-tokenizer \
     php8-pecl-redis

第一个 RUN 指令表示必须安装 PHP 和所有列出的扩展。如前所述,此 Dockerfile 用于服务 Laravel 应用程序,因此 PHP 扩展是任意的,并且可以根据您运行的框架或应用程序而改变。

 RUN curl -sS https://getcomposer.org/installer -o composer-setup.php
 RUN php composer-setup.php --install-dir=/usr/local/bin --filename=composer
 RUN rm -rf composer-setup.php

这里主要是安装composer

2. 相关软件配置

现在所有需要的软件都已安装,它们必须配置并紧密结合在一起,以使 Laravel 应用程序的服务按预期运行起来。

2.1 配置 supervisor

 RUN mkdir -p /etc/supervisor.d/
 COPY .docker/supervisord.ini /etc/supervisor.d/supervisord.ini

在此 RUN 指令中,Dockerfile 创建目录 /etc/supervisor.d/。该目录将保存初始化文件,这些文件指定了 Supervisor 在容器启动时运行的参数配置。

在第二条 RUN 指令中,将本地文件 .docker/supervisord.ini 复制到 /etc/supervisor.d/ 容器文件夹中。如上所述,该文件包含 Supervisor 运行的配置,这些配置参数是:

 [supervisord]
 nodaemon=true
 ​
 [program:nginx]
 command=nginx
 stdout_logfile=/dev/stdout
 stdout_logfile_maxbytes=0
 stderr_logfile=/dev/stderr
 stderr_logfile_maxbytes=0
 ​
 [program:php-fpm]
 command=php-fpm8
 stdout_logfile=/dev/stdout
 stdout_logfile_maxbytes=0
 stderr_logfile=/dev/stderr
 stderr_logfile_maxbytes=0

2.2 配置 PHP

 RUN mkdir -p /run/php/
 RUN touch /run/php/php8.0-fpm.pid
 ​
 COPY .docker/php.ini-production /etc/php8/php.ini
 COPY .docker/php-fpm.conf /etc/php8/php-fpm.conf

在第一个 RUN 语句中,Dockerfile 将创建 /run/php 目录。该目录将保存包含特定软件的进程 ID 的 .pid文件。

第二条语句,在/run/php/ 目录下创建文件 php8.0-fpm.pid 。现在 Alpine 发行版有一个文件来存储 PHP-FPM 启动时将创建的进程 ID。

第三条语句将 php.ini-production 文件从本地 .docker 文件夹复制到 /etc/php8/ 容器文件夹中。此文件包含 PHP 将运行的所有配置,此文件的内容是从GitHub 上的 PHP 官方存储库复制而来的。

第四个语句将 php-fpm.conf 文件从本地 .docker 文件夹复制到 /etc/php8/ 容器文件夹中。该文件包含 PHP-FPM 将运行的所有配置。

请注意,php-fpm.conf 没有任何自定义配置或优化,您可以根据需要随意配置此文件。

2.3 配置 NGINX

 COPY .docker/nginx.conf /etc/nginx/
 COPY .docker/nginx-laravel.conf /etc/nginx/modules/
 ​
 RUN mkdir -p /run/nginx/
 RUN touch /run/nginx/nginx.pid
 ​
 RUN ln -sf /dev/stdout /var/log/nginx/access.log
 RUN ln -sf /dev/stderr /var/log/nginx/error.log

在第一个语句中,nginx.conf 从本地 .docker 文件夹复制到 /etc/nginx/ 容器文件夹中。此文件包含运行 NGINX 所有的配置。

第三条语句,将 nginx-laravel.conf 从本地 .docker 文件夹复制到 /etc/nginx/modules/ 容器文件夹。该文件包含 NGINX 用于正确启动 Laravel 服务的所有配置,配置内容包括:

 server {
     listen 80;
     server_name localhost;
     root /var/www/html/public;
 ​
     add_header X-Frame-Options "SAMEORIGIN";
     add_header X-Content-Type-Options "nosniff";
 ​
     index index.php;
 ​
     charset utf-8;
 ​
     location / {
         try_files $uri $uri/ /index.php?$query_string;
     }
 ​
     location = /favicon.ico { access_log off; log_not_found off; }
     location = /robots.txt  { access_log off; log_not_found off; }
 ​
     error_page 404 /index.php;
 ​
     location ~ .php$ {
         fastcgi_pass localhost:9000;
         fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
         include fastcgi_params;
     }
 ​
     location ~ /.(?!well-known).* {
         deny all;
     }
 }

第四个语句指定创建 /run/nginx/ 目录。如 PHP-FPM 配置会话中所述,运行目录包含 .pid 文件,其中写入特定软件的进程 ID。

在第五条语句中,在 /run/nginx/ 目录中创建文件nginx.pid。现在,Alpine 发行版有一个文件来存储将在 NGINX 启动时创建的进程 ID。

第六个语句在 /var/log/nginx/access.log 中创建 Alpine 标准输出的符号链接。正如 Supervisor 部分中提到的,此配置允许我们从容器中查看 NGINX 日志。

最后,第七条语句 /var/log/nginx/error.log 中创建 Alpine 标准错误的符号链接。正如 Supervisor 部分中提到的,此配置允许我们从容器中查看 NGINX 错误。

3. 构建过程

构建过程是将应用程序复制到容器中,并安装其依赖项,让 Laravel 应用程序准备好由 NGINX、PHP-FPM 和 Supervisor 提供服务。

 COPY . .
 RUN composer install --no-dev

在 COPY 语句中,Dockerfile 所在目录中的所有 Laravel 文件和文件夹都被复制到 WORKDIR 指令指定的工作目录中。

在 RUN 语句中,安装 Laravel 应用程序的生产依赖项,使应用程序准备好由 Supervisor、NGINX 和 PHP-FPM 提供服务。

4. 启动容器

现在一切都已安装并正确配置,我们需要指定一旦容器启动后此容器镜像将如何开始为应用程序提供服务以及使用哪个 TCP 端口。

 EXPOSE 80
 CMD ["supervisord", "-c", "/etc/supervisor.d/supervisord.ini"]

EXPOSE 指令通知容器在运行时监听指定的网络端口,而 CMD 指令的目的是为正在执行的 Docker 容器提供默认命令。

现在您的 Dockerfile 终于完成了,您可以通过docker build -t laravel-alpine:latest . --no-cache在终端中执行来从中构建一个容器。