在 Docker 中使用 Laravel schedule

在 Docker 中部署 Laravel 应用,难免会用到 Laravel 本身的任务调度系统(Schedule),而 Schedule 需要用到 cron,在 Docker 中使用 cron,有以下三种方案:

  • 使用独立的 cron 容器。❎ 如果有多个不同的容器应用需要依赖 cron 时,那么这是一种很完美的解决方案,但是我们只用到 Laravel 的任务调度,因此独立的 cron 容器显得多余,所以不能采用此方案。

  • 直接使用宿主机的 cron。❎ 如果直接使用宿主机的 cron,那么「定时任务」这个功能就不属于容器的一部分了。以后在应用迁移与快速部署时,cron 需要单独配置,这违背了我们使用 Docker 的初衷,我们的初衷是希望用docker-compose up这一个命令就能快速构建出一个线上应用。并且,在宿主机上使用 cron,使用 Laravel schedule 任务调度时不能和 MySQL 或 Redis 进行通信,所以这个方案需要被否决。

  • 在 PHP 容器中设置 cron。✅ 这才是最佳解决方案。Laravel 只需要在 cron 中配置一个Laravel Task Scheduler,使用此容器中的 PHP 执行文件可以直接执行 Laravel 的任务调度。

在 PHP 容器中使用 cron

我们直接使用基于 Ubuntu 的 php:7.1.11-fpm的 Docker Hub 的官方镜像,然后需要在Dockerfile文件中安装 cron.

RUN apt-get install cron -y

然后在Dockerfile文件所在目录下创建crontab文件,内容为:

* * * * * /usr/local/bin/php /your_laravel_app_path/artisan schedule:run >> /dev/null 2>&1

其中your_laravel_app_path为 Laravel 应用在容器中的实际路径。

其实上面这一步我是踩了坑的,之前我一直按照官方手册的以下写法来配置的:

* * * * * php /your_laravel_app_path/artisan schedule:run >> /dev/null 2>&1

打了日志之后我才发现,容器内无法直接找到 PHP 的执行文件,所以要写全 PHP 执行文件的完整路径,例如/usr/local/bin/php。当然,PHP 执行文件的路径可能会有所不同,如果不清楚,需要到 PHP 容器中查看一下。

然后继续写Dockerfile,将crontab文件映射到容器目录中,赋予其读写权限:

COPY ./crontab /var/spool/cron/crontabs/root
RUN chmod 0644 /var/spool/cron/crontabs/root
RUN crontab /var/spool/cron/crontabs/root

至于第三行的命令,这么做是为了使 crontab 配置生效,我参考了 这篇文章,具体原因没有深究。

注意:如果容器文件系统是 Debian,cron 的配置路径是有所不同的,本文不赘述,这里只讨论 Ubuntu 的镜像

运行 cron

一开始我直接在Dockerfile中写了CMD ["cron"],发现 PHP-FPM 服务不启动了,是因为Dockerfile中只会执行一次 CMD 命令,多条 CMD 只执行最后一条,CMD ["cron"]CMD ["php-fpm"]覆盖了。

因此我们需要一个 bash 脚本来启动 cron,在Dockerfile文件所在目录下创建entrypoint.sh,文件内容为:

#!/bin/bash

set -e

cron

exec "$@"

然后使用ENTRYPOINT命令添加到Dockerfile就好。
完整的Dockerfile文件应该是这个样子:

######
# See: https://hub.docker.com/_/php/
######

FROM php:7.1.11-fpm

RUN apt-get update && apt-get install -y cron
RUN rm -rf /var/lib/apt/lists

COPY ./crontab /var/spool/cron/crontabs/root
RUN chmod 0644 /var/spool/cron/crontabs/root
RUN crontab /var/spool/cron/crontabs/root

COPY ./entrypoint.sh /usr/local/bin/
ENTRYPOINT ["entrypoint.sh"]

CMD ["php-fpm"]

重点结论

  1. 要在容器中使用 cron,需要在Dockerfile中安装 cron,并将 crontab 配置信息映射到容器内。
  2. crontab 配置 Laravel-scheduler 时,要填写 PHP 的执行文件路径,不然可能无法正确执行。
  3. 要写单独的脚本启动 cron,否则会覆盖掉 PHP 容器的 PHP-FPM 服务。
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 4年前 自动加精
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 9
leo

多个 container 就炸了

6年前 评论

@leo 我没能理解,能详细说明下吗 leo 兄。

6年前 评论
leo

@Patrick95 如果启动了多个 container,那么每个 container 都会去执行定时任务,比如你设了一个每天定时发邮件的任务,那么你有多少个 container,每个用户就会收到多少封邮件

6年前 评论

@leo 啊对,文中说的情况只适用于一个 container 运行 Laravel 应用的情况,多个容器的话可以考虑单独用一个 cron 容器来做计划任务。

6年前 评论

@Patrick95
谢谢你的方案。 只是对 @leo 提到的“启动多个 container 会导致每个用户收到多封邮件”有些不明白。

例如,现在有多个跑 Laravel 应用的 container,虽然他们都是基于这同一个 Dockerfile 生成的(拥有 crontab 特性),但是启动的时候,使用 -v 和 --link 让每个 Laravel 应用加载不同的项目文件夹、数据库。 那么“发邮件”这个动作并不会在不同的 Laravel 应用之间共享吧?

不知道是不是我理解有误,望解答。谢谢。

6年前 评论

@my101du
在容器集群的情况下需要用到多个php-fpm容器,如果按照此方案来说那么启动同样的php-fpm容器就会有同样的crontab任务

6年前 评论

@稻草人AQA
谢谢,这个我明白。生成的多个 php-fpm 容器 都是基于同一份 Dockerfile 创建出来的,“配置参数”相同,因此都拥有 crontab 任务。

@leo 提到的某个容器中“发邮件”动作会“污染”别的容器, 我觉得难以理解。
(1)虽然每个 php-fpm 容器有 crontab
(2)但是 Laravel 的定时任务,是由容器内的 crontab 调用 Laravel 应用程序的 artisan schedule:run 来执行的
(3) 这个 发邮件动作 ,在一些 Laravel 应用程序里可能并没有。

例如:
php-fpm Container 1 --> Crontab --> Laravel Task Schedule --> 有发邮件任务
php-fpm Container 2 --> Crontab --> Laravel Task Schedule --> 没有发邮件任务

我试试创建几个容器看看。

6年前 评论

file docker build . 出错

5年前 评论

@wenlongh

然后在Dockerfile文件所在目录下创建crontab文件,内容为:


* * * * * /usr/local/bin/php /your_laravel_app_path/artisan schedule:run >> /dev/null 2>&1

这一步做了吗

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!