由于不同的机器有不同的操作系统,以及不同的库和组件,在将一个应用部署到多台机器上需要进行大量的环境配置操作。

Docker 主要解决环境配置问题,它是一种虚拟化技术,对进程进行隔离,被隔离的进程独立于宿主操作系统和其它隔离的进程。使用 Docker 可以不修改应用程序代码,不需要开发人员学习特定环境下的技术,就能够将现有的应用程序部署在其它机器上。

注:本文档借助了 AI 进行润色和纠错。


虚拟化技术与 Docker

虚拟化技术的核心目标是在单一物理硬件上运行多个隔离的计算环境。目前主流的两类虚拟化方案是虚拟机 (Virtual Machine, VM)容器 (Container),它们在架构、资源开销、启动速度和适用场景等方面存在显著差异。

容器 (如 Docker) 直接运行在宿主机的操作系统内核之上,通过命名空间 (Namespaces) 和控制组 (Cgroups) 等 Linux 内核特性实现进程级别的隔离。每个容器仅包含应用程序及其依赖 (如运行库、配置文件等),共享宿主机的内核,无需额外的操作系统实例。

+-------------------+  +-------------------+  +-------------------+
|   CONTAINER-1     |  |    CONTAINER-2    |  |    CONTAINER-3    |
| +---------------+ |  | +---------------+ |  |                   |
| |    Tomcat     | |  | |  SQL Server   | |  |                   |
| |---------------| |  | |---------------| |  | +---------------+ |
| |     Java      | |  | |     .NET      | |  | | Static Binary | |
| |---------------| |  | |---------------| |  | |---------------| |
| |    Dibian     | |  | |    Ubuntu     | |  | |    Alpine     | |
| +---------------+ |  | +---------------+ |  | +---------------+ |
+-------------------+  +-------------------+  +-------------------+
+-----------------------------------------------------------------+
|                             Kernel                              |
+-----------------------------------------------------------------+

虚拟机通过 Hypervisor (如 VMware、KVM、Hyper-V) 模拟完整的硬件环境,在其上运行独立的 Guest OS。每个虚拟机都包含完整的操作系统、系统库和应用程序,彼此完全隔离,但资源开销大。虚拟机也是一种虚拟化技术,它与 Docker 最大的区别在于它是通过模拟硬件,并在硬件上安装操作系统来实现。

+---------------------------------+    +---------------------------------+
|     App A      |     App B      |    |     App A      |     App B      |
|----------------|----------------|    |----------------|----------------|
|    Bins/Libs   |    Bins/Libs   |    |    Bins/Libs   |    Bins/Libs   |
|----------------|----------------|    |---------------------------------|
|    Guest OS    |    Guest OS    |    |                                 |
|---------------------------------|    |          Docker Engine          |
|            Hypervisor           |    |                                 |
|---------------------------------|    |---------------------------------|
|             Host OS             |    |             Host OS             |
|---------------------------------|    |---------------------------------|
|              Server             |    |              Server             |
+---------------------------------+    +---------------------------------+

Tips: (1) 左图为虚拟机架构示意图,右图为容器 (docker) 架构示意图
(2) 容器共享宿主机内核,而虚拟机各自拥有完整的操作系统栈

Docker 的核心优势在于轻量化。启动虚拟机需要先启动虚拟机的操作系统,再启动应用,这个过程非常慢。并且由于是一个完整的操作系统,一个虚拟机需要占用大量的磁盘、内存,以及 CPU 资源,一台物理机只能开启几十个虚拟机。而一个 Docker 容器相当于启动宿主操作系统上的一个进程,只需要将应用以及相关的组件打包,在运行时占用很少的资源,一台机器可以开启成千上万个 Docker。

除了启动速度快以及占用资源少之外,Docker 还具备诸多显著优势:

  1. 更容易迁移:提供一致性的运行环境。已经打包好的应用可以在不同的机器上进行迁移,而不用担心环境变化导致无法运行。
  2. 更容易维护:使用分层技术和镜像,使得应用可以更容易复用重复的部分。复用程度越高,维护工作也越容易。
  3. 更容易扩展:可以使用基础镜像进一步扩展得到新的镜像,并且官方和开源社区提供了大量的镜像,通过扩展这些镜像可以非常容易得到我们想要的镜像。

