Jiahong 的个人博客

凡事预则立,不预则废


  • Home

  • Tags

  • Archives

  • Navigation

  • Search

Docker——Docker使用笔记

本文用于记录一些Docker使用过程中的经验,最新添加的一些部分包含 AI 辅助创作


整体说明

  • Docker 是一个开源的容器化平台,可以让开发者将应用程序及其依赖项打包到一个可移植的容器中,然后发布到任何支持 Docker 的环境中
  • Docker 的核心基本概念
    • 镜像(Image) :包含运行应用所需的代码、库、环境变量和配置文件的模板,占用硬盘物理存储
    • 容器(Container) :镜像的运行实例 ,可以被创建、启动、停止、删除
      • 一个镜像可以启动多个容器,启动的容器会占用内存,就像一个虚拟系统
    • 仓库(Repository) :存储和分发 Docker 镜像的地方(如 Docker Hub)

Docker 常用命令

镜像(Image)相关 Docker 命令

  • 镜像相关 Docker 命令示例
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 搜索镜像
    docker search [镜像名]

    # 拉取镜像
    docker pull [镜像名]:[标签] # 不指定标签默认latest

    # 查看本地镜像
    docker images

    # 删除镜像
    docker rmi [镜像ID或镜像名]

容器(Container)相关 Docker 命令

  • 创建和启动容器

    1
    2
    3
    4
    5
    # 创建并启动容器,docker run = docker create + docker start
    docker run [选项] 镜像名 [命令]

    # 示例: 创建并启动一个nginx容器
    docker run -d -p 8080:80 --name mynginx nginx
    • 常用选项包括
      • -d: 后台运行
      • -p 主机端口:容器端口:端口映射
      • -v 主机目录:容器目录: 目录挂载,可多次使用 -v 参数挂在多个目录
      • --name 容器名:指定容器名称
      • -it:组合选项,-i 保持标准输入打开,-t 分配伪终端(终端交互模式)
      • 更多详细选项见附录
  • 容器相关常用操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    # 查看运行中的容器
    docker ps

    # 查看所有容器(包括停止的,刚启动的也算是停止的)
    docker ps -a

    # 创建容器,参数和 docker run 几乎一致
    docker create xxx

    # 启动/停止/重启容器
    docker start [容器ID/容器名]
    docker stop [容器ID/容器名]
    docker restart [容器ID/容器名]

    # 进入容器内部,不影响启动中的 docker 并与之交互
    docker exec -it [容器ID/容器名] /bin/bash

    # 删除容器
    docker rm [容器ID/容器名]

    # 查看容器日志
    docker logs [容器ID/容器名]

其他重要命令

  • 查看Docker系统信息

    1
    docker info
  • 清理无用的容器、镜像等

    1
    2
    # 清空所有悬空(dangling)镜像
    docker system prune

创建自定义镜像及构建

  • 创建 Dockerfile 文件:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    # 基础镜像
    FROM ubuntu:20.04

    # 维护者信息
    MAINTAINER Your Name <your@email.com>

    # 安装依赖
    RUN apt-get update && apt-get install -y python3

    # 复制文件到容器
    COPY ./app /app

    # 工作目录
    WORKDIR /app

    # 暴露端口
    EXPOSE 8000

    # 容器启动命令
    CMD ["python3", "app.py"]
  • 基于上述的 dockerfile 文件构建镜像:

    1
    2
    # . 表示在当前目录下读取 dockerfile 文件以构建镜像
    docker build -t myapp:1.0 .
    • -t, --tag 指定标签
    • -f, --file 指定 Dockerfile 文件,默认在当前目录下搜索 Dockerfile 文件
    • 命令最后的这里的 . 表示 “当前工作目录”,即你在终端中执行 docker build 命令时所在的目录,也称为构建上下文
      • 这是指 Docker 引擎在构建镜像时可以访问的文件目录
      • 当执行 docker build 时,Docker 会将这个目录下的所有文件(除了 .dockerignore 中排除的文件)打包发送给 Docker 引擎,供 Dockerfile 中的指令(如 COPY、ADD)使用
      • 通常这个目录下包含一些配置文件或代码等必须的东西
  • 运行自定义镜像:

    1
    docker run -d -p 8000:8000 myapp:1.0

Docker 服务相关操作

  • 服务相关的常见操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    # 查看服务是否启动
    systemctl status docker

    # 启动Docker服务
    sudo systemctl start docker

    # 停止Docker服务
    sudo systemctl stop docker

    # 重启Docker服务
    sudo systemctl restart docker

    # 设置开机启动
    sudo systemctl enable docker
  • 在修改镜像源等操作后,经常还可能涉及到 docker 后台进程的重新加载,再重启 docker 服务,使得修改生效

    1
    2
    sudo systemctl daemon-reload
    sudo systemctl restart docker

高阶用法:多容器 Docker Compose

  • 对于多容器应用,可以使用 Docker Compose 管理:
    • 第一步:创建 docker-compose.yml 文件
    • 第二步:使用 docker-compose 命令启动所有服务
  • 详情待补充

高阶用法:修改镜像并提交

整体说明

  • Docker 中,提交(commit) 是指将容器的当前状态保存为一个新的镜像的操作
  • 当你对一个运行中的容器做了修改(比如安装了软件、配置了环境、添加了文件等),可以通过 docker commit 命令将这些修改固化为一个新的镜像 ,以便后续可以基于这个新镜像创建出具有相同状态的容器
  • 提交操作包含如下含义:
    • 保存容器的修改:当你在容器内做了一系列配置后,不需要重新编写 Dockerfile 就能将这些修改保存为新镜像
    • 快速创建自定义镜像:对于简单的环境定制,commit 比编写 Dockerfile 更快捷
    • 临时保存工作状态:可以作为开发过程中的临时快照

