Hexo

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

Docker——容器深入理解


整体说明

  • TLDR:容器的本质是封装了指定命令的进程

容器生命周期管理

  • 启动容器:
    • docker run [OPTIONS] IMAGE [COMMAND] [ARG...]:该命令用于从镜像创建并启动一个新的容器
      • [COMMAND] 是希望在容器启动时执行的程序或脚本,而 [ARG…] 就是这个程序或脚本运行所需的额外信息,比如 ls -l /
    • docker start [OPTIONS] CONTAINER [CONTAINER...]:该命令用于启动一个或多个已经停止的容器
    • 常用选项:
      • -d:后台运行容器
      • -p:端口映射,如 -p 8080:80 将宿主机的 8080 端口映射到容器的 80 端口
      • -v:数据卷挂载,如 -v /host/data:/container/data 将宿主机的目录挂载到容器内,可多次使用 -v 参数挂在多个目录
      • --name:给容器指定一个名称,如 --name my-web-app
      • -it:交互式终端,i 保持标准输入打开,t 分配一个伪终端
  • 停止容器:
    • docker stop [OPTIONS] CONTAINER [CONTAINER...]:优雅地停止一个或多个运行中的容器
    • docker kill [OPTIONS] CONTAINER [CONTAINER...]:强制停止一个或多个容器,不进行任何清理
    • docker restart [OPTIONS] CONTAINER [CONTAINER...]:重启一个或多个容器
  • 删除容器:
    • docker rm [OPTIONS] CONTAINER [CONTAINER...]:删除一个或多个已停止的容器
    • docker rm -f CONTAINER:强制删除一个正在运行的容器

查看容器信息

  • 查看容器:
    • docker ps [OPTIONS]:列出所有正在运行的容器
    • 常用选项:
      • -a:列出所有容器,包括已停止的
      • -s:显示总文件大小
      • -l:显示最新创建的容器
  • 查看容器日志:
    • docker logs [OPTIONS] CONTAINER:获取容器的日志
    • 常用选项:
      • -f:实时跟踪日志输出
      • --tail N:仅显示最新的 N 行日志
  • 查看容器详细信息:
    • docker inspect [OPTIONS] CONTAINER [CONTAINER...]:获取容器的详细配置和状态信息,以 JSON 格式输出
  • 查看容器资源使用情况:
    • docker stats [OPTIONS] [CONTAINER...]:实时显示一个或多个容器的 CPU、内存、网络 I/O 和块 I/O 使用情况

容器内部操作

  • 进入容器内部:
    • docker exec [OPTIONS] CONTAINER COMMAND [ARG...]:在运行中的容器内执行命令。这是最常用的方式,因为它不会启动新的进程
    • 常用例子:
      • docker exec -it my-container bash:在名为 my-container 的容器中启动一个 bash 终端
      • docker exec my-container ls -l /:在容器中执行 ls -l / 命令
  • 复制文件到容器或从容器复制文件:
    • docker cp [OPTIONS] SRC_PATH CONTAINER:DEST_PATH:从宿主机复制文件到容器
    • docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH:从容器复制文件到宿主机

容器进阶管理

  • 暂停/恢复容器:
    • docker pause CONTAINER [CONTAINER...]:暂停容器内的所有进程。容器状态变为 paused,资源仍然被占用
    • docker unpause CONTAINER [CONTAINER...]:恢复被暂停的容器
  • 清理无用容器:
    • docker container prune:删除所有已停止的容器
  • 批量管理:
    • docker stop $(docker ps -a -q):停止所有容器
    • docker rm $(docker ps -a -q):删除所有容器
    • 注意 :docker ps -a -q 会列出所有容器的 ID

docker create 和 docker run 的区别

  • TLDR:**docker run = docker create + docker start**

docker create

  • docker create 命令只负责从一个镜像创建一个容器
  • docker create 它会为容器分配一个 ID,设置好配置(如端口映射、数据卷等),但不会启动容器
  • 总结 docker create 命令:
    • 只创建,不启动 :容器处于“已创建”(Created)状态
    • 分步操作 :您需要先使用 docker create 创建容器,然后再使用 docker start 来启动它
    • 适用场景 :当你需要先配置好容器,但暂时不希望它运行,或者需要对容器进行额外的配置或检查后才启动时,这个命令很有用
      • 例如,在自动化脚本中,您可以先批量创建容器,然后再按需启动

