Skip to content

Docker daemon配置原理、安全变更与故障回滚

daemon.json 不是一份“复制后重启Docker”的万能模板。它控制的是宿主机上的Docker daemon,可能同时影响所有镜像、容器、网络、Volume、日志和管理API。一个逗号错误、重复启动参数、冲突地址池或错误的 data-root,都可能让整台机器的Docker无法启动。

本页从配置加载链路开始,讲清每个常用配置为什么存在、何时生效、不这样做会怎样,以及生产如何变更、验证和回滚。Docker组件安装、仓库签名和rootless安装前置知识见 Docker安装、权限与升级

学习目标

学完后应能:

  1. 区分Docker CLI配置、daemon配置和容器级配置。
  2. 找到rootful、rootless和Docker Desktop各自的配置入口。
  3. 解释systemd unit、drop-in、dockerd命令行参数和 daemon.json 的关系。
  4. 使用JSON解析、dockerd --validate、systemd状态和journal定位启动失败。
  5. 正确配置日志、data-root、Registry、代理、地址池、DNS、live-restore和资源默认值。
  6. 解释哪些配置只影响新容器,哪些需要重建容器,哪些会影响daemon全局行为。
  7. 在不删除Docker数据的前提下完成配置失败回滚。
  8. 设计摘流、备份、金丝雀、验证和回退闭环。

一、先分清三类配置

mermaid
flowchart TD
    A["用户执行docker命令"] --> B["Docker CLI配置"]
    B --> C["选择context、daemon地址和凭据"]
    C --> D["Docker daemon配置"]
    D --> E["存储、网络、日志、Registry和API行为"]
    E --> F["容器创建配置"]
    F --> G["镜像、环境、端口、挂载、资源和重启策略"]
类型常见位置或形式解决什么问题
CLI配置~/.docker/config.json、context、环境变量CLI连接哪个daemon、登录哪个Registry、输出格式等
daemon配置/etc/docker/daemon.json、dockerd flagsDocker Engine全局存储、网络、日志和API行为
容器配置docker run、Compose service单个容器的端口、环境、Volume、CPU、内存等

不要把 ~/.docker/config.json/etc/docker/daemon.json 混为一谈。前者通常属于当前用户的CLI,后者属于daemon。修改CLI登录配置不会自动改变daemon的日志驱动,修改daemon默认日志也不会重建已有容器。

二、配置文件在哪里

常见位置:

环境常见配置入口注意事项
Linux rootful/etc/docker/daemon.json通常由系统级dockerd读取
Linux rootless~/.config/docker/daemon.json属于运行rootless daemon的用户
Docker DesktopDesktop设置界面或Engine JSON入口VM内部daemon由Desktop管理,不应机械修改宿主机Linux路径
自定义启动dockerd --config-file <path>必须检查systemd实际ExecStart

权威证据不是“教程说路径是这里”,而是当前daemon的实际启动方式:

bash
systemctl cat docker
systemctl show docker -p ExecStart -p Environment -p FragmentPath -p DropInPaths
ps -ef | grep '[d]ockerd'

rootless环境通常使用用户级systemd:

bash
systemctl --user cat docker
systemctl --user status docker

三、配置加载链路与参数冲突

mermaid
flowchart TD
    A["systemd读取docker.service和drop-in"] --> B["组装ExecStart与环境变量"]
    B --> C["启动dockerd"]
    C --> D["解析命令行flags"]
    C --> E["读取config-file"]
    D --> F{"同一选项是否重复配置"}
    E --> F
    F -- "是" --> G["dockerd可能拒绝启动并报告冲突"]
    F -- "否" --> H["初始化存储、网络、日志和API"]
    H --> I["daemon开始接受CLI请求"]

一个重要误区是:“命令行一定覆盖daemon.json”。对dockerd来说,同一选项同时出现在flags和配置文件中,很多情况下会被视为重复冲突并导致启动失败,而不是静默覆盖。