Docker 的适用场景也十分广泛,在持续集成场景中,持续集成强调频繁将代码集成到主干以快速发现错误,而 Docker 轻量级、隔离性强的特性,能保证代码集成到某个 Docker 容器中时,不会对其他 Docker 容器产生任何影响;在云服务领域,可根据应用的实际负载情况,灵活增加或减少 Docker 容器数量,以此搭建可伸缩的云服务;此外,Docker 轻量级的特点也使其成为部署、维护和组合微服务的理想选择,非常适合用于搭建微服务架构。


核心概念(镜像、容器、仓库)

首先,需要了解 docker 的几个概念:

镜像 (Image)

Docker 镜像就相当于是一个 root 文件系统。比如官方镜像 ubuntu:16.04 就包含了完整的一套 Ubuntu16.04 最小系统的 root 文件系统。

镜像是一种静态的结构,可以看成面向对象里面的类,而容器是镜像的一个实例。还可以类比虚拟机。镜像就好像是虚拟机快照文件,通过同一个快照文件,可以复制成很多个不同的虚拟机 (容器)。

镜像包含着容器运行时所需要的代码以及其它组件,它是一种分层结构,每一层都是只读的 (read-only layers)。构建镜像时,会一层一层构建,前一层是后一层的基础。镜像的这种分层存储结构很适合镜像的复用以及定制。构建容器时,通过在镜像的基础上添加一个可写层 (writable layer),用来保存着容器运行过程中的修改。

Docker 镜像可以通过多种方式唯一标识。既可以使用完整的长 ID (如 sha256:abc123...),也可以使用其前几位的短 ID (如 abc123);此外,还可以通过 Repository:TAG (如 nginx:1.25) 或基于内容的摘要 (digest,如 nginx@sha256:...) 来引用。其中,标签便于人类阅读但可能被覆盖,而摘要和 ID 基于镜像内容生成,具有唯一性和不可变性,更适合在生产环境中确保一致性。

Docker 分层结构

容器 (Container)

镜像和容器的关系,就像是面向对象程序设计中的类和实例一样,镜像是静态的定义,容器是镜像运行时的实体。容器可以被创建、启动、停止、删除、暂停等。

镜像 (Image) 是容器运行的静态模板,包含了应用及其所有依赖。当镜像被启动时,就生成了一个容器 (Container)。容器并非运行在独立的操作系统之上,而是直接依托于宿主机的操作系统内核,它通过命名空间 (Namespaces) 和控制组 (Cgroups) 等内核机制,在共享宿主机内核的同时实现进程、文件系统、网络等资源的隔离。因此,容器本质上是宿主机上的一个轻量级、隔离的进程,既无需引导完整操作系统,又能提供类似独立环境的运行体验。这种与宿主系统的紧密耦合,使得容器具备秒级启动、低开销和高密度部署的优势。

容器的生命周期状态有 7 种:

1    created       已创建
2    restarting    重启中
3    running       运行中
4    removing      迁移中
5    paused        暂停
6    exited        停止
7    dead          死亡

仓库 (Repository) 与注册中心 (Registry)

仓库可看成一个代码控制中心,用来保存镜像。

镜像作为容器运行的基础,不仅需要在本地构建和使用,更常常需要在团队、环境或云平台之间共享与复用。为了高效地存储、版本管理和分发镜像,Docker 引入了仓库 (Repository) 与注册中心 (Registry) 的概念。注册中心是一个集中式的镜像存储服务,而仓库则是其中用于组织某一类镜像 (通常对应一个应用或组件) 的逻辑单元,包含该镜像的多个版本 (Tag)。通过将镜像推送到注册中心,开发者可以在任意支持 Docker 的环境中拉取并运行一致的应用实例,从而真正实现 “构建一次,随处运行” 的核心价值。

(Docker 从 17.03 版本之后分为 CE 和 EE 两个版本。(Community Edition,社区版),EE(Enterprise Edition,企业版),一般使用社区版。)


Docker 镜像管理命令(Image)

命令 说明 注释
docker search [OPTIONS] <keyword> 搜索镜像
docker build [OPTIONS] PATH/URL/- 从Dockerfile或者上下文(PATH、URL)中构建镜像
其中URL可以是以下内容:
(1) 引用Git存储库
(2) 引用预打包的tarball上下文
(3) 引用纯文本文件
(1)
docker images [OPTIONS] [REPOSITORY[:TAG]] 查看已经构建的镜像列表 (2)(3)
docker pull [OPTIONS] NAME[:TAG/@DIGEST] 从docker存储库中拉取镜像到本地 (4)
docker rmi [OPTIONS] IMAGE [IMAGE...] 删除已经构建好的镜像 (5)

