🏝 Docker 就是"房地产开发":从施工图纸到小区物业的容器化指南

0 阅读10分钟

写在前面:前几节课搭了 React 前端、NestJS 后端、Nginx 代理,一个全栈项目零件齐了。但怎么把它们打包部署?答案是 Docker。今天的课堂从一个 console.log('hello Docker!') 开始,讲到 Dockerfile 标准配方、镜像的 build/push/pull 生命周期,再到 docker-compose 编排多容器——最后用 Milvus 向量数据库做了实战:一个 docker-compose 文件拉起三个容器,各司其职。有趣的是,readme 用蜜雪冰城的 SOP 来类比 Dockerfile,但今天我要换个更宏大的视角——房地产开发。图纸、楼盘、小区物业,一个不少。以下所有代码和概念均来自课堂真实文件。


一、Dockerfile:施工图纸

图纸就是图纸

readme 原话:

"蜜雪冰城标准操作手册 SOP,写清'先加奶茶、再加奶、放三勺糖、摇匀',任何人照着做,出来味道都一样,就成了连锁店。"

"DOCKERFILE 是一个文本配方文件,里面写着一步步'做菜'的指令,Docker 照着做它就能自动做出一个一模一样的 Docker 镜像,运行。"

这个比喻精准到可怕。但我想换一个更"工程"的视角——Dockerfile 就是施工图纸

施工图纸上写的是什么?

