忘机山人
> 一直想写一本容器小书,真好懒猫基本都做了容器化,所以把这部分分享出来。不同的是,懒猫微服中使用pg-docker来替代docker命令,使用dockge来执行docker-compose。以下讲解以标准docker为主,这样子既学会了docker知识,也能够在懒猫微服上启动Docker服务。
# 《镜像旅馆的秘密》讲的是Docker 镜像的原理、分层结构、生命周期、Docker Hub 上传与下载、常见镜像命令详解
### 🏰 开篇:进入镜像旅馆
自从小李用 Docker 成功打包并运行了自己的 Flask 项目,他的开发效率飞快提高。
某天,老周带他来到一座巨大的数字建筑——**Docker 镜像旅馆**。
“这是你所有镜像的家,”老周说,“也是全球程序员共享旅程资源的中转站。”
镜像旅馆里,层层叠叠地存放着成千上万个镜像,就像一栋模块化的高楼大厦。
------
### 🧱 镜像的本质:一层一层搭起来的文件系统
老周告诉小李:
> “镜像(Image)其实是一个**只读的分层文件系统**。你写的每一条 Dockerfile 指令,都会构成一层 Layer。”
比如这个简单的 Dockerfile:
```Dockerfile
FROM python:3.11-slim
WORKDIR /app
COPY . /app
RUN pip install -r requirements.txt
CMD ["python", "main.py"]
```
对应的镜像层如下:
1. `FROM` → 拉了一个基础镜像层(Python 3.11)
2. `WORKDIR` → 添加一个设置工作目录的 Layer
3. `COPY` → 拷贝代码文件的 Layer
4. `RUN` → 安装依赖的新 Layer
5. `CMD` → 容器入口(不是 Layer,但存配置)
> 💡 小知识:Docker 会尽量缓存和复用前面的 Layer,节省时间和存储。
------
### 🧪 镜像命令全攻略
小李打开终端,开始探索这些镜像的日常操作。
#### 1. 查看本地镜像:
```bash
docker images
```
输出示例:
```
REPOSITORY TAG IMAGE ID CREATED SIZE
my-flask-app latest 123abc456def 2 minutes ago 125MB
python 3.11-slim 789xyz654hij 3 days ago 40MB
```
解释:
- `REPOSITORY`:镜像名
- `TAG`:标签(版本号)
- `IMAGE ID`:镜像唯一标识符
- `SIZE`:镜像大小
#### 2. 查看镜像历史构建过程(看每层):
```bash
docker history my-flask-app
```
#### 3. 删除镜像:
```bash
docker rmi my-flask-app
```
(⚠️ 若有容器在运行该镜像,需先停止并删除容器)
------
### 🗂 镜像仓库:Docker Hub
老周指了指旅馆大堂里的一个巨大电梯:
> “这是 Docker Hub,全球最大的镜像共享仓库。”
在这里,小李能下载成千上万的开源镜像,也能上传自己的。
#### 登录 Docker Hub:
```bash
docker login
```
(需要先注册账号)
#### 下载镜像:
```bash
docker pull nginx
```
这会从 Docker Hub 拉取最新版本的 `nginx` 镜像
#### 指定版本拉取:
```bash
docker pull redis:6.2
```
> 如果docker run/pull 有问题,那么可以通过`lzc-cli appstore copy-image your-images`来使用懒猫的镜像仓库。
(相当于拉取 `redis` 仓库中 tag 为 `6.2` 的镜像)
#### 上传镜像(先打标签):
```bash
docker tag my-flask-app yourdockerhubname/my-flask-app:1.0
docker push yourdockerhubname/my-flask-app:1.0
```
------
### 📦 镜像 Tag 与版本控制
老周问:“小李,你知道为什么镜像都有个 `:latest` 吗?”
小李说:“这是默认版本号吧?”
“对,但我们不能依赖它。**开发、测试、生产应使用明确版本号,比如 1.0、20240321 等**。”
Docker 镜像是通过 `tag` 来区分版本的:
```bash
docker build -t myapp:1.0 .
docker build -t myapp:latest .
```
你可以为同一个镜像打多个标签,对应不同场景使用。
------
### 🔍 镜像体积优化技巧
小李注意到镜像越来越大了,占了很多硬盘空间。
老周给了他几点建议:
1. 使用轻量级基础镜像:
- 比如 `python:3.11-slim` 代替 `python:3.11`
2. 合并
```
RUN
```
命令,减少层数:
```Dockerfile
RUN apt update && apt install -y git && rm -rf /var/lib/apt/lists/*
```
3. 删除临时文件:
- 安装后清理缓存,避免垃圾文件残留
4. 多阶段构建(进阶):
- 构建和运行使用不同的镜像阶段
------
### 📂 镜像保存与迁移
后来小李想把自己的镜像传给另一位没有 Docker Hub 的同事。
他用到了镜像导出与导入:
#### 导出镜像为 `.tar` 文件:
```bash
docker save my-flask-app > myapp.tar
```
#### 导入镜像:
```bash
docker load xxx.tar` |
| 镜像导入还原 | `docker load 多阶段构建的目标是:**编译用谁都行,最终镜像要最小。**
#### 示例:Node 构建 + nginx 托管
```Dockerfile
# 第一阶段:使用 node 构建前端
FROM node:18 AS builder
WORKDIR /app
COPY . .
RUN npm install && npm run build
# 第二阶段:用 nginx 托管打包后的静态文件
FROM nginx:alpine
COPY --from=builder /app/dist /usr/share/nginx/html
```
- 第一阶段装依赖、打包代码
- 第二阶段只取编译结果,**不用带上 node/npm 等工具**
小李一测试,镜像体积从 300MB 降到 25MB,部署速度快了 10 倍!
------
### 🧩 使用 `.dockerignore`:镜像防垃圾机制
构建时,小李发现镜像中夹杂了 `.git`、`node_modules`、`__pycache__`……
老周摇头道:“你忘了 `.dockerignore` 文件。”
就像 `.gitignore` 一样,`.dockerignore` 告诉 Docker 哪些文件在构建镜像时要排除。
#### 示例:
```
__pycache__/
.git/
node_modules/
.env
*.log
```
这个文件放在 Dockerfile 同目录下,**能显著加快构建速度和减小镜像大小**。
------
### 📦 自建私有镜像仓库(Registry)
当公司禁止使用 Docker Hub 时,小李开始尝试搭建自己的镜像库。
老周带他部署了一个本地私有镜像仓库(基于 Docker 官方镜像):
```bash
docker run -d -p 5000:5000 --restart=always --name registry registry:2
```
现在他可以:
- 推送到私库:
```bash
docker tag myapp localhost:5000/myapp
docker push localhost:5000/myapp
```
- 拉取镜像:
```bash
docker pull localhost:5000/myapp
```
适合公司内部使用,搭配 Nexus、Harbor 可实现更完善的权限、审计、镜像管理等功能。(比如懒猫的copy-image)
### 🧠 镜像调试技巧:如何从镜像中探查问题?
如果小李的镜像出错了,他可以通过两种方式“探测”镜像内部:
#### 方法 1:运行一个交互式 shell 容器
```bash
docker run -it myapp /bin/bash
```
(如果 bash 不存在,可以用 `/bin/sh`)
#### 方法 2:打开已有容器的终端
```bash
docker exec -it container_id /bin/bash
```
通过 `ls`、`cat`、`which`、`env` 命令,可以检查:
- 文件有没有 COPY 进去?
- `pip install` 是否安装成功?
- 环境变量是否丢失?
------
### 🔐 镜像安全:不要把密码打包进镜像!
小李曾在 Dockerfile 里写了:
```Dockerfile
ENV DB_PASSWORD=123456
```
老周当场拍桌:“你这是把钥匙写死进容器了!”
最佳做法:
- 在容器运行时注入环境变量(例如使用 `.env` 文件 + `--env` 参数)
- 使用 `docker secret` 或 KMS 管理
- 使用 BuildKit 的 `--secret` 机制加密构建时参数(高级用法)
------
### 🧾 镜像标签管理规范建议
小李准备上线,他开始给镜像打各种 tag:
```bash
docker build -t myapp:1.0.0 .
docker tag myapp:1.0.0 myapp:latest
```
老周说:
> “tag 是镜像的版本名,不要用 `latest` 作为生产环境唯一标识。”
推荐命名规范:
| 标签 | 含义 |
| --------------------- | -------------- |
| `myapp:1.0.0` | 语义化版本控制 |
| `myapp:20240324` | 构建时间戳 |
| `myapp:prod` | 环境标识 |
| `myapp:feature-login` | 功能分支测试 |
------
### 🔁 镜像缓存失效调试技巧
有时候构建镜像时,小李发现修改了某个文件,Docker 却好像没更新。
老周点拨他:“那是缓存搞的鬼。”
#### 方法一:强制跳过缓存
```bash
docker build --no-cache -t myapp .
```
#### 方法二:注意 COPY 顺序影响缓存命中
Docker 会从上到下按顺序缓存。如果把变化频繁的文件 COPY 太早,就会导致缓存失效:
```Dockerfile
COPY requirements.txt . # OK,变动少,适合先复制
RUN pip install -r requirements.txt
COPY . . # 后复制代码,避免频繁无效重建
```
> ✨ 技巧:越是稳定的文件,越早 COPY,利于缓存复用。
------
## 📘 第二章 · 补充总结更新版
| 技术点 | 命令 / 说明 |
| ---------------- | --------------------------------------------- |
| 多阶段构建 | `FROM ... AS builder` + `COPY --from=builder` |
| 忽略文件 | `.dockerignore` 文件 |
| 镜像上传私库 | `docker push localhost:5000/myapp` |
| 开启 BuildKit | `DOCKER_BUILDKIT=1 docker build ...` |
| 进入镜像内调试 | `docker run -it 镜像 /bin/bash` |
| 镜像版本管理建议 | 避免乱用 `latest`,使用语义化 tag |
| 跳过缓存构建 | `docker build --no-cache ...` |
------
小李站在镜像旅馆的屋顶,看着一层层高楼像乐高积木一样堆叠而起。
他感到激动——他已经不再为“部署”苦恼,而是拥有了一个随时可打包、可还原的开发宇宙。
老周说:“你的旅程才刚刚开始,容器的世界比镜像更复杂。”
评论
0暂无评论