docker commit 命令的基本用法

  • 用法示例:

    1
    2
    3
    4
    5
    6
    7
    8
    # 用法模版
    docker commit [选项] 容器ID/容器名 新镜像名[:标签]

    # 提交容器时指定作者、描述信息,并添加暴露端口的指令
    docker commit -a "JoeZJH joezjh@gmail.com>" \
    -m "download and install Node.js" \
    -c "EXPOSE 3000" \
    mycontainer mynodeapp:1.0
  • 可用的选项(options)主要用于设置新镜像的元数据信息,常用选项如下:

    • -a 或 --author
      • 指定新镜像的作者信息,格式通常为 姓名 <邮箱>
      • 例如:-a "JoeZJH <joezjh@gmail.com>"
    • -c 或 --change
      • 在提交时为新镜像添加 Dockerfile 指令(如 CMD、ENV、EXPOSE 等),可以多次使用该选项添加多个指令
      • 例如:-c "EXPOSE 8080" -c "CMD ['nginx']"
    • -m 或 --message
      • 为本次提交添加描述信息(类似 Git 的 commit 消息)
      • 例如:-m "添加了 Python 环境和自定义配置"
    • -p 或 --pause
      • 提交时是否暂停容器(默认值为 true,即自动暂停容器以保证数据一致性)
      • 若需在容器运行中提交,可指定 -p false,但可能导致数据不一致

完整示例

  • 第一步:先启动一个基础容器并进行修改:

    1
    2
    3
    4
    5
    6
    7
    8
    # 启动一个ubuntu容器并进入
    docker run -it --name myubuntu ubuntu:20.04 /bin/bash

    # 在容器内安装nginx(模拟修改操作)
    apt-get update && apt-get install -y nginx

    # 退出容器
    exit
  • 第二步:将修改后的容器提交为新镜像:

    1
    docker commit myubuntu myubuntu-nginx:1.0
  • 第三步:查看新创建的镜像:

    1
    2
    docker images
    # 会看到 myubuntu-nginx:1.0 这个新镜像
  • 基于新镜像创建容器:

    1
    2
    docker run -it myubuntu-nginx:1.0 /bin/bash
    # 此时容器内已经预装了nginx

docker commit 注意事项

  • docker commit 与 Dockerfile 的区别:
    • commit 是通过容器修改生成镜像(黑箱操作,不清楚具体做了哪些修改)
    • Dockerfile 是通过明文指令构建镜像(可追溯、可重复、易维护)
    • 推荐使用 Dockerfile 而非 docker commit 来管理镜像 ,尤其是在生产环境
  • 频繁 commit 会导致镜像体积增大(因为每次提交都会增加新的层,可能会包含不必要的临时文件或缓存)
  • 可以通过 -a(作者)和 -m(提交信息)选项添加元数据:
    1
    docker commit -a "Your Name" -m "Install nginx" myubuntu myubuntu-nginx:1.0

附录:docker run 选项详解

  • 注:可以通过 docker run --help 查看所有选项的完整说明

容器标识与命名

  • --name <容器名>:为容器指定一个自定义名称,而非随机生成
    • 示例:docker run --name myapp nginx
  • --hostname <主机名>:设置容器内的主机名(/etc/hostname)
    • 示例:docker run --hostname container-host nginx
  • --network-alias <别名>:为容器在网络中设置别名(便于同一网络内的容器通过别名访问)

运行模式与交互

  • -d 或 --detach:后台运行容器(守护进程模式),不占用终端
    • 示例:docker run -d nginx
  • -it:组合选项,-i 保持标准输入打开,-t 分配伪终端(终端交互模式)
    • 示例:docker run -it ubuntu /bin/bash(进入容器交互界面)
  • --rm:容器停止后自动删除(适合临时任务,避免残留容器)
    • 示例:docker run --rm -it alpine ping baidu.com

资源限制

  • --memory <内存大小> 或 -m:限制容器使用的最大内存(如 1g、512m)
    • 示例:docker run -m 1g nginx
  • --cpus <核心数>:限制容器使用的 CPU 核心数(如 0.5 表示半核,2 表示双核)
    • 示例:docker run --cpus 2 redis
  • --gpus <参数>:分配 GPU 资源(需主机支持 NVIDIA Docker)
    • 示例:docker run --gpus all nvidia/cuda:11.0-base nvidia-smi(使用所有 GPU)

IPC 进程间通信设置

  • --ipc:
    • IPC(Inter-Process Communication,进程间通信)设置
    • 如 --ipc=host 表示容器将使用主机的 IPC 命名空间
    • 作用:允许容器内的进程与主机系统或其他共享相同 IPC 命名空间的容器进行通信,常用于需要共享内存的场景(如多进程训练)
  • --shm-size:
    • shm 是 /dev/shm 的缩写,即共享内存 tmpfs(临时文件系统),属于容器内进程间通信(IPC)的一部分,用于进程间快速共享数据
    • 如 --shm-size=512m 设置容器内共享内存的大小为 512MB
    • 作用:某些应用(如 PyTorch 的数据加载器 DataLoader、数据库、浏览器等)会使用 /dev/shm 作为临时缓存或共享内存区域
    • 默认大小通常为容器内存的 1/2(但不超过 64MB),如果应用需要更大的共享内存空间,需手动指定(如 --shm-size=512m)
      • 避免因默认共享内存不足导致的错误

端口映射

  • -p <主机端口>:<容器端口> 或 --publish:将容器内的端口映射到主机端口,实现外部访问
    • 格式:[主机IP:]主机端口:容器端口(主机IP可选,默认绑定所有IP)
      • 示例:
        • docker run -p 8080:80 nginx(主机8080端口映射到容器80端口)
        • docker run -p 127.0.0.1:8080:80 nginx(仅本地可访问)
  • -P 或 --publish-all:自动映射容器暴露的所有端口(随机映射到主机的高位端口)

数据持久化

  • -v <主机路径>:<容器路径> 或 --volume:挂载主机目录或数据卷到容器,实现数据持久化
    • 格式:主机路径:容器路径[:权限](权限如 ro 表示只读)
    • 示例:
      • docker run -v /host/data:/container/data nginx(主机目录挂载)
      • docker run -v myvolume:/container/data nginx(数据卷挂载,myvolume 为卷名)
  • --mount:更灵活的挂载方式(支持更多参数,如 type=bind 绑定目录、type=volume 数据卷等)
    • 示例:docker run --mount type=bind,source=/host/data,target=/container/data nginx

