推送与部署(普通应用)

普通应用 = 你把镜像推到平台分配的私有仓库,平台轮询到新镜像后自动部署。 本页讲从 nae create 到 running 的完整闭环、镜像必须满足的运行时约束、以及怎么排查部署失败。 (模板应用走另一条路:一次性部署、镜像 pin 在模板版本,见模板。)

一条龙

# 1) 建应用:拿到推送地址,此时状态 awaiting_image(等首次推镜像)
nae create --app-id myweb --tag latest --port 8080 --cpu 500m --mem 512Mi
nae app myweb            # 看 PushCN / PushGlobal(推送地址)与 Route(访问地址)

# 2) 构建并推送镜像到上一步拿到的地址(无需 docker login,见下)
docker build --platform linux/amd64 -t <PushCN 或 PushGlobal>:latest .
docker push <PushCN 或 PushGlobal>:latest

# 3) 平台轮询检测到 latest 标签的新 digest → 自动部署 → running。无需手动「deploy」
nae app myweb            # 等状态变 running
nae events myweb         # 看 image_detected → deploy_started → running 时间线

没有 Docker?用 nae build 让平台代构建(备选)

推荐:本机有 Docker 就用上面的本地构建 + 推送。 本地构建全量复用你机器上的层缓存、 不占上传带宽、不受上下文 200MB 上限与服务端排队影响,构建出问题也最好排查。 线上代构建(nae build / 网页上传构建)仅作本机没有 Docker 时的备选,不建议当作主力发布方式。

本机没装 Docker、或不方便本地构建,就把带 Dockerfile 的项目目录直接交给平台——平台替你 docker build + docker push,推完照样触发上面的自动部署闭环。

# 在项目根目录(有 Dockerfile 的地方)
nae build myweb                 # 打包当前目录 → 上传 → 排队构建 → 跟随日志直至结束
nae build myweb ./svc --tag v2  # 指定上下文目录与镜像 tag
nae build myweb --dockerfile docker/Dockerfile.prod   # Dockerfile 不在根/不叫 Dockerfile
nae build myweb --relay         # 海外/跨境网络直传不稳:直接经平台中转上传

要点:

查状态与日志(nae build 默认已跟随;断线或用了 --no-follow 时再看):

nae builds myweb                # 该应用最近构建任务(状态 / 排队位置)
nae build-logs <job> -f         # 跟随某任务日志直到结束(游标续读,断线不丢)
nae build-cancel <job>          # 取消排队中的任务(仅 queued 可取消,已开始的不能中断)

网页端「应用详情 → 构建」Tab 同样能提交构建:点「上传构建」选中项目文件夹,浏览器就地打成 构建上下文直传,效果与 nae build 一致;也能看历史/排队位置/日志、取消排队中的任务。

推送镜像

镜像运行时约束(重要)

平台以非 root 用户运行容器,且容器无新增特权能力。镜像必须满足:

  1. 能以非 root 用户启动——启动时不要 chown、不要写系统目录(/etc、/var/cache 等)。 缓存/临时文件请写 /tmp 或挂载的可写卷。
  2. 监听端口 ≥ 1024——非 root 不能绑定 1024 以下端口(如 80/443)。让程序监听如 8080, 并把它填进 --port。

不满足会在部署阶段崩溃。典型坑:官方 nginx:alpine 启动即 chown("/var/cache/nginx/...") failed (Operation not permitted),且默认监听 80。 换 rootless 基底并改监听端口即可。

已验证可用的 nginx 静态站点最小示例:

# 用 unprivileged 基底,不 chown、不抢低端口
FROM nginxinc/nginx-unprivileged:alpine
COPY dist/ /usr/share/nginx/html/
# 该镜像默认就监听 8080、以非 root 跑,无需再改
nae create --app-id mysite --tag latest --port 8080

部署状态怎么看

应用整体状态 nae app <id> 的 Status 只有这几种:

Status 含义
awaiting_image 普通应用已建、等首次推镜像
running 在跑
stopped 已停止(缩到 0 副本)
deleting 删除中(模板级联清理)

