SuperAPI AI 工具集

symfony-yoandev-deployment

部署与运维

查看项目

更新于 10-16 07:07

技能介绍

用途

该技能面向将 Symfony 应用部署到生产环境,目标形态是一个运行 FrankenPHP 的 Docker 镜像,通过替换容器完成发布。技能本身不编写 Dockerfile,而是要求使用 dunglas/symfony-docker。

技能适用范围:从镜像即将构建开始,到日志与指标的去向为止。本地运行与类生产的 FrankenPHP 检查属于 symfony-yoandev-local-dev;CI 流水线属于 symfony-yoandev-quality;应用输出的日志通道、级别、脱敏、告警与健康检查属于 symfony-yoandev-observability。本技能负责生产环境的 handler 配置,后者负责写入其中的一切内容。

为什么使用 dunglas/symfony-docker

由 FrankenPHP 作者维护,跟随 Symfony recipes。手写 Dockerfile 虽然更快到达生产,但通常会缺少:

  • 多阶段构建:最终镜像为 debian:13-slim 加二进制与 /app,没有 Composer、git 和构建工具链可供攻击。
  • 非 root 用户:以 www-data 运行,去除 setuid/setgid 位,var/ 属组为 0 且 g=u,也可运行在 OpenShift 等任意 UID 平台。
  • 健康检查:探测 Caddy 在 :2019 的 metrics 端点,使编排器能区分“容器已启动”与“应用可应答”。
  • Worker 模式:Caddyfile 中声明 worker { file ./public/index.php },内核只启动一次并复用,这是 FrankenPHP 速度的主要来源。
  • TLS:通过 SERVER_NAME 自动获取 Let's Encrypt 证书,支持 HTTP/2 与 HTTP/3。AssetMapper 需要 HTTP/2 才不会比打包更慢。
  • ###> recipes ### 标记:Flex 会写入 Dockerfile 本身,安装需要 PHP 扩展的 bundle 时会自动更新镜像。

发布顺序

每一步都有其职责:

  • composer install --no-dev --optimize-autoloader:安装生产依赖,使用类映射而非文件系统探测。跳过会导致生产环境出现开发 bundle,且每次类加载都访问磁盘。
  • composer dump-env prod:把 .env 文件编译为 .env.local.php。跳过会导致每个请求都解析 .env。
  • cache:clear 或 cache:warmup:构建容器、路由、Twig、验证器元数据以及 .preload.php 文件。跳过会导致每个 worker 首个请求承担完整构建,opcache.preload 静默失效。
  • asset-map:compile:在 public/assets/ 下写入带哈希的资源文件。跳过会导致每个资源在每次请求时由 PHP 运行时生成。
  • doctrine:migrations:migrate --no-interaction:使 schema 与代码期望一致。跳过会导致新代码查询不存在的列。
  • messenger:stop-workers:要求运行中的 worker 处理完当前消息后停止。跳过会导致 worker 用旧代码针对新 schema 执行。
  • 重启 workers:让消费者在新代码上运行。跳过会导致队列静默堆积。

其中两点常被写错:

  • cache:clear 除非传入 --no-warmup,否则已经会预热,并运行产生 preload 文件的可选 warmer。同时运行 cache:clear 与 cache:warmup 会预热两次。在干净检出的镜像中无内容可清理,单独 cache:warmup 更合适。
  • messenger:stop-workers 不向进程发信号,而是向 cache.app 池写入时间戳,worker 在消息之间读取并退出。因此部署任务必须写入 worker 读取的同一个池;在 Symfony < 7.4 中该池位于 var/cache/,之后的 cache:clear 会抹掉该信号。

各步骤运行位置

构建时固化进镜像:composer install、dump-env、缓存预热、asset-map:compile。它们是确定性的、无需数据库,放在这里意味着容器毫秒级启动且每个副本字节一致。

