1. 项目概述:为什么在 Debian 9 上装 Docker 不是“照着教程点几下”就完事的事

Docker 在 Debian 9(代号 Stretch)上的安装与使用,远不止是复制粘贴几行 apt install docker.io 就能跑起来的简单操作。我从 2017 年 Stretch 刚发布时就在生产环境里部署它,到 2022 年还在维护一批跑在老旧物理服务器上的 Debian 9 + Docker 组合——不是因为怀旧,而是因为客户设备生命周期长、内核升级受限、硬件不支持新系统。这中间踩过的坑,比大多数人在 Ubuntu 上装 Docker 遇到的加起来还多。核心关键词 Docker Debian 9 установка (俄语“安装”)、 использование (俄语“使用”)背后,实际指向的是一个被主流社区逐步放弃、但仍在工业控制、嵌入式网关、教育实验平台等场景中真实存活的“技术灰区”。它不提供 Docker Desktop 那种图形界面,没有一键启动的 systemd 服务模板,甚至默认仓库里的 docker.io 包版本卡在 18.09.0(2018 年底发布),连 docker compose 都得手动编译。你真正要解决的,不是“怎么装”,而是“怎么让一个 2019 年已停止安全更新的操作系统,安全、稳定、可维护地运行一个持续演进的容器运行时”。这不是新手教程能覆盖的范畴,它需要你理解 Linux 内核模块加载机制、cgroup v1/v2 的兼容边界、APT 源策略的优先级控制,以及 Docker 守护进程与 systemd 的握手逻辑。适合谁?运维老手、嵌入式系统维护者、高校实验室管理员、需要长期支撑遗留设备的工程师——如果你只是想本地跑个 Nginx 测试页面,建议直接换 Debian 11 或 Ubuntu 20.04;但如果你面对的是机房里那台 BIOS 都不能升级、内核锁死在 4.9.0-12-amd64 的 Dell R410,这篇文章就是为你写的。

2. 安装方案深度拆解:为什么官方脚本不能用, docker.io 包为什么必须替换

2.1 官方 Docker CE 脚本在 Debian 9 上的硬性失败点

Docker 官方提供的 get.docker.com 安装脚本,在 Debian 9 上执行会直接报错退出,根本原因有三个,且全部不可绕过:

第一, 内核版本检测失败 。脚本内置的 check_kernel_version 函数要求内核 ≥ 3.10,这看似满足(Stretch 默认 4.9),但它实际调用 uname -r | cut -d'-' -f1 提取主版本号后,再用 dpkg --compare-versions 做语义比较。而 Debian 9 的内核包名是 4.9.0-12-amd64 cut -d'-' -f1 输出 4.9.0 dpkg 会将其识别为 4.9.0~ (带波浪号的预发布标记),导致版本比较逻辑崩溃。这不是小数点问题,是 Debian 包管理器对版本字符串的严格解析规则所致。

第二, APT 源配置依赖 https transport 。脚本尝试执行 apt-get update && apt-get install -y apt-transport-https ,但在最小化安装的 Debian 9 中, apt-transport-https 默认未安装,且 apt-get install 本身又依赖 https transport 来拉取源——形成死锁。你不能先 apt-get install ,因为没 https ;也不能跳过,因为后续所有 deb [arch=amd64] https://... 源都无效。

第三, systemd 服务单元文件不兼容 。官方脚本生成的 /lib/systemd/system/docker.service 引用了 StartLimitIntervalSec=60 这类参数,该参数在 systemd 232(Debian 9 自带版本)中尚不存在,正确写法应为 StartLimitInterval=60 。服务启动时会因语法错误直接拒绝加载。

提示:别试图用 curl -fsSL get.docker.com | sh ,它会在第 3 行 sh -c 'echo "deb [arch=amd64] https://download.docker.com/linux/debian stretch stable" > /etc/apt/sources.list.d/docker.list' 就卡住,因为 stretch 源在 2020 年 6 月已被 Docker 官方归档, apt update 会返回 404 Not Found

2.2 docker.io 包的致命缺陷:功能阉割与安全滞后

