1. 问题现象与排查思路拆解1.1 这个报错到底在说什么先把场景还原一下。你在一台 Linux 机器上为了让容器内部能通过某个自定义域名访问到指定服务动手改了 Docker 的配置比如往daemon.json里塞了hosts字段或者直接改了/etc/hosts之后重启 Docker结果systemctl restart docker直接报错服务起不来了。这时候systemctl status docker一看大概率是Job for docker.service failed或者start request repeated too quickly这类提示。这个问题的核心矛盾在于Docker 守护进程的启动流程对配置文件的格式和内容极其敏感任何它不认识的字段、格式错误的 JSON、或者与运行环境冲突的配置都会导致守护进程在初始化阶段直接退出。而hosts这个词在 Docker 的语境里有好几层含义很多人会把它们搞混这是踩坑的根源。我见过太多人把「给容器加 hosts 解析」和「给 Docker 守护进程本身配 hosts」这两件事混为一谈。前者是容器运行时的行为后者是守护进程启动时的行为两者的配置位置、生效方式、出错表现完全不同。你如果往daemon.json里写了一个 Docker 不支持的hosts字段守护进程解析配置时就会直接报unknown field然后拒绝启动。这就是最典型的「配置 hosts 导致 Docker 起不来」的场景。1.2 三种「hosts」场景必须先分清在动手排查之前你得先确认自己到底想干哪件事因为不同诉求对应的正确做法完全不一样。我把它整理成一张表你对号入座你的真实诉求错误做法正确做法出错表现让容器内能解析某个自定义域名往 daemon.json 写 hosts 字段用--add-host或 compose 的extra_hostsDocker 服务起不来让 Docker 守护进程拉镜像时走自定义解析直接改 /etc/hosts 后重启改 /etc/hosts 但不要重启 Docker或配 DNS一般不影响但可能不生效修改容器内 /etc/hosts 文件挂载覆盖 /etc/hosts用--add-host参数容器启动失败或解析异常这张表是我踩了无数次坑之后总结出来的。最要命的就是第一行很多人以为daemon.json是个万能配置箱什么都能往里塞实际上它只接受 Docker 官方定义的那几十个字段多一个都不行。Docker 的配置解析器是严格模式遇到未知字段直接 fatal连警告都不给你。1.3 排查的第一步永远是看日志不管你是哪种场景服务起不来第一件事就是看日志别瞎猜。我常用的命令组合是这样的# 看服务状态和最近的日志 systemctl status docker -l --no-pager # 看 Docker 守护进程自己的日志 journalctl -u docker.service -n 100 --no-pager # 如果是配置问题直接手动前台启动看报错 dockerd --debug最后那条dockerd --debug是杀手锏。它会绕过 systemd直接在前台把守护进程拉起来所有配置解析的错误会原封不动打印在终端上。你大概率会看到类似这样的输出unable to configure the Docker daemon with file /etc/docker/daemon.json: invalid character x looking for beginning of value或者unknown field hosts in daemon configuration看到unknown field就实锤了你写了个 Docker 不认的字段。看到invalid character就是 JSON 格式写错了多半是多了个逗号、少了引号、或者用了单引号。这两种是最高频的原因占了这类问题的八成以上。提示dockerd --debug前台启动时如果成功会一直占着终端不退出这时候你按 CtrlC 就能停掉不会影响 systemd 管理的服务。用它来验证配置是否合法比反复重启服务高效得多。2. daemon.json 配置的坑与正确写法2.1 为什么 daemon.json 不接受 hosts 字段这里得讲清楚原理不然你下次还会犯。Docker 守护进程的配置文件daemon.json采用的是白名单式的字段校验。Docker 源码里定义了一个Config结构体只有结构体里声明的字段才能被反序列化其他一律报错。这个设计是故意的目的是防止用户写错字段名却浑然不知导致配置静默失效。那hosts到底能不能配答案是在特定版本和特定场景下可以但绝大多数人用错了地方。Docker 守护进程本身确实有一个hosts配置项但它指的是Docker API 的监听地址比如unix:///var/run/docker.sock或者tcp://0.0.0.0:2375跟域名解析半毛钱关系都没有。你要是写了个hosts: [example.com:1.2.3.4]Docker 会尝试把它当成 API 监听地址去解析解析失败直接退出。我实测过在较新的 Docker 版本里往daemon.json写这种 hosts 数组报错信息大概是failed to parse daemon config: json: cannot unmarshal string into Go struct field Config.hosts of type []string或者干脆就是unknown field。总之就是起不来。所以记住一句话daemon.json 里的 hosts 是给 Docker API 用的不是给容器做域名解析用的。2.2 正确的 daemon.json 长什么样既然说到了我干脆把一份生产环境常用的daemon.json模板贴出来你可以直接抄。这份配置我用了很久覆盖了镜像加速、日志轮转、存储驱动、数据目录这些高频需求{ data-root: /var/lib/docker, storage-driver: overlay2, log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 }, registry-mirrors: [ https://your-mirror.example.com ], max-concurrent-downloads: 10, max-concurrent-uploads: 5, default-ulimits: { nofile: { Name: nofile, Hard: 65536, Soft: 65536 } }, live-restore: true, iptables: true, ip-forward: true }几个关键点解释一下。log-opts里的日志轮转是必须配的不然容器日志能把磁盘撑爆这是运维事故的高发区。live-restore开启后Docker 守护进程重启时容器不会跟着停对生产环境很友好。default-ulimits解决的是容器内文件句柄数不够的问题跑高并发服务时经常遇到。写这个文件有几个硬性规矩违反了必挂必须是合法 JSON不能有注释不能有尾随逗号字符串必须用双引号。字段名大小写敏感># 1. 先备份这是保命操作 cp /etc/docker/daemon.json /etc/docker/daemon.json.bak # 2. 改配置 vim /etc/docker/daemon.json # 3. 验证 JSON 语法 jq . /etc/docker/daemon.json # 4. 用前台模式验证配置能否被 Docker 接受 dockerd --validate --config-file /etc/docker/daemon.json # 5. 验证通过后再重载 systemctl reload docker # 如果 reload 不支持再 restart systemctl restart docker # 6. 确认服务状态 systemctl is-active docker docker info第 4 步的dockerd --validate是很多人不知道的隐藏功能它只校验配置不启动服务安全得很。第 5 步优先用reload而不是restart因为 reload 是平滑重载对运行中的容器影响最小。当然不是所有配置项都支持热重载像>docker run -d \ --name myapp \ --add-host api.internal:192.168.1.100 \ --add-host db.internal:192.168.1.101 \ nginx:latest这个参数的作用是往容器内的/etc/hosts文件里追加条目。容器启动后你docker exec -it myapp cat /etc/hosts就能看到这两行。这种方式的好处是只影响这一个容器不会污染宿主机也不会影响 Docker 守护进程。--add-host还支持一个特殊值host-gateway用来指向宿主机docker run -d --add-host host.docker.internal:host-gateway myapp这个在开发环境特别有用容器里想访问宿主机上跑的服务直接用host.docker.internal这个域名就行不用去查宿主机的 IP。这个特性在 Docker 20.10 之后才支持老版本用不了这点要注意。3.2 compose 里的 extra_hosts 写法如果你用 docker compose 编排服务对应的配置项叫extra_hosts写在服务的定义里version: 3.8 services: web: image: nginx:latest extra_hosts: - api.internal:192.168.1.100 - db.internal:192.168.1.101 - host.docker.internal:host-gateway ports: - 80:80这里有个坑我得提醒你extra_hosts是写在service 层级下的不是写在顶层。我见过有人把它写到和services平级的位置compose 直接报Additional property extra_hosts is not allowed。这个错误信息其实挺明确的但新手容易懵。另外compose 文件里的version字段现在其实已经废弃了新版 compose 会警告你。但为了兼容老版本留着也无妨不影响功能。3.3 批量场景用自定义网络加别名如果你有一堆容器需要互相通过域名访问一个个加--add-host太蠢了。这时候应该用 Docker 的自定义网络加网络别名# 创建自定义网络 docker network create --driver bridge mynet # 启动容器时加入网络并指定别名 docker run -d --name db --network mynet --network-alias database mysql:8 docker run -d --name cache --network mynet --network-alias redis-cache redis:7 docker run -d --name app --network mynet myapp:latest这样app容器里就能直接用database和redis-cache这两个域名访问对应服务Docker 内置的 DNS 服务器会自动解析。这是最优雅的方案因为它不依赖任何静态 IP容器重建后 IP 变了也不影响解析。自定义网络的好处还不止于此。默认的 bridge 网络不支持自动 DNS 解析只有自定义网络才支持。所以哪怕你不需要别名只要涉及容器间通信都应该用自定义网络这是最佳实践。方案适用场景是否影响 Docker 服务动态 IP 友好--add-host单个容器加少量静态解析否否extra_hostscompose 编排否否自定义网络别名多容器互访否是改 daemon.json不存在这种用法是会挂否这张表建议收藏下次遇到类似需求直接对照选方案能避开九成的坑。4. 服务已经起不来的急救与恢复4.1 快速恢复服务的三步法假设你现在已经处于 Docker 起不来的状态业务可能还等着恢复这时候别慌按这个顺序来第一步定位坏配置。用dockerd --debug前台启动把报错信息完整看一遍。如果报的是unknown field那就是 daemon.json 里有非法字段如果报的是invalid character那就是 JSON 语法错误。把出错的行号和字段记下来。第二步回滚配置。如果你之前备份过直接cp daemon.json.bak daemon.json覆盖回去。如果没备份就把可疑字段删掉或者干脆把 daemon.json 临时清空成{}先让服务起来再说。空配置是合法的Docker 会用默认值启动。# 临时清空配置先恢复服务 echo {} /etc/docker/daemon.json systemctl start docker systemctl is-active docker第三步逐步加回配置。服务起来之后别急着把原来的配置一股脑塞回去。一个字段一个字段地加每加一个就dockerd --validate验证一次确认没问题再继续。这样能精确定位到底是哪个字段惹的祸。我踩过最坑的一次是配置里有个字段名拼错了storage-driver写成了storage_driver下划线换成了连字符。Docker 报unknown field但我盯着看了半天没发现因为两个词长得太像了。后来用dockerd --validate才定位到。所以验证工具一定要用别靠肉眼。4.2 常见报错信息对照表我把这类问题常见的报错信息整理成一张速查表你对着报错找原因比瞎试快得多报错信息关键词根本原因解决方法unknown field hostsdaemon.json 写了非法字段删除该字段改用 --add-hostinvalid characterJSON 语法错误用 jq 检查并修复cannot unmarshal字段类型不对检查值的类型数组还是字符串failed to start daemon配置冲突或端口占用看完整日志排查端口和 socketstart request repeated too quickly反复失败被 systemd 限流先修配置再systemctl reset-failed dockerpermission denied文件权限或 SELinux检查文件权限排查安全策略最后那个start request repeated too quickly特别值得说。systemd 有个启动频率限制服务短时间内失败太多次会被临时禁止启动。这时候你光修配置还不够得先systemctl reset-failed docker把失败计数清零否则修好了也起不来。这个坑我见过太多人卡住明明配置改对了就是起不来急得团团转。4.3 预防胜于救火配置管理习惯与其每次出事再救不如养成几个好习惯从源头避免。习惯一配置即代码。把daemon.json纳入版本管理每次改动都有记录出问题能快速 diff 出改了什么。哪怕不用 git至少保留最近几个版本的备份。习惯二改前必验证。前面反复强调的dockerd --validate和jq养成肌肉记忆。改完配置先验证再重启这个顺序不能反。习惯三变更窗口化。生产环境的 Docker 配置变更尽量安排在低峰期并且准备好回滚方案。别在业务高峰期动守护进程配置万一挂了影响面太大。习惯四用配置管理工具。如果机器多手工改配置迟早出事。用 Ansible、SaltStack 这类工具统一管理配置模板化改一处推全网一致性有保障。提示systemctl reset-failed docker这个命令建议记牢。它专门用来清除 systemd 对某个服务的失败计数是「服务反复启动失败后被锁死」场景的救命命令。5. 几个容易混淆的边界场景5.1 改 /etc/hosts 后重启 Docker 会怎样有人会问我不动 daemon.json就改宿主机的/etc/hosts然后重启 Docker会不会有问题答案是一般不会导致 Docker 起不来但可能不生效。Docker 守护进程启动时确实会读宿主机的/etc/hosts但它主要是为了解析自己的 API 监听地址和镜像仓库地址。你往里加的普通域名条目Docker 守护进程不一定用得上。而且容器内的/etc/hosts是 Docker 在容器启动时动态生成的跟宿主机的/etc/hosts是两回事你改宿主机的不影响容器内的。真正需要注意的是如果你在/etc/hosts里把localhost或者127.0.0.1指向了错误的东西那可能会影响 Docker 守护进程的启动。因为 Docker 启动时会解析localhost解析错了就可能卡住。这种情况比较少见但确实存在。5.2 容器内 /etc/hosts 被挂载覆盖的坑另一个高频坑是有人为了给容器加解析直接把宿主机的 hosts 文件挂载进容器# 错误示范别这么干 docker run -v /etc/hosts:/etc/hosts myapp这么干会导致容器启动异常因为 Docker 在容器启动时需要往/etc/hosts里写入容器自己的主机名和网络信息你把文件挂载覆盖了Docker 写不进去容器就可能起不来或者网络异常。正确的做法还是用--add-host让 Docker 自己管理这个文件。如果确实需要完全自定义容器内的 hosts可以用--add-host加多条或者用 init 容器在启动后动态修改。但直接挂载覆盖是绝对要避免的。5.3 Docker Desktop 环境的特殊性如果你用的是桌面版 Docker比如在开发机上情况又有点不一样。桌面版 Docker 跑在一个轻量虚拟机里daemon.json的位置和 Linux 原生环境不同通常在设置界面里配置而不是直接改文件。你在宿主机上改/etc/hosts对虚拟机里的 Docker 守护进程基本没影响。桌面版环境里想给容器加解析还是用--add-host或者 compose 的extra_hosts这个跨环境是通用的。别去折腾虚拟机的配置文件容易把整个 Docker 环境搞坏重装起来很麻烦。6. 实操心得与避坑清单6.1 我踩过的三个真实坑坑一JSON 尾随逗号。这个太经典了。我写配置时习惯性地在最后一个字段后面加了个逗号JSON 标准不允许尾随逗号Docker 直接报invalid character }。肉眼看了十分钟没看出来jq一跑就定位到了。从此我改完配置必跑jq。坑二字段名想当然。我以为 Docker 支持dns字段配全局 DNS就写了个dns: [8.8.8.8]。结果 Docker 报unknown field。后来查文档才知道全局 DNS 配置的正确字段是dns但它的值格式和我想的不一样而且这个字段在某些版本里行为有变化。教训是别猜字段名查官方文档。坑三reload 不生效。我改了配置用systemctl reload docker以为生效了结果发现某些字段根本没重载。后来才明白Docker 的 reload 只支持部分字段的热重载像>
阅读完成 · 觉得有帮助?