2. Docker核心概念:镜像、容器、仓库的关系与区别
刚接触Docker那会儿,我也跟你一样蒙圈。镜像、容器、仓库——这三个词天天挂在嘴边,但真要让我说清楚它们到底什么关系……嗯,我琢磨了好一阵子。
后来在项目里踩过坑、翻过车,才慢慢摸出门道。说白了,这三个东西就像「菜谱、菜品、菜市场」。我习惯用这个比喻来跟团队新人解释,今天也跟你聊聊。
2.1 镜像(Image)——只读的模板
镜像是什么?一个静态的、只读的文件。它包含了运行某个应用所需的全部环境——操作系统层、依赖库、配置文件、启动脚本,一样不落。
你可以把它想象成 Java 里的类(Class),或者虚拟机里的快照。但有个关键区别:镜像是分层的。
我曾经遇到一个典型案例:项目的 Python 依赖有 50 多个包,每次构建都从零下载,慢得让人崩溃。后来我改了 Dockerfile,把稳定的依赖放前面、经常改的放后面,利用镜像层缓存,构建时间从 8 分钟降到了 40 秒。
个人小习惯:写 Dockerfile 时,我会把 apt-get update 和 pip install 拆成多层,每层只变一个东西。这样改动小层时前面的大层缓存还能用。
镜像的关键特性:
- 只读——启动后不能修改镜像本身
- 分层——每一层只记录差异,复用效率高
- 可复用——同一镜像可以启动无数个容器
- 可标记——用
image:tag区分版本
看看你本地有哪些镜像:
docker images
# 或者
docker image ls
列出来的每一行就是一个镜像。注意那个 IMAGE ID,它是内容的哈希值,不是随机的——同样的内容永远生成同样的 ID。
2.2 容器(Container)——运行中的实例
容器就是「镜像+可写层」运行起来的东西。你想想,镜像只读,那怎么改配置文件?怎么写日志?容器在镜像顶层加了一个可写层(也叫容器层),所有变更都写在这一层。
容器和镜像的关系,就是对象和类的关系。一个镜像可以同时跑出几十个容器,彼此隔离,互相不干扰。
我记得有一次线上排查,有个容器日志爆了,磁盘打满。我用 docker exec 进去看了下,发现是某个程序在循环写调试日志。因为容器层是可写的,所以日志一直往上堆。后来我在启动时加了日志大小限制,才彻底解决。
容器的生命周期:
- 创建(
docker create)——拿到一个容器 ID,但没运行 - 启动(
docker start)——运行起来 - 运行中——处理请求,写日志
- 停止(
docker stop)——进程退出,可写层还在 - 删除(
docker rm)——全部清空
避坑指南:我曾经在开发环境里直接 docker run 了 20 个容器做压力测试,跑完忘了删。第二天发现 /var/lib/docker 用掉了 80GB。后来我养成了习惯:临时容器加 --rm 参数,退出自动删除。
2.3 仓库(Registry)——镜像的存储与分发
仓库就是存镜像的地方。本地镜像只能自己在单机用,要分享给其他机器、其他团队,就得推送到仓库。
官方仓库是 Docker Hub,很多公司也会搭私有仓库(比如 Harbor、Nexus、AWS ECR)。
常用命令:
# 从仓库拉取
docker pull nginx:1.25-alpine
# 推送到仓库
docker push myregistry.example.com/myapp:v2.0
# 搜索镜像(不推荐线上用,耗时长)
docker search python
仓库里每个镜像通过 repo/name:tag 定位。比如 library/nginx:1.25-alpine,library 是命名空间(官方镜像的命名空间就叫 library),nginx 是镜像名,1.25-alpine 是标签。
2.4 三者的关系——一张图讲清楚
下面这张图是我给团队培训时画的精简版。看完你就明白它们怎么连在一起了。
2.5 关键区别对照表
我整理了一个表格,把三者的核心维度说清楚。你在脑子里过一遍,基本就不会再搞混了。
| 维度 | 镜像 | 容器 | 仓库 |
|---|---|---|---|
| 本质 | 静态只读文件 | 运行中的进程 | 存储系统 |
| 是否可写 | 否(只读) | 是(可写层) | 是(可存储、删除) |
| 生命周期 | 持久(删了才没) | 临时(停止可保留) | 持久(除非手动删) |
| 标识方式 | name:tag + Image ID |
Container ID + name | registry/name:tag |
| 常见操作 | build, pull, push, tag |
run, stop, rm, exec |
login, push, pull |
| 类比 | 菜谱(印好的) | 按菜谱炒出的菜 | 菜市场(买/卖菜谱) |
2.6 动手实践:体验三者的流转
光说不练假把式。跟着走一遍,你就能感受到三者在手底下的感觉。
-
从仓库拉一个镜像
docker pull alpine:3.19 -
查看本地镜像列表——你会发现仓库里的东西跑到你本地了。
docker images -
根据镜像启动一个容器
docker run --name myalpine -it alpine:3.19 /bin/sh这会直接进到容器 shell。在里面敲
cat /etc/os-release看看系统版本。 -
退出但不删除容器:按
Ctrl+D或打exit。容器会停止,但可写层还在。 -
重启容器并看看变化
docker start -ai myalpine你之前对文件系统做的修改还在,因为可写层没有丢。
-
用容器创建一个新镜像(了解即可,生产不常用)
docker commit myalpine mycustom:test这会把容器的可写层+原始镜像层打包成一个新镜像。
我的一点体会:刚入门时总以为镜像和容器差不多,直到我发现同样一个镜像启动的不同容器,在里面装不同的包、改不同的配置,彼此完全独立。那一刻我才真正理解了「隔离」的含义。而且你还记得吗?容器删了没关系,镜像还在,可以再起来一个全新的。
2.7 避坑总结
我把常见的坑列出来,你记一下,能省不少时间:
- 别把容器当虚拟机玩——容器里不要跑 systemd,不要装 sshd,每个容器只跑一个进程才是正解。
- 镜像标签别用
latest打生产环境——latest是动态的,今天拉和明天拉可能不同。我习惯用语义版本号 + Git commit 短哈希。 - 仓库一定要配访问控制——别让你的私有镜像流到公网上。我曾经见过有人把生产环境的数据库密码直接写进 Dockerfile,然后又推到公开仓库……后果你自己想。
- 容器数据要持久化必须用 volume——容器一删,可写层就没了。重要数据一定要挂载到宿主机或 volume 里。