LHIDC

为大语言模型推理服务器建立自动化运维,如何实现可重复部署与失败回滚

面向大语言模型推理服务器运维人员,介绍如何固化镜像、模型、配置与启动参数,通过健康检查、业务探测、蓝绿切换、定时巡检和审计记录实现可重复部署,并提供失败回滚、故障排查及修复后验证方法。

为大语言模型推理服务器建立自动化运维,如何实现可重复部署与失败回滚

假设一个典型现场:自动发布任务显示“成功”,容器也处于运行状态,但大语言模型推理服务器开始返回 502,或者接口能够连接却迟迟无法完成推理。值班人员准备回退时才发现,旧容器使用的是 latest 镜像,启动参数没有留档,当前配置又被脚本原地覆盖,所谓“回滚”只能依靠人工回忆。

这类问题应按“入口状态、当前发布版本、配置渲染结果、容器与模型日志、GPU或系统资源、业务级推理验证”的顺序检查。要让部署可重复、失败后可回滚,关键不是增加一条自动重启命令,而是把镜像、模型、配置和启动方式固化为不可变发布单元,在切换流量前完成健康检查,并保留上一份已知正常版本及完整审计记录。

先区分进程存活和推理可用

推理服务的启动过程通常包含多个阶段:

  1. 容器进程启动。
  2. 推理引擎读取配置。
  3. 模型文件加载或映射到内存、显存。
  4. 分词器、并行策略和缓存初始化。
  5. HTTP接口开始监听。
  6. 服务完成预热并能够返回有效推理结果。

因此,docker ps 显示 Up,只能证明容器主进程还没有退出。即使 /health 返回 200,也不一定代表模型已经加载完成。自动化发布至少应设置三道门:

  • 配置门:Compose、环境变量和代理配置能够通过语法检查。
  • 就绪门:模型加载完成,推理接口已进入可服务状态。
  • 业务门:发送一条低成本、固定格式的探测请求,响应结构符合预期。

业务门需要按照实际推理框架的API定义实现,不能把某个框架的健康路径直接套用到所有服务上。部署前应先确认健康接口表示的是“进程存活”还是“模型就绪”。

把一次发布固化为不可变单元

可重复部署并不等于重复执行同一段Shell命令。更可靠的定义是:在前置环境一致的服务器上,使用相同发布清单,能够获得相同的镜像、模型版本、运行参数和配置内容。

建议每个发布单元至少记录以下信息:

项目 推荐记录方式 回滚时的作用
推理镜像 使用镜像摘要,如 @sha256:... 避免标签指向发生变化
模型版本 模型目录版本、制品摘要或仓库修订号 防止代码回退但模型未回退
运行配置 Git提交号和配置文件SHA-256 判断配置是否被人工修改
启动方式 固化的Compose文件或systemd单元 避免参数依赖命令历史
监听端口 写入发布清单 支持蓝绿切换
健康路径 写入发布清单并随版本管理 防止探测接口不匹配
发布时间与操作者 结构化审计日志 还原故障时间线

目录可以按发布版本组织:

/opt/llm-inference/
├── releases/
│   ├── 20260910-001/
│   │   ├── compose.yaml
│   │   └── manifest.env
│   └── 20260910-002/
│       ├── compose.yaml
│       └── manifest.env
└── state/
    ├── current
    └── previous

currentprevious 文件只保存发布编号,不保存完整配置。发布目录不应在上线后原地修改;需要调整参数时,应生成新的发布编号。

以下示例适用于安装了 Docker Engine、Docker Compose v2 和 systemd 的 Ubuntu 22.04/24.04。其他发行版的服务名、Nginx路径和包管理方式可能不同,应先核对环境:

docker version
docker compose version
systemctl --version
nginx -v
nvidia-smi

如果服务器使用CPU推理、其他GPU运行时或非NVIDIA设备,应删除或调整设备预留配置,不要直接照搬。

一个基础Compose文件可以这样组织:

services:
  inference:
    image: "${INFERENCE_IMAGE}"
    restart: unless-stopped
    env_file:
      - /etc/llm-inference/runtime.env
      - /etc/llm-inference/secret.env
    ports:
      - "127.0.0.1:${HOST_PORT}:${CONTAINER_PORT}"
    volumes:
      - "${MODEL_ROOT}:/models:ro"
    stop_grace_period: 120s
    deploy:
      resources:
        reservations:
          devices:
            - driver: nvidia
              count: all
              capabilities:
                - gpu

对应的发布清单使用确定值,而不是 latest

