容器是现代基础设施的构建单元——它是 Kubernetes 编排的对象、CI 流水线构建的产物、微服务发布的方式。Docker 把"把应用连同它运行所需的一切打包"变成一条命令的工作流,从而让容器普及。最关键要理解的是:容器不是一台轻量虚拟机——它是共享内核上的一个隔离进程,这个区别解释了它的速度,也解释了它的局限。

⚡ 速览要点
  • 容器把应用 + 它所有依赖打包成一个可移植镜像,在任何主机上运行结果一致——终结"在我机器上是好的"。
  • 它是隔离进程,不是 VM——Linux namespaces 给它自己的系统视图;cgroups 限制它的 CPU/内存。它共享主机内核。
  • 对比 VM:没有客机操作系统,所以容器是 MB 级而非 GB 级、毫秒级启动、可高密度部署——代价是隔离更弱。
  • 镜像是分层且只读的——每条 Dockerfile 指令是一个可缓存的层;层在镜像间共享以省空间和构建时间。
  • Dockerfile 是可复现的构建配方;registry(Docker Hub、ECR)存储并分发镜像。
  • 容器是临时且不可变的——状态放在 volume/外部存储里;你替换而非修补一个运行中的容器。
tldr

容器把应用和它的依赖打包成一个不可变镜像,到处运行结果都一样。底层它只是一个被 Linux namespaces(自己的文件系统、网络、PID 空间)隔离、被 cgroups(CPU/内存上限)约束的主机进程,共享主机内核——这就是它远比 VM 轻的原因。镜像由 Dockerfile 构建成可缓存、可共享的层,经 registry 分发。让容器保持无状态且不可变,你就得到可移植、可回滚、能被 Kubernetes 管理的部署。

问题:"在我机器上是好的"

有容器之前,部署软件意味着在每台机器上复现它的环境——对的语言运行时、库版本、系统包、配置。开发者笔记本、CI、生产之间的细微差异造成了那句臭名昭著的"可在我机器上是好的"。容器解决这个,靠把应用连同它整个用户态环境打包成一个产物,所以你测的东西和你在生产跑的东西逐字节相同。

容器到底是什么

容器是一个普通的 Linux 进程,只是被赋予了对系统的隔离视图,用到两个内核特性:

关键是,主机上所有容器共享那台主机的内核。容器里没有客机操作系统——只有你的应用和它的用户态文件,作为隔离、受限的主机进程运行。这一个事实是容器对比 VM 一切取舍的根源。

容器对比虚拟机

方面容器虚拟机
隔离单元进程(namespaces + cgroups)hypervisor 上的完整客机 OS
内核共享主机内核每个 VM 有自己的内核
体积MB 级GB 级
启动毫秒级数秒到数分钟
密度每主机数百个每主机几个
隔离强度较弱(共享内核)较强(硬件级)

结论:容器在轻量、速度、密度上胜出(很适合大量小服务),而 VM 在隔离上胜出(更强的安全边界)。实践中两者常分层——云上容器常跑在 VM ,以兼得两者。

镜像与分层

容器镜像(image)是实例运行所基于的、不可变、打包好的文件系统 + 元数据。它的定义性特征是分层(layer)构建:每层是一个只读的文件系统差异,通过联合文件系统(union filesystem)叠成最终的根文件系统。层是内容寻址且共享的——如果十个镜像基于同一个基础层,该层只存一份、被复用。容器运行时,顶上加一个薄的可写层(写时复制),所以底下的镜像保持不可变。

分层镜像 + 可写容器层
┌─────────────────────────────┐  ← 可写层(每容器一个,临时)
├─────────────────────────────┤
│ COPY app/        (层 4)     │  ┐
│ RUN npm install  (层 3)     │  │ 只读镜像层,
│ COPY package.json(层 2)     │  │ 可缓存 + 跨镜像共享
│ FROM node:20     (层 1)     │  ┘
└─────────────────────────────┘

  改应用代码 → 只重建层 4;层 1–3 从缓存复用

这种分层是构建快(未变的层走缓存)的原因,也是为什么 Dockerfile 指令顺序重要——把不常变的步骤(装依赖)放在常变的步骤(拷源码)之前,让缓存能扛住大多数改动。

Dockerfile

镜像由 Dockerfile 构建——一份声明式配方,每条指令产生一个层:

Dockerfile
FROM node:20-alpine          # 小的基础镜像
WORKDIR /app
COPY package*.json ./        # 先拷依赖 → 对缓存友好
RUN npm ci --omit=dev
COPY . .                      # 再拷源码(常变)
EXPOSE 3000
CMD ["node", "server.js"]    # 容器运行的进程

多阶段构建(multi-stage build)(一个编译的构建阶段,再一个只拷产物的瘦运行阶段)把构建工具留在身后,从而让最终镜像很小——是做精简、安全镜像的关键实践。

Registry

镜像通过 registry 分发——你把构建好的镜像 push 上去、再 pull 下来(Docker Hub、AWS ECR、GitHub Container Registry 等)。镜像打标签(如 myapp:1.4.2),而因为层是内容寻址的,一次 pull 只下载主机还没有的层。registry 是你的 CI/CD 流水线(构建并推送)与编排器(拉取并运行)之间的交接点。

状态、网络与运行时

两个运维事实很重要。第一,容器的可写层是临时的——容器被删时它就没了,所以持久数据放在 volume(挂载到主机或网络存储)或外部服务(数据库、对象存储)里,绝不放在容器内。第二,每个容器有自己的网络 namespace;Docker 用虚拟网络把它们连起来、把端口映射到主机。Docker 引擎(或 containerd 等其他运行时)负责把镜像变成一个运行中的隔离进程。

容器为什么胜出

常见坑

总结

容器是一个共享主机内核、被隔离且资源受限的进程——不是 VM——这就是它小、快、密的原因。镜像是不可变、分层、可共享的,由 Dockerfile 可复现地构建、经 registry 分发。让它们无状态且不可变,你就得到可移植、可回滚的部署,能被 Kubernetes 这样的编排器大规模管理。

🎯 面试速答

容器对比 VM?容器是共享主机内核的隔离进程(namespaces + cgroups);VM 在 hypervisor 上跑完整客机 OS。容器 MB 级、毫秒启动;VM GB 级、隔离更强。
到底是什么在隔离容器?Linux namespaces(文件系统、网络、PID 视图)加 cgroups(CPU/内存上限)——没有客机内核。
镜像分层有什么用?每条 Dockerfile 步骤是一个可缓存、内容寻址、跨镜像共享的层——构建快、存储小;把最不常变的步骤放前面。
容器状态放哪?不放在临时的可写层里——放 volume 或外部存储,因为容器不可变、可替换。
容器为何在微服务上胜出?可移植、毫秒启动、高密度、不可变带版本部署,还是编排的完美单元。

← 上一篇
CDN