使用Docker为开发和生产的Laravel应用程序提供容器的教程

810 阅读14分钟

在过去的5年里, Docker和Laravel都有爆炸性的增长.在这个循序渐进的教程中, 我们将深入研究如何将一个现有的Laravel应用程序dockerize,使其在本地运行,然后使其准备在生产环境中运行。我们还将把它部署到Google Cloud Run,而不需要进入Kubernetes或任何YAML配置。让我们开始吧

什么是容器?

容器简单地说,是一种打包应用程序的方式,除了应用程序代码和它的依赖性,整个堆栈包括语言、文件系统和操作系统都可以一起运送。这种包装形式的另一个额外好处是可以在每次构建时指定语言和操作系统的具体版本。

有多种方法可以定义什么是容器容器化以及它们是如何运作的。但在不涉及虚拟化和管理程序的细节的情况下,上述方式对它们的理解比较简单。

如果你想参考航运集装箱的比喻,请便。使用容器的好处包括体积小、速度快、效率高和可移植性。

为了过度简化事情,你可以把容器看作是一种改进的虚拟机,它更小、更快、更节省资源。

什么是docker?

那么,如果容器使我们能够在成功构建后的每次部署中运送整个堆栈,当然,Docker这个东西又是怎么来的?Docker是一个开源的平台(还有一个公司Docker Inc),它使软件工程师能够将应用程序打包成容器。

Docker logo with whale and shipping containers

因此,Docker是一个让我们以容器形式构建、打包和运行应用程序的软件。但它并不是唯一的选择。

就流行程度而言,可以把Docker看作是容器世界的AWS,还有另一个容器平台叫做火箭(rkt),在这个比喻中可以认为是类似Vultr的东西。

开放容器计划着眼于容器运行时的标准化和治理。

接下来,我们将把这些术语和理论停在这里,并跳到在命令行中运行一些命令,以满足我们的目标,为现有的Laravel应用程序创建开发和生产就绪的容器。

前提条件

在我们深入研究代码和docker命令之前, 最好能确保以下几点:

  1. 你的机器上有docker和docker-compose在运行。
  2. 需要对Laravel的工作方式有一个大致的了解
  3. 你知道容器是如何工作的,以及需要构建容器并将其推送到容器注册中心。
  4. 为了部署应用程序,你将需要一个谷歌云账户。

是时候深入了解Laravel应用的docker化了。

应用实例

在这篇文章中,由于我们想对一个现有的Laravel应用程序进行dockerize,我们将使用Digamber Rawat用Laravel构建的学生CRUD应用程序。这个应用程序是开源的,他有一个很好的教程来解释这个应用程序是如何建立的。

它是一个相对简单的Laravel 8应用,使用MySQL作为数据库。我们将分叉这个应用程序,并将其dockerize,使其在生产中运行,而不仅仅是在开发环境中。我想感谢他在这个应用程序上的出色工作。

使用Laravel sail对本地开发环境进行Dockerize

首先, 我们将使用Laravel sail来在本地的开发环境中运行应用程序.有一些非官方的Laravel Docker环境,比如Laradock,但Sail是Laravel的官方docker开发环境。Sail是在docker compose之上的一个封装器。

为了Sail-ize 我们现有的学生CRUD应用程序, 我们将在克隆存储库后运行以下命令:

cd laravel-crud-app
cp .env.example .env
composer require laravel/sail --dev && php artisan sail:install

命令运行后, 我们应该为MySQL选择0来安装MySQL作为Laravel sail创建的docker-compose文件的一部分,如下图所示。

In Laravel sail prompt select 0 for MySQL

在这一点上, 确保我们已经在项目的根部创建了docker-compose.yml 文件.如果该文件已经创建,我们可以运行以下命令来构建和运行所需的容器:

COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 ./vendor/bin/sail build

由于Laravel Sail是Docker compose的一个包装,我在这里所做的是指示docker-compose用BuildKit构建所需的容器。

Docker BuildKit是Docker新版本中的构建功能,可以使Docker的构建更快、更有效。当构建过程完成后,我们将看到以下输出:

Laravel sail built with docker buildkit enabled

即使启用了Docker BuildKit,构建过程也需要5-10分钟,这取决于网络速度。为了运行这些容器,我们将运行以下命令:

./vendor/bin/sail up