INFERENCE_IMAGE=registry.example.invalid/inference@sha256:<已核验的镜像摘要>
MODEL_ROOT=/srv/models/<已核验的模型版本>
HOST_PORT=18081
CONTAINER_PORT=8000
HEALTH_PATH=/health
CONFIG_SHA256=<运行配置的SHA-256>
MODEL_REVISION=<模型修订号>

示例中的仓库、摘要、模型目录、端口和健康路径都必须替换成实际值。manifest.env 会被部署脚本读取,只能由受信任的发布流程生成,并应限制普通用户的写权限。

失败回滚应发生在流量切换边界

比较稳妥的部署方式是蓝绿发布:旧版本继续占用原端口,新版本使用另一个本地端口启动。新版本通过检查后,Nginx再把流量切过去。

Nginx上游文件可以保持简单:

upstream llm_backend {
    server 127.0.0.1:18080;
    keepalive 32;
}

其他站点配置通过 proxy_pass http://llm_backend; 引用该上游。自动化脚本只修改受控的上游文件,不直接重写整个Nginx配置。

一次部署应依次执行:

  1. 获取发布锁,防止两个任务同时运行。
  2. 检查发布编号、文件权限和清单内容。
  3. 执行 docker compose config -q
  4. 拉取指定摘要的镜像。
  5. 检查模型目录是否存在并且可读。
  6. 检查候选端口是否被占用。
  7. 启动候选版本。
  8. 等待就绪检查和业务探测通过。
  9. 备份当前Nginx上游文件。
  10. 写入新上游,执行 nginx -t
  11. 重载Nginx并从代理入口再次验证。
  12. 成功后更新 currentprevious
  13. 记录镜像摘要、配置摘要、操作者和结果。

下面是切换逻辑的核心示例。执行前应确认路径、服务名和健康接口;脚本会覆盖指定的Nginx上游文件,因此必须先保留已知正常配置。

#!/usr/bin/env bash
set -Eeuo pipefail

if [[ "${EUID}" -ne 0 ]]; then
  echo "请以root运行,以便管理Docker、Nginx和状态文件" >&2
  exit 1
fi

RELEASE_ID="${1:-}"
[[ "${RELEASE_ID}" =~ ^[A-Za-z0-9._-]+$ ]] || {
  echo "发布编号格式不合法" >&2
  exit 1
}

BASE=/opt/llm-inference
RELEASE_DIR="${BASE}/releases/${RELEASE_ID}"
MANIFEST="${RELEASE_DIR}/manifest.env"
COMPOSE="${RELEASE_DIR}/compose.yaml"
STATE_DIR="${BASE}/state"
UPSTREAM=/etc/nginx/conf.d/llm-upstream.conf

exec 9>/run/lock/llm-deploy.lock
flock -n 9 || {
  echo "已有部署任务正在运行" >&2
  exit 1
}

[[ -f "${MANIFEST}" && -f "${COMPOSE}" ]] || {
  echo "发布文件不完整" >&2
  exit 1
}

# manifest.env必须来自受控目录,不能接受用户临时上传的任意内容。
set -a
source "${MANIFEST}"
set +a

[[ "${HOST_PORT}" =~ ^[0-9]+$ ]] || {
  echo "HOST_PORT不是有效数字" >&2
  exit 1
}

docker compose --env-file "${MANIFEST}" -f "${COMPOSE}" config -q
docker compose --env-file "${MANIFEST}" -f "${COMPOSE}" pull

if ss -lnt | awk '{print $4}' | grep -Eq ":${HOST_PORT}$"; then
  echo "候选端口 ${HOST_PORT} 已被占用" >&2
  exit 1
fi

PROJECT="llm-${RELEASE_ID}"
docker compose -p "${PROJECT}" \
  --env-file "${MANIFEST}" \
  -f "${COMPOSE}" up -d

READY=0
for _ in $(seq 1 60); do
  if curl --fail --silent --show-error \
    --max-time 3 \
    "http://127.0.0.1:${HOST_PORT}${HEALTH_PATH}" >/dev/null; then
    READY=1
    break
  fi
  sleep 5
done

if [[ "${READY}" -ne 1 ]]; then
  logger -t llm-deploy "release=${RELEASE_ID} status=readiness_failed"
  echo "候选版本未通过就绪检查,现有流量未切换" >&2
  exit 1
fi

BACKUP="$(mktemp)"
cp -a "${UPSTREAM}" "${BACKUP}"

NEW_UPSTREAM="$(mktemp)"
cat >"${NEW_UPSTREAM}" <<EOF
upstream llm_backend {
    server 127.0.0.1:${HOST_PORT};
    keepalive 32;
}
EOF

install -o root -g root -m 0644 "${NEW_UPSTREAM}" "${UPSTREAM}"

