镜像

当你创建一个 Docker 镜像时,镜像内的文件是固定的,意味着该镜像包含了创建时所有的文件系统状态,包括应用、依赖、配置文件等。这个镜像本身是不可变的,也就是说一旦镜像被创建,镜像内的文件和结构不会再被修改。

  1. 镜像的不可变性

    • Docker 镜像是分层构建的,每一层都表示文件系统的一个增量。当你创建一个新的镜像时,这些层是只读的,镜像一旦生成后就不会改变。
    • 你可以使用 docker commit 或重新构建镜像来创建新的镜像版本,但原始镜像不会被修改。
  2. 容器是镜像的可写副本

    • 当你使用 docker run 命令启动一个容器时,Docker 会在镜像的基础上创建一个容器。容器包含一个可写的层,允许你在容器内修改、添加或删除文件。
    • 这些更改只发生在容器中,镜像本身不会受到影响。如果删除容器,容器内的更改也会被丢失,除非你将更改保存到一个新的镜像中。
  3. 镜像内的文件在容器中的表现

    • 容器启动时,会基于镜像文件系统创建可写层。你可以在容器中修改文件(例如,写入日志、修改配置),但这些修改不会影响原始镜像。
    • 当容器停止或删除时,所有对文件系统的更改都会消失,除非你将更改保存为一个新的镜像(使用 docker commit)。

总结:

  • 镜像本身是不可变的,创建后镜像中的文件不会自动更改。
  • 容器是镜像的运行副本,并有一个可写层,容器内的文件可以改变,但不会影响原始镜像。
  • 如果你希望镜像的内容发生变化,必须重新构建或使用 docker commit 创建一个新的镜像。

构建

当你启动一个 Docker 容器时,容器内部执行的程序所处的初始路径通常由 Docker 镜像的 WORKDIR 指令决定。如果镜像没有指定 WORKDIR,则默认路径通常是容器的根目录 /

情况 1:镜像指定了 WORKDIR

  • 如果 Dockerfile 中使用了 WORKDIR 指令,那么容器启动后,程序会在这个指定的目录中运行。例如,以下 Dockerfile 片段:
    1
    WORKDIR /app
    当你启动容器时,容器内部程序的工作目录就是 /app,你可以在该目录下执行程序。

情况 2:镜像没有指定 WORKDIR

  • 如果 Dockerfile 中没有指定 WORKDIR,容器内的默认路径会是根目录 /。这意味着程序会从 / 目录开始执行。

如何检查容器内的当前路径:

如果你已经启动了一个容器,可以通过以下方式检查当前路径:

  1. 进入容器:
    1
    docker exec -it <container_name> /bin/bash
  2. 使用 pwd 命令查看当前工作路径:
    1
    pwd

示例:

假设启动了一个名为 my_container 的容器,你可以执行:

1
2
docker exec -it my_container /bin/bash
pwd

如果 Dockerfile 中有 WORKDIR /app,那么输出将是 /app。如果没有 WORKDIR,则输出通常为 /

总结:

  • 容器内程序执行的路径由 Dockerfile 中的 WORKDIR 指定。
  • 如果没有 WORKDIR,则默认是根目录 /
  • 你可以使用 pwd 命令来查看容器内程序当前的工作路径。

修改镜像

在 Docker 容器的镜像中,镜像本身是不可变的,但你可以通过多种方式修改镜像内部的文件。一般情况下有两种方法来实现这一目的:

方法 1:通过容器修改文件并创建新的镜像

  1. 启动容器并修改文件
    你可以先基于现有的镜像启动一个容器,然后在该容器内修改文件。

    1
    docker run -it --name my_container <image_name> /bin/bash

    进入容器后,你可以通过标准的 Linux 命令(如 vim, nano, cp, mv 等)修改容器内的文件。

  2. 提交更改创建新的镜像
    完成文件修改后,退出容器并使用 docker commit 命令将容器的更改保存为一个新的镜像。

    1
    docker commit my_container my_new_image

    这样,你就可以创建一个包含所做更改的新镜像。my_new_image 是新镜像的名字。

  3. 删除旧容器(可选):
    如果不再需要修改时用到的容器,可以删除它。

    1
    docker rm my_container

方法 2:通过 Dockerfile 修改镜像