我们将在命令的最后看到如下的输出:

Laravel sail run project with sail up

我们必须在容器运行后在.env中添加APP密钥,运行以下命令:

./vendor/bin/sail artisan key:generate

我们还需要改变.env 文件中的数据库凭证,如下所示,以使应用程序工作:

DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=laravel
DB_USERNAME=root
DB_PASSWORD=

默认情况下,MySQL容器有一个没有密码的根用户,上述配置将工作。之后,为了创建数据库结构,我们需要用它来运行迁移:

./vendor/bin/sail artisan migrate --force

接下来,当我们点击http://localhost/students ,我们应该看到空的列表,我们可以进去添加一个学生,就会有一个类似下面的输出:

Laravel project running with sail locally

祝贺你!我们的应用程序正在本地运行Laravel sail。是时候检查Laravel sail容器的生产准备情况了。

Laravel sail的docker镜像是否已经准备好用于生产?

docker images | grep sail ,快速检查一下镜像的大小,发现docker镜像是732MB。

Laravel sail docker image is very big at 732 MB

这不仅是在深入检查Docker文件的大小,在./vendor/laravel/sail/runtimes/8.0/Dockerfile ,它发现监督者文件正在使用。

command=/usr/bin/php -d variables_order=EGPCS /var/www/html/artisan serve --host=0.0.0.0 --port=80 user=sail

这个镜像是建立在Ubuntu镜像之上的,而不是官方的PHP docker镜像。它还有Node.js 15(不是Node.js的LTS版本)、yarn和composer,这些都是精简的生产镜像所不需要的。它是在考虑到开发用例的情况下建立的。

因此,我们将创建一个新的Dockerfile和一个新的docker-compose文件来测试Laravel的生产就绪的Docker镜像。作为下一步,我们将把当前的docker-compose.yml 改名为docker-compose-dev-sail.yml with:

mv docker-compose.yml docker-compose-dev-sail.yml

同样的, 扬帆起航的命令也将改为:

./vendor/bin/sail -f docker-compose-dev-sail.yml up

如果我们再次点击网址http://localhost/students ,它应该会像之前那样工作。到目前为止所做的所有修改都在这个拉动请求中,供你参考。接下来我们将创建一个生产就绪的docker文件和一个docker-compose文件,以便于测试。

Dockerize应用程序为生产准备就绪

为了使我们的学生CRUD应用建立在Laravel上,我们将在以下假设下工作:

  1. 对于一个生产环境,我们将使用一个数据库作为服务,如AWS RDS或谷歌云SQL。对于这个演示, 我将使用一个免费的远程MySQL数据库.
  2. 我们将只使用官方的docker镜像以避免任何兼容性问题或安全风险。
  3. 将使用官方的PHP Apache镜像,以保持低的复杂性,并避免有多个容器。
  4. 我们将使用带有opcache和JIT的PHP 8.0来提高速度。
  5. Docker多阶段构建和BuildKit将被用来使镜像更小,构建更快。
  6. 在这个演示中,数据库凭证在.env.prod 文件中是 "开放 "的,最好是在运行时使用环境变量来填充它们。

假设清楚了,我们就可以跳到添加docker文件。

对Laravel应用程序的生产友好的Docker文件

我们可以先在项目的根目录下添加一个Docker文件,如下所示:

FROM composer:2.0 as build
COPY . /app/
RUN composer install --prefer-dist --no-dev --optimize-autoloader --no-interaction

FROM php:8.0-apache-buster as production

ENV APP_ENV=production
ENV APP_DEBUG=false

RUN docker-php-ext-configure opcache --enable-opcache && \
    docker-php-ext-install pdo pdo_mysql
COPY docker/php/conf.d/opcache.ini /usr/local/etc/php/conf.d/opcache.ini

COPY --from=build /app /var/www/html
COPY docker/000-default.conf /etc/apache2/sites-available/000-default.conf
COPY .env.prod /var/www/html/.env

RUN php artisan config:cache && \
    php artisan route:cache && \
    chmod 777 -R /var/www/html/storage/ && \
    chown -R www-data:www-data /var/www/ && \
    a2enmod rewrite

让我们来看看如何使用这个多阶段的dockerfile来构建镜像。

