第二章 Docker镜像管理:镜像分层原理、常用镜像命令、构建自定义镜像与仓库管理

好,咱们进入第二章。镜像管理,说白了就是Docker世界的「地基」。你想想看,没有镜像,容器跑什么呢?这一章我会把镜像的分层原理、常用命令、Dockerfile编写,还有仓库管理这些核心内容,掰开揉碎了讲清楚。

2.1 镜像分层原理

我第一次接触Docker时,有个问题困扰了我很久:为什么一个几百MB的镜像,拉取起来感觉比想象中快很多?后来我才明白,这背后就是分层存储的功劳。

Docker镜像不是一个大文件,而是由多个只读层堆叠起来的。每一层都代表一个Dockerfile中的指令。比如:

FROM ubuntu:20.04        # 基础层
RUN apt-get update       # 第二层
RUN apt-get install -y nginx  # 第三层
COPY index.html /var/www/html/  # 第四层
CMD ["nginx", "-g", "daemon off;"]  # 元数据层

每一层都是上一层的增量变化。拉取镜像时,如果本地已经有某些层,Docker会直接复用,只下载缺失的层。这就是为什么你第二次拉同一个镜像会快很多。

核心要点:镜像层是只读的,容器启动时会在最上层加一个可写层。所有对容器的修改都写在这个可写层,不会影响镜像本身。

我在项目中遇到过一个问题:有人反复修改Dockerfile,每次构建都生成全新的层,导致镜像体积越来越大。其实只要调整指令顺序,把不常变动的层放在前面,就能充分利用缓存。

我的建议:写Dockerfile时,把「变化频率低」的指令放前面,「变化频率高」的放后面。这样能最大化利用层缓存,构建速度能快好几倍。

2.2 常用镜像命令

命令这东西,光背没用,得在实际场景里用。我挑几个最常用的,结合我的经验给你讲讲。

2.2.1 docker pull

拉取镜像。默认从Docker Hub拉取,也可以指定私有仓库地址。

docker pull nginx:latest
docker pull registry.cn-hangzhou.aliyuncs.com/myproject/myapp:v1.0

嗯,这里要注意:如果不写标签,默认拉取latest。但生产环境我强烈建议指定具体版本号,不然哪天latest变了,你的流水线可能就崩了。

2.2.2 docker push

推送镜像到仓库。前提是你已经登录了。

docker login
docker push myusername/myapp:v1.0

我曾经犯过一个低级错误:push之前忘了打tag,结果把本地镜像名直接推上去了,仓库里一堆乱七八糟的名字。后来我养成了习惯:push前先检查镜像名和标签。

2.2.3 docker tag

给镜像打标签。说白了就是给镜像起个别名。

docker tag nginx:latest myregistry.com/mynginx:1.21

这个命令在CI/CD里特别常用。构建完镜像后,先打个版本标签,再打个latest标签,然后一起推上去。

2.2.4 docker rmi

删除本地镜像。注意,如果有容器正在使用这个镜像,是删不掉的。

docker rmi nginx:latest
docker rmi -f nginx:latest  # 强制删除

我建议定期清理无用的镜像,尤其是CI服务器上,磁盘很容易被撑爆。可以用 docker image prune 一键清理悬空镜像。

2.3 构建自定义镜像(Dockerfile基础)

实际项目中,我们很少直接用官方镜像。更多时候,是在官方镜像基础上,加上自己的代码和配置。这就需要写Dockerfile了。

一个典型的Dockerfile长这样:

# 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 8000

# 7. 启动命令
CMD ["python", "app.py"]

你看,每一步都有讲究。我重点说几个容易踩坑的地方:

  • WORKDIR vs RUN cd:尽量用WORKDIR,它会在每一层都生效。RUN cd只在当前层有效,下一层又回到根目录了。
  • COPY vs ADD:能用COPY就别用ADD。ADD虽然能自动解压tar包,但行为不够透明,容易出问题。
  • CMD vs ENTRYPOINT:CMD可以被覆盖,ENTRYPOINT不行。我一般用ENTRYPOINT指定可执行文件,用CMD传默认参数。

避坑指南:我曾经在Dockerfile里写了RUN rm -rf /var/lib/apt/lists/*,以为能减小镜像体积。结果下一层又执行了apt-get update,白删了。记住:每一层都是独立的,删除操作只在当前层生效。

2.4 镜像仓库:Docker Hub与私有仓库

镜像构建好了,总得有个地方存吧?这就涉及到镜像仓库了。

2.4.1 Docker Hub

Docker官方的公共仓库,类似GitHub。优点是不用自己搭建,缺点是有拉取频率限制,而且公开镜像所有人都能看到。

我个人习惯:个人项目或开源项目用Docker Hub,公司项目用私有仓库。

2.4.2 私有仓库

私有仓库有两种主流方案:

方案 优点 缺点
Docker Registry 轻量、简单、官方支持 没有Web UI,管理不方便
Harbor 功能丰富、有Web UI、支持权限管理 部署相对复杂

搭建一个简单的Docker Registry:

docker run -d -p 5000:5000 --name registry registry:2

然后就可以推送镜像了:

docker tag myapp:latest localhost:5000/myapp:latest
docker push localhost:5000/myapp:latest

我在公司用的是Harbor,因为它支持镜像复制、漏洞扫描、机器人账户这些功能。尤其是机器人账户,在CI/CD流水线里特别方便,不用暴露真实密码。

小技巧:私有仓库一定要配置HTTPS,否则Docker客户端默认拒绝推送。可以用Nginx反向代理加Let's Encrypt证书,几分钟就能搞定。

2.5 本章小结

镜像管理是Docker的核心技能。你掌握了分层原理,就能写出更高效的Dockerfile;你熟悉了常用命令,就能在CI/CD流水线里游刃有余;你学会了搭建私有仓库,就能安全地管理公司镜像资产。

下一章,我们会把这些知识串起来,搭建一条完整的CI/CD流水线。到时候你会发现,前面这些基础打得越扎实,后面就越顺手。