如果你想批量修改文件或进行更多自定义操作,可以创建或修改一个 Dockerfile,然后重新构建镜像。以下是使用 Dockerfile 修改镜像的步骤。

  1. 创建 Dockerfile
    假设你想修改某个镜像中的文件内容,可以创建一个 Dockerfile,基于原有的镜像进行修改。

    1
    2
    3
    4
    5
    6
    7
    8
    FROM <base_image>

    # 复制本地文件到镜像
    COPY ./new_file /path/in/container/

    # 或者删除、修改镜像中的文件
    RUN rm /path/in/container/old_file
    RUN echo "New content" > /path/in/container/existing_file
  2. 构建新的镜像
    编写好 Dockerfile 后,可以通过 docker build 命令构建新的镜像。

    1
    docker build -t my_modified_image .

    这个命令会根据 Dockerfile 的指令构建一个新的镜像,my_modified_image 就是新镜像的名字。

  3. 使用新镜像
    完成构建后,你可以使用这个新镜像来启动容器,修改后的文件将会包含在新镜像中。

    1
    docker run -it my_modified_image /bin/bash

方法 3:直接在已有镜像中修改(不推荐)

你可以通过以下方式修改镜像内的文件,但这种方式非常不灵活,且容易丢失更改,因此不推荐。

  1. 启动容器并进入:

    1
    docker run -it --name temp_container <image_name> /bin/bash
  2. 在容器内部修改文件。

  3. 停止并提交容器:

    1
    docker commit temp_container <new_image_name>
  4. 删除临时容器(可选):

    1
    docker rm temp_container

总结:

  • 直接修改容器内的文件并提交为新镜像:适用于小范围修改。
  • 使用 Dockerfile 进行文件修改和重建镜像:适合大规模、批量或可重复的修改操作。

启动

启动命令参数

这个 Bash 脚本的作用是运行一个 Docker 容器,执行 python main.py 脚本,并且设置了网络和挂载选项。让我们逐步解析每个部分的含义:

  1. docker run -d

    • docker run:启动一个新的 Docker 容器。
    • -d:表示后台运行容器(detach 模式),不会在前台阻塞终端。
  2. --net=host

    • 这个选项将 Docker 容器的网络模式设置为与宿主机相同,容器将共享宿主机的网络堆栈。这意味着容器内的进程可以直接使用宿主机的 IP 和端口,而不会通过 NAT 进行端口映射。
    • 在某些应用场景下(如需要高性能的网络连接,或与宿主机进行通信的情况),--net=host 是非常有用的。
  3. --ipc="host"

    • 将容器的 IPC(进程间通信)命名空间设置为与宿主机共享。容器内的进程可以与宿主机的进程共享 IPC 资源(如共享内存)。
    • 这种配置通常在需要容器与宿主机共享内存时使用。
  4. --name=container

    • 设置容器的名称为 container。之后你可以使用这个名称来管理或查看容器,例如使用 docker logs container 查看日志。
  5. --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,这对容器内的网络配置是有用的。
  6. -v "$ITEM2VEC_DATA"/data:/data

    • -v:这是一个挂载卷的选项,用来将宿主机目录与容器内的目录进行映射。
    • "$ITEM2VEC_DATA"/data:这是宿主机的目录,其中 $ITEM2VEC_DATA 是一个环境变量,指向宿主机的某个路径。这个路径将映射到容器内的 /data 目录。
    • /data:容器内部的路径,容器内的应用可以在这个目录中读取和写入数据。
  7. test/container:latest

    • 这是 Docker 镜像的名称和版本号。该命令使用名为 test/container 的镜像,并拉取 latest 标签对应的版本。
  8. python main.py

    • 这是容器启动后执行的命令。启动容器后,它将执行镜像中的 python main.py 脚本。
    • main.py 应该是一个位于容器内的 Python 文件。可以在镜像构建时将 main.py 放置在合适的路径中。

完整脚本的作用:

  1. 在后台启动一个名为 container 的 Docker 容器。
  2. 容器使用宿主机的网络和 IPC 命名空间。
  3. 在容器的 /etc/hosts 文件中添加 solrdic.test.com172.16.1.1 的映射。
  4. 将宿主机的 $ITEM2VEC_DATA/data 目录挂载到容器的 /data 目录中。
  5. 启动镜像 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:使用 COPYADD 指令在 Dockerfile 中导入文件

