1. Docker 基础概念:镜像、容器、仓库、Docker 架构详解

1.1 先聊聊我的 Docker 入门故事

我记得第一次接触 Docker 是在一个微服务改造项目里。

那时候团队还在用虚拟机部署,每次环境不一致都让人抓狂。

“我的机器上跑得好好的啊”——这句话我听了不下十遍。

后来我把服务全部容器化,环境问题基本绝迹了。

我个人习惯把 Docker 比作一个“轻量级集装箱系统”:

  • 镜像就是集装箱的模板——造好以后能重复用。
  • 容器就是真正装货的箱子——启动后就能跑。
  • 仓库就是码头仓库——放着各种模板等你拿。

嗯,这么理解基本不跑偏。

1.2 镜像(Image)—— 只读的“运行环境快照”

镜像说白了就是一个分层构建的只读文件系统。

里面包含了运行应用所需的一切:代码、运行时、系统工具、库、配置。

我在项目中遇到过一个问题:基础镜像选 ubuntu:latest,结果半年后镜像更新把 glibc 版本升了,导致旧二进制无法运行。

所以我建议:

  • 生产环境用 镜像名:具体版本,别用 :latest。
  • 镜像层数不是越少越好,但太多层也会影响拉取和构建速度。
小提示: 查看镜像分层可以使用 docker history 镜像名,每层都对应一个 Dockerfile 指令。

1.3 容器(Container)—— 镜像的运行实例

镜像是静态的,容器是动态的。

你拉下一个 nginx 镜像,然后 docker run,它就变成一个可读写的容器层加在镜像只读层上面。

你以为容器里能随便改文件?能改,但一旦容器删除,修改就丢了——除非挂载了卷。

我曾经犯过一个低级错误:在生产容器里直接改了配置文件,没做卷映射。第二天重启容器,配置全没了……

警告: 永远不要把持久化数据直接写在容器可写层。用 -v 或 --mount 挂载数据卷。

1.4 仓库(Registry / Repository)—— 镜像的分发中心

Docker Hub 是最知名的公共仓库,但你也能搭建私有仓库(比如 Harbor、AWS ECR)。

镜像的完整名称格式是:仓库地址/命名空间/镜像名:标签。

# 从 Docker Hub 拉取官方 nginx
docker pull nginx:1.25

# 推送到私有仓库
docker tag myapp:1.0 registry.example.com/dev/myapp:1.0
docker push registry.example.com/dev/myapp:1.0

国内团队最好用阿里云或腾讯云的容器镜像服务,拉取速度快很多。

1.5 Docker 架构—— 客户端 / 守护进程 / 运行时

谈到架构,你脑海里有这么一张图就行:

  • Docker Client ( CLI ) —— 你敲命令的地方
  • Docker Daemon ( dockerd ) —— 管理镜像、容器、网络的主力
  • containerd —— 真正负责容器生命周期的守护进程
  • runc —— 最底层,按 OCI 标准生成容器进程
  • 镜像仓库 —— 远程存储你需要的镜像

如果你只是用 docker run,其实背后发生了一系列调用:

  1. 客户端发 REST 请求给 dockerd
  2. dockerd 通过 gRPC 调用 containerd
  3. containerd 调用 runc 启动容器

为什么会这样?因为 Docker 公司后来把容器运行时捐给了 CNCF,变成了 containerd + runc 的组合。

核心理解: Docker 不再是单一二进制,而是一个“客户端 + 守护进程 + containerd + runc”的多层架构。
Docker 架构结构图 Docker 核心架构 Docker Client (CLI) docker run / build / push ... REST API (HTTP) Docker Daemon (dockerd) 镜像管理 / 网络 / 存储 gRPC (containerd API) containerd 容器生命周期管理 OCI 标准调用 runc 容器进程生成 镜像仓库 Docker Hub / Harbor push / pull ❗ 架构演进 早期 Docker 不分离

1.6 镜像、容器、仓库核心区别

概念本质是否可写生命周期典型操作
镜像只读模板否(构建时写,运行只读)持久存储pull、build、tag、push
容器镜像的运行实例是(可写层+数据卷)临时(可 stop/start/rm)run、exec、logs、stop
仓库镜像的存储和分发中心—持久login、pull、push

1.7 避坑指南 —— 我踩过的几个坑

  • 镜像太大导致拉取慢: 曾经我直接用 python:3.10-slim 当基础镜像,结果项目依赖里带了 opencv,不得不换用更精简的 slim-buster。记住原则——用最小化 base 镜像。
  • 容器时区问题: 默认 UTC。我建议在 Dockerfile 里加一行 ENV TZ=Asia/Shanghai,并安装 tzdata。
  • 容器里跑 PID 1: 直接在 entrypoint 运行脚本而不处理 SIGTERM,会导致容器 stop 时无法优雅退出。用 exec 前缀或 tini 作为 init。

1.8 一个简单的“理论变实践”场景

你想快速验证新环境?试试这个命令(不要在生产用):

# 拉一个最小的 Alpine Linux 镜像
docker pull alpine:3.19

# 启动一个交互式容器,自动删除
docker run --rm -it alpine:3.19 /bin/sh

# 在容器里查看 /etc/os-release
/ # cat /etc/os-release

如果你看到 NAME="Alpine Linux",说明镜像、容器、仓库三个概念在你的机器上已经串通了。嗯,就这么简单。

1.9 最后的个人经验小结

学 Docker 别跳概念。镜像、容器、仓库是地基;架构里的 dockerd、containerd、runc 是承重墙。

我见过太多人只会 docker run 就觉得自己会了。结果容器挂掉不会排查,镜像层叠冲突搞不清楚。

你把这一章的概念真正消化了,后续的构建、优化、调试才会顺。