docker run

  • docker run 命令是 docker create 和 docker start 的组合
  • docker run 它从一个镜像创建一个新的容器,并立即启动它
  • 总结 docker run 命令:
    • 创建并启动 :这是最常用的方式,一步到位
    • 一步到位 :大部分情况下,你希望容器创建后立即运行,所以 docker run 更方便快捷
    • 适用场景 :这是日常开发和部署中最常见的命令,因为它简化了操作流程

示例

  • 如果想创建一个 Nginx 容器并让它在后台运行,可以使用:

    1
    docker run -d --name my-nginx nginx
  • 这一个命令就完成了创建和启动

  • 但如果你想分步操作,可以这么做:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 只创建容器,容器不会运行,注意,容器启动命令在这里传入
    docker create --name my-nginx-created nginx

    # 检查容器是否已创建(状态为 "Created")
    docker ps -a

    # 手动启动这个已创建的容器
    docker start my-nginx-created

    # 检查容器是否已启动(状态为 "Up")
    docker ps

附录:一个 docker 容器创建和启动的实例

  • 容器创建:

    1
    docker create --runtime=nvidia --gpus all --net=host --shm-size="10g" --cap-add=SYS_ADMIN -v .:/workspace/python_demo --name python_demo <image:tag> sleep infinity
    • --runtime=nvidia:使用 NVIDIA 容器运行时,支持 GPU 加速
    • --gpus all:允许容器使用所有可用 GPU
    • --net=host:使用主机网络模式,容器与主机共享网络栈
    • --shm-size="10g":设置共享内存大小为 10GB,提升进程间通信效率
    • --cap-add=SYS_ADMIN:添加系统管理员权限,允许容器内执行更多系统操作
    • -v .:/workspace/python_demo:将当前目录挂载到容器内的 /workspace/python_demo 目录
    • --name python_demo:给容器命名为 “python_demo”
    • <image:tag>:指定要使用的 Docker 镜像及标签
    • sleep infinity:容器启动后执行无限休眠命令,保持容器运行
  • 启动容器:

    1
    docker start python_demo
    • 启动名为 “python_demo” 的容器
    • 执行此命令后,之前创建的容器将开始运行
  • 进入容器终端与容器交互:

    1
    docker exec -it python_demo bash
    • 进入 “python_demo” 容器的 bash 交互终端
    • -it:以交互式终端模式进入容器
    • python_demo:目标容器名称
    • bash:在容器内启动 bash shell
  • 以上所有命令也可以使用一个 docker run 命令来实现


附录:Linux 中的 sleep infinity 命令常用于 docker 容器

  • 在 Linux 系统中,sleep infinity 命令会让进程进入无限期休眠状态
    • sleep 命令的作用是让当前进程暂停指定的时间
    • 当使用 infinity 作为参数时,它表示”无限长的时间”
    • 这会导致执行该命令的进程一直处于休眠状态,不会自动退出
    • 要终止这个进程,需要手动干预(通常使用 Ctrl+C 快捷键或 kill 命令)
  • 这种命令常用于需要保持容器运行的场景(如 Docker 容器),或者作为一种简单的方式让进程在后台持续运行而不占用过多系统资源
  • 由于进程处于休眠状态,它只会占用极少的系统资源(主要是进程表项)

终端于进程的关系

  • TLDR:终端存在是进程持续运行的一个条件(避免被 SIGHUP 终止),但进程本身还需要有“不主动退出”的逻辑(如无限循环、sleep infinity 等)才能一直运行
    • sleep infinity 是实现这种“不退出”的简单高效方式
  • 前台进程与终端的关系
    • 如果进程是在终端中直接启动的前台进程(比如直接执行 python script.py),那么当终端关闭时,该进程会收到 SIGHUP(挂断信号),通常会随之终止=
    • 这种情况下,即使不关闭终端,进程也可能因为自身逻辑结束(比如脚本执行完毕)而退出,并非“一直运行”
  • 后台进程的特殊性
    • 如果用 & 将进程放到后台(如 python script.py &),终端关闭时它仍可能被 SIGHUP 终止(取决于 shell 配置,如 huponexit 选项)
    • 即使终端保持打开,后台进程若自身逻辑执行完毕(比如循环结束),也会自动退出
  • sleep infinity 的作用
    • sleep infinity 的核心是让进程进入“无限等待”状态,自身不会主动结束,且几乎不消耗资源
    • sleep infinity 与普通进程的区别在于:
      • 普通进程需要自身逻辑(如无限循环)来维持运行,可能消耗 CPU 或内存;
      • sleep infinity 是利用系统调用让进程进入休眠,本质上是“挂起等待”,资源占用极低
  • 终端存在≠进程持续运行
    • 终端存在只是避免了 SIGHUP 导致的终止,但进程是否持续运行仍取决于其自身是否会主动退出
    • 比如执行 echo "hello" 后,终端还在,但进程已经结束;
    • 而 sleep infinity 无论终端是否关闭(若处理了 SIGHUP),都会一直休眠