如果你想在构建镜像时将文件复制到容器中,可以使用 COPYADD 指令。

1
2
3
4
FROM <base_image>

# 复制文件到镜像
COPY /path/on/host/file.txt /path/in/container/file.txt

然后,你可以构建镜像并启动容器:

1
2
docker build -t <image_name> .
docker run -d --name my_container <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 中使用 COPYADD 可以在构建镜像时导入文件。
  • 使用 docker cp 可以在容器运行后导入文件。
  • 通过交互式终端,你可以在容器内部手动进行文件操作。

根据你的需求选择合适的方法。

设置时间

Docker 容器的日志时间戳默认是容器内部的时间,但是这个内部时间通常与宿主机的系统时间同步。如果容器内的时区或时间设置与宿主机不同,日志的时间戳可能会显示不同的时间。

在 Docker 容器中,容器的时间默认情况下与宿主机(系统)的时间是同步的。容器使用宿主机的时区和时间信息,但有时由于某些原因(例如时区设置、NTP 同步问题),可能会出现时间不同步的情况。如果你遇到容器内的时间与宿主机不一致的情况,可以按照以下方法对齐它们的时间。

方法 1:同步容器时间与宿主机时间

  1. 容器的时间与宿主机默认同步
    默认情况下,Docker 容器是通过共享宿主机的时间命名空间来同步时间的。因此,你不需要额外的配置,容器应该与宿主机的时间保持一致。

  2. 时区问题导致时间不一致
    如果容器中的时间与宿主机不同步,通常是时区设置的问题。你可以通过以下方式确保时区与宿主机对齐。

方法 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. 进入容器:

    1
    docker exec -it <container_name> /bin/bash
  2. 在基于 Debian 或 Ubuntu 的容器中,你可以通过安装 tzdata 包来调整时区:

    1
    apt-get update && apt-get install -y tzdata
  3. 然后配置时区:

    1
    dpkg-reconfigure tzdata
  4. 选择正确的时区后,容器的时间会自动与时区同步。

方法 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 会保留该容器原有的所有配置参数。这意味着容器启动时的所有设置,包括环境变量、卷挂载、端口映射等,都会保持不变。具体来说:

  1. 环境变量:容器内原有的环境变量不会改变。
  2. 卷挂载:任何与主机文件系统绑定的卷挂载都会保持原样。
  3. 网络配置:包括端口映射、网络模式等都不会发生变化。
  4. 资源限制:如CPU和内存限制等也会保持不变。
  5. 重启策略:如果容器有设置重启策略,重启后仍然有效。

命令语法

1
docker restart [OPTIONS] CONTAINER [CONTAINER...]

参数说明

  • CONTAINER: 需要重启的一个或多个容器的名称或 ID。
  • OPTIONS: 可选参数,用于控制重启行为。

常见选项

  • -t, --time int:在停止容器之前等待的时间(秒)。默认值为 10 秒。如果在指定时间内容器没有停止,Docker 将强制停止容器。

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#!/bin/bash

# 定义需要检查的字符串列表
ERROR_STRINGS=("device-side assert triggered")
# ERROR_STRINGS=("device-side assert triggered" "out of memory" "segmentation fault")

# 定义需要监控的容器列表
CONTAINERS=("product_ranking_service")
# CONTAINERS=("product_ranking_service" "another_container" "yet_another_container")

# 使用关联数组记录每个容器的最后时间戳
declare -A LAST_LOG_TIMESTAMPS