发布时每次部署执行一次,而非每个容器一次:迁移、停止 workers、重启它们。当 migrations/ 非空时,symfony-docker 的 entrypoint 会自动运行迁移,单容器方便,但扩缩容时存在竞态。

迁移自动运行及其代价

这是刻意选择,代价明确:迁移不保证与当前运行代码向后兼容。若迁移与被替换版本不兼容,短暂中断是可接受的。代价是通常存在数秒窗口,旧容器针对新 schema 运行。若要求零停机,则不适用,需要使用 expand/contract 模式:新增可空列 → 部署同时写入两者的代码 → 回填 → 设为非空 → 部署只读新列的代码 → 删除旧列,共五次部署。

生产设置

APP_ENV=prod、APP_DEBUG=0,以及 php.ini 中:

opcache.preload = /app/config/preload.php opcache.preload_user = www-data opcache.validate_timestamps = 0 realpath_cache_size = 4096K realpath_cache_ttl = 600 opcache.memory_consumption = 256 opcache.max_accelerated_files = 32531 opcache.interned_strings_buffer = 16

opcache.validate_timestamps = 0 意味着 PHP 不再重读已编译文件,新代码在被丢弃前不可见。替换容器可免费完成此事。opcache.preload 需要 config/preload.php,它要求已预热 prod 缓存,这是预热属于构建的第二个原因。

环境变量与密钥

composer dump-env prod 把 .env 文件编译为 .env.local.php,真实环境变量优先级更高。密钥来自平台密钥存储,以环境变量注入。此方案不叠加 Symfony secrets vault,原因是其解密密钥本身会是平台密钥。当缺少平台存储或需要密钥入库版本化时,可整体采用 vault。绝不接受生产主机上的 .env.local。

验证部署

四条命令:

php bin/console about php bin/console doctrine:migrations:up-to-date php bin/console lint:container php bin/console messenger:stats

随后发起一次真实请求,检查状态码、资源是否来自 /assets/…-<hash>.css 而非 PHP,以及错误日志是否安静。

回滚

回滚成立需两个条件:镜像 tags 不可变(部署 app:a1b2c3d 而非 app:latest);本次发布的迁移未破坏数据。带有破坏性迁移的发布没有回滚,应在部署前说明。若某次发布必须可逆且要删除内容,则该次变更拆分为:本次停止写入该列,后续再删除。

导致回滚不可能的原因:可变 tag、从未运行过的 down() 方法、在生产主机上构建镜像导致没有可返回的旧产物。

生产异常排查

  • 空白页、500、日志无内容:APP_DEBUG=0 隐藏错误但日志 handler 配置错误,检查日志是否输出到 stderr。
  • CSS 与 JS 404 或缓慢:构建中未运行 asset-map:compile。
  • 新代码已部署但行为未变:opcache.validate_timestamps=0 且进程未被替换。
  • Workers 用旧代码处理消息:跳过 messenger:stop-workers,或信号写入了 worker 不读取的池。
  • 重启信号被忽略:cache:clear 在 messenger:stop-workers 之后运行并删除信号(Symfony < 7.4)。
  • 无法写入缓存目录:var/ 不可写,或镜像以不拥有它的用户运行。
  • 一个副本正常另一个失败:状态写入本地文件系统,如会话、上传、cache.app 池。
  • 迁移挂起部署:多个副本同时运行 doctrine:migrations:migrate 并在锁上阻塞。
  • 服务器上设置了环境变量却被忽略:它为 shell 设置,而非 supervisor 启动的进程。

参考文件

  • references/deploy-sequence.md:编写或修复部署脚本、发布时迁移、workers、停止信号、supervisor 与 systemd、验证清单、回滚。
  • references/production-config.md:php.ini 与 OPcache、预加载、容器与区域设置、环境变量与密钥、文件系统与日志、FrankenPHP worker 模式。
  • assets/README.md:复制 worker unit 文件前阅读,两者都需要项目特定值。