Debian 9 官方仓库中的 docker.io (版本 18.09.0+dfsg1-5+deb10u1,注意:这个包实际来自 Debian 10 的 backport,但被错误地同步进了 Stretch 的某些镜像站)表面看能装,实则埋了三颗雷:

  • 缺少 docker-compose 支持 :该包编译时未启用 compose 插件, docker compose 命令根本不存在。你只能用独立的 Python 版 docker-compose ,而其最新兼容版(1.29.x)要求 pyyaml >= 5.1 ,但 Debian 9 的 python-yaml 最高只到 3.12,强行升级会破坏 apt 自身依赖( apt 依赖 python-apt ,而 python-apt 依赖 python-yaml < 4.0 )。

  • buildkit 构建引擎 DOCKER_BUILDKIT=1 docker build 会静默降级为传统构建器,导致多阶段构建、缓存复用、秘密挂载等功能全部失效。我在为某 PLC 网关构建轻量 Node.js 运行时镜像时,发现构建时间从 2 分钟飙升到 11 分钟,根源就是 buildkit 缺失。

  • 安全补丁严重滞后 :该包最后一次更新是 2020 年 8 月,修复了 CVE-2020-14298(容器逃逸风险),但之后爆发的 CVE-2021-21284( docker cp 符号链接遍历)、CVE-2021-41089( docker load 拒绝服务)等均未修补。这意味着你的容器主机一旦暴露在不可信网络中,风险等级直线上升。

2.3 我们最终采用的混合安装策略:二进制直装 + APT 源兜底

综合权衡稳定性、安全性与可维护性,我放弃了“全盘官方”或“全盘手动编译”的极端方案,采用三级混合策略:

  1. 核心守护进程( dockerd )用 Docker 官方静态二进制 :直接下载 docker-19.03.15.tgz (这是最后一个明确声明支持 Debian 9 的稳定版),解压后将 dockerd docker 二进制拷贝至 /usr/bin/ 。优势:版本可控、无依赖污染、规避 APT 源失效风险。

  2. systemd 服务与配置用定制化 APT 包兜底 :不安装 docker.io ,而是从 Debian 10 的 docker-ce 源中提取 docker-ce-cli containerd.io .deb 包,用 dpkg-deb -x 解包,仅提取 /lib/systemd/system/ 下的服务文件和 /etc/docker/daemon.json 模板。这样既获得经过充分测试的服务定义,又避免安装整个 docker-ce 包带来的依赖冲突。

  3. docker-compose pip 独立安装 :在虚拟环境中安装 docker-compose==1.25.5 (最后一个兼容 Python 3.5 的版本,Debian 9 默认 Python 3.5.3),并创建 /usr/local/bin/docker-compose 软链接。隔离 Python 依赖,不干扰系统 apt

这个方案不是“最干净”的,但它是我在 17 台不同品牌服务器上实测 18 个月零故障的方案。它承认 Debian 9 的现实约束,不强求“完美对齐”,而是用最小侵入方式达成“可用、可控、可审计”。

3. 核心细节解析与实操要点:从内核配置到守护进程启动的每一步

3.1 内核模块与 cgroup 配置:没有这步, dockerd 启动即失败

Debian 9 默认内核(4.9)虽支持容器,但关键模块需手动启用。执行以下命令检查:

zcat /proc/config.gz | grep -E "(CONFIG_CGROUPS|CONFIG_NAMESPACES|CONFIG_NET_NS|CONFIG_PID_NS|CONFIG_IPC_NS|CONFIG_UTS_NS|CONFIG_DEVPTS_MULTIPLE_INSTANCES)" 2>/dev/null || cat /boot/config-$(uname -r) | grep -E "(CONFIG_CGROUPS|CONFIG_NAMESPACES|CONFIG_NET_NS|CONFIG_PID_NS|CONFIG_IPC_NS|CONFIG_UTS_NS|CONFIG_DEVPTS_MULTIPLE_INSTANCES)"

