运维与工具
Forge、Sail 与 Telescope。
运维与工具
这一课你将能做到
- 用
.env 与 config/ 管理多环境配置并开启配置缓存,按通道输出结构化日志
- 用任务调度集中管理定时任务,独立完成一套可复用的生产部署流程
- 说清 Octane 与 Vapor 解决什么问题、代价是什么,知道 Pint、Telescope、Pulse 的用途
为什么要学
能跑只是起点:密码不能写死,半夜的异常要能从日志追溯,日报要准时跑,发版要可重复可回滚。这些「代码之外」的事决定了项目是玩具还是产品。Laravel 运维体系完整:配置对应 Spring 的 application.yml+Profile,日志同样是 Monolog(Logback 的角色),调度对应 @Scheduled/xxl-job,部署有 php-fpm、Octane、Vapor 三条路线。
先建立图像
Git 仓库 ──pull──> 生产服务器(Nginx + PHP-FPM)
├── config/route/view 缓存
├── Supervisor ── queue worker
└── crontab ── schedule:run(每分钟)
性能三形态:PHP-FPM(每请求引导) Octane(常驻内存) Vapor(AWS Lambda)
| 关注点 | Laravel | Spring Boot |
|---|
| 配置 | .env+config/*.php | application-{profile}.yml |
| 日志 | Monolog channels | Logback appender |
| 定时任务 | Schedule facade | @Scheduled/xxl-job |
| 进程守护 | Supervisor+php-fpm | systemd+内嵌 Tomcat |
| 部署 | Forge(VPS)/Vapor(Serverless) | 虚拟机/容器/云函数 |
跟着做
步骤 1:环境与配置
.env 不进 Git(只保留 .env.example):
APP_ENV=production
APP_KEY=base64:... # php artisan key:generate
APP_DEBUG=false # 生产必须 false,否则错误页泄露堆栈
DB_HOST=127.0.0.1
DB_DATABASE=app
DB_USERNAME=app
DB_PASSWORD=强密码
原则:代码读 config(),env() 只出现在 config/ 文件里。config/services.php:
return [
'sms' => ['key' => env('SMS_KEY'), 'mock' => env('SMS_MOCK', true)],
];
$smsKey = config('services.sms.key');
配置缓存后,config 文件之外的 env() 返回 null——初学者最常踩的坑:
php artisan config:cache
php artisan config:clear
步骤 2:日志
config/logging.php 生产用 daily(按天留文件、自动清理):
'stack' => ['driver' => 'stack', 'channels' => ['daily']],
'daily' => ['driver' => 'daily', 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'days' => 14],
use Illuminate\Support\Facades\Log;
Log::info('订单支付成功', ['order_no' => $order->order_no]);
Log::warning('短信接口超时,已降级', ['order_no' => $order->order_no]);
Log::error('发货失败', ['order' => $order->id]);
要同步到团队 IM,给 stack 加 slack/logstash 通道。异常上报入口在 bootstrap/app.php 的 withExceptions。
步骤 3:任务调度
Laravel 11 在 routes/console.php 定义,不再需要 Kernel:
use Illuminate\Support\Facades\Schedule;
Schedule::command('report:daily --yesterday')
->dailyAt('02:15')
->withoutOverlapping() // 上一次没跑完不重叠
->onOneServer() // 多机只在一台执行
->runInBackground();
Schedule::job(new GenerateDailyReport(now()->subDay()))->weeklyOn(1, '08:00');
Schedule::command('telescope:prune')->hourly();
服务器只加一条 crontab,时间全部集中在代码管理:
* * * * * cd /var/www/app && php artisan schedule:run >> /dev/null 2>&1
开发用 php artisan schedule:work;查看用 schedule:list、schedule:test。
步骤 4:标准部署(Nginx + PHP-FPM)
Nginx root 指向 public/,PHP 8.2+ 并装好 pdo_mysql、redis、mbstring、bcmath 扩展。授权目录:chown -R www-data:www-data storage bootstrap/cache。
每次发版(写成 deploy.sh 或用 Forge/Actions):
php artisan down
git pull origin main
composer install --no-dev --optimize-autoloader
php artisan migrate --force
php artisan config:cache
php artisan route:cache
php artisan view:cache
php artisan event:cache
php artisan optimize
php artisan queue:restart
php artisan up
迁移要向后兼容:先加字段、再发代码、最后清理旧字段,避免新旧版本交替报错。
步骤 5:Octane——常驻内存
composer require laravel/octane
php artisan octane:install --server=frankenphp # 可选 roadrunner/swoole
php artisan octane:start --workers=4 --task-workers=8
php artisan octane:reload
框架只引导一次,复杂接口常数倍提速。代价是状态跨请求残留:不要在单例/静态属性里存请求数据(用户、租户),请求级服务用 scoped 绑定或中间件重置;上线前过一遍官方内存泄漏清单。
步骤 6:Vapor——Serverless
composer require laravel/vapor-cli
php artisan vendor:publish --provider="Laravel\Vapor\VaporServiceProvider"
vapor login && vapor init
id: 1234
name: coderoadmap
environments:
production:
runtime: php-83
build: ['composer install --no-dev']
http: true
queue: true # 队列消费者自动托管
scheduler: true # 调度平台执行,无需 crontab
storage: s3
vapor deploy production
vapor env production
Vapor 基于 AWS Lambda:零服务器、按调用付费、自动弹性;代价是冷启动、本地磁盘不持久、执行有时间上限。想要「有服务器但免手运维」看 Forge(自动管 VPS、Nginx、证书、部署)。
步骤 7:日常工具
./vendor/bin/pint # 风格自动修(PSR-12 + Laravel 规范)
php artisan about # 环境与驱动一览
# Telescope:请求/查询/任务/异常调试面板(仅开发,--dev 安装)
# Pulse:生产慢请求、吞吐、队列、服务器指标面板
本地 Docker 环境用 Sail:./vendor/bin/sail up 一键拉起 PHP、MySQL、Redis、Mailpit。
讲清楚要点
- 配置分层:
.env 放环境差异与密钥,config/ 放结构默认值;缓存是生产必选,也是「本地好线上 null」的头号原因。
- 日志可检索:带上下文键值、按级别使用;不要把密码写进日志。
- 调度防重:多机
onOneServer()、长任务 withoutOverlapping();入口依赖系统时间,注意时区与 NTP。
- 部署本质是缓存+重启:config/route/view 缓存决定性能,worker/Octane 重启决定代码一致性;用脚本固化。
- 零停机迁移:破坏性变更分两次发布;迁移只做结构,数据回填用 Job 分批。
- 选型:普通流量 php-fpm+Forge;接口密集且能守无状态纪律选 Octane;波峰波谷大、想免运维选 Vapor。
你可能会懵的地方
- 上线后
env('DB_HOST') 为 null?
config:cache 后只有 config 文件内的 env() 生效。修复:代码改读 config()。
route:cache 报闭包错误?
路由缓存不支持闭包路由,改成控制器方法。
- 调度没执行?
三层排查:crontab 是否在跑 → 时区是否正确 → 是否被
withoutOverlapping 旧锁卡住。
- Vapor 和 Forge 怎么选?
Forge 管自己的 VPS(固定月费、磁盘持久);Vapor 是 Serverless(按用量、弹性、无状态约束),两种运维模型。
错法对照
| 错法 | 问题 | 正解 |
|---|
.env 提交 Git | 密钥泄露 | 只提交 .env.example |
生产 APP_DEBUG=true | 暴露密码堆栈 | false,错误看日志 |
| 各机器各自 crontab | 任务重复 | 集中定义+onOneServer() |
| 手工 SSH 敲部署 | 易漏不可追溯 | deploy 脚本/Forge/CI |
| 一次发版直接删字段 | 交替期报错 | 扩展-发布-收缩 |
| Octane 单例存当前用户 | 串号越权 | 请求级绑定/中间件重置 |
| 文件传 Vapor 本地盘 | 回收即丢 | S3 |
卡点急救
- 500 且无日志:看
storage/logs/ 属主与磁盘/ inode(df -h、df -i);确认 bootstrap/cache 可写;Nginx 层看其错误日志。
- 迁移卡住或锁表:大表变更用
ALGORITHM=INPLACE 或 pt-online-schema-change,低峰执行。
- 队列/调度静默停止:
supervisorctl status;确认 PHP 版本与运行用户;Vapor 看控制台日志与超时。
面试常问
-
说说 Laravel 生产部署流程。
拉代码 → composer install --no-dev → migrate --force → 四类缓存+optimize → 重启 worker/Octane → 健康检查。能提到缓存、worker 重启、迁移兼容三个细节即合格。
-
配置缓存有什么坑?
缓存后 config 文件外的 env() 返回 null、闭包路由不能缓存;纪律是 env() 只进 config 层。
-
Octane 为什么快?注意什么?
框架引导一次常驻内存,省每请求启动;必须保证无跨请求状态、发布 reload。Vapor 则是另一条路:弹性但有冷启动与无盘约束。
自测三问
- 同一应用部署测试/生产两套环境,密码与调试开关怎么管?为何不能写进代码?
- 每天 3 点的对账任务可能跑超一天、部署了 4 台机器,调度如何配置?
- 要求接口平均 30ms 以内又不想管服务器,Octane 与 Vapor 你选哪个?
小结一句话
配置靠分层缓存、日志靠上下文级别、调度靠集中加锁、部署靠脚本,再按情况选 php-fpm、Octane 或 Vapor。
延伸