注释:

  1. --tag/-t:名称和可选的"名称:标签"格式的标签
  2. --all/-a:显示所有图像(默认隐藏中间图像)
  3. --filter/-f:根据提供的条件过滤输出
  4. --all-tags/-a:下载存储库中的所有标记图像
  5. --force/-f:强制删除图像,如果使用该 -f 标志并指定图像的短 ID 或长 ID,则此命令将取消 tag 并删除与指定 ID 匹配的所有镜像

Docker 容器生命周期管理(Container)

命令 说明 注释
docker run 初始化一个容器,将镜像放到容器中
docker ps [OPTIONS] 查看容器列表 (类比ls) (1)(2)(3)(4)
docker start/restart 启动/重启一个容器
docker stop 停止一个正在运行的容器
docker rm 删除一个容器

注释:

  1. --all/-a:显示所有容器 (默认显示刚刚运行)
  2. --filter/-f "conditions": 根据提供的条件过滤输出,可通过容器名称or容器端口号等条件过滤
  3. --quiet/-q:仅显示容器 ID
  4. --size/-s: 显示总文件大小,每个容器显示两个不同的磁盘大小。

初始化容器 (docker run)

初始化 docker 容器的命令如下,其中 IMAGE 为要运行的镜像。

docker run [OPTIONS] IMAGE [COMMAND] [ARG...]

选项 (OPTIONS)

  • detach/-d:让容器在后台运行
  • publish-all/-P:将所有暴露的端口发布到随机端口
  • publish/-p:是容器内部端口绑定到指定的主机端口,如 docker run -d -p [主机端口]:[容器端口] [IMAGE]
  • hostname/-h:容器主机名,访问域名或 ip
  • name:自定义容器名字,需唯一
  • restart:容器退出时应用的重启策略,默认值为 no
  • volume/-v:绑定挂载卷,即目录映射:[主机目录]:[容器内目录],把容器的数据保存到主机,防止容器突然停止或被删除数据丢失
  • rm:容器退出时自动移除
  • interactive/-i:交互式操作,即使没有连接,也保持 STDIN 打开
  • tty/-t:终端,分配一个伪 TTY

示例1:交互式运行

docker run -it ubuntu /bin/bash

# ubuntu: ubuntu 镜像。
# /bin/bash:放在镜像名后的是命令,这里我们希望有个交互式 Shell,因此用的是 /bin/bash。

示例2:Nginx 部署

docker run -itd -p 8080:80 --name my-nginx nginx
# -d,--detach:以后台模式运行容器,并返回容器ID;
# -t,--tty:为容器分配一个伪终端;
# -i,--interactive,以交互模式运行容器,并保持终端激活;
## 一般创建的docker容器都需要使用 -d ,除非你想创建一个前台运行的容器,操作完成后就想让它停止;
## 如果在 docker run -it 不使用 -d 那么启动容器后如果你使用 ctrl+c 或 exit 退出交互模式,docker容器会停止运行
## -it 一般会同时使用。

# -p 选项用于将容器的80端口映射到宿主机的8080端口;
# --name 选项用于指定容器的名称为my-nginx;
# 最后的 nginx 是要运行的镜像的名称。

示例3:Pikachu 靶场

docker run -itd -p 8888:80 --name pikachu area39/pikachu

此时再执行 docker ps -a,这时会新开一个 pikachu 容器,PORT为 3306/tcp, 0.0.0.0:8888->80/tcp, :::8888->80/tcp。在物理主机访问 http://虚拟机IP:8080,成功访问靶场。


查看容器 (docker ps)

使用 ps 命令查看容器信息:

docker ps [OPTIONS]

选项 (OPTIONS)

  1. --all/-a:显示所有容器 (默认显示刚刚运行)
  2. --filter/-f "conditions": 根据提供的条件过滤输出,可通过容器名称or容器端口号等条件过滤
  3. --quiet/-q:仅显示容器 ID
  4. --size/-s: 显示总文件大小,每个容器显示两个不同的磁盘大小。(size 是每个容器的可写层的数据量 (在磁盘上),virtual size 是容器使用的只读镜像数据加上容器的可写层所使用的总数据量。两个容器间的镜像数据可能是共享的)

