当前位置: 首页 > news >正文

Docker 容器(二)

Docker

  • 四、Docker容器数据卷
      • 1.数据卷的主要特点
      • 2.卷的共享与继承
        • (1)卷的共享(Sharing)
        • (2) 卷的继承(Inheritance)
      • 3.数据卷运行实例
  • 五、Dockerfile
    • 1.Dockerfile
    • 2. 创建一个名为 myubuntu的自定义镜像
        • 第 1 步:准备并编写 Dockerfile 文件
        • 第 2 步:构建镜像
        • 第 3 步:运行镜像
    • 3、什么是虚悬镜像?​

四、Docker容器数据卷

Docker 容器数据卷(Volume)​​ 是一种​​持久化数据​​和​​共享数据​​的机制,用于解决容器内数据生命周期的问题。它本质上是绕过容器联合文件系统(UnionFS)的​​一个特殊目录​​,可以存在于一个或多个容器中,但其数据直接存储在宿主机上或由外部服务管理。

1.数据卷的主要特点

  1. 数据持久化 (Persistence)
    • 卷中的数据是独立于容器生命周期的。即使容器被删除、重启或停止,卷中的数据依然安全地保留在宿主机上。一直到没有容器使用它为止。
    • 这是数据卷最核心的价值。
  2. 数据共享与复用 (Sharing & Reuse)
    • 一个数据卷可以同时被多个容器挂载和使用,是实现容器间数据共享的最佳方式。
    • 一个容器也可以挂载多个数据卷
  3. 高性能 (Performance)
    • 相对于容器内联合文件系统的写时复制(CoW)机制,直接对数据卷的读写性能更高,更接近原生文件系统I/O。
  4. 解耦应用与数据 (Decoupling)
    • 将动态变化的、需要持久化的数据(如数据库文件)与静态的应用环境(如已安装的程序)分离开,使容器更加轻量和专注于业务逻辑。

2.卷的共享与继承

(1)卷的共享(Sharing)

这是数据卷最直接和常用的功能。多个容器(无论是否同时运行)可以挂载同一个预先创建好的数据卷(通常是命名卷),从而实现数据的共同访问和交换。

工作机制
多个容器的挂载点(target)指向同一个卷源(source)。

操作示例:容器间共享数据

  1. 创建一个命名卷 (shared-data)

    docker volume create shared-data
    
  2. 启动第一个生产者容器 (producer),向共享卷写入数据

    docker run -it --name producer --mount source=shared-data,target=/data ubuntu bash
    

    在容器内执行:

    echo "Hello from Producer Container!" > /data/message.txt
    exit
    
  3. 启动第二个消费者容器 (consumer),从同一个共享卷读取数据

    docker run -it --name consumer --mount source=shared-data,target=/app ubuntu bash
    

    在容器内执行:

    cat /app/message.txt # 输出: Hello from Producer Container!
    
  4. 验证

  • 你可以在 consumer 容器中看到 producer 容器创建的文件。
  • consumer 中修改 /app/ 下的文件,producer 重新挂载后也能看到。
  • 即使这两个容器都停止了,shared-data 卷里的数据依然存在。
(2) 卷的继承(Inheritance)

卷的继承主要通过 --volumes-from 参数实现。它允许一个新容器自动继承另一个容器所挂载的所有数据卷,包括挂载点和权限。

重要提示--volumes-from 继承的是挂载规则,而不是复制数据。最终,继承的容器和被继承的容器会挂载到同一个物理卷上,因此它们操作的也是同一份数据,本质上也构成了一种共享。

操作示例:继承挂载规则

  1. 启动一个“数据卷容器” (data-container)
    这个容器本身可以不运行任何应用,它的唯一目的就是定义要挂载的卷。

    docker run -d --name data-container \-v /db-data \          # 定义一个匿名卷-v /config \           # 定义另一个匿名卷ubuntu sleep infinity  # 让它执行一个无害的长期命令
    
  2. 通过继承启动一个新容器 (app-container)

    docker run -d --name app-container \--volumes-from data-container \ # 关键参数:继承 data-container 的所有卷my-app-image
    

    此时,app-container 容器会自动拥有两个挂载点:/db-data/config,它们与 data-container 中的对应卷是同一个。

  3. 再启动一个容器也继承它 (backup-container)

    docker run -d --name backup-container \--volumes-from data-container \backup-image
    

    现在,data-containerapp-containerbackup-container 都共享着相同的 /db-data/config 卷。

