写给懒猫微服玩家的容器小书 Docker篇(二):《镜像旅馆的秘密》

忘机山人

发布于383天前
博客图片修整中,看不了可以先搜索公众号“忘机山人”看。
> 一直想写一本容器小书,真好懒猫基本都做了容器化,所以把这部分分享出来。不同的是,懒猫微服中使用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

暂无评论

说点什么呢~
收藏
0
0
0