接手一台CentOS7服务器时,最容易让人茫然的就是Docker的“疑难杂症”同时涌上来:docker pull慢得像蜗牛、容器日志无限增长把磁盘塞满、想开2375端口远程调试、自建Harbor仓库还需要跳过HTTPS校验。这些问题单独去搜,答案五花八门,但细看全部指向同一个文件——/etc/docker/daemon.json。折腾了两年多CentOS7上的Docker,今天把这台机器上关于daemon.json的经验一次性说清楚:它到底管什么、在CentOS7里小版本差异有多大、配置完怎么验证、出错怎么救回来。
1. daemon.json在CentOS7里的角色:它管的是Docker引擎,不是容器
1.1 一个文件管住dockerd的所有启动参数
docker daemon(也就是dockerd进程)本身有一大堆启动参数,包括镜像仓库地址、存储驱动、日志格式、监听地址、数据目录等等。在systemd时代之前,这些参数要么写在启动脚本里,要么靠命令行拼接,改起来很痛苦。后来Docker出了daemon.json,统一用JSON格式保存dockerd的配置,相当于把“引擎启动选项”集中到了一个文件里。
它和普通容器配置文件最大的区别是:daemon.json不影响单个容器的运行参数(那是docker run或者compose文件的事),它影响的是Docker引擎本身的行为。换句话说,你改了daemon.json,影响的是这一台机器上所有容器的运行底座。比如日志上限、存储驱动这类全局策略,必须在daemon.json里定,而不是在个别的容器启动命令里碰运气。
1.2 默认路径:为什么一定是/etc/docker/daemon.json
正常情况下,CentOS7安装完Docker之后,/etc/docker目录下可能只有证书相关的子目录,daemon.json这个文件是不存在的,需要你自己手动创建。
dockerd在启动时会按固定顺序查找配置文件,默认取的就是/etc/docker/daemon.json。如果你用systemd管理docker服务,可以通过这个命令确认实际读取路径:
systemctl cat docker | grep ExecStart正常输出类似:
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock这里没有带--config-file参数,就说明dockerd用的是默认路径。如果你在/etc/systemd/system/docker.service.d/下创建了override.conf,内容里可以追加--config-file指向别的路径,但绝大多数场景不需要动这个,就老老实实用/etc/docker/daemon.json。
文件创建后建议设置root:root所有者和644权限:
touch /etc/docker/daemon.json chown root:root /etc/docker/daemon.json chmod 644 /etc/docker/daemon.json1.3 和/etc/sysconfig/docker的分工:新旧两套体系的冲突点
CentOS7上玩Docker时间比较久的人,一定见过/etc/sysconfig/docker这个文件。它是老版本的Docker用systemd环境变量传递启动参数的方式,里面通常有一行OPTIONS='--selinux-enabled --log-driver=journald'之类的配置。
新版本Docker虽然默认读daemon.json,但CentOS7上如果升级Docker时没有清理干净,这两个地方的配置会互相打架。最常见的报错是:
unable to configure the Docker daemon with file /etc/docker/daemon.json: the following directives are specified both as a flag and in the configuration file意思是你把同一个参数既写在了sysconfig的OPTIONS里,又写在了daemon.json里。解决方法也很简单:检查/etc/sysconfig/docker,把和daemon.json重复的OPTIONS去掉,或者干脆停用这个文件里的Docker配置段,只保留环境变量部分。我个人的习惯是,新部署的机器直接忽略/etc/sysconfig/docker,所有配置一律走daemon.json,这样排查问题时只需要盯一个文件。
2. CentOS7上最常用的四组配置:照着抄就能用的示例
2.1 registry-mirrors:docker pull慢的常规解法
CentOS7默认从Docker Hub拉镜像,网络环境大家都懂,速度经常很感人。配置镜像加速是daemon.json里最常见的使用场景:
{ "registry-mirrors": [ "https://docker.mirrors.ustc.edu.cn", "https://hub-mirror.c.163.com" ] }如果你用的是云厂商的服务器,建议直接登录对应云平台的容器镜像服务控制台,会有一个专属加速地址,填进去就行。注意registry-mirrors是数组,可以配多个地址,Docker会依次尝试,哪个通就哪个。
配置完一定要看一眼docker info里的Registry Mirrors字段:
docker info | grep -A 5 "Registry Mirrors"如果这里还是空的,说明配置没被读到,回到第1章的路径问题排查。
2.2 storage-driver和data-root:磁盘占用与数据迁移
CentOS7默认的内核是3.10,早期Docker版本默认用的还是devicemapper,那个东西在loopback模式下磁盘利用率极差,动不动就报Data Space Used。所以新一点的Docker版本在CentOS7上默认已经是overlay2了。如果你的机器还在用老配置,建议显式指定:
{ "storage-driver": "overlay2", "data-root": "/var/lib/docker" }这里补充一个关键点:存储驱动切换不会自动迁移已有镜像和容器。如果你从devicemapper切到overlay2,旧数据在新驱动下是读不到的。稳妥的做法是:先把常用镜像docker save导出,切换驱动后重新docker load,容器用compose文件重新创建。
如果你的根分区比较紧张(很多人说CentOS7根目录扩容),更建议直接迁移data-root。操作步骤也不复杂:停docker,把/var/lib/docker整个目录rsync到新分区,然后修改daemon.json的data-root指向新目录,再启动docker。
systemctl stop docker rsync -avx /var/lib/docker/ /data/docker/ # 修改daemon.json,加入 "data-root": "/data/docker" systemctl start docker启动后docker info里Docker Root Dir字段会变成/data/docker,这样比给根分区扩容更省事。
2.3 log-driver和log-opts:防止容器日志写满磁盘
这是我踩过最狠的坑。CentOS7默认的日志驱动是json-file,如果不设置上限,一个疯狂打日志的容器能在几天内把整个磁盘塞满。daemon.json里加这段:
{ "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3" } }含义是单个日志文件最大100MB,最多保留3个,超过就滚动删除。
要注意,这个配置只对之后创建的容器生效,已经存在的容器不会自动套用新策略。所以改完daemon.json后,你需要把仍然乱打日志的旧容器重新创建一遍(比如docker-compose up -d --force-recreate),才能让日志上限真正生效。
2.4 hosts和insecure-registries:远程管理端口与自建仓库
需要远程调试Docker时,可以配置监听TCP端口:
{ "hosts": [ "unix:///var/run/docker.sock", "tcp://0.0.0.0:2375" ] }这个配置非常实用,但也非常危险:2375端口是明文端口,没有任何认证,一旦暴露到公网,等于把Docker的root权限送给了别人。我的建议是只在内网调试环境用,生产环境要么用TLS证书保护,要么配合防火墙只允许特定IP访问。
自建Harbor仓库时,如果Harbor用的是HTTP协议或者自签名HTTPS证书,Docker客户端默认会拒绝,需要在daemon.json里把仓库地址加入白名单:
{ "insecure-registries": ["192.168.1.100:5000"] }配置完成后,docker login 192.168.1.100:5000就不会再报证书错误了。如果配了还是报错,先确认Harbor那边是不是强制HTTPS,再用curl -k https://192.168.1.100:5000/v2/测一下连通性再排查。
3. 配置改动后的生效逻辑:为什么很多人改完发现“没效果”
3.1 正确的重载姿势
daemon.json不是热加载配置,改完之后必须重启dockerd进程。很多人习惯只跑systemctl daemon-reload,认为这就够了,其实这是个误区。
systemctl daemon-reload的作用是重新加载systemd的unit文件,让你在/etc/systemd/system/docker.service.d/里做的修改生效。它并不会主动去重新读取daemon.json。真正让daemon.json生效的是重启docker服务本身。标准操作是:
systemctl daemon-reload systemctl restart docker systemctl status docker --no-pager把daemon-reload放在前面,是为了保险起见,万一docker.service确实有unit级别的修改,先重新加载了再重启,不会出现改了unit又因服务未重启而失效的情况。
3.2 验证配置是否生效的三个命令
改完配置后,我一般按顺序跑三个命令确认:
docker info | grep -iE "storage driver|logging driver|registry mirrors|docker root dir"这条命令一次性检查存储驱动、日志驱动、镜像加速、数据目录四个关键项。
docker inspect <容器名> | grep -i log这条命令检查特定容器实际生效的日志配置,因为前面说了,daemon.json的日志上限只对新建容器生效,必须用inspect确认。
cat /proc/$(pgrep dockerd)/cmdline | tr '\0' '\n' | grep -E "config|host"这条是进阶技巧,直接看dockerd进程的真实启动参数,判断它到底有没有读到daemon.json,以及最终监听了哪些地址。如果daemon.json里的配置没有出现在进程参数里,多半是文件路径或权限的问题。
3.3 为什么配置没生效的常见原因
根据我的经验,配置“没生效”基本逃不出这几个原因:
- 文件不是合法JSON:用了单引号、写了注释、末尾多了逗号,都会导致dockerd直接拒绝读取。
- 字段名拼写错误:比如
log-driver写成了log-driverr,区分大小写,写错了不会报错,只是默默忽略。 - 没有重启docker:这就不用多说了,排第一。
- 配置与sysconfig冲突:老CentOS7的常见问题,第1章已经展开讲过。
- 文件权限异常:如果docker无法读取文件,启动时会直接报permission denied,而不是继续用默认值。
4. 真实踩坑记录:一次daemon.json错误导致Docker起不来的完整排查链路
4.1 事故现场
之前给一台CentOS7虚拟机加insecure-registries配置,内容大概是这样的:
{ "insecure-registries": ["192.168.10.5:5000"], "log-driver": "json-file", "log-opts": { "max-size": "100m", "max-file": "3", } }注意看,max-file后面多了一个逗号。我当时手快没注意,接着执行systemctl restart docker,然后机器上的所有容器瞬间全部挂了。
4.2 第一步:系统状态确认
重启后第一件事是看服务状态:
systemctl status docker --no-pager输出显示Active: failed (Result: exit-code),进程直接退出。
4.3 第二步:journalctl看真实报错
systemd管理的服务,启动失败的真实原因要去journald里翻:
journalctl -u docker --since "10 minutes ago" --no-pager | tail -50关键报错信息是:
unable to configure the Docker daemon with file /etc/docker/daemon.json: invalid character '}' looking for beginning of value这句话翻译过来就是:JSON解析到某个位置的右花括号时,发现前面不该有内容。结合我的文件内容,就是max-file后面的那个逗号惹的祸。
4.4 第三步:修复与恢复
确认问题后,先把错误文件备份,再改成正确的JSON:
cp /etc/docker/daemon.json /etc/docker/daemon.json.bak然后移除多余的逗号,保存后重启:
systemctl start docker docker ps容器列表出来了,Docker服务恢复。
4.5 预防手段:改配置前先校验JSON
这次事故之后,我养成一个习惯:每次改daemon.json都要先跑一遍JSON校验命令,确认无误再重启docker。
python3 -m json.tool /etc/docker/daemon.json或者用jq:
jq . /etc/docker/daemon.json如果命令无报错并输出了格式化的JSON内容,才说明文件是合法的。这个习惯帮我后面避开了好几次同样的坑。改配置文件前先备份,校验语法后再重启服务,这两条做到了,daemon.json相关的故障率能降九成。
我把常见的启动失败场景整理成了表格,方便你排错时快速对照:
| 报错关键词 | 含义 | 处理方式 |
|---|---|---|
| invalid character ... looking for beginning of value | JSON语法错误 | 用json.tool或jq校验并修正 |
| both as a flag and in the configuration file | 与sysconfig配置冲突 | 清理/etc/sysconfig/docker里的重复OPTIONS |
| permission denied while trying to connect | 文件权限错误 | 检查daemon.json的属主和权限 |
| is not a valid repository name | 镜像地址格式错误 | 检查registry-mirrors里有没有写错协议头 |
5. 不同Docker版本与CentOS7特有的兼容性差异
5.1 版本跨度大,字段兼容性要留神
CentOS7的官方源里带的docker版本比较老,现在很多新机器装的是docker-ce。这两个体系下,daemon.json的字段兼容性有差异。比如features、builder这类新字段,老版本dockerd根本不认识;反过来,storage-driver设为overlay2在很老的版本上可能不受支持。
我处理过一台CentOS7,官方源里的docker还是1.13,配置里写了exec-opts这个字段,结果dockerd直接忽略,导致Kubernetes的kubelet死活报cgroup驱动不对。后来升级到docker-ce 20.10才正常。
所以升级Docker版本前,建议先把daemon.json完整备份,升级后用docker info逐项核对,重点看Storage Driver、Logging Driver、Registry Mirrors、Docker Root Dir这几个关键字段是否还和升级前一致。
5.2 cgroup驱动与systemd的配合
CentOS7用systemd作为init系统,如果你在这台机器上跑Kubernetes,Docker的cgroup驱动必须和kubelet保持一致。现在很多K8s环境要求使用systemd驱动,配置如下:
{ "exec-opts": ["native.cgroupdriver=systemd"] }如果不加这个,Docker默认用cgroupfs,kubelet初始化时会报:
failed to run Kubelet: failed to validate kubelet flags: cgroup-driver is set to "systemd" but Docker is set to "cgroupfs"改完这个配置后,所有正在运行的容器需要重建才会切换到新的cgroup驱动。
5.3 内核3.10与overlay2的历史问题
CentOS7默认内核是3.10,早期overlay2在这个内核上有兼容性问题,后来Docker对内核做了特殊判断才逐步解决。如果你用的是很早的Docker版本,又遇到overlay2相关的奇怪错误,可以考虑先升级docker而不是单独改daemon.json。内核升级没有把握的话,至少把Docker升级到较新的稳定版本,大部分overlay2问题都已经被处理过了。
还有一个小细节:不要在CentOS7上随意开启experimental特性字段,比如"experimental": true,这类选项需要新版内核配合,老机器上开了反而容易导致dockerd启动异常。
最后分享一个我个人养成的操作习惯,也算是这几年的总结:daemon.json保持最小化,上面列到的四组常用配置,够用就好,不要一次塞进一堆似是而非的字段。每改一次之前,先cp一个带日期的备份,然后用python3 -m json.tool校验语法,再重启docker。这套流程在CentOS7上帮我和团队避开了无数个“服务起不来”的凌晨,也希望你能用得上。