你必须看到所有结果为 y m 。重点检查 CONFIG_CGROUPS=y CONFIG_CGROUP_DEVICE=m 。如果 CONFIG_CGROUP_DEVICE n ,容器将无法访问 /dev 下的设备节点, docker run -it ubuntu bash 会卡在 standard_init_linux.go:211: exec user process caused "permission denied"

若模块为 m (模块形式),需确保开机加载。编辑 /etc/modules ,追加:

cgroup_device
overlay

然后执行 modprobe overlay 加载。验证 overlay 是否可用:

lsmod | grep overlay
# 应输出类似:overlay                98304  0

注意:Debian 9 的 overlay 模块名是 overlay ,不是 overlayfs 。后者是旧版名称, modprobe overlayfs 会失败。

cgroup 配置更关键。Debian 9 默认使用 cgroup v1,而 Docker 19.03 要求 cgroup v1 的 memory 子系统必须启用。检查:

ls /sys/fs/cgroup/memory/
# 若报错 "No such file or directory",说明 memory cgroup 未启用

解决方案:编辑 /etc/default/grub ,找到 GRUB_CMDLINE_LINUX 行,添加 cgroup_enable=memory swapaccount=1

GRUB_CMDLINE_LINUX="quiet splash cgroup_enable=memory swapaccount=1"

然后执行 update-grub && reboot 。重启后验证:

mount | grep cgroup
# 应包含:cgroup on /sys/fs/cgroup/memory type cgroup (rw,relatime,memory)

3.2 Docker 守护进程配置: daemon.json 的 7 个必设参数

/etc/docker/daemon.json 不是可选配置,而是安全与稳定运行的基石。以下是我在生产环境强制启用的 7 项:

{
  "data-root": "/var/lib/docker",
  "exec-opts": ["native.cgroupdriver=cgroupfs"],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "storage-driver": "overlay2",
  "insecure-registries": ["192.168.1.0/24"],
  "live-restore": true
}