3.数据卷运行实例

  1. ​​直接命令添加​​宿主机与容器的卷映射。
  2. 设置不同的​​读写规则​​(rw与 ro)。
  3. 实现容器间的​​卷的继承和共享​​。

场景模拟​​
我们创建两个容器:

  • 容器1 (container1)​​:作为一个数据生产者,与宿主机建立映射,并创建数据。
  • ​​容器2 (container2)​​:继承容器1的卷规则,作为数据消费者来读取数据。

步骤 1: 直接命令添加容器卷(宿主机 vs 容器映射)

我们使用 docker run -v命令直接创建映射。

语法-v <宿主机目录>:<容器目录>:<读写规则>

执行命令:创建容器1并完成映射

# 在宿主机上创建一个目录用于映射
sudo mkdir -p /docker_volume_example/data# 运行容器1,并建立映射关系
# -v /docker_volume_example/data:/data:rw 表示将宿主机的目录映射到容器的/data目录,读写规则为读写(默认)
docker run -itd \--name container1 \-v /docker_volume_example/data:/data:rw \ubuntu:22.04 \bash
  • -v /docker_volume_example/data:/data:rw:这就是图中的“直接命令添加”。
    • 宿主机目录/docker_volume_example/data
    • 容器目录/data
    • 读写规则rw(读写,为默认模式,可省略)

步骤 2: 读写规则映射添加说明

现在我们在容器1内进行操作,验证读写权限,并模拟数据生产。

进入容器1并写入数据:

# 进入容器1
docker exec -it container1 bash# 此时,我们已在容器1的内部Shell中
# 查看映射的目录
ls /data # 此时应该是空的,因为宿主机目录是空的# 向数据卷中写入一个文件,模拟生产数据
echo "This is important data created by Container1." > /data/data_from_container1.txt# 退出容器1
exit

在宿主机上验证数据共享:

# 检查宿主机映射目录,应该能看到容器1创建的文件
cat /docker_volume_example/data/data_from_container1.txt

输出结果This is important data created by Container1.

这证明了宿主机和容器1之间的数据是实时共享的。


步骤 3: 卷的继承和共享

现在,我们启动容器2,让它继承容器1的卷规则,实现数据共享。

执行命令:创建容器2并继承容器1的卷

# 使用 --volumes-from 参数继承容器1的所有卷映射规则
docker run -itd \--name container2 \--volumes-from container1 \ # 这是实现继承的关键!ubuntu:22.04 \bash

验证继承与共享:

# 进入容器2
docker exec -it container2 bash# 检查容器2的 /data 目录,应该能看到容器1创建的文件
ls /data
cat /data/data_from_container1.txt

输出结果This is important data created by Container1.

成功!容器2没有直接映射宿主机目录,但通过 --volumes-from继承了容器1的映射,因此也能访问到同一份数据。

测试只读(ro)规则:

假设我们想让容器2只能读,不能写,我们可以在继承时重新定义挂载的读写规则。

1. 先删除旧的容器2

docker stop container2
docker rm container2

2. 以只读模式重新运行容器2

# 注意命令中的 :ro
docker run -itd \--name container2 \--volumes-from container1 \ # 先继承-v /docker_volume_example/data:/app_data:ro \ # 再重新挂载并覆盖为只读规则ubuntu:22.04 \bash
  • 这里我们做了一个调整,将容器2的挂载点从 /data改到了 /app_data,并设置了 :ro(只读)。

3. 验证容器2的只读权限

docker exec -it container2 bash# 可以成功读取
cat /app_data/data_from_container1.txt# 尝试写入会报错:Read-only file system
echo "Try to write from Container2" > /app_data/data_from_container2.txt

输出结果bash: /app_data/data_from_container2.txt: Read-only file system

这证明了只读(ro)规则已生效,完美实现了“只读”的说明。

五、Dockerfile

1.Dockerfile

