Docker daemon配置原理、安全变更与故障回滚
daemon.json 不是一份“复制后重启Docker”的万能模板。它控制的是宿主机上的Docker daemon,可能同时影响所有镜像、容器、网络、Volume、日志和管理API。一个逗号错误、重复启动参数、冲突地址池或错误的 data-root,都可能让整台机器的Docker无法启动。
本页从配置加载链路开始,讲清每个常用配置为什么存在、何时生效、不这样做会怎样,以及生产如何变更、验证和回滚。Docker组件安装、仓库签名和rootless安装前置知识见 Docker安装、权限与升级。
学习目标
学完后应能:
- 区分Docker CLI配置、daemon配置和容器级配置。
- 找到rootful、rootless和Docker Desktop各自的配置入口。
- 解释systemd unit、drop-in、dockerd命令行参数和
daemon.json的关系。 - 使用JSON解析、
dockerd --validate、systemd状态和journal定位启动失败。 - 正确配置日志、data-root、Registry、代理、地址池、DNS、live-restore和资源默认值。
- 解释哪些配置只影响新容器,哪些需要重建容器,哪些会影响daemon全局行为。
- 在不删除Docker数据的前提下完成配置失败回滚。
- 设计摘流、备份、金丝雀、验证和回退闭环。
一、先分清三类配置
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 flags | Docker 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 Desktop | Desktop设置界面或Engine JSON入口 | VM内部daemon由Desktop管理,不应机械修改宿主机Linux路径 |
| 自定义启动 | dockerd --config-file <path> | 必须检查systemd实际ExecStart |
权威证据不是“教程说路径是这里”,而是当前daemon的实际启动方式:
systemctl cat docker
systemctl show docker -p ExecStart -p Environment -p FragmentPath -p DropInPaths
ps -ef | grep '[d]ockerd'rootless环境通常使用用户级systemd:
systemctl --user cat docker
systemctl --user status docker三、配置加载链路与参数冲突
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已经包含:
dockerd --host=fd://又在 daemon.json 配置同类 hosts,可能出现冲突。修改前必须检查:
systemctl cat docker
systemctl show docker -p ExecStart四、daemon-reload和restart不是一回事
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”。
五、先读取当前真实状态
变更前保存证据:
docker version
docker info
docker context show
systemctl status docker --no-pager
systemctl cat docker
journalctl -u docker --since "30 min ago" --no-pager保存业务现场:
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语法:
jq empty /etc/docker/daemon.json没有jq时可使用系统已有的JSON解析工具,但JSON合法只证明语法正确,不证明dockerd认识这些选项。
使用目标版本dockerd验证:
dockerd --validate --config-file=/etc/docker/daemon.json不同版本支持情况以目标环境的:
dockerd --help
dockerd --version为准。不要在开发机用新版本校验后,直接把配置下发给旧版生产daemon。
七、一个安全起点,不是万能模板
{
"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。
- 网络和插件相关状态。
查看当前值:
docker info --format 'DockerRootDir={{.DockerRootDir}} Driver={{.Driver}}'8.1 为什么修改后“容器全没了”
flowchart TD
A["旧data-root有镜像、容器和Volume"] --> B["配置指向新空目录"]
B --> C["dockerd从新目录加载元数据"]
C --> D["docker ps和images看起来为空"]
D --> E["旧数据通常仍在旧目录,但当前daemon未读取"]这不一定是数据已经删除,可能只是daemon切换了数据视图。此时不要初始化、prune或覆盖旧目录,应停止扩散并核对旧、新路径。
8.2 迁移原则
- 盘点容器和有状态服务,完成数据库一致性备份。
- 摘流并停止会写入Docker数据目录的工作负载。
- 停止dockerd和相关管理操作。
- 使用保留权限、所有者、硬链接、xattr的可靠方式复制。
- 对比容量、文件数量和抽样校验。
- 修改配置并验证。
- 启动daemon,检查镜像、容器、网络、Volume和业务。
- 保留旧目录作为短期回退点,不立即删除。
复制Docker Root Dir不是数据库热备。有状态业务仍需要MySQL、Redis等自己的备份和恢复流程。
九、storage-driver为什么不能随便指定
常见Linux驱动是 overlay2,但是否可用取决于:
- 内核能力。
- backing filesystem。
- d_type等文件系统特性。
- rootless模式。
- 发行版与Docker版本。
查看:
docker info --format 'Driver={{.Driver}} Backing={{.DriverStatus}}'改变Storage Driver可能让原驱动管理的镜像和容器在新驱动视图中不可见,也可能需要重新拉取镜像和重建容器。不要因为模板里写了 overlay2 就在已有生产节点盲目修改。
十、日志驱动与日志轮转
默认日志配置:
{
"log-driver": "json-file",
"log-opts": {
"max-size": "20m",
"max-file": "5"
}
}原理:
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日志驱动看不到,仍可能撑满可写层。
验证新容器最终配置:
docker inspect <container> \
--format 'Driver={{.HostConfig.LogConfig.Type}} Options={{json .HostConfig.LogConfig.Config}}'十一、Registry mirror、私有仓库和TLS
镜像加速配置示意:
{
"registry-mirrors": [
"https://mirror.example.internal"
]
}生产镜像源必须考虑:
- 镜像来源是否可信。
- mirror是否同步及时。
- 是否保留镜像Digest。
- 供应链扫描和签名。
- mirror故障时的回退。
- 网络、代理、DNS和证书链。
insecure-registries 会允许非TLS或不受信TLS访问,降低传输安全,不应作为“证书报错的快捷修复”。正确做法通常是:
- 为Registry配置HTTPS。
- 把受信CA按Docker要求部署到宿主机。
- 验证域名、证书链和时间。
- 重启daemon并重新pull测试。
十二、daemon代理为什么与Shell代理不同
当前Shell设置:
export HTTPS_PROXY=http://proxy.example:3128不一定会被systemd启动的dockerd继承。链路是:
flowchart TD
A["用户Shell环境"] --> B["docker CLI"]
C["systemd unit与drop-in环境"] --> D["dockerd"]
D --> E["Registry与外部网络"]常见systemd drop-in路径:
/etc/systemd/system/docker.service.d/proxy.conf示意:
[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,所以需要:
systemctl daemon-reload
systemctl restart docker验证systemd实际环境:
systemctl show docker -p Environment代理URL可能带凭据,unit、日志、工单和命令输出都应按Secret保护。NO_PROXY的域名、IP、CIDR和端口匹配行为要按目标组件版本验证。
十三、默认地址池与网段冲突
Docker创建bridge网络时会分配子网。若与企业VPN、办公网、云VPC或机房网段重叠,可能出现:
- 宿主机访问某内网地址被错误路由到Docker bridge。
- 容器访问数据库超时。
- VPN连接后部分服务突然不可达。
- 不同环境表现不一致。
示意:
{
"default-address-pools": [
{
"base": "172.30.0.0/16",
"size": 24
}
]
}含义是从base中为新建网络切分 /24 子网。选择前必须由网络团队核对现有路由。它通常影响新建网络,不会自动重编号所有已有网络;已有冲突网络需要受控重建,并评估容器连接中断。
检查:
ip route
docker network ls
docker network inspect <network>十四、daemon DNS和容器DNS边界
可以配置daemon默认DNS,但不能把所有DNS问题都靠写死公共DNS解决:
{
"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能解决什么
{
"live-restore": true
}它的目标是在部分daemon不可用或重启场景中,尽量让运行容器进程继续运行。但它不表示:
- Docker CLI仍可正常管理容器。
- 可以创建新容器。
- 网络、日志和插件一定完全无影响。
- 所有版本升级都无缝。
- 宿主机重启后容器仍不受影响。
- 单机因此变成高可用。
必须在目标版本、存储和业务连接模型上演练。不能把live-restore当作无需摘流和回滚的理由。
十六、default-ulimits与资源默认值
文件描述符不足会导致Nginx、Redis、网关和高连接Java服务异常。daemon可以提供默认ulimit,但容器或Compose也可能覆盖:
{
"default-ulimits": {
"nofile": {
"Name": "nofile",
"Soft": 65536,
"Hard": 65536
}
}
}验证最终容器:
docker inspect <container> --format '{{json .HostConfig.Ulimits}}'
docker exec <container> sh -c 'ulimit -n'提高FD上限不等于系统能安全承受更多连接,还要评估内存、端口、连接池和后端容量。
十七、不要随意关闭iptables
类似配置:
{
"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仍可能控制宿主机。
十九、生产安全变更流程
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 变更前
systemctl cat docker
docker info
dockerd --validate --config-file=/etc/docker/daemon.json同时确保旧配置副本和回退步骤可用。备份文件本身可能包含代理凭据、Registry配置等敏感内容,应限制权限。
19.2 重启后立即检查
systemctl is-active docker
systemctl status docker --no-pager
journalctl -u docker --since "10 min ago" --no-pager
docker version
docker info
docker ps -a19.3 分层验收
- CLI能连接正确context。
- daemon版本、Root Dir、Storage Driver和日志驱动符合预期。
- 原有容器、镜像、网络和Volume可见。
- 能拉取受信测试镜像。
- 能创建并停止测试容器。
- 自定义网络DNS和端口发布正常。
- Volume写入、重建和读取正常。
- 资源限制和日志轮转对新容器生效。
- 核心业务健康、错误率和延迟正常。
二十、daemon启动失败怎么回滚
现象:
systemctl restart docker失败
docker命令提示无法连接daemon不要删除 /var/lib/docker,也不要执行prune。按下面顺序:
20.1 看systemd状态
systemctl status docker --no-pager -l
journalctl -u docker --since "15 min ago" --no-pager20.2 校验配置
dockerd --validate --config-file=/etc/docker/daemon.json
systemctl cat docker
systemctl show docker -p ExecStart -p Environment20.3 判断错误类型
| 日志关键词 | 常见原因 |
|---|---|
| invalid character / JSON | 逗号、引号、尾随逗号等语法错误 |
| specified both as a flag and in configuration | unit flags和daemon.json重复选项 |
| permission denied | data-root、证书、Socket或插件目录权限 |
| address already in use | API监听地址或端口冲突 |
| pool overlaps | 默认地址池与已有网络重叠 |
| graphdriver / overlay | 存储驱动与文件系统不兼容 |
| certificate | Registry CA、域名、证书链或时间错误 |
20.4 恢复旧配置
恢复变更前经过验证的文件;如果同时修改了drop-in,恢复后执行:
systemctl daemon-reload
systemctl restart docker如果只恢复 daemon.json,校验后重启docker即可。启动后仍需检查Root Dir,避免在错误空目录上继续创建对象。
二十一、商业场景一:日志配置改了但磁盘仍增长
现象:设置daemon默认 max-size 和 max-file 后,旧容器日志仍持续增长。
根因:daemon默认日志配置通常只在创建容器时写入HostConfig,restart旧容器不会重建配置。
证据:
docker inspect old-api \
--format '{{json .HostConfig.LogConfig}}'修复:按发布流程重建容器,并验证新容器日志配置。还要检查应用是否把大量日志写入容器内文件,因为那部分不受Docker日志轮转控制。
二十二、商业场景二:修改data-root后容器消失
现象:daemon能启动,但 docker ps -a、docker images近乎为空。
根因:新 data-root 是空目录,daemon加载了新的元数据视图;旧目录可能仍保留原数据。
处理:停止写入新目录,保存现场,核对 docker info、unit参数和旧路径,按迁移或回滚方案恢复。不能因为“看不到”就执行全量重新部署,更不能删除旧目录。
二十三、商业场景三:VPN开启后数据库超时
现象:容器平时正常,连接企业VPN后访问某数据库网段超时。
根因:Docker bridge子网与VPN路由重叠,宿主机把目标流量发到错误网桥。
证据:
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,并结合管理网络、白名单、审计和最小权限。
二十六、学习验收
不看答案完成:
- 区分CLI config、daemon config和容器HostConfig。
- 从systemd unit证明dockerd实际config-file、flags和环境变量。
- 制造一个JSON语法错误,用validate和journal定位后恢复,不删除数据目录。
- 制造一个flags与daemon.json重复选项,解释为什么不是简单覆盖。
- 配置日志轮转,证明旧容器不变、新建容器生效。
- 画出data-root切换后对象看似消失的原因和安全迁移流程。
- 规划不与VPC、VPN、办公网冲突的默认地址池。
- 区分Shell代理和systemd daemon代理,并验证NO_PROXY。
- 为私有Registry配置受信CA,不使用insecure绕过错误。
- 解释live-restore能保证什么、不能保证什么。
- 验证默认ulimit、容器覆盖和进程实际限制。
- 完成金丝雀变更、分层验收、失败回滚和事故记录。