Docker——镜像深入理解


整体说明

  • TLDR:镜像的本质是静态的只读模板
  • 镜像(Image)是一个静态的、只读的二进制文件集合 ,包含运行应用所需的代码、 runtime、库、环境变量、配置文件等所有依赖
  • 镜像的核心作用是:
    • 作为容器的”模板”:容器是镜像的运行实例(镜像 + 可写层)
    • 保证环境一致性:无论在哪个宿主机器上,基于同一镜像创建的容器都能运行相同的应用(”一次构建,到处运行”)
  • 镜像的核心特性包括
    • 只读性 :所有层不可修改,保证安全性和可复用性
    • 分层存储 :基于 UnionFS,层可共享,减少冗余
    • 轻量高效 :仅包含应用依赖,体积远小于虚拟机镜像
    • 可移植性 :镜像内容与宿主环境无关,实现”一次构建,到处运行”

镜像的分层结构:UnionFS 与 Copy-on-Write

  • Docker 镜像最核心的设计是分层存储 ,基于 Union File System(联合文件系统) 实现
  • 这种分层结构让镜像具备了”可复用、轻量、高效”的特性

分层的本质

  • 每个镜像由多个只读层(Layer) 叠加而成,每层对应镜像构建过程中的一个操作(如 RUN、COPY 等 Dockerfile 指令)
  • 层与层之间通过哈希值唯一标识(如 sha256:a1b2c3...),相同的层会被不同镜像共享(避免重复存储)
  • 例如,一个 nginx 镜像可能包含以下层:
    • 基础层:ubuntu:20.04 的底层文件系统(如 /bin、/etc 等)
    • 依赖层:安装 nginx 所需的库(如 libpcre3 等)
    • 应用层:nginx 二进制文件和配置文件(如 /usr/sbin/nginx、/etc/nginx/)

联合挂载(Union Mount)

  • 当镜像被用于创建容器时,Docker 会将所有只读层联合挂载为一个统一的文件系统,对容器来说,这些层看起来是一个完整的目录(透明化分层细节)

Copy-on-Write(写时复制)机制

  • Copy-on-Write(写时复制)机制是镜像分层与容器交互的核心机制
  • 镜像的所有层都是只读的,容器启动时,Docker 会在镜像顶层添加一个可写层(Writable Layer)
  • 当容器需要修改文件时:
    • 若文件在底层(镜像层),会先将文件复制到可写层 ,再修改可写层的副本(底层文件不变)
    • 若文件是新创建的,直接写入可写层
  • 这种机制保证了:
    • 镜像层不会被容器修改(只读),可安全复用
    • 容器的修改仅保存在自己的可写层,不影响其他容器或镜像

Dockerfile 构建镜像与分层

  • 镜像的构建通常通过 Dockerfile 定义(而非 docker commit,后者不推荐)

  • Dockerfile 中的每一条指令都会生成一个新的只读层 ,指令与层的对应关系是理解镜像体积和优化的关键

  • 下面是一个 Dockerfile 与分层对应的示例:

    1
    2
    3
    4
    5
    6
    FROM ubuntu:20.04       # 基础层(复用 ubuntu:20.04 的所有层)
    RUN apt-get update # 层 1:执行 update 后的文件变化
    RUN apt-get install -y nginx # 层 2:安装 nginx 后的变化
    COPY nginx.conf /etc/nginx/ # 层 3:复制配置文件的变化
    EXPOSE 80 # 元数据(不生成层,仅记录信息)
    CMD ["nginx", "-g", "daemon off;"] # 元数据(容器启动命令)
  • EXPOSE、CMD、ENV 等指令仅修改镜像的元数据(保存在镜像的配置层),不生成新的文件层

  • 多条指令会生成多个层,层越多,镜像体积可能越大(需优化)