Dockerfile 是一个文本文件​​,里面包含了一系列的​​指令(Instruction)​​ 和​​参数​​。每一条指令都会在镜像上构建一层,因此每一条指令的内容,就是描述该层应当如何构建。
它将基础镜像一步步变成自定义镜像。

一个典型的 Dockerfile 分为四部分:

  1. ​​基础镜像信息​​:使用 FROM指令指定从哪个镜像开始构建。
  2. ​​元数据信息​​:使用 LABEL、MAINTAINER等指令添加镜像的描述信息。
  3. ​​构建指令​​:运行命令、复制文件、设置环境变量等,按顺序逐步构建镜像层。
  4. ​​容器启动指令​​:指定镜像构建完成后,启动容器时默认要运行的命令。

Dockerfile 的常用保留字

指令功能描述
FROM指定基础镜像,必须是第一条指令。
LABEL为镜像添加元数据(键值对),如版本、描述等。替代旧的 MAINTAINER
RUN在镜像构建过程中执行命令,用于安装软件、下载依赖等。
COPY宿主机上的文件或目录复制到镜像中。
ADD类似 COPY,但功能更多(如自动解压 tar 包、支持 URL)。推荐优先使用 COPY
WORKDIR设置后续指令的工作目录(如果不存在则创建)。类似 cd
EXPOSE声明容器运行时监听的网络端口(只是一个说明,实际映射需用 -p)。
ENV设置环境变量,后续指令和容器运行时都可以使用。
ARG设置构建时的环境变量,镜像运行时不存在。用于动态传入参数。
VOLUME创建匿名数据卷挂载点,用于持久化数据。
USER指定后续指令以哪个用户身份运行(默认为 root)。
CMD指定容器启动时默认执行的命令。一个 Dockerfile 只能有一条 CMD
ENTRYPOINT指定容器启动时的主要命令,CMD的内容会成为其参数。

2. 创建一个名为 myubuntu的自定义镜像

体验从编写 Dockerfile 到构建,最后运行的完整流程