环境变量与配置

  • -e <键=值> 或 --env:设置容器内的环境变量
    • 示例:docker run -e "DB_HOST=localhost" -e "DB_PORT=3306" mysql
  • --env-file <文件路径>:从文件中读取环境变量(每行一个 键=值)
    • 示例:docker run --env-file .env nginx(.env 为环境变量文件)
  • --config <文件路径>:指定容器的配置文件(较少用)

网络配置

  • --network <网络名>:指定容器加入的网络(默认使用 bridge 网络)
    • 示例:docker run --network mynet nginx(加入自定义网络 mynet)
  • --dns <DNS地址>:设置容器的 DNS 服务器
    • 示例:docker run --dns 8.8.8.8 --dns 8.8.4.4 ubuntu
  • --add-host <主机名:IP>:在容器的 /etc/hosts 中添加主机映射
    • 示例:docker run --add-host "test:192.168.1.100" ubuntu

容器生命周期与依赖

  • --restart <策略>:设置容器退出后的重启策略(常用于服务型容器)
    • 可选策略包括
      • no:不重启(默认)
      • always:总是重启
      • on-failure[:次数]:失败时重启(可指定最大次数)
      • unless-stopped:除非手动停止,否则总是重启
    • 示例:docker run --restart always nginx
  • --link <容器名:别名>:链接到另一个容器(已过时,推荐用网络替代)

其他常用选项

  • --entrypoint <命令>:覆盖容器的默认入口命令(ENTRYPOINT)
    • 示例:docker run --entrypoint /bin/bash nginx(用 bash 替代默认的 nginx 启动命令)
  • -u <用户> 或 --user:指定容器内的运行用户(避免以 root 运行)
    • 示例:docker run -u 1000:1000 ubuntu(用 UID 1000、GID 1000 的用户运行)
  • --log-driver <驱动>:设置容器日志的驱动(如 json-file、syslog 等)

Docker——访问官方镜像库-代理配置


