mirror of
https://github.com/yeasy/docker_practice.git
synced 2026-03-10 20:04:36 +00:00
Add more content
This commit is contained in:
@@ -1,13 +1,246 @@
|
||||
# Docker 容器
|
||||
|
||||
镜像(`Image`)和容器(`Container`)的关系,就像是面向对象程序设计中的 `类` 和 `实例` 一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。
|
||||
## 一句话理解容器
|
||||
|
||||
容器的实质是进程,但与直接在宿主执行的进程不同,容器进程运行于属于自己的独立的 [命名空间](https://en.wikipedia.org/wiki/Linux_namespaces)。因此容器可以拥有自己的 `root` 文件系统、自己的网络配置、自己的进程空间,甚至自己的用户 ID 空间。容器内的进程是运行在一个隔离的环境里,使用起来,就好像是在一个独立于宿主的系统下操作一样。这种特性使得容器封装的应用比直接在宿主运行更加安全。也因为这种隔离的特性,很多人初学 Docker 时常常会混淆容器和虚拟机。
|
||||
> **容器是镜像的运行实例。如果把镜像比作程序,那么容器就是进程。**
|
||||
|
||||
前面讲过镜像使用的是分层存储,容器也是如此。每一个容器运行时,是以镜像为基础层,在其上创建一个当前容器的存储层,我们可以称这个为容器运行时读写而准备的存储层为 **容器存储层**。
|
||||
用面向对象编程的术语来说:**镜像是类(Class),容器是对象(Instance)**。
|
||||
|
||||
容器存储层的生存周期和容器一样,容器消亡时,容器存储层也随之消亡。因此,任何保存于容器存储层的信息都会随容器删除而丢失。
|
||||
- 一个镜像可以创建多个容器
|
||||
- 每个容器相互独立,互不影响
|
||||
- 容器可以被创建、启动、停止、删除、暂停
|
||||
|
||||
按照 Docker 最佳实践的要求,容器不应该向其存储层内写入任何数据,容器存储层要保持无状态化。所有的文件写入操作,都应该使用 [数据卷(Volume)](../data_management/volume.md)、或者 [绑定宿主目录](../data_management/bind-mounts.md),在这些位置的读写会跳过容器存储层,直接对宿主(或网络存储)发生读写,其性能和稳定性更高。
|
||||
## 容器的本质
|
||||
|
||||
数据卷的生存周期独立于容器,容器消亡,数据卷不会消亡。因此,使用数据卷后,容器删除或者重新运行之后,数据却不会丢失。
|
||||
> 💡 **笔者认为,理解这一点是理解 Docker 的关键**
|
||||
|
||||
**容器的本质是一个特殊的进程。**
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 普通进程 │
|
||||
│ • 与其他进程共享系统资源 │
|
||||
│ • 可以看到其他进程 │
|
||||
│ • 共享网络和文件系统 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 容器进程 │
|
||||
│ ┌───────────────────────────────────────────────────────┐ │
|
||||
│ │ • 有自己的进程空间(看不到宿主机上的其他进程) │ │
|
||||
│ │ • 有自己的网络(独立 IP、端口) │ │
|
||||
│ │ • 有自己的文件系统(独立的 root 目录) │ │
|
||||
│ │ • 有自己的用户(容器内的 root ≠ 宿主机的 root) │ │
|
||||
│ └───────────────────────────────────────────────────────┘ │
|
||||
│ 但仍然运行在宿主机的内核上 │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
这种隔离是通过 Linux 内核的 **Namespace** 技术实现的。
|
||||
|
||||
## 容器 vs 虚拟机:核心区别
|
||||
|
||||
很多初学者会混淆容器和虚拟机。笔者用一张图来说明:
|
||||
|
||||
```
|
||||
虚拟机 容器
|
||||
┌───────────────────────┐ ┌───────────────────────┐
|
||||
│ App A │ App B │ │ App A │ App B │
|
||||
├────────────┼──────────┤ ├────────────┼──────────┤
|
||||
│ Guest OS │ Guest OS │ │ Container │ Container│
|
||||
│ (完整系统) │ (完整系统)│ │ (仅应用) │ (仅应用) │
|
||||
├────────────┴──────────┤ └────────────┴──────────┤
|
||||
│ Hypervisor │ │ Docker Engine │
|
||||
├───────────────────────┤ ├───────────────────────┤
|
||||
│ Host OS │ │ Host OS │
|
||||
├───────────────────────┤ ├───────────────────────┤
|
||||
│ Hardware │ │ Hardware │
|
||||
└───────────────────────┘ └───────────────────────┘
|
||||
每个 VM 运行完整 OS 所有容器共享宿主机内核
|
||||
```
|
||||
|
||||
| 特性 | 容器 | 虚拟机 |
|
||||
|------|------|--------|
|
||||
| **隔离级别** | 进程级(Namespace) | 硬件级(Hypervisor) |
|
||||
| **启动时间** | 秒级(甚至毫秒) | 分钟级 |
|
||||
| **资源占用** | MB 级别 | GB 级别 |
|
||||
| **性能损耗** | 几乎为零 | 5-20% |
|
||||
| **内核** | 共享宿主机内核 | 各自独立内核 |
|
||||
|
||||
## 容器的存储层
|
||||
|
||||
### 镜像层 + 容器层
|
||||
|
||||
当容器运行时,Docker 会在镜像的只读层之上创建一个**可写层**(容器存储层):
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────┐
|
||||
│ 容器存储层(可读写) │ ← 容器运行时创建
|
||||
│ 运行时产生的文件变化记录在这里 │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 镜像第 N 层(只读) │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 镜像第 N-1 层(只读) │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ ... │
|
||||
├─────────────────────────────────────────────┤
|
||||
│ 镜像第 1 层(只读) │ ← 基础镜像层
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Copy-on-Write(写时复制)
|
||||
|
||||
当容器需要修改镜像层中的文件时:
|
||||
|
||||
1. Docker 将该文件**复制**到容器存储层
|
||||
2. 在容器层中进行修改
|
||||
3. 原始镜像层保持不变
|
||||
|
||||
```
|
||||
读取文件:直接从镜像层读取(共享,高效)
|
||||
修改文件:复制到容器层,然后修改(只有这个容器能看到修改)
|
||||
```
|
||||
|
||||
### ⚠️ 容器存储层的生命周期
|
||||
|
||||
> **笔者特别强调**:这是新手最容易踩的坑!
|
||||
|
||||
**容器存储层与容器生命周期绑定。容器删除,数据就没了!**
|
||||
|
||||
```bash
|
||||
# 创建容器,写入数据
|
||||
$ docker run -it ubuntu bash
|
||||
root@abc123:/# echo "important data" > /data.txt
|
||||
root@abc123:/# exit
|
||||
|
||||
# 删除容器
|
||||
$ docker rm abc123
|
||||
|
||||
# 数据丢了!没有任何办法恢复!
|
||||
```
|
||||
|
||||
### 正确的数据持久化方式
|
||||
|
||||
按照 Docker 最佳实践,容器存储层应该保持**无状态**。需要持久化的数据应该使用:
|
||||
|
||||
| 方式 | 说明 | 适用场景 |
|
||||
|------|------|---------|
|
||||
| **[数据卷(Volume)](../data_management/volume.md)** | Docker 管理的存储 | 数据库、应用数据 |
|
||||
| **[绑定挂载(Bind Mount)](../data_management/bind-mounts.md)** | 挂载宿主机目录 | 开发时共享代码 |
|
||||
|
||||
```bash
|
||||
# 使用数据卷(推荐)
|
||||
$ docker run -v mydata:/var/lib/mysql mysql
|
||||
|
||||
# 使用绑定挂载
|
||||
$ docker run -v /host/path:/container/path nginx
|
||||
```
|
||||
|
||||
这些位置的读写**会跳过容器存储层**,直接写入宿主机,性能更好,也不会随容器删除而丢失。
|
||||
|
||||
## 容器的生命周期
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────┐
|
||||
│ 容器生命周期 │
|
||||
└──────────────────────────────────────────────────┘
|
||||
|
||||
docker create docker start docker stop
|
||||
│ │ │
|
||||
▼ ▼ ▼
|
||||
┌─────────┐ ┌─────────┐ ┌─────────┐
|
||||
│ Created │───────────▶│ Running │───────────▶│ Stopped │
|
||||
└─────────┘ └─────────┘ └─────────┘
|
||||
│ │ │
|
||||
│ │ docker pause │
|
||||
│ ▼ │
|
||||
│ ┌─────────┐ │
|
||||
│ │ Paused │ │
|
||||
│ └─────────┘ │
|
||||
│ │ │
|
||||
│ docker rm │ docker rm │
|
||||
└───────────────────────┴──────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌──────────┐
|
||||
│ Deleted │
|
||||
└──────────┘
|
||||
```
|
||||
|
||||
### 常用生命周期命令
|
||||
|
||||
```bash
|
||||
# 创建并启动容器(最常用)
|
||||
$ docker run nginx
|
||||
|
||||
# 分步操作
|
||||
$ docker create nginx # 创建容器(不启动)
|
||||
$ docker start abc123 # 启动容器
|
||||
|
||||
# 停止容器
|
||||
$ docker stop abc123 # 优雅停止(发送 SIGTERM,等待后发送 SIGKILL)
|
||||
$ docker kill abc123 # 强制停止(直接发送 SIGKILL)
|
||||
|
||||
# 暂停/恢复(不常用,但有时有用)
|
||||
$ docker pause abc123 # 暂停容器内所有进程
|
||||
$ docker unpause abc123 # 恢复
|
||||
|
||||
# 删除容器
|
||||
$ docker rm abc123 # 删除已停止的容器
|
||||
$ docker rm -f abc123 # 强制删除运行中的容器
|
||||
```
|
||||
|
||||
## 容器与进程的关系
|
||||
|
||||
> **核心概念**:容器的生命周期 = 主进程(PID 1)的生命周期
|
||||
|
||||
```bash
|
||||
# 主进程运行,容器运行
|
||||
# 主进程退出,容器停止
|
||||
```
|
||||
|
||||
这就是为什么:
|
||||
|
||||
```bash
|
||||
# 这个容器会立即退出(bash 没有输入就退出了)
|
||||
$ docker run ubuntu
|
||||
|
||||
# 这个容器会持续运行(nginx 作为守护进程持续运行)
|
||||
$ docker run nginx
|
||||
```
|
||||
|
||||
详细解释请参考[后台运行](../container/daemon.md)章节。
|
||||
|
||||
## 容器的隔离性
|
||||
|
||||
Docker 容器通过以下 Namespace 实现隔离:
|
||||
|
||||
| Namespace | 隔离内容 | 效果 |
|
||||
|-----------|---------|------|
|
||||
| **PID** | 进程 ID | 容器内 PID 1 是应用进程,看不到宿主机其他进程 |
|
||||
| **NET** | 网络 | 独立的网络栈、IP 地址、端口 |
|
||||
| **MNT** | 文件系统 | 独立的根目录和挂载点 |
|
||||
| **UTS** | 主机名 | 独立的主机名和域名 |
|
||||
| **IPC** | 进程间通信 | 独立的信号量、消息队列 |
|
||||
| **USER** | 用户 | 独立的用户和组 ID |
|
||||
|
||||
> 想深入了解?请阅读[底层实现 - 命名空间](../underly/namespace.md)。
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **容器是什么** | 镜像的运行实例,本质是隔离的进程 |
|
||||
| **容器 vs 虚拟机** | 共享内核,更轻量,但隔离性较弱 |
|
||||
| **存储层** | 可写层随容器删除而消失 |
|
||||
| **数据持久化** | 使用 Volume 或 Bind Mount |
|
||||
| **生命周期** | 与主进程(PID 1)绑定 |
|
||||
|
||||
理解了镜像和容器,接下来让我们学习[仓库](repository.md)——存储和分发镜像的服务。
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [启动容器](../container/run.md):详细的容器启动选项
|
||||
- [后台运行](../container/daemon.md):理解容器为什么会"立即退出"
|
||||
- [进入容器](../container/attach_exec.md):如何操作运行中的容器
|
||||
- [数据管理](../data_management/README.md):Volume 和数据持久化详解
|
||||
|
||||
@@ -1,15 +1,222 @@
|
||||
# Docker 镜像
|
||||
|
||||
我们都知道,操作系统分为 **内核** 和 **用户空间**。对于 `Linux` 而言,内核启动后,会挂载 `root` 文件系统为其提供用户空间支持。而 **Docker 镜像**(`Image`),就相当于是一个 `root` 文件系统。比如官方镜像 `ubuntu:24.04` 就包含了完整的一套 Ubuntu 24.04 最小系统的 `root` 文件系统。
|
||||
## 一句话理解镜像
|
||||
|
||||
**Docker 镜像** 是一个特殊的文件系统,除了提供容器运行时所需的程序、库、资源、配置等文件外,还包含了一些为运行时准备的一些配置参数(如匿名卷、环境变量、用户等)。镜像 **不包含** 任何动态数据,其内容在构建之后也不会被改变。
|
||||
> **Docker 镜像是一个只读的模板,包含了运行应用所需的一切:代码、运行时、库、环境变量和配置文件。**
|
||||
|
||||
## 分层存储
|
||||
如果用一个类比:**镜像就像是一张光盘或 ISO 文件**。你可以用同一张光盘在不同电脑上安装系统,而光盘本身不会被修改。同样,一个镜像可以创建多个容器,而镜像本身保持不变。
|
||||
|
||||
因为镜像包含操作系统完整的 `root` 文件系统,其体积往往是庞大的,因此在 Docker 设计时,就充分利用 [Union FS](https://en.wikipedia.org/wiki/Union_mount) 的技术,将其设计为分层存储的架构。所以严格来说,镜像并非是像一个 `ISO` 那样的打包文件,镜像只是一个虚拟的概念,其实际体现并非由一个文件组成,而是由一组文件系统组成,或者说,由多层文件系统联合组成。
|
||||
## 镜像与操作系统的关系
|
||||
|
||||
镜像构建时,会一层层构建,前一层是后一层的基础。每一层构建完就不会再发生改变,后一层上的任何改变只发生在自己这一层。比如,删除前一层文件的操作,实际不是真的删除前一层的文件,而是仅在当前层标记为该文件已删除。在最终容器运行的时候,虽然不会看到这个文件,但是实际上该文件会一直跟随镜像。因此,在构建镜像的时候,需要额外小心,每一层尽量只包含该层需要添加的东西,任何额外的东西应该在该层构建结束前清理掉。
|
||||
我们都知道,操作系统分为**内核**和**用户空间**:
|
||||
|
||||
分层存储的特征还使得镜像的复用、定制变的更为容易。甚至可以用之前构建好的镜像作为基础层,然后进一步添加新的层,以定制自己所需的内容,构建新的镜像。
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────┐
|
||||
│ 用户空间 │
|
||||
│ ┌─────────────────────────────────────────────────────┐ │
|
||||
│ │ 应用程序、工具、库、配置文件... │ │
|
||||
│ │ (这部分被打包成 Docker 镜像) │ │
|
||||
│ └─────────────────────────────────────────────────────┘ │
|
||||
├─────────────────────────────────────────────────────────────┤
|
||||
│ Linux 内核 │
|
||||
│ (容器共享宿主机的内核) │
|
||||
└─────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
关于镜像构建,将会在后续相关章节中做进一步的讲解。
|
||||
对于 Linux 而言,内核启动后会挂载 `root` 文件系统来提供用户空间支持。**Docker 镜像**本质上就是一个 `root` 文件系统。
|
||||
|
||||
例如,官方镜像 `ubuntu:24.04` 包含了一套完整的 Ubuntu 24.04 最小系统的 root 文件系统——但**不包含 Linux 内核**(因为容器共享宿主机的内核)。
|
||||
|
||||
## 镜像包含什么?
|
||||
|
||||
Docker 镜像是一个特殊的文件系统,包含:
|
||||
|
||||
| 内容类型 | 示例 |
|
||||
|---------|------|
|
||||
| **程序文件** | 应用二进制文件、Python/Node 解释器 |
|
||||
| **库文件** | libc、OpenSSL、各种依赖库 |
|
||||
| **配置文件** | nginx.conf、my.cnf 等 |
|
||||
| **环境变量** | PATH、LANG 等预设值 |
|
||||
| **元数据** | 启动命令、暴露端口、数据卷定义 |
|
||||
|
||||
**关键特性**:
|
||||
- ✅ 镜像是**只读**的
|
||||
- ✅ 镜像**不包含**动态数据
|
||||
- ✅ 镜像构建后**内容不会改变**
|
||||
|
||||
## 分层存储:镜像的核心设计
|
||||
|
||||
### 为什么需要分层?
|
||||
|
||||
笔者认为,分层存储是 Docker 最巧妙的设计之一。
|
||||
|
||||
假设你有三个应用,都基于 Ubuntu 运行:
|
||||
|
||||
```
|
||||
传统方式(不分层):
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ App A │ │ App B │ │ App C │
|
||||
│ Ubuntu │ │ Ubuntu │ │ Ubuntu │
|
||||
│ 500MB │ │ 500MB │ │ 500MB │
|
||||
└─────────────┘ └─────────────┘ └─────────────┘
|
||||
总计:1.5GB ❌
|
||||
|
||||
Docker 分层方式:
|
||||
┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||
│ App A │ │ App B │ │ App C │
|
||||
│ 50MB │ │ 30MB │ │ 40MB │
|
||||
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘
|
||||
│ │ │
|
||||
└────────────────┼────────────────┘
|
||||
▼
|
||||
┌─────────────────┐
|
||||
│ Ubuntu │
|
||||
│ (共享)500MB │
|
||||
└─────────────────┘
|
||||
总计:620MB ✅
|
||||
```
|
||||
|
||||
### 分层是如何工作的?
|
||||
|
||||
笔者用一个实际的 Dockerfile 来解释分层:
|
||||
|
||||
```docker
|
||||
FROM ubuntu:24.04 # 第 1 层:基础系统(约 78MB)
|
||||
RUN apt-get update # 第 2 层:更新包索引
|
||||
RUN apt-get install nginx # 第 3 层:安装 nginx
|
||||
COPY app.conf /etc/nginx/ # 第 4 层:复制配置文件
|
||||
```
|
||||
|
||||
构建后的镜像结构:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────┐
|
||||
│ 第 4 层: COPY app.conf (只读) │ ← 最新添加的层
|
||||
├─────────────────────────────────────┤
|
||||
│ 第 3 层: nginx 安装文件 (只读) │
|
||||
├─────────────────────────────────────┤
|
||||
│ 第 2 层: apt 缓存更新 (只读) │
|
||||
├─────────────────────────────────────┤
|
||||
│ 第 1 层: Ubuntu 基础系统 (只读) │ ← 基础镜像层
|
||||
└─────────────────────────────────────┘
|
||||
```
|
||||
|
||||
每一层的特点:
|
||||
- **只读**:构建完成后不可修改
|
||||
- **可共享**:多个镜像可以共享相同的层
|
||||
- **有缓存**:未变化的层不会重新构建
|
||||
|
||||
### 分层存储的"陷阱"
|
||||
|
||||
> ⚠️ **笔者特别提醒**:理解这一点可以帮你避免构建出臃肿的镜像。
|
||||
|
||||
**关键原理**:每一层的文件变化会被记录,但**删除操作只是标记,不会真正减小镜像体积**。
|
||||
|
||||
```docker
|
||||
# 错误示范 ❌
|
||||
FROM ubuntu:24.04
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y build-essential # 安装编译工具(约 200MB)
|
||||
RUN make && make install # 编译应用
|
||||
RUN apt-get remove build-essential # 试图删除编译工具
|
||||
# 结果:镜像仍然包含 200MB 的编译工具!
|
||||
```
|
||||
|
||||
```docker
|
||||
# 正确做法 ✅
|
||||
FROM ubuntu:24.04
|
||||
RUN apt-get update && \
|
||||
apt-get install -y build-essential && \
|
||||
make && make install && \
|
||||
apt-get remove -y build-essential && \
|
||||
apt-get autoremove -y && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
# 在同一层完成安装、使用、清理
|
||||
```
|
||||
|
||||
### 查看镜像的分层
|
||||
|
||||
```bash
|
||||
# 查看镜像的历史(每层的构建记录)
|
||||
$ docker history nginx:latest
|
||||
|
||||
IMAGE CREATED CREATED BY SIZE
|
||||
a6bd71f48f68 2 weeks ago CMD ["nginx" "-g" "daemon off;"] 0B
|
||||
<missing> 2 weeks ago STOPSIGNAL SIGQUIT 0B
|
||||
<missing> 2 weeks ago EXPOSE map[80/tcp:{}] 0B
|
||||
<missing> 2 weeks ago ENTRYPOINT ["/docker-entrypoint.sh"] 0B
|
||||
<missing> 2 weeks ago COPY 30-tune-worker-processes.sh /docker-ent… 4.62kB
|
||||
...
|
||||
```
|
||||
|
||||
## 镜像的标识
|
||||
|
||||
Docker 镜像有多种标识方式:
|
||||
|
||||
### 1. 镜像名称和标签
|
||||
|
||||
格式:`[仓库地址/]仓库名[:标签]`
|
||||
|
||||
```bash
|
||||
# 完整格式
|
||||
registry.example.com/myproject/myapp:v1.2.3
|
||||
|
||||
# 简写(使用 Docker Hub)
|
||||
nginx:1.25
|
||||
ubuntu:24.04
|
||||
|
||||
# 省略标签(默认使用 latest)
|
||||
nginx # 等同于 nginx:latest
|
||||
```
|
||||
|
||||
### 2. 镜像 ID(Content-Addressable)
|
||||
|
||||
每个镜像有一个基于内容计算的唯一 ID:
|
||||
|
||||
```bash
|
||||
$ docker images
|
||||
REPOSITORY TAG IMAGE ID CREATED SIZE
|
||||
nginx latest a6bd71f48f68 2 weeks ago 187MB
|
||||
ubuntu 24.04 ca2b0f26964c 3 weeks ago 78.1MB
|
||||
```
|
||||
|
||||
### 3. 镜像摘要(Digest)
|
||||
|
||||
更精确的标识,基于镜像内容的 SHA256 哈希:
|
||||
|
||||
```bash
|
||||
$ docker images --digests
|
||||
REPOSITORY TAG DIGEST IMAGE ID
|
||||
nginx latest sha256:6db391d1c0cfb30588ba0bf72ea999404f2764184d8b8d10d89e8a9c6... a6bd71f48f68
|
||||
```
|
||||
|
||||
> 💡 笔者建议:在生产环境使用镜像摘要而非标签,因为标签可以被覆盖,但摘要是不可变的。
|
||||
|
||||
## 镜像的来源
|
||||
|
||||
Docker 镜像可以通过以下方式获取:
|
||||
|
||||
| 方式 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **从 Registry 拉取** | 最常用的方式 | `docker pull nginx` |
|
||||
| **从 Dockerfile 构建** | 自定义镜像 | `docker build -t myapp .` |
|
||||
| **从容器提交** | 保存容器状态(不推荐) | `docker commit` |
|
||||
| **从文件导入** | 离线传输 | `docker load < image.tar` |
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **镜像是什么** | 只读的应用模板,包含运行所需的一切 |
|
||||
| **分层存储** | 多层叠加,共享基础层,节省空间 |
|
||||
| **只读特性** | 构建后不可修改,保证一致性 |
|
||||
| **层的陷阱** | 删除操作只是标记,不减小体积 |
|
||||
|
||||
理解了镜像,接下来让我们学习[容器](container.md)——镜像的运行实例。
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [获取镜像](../image/pull.md):从 Registry 下载镜像
|
||||
- [使用 Dockerfile 定制镜像](../image/build.md):创建自己的镜像
|
||||
- [Dockerfile 最佳实践](../appendix/best_practices.md):构建高质量镜像的技巧
|
||||
- [底层实现 - 联合文件系统](../underly/ufs.md):深入理解分层存储的技术原理
|
||||
|
||||
@@ -1,29 +1,250 @@
|
||||
# Docker Registry
|
||||
|
||||
镜像构建完成后,可以很容易的在当前宿主机上运行,但是,如果需要在其它服务器上使用这个镜像,我们就需要一个集中的存储、分发镜像的服务,[Docker Registry](../repository/registry.md) 就是这样的服务。
|
||||
## 一句话理解 Registry
|
||||
|
||||
一个 **Docker Registry** 中可以包含多个 **仓库**(`Repository`);每个仓库可以包含多个 **标签**(`Tag`);每个标签对应一个镜像。
|
||||
> **Docker Registry 是存储和分发 Docker 镜像的服务,类似于代码的 GitHub 或包管理的 npm。**
|
||||
|
||||
通常,一个仓库会包含同一个软件不同版本的镜像,而标签就常用于对应该软件的各个版本。我们可以通过 `<仓库名>:<标签>` 的格式来指定具体是这个软件哪个版本的镜像。如果不给出标签,将以 `latest` 作为默认标签。
|
||||
镜像构建完成后,可以在当前机器上运行。但如果需要在其他服务器上使用这个镜像,就需要一个集中的存储和分发服务——这就是 Docker Registry。
|
||||
|
||||
以 [Ubuntu 镜像](https://hub.docker.com/_/ubuntu) 为例,`ubuntu` 是仓库的名字,其内包含有不同的版本标签,如,`22.04`, `24.04`。我们可以通过 `ubuntu:22.04`,或者 `ubuntu:24.04` 来具体指定所需哪个版本的镜像。如果忽略了标签,比如 `ubuntu`,那将视为 `ubuntu:latest`。
|
||||
## 核心概念
|
||||
|
||||
仓库名经常以 *两段式路径* 形式出现,比如 `jwilder/nginx-proxy`,前者往往意味着 Docker Registry 多用户环境下的用户名,后者则往往是对应的软件名。但这并非绝对,取决于所使用的具体 Docker Registry 的软件或服务。
|
||||
### Registry、仓库、标签的关系
|
||||
|
||||
## Docker Registry 公开服务
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────┐
|
||||
│ Docker Registry │
|
||||
│ (如 Docker Hub) │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Repository(仓库): nginx │ │
|
||||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
||||
│ │ │ :latest │ │ :1.25 │ │ :1.24 │ │ :alpine │ ... │ │
|
||||
│ │ │ (tag) │ │ (tag) │ │ (tag) │ │ (tag) │ │ │
|
||||
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
│ │
|
||||
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||
│ │ Repository(仓库): mysql │ │
|
||||
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
||||
│ │ │ :latest │ │ :8.0 │ │ :5.7 │ ... │ │
|
||||
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
|
||||
│ └─────────────────────────────────────────────────────────────┘ │
|
||||
└─────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
Docker Registry 公开服务是开放给用户使用、允许用户管理镜像的 Registry 服务。一般这类公开服务允许用户免费上传、下载公开的镜像,并可能提供收费服务供用户管理私有镜像。
|
||||
| 概念 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| **Registry** | 存储镜像的服务 | Docker Hub、ghcr.io |
|
||||
| **Repository(仓库)** | 同一软件的镜像集合 | `nginx`、`mysql`、`mycompany/myapp` |
|
||||
| **Tag(标签)** | 仓库内的版本标识 | `latest`、`1.25`、`alpine` |
|
||||
|
||||
最常使用的 Registry 公开服务是官方的 [Docker Hub](https://hub.docker.com/),这也是默认的 Registry,并拥有大量的高质量的 [官方镜像](https://hub.docker.com/search?q=&type=image&image_filter=official)。除此以外,还有 Red Hat 的 [Quay.io](https://quay.io/repository/);Google 的 [Google Container Registry](https://cloud.google.com/container-registry/),[Kubernetes](https://kubernetes.io/) 的镜像使用的就是这个服务;代码托管平台 [GitHub](https://github.com) 推出的 [ghcr.io](https://docs.github.com/cn/packages/working-with-a-github-packages-registry/working-with-the-container-registry)。
|
||||
### 镜像的完整名称
|
||||
|
||||
由于某些原因,在国内访问这些服务可能会比较慢。国内的一些云服务商提供了针对 Docker Hub 的镜像服务(`Registry Mirror`),这些镜像服务被称为 **加速器**。常见的有 [阿里云加速器](https://www.aliyun.com/product/acr?source=5176.11533457&userCode=8lx5zmtu)、[DaoCloud 加速器](https://www.daocloud.io/mirror#accelerator-doc) 等。使用加速器会直接从国内的地址下载 Docker Hub 的镜像,比直接从 Docker Hub 下载速度会提高很多。在 [安装 Docker](../install/mirror.md) 一节中有详细的配置方法。
|
||||
```
|
||||
[registry地址/][用户名/]仓库名[:标签]
|
||||
```
|
||||
|
||||
国内也有一些云服务商提供类似于 Docker Hub 的公开服务。比如 [网易云镜像服务](https://c.163.com/hub#/m/library/)、[DaoCloud 镜像市场](https://hub.daocloud.io/)、[阿里云镜像库](https://www.aliyun.com/product/acr?source=5176.11533457&userCode=8lx5zmtu) 等。
|
||||
示例:
|
||||
|
||||
## 私有 Docker Registry
|
||||
```bash
|
||||
# 完整格式
|
||||
registry.example.com/mycompany/myapp:v1.2.3
|
||||
│ │ │ │
|
||||
│ │ │ └── 标签
|
||||
│ │ └── 仓库名
|
||||
│ └── 用户名/组织名
|
||||
└── Registry 地址
|
||||
|
||||
除了使用公开服务外,用户还可以在本地搭建私有 Docker Registry。Docker 官方提供了 [Docker Registry](https://hub.docker.com/_/registry/) 镜像,可以直接使用做为私有 Registry 服务。在 [私有仓库](../repository/registry.md) 一节中,会有进一步的搭建私有 Registry 服务的讲解。
|
||||
# Docker Hub 官方镜像(省略 registry 和用户名)
|
||||
nginx:1.25
|
||||
ubuntu:24.04
|
||||
|
||||
开源的 Docker Registry 镜像只提供了 [Docker Registry API](https://docs.docker.com/registry/spec/api/) 的服务端实现,足以支持 `docker` 命令,不影响使用。但不包含图形界面,以及镜像维护、用户管理、访问控制等高级功能。
|
||||
# Docker Hub 用户镜像
|
||||
jwilder/nginx-proxy:latest
|
||||
|
||||
除了官方的 Docker Registry 外,还有第三方软件实现了 Docker Registry API,甚至提供了用户界面以及一些高级功能。比如,[Harbor](https://github.com/goharbor/harbor) 和 [Sonatype Nexus](../repository/nexus3_registry.md)。
|
||||
# 其他 Registry
|
||||
ghcr.io/username/myapp:v1.0
|
||||
gcr.io/google-containers/pause:3.6
|
||||
```
|
||||
|
||||
> 💡 **笔者提示**:如果不指定 Registry 地址,默认使用 Docker Hub。如果不指定标签,默认使用 `latest`。
|
||||
|
||||
## 公共 Registry 服务
|
||||
|
||||
### Docker Hub(默认)
|
||||
|
||||
[Docker Hub](https://hub.docker.com/) 是最大的公共 Registry,也是 Docker 的默认 Registry。
|
||||
|
||||
**特点**:
|
||||
- 拥有大量[官方镜像](https://hub.docker.com/search?q=&type=image&image_filter=official)(nginx、mysql、redis 等)
|
||||
- 免费账户可以创建公开仓库
|
||||
- 付费账户支持私有仓库
|
||||
|
||||
```bash
|
||||
# 从 Docker Hub 拉取镜像
|
||||
$ docker pull nginx # 官方镜像
|
||||
$ docker pull bitnami/redis # 第三方镜像
|
||||
|
||||
# 推送镜像到 Docker Hub
|
||||
$ docker login
|
||||
$ docker push username/myapp:v1.0
|
||||
```
|
||||
|
||||
### 其他公共 Registry
|
||||
|
||||
| Registry | 地址 | 说明 |
|
||||
|----------|------|------|
|
||||
| **GitHub Container Registry** | ghcr.io | GitHub 提供,与 GitHub Actions 集成好 |
|
||||
| **Google Container Registry** | gcr.io | Google Cloud 提供,Kubernetes 镜像常用 |
|
||||
| **Quay.io** | quay.io | Red Hat 提供 |
|
||||
| **阿里云容器镜像服务** | registry.cn-*.aliyuncs.com | 国内访问快 |
|
||||
| **腾讯云容器镜像服务** | ccr.ccs.tencentyun.com | 国内访问快 |
|
||||
|
||||
## 镜像加速器
|
||||
|
||||
由于网络原因,在国内直接访问 Docker Hub 可能会很慢。可以配置**镜像加速器**(Registry Mirror)来加速下载。
|
||||
|
||||
```json
|
||||
// /etc/docker/daemon.json
|
||||
{
|
||||
"registry-mirrors": [
|
||||
"https://your-accelerator-url"
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
详细配置方法请参考[镜像加速器](../install/mirror.md)章节。
|
||||
|
||||
> ⚠️ **笔者提醒**:镜像加速器的可用性经常变化,使用前建议先测试是否可用。
|
||||
|
||||
## 私有 Registry
|
||||
|
||||
对于企业用户,通常需要搭建私有 Registry 来存储内部镜像。
|
||||
|
||||
### 官方 Registry 镜像
|
||||
|
||||
Docker 官方提供了 [registry](https://hub.docker.com/_/registry/) 镜像,可以快速搭建私有 Registry:
|
||||
|
||||
```bash
|
||||
# 启动一个本地 Registry
|
||||
$ docker run -d -p 5000:5000 --name registry registry:2
|
||||
|
||||
# 推送镜像到本地 Registry
|
||||
$ docker tag myapp:v1.0 localhost:5000/myapp:v1.0
|
||||
$ docker push localhost:5000/myapp:v1.0
|
||||
|
||||
# 从本地 Registry 拉取
|
||||
$ docker pull localhost:5000/myapp:v1.0
|
||||
```
|
||||
|
||||
### 企业级解决方案
|
||||
|
||||
官方 Registry 功能较为基础,企业环境常用以下方案:
|
||||
|
||||
| 方案 | 特点 |
|
||||
|------|------|
|
||||
| **[Harbor](https://goharbor.io/)** | CNCF 项目,功能全面(用户管理、漏洞扫描、镜像签名) |
|
||||
| **[Nexus Repository](../repository/nexus3_registry.md)** | 支持多种制品类型(Docker、Maven、npm 等) |
|
||||
| **云厂商服务** | 阿里云 ACR、腾讯云 TCR、AWS ECR 等 |
|
||||
|
||||
笔者建议:
|
||||
- 小团队:可以先用官方 Registry,够用即可
|
||||
- 中大型团队:推荐 Harbor,功能完善且开源免费
|
||||
- 已使用云服务:直接用云厂商的 Registry 服务更省心
|
||||
|
||||
## 镜像的推送和拉取
|
||||
|
||||
### 完整工作流程
|
||||
|
||||
```
|
||||
开发者机器 Registry 生产服务器
|
||||
│ │ │
|
||||
│ docker build │ │
|
||||
│ 构建镜像 │ │
|
||||
│ │ │
|
||||
│ docker push ─────────────▶ │
|
||||
│ 推送镜像 │ 存储镜像 │
|
||||
│ │ │
|
||||
│ │ ◀───────────── docker pull │
|
||||
│ │ 拉取镜像 │
|
||||
│ │ │
|
||||
│ │ docker run │
|
||||
│ │ 运行容器 │
|
||||
```
|
||||
|
||||
### 常用命令
|
||||
|
||||
```bash
|
||||
# 登录 Registry
|
||||
$ docker login # 登录 Docker Hub
|
||||
$ docker login registry.example.com # 登录其他 Registry
|
||||
|
||||
# 拉取镜像
|
||||
$ docker pull nginx:1.25
|
||||
|
||||
# 标记镜像(准备推送)
|
||||
$ docker tag myapp:latest registry.example.com/myteam/myapp:v1.0
|
||||
|
||||
# 推送镜像
|
||||
$ docker push registry.example.com/myteam/myapp:v1.0
|
||||
|
||||
# 登出
|
||||
$ docker logout
|
||||
```
|
||||
|
||||
## 镜像的安全性
|
||||
|
||||
### 使用官方镜像
|
||||
|
||||
Docker Hub 的[官方镜像](https://hub.docker.com/search?q=&type=image&image_filter=official)(标有 "Official Image" 标识)经过 Docker 团队审核,相对更安全。
|
||||
|
||||
```bash
|
||||
# 官方镜像示例
|
||||
nginx # ✅ 官方
|
||||
mysql # ✅ 官方
|
||||
redis # ✅ 官方
|
||||
|
||||
# 第三方镜像(需要自行评估可信度)
|
||||
bitnami/redis # ⚠️ 需要评估
|
||||
someuser/myapp # ⚠️ 需要评估
|
||||
```
|
||||
|
||||
### 镜像签名
|
||||
|
||||
使用 Docker Content Trust (DCT) 验证镜像来源:
|
||||
|
||||
```bash
|
||||
# 启用镜像签名验证
|
||||
$ export DOCKER_CONTENT_TRUST=1
|
||||
|
||||
# 此后的 pull/push 会验证签名
|
||||
$ docker pull nginx:latest
|
||||
```
|
||||
|
||||
### 漏洞扫描
|
||||
|
||||
```bash
|
||||
# 使用 Docker Scout 扫描镜像漏洞
|
||||
$ docker scout cves nginx:latest
|
||||
|
||||
# 使用 Trivy(开源工具)
|
||||
$ trivy image nginx:latest
|
||||
```
|
||||
|
||||
## 本章小结
|
||||
|
||||
| 概念 | 要点 |
|
||||
|------|------|
|
||||
| **Registry** | 存储和分发镜像的服务 |
|
||||
| **仓库(Repository)** | 同一软件的镜像集合 |
|
||||
| **标签(Tag)** | 版本标识,默认为 latest |
|
||||
| **Docker Hub** | 默认的公共 Registry |
|
||||
| **私有 Registry** | 企业内部使用,推荐 Harbor |
|
||||
|
||||
现在你已经了解了 Docker 的三个核心概念:[镜像](image.md)、[容器](container.md)和仓库。接下来,让我们开始[安装 Docker](../install/README.md),动手实践!
|
||||
|
||||
## 延伸阅读
|
||||
|
||||
- [Docker Hub](../repository/dockerhub.md):Docker Hub 的详细使用
|
||||
- [私有仓库](../repository/registry.md):搭建私有 Registry
|
||||
- [私有仓库高级配置](../repository/registry_auth.md):认证、TLS 配置
|
||||
- [镜像加速器](../install/mirror.md):配置镜像加速
|
||||
|
||||
Reference in New Issue
Block a user