首页
/ Immich 多实例水平扩展实战:共享基础设施、Worker 拆分与弹性缩减

Immich 多实例水平扩展实战:共享基础设施、Worker 拆分与弹性缩减

2026-09-04 09:23:08作者:裴锟轩Denise

本文围绕 Immich 官方的 Scaling Immich 指南展开,讲解如何将 Immich 后端从单容器扩展到多实例并行部署。读完本文,你将理解 Immich 水平扩展的唯一硬性前提(Postgres、Redis 与文件系统三重共享)、如何通过 IMMICH_WORKERS_INCLUDE / IMMICH_WORKERS_EXCLUDE 环境变量拆分 API 与微服务 Worker,以及为什么停掉任意一个实例都不会丢失任何数据——并附有对应源码级实现证据。

多实例扩展的核心前提:共享基础设施

Immich 采用现代化部署实践构建,其后端在设计上就支持多个实例并行运行。官方指南指出,多实例部署唯一需要牢记的要求是:每个实例都必须连接到同一套共享基础设施,具体包括:

  • 所有实例访问同一个 Postgres 数据库
  • 所有实例访问同一个 Redis 实例
  • 所有实例的容器内挂载相同的文件目录(照片/视频原文件、缩略图等)。

这一点可以从生产 compose 文件中得到印证:docker/docker-compose.prod.ymlimmich-server 服务通过 ${UPLOAD_LOCATION}/photos:/data 挂载共享存储,并声明 depends_on: redisdepends_on: database。这意味着当你复制出第二个 immich-server 实例时,必须保证它指向同样的 POSTGRES_HOSTNAME / REDIS_HOSTNAME(或对应的 URL 环境变量),并挂载同一份 /data 目录,两个实例才能像一台机器的两部分一样协同工作。

官方文档同时给出了两类典型的扩展动机:

  • 你有一台带强力 GPU 的游戏 PC,想让它专门承担视频转码和缩略图生成这类 CPU/GPU 密集型任务;
  • 你运营着一个跨多台高性能服务器的 Kubernetes 集群,希望把算力用起来。

:::提示(原文档强调)::: 如果你只有一台机器可以运行 Immich,那么扩展到多个容器大概率不会带来任何收益。单个 Immich 容器内部就会并发运行多个后台任务,并且你可以在管理面板中直接调增这些任务的数量。 :::

为什么单机不需要多容器:Worker 架构的源码视角

理解“单机无需扩展”的关键,在于 Immich 服务内部的 Worker 模型。从源码结构看,immich-server 进程本身是一个 Worker 管理器:server/src/main.ts 中的 Workers 类在 bootstrap() 里读取环境配置得到的 Worker 列表,并逐个启动:

// server/src/main.ts
async bootstrap() {
  const isMaintenanceMode = await this.isMaintenanceMode();
  const { workers } = new ConfigRepository().getEnv();

  if (isMaintenanceMode) {
    this.startWorker(ImmichWorker.Maintenance);
  } else {
    await this.waitForFreeLock();
    for (const worker of workers) {
      this.startWorker(worker);
    }
  }
}

可用的 Worker 类型定义在 server/src/enum.ts

export enum ImmichWorker {
  Api = 'api',
  Maintenance = 'maintenance',
  Microservices = 'microservices',
}

也就是说,默认每一个 immich-server 容器内部已经同时跑着 apimicroservices 两个 Workerapi 负责响应 Web 与移动端 App 的数据/文件请求,microservices 负责缩略图生成、视频编码等后台任务(job)。官方 Jobs and Workers 文档对此有专门说明。既然单容器内已是多线程并发处理,单机场景下再拆容器只是增加了编排复杂度,而非吞吐能力。

拆分 Worker:用环境变量决定容器职责

当你确实要扩容(例如加一台转码机),官方推荐的技巧是:新实例可以只保留 microservices Worker、关掉 API Worker。这通过两个环境变量实现,其含义可参考 环境变量文档

变量 作用
IMMICH_WORKERS_INCLUDE 只运行这些 Worker(白名单)
IMMICH_WORKERS_EXCLUDE 不运行这些 Worker;未指定 INCLUDE 时匹配默认 Worker,指定了则匹配 INCLUDE 的取值

这两个变量在 server/src/dtos/env.dto.ts 中声明为可选字符串,其解析逻辑在 server/src/repositories/config.repository.ts

const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ImmichWorker.Api, ImmichWorker.Microservices]);
const excludedWorkers = asSet(dto.IMMICH_WORKERS_EXCLUDE, []);
const workers = [...setDifference(includedWorkers, excludedWorkers)];
for (const worker of workers) {
  if (!WORKER_TYPES.has(worker)) {
    throw new Error(`Invalid worker(s) found: ${workers.join(',')}`);
  }
}