附录:镜像 ID 与 Digest的区别

  • 镜像 ID是镜像的唯一标识符(64 位哈希,通常显示前 12 位),由镜像的所有层和元数据共同计算得出
    • f9c8f87e172b(完整 ID 为 f9c8f87e172b2a4e41a73e2d685c8f...)
  • Digest(摘要) :镜像内容的哈希值(基于所有层的内容计算),用于验证镜像的完整性(避免篡改)
    • 拉去影响完成时,给出的 Digest: sha256:abc123... 就是摘要,相同摘要的镜像内容一定相同

附录:Storage Driver

  • Docker 通过存储驱动管理镜像层和容器可写层的存储与联合挂载
  • 不同的存储驱动实现方式不同,主流的是 overlay2(Linux 推荐,性能最优)
  • overlay2 驱动的核心结构如下:
    • 镜像层 :存储在 /var/lib/docker/overlay2/ 下,每层对应一个目录(以层哈希命名)
    • 可写层 :容器启动时,overlay2 会创建一个新目录作为可写层,并通过”上下层”关系关联镜像层
    • 合并视图 :通过 overlay2 的联合挂载,将所有层合并为容器内看到的统一文件系统

附录:镜像的体积优化

  • 镜像体积过大会导致存储、传输和启动效率下降,优化核心是减少层数、删除冗余文件 :

  • 合并指令(减少层数) :多条 RUN 指令可合并为一条(用 && 连接),并清理缓存(如 apt-get clean):

    1
    2
    3
    4
    5
    6
    7
    8
    # 优化前(2 层)
    RUN apt-get update
    RUN apt-get install -y nginx

    # 优化后(1 层,且清理缓存)
    RUN apt-get update && \
    apt-get install -y nginx && \
    rm -rf /var/lib/apt/lists/* # 删除 apt 缓存
  • 多阶段构建(丢弃无用层) :用于编译型应用(如 Go、Java),仅保留运行时所需文件(下面的代码最终镜像仅包含 alpine 基础层 + 二进制文件,体积大幅减小):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 阶段 1:编译(包含编译器等冗余工具)
    FROM golang:1.20 AS builder
    WORKDIR /app
    COPY . .
    RUN go build -o myapp .

    # 阶段 2:运行(仅保留编译产物)
    FROM alpine:3.18
    COPY --from=builder /app/myapp /myapp # 仅复制编译好的二进制文件
    CMD ["/myapp"]
  • 优先选择 alpine(几 MB)、slim 版本,而非完整版(如 ubuntu 完整版约 200MB,alpine 约 5MB)

  • 删除临时文件、日志、包管理缓存(如 yum clean all、npm cache clean)


附录:执行 docker pull 时在发生什么?

  • 当执行 docker pull [镜像名] 时,终端会显示的多行输出,这是 Docker 拉取镜像过程的详细日志,每一行对应镜像的一个 层(Layer) 的下载或处理状态,包含层的信息
  • 如果本地已经有的层,不会再下载,且不同镜像是可以共享同一个层的(通过 ID 唯一识别)
  • 层 ID :每个层的唯一标识符(如 a1b2c3d4...)
  • 操作类型 :
    • Pull complete:该层已成功下载并解压
    • Already exists:本地已存在该层,无需重复下载
    • Downloading:正在下载该层,会显示进度(如 50% [=====>])
    • Verifying Checksum:验证文件完整性
    • Extracting:解压下载的层文件
  • 为什么会有这么多层?
    • Docker 镜像是由多个 只读层(Layer) 叠加而成的
    • 每个层对应镜像构建过程中的一个操作(如 RUN、COPY 等指令)
    • 层具有 可复用性 :不同镜像可能共享相同的层,避免重复存储和下载
    • 层的设计让镜像更新更高效(只需更新变化的层)

附录:镜像与容器的关系:动态 vs 静态

  • 镜像 :静态、只读、多分层,是容器的”模板”
  • 容器 :动态、可写,是镜像的”运行实例”(= 镜像所有只读层 + 容器独有的可写层)
  • 可以理解为:容器 = 镜像(只读层) + 可写层(容器私有) + 容器元数据(如网络配置、环境变量等)
  • 当容器被删除时,其可写层和元数据会被清理 ,但镜像的只读层不受影响(可继续用于创建新容器)
1…258259260…352
San Ye

San Ye

Stay Hungry. Stay Foolish.

704 posts
53 tags
© 2026 San Ye
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4