if ! nginx -t; then
  cp -a "${BACKUP}" "${UPSTREAM}"
  nginx -t
  logger -t llm-deploy "release=${RELEASE_ID} status=nginx_validation_failed"
  exit 1
fi

systemctl reload nginx

# 此地址应替换为经过Nginx代理的本地健康入口。
if ! curl --fail --silent --show-error \
  --max-time 5 "http://127.0.0.1/health" >/dev/null; then
  cp -a "${BACKUP}" "${UPSTREAM}"
  nginx -t
  systemctl reload nginx
  logger -t llm-deploy "release=${RELEASE_ID} status=post_switch_failed rollback=completed"
  exit 1
fi

mkdir -p "${STATE_DIR}"
OLD_RELEASE="$(cat "${STATE_DIR}/current" 2>/dev/null || true)"
printf '%s\n' "${OLD_RELEASE}" >"${STATE_DIR}/previous.tmp"
printf '%s\n' "${RELEASE_ID}" >"${STATE_DIR}/current.tmp"
mv "${STATE_DIR}/previous.tmp" "${STATE_DIR}/previous"
mv "${STATE_DIR}/current.tmp" "${STATE_DIR}/current"

logger -t llm-deploy \
  "release=${RELEASE_ID} previous=${OLD_RELEASE:-none} status=success"

rm -f "${BACKUP}" "${NEW_UPSTREAM}"

候选版本检查失败时,脚本不切换流量,也不默认删除容器,便于保留日志。确认不再需要现场后,才可以对明确的候选项目执行:

docker compose -p "llm-<候选发布编号>" \
  --env-file "/opt/llm-inference/releases/<候选发布编号>/manifest.env" \
  -f "/opt/llm-inference/releases/<候选发布编号>/compose.yaml" down

down 会停止并删除该Compose项目中的容器和网络。执行前必须再次核对项目名,且不要附加 -v,以免删除关联卷。

单卡资源不足时不能机械使用蓝绿发布

如果旧模型已经占满显存,新旧实例通常无法并行加载。此时蓝绿发布可能在候选阶段直接出现显存不足,自动化流程应改成“可回退的停止—启动”模式:

  1. 提前拉取镜像并完成配置检查。
  2. 停止接收新请求,等待在途请求结束。
  3. 停止旧实例,但保留旧发布目录和镜像。
  4. 启动新实例并完成就绪、推理探测。
  5. 新实例失败时立即停止候选版本,按旧清单重新启动旧实例。
  6. 旧版本恢复健康后再开放入口。

这种方式能够自动恢复,但不能承诺无中断。是否采用蓝绿发布,取决于服务器能否同时容纳两份模型及其缓存,而不是取决于脚本是否支持两个端口。

回滚也不能只做“镜像回退”。如果新版本修改了共享可写数据、缓存格式或外部数据库,旧程序可能无法读取新格式。模型目录应尽量只读挂载;需要变更共享状态时,应单独设计向后兼容和数据备份方案。

配置管理不要在变更后直接重启服务

Ansible等配置管理工具适合保证目录、权限和配置内容一致,但不应在模板发生变化后立刻触发推理服务重启。更安全的做法是:配置变更生成新发布单元,经过校验后再交给部署流程切换。

以下Ansible任务示例只负责落盘和检查,不自动重启:

- name: 创建推理服务配置目录
  ansible.builtin.file:
    path: /etc/llm-inference
    state: directory
    owner: root
    group: llmops
    mode: "0750"

- name: 渲染运行配置
  ansible.builtin.template:
    src: runtime.env.j2
    dest: /etc/llm-inference/runtime.env
    owner: root
    group: llmops
    mode: "0640"
  register: runtime_config_result

- name: 检查发布配置能否被Compose解析
  ansible.builtin.command:
    argv:
      - docker
      - compose
      - --env-file
      - /opt/llm-inference/releases/{{ release_id }}/manifest.env
      - -f
      - /opt/llm-inference/releases/{{ release_id }}/compose.yaml
      - config
      - -q
  changed_when: false

密钥、访问令牌和对象存储凭据不要写入普通Git仓库,也不要输出到Ansible日志。处理密钥的任务应使用受控密钥系统,并根据需要设置 no_log: true

定时任务负责发现问题,不负责盲目重启

定时巡检建议使用systemd timer,而不是在多个用户的crontab中分散维护。巡检默认只检查并告警,不应无限重启容器。只有同时满足“刚完成发布、上一版本已知正常、失败达到规定阈值”时,才适合触发自动回滚。

服务单元示例:

[Unit]
Description=LLM inference health watchdog
After=network-online.target

[Service]
Type=oneshot
ExecStart=/usr/local/sbin/llm-watchdog --check-only