示例--filter

# 匹配名称中包含该`name=nostalgic`字符串的所有容器
docker ps --filter "name=nostalgic"

# 匹配所有发布端口为 80 的容器
docker ps --filter publish=80

# 输出条件介绍:
 # CONTAINER ID: 容器ID。
 # IMAGE: 使用的镜像。
 # COMMAND: 启动容器时运行的命令。
 # CREATED: 容器的创建时间。
 # STATUS: 容器状态。状态有7种:
   # 1. created(已创建)
   # 2. restarting(重启中)
   # 3. running(运行中)
   # 4. removing(迁移中)
   # 5. paused(暂停)
   # 6. exited(停止)
   # 7. dead(死亡)
 # PORTS: 容器的端口信息和使用的连接类型 (tcp\udp)
 # NAMES: 自动分配/自定义的容器名称。

容器的启停、删除

启动已停止运行的容器:

docker start CONTAINER_ID/CONTAINER_NAME
docker restart CONTAINER_ID/CONTAINER_NAME

停止运行的容器:

docker stop CONTAINER_ID/CONTAINER_NAME

删除已停止运行的容器:

docker rm [OPTIONS] CONTAINER_ID/CONTAINER_NAME

可通过 --force/-f 强制删除正在运行的容器。


与运行中容器交互

命令 说明 选项 Options
docker exec [OPTIONS] CONTAINER COMMAND [ARG...] 临时进入一个容器(命令行)
用此命令退出容器终端,不会导致容器的停止。
--detach/-d分离模式:后台运行命令
-interactive/-i交互式操作,即使没有连接,也保持STDIN打开
--tty/-t终端,分配一个伪TTY
docker attach [OPTIONS] CONTAINER 临时进入一个容器(命令行)。
如果从这个容器退出,会导致容器的停止。
docker logs [OPTIONS] CONTAINER 查看某个容器的日志 --follow/-f: 跟踪日志输出
--since: 显示某个开始时间的所有日志
--time/-t: 显示时间戳
--tail: 仅列出最新N条容器日志
例如:docker logs --since="2016-07-01" --tail=10 mynginx
表示"查看容器mynginx从2016年7月1日后的最新10条日志"
docker inspect [OPTIONS] CONTAINER 查看Docker底层信息 --format/-f: 使用给定的Go模板格式化输出

进入容器

在使用 -d 参数时,容器启动后会进入后台。此时想要进入容器,可以通过以下指令进入:

docker exec -it [容器ID] /bin/bash
# 如果没有启动,可以使用 docker start [CONTAINER_ID/CONTAINER_NAME] 来启动容器

# 进行一些手动操作,例如向 hosts 中写入一行
echo "172.16.10.20 csmp.qaxc-inc.cn" >> /etc/hosts

查看容器详细信息

使用 inspect 参数查看 Docker 的底层信息, 它会返回一个 JSON 文件记录着 Docker 容器的配置和状态信息,如 ip 地址,mac 地址,镜像名称,日志路径,端口映射等。

# 获取IP地址
docker inspect --format='{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' $INSTANCE_ID

# 获取mac地址
docker inspect --format='{{range .NetworkSettings.Networks}}{{.MacAddress}}{{end}}' $INSTANCE_ID

# 获取日志路径
docker inspect --format='{{.LogPath}}' $INSTANCE_ID

# 获取镜像名称
docker inspect --format='{{.Config.Image}}' $INSTANCE_ID

# 获取所有端口映射
docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}} {{$p}} -> {{(index $conf 0).HostPort}} {{end}}' $INSTANCE_ID

查看容器日志

使用 logs 参数查看容器日志文件。

docker logs [OPTIONS] CONTAINER

# 常用选项:
# --follow/-f: 跟踪日志输出
# --since: 显示某个开始时间的所有日志
# --time/-t: 显示时间戳
# --tail: 仅列出最新N条容器日志
# 例如: docker logs --since="2016-07-01" --tail=10 mynginx
# 表示"查看容器mynginx从2016年7月1日后的最新10条日志"

Docker 高级特性与数据管理

端口映射与网络