例如systemd unit已经包含:

text
dockerd --host=fd://

又在 daemon.json 配置同类 hosts,可能出现冲突。修改前必须检查:

bash
systemctl cat docker
systemctl show docker -p ExecStart

四、daemon-reload和restart不是一回事

mermaid
flowchart TD
    A{"修改了什么"} --> B["只修改daemon.json"]
    A --> C["修改systemd unit或drop-in"]
    B --> D["校验配置后重启docker服务"]
    C --> E["systemctl daemon-reload"]
    E --> D
    D --> F["dockerd重新读取配置"]

systemctl daemon-reload

让systemd重新读取unit文件和drop-in。它不会重启dockerd,也不会让dockerd自动重读 daemon.json

systemctl restart docker

停止并重新启动dockerd,新的daemon进程重新加载配置。这可能影响:

  • Docker CLI和API暂时不可用。
  • 新容器创建、删除、inspect和网络管理中断。
  • 运行容器是否继续运行取决于版本、配置和live-restore等条件。
  • 日志、网络规则、插件和管理连接可能受到影响。

因此不能在生产高峰直接“改完就restart”。

五、先读取当前真实状态

变更前保存证据:

bash
docker version
docker info
docker context show
systemctl status docker --no-pager
systemctl cat docker
journalctl -u docker --since "30 min ago" --no-pager

保存业务现场:

bash
docker ps -a --no-trunc
docker system df -v
docker network ls
docker volume ls

还要记录:

  • Docker Engine、containerd和runc版本。
  • 当前Docker Root Dir和Storage Driver。
  • 当前日志驱动。
  • 运行容器、镜像Digest、网络与Volume。
  • 节点是否已经摘流。
  • 是否有数据库、Redis等有状态容器。
  • 回滚配置和允许停机时间。

六、JSON语法和版本校验

daemon.json 是严格JSON:

  • Key和字符串必须使用双引号。
  • 不允许尾随逗号。
  • 不支持普通注释。
  • 布尔值是 true/false,不是字符串。
  • 数字通常不应随意写成带单位字符串,除非该选项文档明确要求。

只验证JSON语法:

bash
jq empty /etc/docker/daemon.json

没有jq时可使用系统已有的JSON解析工具,但JSON合法只证明语法正确,不证明dockerd认识这些选项。

使用目标版本dockerd验证:

bash
dockerd --validate --config-file=/etc/docker/daemon.json

不同版本支持情况以目标环境的:

bash
dockerd --help
dockerd --version

为准。不要在开发机用新版本校验后,直接把配置下发给旧版生产daemon。

七、一个安全起点,不是万能模板

json
{
  "data-root": "/data/docker",
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "20m",
    "max-file": "5"
  },
  "live-restore": true,
  "default-address-pools": [
    {
      "base": "172.30.0.0/16",
      "size": 24
    }
  ]
}

这只是讲解起点。上线前必须根据现有网络、磁盘、版本、日志平台和编排系统调整。尤其不能在已有Docker节点上未经迁移直接加入新的 data-root,否则daemon可能看到一个空目录,表现得像“所有容器都消失了”。

八、data-root到底保存什么

data-root 是Docker管理数据的根目录,可能包含:

  • 镜像内容与元数据。
  • 容器元数据和可写层。
  • 本地Volume。
  • 容器日志文件。
  • Build Cache。
  • 网络和插件相关状态。

查看当前值:

bash
docker info --format 'DockerRootDir={{.DockerRootDir}} Driver={{.Driver}}'

8.1 为什么修改后“容器全没了”

mermaid
flowchart TD
    A["旧data-root有镜像、容器和Volume"] --> B["配置指向新空目录"]
    B --> C["dockerd从新目录加载元数据"]
    C --> D["docker ps和images看起来为空"]
    D --> E["旧数据通常仍在旧目录,但当前daemon未读取"]