从这段实现可以看到三点:

  1. INCLUDE默认值是 api,microservices——即不配置时两个 Worker 全开;
  2. 实际启动集合是 INCLUDE 减去 EXCLUDE(集合差),且变量按逗号分隔、自动去除空白;
  3. 解析结果会校验合法性,出现未知 Worker 名会直接抛错拒绝启动,防止拼写错误导致容器静默变成“空壳”。

测试用例 server/src/repositories/config.repository.spec.ts 覆盖了 INCLUDE: 'api'EXCLUDE: 'api'、混合逗号与空格输入、未知 Worker 报错等分支,行为与上述实现一致。

官方给出的 Docker Compose 拆分示例

Jobs and Workers 文档提供了一个“一个容器只跑 Web/API、另一个容器跑全部微服务”的最小拆分方案:先把整个 immich-server 服务块复制为一份新服务并改名(副本上删除端口映射):

- immich-server:
-   container_name: immich_server
- ...
-   ports:
-     - 2283:2283
+ immich-microservices:
+   container_name: immich_microservices

然后为两份服务分别添加环境变量,实现职责隔离:

services:
  immich-server:
    ...
+   environment:
+     IMMICH_WORKERS_INCLUDE: 'api'

  immich-microservices:
    ...
+   environment:
+     IMMICH_WORKERS_EXCLUDE: 'api'

这个模式与 Scaling 指南中“新实例专做转码/缩略图”的场景完全一致:把 immich-microservices 部署到算力更强的那台机器,并确保它与 immich-server 共享 Postgres、Redis 和 /data 挂载即可。

一个容易被忽略但很关键的细节是健康检查的适配server/bin/immich-healthcheck 在容器启动时会先判断 API Worker 是否被禁用:

if [[ ( $IMMICH_WORKERS_INCLUDE != '' && $IMMICH_WORKERS_INCLUDE != *api* ) || $IMMICH_WORKERS_EXCLUDE == *api* ]]; then
  echo "API worker excluded, skipping"
  exit 0
fi

即纯 microservices 容器会直接跳过对 http://IMMICH_HOST:2283/api/server/ping 的 ping 检查并视为健康。如果不知道这一点,你可能会误以为拆分后的容器“健康检查失败”而反复排查——实际上这是 Immich 有意为之的行为。

跨机器扩展:官方立场是“原则明确,细节自证”

Scaling 指南坦率地说明:跨机器扩展的具体做法因环境而异,需要一定的运维知识,因此官方不给出统一的分步教程。它给出的原则是:

  • 在 Kubernetes 上,扩展可能简单到只需调大 Deployment 的副本数(注意所有副本的镜像、环境变量、PVC/共享卷配置一致);
  • 在其他环境,你可能需要配置网络隧道或 NFS 挂载,让多个节点上的容器都能访问同一套 Postgres、Redis 与照片目录。

原文以一句“细节留给读者练习 ;)”收尾——但配合前文的共享基础设施要求,判断标准其实很清晰:任何一台新机器上的容器,只要能读写同一个数据库、同一个 Redis 和同一份文件,它就是一个合格的扩展节点。

向下缩减(Scaling Down):零风险的弹性伸缩

Scaling 指南的另一个亮点是缩减同样安全。由于 Immich 的全部状态都存放在三个地方——Postgres、Redis 与文件系统——停止任意一个正在运行的 immich-server 容器没有任何数据风险。官方给出的真实场景是:你想临时把 GPU 让出来打游戏,直接把那台机器上的 Immich 容器停掉即可。

缩减后的行为边界也很明确:

  • 只要还有任意一个 api Worker 在运行,用户就能正常浏览 Immich(Web/App 的数据请求全部落到它身上);
  • 正在处理的任务队列会等待,直到有可用的 microservices Worker 出现才继续被消费——任务本身不会因为节点下线而丢失,因为任务状态在共享的 Postgres/Redis 中。

这实际上构成了一套极简的“弹性伸缩”方案:平时多实例并行消化上传积压,空闲时保留至少一个带 api Worker 的实例维持服务可用性,其余节点随时可以下线回收算力。

小结:扩展决策清单

场景 建议
单机部署,想加速后台任务 不加容器,直接在管理面板调大后台任务并发数
新增一台高性能机器分担转码/缩略图 部署第二实例,设置 IMMICH_WORKERS_EXCLUDE: 'api'(或 INCLUDE: 'microservices'),共享 Postgres/Redis/文件目录
已有 API 容器想进一步拆出微服务容器 参考 Jobs and Workers 的 Compose 拆分示例
集群空闲期回收算力 直接停掉任一 immich-server 实例,保留至少一个带 api Worker 的实例
Kubernetes 环境 调副本数即可,确保共享存储与中间件配置一致

以上全部行为均可在当前仓库中复核:Worker 启动与生命周期管理见 server/src/main.ts,Worker 枚举见 server/src/enum.ts,环境变量解析与默认值见 server/src/repositories/config.repository.ts,拆分示例与健康检查适配分别见 docs/docs/administration/jobs-workers.mdserver/bin/immich-healthcheck

登录后查看全文
热门项目推荐
相关项目推荐