「正在部署 / 部署失败」不是应用状态,而是某个镜像版本的状态——见 nae versions <id> 里每条版本的 Status:deploying / succeeded / failed。最新版本 failed 就是这次部署没起来。

排查部署失败:

nae versions myweb       # 哪个版本 failed
nae events myweb         # 时间线:image_detected / deploy_started / deploy_failed
nae logs myweb           # 容器日志(崩溃原因,如上面的 chown 报错)

前端 SPA 注意事项(路径前缀)

普通应用默认剥离 /apps/<appid> 前缀:外部请求 …/apps/myweb/api/health 到容器里是 /api/health。 这对「在根路径服务」的程序刚好。但前端 SPA 用绝对路径引用静态资源会 404:

若你的框架自带 basePath(如 Next.js Node server 已配 /apps/myweb),则需要保留前缀: 创建时加 --keep-path-prefix,让平台不剥离。

# 自带 basePath 的框架:保留前缀
nae create --app-id mynext --tag latest --port 3000 --keep-path-prefix
# 建好后想切换前缀策略,不必删库重建:
nae update mynext --strip-path-prefix     # 改回剥离
nae update mynext --keep-path-prefix      # 改成保留

长连接(WebSocket / SSE)

普通应用的 WebSocket 与 SSE 走和 HTTP 同一个路由地址,无需额外配置;路径前缀规则也一样(默认剥离 /apps/<appid>)。

// 浏览器端:与页面同协议,https 页面用 wss://
const ws = new WebSocket(`wss://${location.host}/apps/myapp/ws`)

务必做到三件事,否则连接会「莫名其妙」断开:

  1. 心跳:长时间无数据的空闲连接会被中途断开。WebSocket 每 ≤30 秒发一次 ping(或应用层心跳消息); SSE 每 ≤30 秒写一行注释 `: ping

。 2. **断线重连**:发布新版本、重启、停止应用都会断开现有连接。客户端须自动重连(建议指数退避, 首次 1 秒、上限 30 秒),并在重连后恢复订阅状态。浏览器EventSource自带重连,WebSocket 要自己写。 3. **SSE 响应头**:Content-Type: text/event-stream+Cache-Control: no-cache。平台不会压缩或缓冲 text/event-stream` 响应,事件可实时到达;别用其它 Content-Type 发 SSE。

访问统计口径:应用详情「趋势」里,WebSocket 连接在断开时计入「WebSocket 会话」与「平均会话时长」; 连接内收发的消息不计入出入口流量,也不提供实时在线连接数——需要在线人数请在应用内自行统计。

模板应用(NodePort)与「仅内部访问」的应用不经平台路由,不受上述路由行为影响,也不计入访问统计。

让普通应用连上模板应用

模板应用(PostgreSQL/Redis 等)create 返回的 Route 就是集群内 DNS 主机名,形如 app-<id>-<appid>.<namespace>.svc.cluster.local:<port>。把它填进业务应用的 env 即可连上:

# 1) 建模板,记下各自的 Route 主机名
nae create --kind template --app-id mypg --template postgres --storage 5Gi \
  --config '{"database":"appdb","username":"appuser","password":"<pg密码>"}'
nae create --kind template --app-id myredis --template redis --storage 2Gi \
  --config '{"password":"<redis密码>"}'

# 2) 业务应用把模板的 Route 主机名 + 端口填进 env(用 --env-file 避免 shell 引号问题)
cat > env.json <<'EOF'
{
  "PGHOST": "app-<id>-mypg.<ns>.svc.cluster.local",
  "PGPORT": "5432",
  "PGUSER": "appuser",
  "PGPASSWORD": "<pg密码>",
  "PGDATABASE": "appdb",
  "REDIS_HOST": "app-<id>-myredis.<ns>.svc.cluster.local",
  "REDIS_PORT": "6379"
}
EOF
nae create --app-id myapi --tag latest --port 8080 --env-file env.json

<id>/<ns> 以 nae app mypg 返回的 Route 为准,直接抄那个主机名即可。 环境变量事后要改用 nae update myapi --env-file env.json(见应用管理),不必删库重建。