这不一定是数据已经删除,可能只是daemon切换了数据视图。此时不要初始化、prune或覆盖旧目录,应停止扩散并核对旧、新路径。

8.2 迁移原则

  1. 盘点容器和有状态服务,完成数据库一致性备份。
  2. 摘流并停止会写入Docker数据目录的工作负载。
  3. 停止dockerd和相关管理操作。
  4. 使用保留权限、所有者、硬链接、xattr的可靠方式复制。
  5. 对比容量、文件数量和抽样校验。
  6. 修改配置并验证。
  7. 启动daemon,检查镜像、容器、网络、Volume和业务。
  8. 保留旧目录作为短期回退点,不立即删除。

复制Docker Root Dir不是数据库热备。有状态业务仍需要MySQL、Redis等自己的备份和恢复流程。

九、storage-driver为什么不能随便指定

常见Linux驱动是 overlay2,但是否可用取决于:

  • 内核能力。
  • backing filesystem。
  • d_type等文件系统特性。
  • rootless模式。
  • 发行版与Docker版本。

查看:

bash
docker info --format 'Driver={{.Driver}} Backing={{.DriverStatus}}'

改变Storage Driver可能让原驱动管理的镜像和容器在新驱动视图中不可见,也可能需要重新拉取镜像和重建容器。不要因为模板里写了 overlay2 就在已有生产节点盲目修改。

十、日志驱动与日志轮转

默认日志配置:

json
{
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "20m",
    "max-file": "5"
  }
}

原理:

mermaid
flowchart TD
    A["容器stdout/stderr"] --> B["Docker日志驱动"]
    B --> C["json-file或local文件"]
    B --> D["journald或远程日志驱动"]
    C --> E["轮转限制本地磁盘"]
    D --> F["日志平台采集与检索"]

关键边界:

  • daemon默认值通常只用于之后新建的容器
  • 已有容器不会因为restart自动改用新日志驱动;通常需要重建。
  • Compose中的service级logging可以覆盖daemon默认值。
  • 轮转只限制本地文件,不代表日志已可靠送达集中平台。
  • 应用写容器内普通文件时,Docker日志驱动看不到,仍可能撑满可写层。

验证新容器最终配置:

bash
docker inspect <container> \
  --format 'Driver={{.HostConfig.LogConfig.Type}} Options={{json .HostConfig.LogConfig.Config}}'

十一、Registry mirror、私有仓库和TLS

镜像加速配置示意:

json
{
  "registry-mirrors": [
    "https://mirror.example.internal"
  ]
}

生产镜像源必须考虑:

  • 镜像来源是否可信。
  • mirror是否同步及时。
  • 是否保留镜像Digest。
  • 供应链扫描和签名。
  • mirror故障时的回退。
  • 网络、代理、DNS和证书链。

insecure-registries 会允许非TLS或不受信TLS访问,降低传输安全,不应作为“证书报错的快捷修复”。正确做法通常是:

  1. 为Registry配置HTTPS。
  2. 把受信CA按Docker要求部署到宿主机。
  3. 验证域名、证书链和时间。
  4. 重启daemon并重新pull测试。

十二、daemon代理为什么与Shell代理不同

当前Shell设置:

bash
export HTTPS_PROXY=http://proxy.example:3128

不一定会被systemd启动的dockerd继承。链路是:

mermaid
flowchart TD
    A["用户Shell环境"] --> B["docker CLI"]
    C["systemd unit与drop-in环境"] --> D["dockerd"]
    D --> E["Registry与外部网络"]

常见systemd drop-in路径:

text
/etc/systemd/system/docker.service.d/proxy.conf

示意:

ini
[Service]
Environment="HTTP_PROXY=http://proxy.example:3128"
Environment="HTTPS_PROXY=http://proxy.example:3128"
Environment="NO_PROXY=localhost,127.0.0.1,registry.example.internal"

因为修改了systemd drop-in,所以需要:

bash
systemctl daemon-reload
systemctl restart docker

