昨天排查一个线上服务问题,日志显示某个依赖库版本不一致。开发环境跑得好好的,测试环境也正常,一到线上就崩。几个人对着屏幕查了半天,最后发现是测试机残留了旧版本的Python包。这种“环境差异”问题,相信各位都遇到过——而Docker正是为了解决它而生的。今天我们不谈命令大全,就聊透三个核心概念:镜像、容器、仓库。理解了它们,Docker才算真正入门。
镜像:你的应用“模具”
镜像不是应用本身,而是应用的静态模板。你可以把它理解成一套模具,里面封装了应用运行所需的一切:代码、运行时、系统工具、系统库,甚至配置文件。镜像本身是只读的,一旦创建就不能修改(除非重新构建)。
举个例子,你从Docker Hub拉一个Nginx镜像:
docker pull nginx:1.20
这就像从工厂仓库里拿到了一个标准的Nginx模具。这个模具里已经装好了Linux系统、Nginx二进制文件、默认配置文件等等。但这时候它还没运行,只是安静地躺在你的磁盘里。
关键点:镜像是分层的。每一层对应Dockerfile里的一条指令。比如:
FROM ubuntu:20.04 # 第一层:基础系统
RUN apt-get update # 第二层:更新包列表
COPY app /var/www/app # 第三层:复制代码
这种分层设计让镜像可以复用——如果你基于同一个基础镜像构建多个应用,基础层只会存一份。
容器:模具浇铸出的“产品”
容器是镜像的运行实例。还是刚才的比喻:镜像如果是模具,容器就是模具浇铸出来的具体产品。你可以启动多个容器,它们都来自同一个镜像,但彼此隔离。
启动一个容器:
docker run -d --name my-web nginx:1.20
这时候,Docker会在镜像层之上创建一个可写的“容器层”。所有运行时修改(比如写入日志、生成临时文件)都发生在这个容器层里,不会影响底下的镜像层。这就是为什么容器可以随意启停、删除,而镜像始终保持干净。
调试时容易踩的坑:有人直接在容器里apt install装调试工具,然后想把修改保存下来,于是:
docker commit my-web my-nginx:debug # 别这样写!
虽然docker commit能生成新镜像,但这样生成的镜像黑盒化,失去可重复性。正确做法是回头修改Dockerfile,重新构建。
仓库:模具的“货架”
仓库是存放镜像的地方。Docker Hub是最大的公共仓库,就像GitHub之于代码。你也可以搭建私有仓库(比如用Harbor)。
镜像命名体现了仓库位置:
nginx→ 默认从Docker Hub的library仓库拉取mycompany/web:latest→ 指向Docker Hub的mycompany命名空间registry.local:5000/app:v1→ 指向私有仓库
实际经验:生产环境一定要用标签明确版本号,别用latest。我吃过亏——某次自动构建覆盖了latest,导致线上拉到了未测试的版本。现在团队规范要求镜像标签必须带Git commit hash,比如app:a1b2c3d。
三者的生命周期流转
- 构建:写Dockerfile →
docker build生成镜像 → 镜像存本地 - 分发:
docker push推镜像到仓库 → 别人docker pull拉取 - 运行:
docker run从镜像创建容器 → 容器运行中可暂停/重启 - 销毁:容器停止后仍占用磁盘(可
docker rm清理) → 镜像不用时可docker rmi删除
一个常见误区:停止的容器依然存在,需要显式删除。可以用这条命令清理所有停止的容器:
docker container prune # 小心使用,别把有用的容器删了
几个容易混淆的场景
场景一:“我改了容器里的文件,怎么镜像没变?”
因为容器层在镜像层之上,修改只存在于容器层。想要固化修改,必须重新构建镜像。
场景二:“我删除了容器,为什么磁盘没释放?”
可能忘了加-v删除关联的卷,或者镜像还占着空间。用docker system df查看磁盘使用明细。
场景三:“从仓库拉取的镜像,怎么本地多了很多<none>镜像?”
那些是中间层镜像或者悬空镜像。定期用docker image prune清理。
给初学者的实操建议
-
镜像设计尽量小:用Alpine基础镜像,合并RUN指令,用.dockerignore排除无关文件。一个臃肿的镜像会让分发和部署变慢。
-
容器当牲口,别当宠物:宠物得精心照料,牲口坏了就换。容器应该是无状态的,数据持久化用卷,日志打到stdout。
-
标签语义化:别只用
latest。结合业务版本(v1.2.3)、环境(prod)、构建时间(20240115)组合标签。 -
本地留一份基础镜像:避免测试时反复从仓库拉取。可以搭建本地镜像缓存(比如用registry mirror)。
-
理解退出码:容器退出时会有退出码。137表示被SIGKILL杀死(可能内存超了),143表示被SIGTERM终止。查问题先看这个。
最后说一句:Docker上手容易,但想用好得理解它的设计哲学——不可变基础设施。一旦接受了“容器即瞬态”这个设定,很多问题就自然有了解决方案。下次遇到环境不一致的问题时,试试先问自己:“能不能塞进Docker里?”

1208

被折叠的 条评论
为什么被折叠?