第 1 步:准备并编写 Dockerfile 文件
  1. 创建一个空目录 作为工作空间,并进入该目录。

    mkdir myubuntu-dockerfile
    cd myubuntu-dockerfile
    
  2. 创建 Dockerfile文件(注意:没有文件扩展名)。

    touch Dockerfile
    
  3. 编辑 Dockerfile文件,输入以下内容:

    # 指定基础镜像
    FROM ubuntu:22.04# 维护者信息(可选)
    LABEL maintainer="student@example.com"# 设置环境变量,防止apt安装时交互式提示
    ENV DEBIAN_FRONTEND=noninteractive# 构建指令:更新软件包列表并安装常用工具
    RUN apt-get update && \apt-get install -y \vim \net-tools \iputils-ping \curl \&& rm -rf /var/lib/apt/lists/* # 清理缓存以减小镜像体积# 设置容器启动时默认执行的命令
    CMD ["/bin/bash"]
    

    代码解释:

    • FROM ubuntu:22.04: 基于官方 Ubuntu 22.04 镜像开始构建。
    • LABEL: 添加镜像的元数据信息。
    • RUN: 这是核心步骤。它执行命令来安装我们需要的软件包。
      • apt-get update && apt-get install -y: 更新软件源并安装指定工具(vim-编辑器, net-tools-网络工具如ifconfig, iputils-ping-ping命令, curl-传输工具)。
      • && rm -rf /var/lib/apt/lists/*: 这是一个很好的实践,清理 apt 缓存,可以显著减小镜像体积。
    • CMD ["/bin/bash"]: 指定当容器启动时,默认进入 bashshell。

第 2 步:构建镜像

使用 docker build命令,根据编写好的 Dockerfile 构建镜像。

命令格式: docker build -t 新镜像名字:TAG .

Dockerfile所在的目录下,执行以下命令:

docker build -t myubuntu:1.0 .

命令解释:

  • docker build: 构建镜像的命令。
  • -t myubuntu:1.0: -t参数用于给新镜像命名和打标签。这里我们将镜像命名为 myubuntu,标签为 1.0
  • .: 这个点 .非常重要!它指定了 构建上下文(Build Context) 的路径,即当前目录。Docker 引擎会在这个路径下寻找 Dockerfile文件。

等待构建完成,会看到类似下面的输出,表示构建成功:

Successfully built xxxxxxxxxxxx
Successfully tagged myubuntu:1.0

验证镜像是否创建成功:

docker images

你应该能在列表中看到 myubuntu镜像。


第 3 步:运行镜像

使用 docker run命令来启动并进入我们新创建的容器。

命令格式: docker run -it 新镜像名字:TAG

执行以下命令:

docker run -it myubuntu:1.0

命令解释:

  • docker run: 运行容器的命令。
  • -it: 这是两个参数的组合。
    • -i(--interactive): 保持标准输入流(STDIN)打开。
    • -t(--tty): 分配一个伪终端(pseudo-TTY)。
    • 组合使用 -it可以让我们以交互模式进入容器,就像登录一台虚拟机一样。
  • myubuntu:1.0: 指定要运行的镜像名称和标签。

验证自定义功能:

命令执行后,你的终端提示符会发生变化,表示你已经进入了容器内部,形如:

root@a1b2c3d4e5f6:/#

现在,可以测试我们在 Dockerfile 中安装的工具是否可用:

# 测试 ping 命令
ping -c 4 github.com# 测试 ifconfig 命令(来自net-tools)
ifconfig# 测试 vim 编辑器
vim --version

退出容器:输入 exit即可退出容器并回到宿主机的命令行。

3、什么是虚悬镜像?​

虚悬镜像​​是指没有标签(TAG)、并且没有被任何容器引用的镜像。它在 Docker 的镜像列表中通常显示为 <none>:<none>

产生场景:

  1. 镜像构建过程中
    当你使用 docker build重新构建一个同名的镜像时,Docker 会为新的构建过程创建新的镜像层。构建成功后,它会给​​新生成的镜像​​打上你指定的标签(如 my-app:latest)。

    而​​旧版本的镜像​​就会失去这个标签,变成 :,即虚悬镜像。

  2. 删除镜像后​​
    如果你删除了一个镜像的​​某个标签​​,而这个镜像还有其他标签,那么它不会变成虚悬镜像。但如果你删除了它的​​最后一个标签​​,这个镜像本身就会变成虚悬镜像。

http://www.xdnf.cn/news/1412857.html

相关文章:

  • 大模型面试题剖析:PPO 与 GRPO 强化学习算法核心差异解析
  • 大模型应用总结
  • shell编程之shell脚本基础(未完待续)
  • 飞牛Docker部署免费frp内网穿透
  • 2025.8.18-2025.8.24第35周:备稿演讲有进步
  • 从零构建中间件:Tower 核心设计的来龙去脉
  • AI 编程新玩法:用 yunqi-saas-kit 框架制作小游戏,看广告变现轻松赚钱​
  • 【Linux】linux进程 vs 线程
  • VisionProC#联合编程火花塞距离检测与VisionPro操作
  • Augment 宣布 Auggie CLI正式向所有用户开放
  • 旦旦的农场打卡计划
  • 刷题日记0831
  • Animal fats vs. seed oils
  • 云渲染如何重新定义视觉艺术的边界
  • DOS 程序
  • DBeaver:一款免费开源的数据库管理工具
  • BLE广播与扫描
  • 前端学习——CSS
  • 随时随地开发:通过 FRP 搭建从 Ubuntu 到 Windows 的远程 Android 调试环境
  • Hutool DsFactory多数据源切换
  • 企业级架构师综合能力项目案例一(各种组件集群搭建+SpringBoot整合)
  • 决策思维研究体系主要构成
  • Python入门教程之类型判别
  • STM32F103C8T6的智能医疗药品存储柜系统设计与华为云实现
  • 解决git push时的错误提示:“error: src refspec master does not match any”
  • 漏洞基础与文件包含漏洞原理级分析
  • 【重学MySQL】九十四、MySQL请求到响应过程中字符集的变化
  • 盛最多水的容器:双指针法的巧妙运用(leetcode 11)
  • 多智能体系统设计:5种编排模式解决复杂AI任务
  • FPGA设计杂谈之七:异步复位为何是Recovery/Removal分析?