终端科学上网说明

  • 配置好科学上网后,网页能科学上网

  • 为了在终端科学上网,需要配置代理(修改环境变量为 VPN 的指定端口)

    1
    2
    export HTTP_PROXY="http://127.0.0.1:7897/"
    export HTTPS_PROXY="http://127.0.0.1:7897/"
  • 确认一下 Ubuntu 终端已经科学上网,即已经配置下面的情况

    1
    2
    3
    4
    # echo $HTTP_PROXY
    http://127.0.0.1:7897/
    # echo $HTTPS_PROXY
    http://127.0.0.1:7897/
    • 从输出可以看到,终端已经配置了代理(HTTP_PROXY 和 HTTPS_PROXY 均指向 http://127.0.0.1:7897/)
  • 此时在终端执行 curl 能成功

    1
    curl https://www.google.com
    • 注意此时用 ping 命令不一定能成功,因为 ping 使用的是 ICMP 协议
  • 综上已经确定了终端可以科学上网

  • 特别注意:一般来说终端的代理配置仅对当前终端会话生效 ,除非把环境变量配置添加到 ~/.bashrc 文件


docker 的问题

  • 通过网页访问 docker 核心库没问题(有返回没验证的说明就是 OK 的)
  • Docker 服务默认不会继承终端的代理环境变量 ,这常常是 Docker 拉取镜像仍失败的关键原因
  • 需要将代理配置同步给 Docker 服务,确保 Docker 能通过代理访问镜像仓库

为 Docker 配置代理(确保与终端代理一致)

第一步:创建 Docker 代理配置文件

  • Docker 服务的代理配置需通过专门的系统目录(/etc/systemd/system/docker.service.d/)生效,执行以下命令创建配置文件:
    1
    2
    3
    4
    5
    # 创建配置目录(若不存在)
    sudo mkdir -p /etc/systemd/system/docker.service.d

    # 创建代理配置文件
    sudo nano /etc/systemd/system/docker.service.d/proxy.conf

第二步:写入代理配置(与终端代理保持一致)

  • 在 proxy.conf 文件中添加以下内容(注意代理地址 127.0.0.1:7897 需与你终端的 HTTP_PROXY 完全一致,包括结尾是否带 /):

    1
    2
    3
    4
    [Service]
    Environment="HTTP_PROXY=http://127.0.0.1:7897/"
    Environment="HTTPS_PROXY=http://127.0.0.1:7897/"
    Environment="NO_PROXY=localhost,127.0.0.1,docker-registry.local.golang.org,.docker.internal"
    • NO_PROXY:指定无需代理的地址(避免本地服务/容器间通信走代理,防止冲突),无需修改

第三步:重启 Docker 服务,使代理生效

1
2
3
4
5
6
7
8
# 重新加载 systemd 配置(识别新的 proxy.conf)
sudo systemctl daemon-reload

# 重启 Docker 服务
sudo systemctl restart docker

# 验证 Docker 是否加载了代理配置
sudo systemctl show --property=Environment docker
* 若输出中包含 `HTTP_PROXY=http://127.0.0.1:7897/` 和 `HTTPS_PROXY=http://127.0.0.1:7897/`,说明 Docker 代理配置成功

再次尝试拉取 Docker 镜像

  • 现在 Docker 已通过代理访问外部仓库,执行拉取命令:
    1
    2
    3
    4
    5
    # 先测试小镜像 hello-world,验证代理是否生效
    sudo docker pull hello-world

    # 若 hello-world 成功,再拉取目标镜像
    sudo docker pull xxx

Docker——容器深入理解


整体说明

  • TLDR:容器的本质是封装了指定命令的进程

容器生命周期管理

  • 启动容器:
    • docker run [OPTIONS] IMAGE [COMMAND] [ARG...]:该命令用于从镜像创建并启动一个新的容器
      • [COMMAND] 是希望在容器启动时执行的程序或脚本,而 [ARG…] 就是这个程序或脚本运行所需的额外信息,比如 ls -l /
    • docker start [OPTIONS] CONTAINER [CONTAINER...]:该命令用于启动一个或多个已经停止的容器
    • 常用选项:
      • -d:后台运行容器
      • -p:端口映射,如 -p 8080:80 将宿主机的 8080 端口映射到容器的 80 端口
      • -v:数据卷挂载,如 -v /host/data:/container/data 将宿主机的目录挂载到容器内,可多次使用 -v 参数挂在多个目录
      • --name:给容器指定一个名称,如 --name my-web-app
      • -it:交互式终端,i 保持标准输入打开,t 分配一个伪终端
  • 停止容器:
    • docker stop [OPTIONS] CONTAINER [CONTAINER...]:优雅地停止一个或多个运行中的容器
    • docker kill [OPTIONS] CONTAINER [CONTAINER...]:强制停止一个或多个容器,不进行任何清理
    • docker restart [OPTIONS] CONTAINER [CONTAINER...]:重启一个或多个容器
  • 删除容器:
    • docker rm [OPTIONS] CONTAINER [CONTAINER...]:删除一个或多个已停止的容器
    • docker rm -f CONTAINER:强制删除一个正在运行的容器

查看容器信息

  • 查看容器:
    • docker ps [OPTIONS]:列出所有正在运行的容器
    • 常用选项:
      • -a:列出所有容器,包括已停止的
      • -s:显示总文件大小
      • -l:显示最新创建的容器
  • 查看容器日志:
    • docker logs [OPTIONS] CONTAINER:获取容器的日志
    • 常用选项:
      • -f:实时跟踪日志输出
      • --tail N:仅显示最新的 N 行日志
  • 查看容器详细信息:
    • docker inspect [OPTIONS] CONTAINER [CONTAINER...]:获取容器的详细配置和状态信息,以 JSON 格式输出
  • 查看容器资源使用情况:
    • docker stats [OPTIONS] [CONTAINER...]:实时显示一个或多个容器的 CPU、内存、网络 I/O 和块 I/O 使用情况

容器内部操作

  • 进入容器内部:
    • docker exec [OPTIONS] CONTAINER COMMAND [ARG...]:在运行中的容器内执行命令。这是最常用的方式,因为它不会启动新的进程
    • 常用例子:
      • docker exec -it my-container bash:在名为 my-container 的容器中启动一个 bash 终端
      • docker exec my-container ls -l /:在容器中执行 ls -l / 命令
  • 复制文件到容器或从容器复制文件:
    • docker cp [OPTIONS] SRC_PATH CONTAINER:DEST_PATH:从宿主机复制文件到容器
    • docker cp [OPTIONS] CONTAINER:SRC_PATH DEST_PATH:从容器复制文件到宿主机

容器进阶管理

  • 暂停/恢复容器:
    • docker pause CONTAINER [CONTAINER...]:暂停容器内的所有进程。容器状态变为 paused,资源仍然被占用
    • docker unpause CONTAINER [CONTAINER...]:恢复被暂停的容器
  • 清理无用容器:
    • docker container prune:删除所有已停止的容器
  • 批量管理:
    • docker stop $(docker ps -a -q):停止所有容器
    • docker rm $(docker ps -a -q):删除所有容器
    • 注意 :docker ps -a -q 会列出所有容器的 ID

docker create 和 docker run 的区别

  • TLDR:docker run = docker create + docker start

docker create

  • docker create 命令只负责从一个镜像创建一个容器
  • docker create 它会为容器分配一个 ID,设置好配置(如端口映射、数据卷等),但不会启动容器
  • 总结 docker create 命令:
    • 只创建,不启动 :容器处于“已创建”(Created)状态
    • 分步操作 :您需要先使用 docker create 创建容器,然后再使用 docker start 来启动它
    • 适用场景 :当你需要先配置好容器,但暂时不希望它运行,或者需要对容器进行额外的配置或检查后才启动时,这个命令很有用
      • 例如,在自动化脚本中,您可以先批量创建容器,然后再按需启动

docker run

  • docker run 命令是 docker create 和 docker start 的组合
  • docker run 它从一个镜像创建一个新的容器,并立即启动它
  • 总结 docker run 命令:
    • 创建并启动 :这是最常用的方式,一步到位
    • 一步到位 :大部分情况下,你希望容器创建后立即运行,所以 docker run 更方便快捷
    • 适用场景 :这是日常开发和部署中最常见的命令,因为它简化了操作流程

示例

  • 如果想创建一个 Nginx 容器并让它在后台运行,可以使用:

    1
    docker run -d --name my-nginx nginx
  • 这一个命令就完成了创建和启动

  • 但如果你想分步操作,可以这么做:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    # 只创建容器,容器不会运行,注意,容器启动命令在这里传入
    docker create --name my-nginx-created nginx

    # 检查容器是否已创建(状态为 "Created")
    docker ps -a

    # 手动启动这个已创建的容器
    docker start my-nginx-created

    # 检查容器是否已启动(状态为 "Up")
    docker ps

附录:一个 docker 容器创建和启动的实例

  • 容器创建:

    1
    docker create --runtime=nvidia --gpus all --net=host --shm-size="10g" --cap-add=SYS_ADMIN -v .:/workspace/python_demo --name python_demo <image:tag> sleep infinity
    • --runtime=nvidia:使用 NVIDIA 容器运行时,支持 GPU 加速
    • --gpus all:允许容器使用所有可用 GPU
    • --net=host:使用主机网络模式,容器与主机共享网络栈
    • --shm-size="10g":设置共享内存大小为 10GB,提升进程间通信效率
    • --cap-add=SYS_ADMIN:添加系统管理员权限,允许容器内执行更多系统操作
    • -v .:/workspace/python_demo:将当前目录挂载到容器内的 /workspace/python_demo 目录
    • --name python_demo:给容器命名为 “python_demo”
    • <image:tag>:指定要使用的 Docker 镜像及标签
    • sleep infinity:容器启动后执行无限休眠命令,保持容器运行
  • 启动容器:

    1
    docker start python_demo
    • 启动名为 “python_demo” 的容器
    • 执行此命令后,之前创建的容器将开始运行
  • 进入容器终端与容器交互:

    1
    docker exec -it python_demo bash
    • 进入 “python_demo” 容器的 bash 交互终端
    • -it:以交互式终端模式进入容器
    • python_demo:目标容器名称
    • bash:在容器内启动 bash shell
  • 以上所有命令也可以使用一个 docker run 命令来实现


附录:Linux 中的 sleep infinity 命令常用于 docker 容器

  • 在 Linux 系统中,sleep infinity 命令会让进程进入无限期休眠状态
    • sleep 命令的作用是让当前进程暂停指定的时间
    • 当使用 infinity 作为参数时,它表示”无限长的时间”
    • 这会导致执行该命令的进程一直处于休眠状态,不会自动退出
    • 要终止这个进程,需要手动干预(通常使用 Ctrl+C 快捷键或 kill 命令)
  • 这种命令常用于需要保持容器运行的场景(如 Docker 容器),或者作为一种简单的方式让进程在后台持续运行而不占用过多系统资源
  • 由于进程处于休眠状态,它只会占用极少的系统资源(主要是进程表项)

终端于进程的关系

  • TLDR:终端存在是进程持续运行的一个条件(避免被 SIGHUP 终止),但进程本身还需要有“不主动退出”的逻辑(如无限循环、sleep infinity 等)才能一直运行
    • sleep infinity 是实现这种“不退出”的简单高效方式
  • 前台进程与终端的关系
    • 如果进程是在终端中直接启动的前台进程(比如直接执行 python script.py),那么当终端关闭时,该进程会收到 SIGHUP(挂断信号),通常会随之终止=
    • 这种情况下,即使不关闭终端,进程也可能因为自身逻辑结束(比如脚本执行完毕)而退出,并非“一直运行”
  • 后台进程的特殊性
    • 如果用 & 将进程放到后台(如 python script.py &),终端关闭时它仍可能被 SIGHUP 终止(取决于 shell 配置,如 huponexit 选项)
    • 即使终端保持打开,后台进程若自身逻辑执行完毕(比如循环结束),也会自动退出
  • sleep infinity 的作用
    • sleep infinity 的核心是让进程进入“无限等待”状态,自身不会主动结束,且几乎不消耗资源
    • sleep infinity 与普通进程的区别在于:
      • 普通进程需要自身逻辑(如无限循环)来维持运行,可能消耗 CPU 或内存;
      • sleep infinity 是利用系统调用让进程进入休眠,本质上是“挂起等待”,资源占用极低
  • 终端存在≠进程持续运行
    • 终端存在只是避免了 SIGHUP 导致的终止,但进程是否持续运行仍取决于其自身是否会主动退出
    • 比如执行 echo "hello" 后,终端还在,但进程已经结束;
    • 而 sleep infinity 无论终端是否关闭(若处理了 SIGHUP),都会一直休眠

Docker——镜像深入理解


整体说明

  • TLDR:镜像的本质是静态的只读模板
  • 镜像(Image)是一个静态的、只读的二进制文件集合 ,包含运行应用所需的代码、 runtime、库、环境变量、配置文件等所有依赖
  • 镜像的核心作用是:
    • 作为容器的”模板”:容器是镜像的运行实例(镜像 + 可写层)
    • 保证环境一致性:无论在哪个宿主机器上,基于同一镜像创建的容器都能运行相同的应用(”一次构建,到处运行”)
  • 镜像的核心特性包括
    • 只读性 :所有层不可修改,保证安全性和可复用性
    • 分层存储 :基于 UnionFS,层可共享,减少冗余
    • 轻量高效 :仅包含应用依赖,体积远小于虚拟机镜像
    • 可移植性 :镜像内容与宿主环境无关,实现”一次构建,到处运行”

镜像的分层结构:UnionFS 与 Copy-on-Write

  • Docker 镜像最核心的设计是分层存储 ,基于 Union File System(联合文件系统) 实现
  • 这种分层结构让镜像具备了”可复用、轻量、高效”的特性

分层的本质

  • 每个镜像由多个只读层(Layer) 叠加而成,每层对应镜像构建过程中的一个操作(如 RUN、COPY 等 Dockerfile 指令)
  • 层与层之间通过哈希值唯一标识(如 sha256:a1b2c3...),相同的层会被不同镜像共享(避免重复存储)
  • 例如,一个 nginx 镜像可能包含以下层:
    • 基础层:ubuntu:20.04 的底层文件系统(如 /bin、/etc 等)
    • 依赖层:安装 nginx 所需的库(如 libpcre3 等)
    • 应用层:nginx 二进制文件和配置文件(如 /usr/sbin/nginx、/etc/nginx/)

联合挂载(Union Mount)

  • 当镜像被用于创建容器时,Docker 会将所有只读层联合挂载为一个统一的文件系统,对容器来说,这些层看起来是一个完整的目录(透明化分层细节)

Copy-on-Write(写时复制)机制

  • Copy-on-Write(写时复制)机制是镜像分层与容器交互的核心机制
  • 镜像的所有层都是只读的,容器启动时,Docker 会在镜像顶层添加一个可写层(Writable Layer)
  • 当容器需要修改文件时:
    • 若文件在底层(镜像层),会先将文件复制到可写层 ,再修改可写层的副本(底层文件不变)
    • 若文件是新创建的,直接写入可写层
  • 这种机制保证了:
    • 镜像层不会被容器修改(只读),可安全复用
    • 容器的修改仅保存在自己的可写层,不影响其他容器或镜像

Dockerfile 构建镜像与分层

  • 镜像的构建通常通过 Dockerfile 定义(而非 docker commit,后者不推荐)

  • Dockerfile 中的每一条指令都会生成一个新的只读层 ,指令与层的对应关系是理解镜像体积和优化的关键

  • 下面是一个 Dockerfile 与分层对应的示例:

    1
    2
    3
    4
    5
    6
    FROM ubuntu:20.04       # 基础层(复用 ubuntu:20.04 的所有层)
    RUN apt-get update # 层 1:执行 update 后的文件变化
    RUN apt-get install -y nginx # 层 2:安装 nginx 后的变化
    COPY nginx.conf /etc/nginx/ # 层 3:复制配置文件的变化
    EXPOSE 80 # 元数据(不生成层,仅记录信息)
    CMD ["nginx", "-g", "daemon off;"] # 元数据(容器启动命令)
  • EXPOSE、CMD、ENV 等指令仅修改镜像的元数据(保存在镜像的配置层),不生成新的文件层

  • 多条指令会生成多个层,层越多,镜像体积可能越大(需优化)


附录:镜像 ID 与 Digest的区别

  • 镜像 ID是镜像的唯一标识符(64 位哈希,通常显示前 12 位),由镜像的所有层和元数据共同计算得出
    • f9c8f87e172b(完整 ID 为 f9c8f87e172b2a4e41a73e2d685c8f...)
  • Digest(摘要) :镜像内容的哈希值(基于所有层的内容计算),用于验证镜像的完整性(避免篡改)
    • 拉去影响完成时,给出的 Digest: sha256:abc123... 就是摘要,相同摘要的镜像内容一定相同

附录:Storage Driver

  • Docker 通过存储驱动管理镜像层和容器可写层的存储与联合挂载
  • 不同的存储驱动实现方式不同,主流的是 overlay2(Linux 推荐,性能最优)
  • overlay2 驱动的核心结构如下:
    • 镜像层 :存储在 /var/lib/docker/overlay2/ 下,每层对应一个目录(以层哈希命名)
    • 可写层 :容器启动时,overlay2 会创建一个新目录作为可写层,并通过”上下层”关系关联镜像层
    • 合并视图 :通过 overlay2 的联合挂载,将所有层合并为容器内看到的统一文件系统

附录:镜像的体积优化

  • 镜像体积过大会导致存储、传输和启动效率下降,优化核心是减少层数、删除冗余文件 :

  • 合并指令(减少层数) :多条 RUN 指令可合并为一条(用 && 连接),并清理缓存(如 apt-get clean):

    1
    2
    3
    4
    5
    6
    7
    8
    # 优化前(2 层)
    RUN apt-get update
    RUN apt-get install -y nginx

    # 优化后(1 层,且清理缓存)
    RUN apt-get update && \
    apt-get install -y nginx && \
    rm -rf /var/lib/apt/lists/* # 删除 apt 缓存
  • 多阶段构建(丢弃无用层) :用于编译型应用(如 Go、Java),仅保留运行时所需文件(下面的代码最终镜像仅包含 alpine 基础层 + 二进制文件,体积大幅减小):

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    # 阶段 1:编译(包含编译器等冗余工具)
    FROM golang:1.20 AS builder
    WORKDIR /app
    COPY . .
    RUN go build -o myapp .

    # 阶段 2:运行(仅保留编译产物)
    FROM alpine:3.18
    COPY --from=builder /app/myapp /myapp # 仅复制编译好的二进制文件
    CMD ["/myapp"]
  • 优先选择 alpine(几 MB)、slim 版本,而非完整版(如 ubuntu 完整版约 200MB,alpine 约 5MB)

  • 删除临时文件、日志、包管理缓存(如 yum clean all、npm cache clean)


附录:执行 docker pull 时在发生什么?

  • 当执行 docker pull [镜像名] 时,终端会显示的多行输出,这是 Docker 拉取镜像过程的详细日志,每一行对应镜像的一个 层(Layer) 的下载或处理状态,包含层的信息
  • 如果本地已经有的层,不会再下载,且不同镜像是可以共享同一个层的(通过 ID 唯一识别)
  • 层 ID :每个层的唯一标识符(如 a1b2c3d4...)
  • 操作类型 :
    • Pull complete:该层已成功下载并解压
    • Already exists:本地已存在该层,无需重复下载
    • Downloading:正在下载该层,会显示进度(如 50% [=====>])
    • Verifying Checksum:验证文件完整性
    • Extracting:解压下载的层文件
  • 为什么会有这么多层?
    • Docker 镜像是由多个 只读层(Layer) 叠加而成的
    • 每个层对应镜像构建过程中的一个操作(如 RUN、COPY 等指令)
    • 层具有 可复用性 :不同镜像可能共享相同的层,避免重复存储和下载
    • 层的设计让镜像更新更高效(只需更新变化的层)

附录:镜像与容器的关系:动态 vs 静态

  • 镜像 :静态、只读、多分层,是容器的”模板”
  • 容器 :动态、可写,是镜像的”运行实例”(= 镜像所有只读层 + 容器独有的可写层)
  • 可以理解为:容器 = 镜像(只读层) + 可写层(容器私有) + 容器元数据(如网络配置、环境变量等)
  • 当容器被删除时,其可写层和元数据会被清理 ,但镜像的只读层不受影响(可继续用于创建新容器)

CA——智能出价与激励兼容

以下思考仅为笔者与其他同事讨论时的一些想法,仅供参考


Background

  • 问题提出 :拍卖机制的激励兼容是指机制能否让商家说真话时从系统获得最大收益,但在智能出价时,还支持激励兼容吗?
  • 激励兼容(IC)的简单定义 :讲真话是利益相关者在这个机制下的最优选择,IC的详细定义看下面

Answer

  • 从计费方式上看 :由于系统为了保ROI会计费到满足商家出价ROI,本质上算是ROI上的一价计费 ,从这里直观上看不能算是激励兼容了
  • 从更广泛的视角看 :
    • 激励兼容有个等价表达是,同时满足以下两个条件:
      • 第一,分配规则:随着出价提升,分配到的资源是单调不减
      • 第二,计费规则:净胜者需要支付的数额等于将使他赢得拍卖的最小值
    • 在oCPC中(假设商家投放期间固定目标ROI),可以算是激励兼容的,但是比较弱:
      • 分配规则 :流量可以看做是连续的,所以流量随着商家出价是单调递增的,分配规则满足激励兼容
      • 计费规则 :由于流量是随着出价单调递增的, 所以实际上,商家计费对应的流量就是他拿到这些流量的最小计费
      • 边际效益递减 :但是,从商家视角考虑,随着出价增多,边际效益递减(流量会越来越贵),再增加支付成本拿到的流量并不多,所以其实容易出现计费高了,但是流量涨幅不多(甚至相当于没涨),从这个角度来讲,好像上述两条激励兼容的满足又有点弱

oCPX是否激励兼容的其他思考(非严格证明,准确性有待商榷)

  • 如果把智能出价下的系统分成两层
    • 第一层:对每个投放周期内,商家出价,给出指定目标(目标在每个投放周期内固定),比如ROI约束下最大化点击的产品,商家出价则是目标ROI值
    • 第二层:对每个请求,智能出价在商家约束和目标下,智能体给出满足商家约束且能最大化商家效果的单次出价
  • 在第一层中,激励兼容可以理解为:每个投放周期内,商家都会说真话,选择一个自己能接受且能最大化自己效果的目标值,比这个目标值小或大都不符合商家利益
    • 此时每个投放周期内,对一个商家来说,系统都在进行一次拍卖,是对一个投放周期内整体流量进行打包拍卖,商家出一次价即可购买一定量的效果,这种拍卖不是0-1拍卖,不是简单的拍卖成功或拍卖失败,而是给出一个连续的出价目标,对应一个连续成本和一个连续的收益,成本和收益之间一般是正相关关系
  • 在第二层中,激励兼容可以理解为:每个请求下,智能体给出的出价都是达成商家目标所能给出的最优出价,即能让自己收益最大化的最优出价
    • 当然,此时商家出价不再仅仅与这次拍卖相关,而是与整个投放周期内的成本和效果相关

智能出价下的计费方式讨论

  • 存在外部平台竞争的情况 :在存在外部其他流量平台竞争且商家预算有限的情况下,商家更多会考虑其他平台投放成本和当前平台投放的边际收益,实际上无论如何商家都不会说真话,此时一价计费反而让商家成本更准确
  • 没有其他流量平台竞争时 :流量平台垄断(没有其他流量平台时),如果一价计费,商家会不断下探,此时需要二价计费防止商家下探
  • 一价计费的其他优点 :面对oCPX这种形式时,一价计费更容易保成本,可以先上一价计费,然后长期观察商家是否降价/调价来判断激励兼容性是否受到影响

附录:激励兼容相关定义

  • 激励兼容(IC) :讲真话是利益相关者在这个机制下的最优选择
    • IC是一种信息激励机制,通过协调委托、代理双方的利益,使被激励方主动公布自己的真实信息,由不诚实转变为诚实 ,达到组织中各成员目标的一致
    • 贝叶斯激励兼容(BIC)、占优策略激励兼容(DSIC)都是激励兼容(IC)的不同表现形式
  • 贝叶斯激励兼容(BIC) :如果其他参与者说真话(如实报告自己的类型),那么每个参与者说真话是最优策略 ,这里的最优策略即使得自己期望效用最大化的策略
  • 占优策略激励兼容(DSIC) :论其他参与者如何行动 ,每个参与者说真话都是一个占优策略 ,即无论其他人采取什么策略,该参与者选择真实策略都能获得最优结果,至少不会比其他策略更差
    • 例如VCG(二价拍卖)就是一种DSIC机制,在这种拍卖中,买家即使知道其他买家的报价,报出自己的真实价值仍然是将自身效用最大化的策略
  • BIC与IC、DSIC的关系 :BIC和DSIC都是IC的子类
    • DSIC是性质更强的一类拍卖机制,而BIC是更广的一类,一个DSIC机制一定也是一个BIC机制,但反过来不一定成立
    • DSIC要求无论其他参与者的策略如何,真实披露都是最优策略 ,这是一个很强的条件;而BIC只要求在给定参与者对其他参与者策略分布的信念下,真实披露是最优的 ,相对条件更宽松一些

服务器——内存扩展


服务器内存通道说明

  • 一般来说两个槽一个通道,由黑白不同的两个插槽组成
  • 某些特殊的产品也会有单个插槽就是一个通道的情况
  • 大部分 CPU 一般有4个通道,有些会有6个通道

一般服务器内存扩展原则

  • 先插满白色口,再插黑色口
  • 也就是充分利用通道,每个通道插一个是速度最快的最优选择

对于特定产品

DELL Power Edge

  • 第一个 CPU 通道数:6个

    • [A3, A5]分别为一个一个单独的通道
    • 其余的每双槽构成一个通道
    • 一共10个插槽
  • 第二个 CPU 通道数:3个

    • 每个通道两个插槽
    • 6个插槽组成3通道
  • 对于一个 CPU 而言,插内存的组合方式

    内存条数量 插槽对应位置
    1 A1
    2 A1, A2
    4 A1, A2, A4, A5
    6 A1, A2, A3, A4, A5, A6
    8 A1, A2, A3, A4, A5, A6, A7, A8
    10 A1, A2, A3, A4, A5, A6, A7, A8, A9, A10

浏览器——设置网页为可编辑模式

用一行代码将网页设置成可修改模式,方便修改


修改网页为可编辑模式

  • 打开 console
  • 执行以下代码
    1
    document.body.contentEditable = 'true'

Python——修改图片背景颜色

利用Python的skimage包实现修改图片背景色等


修改签名背景,按照灰度图像处理

代码

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
from skimage import io, color
import matplotlib.pyplot as plt


def change_grey_to_white(origin, new):
"""
Change background color for image, such as signature
:param origin: the path of original image
:param new: the path of new image
:return: None
"""
# img = io.imread('./origin.jpeg')
img = io.imread(origin)
io.imshow(img)
plt.show()
img_gray = color.rgb2gray(img)
rows, cols = img_gray.shape
for i in range(rows):
for j in range(cols):
if img_gray[i, j] <= 0.5:
img_gray[i, j] = 0
else:
img_gray[i, j] = 1
io.imshow(img_gray)
plt.show()
io.imsave(new, img_gray)

# example
change_grey_to_white('./origin.jpeg', 'new.jpeg')

说明

  • 这里可接受彩色图片,比如拍照片得到的原始图片
  • 函数会将其转换为灰度图片然后处理,最终输出也是灰度图片
  • 如果需要处理指定灰度,可通过修改判断语句中0.5这个值从而实现(比如修改为区间等)

修改证件照背景,按彩色图像处理

代码

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
from skimage import io
import numpy as np


def change_pixel_color(pixel, old_pixel, new_pixel=None, error=60):
"""
change color for pixel if pixel in range [old_pixel-error, old_pixel+error]
:param pixel:
:param old_pixel:
:param new_pixel:
:param error:
:return: the new pixel
"""
if new_pixel is None:
new_pixel = [255, 255, 255]
similar = True
for i in abs(pixel - old_pixel):
if i > error:
similar = False
break
if similar:
return new_pixel
else:
return pixel


def change_background(origin_path, new_path, new_color, error=60):
"""
change background, auto detect background
:param origin_path:
:param new_path:
:param new_color: target background color
:param error: the error for change color
:return: None
"""
img = io.imread(origin_path)
bg = img[10:30, 10:30, :]
bg_pixel = [1.0 * np.sum(bg[:, :, channel])/bg[:, :, channel].size for channel in range(3)]
print("background color: %s" % bg_pixel)
new_img = np.array([[change_pixel_color(pixel, [0, 160, 234], new_color, error) for pixel in row] for row in img])
io.imshow(new_img)
# import matplotlib.pyplot as plt
# plt.show()
io.imsave(new_path, new_img)


# example
change_background(origin_path="origin.jpeg",
new_path='new.jpeg',
new_color=[0, 255, 255])

说明

  • 自动读取图片的左上角部分像素的平均值作为背景颜色
  • 允许差范围在合适的范围内,可通过error参数调节,该参数不宜过大也不宜过小,可测试多次选择比较合适的

ML——缺失值处理

missing值处理


对于数值类型的特征

中位数填充

  • 用当前特征所有未缺失的值的中位数(median)填充当前特征的缺失值

均值填充

  • 用当前特征所有未缺失的值的均值填充当前特征的缺失值

加权填充

  • 引入相似性矩阵,评估缺失样本与其他未缺失样本的相似性,按照相似性分配权重
  • 效果更好,但是需要更多时间

对于Category类型

特殊字符填充

  • 使用某种未出现过的特殊字符填充
  • 等价于将缺失值看成是个特殊类别

填充众数

  • 寻找所有未缺失数据中样本最多的类别,然后将缺失值填充为该众数类别

什么情况下不用填充

树模型

普通树模型
  • ID3不支持缺失值处理(也可能可以,但是我没看到具体介绍)
  • C4.5和CART都使用下面的方法进行缺失值处理
  • 结点分裂时:
    • 参考自周志华机器学习书籍中
    • 先按照无缺失的数据正常划分(缺失数据不参与计算)
    • 对于无缺失值的样本,正常分配到对应叶子节点
    • 对于缺失值的样本,每个样本以不同的概率分配到各个叶子节点, 概率值为: 子节点中无缺失样本的数量 / 无缺失样本的总数
  • 问题: 如果是训练时没有缺失,预测时有缺失怎么办?
    • 一种可能的方式是直接放到某个结点中
    • [待更新]
GBDT
  • 如果使用的是树模型(CART)作为基分类器是否可以直接借用树模型对缺失值的处理方法?
  • 如果使用的是逻辑回归模型作为基分类器,需要自己对缺失值进行处理
XGBoost
  • 寻找分裂点时(split point):
    • 不遍历缺失值对应的样本,只使用无缺失的样本确定分裂点(节省开销)
    • 尝试将缺失值分配到左叶子结点或者右叶子结点,分别计算增益(保证完备性)
    • 选择增益大的点即可
  • 如果训练时没有缺失值,预测时有缺失值:
    • 将缺失值自动放到右子树中即可

不同模型对缺失值的敏感度总结

不敏感模型

  • 树模型
  • 神经网络的鲁棒性强, 数据量够的话不敏感?
    • 神经网络的输入必须是没有缺失值的,需要我们使用特征工程的方法填充缺失值
    • 其实我觉得自己需要填充数据的其实都有点敏感吧, 所以神经网络在使用时有时候感觉并不敏感,因为缺失值被我们填充后总能得到不错的效果

敏感模型

  • 距离度量模型: KNN, SVM等
    • 在尽量保证缺失值是随机的前提下使用基于统计分布的填充方法可能降低缺失值造成的负面影响
    • 但是SVM这样的模型对缺失值的抗性非常差,不恰当的非随机缺失值可能导致模型出现意外
  • 线性模型的损失函数往往也涉及到距离的计算?

Chrome——常用插件总结

Chrome是最好用的浏览器没有之一,本文将总结Chrome中功能强大的插件


Markdown Viewer

  • 用于浏览markdown文件内容
  • 自动为markdown文件生成HTML源码
  • 安装后记得设置允许该插件访问文件URL

油猴插件(Tampermonkey)

  • 脚本引擎,用于管理其他脚本
  • 功能强大,里面有形形色色黑科技油猴脚本

Postman

  • 程序员必备,测试API首选
  • 各种http接口(如GET, POST)和数据格式(如表格)

AdBlock

  • 能屏蔽大部分的广告

SwitchyOmega

  • shadowsocks的伴侣
  • 特别是Ubuntu必备
1…323334…61
Joe Zhou

Joe Zhou

Stay Hungry. Stay Foolish.

608 posts
49 tags
GitHub E-Mail
© 2026 Joe Zhou
Powered by Hexo
|
Theme — NexT.Gemini v5.1.4