Files
docker_practice/02_basic_concept/2.2_container.md
T
2026-04-21 14:39:41 -07:00

7.8 KiB
Raw Blame History

2.2 容器

容器是 Docker 技术的核心,是应用实际运行的载体。本节将从容器的本质、与虚拟机的区别、存储层机制以及生命周期管理等方面,全面解析 Docker 容器。

2.2.1 一句话理解容器

容器是镜像的运行实例。如果把镜像比作程序,那么容器就是进程。 用面向对象编程的术语来说:镜像是类 (Class),容器是对象 (Instance)

  • 一个镜像可以创建多个容器
  • 每个容器相互独立,互不影响
  • 容器可以被创建、启动、停止、删除、暂停

2.2.2 容器的本质

💡 笔者认为,理解这一点是理解 Docker 的关键:容器的本质是一个特殊的进程

flowchart TD
    subgraph NormalProcess ["普通进程"]
        direction TB
        N1["• 共享系统资源<br/>• 共享网络<br/>• 共享文件系统"]
    end

    subgraph ContainerProcess ["容器进程 (运行在宿主机内核上)"]
        direction TB
        C1["• 独立进程空间<br/>• 独立网络环境<br/>• 独立文件系统<br/>• 独立用户空间"]
    end

这种隔离主要通过 Linux 内核的 Namespace 实现,资源限制通常与 cgroups 配合。具体表现为:

  • 进程空间:容器看不到宿主机上的其他进程。
  • 网络:在默认网络模式下,容器通常拥有独立的网络命名空间,并可分配独立 IP;使用 hostcontainer: 等模式时则例外。
  • 文件系统:容器拥有独立的 root 目录。
  • 用户:默认情况下,容器内的 root 仍是 uid 0,但通常只拥有受限 capabilities;如果启用 userns-remap 或 rootless 等机制,还会进一步映射为宿主机上的低权限用户。

2.2.3 容器 vs 虚拟机:核心区别

很多初学者会混淆容器和虚拟机。笔者用一张图来说明:

flowchart TD
    subgraph VM ["虚拟机 (每个 VM 运行完整 OS)"]
        direction TB
        subgraph VMApp ["应用层"]
            VA[App A] & VB[App B]
        end
        subgraph VMGuest ["Guest OS (完整系统)"]
            G1[Guest OS] & G2[Guest OS]
        end
        V[Hypervisor]
        VMH[Host OS]
        VMHW[Hardware]
        VMApp --> VMGuest --> V --> VMH --> VMHW
    end

    subgraph Container ["容器 (所有容器共享宿主机内核)"]
        direction TB
        subgraph CApp ["应用层"]
            CA[App A] & CB[App B]
        end
        subgraph CContainer ["隔离层"]
            CC1[Container 仅应用] & CC2[Container 仅应用]
        end
        CE[Docker Engine]
        CH[Host OS]
        CHW[Hardware]
        CApp --> CContainer --> CE --> CH --> CHW
    end
特性 容器 虚拟机
隔离级别 操作系统级 (Namespaces/cgroups) 硬件虚拟化级 (Hypervisor)
启动时间 通常更快 通常更慢
资源占用 通常更低 通常更高
运行开销 通常更接近原生 通常有更高虚拟化开销
内核 共享宿主机内核 各自独立内核

2.2.4 容器的存储层

理解容器的存储层机制对于数据的持久化和镜像的优化至关重要。本节将介绍容器的可写层以及 Copy-on-Write 机制。

镜像层 + 容器层

当容器运行时,Docker 会在镜像的只读层之上创建一个 可写层 (容器存储层)

flowchart TD
    ContainerLayer["容器存储层(可读写)<br/>容器运行时创建,记录文件变化"]
    ImageLayerN["镜像第 N 层(只读)"]
    ImageLayerN1["镜像第 N-1 层(只读)"]
    Dots["..."]
    ImageLayer1["镜像第 1 层(只读)<br/>基础镜像层"]

    ContainerLayer --> ImageLayerN --> ImageLayerN1 --> Dots --> ImageLayer1

Copy-on-Write:写时复制

当容器需要修改镜像层中的文件时:

  1. Docker 将该文件 复制 到容器存储层
  2. 在容器层中进行修改
  3. 原始镜像层保持不变
读取文件:直接从镜像层读取(共享,高效)
修改文件:复制到容器层,然后修改(只有这个容器能看到修改)

⚠️ 容器存储层的生命周期

笔者特别强调:这是新手最容易踩的坑!容器存储层与容器生命周期绑定。容器删除,数据就没了!

## 创建容器,写入数据

$ docker run -it ubuntu bash
root@abc123:/# echo "important data" > /data.txt
root@abc123:/# exit

## 删除容器

$ docker rm abc123

## 数据丢了!没有任何办法恢复!

正确的数据持久化方式

按照 Docker 最佳实践,容器存储层应该保持 无状态。需要持久化的数据应该使用:

方式 说明 适用场景
数据卷 (Volume) Docker 管理的存储 数据库、应用数据
绑定挂载 (Bind Mount) 挂载宿主机目录 开发时共享代码
## 使用数据卷(推荐)

$ docker run -v mydata:/var/lib/mysql mysql

## 使用绑定挂载

$ docker run -v /host/path:/container/path nginx

这些位置的读写 会跳过容器存储层,直接写入宿主机,性能更好,也不会随容器删除而丢失。

2.2.5 容器的生命周期

掌握容器的生命周期对于管理和调试 Docker 应用非常重要。如图 2-1 所示,这里先聚焦最常见的创建、运行、暂停、停止和删除流转;Docker CLI 中还可能看到 restartingremovingdead 等状态。

stateDiagram-v2
    direction TB
    [*] --> Created : docker create
    Created --> Running : docker start
    Running --> Stopped : docker stop
    Running --> Paused : docker pause
    Paused --> Running : docker unpause

    Created --> Deleted : docker rm
    Stopped --> Deleted : docker rm
    Paused --> Deleted : docker rm

    Deleted --> [*]

图 2-1:容器生命周期状态流转图

常用生命周期命令如下:

## 创建并启动容器(最常用)

$ 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    # 强制删除运行中的容器

2.2.6 容器与进程的关系

核心概念:容器的生命周期 = 主进程 (PID 1) 的生命周期

## 主进程运行,容器运行

## 主进程退出,容器停止

这就是为什么:

## 这个容器会立即退出(bash 没有输入就退出了)

$ docker run ubuntu

## 这个容器会持续运行(官方 nginx 镜像让 nginx 在前台运行)

$ docker run nginx

官方 nginx 镜像默认使用 nginx -g 'daemon off;',让主进程保持在前台运行,这样容器才会持续处于运行状态。

详细解释请参考后台运行章节。

2.2.7 容器的隔离性

Docker 容器通过以下 Namespace 实现隔离:

Namespace 隔离内容 效果
PID 进程 ID 容器内 PID 1 是应用进程,看不到宿主机其他进程
NET 网络 独立的网络栈、IP 地址、端口
MNT 文件系统 独立的根目录和挂载点
UTS 主机名 独立的主机名和域名
IPC 进程间通信 独立的信号量、消息队列
USER 用户 独立的用户和组 ID

想深入了解?请阅读底层实现 - 命名空间