定时器示例:

[Unit]
Description=Run LLM inference health watchdog periodically

[Timer]
OnBootSec=3min
OnUnitActiveSec=5min
RandomizedDelaySec=30s
Persistent=true

[Install]
WantedBy=timers.target

核对脚本路径和权限后,再加载定时器:

systemctl daemon-reload
systemctl enable --now llm-watchdog.timer
systemctl list-timers llm-watchdog.timer
journalctl -u llm-watchdog.service --since today

巡检至少应区分连接失败、HTTP错误、模型未就绪、响应结构异常和响应超时。连续失败次数应写入独立状态文件,成功后清零,避免单次抖动触发回滚。

出现故障时按优先级缩小范围

排查时先做只读检查,确认故障位置后再重启或回滚。

1. 检查代理入口和本地后端

curl -v --max-time 5 http://127.0.0.1/health
curl -v --max-time 5 http://127.0.0.1:<当前后端端口>/<实际健康路径>
journalctl -u nginx --since "-15 min" --no-pager
  • 代理入口失败、后端正常:重点检查Nginx上游、监听地址和重载结果。
  • 两者都失败:继续检查容器和模型加载。
  • 两者都正常但业务请求失败:进入业务级探测和请求日志检查。

2. 核对当前版本是否与发布记录一致

cat /opt/llm-inference/state/current
cat /opt/llm-inference/state/previous
docker compose -p "llm-<当前发布编号>" \
  --env-file "/opt/llm-inference/releases/<当前发布编号>/manifest.env" \
  -f "/opt/llm-inference/releases/<当前发布编号>/compose.yaml" ps

如果状态文件指向新版本,而Nginx仍指向旧端口,说明切换或状态更新中断。应先确定实际承载流量的版本,不要直接覆盖状态文件。

3. 检查配置和镜像摘要

docker compose \
  --env-file "/opt/llm-inference/releases/<发布编号>/manifest.env" \
  -f "/opt/llm-inference/releases/<发布编号>/compose.yaml" config

sha256sum /etc/llm-inference/runtime.env
docker image inspect "<带摘要的完整镜像引用>"

配置摘要不一致通常意味着存在人工修改或发布内容未同步。此时应重新生成发布单元,不建议继续修改线上文件“凑出”正确状态。

4. 检查容器退出原因和推理日志

docker compose -p "llm-<发布编号>" \
  --env-file "/opt/llm-inference/releases/<发布编号>/manifest.env" \
  -f "/opt/llm-inference/releases/<发布编号>/compose.yaml" logs \
  --tail 300 --timestamps

docker inspect "llm-<发布编号>-inference-1" \
  --format '{{json .State}}'

退出码为137可能与进程被强制终止有关,但不能只凭退出码认定为内存不足,还应查看 .State.OOMKilled、内核日志和发布过程中的停止操作。

5. 检查主机和GPU状态

nvidia-smi
journalctl -k --since "-30 min" --no-pager | grep -iE 'oom|NVRM|Xid'
journalctl -u docker --since "-30 min" --no-pager
df -h
df -i
  • 主机OOM记录:检查内存、并发数、模型加载方式和容器限制。
  • GPU错误或显存不足:确认是否同时运行了新旧模型,以及模型并行参数是否发生变化。
  • 磁盘或inode耗尽:检查镜像、日志和模型缓存,但不要在未确认用途时批量删除文件。

上线后的验证与回滚边界

完成修复或回滚后,不应只看一次健康检查。至少需要核对:

  • current 指向实际承载流量的版本,previous 仍可用于回退。
  • Nginx上游端口与当前发布清单一致,nginx -t 通过。
  • 容器没有持续重启,模型日志中没有重复加载或显存分配失败。
  • 代理入口、后端就绪接口和业务级推理探测均通过。
  • 审计日志包含发布编号、镜像摘要、模型版本、配置摘要、操作者、失败阶段和回滚结果。
  • 上一版本在观察期结束前没有被提前删除。
  • 定时巡检不会因单次超时触发重启风暴。
  • 日志中没有记录访问令牌、完整用户提示词或其他敏感内容。

最容易忽略的是“旧版本还能启动”这一条件。镜像存在并不代表模型文件、配置文件和依赖状态仍然可用。应定期对上一发布单元执行只读完整性检查,并在维护窗口验证回滚路径;否则自动化系统保存的可能只是一个无法真正恢复的版本编号。

上一篇 Kubernetes集群上线前如何加固:API端口、身份认证、RBAC与审计检查

LHIDC 产品中心

继续查看可购买的海外服务器产品

文章用于辅助选型,最终价格、库存与配置请以产品详情页和下单页面展示为准。

查看产品 查看方案