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.数据卷的主要特点
- 数据持久化 (Persistence):
- 卷中的数据是独立于容器生命周期的。即使容器被删除、重启或停止,卷中的数据依然安全地保留在宿主机上。一直到没有容器使用它为止。
- 这是数据卷最核心的价值。
- 数据共享与复用 (Sharing & Reuse):
- 一个数据卷可以同时被多个容器挂载和使用,是实现容器间数据共享的最佳方式。
- 一个容器也可以挂载多个数据卷。
- 高性能 (Performance):
- 相对于容器内联合文件系统的写时复制(CoW)机制,直接对数据卷的读写性能更高,更接近原生文件系统I/O。
- 解耦应用与数据 (Decoupling):
- 将动态变化的、需要持久化的数据(如数据库文件)与静态的应用环境(如已安装的程序)分离开,使容器更加轻量和专注于业务逻辑。
2.卷的共享与继承
(1)卷的共享(Sharing)
这是数据卷最直接和常用的功能。多个容器(无论是否同时运行)可以挂载同一个预先创建好的数据卷(通常是命名卷),从而实现数据的共同访问和交换。
工作机制:
多个容器的挂载点(target
)指向同一个卷源(source
)。
操作示例:容器间共享数据
-
创建一个命名卷 (
shared-data
)docker volume create shared-data
-
启动第一个生产者容器 (
producer
),向共享卷写入数据docker run -it --name producer --mount source=shared-data,target=/data ubuntu bash
在容器内执行:
echo "Hello from Producer Container!" > /data/message.txt exit
-
启动第二个消费者容器 (
consumer
),从同一个共享卷读取数据docker run -it --name consumer --mount source=shared-data,target=/app ubuntu bash
在容器内执行:
cat /app/message.txt # 输出: Hello from Producer Container!
-
验证:
- 你可以在
consumer
容器中看到producer
容器创建的文件。 - 在
consumer
中修改/app/
下的文件,producer
重新挂载后也能看到。 - 即使这两个容器都停止了,
shared-data
卷里的数据依然存在。
(2) 卷的继承(Inheritance)
卷的继承主要通过 --volumes-from
参数实现。它允许一个新容器自动继承另一个容器所挂载的所有数据卷,包括挂载点和权限。
重要提示:--volumes-from
继承的是挂载规则,而不是复制数据。最终,继承的容器和被继承的容器会挂载到同一个物理卷上,因此它们操作的也是同一份数据,本质上也构成了一种共享。
操作示例:继承挂载规则
-
启动一个“数据卷容器” (
data-container
)
这个容器本身可以不运行任何应用,它的唯一目的就是定义要挂载的卷。docker run -d --name data-container \-v /db-data \ # 定义一个匿名卷-v /config \ # 定义另一个匿名卷ubuntu sleep infinity # 让它执行一个无害的长期命令
-
通过继承启动一个新容器 (
app-container
)docker run -d --name app-container \--volumes-from data-container \ # 关键参数:继承 data-container 的所有卷my-app-image
此时,
app-container
容器会自动拥有两个挂载点:/db-data
和/config
,它们与data-container
中的对应卷是同一个。 -
再启动一个容器也继承它 (
backup-container
)docker run -d --name backup-container \--volumes-from data-container \backup-image
现在,
data-container
、app-container
和backup-container
都共享着相同的/db-data
和/config
卷。
3.数据卷运行实例
- 直接命令添加宿主机与容器的卷映射。
- 设置不同的读写规则(rw与 ro)。
- 实现容器间的卷的继承和共享。
场景模拟
我们创建两个容器:
- 容器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 分为四部分:
- 基础镜像信息:使用 FROM指令指定从哪个镜像开始构建。
- 元数据信息:使用 LABEL、MAINTAINER等指令添加镜像的描述信息。
- 构建指令:运行命令、复制文件、设置环境变量等,按顺序逐步构建镜像层。
- 容器启动指令:指定镜像构建完成后,启动容器时默认要运行的命令。
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 文件
-
创建一个空目录 作为工作空间,并进入该目录。
mkdir myubuntu-dockerfile cd myubuntu-dockerfile
-
创建
Dockerfile
文件(注意:没有文件扩展名)。touch Dockerfile
-
编辑
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"]
: 指定当容器启动时,默认进入bash
shell。
第 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>
。
产生场景:
-
镜像构建过程中
当你使用 docker build重新构建一个同名的镜像时,Docker 会为新的构建过程创建新的镜像层。构建成功后,它会给新生成的镜像打上你指定的标签(如 my-app:latest)。而旧版本的镜像就会失去这个标签,变成 :,即虚悬镜像。
-
删除镜像后
如果你删除了一个镜像的某个标签,而这个镜像还有其他标签,那么它不会变成虚悬镜像。但如果你删除了它的最后一个标签,这个镜像本身就会变成虚悬镜像。