首先,我们使用官方composer 2.0镜像作为build 阶段。在这个阶段,我们把整个应用程序复制到容器的/app 。然后我们运行composer install,参数为--no-dev--optimize-autoloader ,这很适合于生产构建。生产阶段没有 composer,因为我们不需要 composer 来运行我们的应用程序,我们只需要它来安装依赖性。

因此,在名为production 的下一个阶段,我们从官方的 PHP 8.0 apache 镜像开始。在设置了两个环境变量后,APP_ENV 为 "production",APP_DEBUG 为 false,我们用下面的配置启用 opcache,放在./docker/php/conf.d/opcache.ini

[opcache]
opcache.enable=1
opcache.revalidate_freq=0
opcache.validate_timestamps=0
opcache.max_accelerated_files=10000
opcache.memory_consumption=192
opcache.max_wasted_percentage=10
opcache.interned_strings_buffer=16
opcache.jit_buffer_size=100M

Opcache通过在共享内存中存储预编译的脚本字节码来提高 PHP 性能。这意味着每次请求时都不需要加载和解析相同的PHP脚本。由于它是一个缓存,所以会加快响应速度,但如果在开发中使用,就会出现问题,因为在刷新缓存之前,变化不会反映出来。

随后,我们将整个应用程序及其在构建阶段下载的 composer 依赖项复制到production 阶段,地址是/var/www/html 官方 PHP Apache docker 镜像的默认文档根。之后,我们将Apache的配置从./docker/000-default.conf 复制到容器内的/etc/apache2/sites-available/000-default.conf 。我们在容器中复制的Apache配置看起来像下面这样:

<VirtualHost *:80>

  ServerAdmin webmaster@localhost
  DocumentRoot /var/www/html/public/

  <Directory /var/www/>
    AllowOverride All
    Require all granted
  </Directory>

  ErrorLog ${APACHE_LOG_DIR}/error.log
  CustomLog ${APACHE_LOG_DIR}/access.log combined

</VirtualHost>

接下来,我们复制.env.prod 文件,其中有我们运行应用程序所需的配置和凭证。我们使用了来自Remote Mysql数据库的MySQL配置。

用docker-compose测试Docker文件

在这个时刻,我们可以测试我们的docker文件。一种方法是使用docker build 命令,并传递大量的参数。为了使我们的生活变得简单,我们将使用下面的docker-compose文件,我们可以运行docker-compose builddocker-compose up ,而不必担心记住所有冗长的docker构建参数。我们的./docker-compose.yml 文件看起来像下面这样:

version: '3'
services:
  app:
    build:
      context: ./
    volumes:
      - .:/var/www/html
    ports:
      - "80:80"
    environment:
      - APP_ENV=local
      - APP_DEBUG=true

很好!我们也准备好了docker-compose文件。我们正在使用第三版的docker-compose定义。我们有一个名为app 的服务,它从docker文件./ 构建。我们正在将当前目录下的所有文件添加到/var/www/html ,这将同步文件。接下来,我们将docker端口80暴露给我们本地机器的端口80。为了调试,我们设置了两个环境变量:APP_ENV 为本地,APP_DEBUG 为真。

在我们构建和运行我们的docker容器之前,让我们不要忘记.dockerignore

不要忘了Docker的忽略

.gitigore 相同,.dockerigore 也是一个非常方便的docker功能。与git ignore文件类似,docker ignore文件将忽略本地机器或构建环境中的文件,以便在构建容器时将其复制到docker容器中。我们的小型docker ignore文件看起来如下:

.git
.env

根据你的需要,你可以在docker ignore文件中添加更多东西。至于哪些东西不应该出现在docker镜像中,我将把这个决定权留给你。人们也倾向于忽略Dockerfile和docker-compose.yml,这是一个选择。

拉动请求中的文件变化。

在本地构建和运行prod模式

由于我们有所有需要的文件, 我们可以用下面的命令建立我们的生产就绪的Laravel docker容器。

COMPOSE_DOCKER_CLI_BUILD=1 DOCKER_BUILDKIT=1 docker-compose build

它将给我们一个输出,看起来像下面的东西,取决于你的网络速度。

Build the producion ready Laravel docker image having PHP and Apache with docker-compose

下一步, 我们可以检查一下我们刚刚用docker images | grep app 创建的镜像有多大。 在我的例子中,它是457 MB,而帆的镜像是732 MB。