验证systemd实际环境:

bash
systemctl show docker -p Environment

代理URL可能带凭据,unit、日志、工单和命令输出都应按Secret保护。NO_PROXY的域名、IP、CIDR和端口匹配行为要按目标组件版本验证。

十三、默认地址池与网段冲突

Docker创建bridge网络时会分配子网。若与企业VPN、办公网、云VPC或机房网段重叠,可能出现:

  • 宿主机访问某内网地址被错误路由到Docker bridge。
  • 容器访问数据库超时。
  • VPN连接后部分服务突然不可达。
  • 不同环境表现不一致。

示意:

json
{
  "default-address-pools": [
    {
      "base": "172.30.0.0/16",
      "size": 24
    }
  ]
}

含义是从base中为新建网络切分 /24 子网。选择前必须由网络团队核对现有路由。它通常影响新建网络,不会自动重编号所有已有网络;已有冲突网络需要受控重建,并评估容器连接中断。

检查:

bash
ip route
docker network ls
docker network inspect <network>

十四、daemon DNS和容器DNS边界

可以配置daemon默认DNS,但不能把所有DNS问题都靠写死公共DNS解决:

json
{
  "dns": ["10.0.0.53", "10.0.0.54"]
}

要区分:

  • dockerd解析Registry域名。
  • 容器解析外部域名。
  • 自定义Docker网络中的服务名解析。
  • 宿主机systemd-resolved、VPN和企业DNS。
  • Compose service级DNS覆盖。

Docker自定义网络上的服务名通常由内置DNS处理,外部域名再转发到上游。错误的daemon DNS可能让新容器无法解析企业内部域名,但已有容器是否立即变化要按实际重建和resolver行为验证。

十五、live-restore能解决什么

json
{
  "live-restore": true
}

它的目标是在部分daemon不可用或重启场景中,尽量让运行容器进程继续运行。但它不表示:

  • Docker CLI仍可正常管理容器。
  • 可以创建新容器。
  • 网络、日志和插件一定完全无影响。
  • 所有版本升级都无缝。
  • 宿主机重启后容器仍不受影响。
  • 单机因此变成高可用。

必须在目标版本、存储和业务连接模型上演练。不能把live-restore当作无需摘流和回滚的理由。

十六、default-ulimits与资源默认值

文件描述符不足会导致Nginx、Redis、网关和高连接Java服务异常。daemon可以提供默认ulimit,但容器或Compose也可能覆盖:

json
{
  "default-ulimits": {
    "nofile": {
      "Name": "nofile",
      "Soft": 65536,
      "Hard": 65536
    }
  }
}

验证最终容器:

bash
docker inspect <container> --format '{{json .HostConfig.Ulimits}}'
docker exec <container> sh -c 'ulimit -n'

提高FD上限不等于系统能安全承受更多连接,还要评估内存、端口、连接池和后端容量。

十七、不要随意关闭iptables

类似配置:

json
{
  "iptables": false
}

会改变Docker创建NAT、转发和端口发布规则的行为。除非你明确由其他网络系统完全接管,否则可能导致:

  • -p端口发布不工作。
  • 容器访问外网失败。
  • 隔离规则变化。
  • 重启后规则不一致。

这不是“避免Docker修改防火墙”的无成本开关。必须结合iptables/nftables、发行版防火墙和编排平台验证。

十八、远程API为什么高危

Docker Unix Socket本身接近宿主机root权限。把daemon监听到未受保护TCP地址风险更大:攻击者可能创建高权限容器、挂载宿主机目录并控制主机。

绝不能为了远程连接直接开放未认证端口。需要远程管理时应优先使用:

  • SSH类型Docker context。
  • 双向TLS认证的受控API。
  • 防火墙和来源白名单。
  • 独立管理网络。
  • 审计和最小权限代理层。

不要把 /var/run/docker.sock 挂进普通业务容器。即使容器自身不是privileged,通过Socket调用高权限daemon仍可能控制宿主机。

