23: 部署与持续集成/持续交付

一旦构建完成,Phlo 应用就是静态的。build::release 将你的 .phlo 源代码转译为普通的 PHP、CSS 和 JS,存放在 release/ 目录下(请参见工具章节);生产环境直接提供这些输出,没有转译器,也没有构建步骤在请求路径中。部署是将一个 经过测试release/ 放到服务器上并安全切换的过程。

本章描述了你可以在任何 CI 提供商上构建的管道。形状始终相同:一次推送构建并测试一个发布;一个人进行批准;经过测试的工件被发送;服务器切换到它并在健康检查失败时回滚。 机制是自动化的,发布的决定由人来做。

23.1: The release build

The unit you deploy is the release/ tree, produced by:

php www/app.php build::release

It runs the release hooks, transpiles every .phlo to release/*.php, and writes the asset bundles to release/www/. Production's webroot points at release/www/ and the entrypoint runs with build: false and debug: false, so no build:: tooling and no control UI ship (see the Runtime config chapter).

The output is deterministic given the same sources and the same engine, so the release you test is the release you ship. Choose the engine policy deliberately. An independent or third-party app should normally pin a tag or exact commit for reproducible builds. A coordinated first-party fleet may track the current engine so all apps grow with Phlo, provided every engine change rebuilds and tests every app and the artifact records the resolved engine commit.

23.2: Continuous integration

Run CI on every push and pull request. A Phlo CI job does four things:

  1. Check out your app and the Phlo engine according to the chosen policy, and record both resolved commits.
  2. Install dependencies.
  3. build::release, then lint the generated output: php -l over release/*.php and node --check over the JS bundles. A release that does not lint never leaves CI.
  4. Run the test suite (phpunit), starting any service the tests need, such as a database.

On success, upload the release/ tree as a build artifact keyed by the commit. That artifact, not a fresh build on the server, is what deployment ships. Building once and shipping the exact bytes you tested takes "works in CI, breaks on the box" off the table.

# Sketch of the CI job. Adapt the syntax to your provider.
on: [push, pull_request]
jobs:
  build:
    steps:
      - checkout app and engine (pinned, or tracked and tested as one fleet)
      - install dependencies
      - run: php www/app.php build::release
      - run: for f in release/*.php; do php -l "$f"; done
      - run: for f in release/www/*.js;  do node --check "$f"; done
      - run: vendor/bin/phpunit
      - upload artifact: release/  (named after the commit)

23.3: 部署门

部署是一个单独的、手动触发的任务(在 GitHub Actions 上是 workflow_dispatch,或您提供者的等效功能)。手动触发 就是 审批步骤:CI 证明一个提交是可发布的,某个人决定何时上线。

部署任务不会重新构建。它会解析您发布分支上的成功 CI 运行(最新的,或您传入的运行 ID),验证它确实是该分支上的绿色运行,下载其发布工件,并进行发布。将构建和部署分开可以让您重新部署一个已知良好的工件,或向前滚动到一个特定的经过测试的提交,而不会因为重新构建而引入意外情况。

23.4: 在服务器上安全部署

通过 SSH 将工件传输到服务器上的单个受限命令;绝不要将 CI 交给一个 shell。一个最小的安全部署接收器:

  1. 将传入的 release/ 解压到一个暂存目录。
  2. 为回滚快照当前的实时发布(一个 tarball)。
  3. 原子性地交换新发布:在实时树旁边写入,然后移动,以便请求永远不会看到半更新的应用。
  4. 对新发布运行健康检查,一个必须返回 200 的真实请求。
  5. 在健康检查失败时,自动恢复快照并以非零状态退出。

由于交换是原子性的,回滚是自动的,因此一个错误的部署会自我修复到最后一个良好的发布,而不是让网站处于停机状态。在交换之前,将数据库迁移作为一个明确的、有序的步骤运行,并确保在切换前的最后时刻旧代码仍然可以服务。

23.5: Recording what is live

CI writes a small build.json into the release artifact, recording the app commit, resolved engine commit and build timestamp. The receiver verifies it and copies it to data/build.json when that artifact goes live:

{ "commit": "<app-sha>", "engine": "<engine-sha>", "built": "<timestamp>" }

This is the source of truth for what is actually running, distinct from the app's own version prop. A dashboard or status page reads it to show a "deployed build" badge, so you can see which commit a server is on at a glance, without logging in.

23.6: 自动化机制,而不是决策

以上所有内容自动化了安全的部分:构建、测试、发送相同的字节、原子交换、健康检查、回滚。它故意留给人类的选择是发布。推送并不会立即生效;当更改准备好后,某人会触发部署,阅读差异并了解其他正在进行的内容。

这种分离正是关键。发布是一个有意识的、内容感知的行为:架构更改、首次运行迁移、robots 规则、资产缓存破坏者都是判断决策,而不是 git push 应该自行执行的步骤。自动化重复的、易出错的机制,使其变得无聊且可逆,并将时机和判断留给人类。

最近更新于 2026年8月7日

我们使用必要的cookie来使该网站正常工作。在您的许可下,我们还使用分析工具来改善网站。