Docker学习
镜像
当你创建一个 Docker 镜像时,镜像内的文件是固定的,意味着该镜像包含了创建时所有的文件系统状态,包括应用、依赖、配置文件等。这个镜像本身是不可变的,也就是说一旦镜像被创建,镜像内的文件和结构不会再被修改。
-
镜像的不可变性:
- Docker 镜像是分层构建的,每一层都表示文件系统的一个增量。当你创建一个新的镜像时,这些层是只读的,镜像一旦生成后就不会改变。
- 你可以使用
docker commit
或重新构建镜像来创建新的镜像版本,但原始镜像不会被修改。
-
容器是镜像的可写副本:
- 当你使用
docker run
命令启动一个容器时,Docker 会在镜像的基础上创建一个容器。容器包含一个可写的层,允许你在容器内修改、添加或删除文件。 - 这些更改只发生在容器中,镜像本身不会受到影响。如果删除容器,容器内的更改也会被丢失,除非你将更改保存到一个新的镜像中。
- 当你使用
-
镜像内的文件在容器中的表现:
- 容器启动时,会基于镜像文件系统创建可写层。你可以在容器中修改文件(例如,写入日志、修改配置),但这些修改不会影响原始镜像。
- 当容器停止或删除时,所有对文件系统的更改都会消失,除非你将更改保存为一个新的镜像(使用
docker commit
)。
总结:
- 镜像本身是不可变的,创建后镜像中的文件不会自动更改。
- 容器是镜像的运行副本,并有一个可写层,容器内的文件可以改变,但不会影响原始镜像。
- 如果你希望镜像的内容发生变化,必须重新构建或使用
docker commit
创建一个新的镜像。
构建
当你启动一个 Docker 容器时,容器内部执行的程序所处的初始路径通常由 Docker 镜像的 WORKDIR
指令决定。如果镜像没有指定 WORKDIR
,则默认路径通常是容器的根目录 /
。
情况 1:镜像指定了 WORKDIR
- 如果 Dockerfile 中使用了
WORKDIR
指令,那么容器启动后,程序会在这个指定的目录中运行。例如,以下 Dockerfile 片段:当你启动容器时,容器内部程序的工作目录就是1
WORKDIR /app
/app
,你可以在该目录下执行程序。
情况 2:镜像没有指定 WORKDIR
- 如果 Dockerfile 中没有指定
WORKDIR
,容器内的默认路径会是根目录/
。这意味着程序会从/
目录开始执行。
如何检查容器内的当前路径:
如果你已经启动了一个容器,可以通过以下方式检查当前路径:
- 进入容器:
1
docker exec -it <container_name> /bin/bash
- 使用
pwd
命令查看当前工作路径:1
pwd
示例:
假设启动了一个名为 my_container
的容器,你可以执行:
1 | docker exec -it my_container /bin/bash |
如果 Dockerfile 中有 WORKDIR /app
,那么输出将是 /app
。如果没有 WORKDIR
,则输出通常为 /
。
总结:
- 容器内程序执行的路径由 Dockerfile 中的
WORKDIR
指定。 - 如果没有
WORKDIR
,则默认是根目录/
。 - 你可以使用
pwd
命令来查看容器内程序当前的工作路径。
修改镜像
在 Docker 容器的镜像中,镜像本身是不可变的,但你可以通过多种方式修改镜像内部的文件。一般情况下有两种方法来实现这一目的:
方法 1:通过容器修改文件并创建新的镜像
-
启动容器并修改文件:
你可以先基于现有的镜像启动一个容器,然后在该容器内修改文件。1
docker run -it --name my_container <image_name> /bin/bash
进入容器后,你可以通过标准的 Linux 命令(如
vim
,nano
,cp
,mv
等)修改容器内的文件。 -
提交更改创建新的镜像:
完成文件修改后,退出容器并使用docker commit
命令将容器的更改保存为一个新的镜像。1
docker commit my_container my_new_image
这样,你就可以创建一个包含所做更改的新镜像。
my_new_image
是新镜像的名字。 -
删除旧容器(可选):
如果不再需要修改时用到的容器,可以删除它。1
docker rm my_container
方法 2:通过 Dockerfile 修改镜像
如果你想批量修改文件或进行更多自定义操作,可以创建或修改一个 Dockerfile
,然后重新构建镜像。以下是使用 Dockerfile
修改镜像的步骤。
-
创建 Dockerfile:
假设你想修改某个镜像中的文件内容,可以创建一个Dockerfile
,基于原有的镜像进行修改。1
2
3
4
5
6
7
8FROM <base_image>
# 复制本地文件到镜像
COPY ./new_file /path/in/container/
# 或者删除、修改镜像中的文件
RUN rm /path/in/container/old_file
RUN echo "New content" > /path/in/container/existing_file -
构建新的镜像:
编写好Dockerfile
后,可以通过docker build
命令构建新的镜像。1
docker build -t my_modified_image .
这个命令会根据
Dockerfile
的指令构建一个新的镜像,my_modified_image
就是新镜像的名字。 -
使用新镜像:
完成构建后,你可以使用这个新镜像来启动容器,修改后的文件将会包含在新镜像中。1
docker run -it my_modified_image /bin/bash
方法 3:直接在已有镜像中修改(不推荐)
你可以通过以下方式修改镜像内的文件,但这种方式非常不灵活,且容易丢失更改,因此不推荐。
-
启动容器并进入:
1
docker run -it --name temp_container <image_name> /bin/bash
-
在容器内部修改文件。
-
停止并提交容器:
1
docker commit temp_container <new_image_name>
-
删除临时容器(可选):
1
docker rm temp_container
总结:
- 直接修改容器内的文件并提交为新镜像:适用于小范围修改。
- 使用 Dockerfile 进行文件修改和重建镜像:适合大规模、批量或可重复的修改操作。
启动
启动命令参数
这个 Bash 脚本的作用是运行一个 Docker 容器,执行 python main.py
脚本,并且设置了网络和挂载选项。让我们逐步解析每个部分的含义:
-
docker run -d
docker run
:启动一个新的 Docker 容器。-d
:表示后台运行容器(detach 模式),不会在前台阻塞终端。
-
--net=host
- 这个选项将 Docker 容器的网络模式设置为与宿主机相同,容器将共享宿主机的网络堆栈。这意味着容器内的进程可以直接使用宿主机的 IP 和端口,而不会通过 NAT 进行端口映射。
- 在某些应用场景下(如需要高性能的网络连接,或与宿主机进行通信的情况),
--net=host
是非常有用的。
-
--ipc="host"
- 将容器的 IPC(进程间通信)命名空间设置为与宿主机共享。容器内的进程可以与宿主机的进程共享 IPC 资源(如共享内存)。
- 这种配置通常在需要容器与宿主机共享内存时使用。
-
--name=container
- 设置容器的名称为
container
。之后你可以使用这个名称来管理或查看容器,例如使用docker logs container
查看日志。
- 设置容器的名称为
-
--add-host=solrdic.test.com:172.16.1.1
- 通过这个选项,可以将
solrdic.test.com
添加到容器内的/etc/hosts
文件中,并将它映射到172.16.1.1
IP 地址。 - 在容器内部,当你访问
solrdic.test.com
,它会解析到172.16.1.1
,这对容器内的网络配置是有用的。
- 通过这个选项,可以将
-
-v "$ITEM2VEC_DATA"/data:/data
-v
:这是一个挂载卷的选项,用来将宿主机目录与容器内的目录进行映射。"$ITEM2VEC_DATA"/data
:这是宿主机的目录,其中$ITEM2VEC_DATA
是一个环境变量,指向宿主机的某个路径。这个路径将映射到容器内的/data
目录。/data
:容器内部的路径,容器内的应用可以在这个目录中读取和写入数据。
-
test/container:latest
- 这是 Docker 镜像的名称和版本号。该命令使用名为
test/container
的镜像,并拉取latest
标签对应的版本。
- 这是 Docker 镜像的名称和版本号。该命令使用名为
-
python main.py
- 这是容器启动后执行的命令。启动容器后,它将执行镜像中的
python main.py
脚本。 main.py
应该是一个位于容器内的 Python 文件。可以在镜像构建时将main.py
放置在合适的路径中。
- 这是容器启动后执行的命令。启动容器后,它将执行镜像中的
完整脚本的作用:
- 在后台启动一个名为
container
的 Docker 容器。 - 容器使用宿主机的网络和 IPC 命名空间。
- 在容器的
/etc/hosts
文件中添加solrdic.test.com
与172.16.1.1
的映射。 - 将宿主机的
$ITEM2VEC_DATA/data
目录挂载到容器的/data
目录中。 - 启动镜像
test/container:latest
,并执行python main.py
脚本。
文件导入
要启动一个 Docker 容器并导入已有的文件,可以通过多种方法实现。以下是一些常用的方法。
方法 1:使用 -v
选项挂载宿主机的目录
你可以在启动容器时使用 -v
(或 --volume
)选项将宿主机上的目录挂载到容器内部。这样,容器可以访问宿主机的文件。
1 | docker run -d --name my_container -v /path/on/host:/path/in/container <image_name> |
/path/on/host
:宿主机上你希望挂载的目录路径。/path/in/container
:容器内部将要挂载的目录路径。<image_name>
:要运行的 Docker 镜像的名称。
示例
假设你想将宿主机的 /data
目录挂载到容器的 /app/data
目录:
1 | docker run -d --name my_container -v /data:/app/data <image_name> |
方法 2:使用 COPY
或 ADD
指令在 Dockerfile 中导入文件
如果你想在构建镜像时将文件复制到容器中,可以使用 COPY
或 ADD
指令。
1 | FROM <base_image> |
然后,你可以构建镜像并启动容器:
1 | docker build -t <image_name> . |
方法 3:使用 docker cp
命令将文件复制到运行中的容器
如果容器已经在运行,你可以使用 docker cp
命令将文件复制到容器内。
1 | docker cp /path/on/host/file.txt my_container:/path/in/container/file.txt |
示例
假设容器 my_container
已经在运行,你希望将宿主机的 /data/file.txt
复制到容器的 /app/data/
目录:
1 | docker cp /data/file.txt my_container:/app/data/file.txt |
方法 4:通过交互式终端访问容器
你也可以启动一个交互式容器,并在容器中手动将文件复制到容器内:
1 | docker run -it --name my_container -v /path/on/host:/app <image_name> /bin/bash |
在容器内部,你可以使用 Linux 命令(如 cp
)将文件移动到所需的位置。
总结
- 使用
-v
选项挂载目录是将宿主机文件导入容器的最常用方法。 - 在 Dockerfile 中使用
COPY
或ADD
可以在构建镜像时导入文件。 - 使用
docker cp
可以在容器运行后导入文件。 - 通过交互式终端,你可以在容器内部手动进行文件操作。
根据你的需求选择合适的方法。
设置时间
Docker 容器的日志时间戳默认是容器内部的时间,但是这个内部时间通常与宿主机的系统时间同步。如果容器内的时区或时间设置与宿主机不同,日志的时间戳可能会显示不同的时间。
在 Docker 容器中,容器的时间默认情况下与宿主机(系统)的时间是同步的。容器使用宿主机的时区和时间信息,但有时由于某些原因(例如时区设置、NTP 同步问题),可能会出现时间不同步的情况。如果你遇到容器内的时间与宿主机不一致的情况,可以按照以下方法对齐它们的时间。
方法 1:同步容器时间与宿主机时间
-
容器的时间与宿主机默认同步:
默认情况下,Docker 容器是通过共享宿主机的时间命名空间来同步时间的。因此,你不需要额外的配置,容器应该与宿主机的时间保持一致。 -
时区问题导致时间不一致:
如果容器中的时间与宿主机不同步,通常是时区设置的问题。你可以通过以下方式确保时区与宿主机对齐。
方法 2:通过挂载宿主机的时区文件
你可以通过挂载宿主机的时区文件,将容器的时区与宿主机保持一致:
1 | docker run -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro <image_name> |
-v /etc/localtime:/etc/localtime:ro
:将宿主机的/etc/localtime
文件挂载到容器中,使得容器使用宿主机的时区。-v /etc/timezone:/etc/timezone:ro
:将宿主机的/etc/timezone
文件挂载到容器中,确保容器的时区与宿主机一致。
这可以确保容器内的时间与宿主机完全对齐。
方法 3:手动设置容器时区
如果你只想调整容器的时区,而不挂载宿主机的时区文件,你可以在容器内部通过安装时区设置工具并手动调整时区:
-
进入容器:
1
docker exec -it <container_name> /bin/bash
-
在基于 Debian 或 Ubuntu 的容器中,你可以通过安装
tzdata
包来调整时区:1
apt-get update && apt-get install -y tzdata
-
然后配置时区:
1
dpkg-reconfigure tzdata
-
选择正确的时区后,容器的时间会自动与时区同步。
方法 4:重新启动容器同步时间
有时,容器的时间可能与宿主机时间不同步,尤其是当宿主机的时间被更新(例如通过 NTP 同步)而容器还没有更新时。在这种情况下,你可以通过重新启动容器来强制同步时间:
1 | docker restart <container_name> |
小结:
- Docker 容器内的时间默认情况下与宿主机同步。
- 如果遇到时间不一致问题,通常是时区问题,可以通过挂载宿主机的
/etc/localtime
和/etc/timezone
文件来同步时区。 - 也可以手动在容器内设置时区。
- 如果时间不同步,可以通过重启容器来强制同步时间。
详细解释:
-
默认情况下,容器内的时间是从宿主机继承的,容器启动时会自动同步宿主机的时区和时间。因此,容器日志中的时间戳通常与宿主机系统时间一致。
-
容器内部时区:如果容器内的时区与宿主机不一致(例如,容器内配置了不同的时区),则日志的时间戳会使用容器内的时区。
-
特殊情况:如果容器内手动修改了系统时间或时区(通过安装
tzdata
包或其他方式),那么日志的时间戳将使用修改后的时间。
检查与同步时区:
-
同步时区:如果容器的时间戳与宿主机时间不同步,可以通过挂载宿主机的时区文件来确保时区一致:
1
docker run -v /etc/localtime:/etc/localtime:ro -v /etc/timezone:/etc/timezone:ro ...
这会将宿主机的时区文件挂载到容器中,使得容器和宿主机的时区同步。
-
查看容器时间:你可以通过进入容器内部,使用
date
命令检查容器的当前时间:1
docker exec -it <container_name> date
总结:
- 默认情况下,Docker 容器日志的时间是容器内的时间,通常与宿主机同步。
- 如果容器的时区设置不同或时间被修改,日志的时间戳可能会有所不同。
重启
Docker 提供了多种命令来管理和控制容器,其中包括重启容器的命令。下面是对 docker restart
命令的完全解析,包括其语法、选项和常见用法。
当你使用 docker restart 命令重启一个容器时,Docker 会保留该容器原有的所有配置参数。这意味着容器启动时的所有设置,包括环境变量、卷挂载、端口映射等,都会保持不变。具体来说:
- 环境变量:容器内原有的环境变量不会改变。
- 卷挂载:任何与主机文件系统绑定的卷挂载都会保持原样。
- 网络配置:包括端口映射、网络模式等都不会发生变化。
- 资源限制:如CPU和内存限制等也会保持不变。
- 重启策略:如果容器有设置重启策略,重启后仍然有效。
命令语法
1 | docker restart [OPTIONS] CONTAINER [CONTAINER...] |
参数说明
- CONTAINER: 需要重启的一个或多个容器的名称或 ID。
- OPTIONS: 可选参数,用于控制重启行为。
常见选项
-t
,--time int
:在停止容器之前等待的时间(秒)。默认值为 10 秒。如果在指定时间内容器没有停止,Docker 将强制停止容器。
示例
1 |
|
详细解析
基本用法
- 重启容器:这条命令会停止并重新启动指定的容器。Docker 会先发送
1
docker restart CONTAINER_NAME
SIGTERM
信号给容器,等待一段时间(默认 10 秒)后,如果容器还没有停止,会发送SIGKILL
信号强制停止容器,然后重新启动容器。
使用选项
- 设置停止等待时间:这条命令会在停止容器之前等待 20 秒。如果在这段时间内容器没有停止,Docker 将强制停止容器。
1
docker restart --time 20 CONTAINER_NAME
多个容器
- 同时重启多个容器:这条命令会同时重启多个容器。
1
docker restart CONTAINER_NAME1 CONTAINER_NAME2 CONTAINER_NAME3
注意事项
-
容器状态:
- 如果容器已经在运行,
docker restart
会先停止容器再启动。 - 如果容器已经停止,
docker restart
会直接启动容器。
- 如果容器已经在运行,
-
健康检查:
- 如果容器配置了健康检查,Docker 会在重启后重新进行健康检查。
-
网络连接:
- 重启容器可能会导致网络连接中断,特别是在容器正在处理请求时。
实际应用
在实际应用中,docker restart
命令常用于以下场景:
- 更新配置:当容器的配置文件发生变化时,重启容器可以使新的配置生效。
- 恢复服务:当容器出现异常时,重启容器可以尝试恢复服务。
- 测试:在开发和测试环境中,频繁重启容器可以帮助验证容器的健壮性和恢复能力。
通过理解和掌握 docker restart
命令及其选项,你可以更高效地管理和维护 Docker 容器。
退出、删除
当一个 Docker 容器的状态是 exited
时,虽然容器已停止运行,但它仍然存在,并且其名字也依然被占用。因此,如果你尝试启动一个同名的容器,Docker 会提示名字已被使用,无法启动一个同名的容器。
但是,你有以下几种选择:
1. 重新启动同一个容器
你可以通过以下命令重新启动已经退出的容器,而不需要新建一个同名容器:
1 | docker start 容器名称或容器ID |
这将会重新启动已经退出的容器。
2. 删除已退出的容器并创建一个同名的新容器
如果你想删除原有的容器并创建一个同名的新容器,你可以按照以下步骤操作:
-
先删除已退出的容器:
1
docker rm 容器名称或容器ID
-
然后创建并启动一个同名的新容器:
1
docker run --name 容器名称 其他参数
3. 强制删除并运行同名容器
你可以使用 docker run
命令中的 --rm
参数来在容器退出后自动删除容器,也可以避免命名冲突。
总结:在容器 exited
的状态下不能直接启动同名的新容器,但可以通过重新启动、删除或修改命名来解决这个问题。
docker logs
docker logs
命令用于查看 Docker 容器的日志。它可以查看标准输出和标准错误输出的日志。下面是 docker logs
命令的所有常用参数及其作用:
1 | docker logs [OPTIONS] 容器名称或ID |
docker logs
的常用选项
-
-f
或--follow
- 作用:实时跟踪日志输出(类似
tail -f
)。 - 示例:
1
docker logs -f 容器名称或ID
- 作用:实时跟踪日志输出(类似
-
--since
- 作用:从指定时间开始显示日志。时间可以是 Unix 时间戳、RFC3339 时间格式,或简单的时间单位(如
1h
表示 1 小时前)。 - 示例:或:
1
docker logs --since "2023-10-15T10:00:00" 容器名称或ID
(表示显示 1 小时前到现在的日志)1
docker logs --since 1h 容器名称或ID
- 作用:从指定时间开始显示日志。时间可以是 Unix 时间戳、RFC3339 时间格式,或简单的时间单位(如
-
--until
- 作用:显示指定时间之前的日志。时间格式与
--since
相同。 - 示例:
1
docker logs --until "2023-10-15T12:00:00" 容器名称或ID
- 作用:显示指定时间之前的日志。时间格式与
-
-n
或--tail
- 作用:只显示最近的
n
条日志。你可以使用all
来显示全部日志。 - 示例:(表示只显示最近的 100 条日志)
1
docker logs --tail 100 容器名称或ID
- 作用:只显示最近的
-
-t
或--timestamps
- 作用:在日志行前加上时间戳。
- 示例:
1
docker logs -t 容器名称或ID
-
--details
- 作用:显示日志的额外详细信息,如有的话(通常取决于日志驱动程序)。
- 示例:
1
docker logs --details 容器名称或ID
组合使用示例
-
实时跟踪容器日志,并只显示最近 100 条日志:
1
docker logs -f --tail 100 容器名称或ID
-
从 1 小时前开始,带时间戳显示日志:
1
docker logs --since 1h -t 容器名称或ID
-
显示今天早上 10 点到现在的日志:
1
docker logs --since "2024-10-15T10:00:00" 容器名称或ID
总结:
docker logs
是查看容器日志的主要工具,可以通过选项-f
进行实时查看,--since
和--until
来指定时间范围,--tail
用于显示最近的几条日志,-t
用于显示时间戳。
使用
要打开一个已经在运行中的 Docker 容器的终端
1 | docker exec -it <container_name_or_id> /bin/bash |
或者:
1 | docker exec -it <container_name_or_id> /bin/sh |
参数解释:
-i
:保持标准输入处于打开状态。-t
:为容器分配一个伪终端。<container_name_or_id>
:你要进入的容器的名称或 ID,可以通过docker ps
查看。/bin/bash
或/bin/sh
:在容器中执行的 shell 程序。大部分容器会包含/bin/bash
,但有些轻量级镜像(如alpine
)可能只包含/bin/sh
。
示例:
假设容器名是 my_container
,你可以使用以下命令进入该容器的终端:
1 | docker exec -it my_container /bin/bash |
如果你不确定是否有 /bin/bash
,可以使用 /bin/sh
:
1 | docker exec -it my_container /bin/sh |
这样,你就可以进入正在运行的容器并执行命令。