部署应用#
所有部署都经过 Luma Control:CLI 发送 manifest,Control 渲染 Nomad job、更新 DNS 和路由、提交 job,并把进度实时返回。
luma deploy app.yaml -> Luma Control -> Nomad job -> Nomad client -> Docker container
选择部署方式#
| 你手上有 | 命令 | 过程 |
|---|---|---|
| 已发布的容器镜像 | luma deploy app.yaml |
部署 manifest 中写明的镜像 |
| 本地代码目录 | luma build local . |
用本机 Docker Buildx 构建,推送到集群镜像仓库,然后部署 |
| GitHub 或 Gitea 仓库 | luma import owner/repo |
由构建节点克隆、构建、推送并部署 |
docker-compose.yml |
luma compose deploy luma.compose.yml |
把所有服务一起部署到同一个节点 |
luma deploy 从不构建源码。构建需要构建节点和镜像仓库,见构建。
部署镜像#
用 luma init 生成 manifest,或手写一份:
name: api
image: ghcr.io/acme/api:1.4.2
region: cn
exposure: cn-edge
domain: api.example.com
port: 3000
replicas: 2
resources:
limits:
memory: 512M
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3000/health"]
然后:
luma validate api.yaml # schema, region/exposure rules; no network
luma deploy api.yaml --dry-run # print the Nomad job Control will submit
luma deploy api.yaml
部署是 upsert:用同一个 name 重新部署会更新已有的 Nomad job,Nomad 会保留上一个版本用于回滚。生产环境请使用不可变的镜像 tag 或 digest;latest 这类可变 tag 会让回滚结果不可预测。
Control 按以下步骤执行并逐步输出:
- 保存 manifest 和作用域密钥;
- 渲染 Nomad job;
tailscale-relay和tcp-relay还会渲染 Traefik 路由文件; - 创建或更新 Cloudflare DNS(
--skip-dns跳过); - 提交 job 并等待滚动发布完成;
- 探测
cn-edge和external-edge服务的公网路由;如果 Traefik 尚未识别,会重建一次 allocation。
默认等待 3000 秒(--timeout)。如果某一步在前面步骤已经修改集群后失败,部署会记录为 failed_partial,之后仍可用 luma app remove 清理。
所有字段见 部署 YAML 参考;调度与入口的含义见核心概念和暴露模型。
密钥#
在 manifest 中引用变量,不要把值写进 Git:
env:
DATABASE_URL: ${DATABASE_URL}
部署时从 .env 提供,或提前保存:
luma deploy api.yaml --env .env # only referenced variables are sent
luma secret set DATABASE_URL --scope api # prompts for the value
luma secret import .env.production --scope api # every variable in the file
密钥按应用名隔离,api/DATABASE_URL 和 worker/DATABASE_URL 互不影响。私有镜像的仓库凭据用 luma registry login 设置。详见密钥与凭据。
构建#
源码构建需要一个装有 Docker Buildx 的构建节点(Linux,或使用 OrbStack / Docker Desktop 的 macOS)和一个集群能拉取的镜像仓库:
luma registry serve --node builder # deploy the managed registry on a node
luma build config --node builder --default-node builder
Luma 会根据目标区域的节点决定镜像架构:ARM 节点得到 linux/arm64,混合架构的区域得到多平台镜像。只有覆盖全部目标架构时才接受 --platform。
原生架构构建节点#
先升级 Control 和构建节点 agent,再同时声明 AMD64 与 ARM64 构建节点:
luma build config --node builder --node air3 --default-node builder \
--registry-host 100.64.0.70:5000 --push-host 100.64.0.70:5000
所有仓库构建节点必须上报 native-arm-build-v1 能力,自动选择还需要 repository-build-plan-v1。先升级 Control 和全部构建 agent。macOS 的 OrbStack 和 Docker Desktop 均受支持;luma status 会显示原生容器平台,macOS ARM 节点对应 linux/arm64。
当声明的构建节点具有不同架构时,Control 会先解析仓库 manifest(包括指定的 Compose sidecar 和服务级部署位置),锁定 Git commit,再选择原生构建节点。只要包含 ARM64,即使同时构建 AMD64,也必须使用原生 ARM64 builder。原生节点忙碌或离线时继续排队;没有对应 ARM 原生节点则报错,不能回退到模拟构建。运行时密钥只在部署阶段使用。
显式 --build-node 只能固定兼容节点,不能绕过 ARM 要求。每次 ARM 构建前都会核实所有 Buildx worker 的实际 Docker 引擎架构,不能仅依据包含 QEMU 模拟架构的支持列表。该校验覆盖仓库导入、Compose、重试和 luma build local,也包括自定义 Buildx builder。本地旧 CLI 需要先升级;无法验证原生架构的 remote/Kubernetes Buildx driver 不允许执行 ARM 构建。
混合 AMD64/ARM64 请求整体交给 ARM builder,其中 AMD64 部分可以使用模拟执行,不拆分到多台机器。不同 ARM 架构(例如 ARMv7 与 ARM64)需要分开提交。不含 ARM 的构建保留原有回退行为。pushHost 必须能被所有 builder 访问,不能使用指向各自机器的 localhost。
构建本地代码#
cd app
luma build local . --env .env
项目身份取自 origin 远程地址(没有时用 --repo-url)。Luma 用本机 Docker Buildx 构建,按与 luma import 相同的仓库路径推送到集群镜像仓库,然后部署。本机必须能访问该镜像仓库。可选参数:--platform、--context、--dockerfile、--compose-sidecar、--builder(复用已有 Buildx builder)和 --proxy(基础镜像和 RUN 步骤使用的 HTTP 代理)。
导入仓库#
luma import acme/app --ref main --env .env # GitHub owner/repo
luma import https://gitea.example.com/acme/app.git --ref v1.4.0
私有仓库先保存一次令牌,之后引用它:
printf '%s' "$GITHUB_TOKEN" | luma git-provider set github personal --username octo --token-stdin
luma git-provider repos github:personal
luma import --provider-id github:personal --repository acme/app --ref main
导入会查找单服务 manifest(.luma.yml、luma.yml、*.luma.yml)或 Compose sidecar(luma.compose.yml、.luma.compose.yml、*.luma.compose.yml、docker-compose.luma.yml)。可以用 --compose-sidecar 仓库内路径 明确指定;仓库里没有部署文件时用 --manifest 本地文件。对 Compose 仓库,带 build: 的服务会被构建并改写为推送后的 image:。本地用 luma compose validate --import-mode luma.compose.yml 检查这一路径。
Dockerfile 阶段与固定 Git 输入#
build.target 选择一个字面量 Dockerfile 阶段名。远程仓库构建还可以把其他仓库作为具名源码上下文:
# docker-compose.yml; the same build fields work in a single-service manifest
services:
worker:
build:
context: .
dockerfile: Dockerfile
target: release
x-luma-git-contexts:
library:
lock: deploy/library.lock.json
lock 是相对主仓库根目录的文件路径,不是内联对象,也不相对 build.context。该 JSON 文件必须且只能包含 repository、commit、archive_sha256:
{
"repository": "https://gitea.example.com/acme/library.git",
"commit": "0123456789abcdef0123456789abcdef01234567",
"archive_sha256": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
将示例摘要替换为完整小写 commit 和 git archive --format=tar.gz <commit> 的 SHA256。Builder 在校验两者后才把导出的文件作为 BuildKit 具名上下文 library,Dockerfile 可用 COPY --from=library 引用。Git 托管平台的 archive 接口可能产生不同字节,不能代替该归档。
额外仓库必须与主仓库具有相同 HTTPS origin(主机和端口),URL 不得包含凭据、query 或 fragment。读取使用主仓库选定的 Git provider 凭据,该凭据必须能访问每个锁定仓库。拉取和校验都发生在 Docker 之外;进入 BuildKit 的只有已验证源码,不包含 Git 凭据、凭据辅助文件或 .git 元数据。
导入时显式要求协议能力:
luma import --provider-id gitea:team --repository acme/app --ref "$APP_COMMIT" \
--compose-sidecar deploy/luma.compose.yml \
--require-build-capability repository-build-inputs-v1
CLI 必须支持该参数,Control 必须声明 required-build-capability-v1,选中的就绪 Builder 必须声明 repository-build-inputs-v1。Control 在执行时再次检查实时 Builder,并在部署前要求构建结果确认能力;重试保留要求。缺能力会明确停止,不会静默忽略额外输入。源码实现不代表已发布或线上已加载:使用前需要发布相应 Luma 版本并升级 Control 与 Builder。
外部 --manifest 会自动要求同一能力,无需重复传该参数。仅当仓库没有清单时,Builder 才使用外部清单中通过校验的构建和调度字段;仓库自带清单在构建与部署阶段都优先。外部清单的运行环境和密钥不会作为构建输入传递。使用新版 CLI 的外部清单入口前,必须升级 Control 与 Builder。
luma build local 不支持 x-luma-git-contexts,包括 Compose 构建。额外 Git 输入必须走远程 luma import,不能把 provider 凭据塞进 Dockerfile 绕过。仅使用 build.target 不代表必须有额外 Git 输入。
构建记录与队列#
导入和重试会进入按仓库、ref 和部署目标划分的持久队列:同一目标的新提交会取消更早的排队请求,而 main 和 dev 互不阻塞。忙碌的构建节点只会让自己的请求排队,不会影响其他构建节点。
luma build list --app api --status failed
luma build logs <build-id>
luma build retry <build-id>
luma build cancel <build-id>
构建被接受后关闭 CLI 不会取消构建。排队中的请求在 Control 重启后保留;被中断的进行中构建会标记为失败,而不是自动重放。
Compose 应用#
保持标准的 docker-compose.yml,再加一个说明部署位置和方式的 Luma sidecar:
luma compose init --compose docker-compose.yml --output luma.compose.yml
luma compose validate luma.compose.yml
luma compose deploy luma.compose.yml --dry-run
luma compose deploy luma.compose.yml --env .env
同一个应用的所有服务运行在同一个节点上,共享网络命名空间。持久卷默认使用该节点的本地存储。Compose 不支持 cloudflare-tunnel。详见 Compose 与存储。
部署方式记录#
Control 会记住每个应用上一次成功的构建部署方式(本地构建、导入、镜像部署或 Compose,以及 ref、构建节点、平台、区域等参数)。之后的 deploy、import、build local、build retry 或 compose deploy 会与记录对比:
- 没有记录或完全一致:继续执行并刷新记录;
- 存在差异:显示差异并停下等待确认。终端中会直接询问;JSON、quiet 或非交互模式会以非零状态退出。确认差异后才用
--accept-workflow-change重试。
luma workflow show api
luma workflow record api --note "Built on the builder node" -- import acme/api --ref main
luma workflow run api --path ~/src/api
记录中不会包含令牌或 .env 内容。备注是自由文本,不要写入密钥。
CI#
CI 可以在不保存登录的情况下使用 CLI:
python -m pip install "luma-infra==0.2.14"
export LUMA_CONTROL_URL=https://luma.example.com
export LUMA_DEPLOY_TOKEN="$CI_LUMA_MANAGEMENT_TOKEN"
luma validate deploy/app.yaml --format json
luma deploy deploy/app.yaml --dry-run --format json
luma deploy deploy/app.yaml --format ndjson --timeout 3000
连接参数的优先级为:命令行参数 > 环境变量(LUMA_CONTROL_URL、LUMA_DEPLOY_TOKEN、LUMA_CONTROL_CONTEXT、LUMA_INSECURE、LUMA_RESOLVE_IP)> 已保存的登录。与已保存登录不同的 --control-url 不会借用其令牌。支持 --format 的命令可输出 text、json 和 ndjson,请同时检查退出状态。
在工作站上,可以把多个集群保存为登录,并按命令选择:
luma login https://staging.example.com --token-stdin < staging.token
luma context list
luma deploy app.yaml --control-context staging