十九、生产安全变更流程

mermaid
flowchart TD
    A["建立变更目标与影响范围"] --> B["保存当前配置、unit和运行对象"]
    B --> C["在同版本测试节点验证"]
    C --> D["JSON解析与dockerd validate"]
    D --> E["摘流一个金丝雀节点"]
    E --> F["重启daemon并检查status和journal"]
    F --> G{"daemon是否正常"}
    G -- "否" --> H["恢复旧配置并重启,保留日志"]
    G -- "是" --> I["验证info、镜像、网络、Volume和业务"]
    I --> J{"观察期是否正常"}
    J -- "否" --> H
    J -- "是" --> K["分批扩散并持续监控"]

19.1 变更前

bash
systemctl cat docker
docker info
dockerd --validate --config-file=/etc/docker/daemon.json

同时确保旧配置副本和回退步骤可用。备份文件本身可能包含代理凭据、Registry配置等敏感内容,应限制权限。

19.2 重启后立即检查

bash
systemctl is-active docker
systemctl status docker --no-pager
journalctl -u docker --since "10 min ago" --no-pager
docker version
docker info
docker ps -a

19.3 分层验收

  1. CLI能连接正确context。
  2. daemon版本、Root Dir、Storage Driver和日志驱动符合预期。
  3. 原有容器、镜像、网络和Volume可见。
  4. 能拉取受信测试镜像。
  5. 能创建并停止测试容器。
  6. 自定义网络DNS和端口发布正常。
  7. Volume写入、重建和读取正常。
  8. 资源限制和日志轮转对新容器生效。
  9. 核心业务健康、错误率和延迟正常。

二十、daemon启动失败怎么回滚

现象:

text
systemctl restart docker失败
docker命令提示无法连接daemon

不要删除 /var/lib/docker,也不要执行prune。按下面顺序:

20.1 看systemd状态

bash
systemctl status docker --no-pager -l
journalctl -u docker --since "15 min ago" --no-pager

20.2 校验配置

bash
dockerd --validate --config-file=/etc/docker/daemon.json
systemctl cat docker
systemctl show docker -p ExecStart -p Environment

20.3 判断错误类型

日志关键词常见原因
invalid character / JSON逗号、引号、尾随逗号等语法错误
specified both as a flag and in configurationunit flags和daemon.json重复选项
permission denieddata-root、证书、Socket或插件目录权限
address already in useAPI监听地址或端口冲突
pool overlaps默认地址池与已有网络重叠
graphdriver / overlay存储驱动与文件系统不兼容
certificateRegistry CA、域名、证书链或时间错误

20.4 恢复旧配置

恢复变更前经过验证的文件;如果同时修改了drop-in,恢复后执行:

bash
systemctl daemon-reload
systemctl restart docker

如果只恢复 daemon.json,校验后重启docker即可。启动后仍需检查Root Dir,避免在错误空目录上继续创建对象。

二十一、商业场景一:日志配置改了但磁盘仍增长

现象:设置daemon默认 max-sizemax-file 后,旧容器日志仍持续增长。

根因:daemon默认日志配置通常只在创建容器时写入HostConfig,restart旧容器不会重建配置。

证据:

bash
docker inspect old-api \
  --format '{{json .HostConfig.LogConfig}}'

修复:按发布流程重建容器,并验证新容器日志配置。还要检查应用是否把大量日志写入容器内文件,因为那部分不受Docker日志轮转控制。

二十二、商业场景二:修改data-root后容器消失

现象:daemon能启动,但 docker ps -adocker images近乎为空。

根因:新 data-root 是空目录,daemon加载了新的元数据视图;旧目录可能仍保留原数据。

处理:停止写入新目录,保存现场,核对 docker info、unit参数和旧路径,按迁移或回滚方案恢复。不能因为“看不到”就执行全量重新部署,更不能删除旧目录。

