docker-compose 与多容器编排

为什么要用 compose

用向导建容器,一两个还轻松,五个以上就开始难受:每个容器的端口、目录、环境变量都散在向导里,改一处要点半天,换台 NAS 还得从头配一遍。docker-compose 把这些全部写进一个 YAML 文件,一个命令或者一次点击就能把整套服务拉起来,删掉重建也只要几秒。配置文件本身就是最好的文档,什么端口、什么目录、什么环境变量,全在明处。

DSM 7.2 的 Container Manager 把 compose 做进了图形界面,这就是「项目」标签页。它不需要你登录 SSH 敲命令,粘贴 YAML、点下一步、等拉取完成,一套多容器服务就跑起来了。习惯命令行的玩家照样可以 SSH 上去用 docker compose 命令管理,两者操作的是同一套项目。

一个典型场景:带数据库的应用。向导方式要分别建两个容器,还要手动建网络让它们互通;compose 方式则是把网络声明进文件,声明依赖关系,启动顺序自动处理。

项目标签页:三步起来一套服务

在「项目」标签页里点「新建」,向导只有三步:

  1. 填项目名称和保存路径。路径建议放在 /volume1/docker/项目名/ 下面,YAML 文件和容器配置集中管理。
  2. 来源选「创建 docker-compose.yml」,在编辑器里粘贴配置。
  3. 点下一步,Container Manager 会先校验 YAML 语法,再拉取镜像、创建容器、启动服务。
步骤 填什么 注意事项
常规 项目名称、路径 路径选 docker 共享文件夹下
来源 粘贴 compose 文件 可选本地已有文件
部署 自动校验并启动 超时失败看日志标签

项目建好之后,列表里会显示运行状态、容器数量和资源占用。停止、启动、重建都是一键操作,「删除」会清掉整套服务和网络,但映射到 NAS 的文件目录不会被动,数据还在原地。

compose 文件结构

一个可照抄的示例

下面是一套常见的应用加数据库组合,结构足够典型,换成别的应用照着改就行:

services:
  app:
    image: linuxserver/openspeedtest:latest
    container_name: speedtest
    restart: unless-stopped
    ports:
      - "3080:3000"
    volumes:
      - /volume1/docker/speedtest/config:/config
    environment:
      - TZ=Asia/Shanghai
      - PUID=1026
      - PGID=100
    depends_on:
      - db
  db:
    image: mariadb:10.11
    container_name: speedtest-db
    restart: unless-stopped
    volumes:
      - /volume1/docker/speedtest/db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=换成你自己的强密码
      - TZ=Asia/Shanghai

照抄时要改三处:PUID 和 PGID 换成你 NAS 上实际用户的 ID,root 密码换成强密码,端口确认没被占用。depends_on 保证 app 在 db 之后启动。两个容器都在默认网络里,app 用主机名 db 就能连到数据库,这正是 compose 相比向导最大的便利。

常用字段速查

compose 的字段很多,日常 80% 的场景用下面这些就够:

字段 作用 示例
image 镜像名和版本 mariadb:10.11
ports 本地端口比容器端口 「3080:3000」
volumes 本地路径比容器路径 /volume1/docker/a:/config
environment 环境变量 TZ=Asia/Shanghai
restart 重启策略 unless-stopped
depends_on 启动依赖 另一个服务名
networks 加入自定义网络 见下一节
mem_limit 内存上限 1024m

写 YAML 最容易犯两个错:一是缩进,必须用空格不能用 Tab,层级错一个空格整个文件就报错;二是冒号后面要有空格,image:mariadb 是无效的。项目标签页的编辑器有语法校验,哪里错了会直接指出来,比命令行友好不少。

自定义网络与跨项目通信

默认情况下,每个项目有自己的网络,项目里的容器互通,跨项目则不行。如果两个项目里的容器需要互访,比如反代容器要转发到另一个项目的服务,可以建一个自定义网桥,让相关容器都加进来。

在「网络」标签页里新增一个网桥网络,名字比如叫 synobridge,然后在 compose 里引用:

services:
  web:
    image: nginx:latest
    networks:
      - synobridge
networks:
  synobridge:
    external: true

external: true 表示这个网络已经存在,不要新建。加好之后,两个项目里的容器就能用容器名互相访问。比起端口映射再绕回来,容器间直连既快又不占主机端口。对网络原理不太熟的读者,可以先补一下桥接和 NAT 的概念,参考 计算机网络基础。

项目部署流程

写 compose 的三个技巧

技巧之一是把可变的东西放环境变量。数据库密码、域名、端口前缀这类值,可以单独放一个 .env 文件里,compose 用 ${变量名} 引用。好处是换环境只改一个文件,YAML 本身可以放心分享和备份,不会把密码泄出去。Container Manager 的项目编辑器支持这种写法,.env 和 docker-compose.yml 放同一目录即可。

技巧之二是加健康检查。给关键服务写 healthcheck,Docker 会周期性探活,挂掉的服务在容器列表里一眼就能看出来,而不是等到你自己访问才发现。网络类应用探一个 HTTP 端口、数据库探端口连通就行。

技巧之三善用 restart: unless-stopped。它和 always 的区别在于:always 在你手动停止后,重启 NAS 时仍会拉起来;unless-stopped 尊重你的手动停止。绝大多数自用场景后者更合适。

重启策略 手动停止后 NAS 重启后 适用场景
no 不启动 不启动 临时任务
always 不启动 启动 核心常驻服务
unless-stopped 不启动 启动 多数自用场景
on-failure 不启动 仅异常退出时启动 一次性脚本

更新与重建

更新项目里的服务有两种情况。只改配置(端口、目录、环境变量),在项目详情里编辑 YAML 保存,Container Manager 会自动重建受影响的容器;要更新镜像版本,把 image 后面的 tag 改掉,保存后同样自动重建。latest 标签不会自动追新,停掉再启动项目也不会拉新镜像,必须改 tag 或者先在镜像标签页下载新版本。

重建过程中有个细节值得知道:项目删除容器时不会删除映射目录,但会删除未映射的数据以及容器本身的网络配置。所以重建前扫一眼 volumes 列表,确认该映射的都映射了。另外,数据库类服务重建前最好停掉项目再改,避免容器运行中文件被改坏。

还有一种常见需求是迁移:把整个 docker 目录打包复制到新 NAS,装好 Container Manager,用相同的路径新建项目,所有服务就回来了。前提是新 NAS 的卷编号相同、用户 ID 相同。卷编号不同要全局替换路径,用户 ID 不同要改 PUID——这也是上一篇权限篇强调统一目录结构的原因。

养成一个习惯:改配置前先把 YAML 复制到本地备份一份。项目配置文件就在项目路径下,重装系统后只要数据还在,把 docker 目录拷回去、重建项目,几分钟就能恢复全部服务。

延伸阅读