# 检查日志并重启容器的函数
check_logs_and_restart() {
local CONTAINER_NAME=$1

# 获取从上次读取的时间戳之后的日志
if [[ -z "${LAST_LOG_TIMESTAMPS[$CONTAINER_NAME]}" ]] || ! date -d "${LAST_LOG_TIMESTAMPS[$CONTAINER_NAME]}" &>/dev/null; then
echo "no valid last log timestamp for container '$CONTAINER_NAME', reading recent 2000 logs"
LOGS=$(docker logs -t --tail 2000 "$CONTAINER_NAME" 2>&1)
else
LOGS=$(docker logs -t --since "${LAST_LOG_TIMESTAMPS[$CONTAINER_NAME]}" "$CONTAINER_NAME" 2>&1)
echo "last log timestamp for container '$CONTAINER_NAME': ${LAST_LOG_TIMESTAMPS[$CONTAINER_NAME]}, logs count: $(echo "$LOGS" | wc -l)"
fi
# 检查日志是否为空
if [[ -z "$LOGS" ]]; then
echo "No new logs to check."
return
fi
# 遍历错误字符串列表
for ERROR_STRING in "${ERROR_STRINGS[@]}"; do
# 如果日志中包含错误字符串,则重启容器
if echo "$LOGS" | grep -q "$ERROR_STRING"; then
echo "Error string '$ERROR_STRING' found in logs for container '$CONTAINER_NAME'. Restarting container..."
docker restart "$CONTAINER_NAME"
return
fi
done

# 更新 LAST_LOG_TIMESTAMPS 为最后一行日志的时间戳
if [[ -n "$LOGS" ]]; then
LAST_LOG_TIMESTAMPS[$CONTAINER_NAME]=$(echo "$LOGS" | tail -1 | awk '{print $1}')
fi
}

# 每分钟检查一次日志
while true; do
for CONTAINER in "${CONTAINERS[@]}"; do
check_logs_and_restart "$CONTAINER"
done
sleep 600
done


详细解析

基本用法

  • 重启容器
    1
    docker restart CONTAINER_NAME
    这条命令会停止并重新启动指定的容器。Docker 会先发送 SIGTERM 信号给容器,等待一段时间(默认 10 秒)后,如果容器还没有停止,会发送 SIGKILL 信号强制停止容器,然后重新启动容器。

使用选项

  • 设置停止等待时间
    1
    docker restart --time 20 CONTAINER_NAME
    这条命令会在停止容器之前等待 20 秒。如果在这段时间内容器没有停止,Docker 将强制停止容器。

多个容器

  • 同时重启多个容器
    1
    docker restart CONTAINER_NAME1 CONTAINER_NAME2 CONTAINER_NAME3
    这条命令会同时重启多个容器。

注意事项

  1. 容器状态

    • 如果容器已经在运行,docker restart 会先停止容器再启动。
    • 如果容器已经停止,docker restart 会直接启动容器。
  2. 健康检查

    • 如果容器配置了健康检查,Docker 会在重启后重新进行健康检查。
  3. 网络连接

    • 重启容器可能会导致网络连接中断,特别是在容器正在处理请求时。

实际应用

在实际应用中,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 的常用选项

  1. -f--follow

    • 作用:实时跟踪日志输出(类似 tail -f)。
    • 示例
      1
      docker logs -f 容器名称或ID
  2. --since

    • 作用:从指定时间开始显示日志。时间可以是 Unix 时间戳、RFC3339 时间格式,或简单的时间单位(如 1h 表示 1 小时前)。
    • 示例
      1
      docker logs --since "2023-10-15T10:00:00" 容器名称或ID
      或:
      1
      docker logs --since 1h 容器名称或ID
      (表示显示 1 小时前到现在的日志)
  3. --until

    • 作用:显示指定时间之前的日志。时间格式与 --since 相同。
    • 示例
      1
      docker logs --until "2023-10-15T12:00:00" 容器名称或ID
  4. -n--tail

    • 作用:只显示最近的 n 条日志。你可以使用 all 来显示全部日志。
    • 示例
      1
      docker logs --tail 100 容器名称或ID
      (表示只显示最近的 100 条日志)
  5. -t--timestamps

    • 作用:在日志行前加上时间戳。
    • 示例
      1
      docker logs -t 容器名称或ID
  6. --details

    • 作用:显示日志的额外详细信息,如有的话(通常取决于日志驱动程序)。
    • 示例
      1
      docker logs --details 容器名称或ID

组合使用示例

  1. 实时跟踪容器日志,并只显示最近 100 条日志:

    1
    docker logs -f --tail 100 容器名称或ID
  2. 从 1 小时前开始,带时间戳显示日志:

    1
    docker logs --since 1h -t 容器名称或ID
  3. 显示今天早上 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

这样,你就可以进入正在运行的容器并执行命令。