二十三、商业场景三:VPN开启后数据库超时

现象:容器平时正常,连接企业VPN后访问某数据库网段超时。

根因:Docker bridge子网与VPN路由重叠,宿主机把目标流量发到错误网桥。

证据:

bash
ip route
docker network inspect <network>

修复:由网络团队规划不重叠地址池,对新网络使用新池;已有网络需要受控重建。只重启应用不会消除路由冲突。

二十四、商业场景四:配置代理后内部Registry失败

现象:公共Registry可以拉取,内部Registry超时或请求走向代理。

根因:dockerd使用systemd代理环境,但 NO_PROXY 没有覆盖内部Registry域名/IP,或者格式与组件解析行为不匹配。

处理:检查systemd实际Environment、DNS、TCP、TLS和代理日志,修正NO_PROXY后daemon-reload、重启并验证。不能把内部Registry改成insecure来绕过代理或证书问题。

二十五、面试标准回答

修改daemon.json后怎样安全生效

先确认当前context、Docker版本、systemd ExecStart和配置文件路径,保存旧配置、docker info及运行对象;用JSON解析器和目标版本 dockerd --validate 校验,并排除同一选项同时存在于flags和配置文件。生产先摘流金丝雀节点再重启daemon,立即查看systemd status与journal,随后验证Root Dir、Storage Driver、日志、Registry、网络、Volume、新容器和业务。失败时恢复旧配置,不删除Docker数据目录。

daemon-reload和restart区别

daemon-reload只让systemd重新读取unit和drop-in,不会重启dockerd,也不会让它加载daemon.json;restart会重启dockerd并重新读取配置。只改daemon.json通常校验后restart即可,修改systemd drop-in则先daemon-reload再restart。

daemon默认日志配置为什么对旧容器不生效

默认日志驱动和选项在创建容器时写入容器HostConfig,已有容器restart仍使用原创建配置。要通过inspect确认,按受控发布重建容器;应用写在容器文件系统内的日志还不受Docker日志驱动轮转控制。

修改data-root后容器为什么都不见了

dockerd从当前data-root读取镜像、容器和Volume元数据。指向新空目录后,它看到的是新的空视图,旧数据不一定已删除。应停止扩散并核对旧、新路径,按停机迁移或配置回滚恢复,不能执行prune或直接删除旧目录。

live-restore是不是重启Docker完全无影响

不是。它只在部分daemon不可用场景尽量保持容器进程运行,CLI管理、新容器创建、网络变更、日志链路和插件仍可能受影响,也不保证所有跨版本升级无缝。生产仍需摘流、目标版本演练、监控和回滚。

为什么不能直接开放Docker远程API

Docker API可以创建高权限容器、挂载宿主机目录和控制网络,权限接近宿主机root。未认证TCP或把docker.sock挂给普通业务容器都可能导致主机失陷。远程管理应使用SSH context或双向TLS,并结合管理网络、白名单、审计和最小权限。

二十六、学习验收

不看答案完成:

  1. 区分CLI config、daemon config和容器HostConfig。
  2. 从systemd unit证明dockerd实际config-file、flags和环境变量。
  3. 制造一个JSON语法错误,用validate和journal定位后恢复,不删除数据目录。
  4. 制造一个flags与daemon.json重复选项,解释为什么不是简单覆盖。
  5. 配置日志轮转,证明旧容器不变、新建容器生效。
  6. 画出data-root切换后对象看似消失的原因和安全迁移流程。
  7. 规划不与VPC、VPN、办公网冲突的默认地址池。
  8. 区分Shell代理和systemd daemon代理,并验证NO_PROXY。
  9. 为私有Registry配置受信CA,不使用insecure绕过错误。
  10. 解释live-restore能保证什么、不能保证什么。
  11. 验证默认ulimit、容器覆盖和进程实际限制。
  12. 完成金丝雀变更、分层验收、失败回滚和事故记录。

关联知识点