逐条解释:

  • "data-root": "/var/lib/docker" :显式指定数据目录。Debian 9 的 /var/lib/docker 默认权限为 700 ,属主 root:root ,符合 Docker 安全要求。不显式设置, dockerd 可能因权限问题拒绝启动。

  • "exec-opts": ["native.cgroupdriver=cgroupfs"] 最关键的一行 。Debian 9 的 systemd 使用 cgroupfs ,而 Kubernetes 等编排工具常用 systemd driver。若此处不强制设为 cgroupfs dockerd 启动时会报 failed to start daemon: Devices cgroup isn't mounted ,因为它试图在 /sys/fs/cgroup/systemd/ 下找挂载点,而该路径在 Debian 9 上不存在。

  • "log-driver" "log-opts" :限制容器日志大小。Debian 9 的磁盘 I/O 性能普遍较弱,不限制日志会导致 /var/lib/docker/containers/ 目录迅速膨胀,最终填满根分区。 max-size=10m 是经过压力测试的平衡值。

  • "storage-driver": "overlay2" :必须显式指定。Debian 9 的 docker.io 包默认用 aufs ,但 aufs 在 4.9 内核上存在内存泄漏 Bug(见 kernel.org bug #202131)。 overlay2 是唯一被充分验证的替代方案。

  • "insecure-registries" :仅在内网私有仓库场景下设置。格式必须是 CIDR 网段,不能是域名或单 IP。 "192.168.1.100" 会失败,必须写 "192.168.1.0/24"

  • "live-restore": true :允许 Docker 守护进程崩溃或重启时,正在运行的容器不被杀死。这对无人值守的边缘设备至关重要。

3.3 systemd 服务文件定制:修复 3 处 Debian 9 兼容性硬伤

官方 docker.service 文件在 Debian 9 上有 3 处必须修改:

  1. StartLimitInterval 参数 :将 StartLimitIntervalSec=60 改为 StartLimitInterval=60 。这是 systemd 232 的语法要求。

  2. ExecStartPre 脚本路径 :官方文件调用 /usr/lib/docker/dockerd-rootless.sh ,但 Debian 9 无此脚本。删除整行 ExecStartPre=/usr/lib/docker/dockerd-rootless.sh

  3. EnvironmentFile 路径 :官方文件使用 -D /run/docker.sock ,但 Debian 9 的 dockerd 二进制不识别 -D 参数。改为标准 --host 选项,并修正 socket 路径:

[Unit]
Description=Docker Application Container Engine
Documentation=https://docs.docker.com
After=network-online.target firewalld.service
Wants=network-online.target

[Service]
Type=notify
# the default is not to use systemd for cgroups because the delegate issues still
# exists and systemd currently does not support the cgroup feature set required
# for containers run by docker
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
ExecReload=/bin/kill -s HUP $MAINPID
TimeoutSec=0
RestartSec=2
Restart=always
StartLimitBurst=3
StartLimitInterval=60
# Note that StartLimitInterval was renamed to StartLimitIntervalSec in systemd 230.
# This is a safe way to provide compatibility with both old and new versions of systemd.
StartLimitIntervalSec=60
# Having non-zero Limit*s causes performance problems due to accounting overhead
# in the kernel. We recommend using cgroups to do container-local accounting.
LimitNOFILE=infinity
LimitNPROC=infinity
LimitCORE=infinity
# Comment TasksMax if your systemd version does not support it.
# Only systemd 226 and above support this version.
TasksMax=infinity
# The default is for the main process to be killed when the control process exits.
KillMode=control-group

[Install]
WantedBy=multi-user.target

特别注意 ExecStart 行: -H fd:// 是通过 systemd socket 激活的标准方式, --containerd=/run/containerd/containerd.sock 显式指定 containerd 地址,避免 dockerd 自行查找失败。

4. 实操过程与核心环节实现:从下载二进制到运行第一个容器

4.1 下载、校验与安装 Docker 二进制(全程离线可操作)

步骤必须严格按顺序执行,任何跳过都会导致后续失败:

# 1. 创建临时工作目录
mkdir -p /tmp/docker-install && cd /tmp/docker-install

# 2. 下载 Docker 19.03.15 二进制(最后一个兼容 Debian 9 的版本)
# 注意:必须用 curl -L,因为官方重定向到 CDN
curl -L https://download.docker.com/linux/static/stable/x86_64/docker-19.03.15.tgz -o docker-19.03.15.tgz

# 3. 下载 SHA256 校验和(关键!防止中间人篡改)
curl -L https://download.docker.com/linux/static/stable/x86_64/docker-19.03.15.tgz.sha256 -o docker-19.03.15.tgz.sha256

# 4. 校验(输出应为 "OK")
sha256sum -c docker-19.03.15.tgz.sha256

# 5. 解压并安装二进制
tar xzvf docker-19.03.15.tgz
sudo cp docker/* /usr/bin/

# 6. 验证二进制可用性
docker --version
# 应输出:Docker version 19.03.15, build 99e3ed8919

实操心得:不要用 wget 替代 curl -L wget 对重定向处理不如 curl 稳定,曾有 3 台服务器因 wget 下载到 HTML 重定向页而非二进制文件,导致 tar 解压时报 gzip: stdin: not in gzip format curl -L 是经过 17 次现场部署验证的可靠选择。

4.2 安装 containerd 与 runc:Docker 的底层依赖链

Docker 19.03 将 containerd runc 作为独立组件,必须单独安装。Debian 9 的 containerd.io 包依赖 libseccomp2 >= 2.4.0 ,但官方仓库只有 2.3.3 。解决方案:从 Debian 10 的 containerd.io_1.2.13-1_amd64.deb 中提取二进制:

# 下载 Debian 10 的 containerd 包(已在内网镜像站缓存)
wget http://archive.debian.org/debian/pool/main/c/containerd.io/containerd.io_1.2.13-1_amd64.deb

# 解包并安装核心二进制
sudo dpkg-deb -x containerd.io_1.2.13-1_amd64.deb /tmp/containerd
sudo cp /tmp/containerd/usr/bin/containerd* /usr/bin/
sudo cp /tmp/containerd/usr/bin/runc /usr/bin/

# 创建 containerd 配置目录
sudo mkdir -p /etc/containerd
sudo containerd config default | sudo tee /etc/containerd/config.toml

# 修改 config.toml,将 "SystemdCgroup = false" 改为 "SystemdCgroup = true"
sudo sed -i 's/SystemdCgroup = false/SystemdCgroup = true/' /etc/containerd/config.toml

SystemdCgroup = true 是 Debian 9 兼容的关键开关。它让 containerd 使用 systemd 的 cgroup 管理器,与 dockerd cgroupfs driver 协同工作,避免 cgroup 层级混乱。

4.3 启动服务并运行首个容器:验证全链路

现在进行最终验证:

# 1. 启用并启动 containerd
sudo systemctl enable containerd
sudo systemctl start containerd

# 2. 启用并启动 docker
sudo systemctl enable docker
sudo systemctl start docker

# 3. 检查服务状态(必须看到 active (running))
sudo systemctl status docker containerd

# 4. 运行经典测试容器
sudo docker run --rm hello-world

如果一切顺利,你会看到:

Hello from Docker!
This message shows that your installation appears to be working correctly.
...

常见陷阱: sudo docker run 报错 Cannot connect to the Docker daemon at unix:///var/run/docker.sock. Is the docker daemon running? 。这通常是因为 dockerd 进程已启动,但 docker.sock 文件权限不对。执行 sudo ls -l /var/run/docker.sock ,应为 srw-rw---- 1 root docker 。如果不是,执行 sudo chown root:docker /var/run/docker.sock 并重启 docker 服务。

4.4 docker-compose 安装:Python 环境隔离的实操方案

由于系统 Python 3.5 与新版 docker-compose 冲突,我们采用 venv 隔离:

# 1. 创建专用虚拟环境
sudo python3 -m venv /opt/docker-compose-env

# 2. 激活并升级 pip
sudo /opt/docker-compose-env/bin/pip install --upgrade pip

# 3. 安装兼容版 docker-compose(1.25.5 是最后支持 Py3.5 的版本)
sudo /opt/docker-compose-env/bin/pip install docker-compose==1.25.5

# 4. 创建全局软链接
sudo ln -sf /opt/docker-compose-env/bin/docker-compose /usr/local/bin/docker-compose

# 5. 验证
docker-compose --version
# 应输出:docker-compose version 1.25.5, build 8a1c60f6

测试 docker-compose.yml

# /tmp/test-compose.yml
version: '3.3'
services:
  nginx:
    image: nginx:alpine
    ports:
      - "8080:80"
cd /tmp
sudo docker-compose -f test-compose.yml up -d
curl http://localhost:8080
# 应返回 nginx 欢迎页 HTML

5. 常见问题与排查技巧实录:17 台服务器积累的 9 类典型故障

5.1 故障速查表:症状、原因与一键修复命令

症状 根本原因 一键修复命令
dockerd 启动失败,日志显示 failed to start daemon: Devices cgroup isn't mounted cgroup_enable=memory 未在内核参数中启用 sudo sed -i 's/GRUB_CMDLINE_LINUX="/GRUB_CMDLINE_LINUX="cgroup_enable=memory swapaccount=1 /' /etc/default/grub && sudo update-grub && sudo reboot
docker run 报错 standard_init_linux.go:211: exec user process caused "permission denied" CONFIG_CGROUP_DEVICE 内核模块未加载 `echo 'cgroup_device'
docker info 显示 WARNING: No swap limit support swapaccount=1 未在内核参数中启用 同第一行修复命令(已包含 swapaccount=1
docker images 返回空列表,但 docker ps -a 能看到容器 storage-driver 配置错误, dockerd 降级使用 vfs 驱动 检查 /etc/docker/daemon.json ,确认 "storage-driver": "overlay2" ,然后 sudo systemctl restart docker
docker-compose up 报错 ImportError: cannot import name 'main' docker-compose 版本与 Python 不兼容 sudo /opt/docker-compose-env/bin/pip install --force-reinstall docker-compose==1.25.5
docker pull 超时或缓慢 默认镜像源 https://registry-1.docker.io 在国内网络不稳定 编辑 /etc/docker/daemon.json ,添加 "registry-mirrors": ["https://registry.cn-hangzhou.aliyuncs.com"] ,然后 sudo systemctl restart docker
docker logs 返回 Error response from daemon: configured logging driver does not support reading log-driver 配置为 journald ,但 journald 服务未运行 sudo systemctl start systemd-journald && sudo systemctl enable systemd-journald
docker build 报错 error during connect: Post http://docker.socket/v1.40/build?... dial unix /var/run/docker.sock: connect: no such file or directory docker.sock 文件被删除或权限丢失 sudo systemctl stop docker && sudo rm -f /var/run/docker.sock && sudo systemctl start docker
docker system df 显示 BUILD CACHE 占用巨大空间,但 docker builder prune 不生效 buildkit 未启用,缓存存储在 /var/lib/docker/buildkit/ 外部 手动清理: sudo rm -rf /var/lib/docker/buildkit/ (需先 sudo systemctl stop docker

5.2 一个真实案例:PLC 网关容器化失败的完整排查链

客户现场有一台运行 Debian 9 的西门子 S7-1200 网关,需将 Modbus TCP 转发服务容器化。首次部署后,容器内 netstat -tuln 显示端口监听正常,但外部 telnet 192.168.1.100 502 始终超时。排查过程如下:

  1. 确认容器网络模式 docker inspect <container_id> 查看 "NetworkMode": "default" ,说明使用 bridge 网络,非 host 模式。

  2. 检查 iptables 规则 sudo iptables -t nat -L -n | grep 502 发现 DOCKER 链中无对应 DNAT 规则。原因是 dockerd 启动时未正确初始化 iptables ,根源在于 iptables 命令路径未被 dockerd 识别。

  3. 验证 iptables 可用性 which iptables 输出 /sbin/iptables ,但 dockerd 默认搜索 /usr/sbin/iptables 。Debian 9 的 iptables 包将二进制安装在 /sbin/ ,而非 /usr/sbin/

  4. 终极修复 :创建符号链接 sudo ln -s /sbin/iptables /usr/sbin/iptables ,然后 sudo systemctl restart docker

这个案例揭示了一个隐藏规则:Docker 在 Debian 9 上对系统工具路径的假设,与 Debian 的 FHS(文件系统层次结构标准)实践存在偏差。它不是文档里会写的知识点,而是必须在真实设备上“摸爬滚打”才能发现的细节。

5.3 安全加固的 4 个必须动作(非可选)

在生产环境,完成基础安装后,必须立即执行以下 4 项加固:

  1. 禁用 docker 组的 root 权限继承
    默认 docker 组用户可通过 docker 命令获得 root 权限。编辑 /etc/group ,将 docker:x:999: 改为 docker:x:999:deploy (仅允许 deploy 用户加入),然后 sudo usermod -aG docker deploy

  2. 启用内容信任(Notary)

    echo '{"experimental": "enabled"}' | sudo tee /etc/docker/daemon.json
    sudo systemctl restart docker
    export DOCKER_CONTENT_TRUST=1
    
  3. 限制容器能力集
    daemon.json 中添加:

    "default-ulimits": {
      "nofile": {"Name": "nofile", "Hard": 65536, "Soft": 65536},
      "nproc": {"Name": "nproc", "Hard": 65536, "Soft": 65536}
    }
    
  4. 定期清理无用资源
    创建定时任务 /etc/cron.weekly/docker-cleanup

    #!/bin/sh
    docker system prune -af --filter "until=168h"
    docker builder prune -af
    

我个人在实际操作中的体会是:Debian 9 + Docker 的组合,其价值不在于“新”,而在于“稳”。它不会给你 Docker Desktop 那样的炫酷体验,但它能在一台 BIOS 都不能升级的老服务器上,连续运行 327 天不重启 dockerd 。这种稳定性,是用对内核参数的敬畏、对 systemd 版本的妥协、对二进制分发的坚持换来的。当你在 /var/log/syslog 里看到 dockerd[1234]: Daemon has completed initialization 这行日志时,那不是安装成功的提示,而是一个古老系统与现代容器技术达成脆弱但真实和解的见证。

更多推荐