1. 打地基(FROM node:18-alpine)
2. 砌墙(WORKDIR /app)
3. 装管线(COPY package*.json ./4. 进设备(RUN npm install)
5. 搬家具(COPY . .)
6. 通水通电(EXPOSE 30007. 交付入住(CMD ["node", "index.js"])

任何施工队拿到这份图纸,建出来的楼一模一样——北京建的和上海建的一样,今天建的和明年建的也一样。这就是 Dockerfile 的核心价值:可重复的构建,消除环境差异。

Hello Docker:最简施工图

课堂的 index.js 只有一行:

console.log(`hello Docker! 我跑在容器里了`)

这行代码本身没什么好说的——但它在容器里运行,意味着背后有一份 Dockerfile 把 Node.js 运行环境、这行代码、启动命令打包成了一个镜像。你不需要在本机装 Node.js,不需要配环境变量,docker run 一下,它就跑起来了。

这就是容器化的威力——环境跟着代码走,不是代码去适应环境。


二、镜像生命周期:施工、挂牌、交房

readme 记录了镜像的完整生命流程:

"构建镜像:docker build -t my-hello . → docker login → docker push → docker pull。Dockerfile 是发布项目的标准项目之一。"

翻译成房地产语言:

Docker 命令房地产类比干了什么
docker build -t my-hello .施工建楼按图纸(Dockerfile)建楼(镜像)
docker login开发商认证登录镜像仓库
docker push挂牌上市把镜像推到 Docker Hub / 私有仓库
docker pull买房从仓库拉取镜像到本地
docker run入住从镜像启动一个容器

施工:docker build

docker build -t my-hello .
  • -t my-hello:给楼起个名字
  • .:施工图纸在哪?当前目录的 Dockerfile

施工队(Docker 引擎)读图纸,一步步执行——装系统、装依赖、拷代码、设端口、定启动命令。建完之后,你得到一个镜像——一栋建好的、随时可以交付的大楼。

挂牌:docker push

docker login    # 开发商认证
docker push my-hello  # 挂牌到 Docker Hub

镜像建好了,只存在你本机——别人用不了。docker push 把镜像推到镜像仓库(Docker Hub 或私有仓库),相当于开发商把楼盘信息挂到房产交易平台上,全世界都能看到、都能下载。

readme 特别强调了一句:

"Dockerfile 是发布项目的标准项目之一。"

以后项目的交付物不止有源代码、文档,还有 Dockerfile——有了图纸,任何人都能复刻你的环境。

买房:docker pull

docker pull my-hello

加盟商(其他开发者或服务器)不需要自己施工,直接从仓库拉取镜像——拎包入住。镜像里包含了运行所需的一切:操作系统、运行时、依赖、代码。不需要装 Node.js,不需要 npm install,不需要配环境变量——一个镜像,到处运行。


三、Docker Compose:小区物业

单个容器好管——一栋楼嘛。但现实项目往往需要多个容器协同。比如课堂提到的全栈项目:

"todos 全栈项目:前端 react + ts + zustand,后端 nest.js Todo Module,nginx 80→3000 跨域。"

至少三个服务:前端、后端、Nginx。手动 docker run 三个容器?可以,但太累。而且启动顺序、网络连接、数据卷挂载全得手动管。

Docker Compose 就是小区物业——一个文件管好所有楼。

readme 对 docker-compose 的定位:

"安装 Milvus:由多个 image 组成的。docker-compose 文件,编排多个 image 工作流。"

一个 YAML 文件,定义所有服务、网络、数据卷。docker-compose up 一条命令,整个小区全部上线。


四、实战:Milvus 向量数据库——一个三栋楼的微型小区

课堂的 milvus-standalone-docker-compose.yml 是一个真实的 docker-compose 文件,拉起 Milvus 向量数据库。Milvus 不是单容器应用——它由三个服务组成。

小区全景图

version: '3.5'

services:
  etcd:        # 楼栋 A:物业档案室
  minio:       # 楼栋 B:地下仓库
  standalone:  # 楼栋 C:主楼

三栋楼,各司其职。让我们逐栋看。

楼栋 A:etcd——物业档案室

etcd:
  container_name: milvus-etcd
  image: quay.io/coreos/etcd:v3.5.25
  environment:
    - ETCD_AUTO_COMPACTION_MODE=revision
    - ETCD_AUTO_COMPACTION_RETENTION=1000
    - ETCD_QUOTA_BACKEND_BYTES=4294967296
    - ETCD_SNAPSHOT_COUNT=50000
  volumes:
    - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd
  command: etcd -advertise-client-urls=http://etcd:2379 -listen-client-urls http://0.0.0.0:2379 --data-dir /etcd
  healthcheck:
    test: ["CMD", "etcdctl", "endpoint", "health"]
    interval: 30s
    timeout: 20s
    retries: 3

etcd 是一个分布式键值存储——Milvus 用它存元数据。就像小区的物业档案室,记录着"几号楼住了谁、车位分配情况、业主信息"。

关键配置解析:

配置含义房地产类比
image: quay.io/coreos/etcd:v3.5.25用哪个图纸建开发商选的施工图纸版本
environment楼内配置每层楼的功能设置
ETCD_QUOTA_BACKEND_BYTES: 42949672964GB 存储配额档案室最多放多少档案
volumes数据持久化档案柜——楼拆了档案还在
command启动命令物业上班要做什么
healthcheck健康检查每月消防检查
interval: 30s每 30 秒查一次检查频率

volumes 这行特别值得注意:

volumes:
  - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/etcd:/etcd

${DOCKER_VOLUME_DIRECTORY:-.} 是 shell 语法——如果设了环境变量 DOCKER_VOLUME_DIRECTORY 就用它,没设就用 .(当前目录)。数据存在宿主机的磁盘上,容器删了数据还在——楼拆了,档案柜搬到新楼继续用。

楼栋 B:minio——地下仓库

minio:
  container_name: milvus-minio
  image: minio/minio:RELEASE.2024-05-28T17-19-04Z
  environment:
    MINIO_ACCESS_KEY: minioadmin
    MINIO_SECRET_KEY: minioadmin
  ports:
    - "9001:9001"
    - "9000:9000"
  volumes:
    - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/minio:/minio_data
  command: minio server /minio_data --console-address ":9001"
  healthcheck:
    test: ["CMD", "mc", "ready", "local"]
    interval: 30s
    timeout: 20s
    retries: 3

MinIO 是一个 S3 兼容的对象存储——Milvus 用它存向量数据和索引文件。就像小区的地下仓库,存大件物品。

它开放了两个端口:

ports:
  - "9001:9001"   # 管理控制台
  - "9000:9000"   # API 接口

左边的 9001 是宿主机端口,右边的 9001 是容器内端口。宿主机:容器——你可以从浏览器访问 localhost:9001 打开 MinIO 的管理控制台。

账号密码都是 minioadmin——课堂环境用的默认值,生产环境绝对不能这样。

楼栋 C:milvus-standalone——主楼

standalone:
  container_name: milvus-standalone
  image: milvusdb/milvus:v3.0.0
  command: ["milvus", "run", "standalone"]
  security_opt:
    - seccomp:unconfined
  environment:
    MINIO_REGION: us-east-1
    ETCD_ENDPOINTS: etcd:2379
    MINIO_ADDRESS: minio:9000
  volumes:
    - ${DOCKER_VOLUME_DIRECTORY:-.}/volumes/milvus:/var/lib/milvus
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:9091/healthz"]
    interval: 30s
    start_period: 90s
    timeout: 20s
    retries: 3
  ports:
    - "19530:19530"
    - "9091:9091"
  depends_on:
    - "etcd"
    - "minio"

主楼是整个小区的核心——Milvus 向量数据库本体。

它怎么找到另外两栋楼的?靠环境变量:

environment:
  ETCD_ENDPOINTS: etcd:2379     # 档案室在哪
  MINIO_ADDRESS: minio:9000     # 仓库在哪

etcd:2379minio:9000 用的是服务名(容器名)作为主机名——Docker Compose 自动创建了一个网络,三栋楼在同一个小区(网络)里,可以用楼栋名互相串门。

三栋楼的协作关系

最后两行是关键:

depends_on:
  - "etcd"
  - "minio"

depends_on 定义了启动顺序——主楼必须在档案室和仓库之后启动。你不能物业档案室还没建好就让人住进来。

但注意:depends_on 只保证启动顺序,不保证服务就绪。etcd 容器启动了,但 etcd 服务可能还在初始化中。这就是 healthcheck 存在的意义——主楼的 start_period: 90s 给了 90 秒的宽限期,让它等另外两栋楼的服务真正就绪。

整个协作流程:

docker-compose up
    ↓
etcd 容器启动        ← 档案室先建
minio 容器启动       ← 仓库同时建
    ↓ healthcheck 通过
milvus 容器启动      ← 主楼开工,连上 etcd 和 minio
    ↓ start_period: 90s 等待
milvus 服务就绪      ← 19530 端口对外服务

小区网络

文件最后一行:

networks:
  default:
    name: milvus

三栋楼在同一个叫 milvus 的网络里。这个网络是 Docker Compose 自动创建的——容器之间用服务名通信(etcd:2379minio:9000),不需要知道对方的 IP。物业内部通讯,靠楼栋名就行。


五、从单楼到小区:Docker 的两层抽象

今天的文件恰好展示了 Docker 的两层抽象:

第一层:Dockerfile → 单个镜像

readme 说的:

"DOCKERFILE 是一个文本配方文件,里面写着一步步'做菜'的指令。"

一个 Dockerfile 产出一个镜像——一栋楼。适合简单应用,比如课堂的 index.js,一个 Node.js 镜像就够了。

第二层:Docker Compose → 多容器编排

readme 说的:

"docker-compose 文件,编排多个 image 工作流。"

一个 docker-compose.yml 编排多个镜像——一个小区。适合复杂应用,比如 Milvus(三个服务协同)或全栈项目(前端 + 后端 + Nginx)。

层级文件产出适用场景
第一层Dockerfile单个镜像单服务应用
第二层docker-compose.yml多容器协作多服务系统

现实项目几乎都是第二层——很少有只用一个容器的生产系统。Docker Compose 是连接"单个容器"到"完整系统"的关键桥梁。


六、回到全栈项目:Todo 的"楼盘化"

readme 最后提到了全栈项目:

"todos 全栈项目:前端 react + ts + zustand,后端 nest.js Todo Module,nginx 80→3000 跨域。"

现在回头看这个项目——如果用 Docker 部署,就是三个容器:

docker-compose.yml
├── frontend    (React + TS + Zustand 镜像)
├── backend     (NestJS 镜像)
└── nginx       (Nginx 镜像,803000 反向代理)

Nginx 容器开放 80 端口对外,内部通过 Docker 网络把 /api 请求转发到 backend 容器的 3000 端口——跨域问题在容器层面自然解决,不需要 Vite 代理了。前端容器跑静态文件,Nginx 容器统一代理。

每个容器都有自己的 Dockerfile——前端一份、后端一份、Nginx 一份。docker-compose.yml 把它们组装成一个完整系统。一张图纸建一栋楼,一个 compose 文件管一个小区。

这就是 readme 说的"Dockerfile 是发布项目的标准项目之一"的完整含义——不只是给你一个镜像,而是给你一套可复现的部署方案。别人拿到你的 docker-compose.yml,docker-compose up 一条命令,整个系统就跑起来了。


七、Milvus 配置文件里的"地产开发"学问

回头看整个 milvus-standalone-docker-compose.yml,每个配置项都有对应的"地产"含义:

配置项房地产含义
version: '3.5'compose 文件版本小区规划规范版本
container_namemilvus-etcd / milvus-minio / milvus-standalone每栋楼的门牌号
imageetcd:v3.5.25 / minio:... / milvus:v3.0.0施工图纸版本
environmentETCD_、MINIO_楼内功能配置
ports"19530:19530"、"9001:9001"对外开门——左宿主右容器
volumes./volumes/xxx:/xxx地下室——楼拆了数据还在
command启动命令物业上班第一件事
healthchecktest/interval/timeout/retries消防检查制度
start_period90s装修宽限期
depends_onetcd + minio前置工程——先建档案室再建主楼
networksmilvus小区内部道路

一个 YAML 文件,56 行,把三个容器的镜像版本、环境变量、端口映射、数据持久化、健康检查、启动顺序、网络拓扑全部定义清楚。这不是运维,是架构设计。


PS:以前觉得 Docker 难,换个角度想——它就是房地产开发。Dockerfile 是图纸,镜像是楼盘,容器是有人住的房子,docker-compose 是小区物业。docker-compose up 一条命令,整个小区拔地而起。你还在手动 docker run 管单栋楼?