在 Docker 中,容器默认运行在一个隔离的网络命名空间中,其内部服务 (如 Web 服务器、数据库等) 无法被宿主机或其他外部设备直接访问。为了让外部能够与容器通信,Docker 提供了端口映射 (Port Mapping) 机制,通过 -p/--publish 选项将容器内部的端口暴露到宿主机上。基本语法如下:

docker run -p <HOST_PORT>:<CONTAINER_PORT> [OPTIONS] <IMAGE>
# HOST_PORT: 外部访问时使用的端口,可自定义以避免冲突。
# CONTAINER_PORT: 应用在容器内部监听的端口 (例如 MySQL 默认使用 3306,Nginx 使用 80)。

常见用法示例

(1) 标准映射。将容器的 80 端口映射到宿主机的 8080 端口:

docker run -d -p 8080:80 nginx
# 此时可通过 http://localhost:8080`访问 Nginx 服务。

(2) 自动分配宿主机端口。若只指定容器端口,Docker 会自动分配一个可用的高位端口(通常在 32768–65535 范围内):

docker run -d -p 80 nginx
# 可通过 `docker port <容器名>` 查看实际映射的端口。

(3) 绑定特定 IP 地址。可限制仅允许特定网络接口访问:

docker run -d -p 127.0.0.1:3306:3306 mysql
# 此时只有本机 (localhost) 能访问 MySQL,外部网络无法连接。

(4) 多端口映射。支持同时映射多个端口:

docker run -d -p 8080:80 -p 8443:443 nginx

端口冲突处理

当宿主机已有服务占用了目标端口 (例如本地已运行 MySQL 占用 3306),再尝试将容器的 3306 映射到同一端口会导致启动失败。此时有以下解决方式:

(1) 修改宿主机映射端口 (推荐):

docker run -d -p 3307:3306 mysql
# 外部通过 `localhost:3307` 连接容器中的 MySQL。

(2) 停止本地冲突服务 (若非必要):

sudo systemctl stop mysql

使用自定义 Docker 网络进行容器间通信(适用于容器互联场景,无需暴露到宿主机)。

补充说明:-P(大写)选项

使用 -P (--publish-all) 会自动将镜像中通过 EXPOSE 指令声明的所有端口映射到宿主机的随机高位端口。适用于快速测试,但不推荐用于生产环境,因其缺乏端口可控性。 (EXPOSE 本身不会发布端口,仅起到文档说明作用;必须配合 -p-P 才能真正实现端口暴露)

docker run -d -P nginx

数据持久化:卷 (Volumes) 与绑定挂载 (Bind Mounts)

文件复制:

docker cp /path/to/host/file.txt <container_name_or_id>:/path/to/container/file.txt

绑定挂载:

# 使用 -v 参数
docker run -v $(pwd)/var/csmpProbeCheck:/var/csmpProbeCheck -it python:latest /bin/bash

# 或使用 --mount 参数
docker run --mount type=bind,source=[/path/to/host/folder],destination=[/app] \
    [OPTIONS] IMAGES_NAME

# 示例:
docker run -d --name csmp-probe-check \
    --mount type=bind,source=/var/csmpProbeCheck,destination=/opt/app \
    csmp-app

文件挂载注意事项:

  1. 权限问题:确保宿主机的文件夹路径 /path/to/host/folder 对 Docker 守护进程是可访问的,并且具有适当的权限。
  2. 用户和组 ID:如果宿主机和容器内的用户和组 ID 不一致,可能会导致权限问题。可以通过在 Dockerfile 中设置用户和组来解决。
  3. 挂载路径:挂载路径在宿主机和容器中必须一致,且容器内的路径必须在镜像中已经存在。通过这种方式,你可以将宿主机的文件夹挂载到容器中,方便在容器中访问和操作宿主机的文件。

构建自定义镜像:Dockerfile

Dockerfile 是一个文本文件,其中包含一系列指令 (instructions),用于自动化构建 Docker 镜像。它定义了应用运行所需的环境、依赖、配置和启动方式,将 “代码+环境” 打包为可重复、可移植的镜像,是实现持续集成、交付与部署 (CI/CD) 及云原生开发的基础。