Production ready Laravel docker image is much smaller at 457 MB

当然,457MB的图像并不是非常小。当然,457MB的图片并不小,但还是比帆的图片小得多。如果你关心的是图像的大小,那么探索使用阿尔卑斯山的基础图像与FPM,并使用Nginx为应用程序提供服务将是非常好的。在本教程的范围内,我们不会冒险进入那条有两个容器的道路。

容器建成后,我们可以用以下命令运行它:

docker-compose up

它将给我们提供以下输出:

Test the production Laravel docker image locally

这里需要注意的一件事是,数据库迁移已经在我们工作的数据库上运行了。如果它们没有被运行,我们需要运行下面的命令,以使表被创建:

docker-compose exec app php artisan migrate --force

这将在容器中运行数据库迁移。它也可以用一个简单的docker运行来执行。

我们可以选择多种方式来运行这个迁移。由于这是一个empotent动作,我们可以把它作为容器的启动脚本的一部分来运行,但最好是把它作为部署过程的一部分,用docker run 。 如果你打算在Kubernetes上部署这个应用,也可以把迁移设置成一个init容器

在Google Cloud Run上部署它

创建一个生产就绪的容器并在本地运行,对我们来说没有什么意义。所以我们将在Google Cloud Run上部署我们的Dockerized Laravel Student CRUD应用。Cloud Run是一个以无服务器方式部署容器的服务。是的, 我们可以在不听到Kubernetes和Pod这些词的情况下部署可以扩展到1000个实例的容器.下面是一个在生产中运行我们的应用程序的快速方法:

  1. 确保你已经登录了你的谷歌云账户。如果你没有,可以用300美元的信用,以0美元的价格获得一个,为期90天
  2. 转到github.com/geshan/lara…
  3. 点击 "在谷歌云上运行"--那个大的蓝色按钮
  4. 它将打开Google Cloud Shell
  5. 勾选trust 复选框,然后点击确认 - 图片 09:54
  6. 云端外壳可能需要一些时间来预热,它将要求我们授权gcloud命令,点击Authorize
  7. 然后我们将需要选择谷歌云的项目
  8. 之后,我们需要选择区域,我一般选择us-central-1

到现在为止,这个过程就像下面这样:

Deploying the Laravel app with docker container using deploy on Google Cloud run button

  • 容器的构建和推送到谷歌容器注册中心(GCR)需要一些时间。
  • 因此,它将使用gcloud CLI为我们在Google Cloud Run上部署容器。
  • 整个过程完成后,它将打印出我们新的云端运行服务的URL,以绿色结尾:a.run.app 。我们可以点击它,看到我们的应用程序在云端运行。

最后三步的过程将如下所示:

Laravel with production ready docker deployed on Google Cloud run

然后,如果我们点击绿色的服务URL,加上/students ,我们就会看到我们的Laravel应用在Google Cloud Run上运行,如下图。

Laravel with production ready docker running on Google Cloud run

祝贺你!我们有了它, 一个Laravel的应用程序被docker化,然后在Google Cloud Run上运行。现在是时候让你更多地去探索Google Cloud Run了。

Explore Google Cloud Run a bit more

Cloud Run在管理无服务器容器方面是非常简单和高效的。

自动缩放,冗余,安全,HTTPS URL,和自定义域名是Cloud Run的一些惊人的功能,你肯定可以利用。

如果你对如何在Cloud Run上轻松部署感兴趣,请看一下这个使用部署到Cloud Run按钮拉动请求

其他需要考虑的事情

你的容器化Laravel应用被部署在谷歌云服务上,而不需要写一行YAML配置,这是非常好的。由于这是一个演示教程,我们肯定可以把它做得更好。其中一个可以做得更好的地方就是用适当的环境变量来管理秘密,以提高安全性。

其他可以提高性能的东西是在Apache上使用Gzip压缩和HTTP缓存头。同样,这些都是需要你进一步探索的事情。

总结

我们已经看到了如何用Laravel sail将一个Laravel应用程序停靠在本地开发。然后我们重新对同一个应用程序进行了dockerize,使其更加面向生产。最后, 我们把这个应用部署在了超级可扩展和功能丰富的Google Cloud Run上, 这肯定是准备好了的.如果云端运行可以处理宜家的工作负载,它肯定可以处理你的工作负载,为无服务器容器点赞。