第2章 Docker镜像管理:镜像仓库概念、常用镜像命令、镜像分层原理、构建自定义镜像
聊到Docker,镜像管理绝对是绕不开的核心话题。我刚开始接触容器化的时候,总觉得镜像就是个黑盒子——拉下来就能跑,至于里面是什么结构、怎么构建的,完全没深究。直到有一次线上环境因为镜像层数太多导致部署慢得离谱,我才意识到:不懂镜像管理,迟早要踩坑。
这一章,咱们就把镜像管理这块彻底讲透。从仓库概念到常用命令,从分层原理到自定义构建,一步步来。
2.1 镜像仓库:你的镜像该放哪儿?
镜像仓库,说白了就是存镜像的地方。你本地构建好的镜像,总不能一直留在本机吧?要部署到别的机器,或者团队协作共享,就得有个集中存放的地方。
镜像仓库分两种:
- 公共仓库:最典型的就是Docker Hub。全球最大的镜像市场,什么镜像都有。我个人习惯把基础镜像都从Docker Hub拉,比如ubuntu、nginx、python这些官方镜像,质量有保障。
- 私有仓库:企业内部用的。可以用Docker Registry自己搭,也可以用Harbor、Nexus这些企业级方案。我在项目中遇到过,公司安全要求所有镜像必须走内部仓库,不能直接连外网。这时候私有仓库就是刚需。
镜像的命名规则也很简单:[仓库地址/]镜像名[:标签]。比如 nginx:1.21,或者 registry.example.com/myapp:v1.0。标签就是版本号,方便你管理不同版本的镜像。
2.2 常用镜像命令:每天都要用的那些
镜像相关的命令,我列几个最常用的。你想想看,平时工作里是不是天天跟它们打交道?
| 命令 | 作用 | 示例 |
|---|---|---|
docker pull |
拉取镜像 | docker pull nginx:1.21 |
docker images |
查看本地镜像列表 | docker images |
docker rmi |
删除镜像 | docker rmi nginx:1.21 |
docker tag |
给镜像打标签 | docker tag nginx:1.21 myrepo/nginx:v1 |
docker push |
推送镜像到仓库 | docker push myrepo/nginx:v1 |
docker inspect |
查看镜像详细信息 | docker inspect nginx:1.21 |
嗯,这里要注意一点:docker rmi 删除的是镜像,不是容器。如果你有容器正在使用这个镜像,得先删容器才能删镜像。我曾经因为没注意这个顺序,在那折腾了半天。
还有一个命令我经常用——docker image prune。它能清理掉那些没用的、悬空的镜像层。时间久了,本地会堆积一堆无用的镜像,占磁盘空间。定期跑一下这个命令,能省不少空间。
2.3 镜像分层原理:为什么Docker镜像这么轻?
这个问题,我当年面试的时候被问过。说白了,Docker镜像之所以轻量,核心就在于分层。
每个Docker镜像由多个只读层叠加而成。每一层代表一个指令的变更。比如你写个Dockerfile:
FROM ubuntu:20.04 # 层1:基础系统
RUN apt-get update # 层2:更新包列表
RUN apt-get install -y nginx # 层3:安装nginx
COPY index.html /var/www/ # 层4:复制文件
CMD ["nginx", "-g", "daemon off;"] # 层5:启动命令
每一行指令,都会生成一个新层。这些层是只读的,而且可以复用。什么意思呢?比如你有两个镜像,都基于ubuntu:20.04,那它们会共享同一个基础层。你拉取第二个镜像时,基础层已经存在了,只需要下载差异层就行。
我遇到过这样一个场景:团队里多个微服务都基于同一个Python基础镜像。因为分层机制,每个服务只需要拉取自己独有的业务代码层,基础层大家共用。部署速度提升了不少。
还有一个细节——写时复制。容器运行时,会在镜像层之上加一个可写层。你对容器的任何修改,都写在这个可写层里。容器删除后,可写层也就没了。所以容器是无状态的,数据要持久化得用卷挂载。
2.4 构建自定义镜像:从零开始造一个
光会拉现成的镜像不够,工作中经常需要自己构建。构建自定义镜像,最标准的方式就是写Dockerfile。
我拿一个简单的Python Web应用举例:
# 1. 选择基础镜像
FROM python:3.9-slim
# 2. 设置工作目录
WORKDIR /app
# 3. 复制依赖文件
COPY requirements.txt .
# 4. 安装依赖
RUN pip install --no-cache-dir -r requirements.txt
# 5. 复制应用代码
COPY . .
# 6. 暴露端口
EXPOSE 5000
# 7. 启动命令
CMD ["python", "app.py"]
然后执行构建命令:
docker build -t my-python-app:v1.0 .
这里有几个坑,我踩过,得提醒你:
- 层数别太多:每一条RUN、COPY都会生成新层。层数太多,镜像会变得臃肿。我建议把多个RUN命令用
&&合并,比如RUN apt-get update && apt-get install -y nginx。 - 注意构建上下文:
docker build最后的.表示构建上下文。Docker会把整个上下文目录打包发送给守护进程。如果你目录里有大文件或敏感文件,记得用.dockerignore排除掉。 - 基础镜像选对版本:slim版本比full版本小很多,适合生产。alpine版本更小,但有些库可能不兼容。我一般选slim,平衡体积和兼容性。
构建完成后,用 docker images 就能看到你刚造的镜像了。再配合 docker tag 和 docker push,就能把它推到仓库里,供团队其他人使用。
说到构建,还有一个技巧——多阶段构建。比如你编译一个Go程序,编译环境需要完整的Go工具链,但运行环境只需要编译好的二进制文件。多阶段构建可以让你在同一个Dockerfile里,先用一个大镜像编译,再把产物复制到一个小镜像里。最终镜像只包含运行所需的东西,体积能小很多。
# 第一阶段:编译
FROM golang:1.18 AS builder
WORKDIR /app
COPY . .
RUN go build -o myapp
# 第二阶段:运行
FROM alpine:3.15
WORKDIR /app
COPY --from=builder /app/myapp .
CMD ["./myapp"]
你看,最终镜像只有几MB,而不是几百MB。这个技巧我在微服务部署中经常用,效果很好。
好了,镜像管理这块就聊到这儿。下一章咱们进入容器运行时的内容,到时候会讲到怎么用镜像启动容器、管理容器生命周期。记得把镜像命令多练练,熟能生巧。