以下是几个核心指令的简要说明:

  • FROM:指定基础镜像 (如 ubuntu:22.04node:18),所有 Dockerfile 必须以此开头
  • WORKDIR:设置后续指令 (如 RUNCOPYCMD) 的工作目录,若目录不存在会自动创建
  • COPY:将本地文件或目录复制到镜像中的指定路径,例如 COPY app.js /app/
  • RUN:在镜像构建过程中执行命令 (如安装软件包),每条 RUN 会生成一个新的镜像层。
  • CMD/ENTRYPOINT:定义容器启动时默认执行的命令。CMD 提供默认参数,可被 docker run 后的命令覆盖;ENTRYPOINT 定义主命令,不易被覆盖,常与 CMD 配合使用以提供默认参数

下面是一个使用 Dockerfile 构建 Python 镜像的示例。

(1) Dockerfile 示例

# 基于官方 Python 镜像
FROM python:3.11.4-slim-bullseye

# 复制自定义的 hosts 文件
COPY hosts /etc/hosts

# 设置工作目录
WORKDIR /opt/app

# 复制当前目录下的文件到工作目录
COPY . /opt/app/

# 安装 python 依赖
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

# 设置默认命令
ENTRYPOINT ["/bin/sh", "-c", "python main.py"]

(2) 构建镜像

docker build -t <image-name> <context-path>
docker build -t csmp-probe-check .
# -t 参数用于指定镜像的名称(例如 csmp-probe-check )。

实战案例(搭建一个Python环境)

最简单快捷的部署方式是直接使用 Docker Hub 上的官方镜像 (如 python:3.11),它预装了标准运行环境,适合快速启动通用应用。

然而,在实际开发中,官方镜像往往无法完全满足特定项目的需求,例如缺少必要的系统依赖、Python 包版本不匹配,或需要集成自定义配置与代码结构。此时,就需要通过编写 Dockerfile 来构建定制化的镜像。这种构建方式允许我们在官方镜像基础上,安装额外依赖、复制应用代码、设置工作目录,并定义启动命令,从而打造一个与项目高度契合、可复现且便于分发的开发环境。


方法一:直接 pull 镜像

Docker Hub 上提供了官方的 Python 镜像,可以根据需要选择合适的版本。

(1) 拉取最新版本的 Python 镜像

# 拉取 Python 镜像
docker pull python:latest
# 如果你需要特定版本的 Python(例如 Python 3.9),可以指定版本号:
docker pull python:3.9

(2) 启动 Python 容器。可以通过 docker run -it 命令启动一个 Python 容器,并进入交互模式:

docker run -it python:latest /bin/bash
# -it        参数表示以交互模式运行容器。
# /bin/bash  是容器启动后执行的命令,用于进入Bash终端。

方法二:基于 Dockerfile 创建

如果你需要一个更定制化的 Python 环境 (例如安装特定的库),可以创建一个 Dockerfile。

# Dockerfile1
# 基于官方 Python 镜像
FROM python:3.11.4-slim-bullseye

# 复制自定义的 hosts 文件
COPY hosts /etc/hosts

# 设置工作目录
WORKDIR /opt/app

# 设置环境参数
# 略

# 复制当前目录下的文件到工作目录
COPY . /opt/app/

# 安装 python 依赖
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

# 设置默认命令
ENTRYPOINT ["/bin/sh", "-c", "python main.py"]

/csmpProbeCheck 文件树如下:

csmpProbeCheck
 +-- log/
 +-- conf.py
 +-- flowProbeCheck.py
 +-- keepAlive.py
 +-- main.py
 +-- requirements.txt
 +-- TOKEN.txt
 `-- toolkits.py

requirements.txt

pandas==2.1.4
requests==2.31.0

方法三:Dockerfile + 挂载宿主机文件

编写 Dockerfile

# 基于官方 Python 镜像
FROM python:3.11.4-slim-bullseye

# 创建一个目录,用于挂载宿主机的文件夹
RUN mkdir -p /opt/app

# 设置工作目录
WORKDIR /opt/app

# 复制当前目录下的 requirements.txt 文件到工作目录
COPY ./requirements.txt /opt/app/

# 安装 python 依赖
RUN pip install -r requirements.txt -i https://pypi.tuna.tsinghua.edu.cn/simple

# 设置默认命令
ENTRYPOINT ["/bin/sh", "-c", "python main.py"]

# 或者使用一个占位命令,确保容器启动后不会立即退出
# CMD ["tail", "-f", "/dev/null"]

生成docker镜像

根据 Dockerfile,通过 docker build 生成镜像:

docker build -t csmp-app .

docker 容器初始化

通过 docker run 将镜像投放到容器中 (初始化):

Docker 的守护模式 (Daemon Mode) 和交互模式 (Interactive Mode) 是运行容器的两种不同方式,它们的主要区别在于容器的运行方式和用户与容器的交互方式。

区别项 守护模式 (Daemon Mode) 交互模式 (Interactive Mode)
运行位置 后台运行,不会占用当前终端 前台运行,占用当前终端
适用场景 适合运行长期运行的服务 (如Web服务器、数据库等) 适合需要用户交互的场景 (如开发环境、调试工具等)
可交互性 容器的标准输入 (stdin) 被关闭,
用户无法直接与容器交互
容器的标准输入 (stdin) 保持打开状态,
用户可以通过终端直接与容器交互
终端信息 容器的标准输出 (stdout) 和标准错误 (stderr)
通常被重定向到日志文件或 Docker 的日志系统中。
容器的标准输出 (stdout) 和标准错误 (stderr)
直接显示在终端上。

(1) 守护模式 (Daemon Mode) 初始化

docker run -d -p 8000:8000 --name my-container \
    -v /path/to/host/folder:/app \
    my-app
# --detach/-d:以守护模式运行容器;
# --port/-p 8000:8000:将容器的8000端口映射到宿主机的8000端口;
# --name my-container:为容器指定一个名称;
# -v /path/to/host/folder:/app:将宿主机的"/path/to/host/folder"挂载到容器的"/app"目录中;
# my-app:使用之前构建的镜像。

## 示例:
docker run -d -p 8000:8000 --name csmp-app \
    -v /var/csmpProbeCheck:/opt/app \
    csmp-app

(2) 交互模式 (Interactive Mode) 初始化

docker run -it --name my-container my-image
# -i:表示交互式,保持标准输入(stdin)打开
# -t:分配一个伪终端

## 示例:
docker run -it --name csmp-app csmp-app

进入已经启动的容器

docker exec -it [CONTAINER_ID] /bin/bash
# 如果没有启动,可以使用 docker start [CONTAINER_ID/CONTAINER_NAME] 来启动容器

# 进行一些手动操作,例如向 hosts 中写入一行
echo "172.16.10.20 csmp.qaxc-inc.cn" >> /etc/hosts

退出 docker 容器

  1. 在交互模式下,可以通过输入 exit 或按 Ctrl+D 退出容器,容器将停止运行。
  2. 如果你只是想暂时离开容器而不退出,可以按 Ctrl+P 然后立刻按下 Ctrl+Q 组合键。容器将继续在后台运行,终端将返回到宿主机。

运维、迁移与应急响应

容器与镜像的清理

删除镜像步骤

在容器处于启动状态下,需要先停用容器,再对容器进行删除操作,最后再删除镜像。

docker stop <container_id>  # 停掉启动容器
docker rm <container_id>    # 删除容器
docker rmi <image_id>       # 删除镜像

docker rm <container_id> 命令报错,可尝试使用 docker rmi -f 强制删除镜像。可以使用镜像的短 ID 或长 ID、tag 或摘要来删除镜像。如果图像有一个或多个引用它的 tag,必须在删除图像之前删除所有这些 tag。当镜像被 tag 删除时,摘要引用会自动删除。

容器、镜像的铲除

  1. 使用 docker ps 查看正在运行的容器,并使用 docker stop [CONTAINER_ID] 停止相关容器;
  2. 使用 docker ps -a 全面检查相关容器,然后使用 docker rm [CONTAINER_ID] 删除相关容器;
  3. 使用 docker images 查看本地镜像列表,然后使用 docker rmi [IMAGES_ID] 删除相关镜像;

容器迁移(导入、导出)

在 Docker 中,移动容器通常指的是将容器从一个主机迁移到另一个主机。以下是一些常见的方法:

使用 Docker 的导出和导入功能

导出容器:可以使用 docker export 命令将运行中的容器导出为一个 tar 包。

docker export -o container.tar <container_id>

导入容器:将导出的 tar 包传输到目标主机后,使用 docker import 命令将其导入为一个新的镜像。

docker import container.tar <image_name>:<tag>

运行容器:导入后,使用 docker run 命令启动容器。

docker run -d --name <new_container_name> <image_name>:<tag>

使用 export + import 命令的方式移动 docker 容器,简单易用不需要额外工具。但这种方式不保留容器的运行状态 (如内存状态、网络配置等),只保存文件系统。

使用 Docker 镜像

# 基于运行中的容器创建镜像 (commit)
docker commit <container_id> <new_image_name>:<tag>
# 推送镜像到仓库 (push)
docker push <new_image_name>:<tag>
# 在目标主机拉取镜像 (pull)
docker pull <new_image_name>:<tag>
# 运行容器 (run)
# 优点:可以保留容器的文件系统和部分配置。
# 缺点:不保留容器的运行状态(如内存、网络等)。
docker run -d --name <new_container_name> <new_image_name>:<tag>

使用 CRIU 进行迁移

CRIU (Checkpoint/Restore in Userspace) 是一种用户态工具,用于冻结正在运行的进程 (包括容器),将其状态保存为文件 (checkpoint),之后可在相同或不同机器上恢复 (restore) 该进程。在 Docker 中,CRIU 可实现容器的热迁移快照备份快速重启。用法示例如下:

# 创建检查点
docker checkpoint create <container> <checkpoint-name>
# 从检查点恢复
docker start --checkpoint <checkpoint-name> <container>

使用 CRIU 需要 Linux 内核支持,并且 Docker 必须在编译时启用 CRIU 功能。(通常需要 Linux Core ≥ 3.11 及以上版本,且部分发行版默认未启用)


失陷容器应急处理

确认容器失陷后,一般我们可以采取暂停容器、隔离容器甚至杀死容器的方式来做紧急处理。

  1. 使用 docker pause 暂停容器中所有的进程

  2. 使用 docker commit 用于将被入侵的容器来构建镜像,从而保留现场痕迹,以便溯源

  3. 将正在运行的 Docker 容器禁用网络

# 将运行中的容器与用户定义的网桥断开连接
$ docker network disconnect bridge <container-name>

# 禁用 veth
$ docker exec -it <container-name> cat /sys/class/net/eth0/iflink
29
$ ip link show | grep 29
29: vethbf5239e@if28: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue masteefault
$ ip link set vethbf5239e down
  1. 使用 docker kill 杀掉运行中的容器 (谨慎操作!!!)
docker kill -s KILL <container-name>

常见问题排查

通过 docker compose 配置文件(yml)拉取 docker 镜像

以 vulhub fastjson/1.2.24-rce 靶场为例:

cd fastjson/1.2.24-rce
docker-compose build
docker-compose up -d

-d 为 “以后台的方式运行容器”


报错 docker-compose API 版本过低

[centos@CentOS]../fastjson/1.2.47-rce❯ docker-compose up -d
ERROR: client version 1.22 is too old. Minimum supported API version is 1.24, please upgrade your client to a newer version

(如果你使用 Docker 1.13 以上版本,需要将 docker-compose.yml 中的 version:‘2’ 修改为 version: ‘2.1’ 也就是这里的 version。然后再 docker-compose up -d 试试。)


报错连接超时

ubuntu@ubuntu:~$ docker pull python:latest
Error response from daemon: Get "https://registry-1.docker.io/v2/ ": net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)

编辑配置文件 daemon.json 文件,配置镜像加速地址,文件内容如下:

{
  "registry-mirrors": [
    "https://docker.m.daocloud.io",
    "https://hub-mirror.c.163.com"
  ]
}

重启 Docker 服务:sudo systemctl daemon-reload && sudo systemctl restart docker


没有vi/vim/nano,如何操作文档

# 删除最后一行
sed -i '$d' [FILEPATH]
# 往文件中写一行
echo "Hello, world!" >> [FILEPATH]

其他注意事项

数据持久化

如果需要持久化数据(例如代码文件或数据库),可以使用Docker卷:

docker run -v $(pwd)/var/csmpProbeCheck:/var/csmpProbeCheck -it python:latest /bin/bash

这会将当前目录下的 /csmpProbeCheck 文件夹挂载到容器的 /var/csmpProbeCheck 目录。

如何查看 docker 配置文件 (daemon.json) 的默认路径

dockerd --help

在输出中,你会看到 --config-file 参数,它指定了 Docker 配置文件的路径。

默认情况下,Docker 使用 /etc/docker/daemon.json 文件。 Daemon configuration file (default “”)


参考文档