资讯动态

Docker配置文件分层与生效边界:daemon.json与Compose排错

发布时间:2026/10/26 14:28:07 来源:尧图企业网站定制
1. Docker 的配置文件其实分五层很多人第一次栽在 Docker 配置文件上都是同一个场景明明在/etc/docker/daemon.json里加了字段systemctl restart docker也执行了可docker info里的结果纹丝不动。我最早做容器化改造时就吃过这个亏——为了让容器日志别把磁盘写满把log-opts加进配置文件重启十几次都没反应最后发现是docker.service的启动命令里带着--log-driver两条路径打架dockerd 直接忽略了半截配置。所以聊 Docker 配置文件先得把配置这个词的范围划清楚。Docker 不是只有一个配置文件它至少分成五个互相独立的层守护进程层的daemon.json、系统服务层的 systemd 单元与EnvironmentFile、客户端层的~/.docker/config.json、构建层的Dockerfile与.dockerignore、编排层的docker-compose.yml与.env。这五层由不同的进程读取、在不同的时机生效、修改后需要不同的动作才能落地。把它们混为一谈就会出现我改了配置怎么没用的经典困惑。这篇内容适合三类人看一是刚接手容器运维、被各种配置文件绕晕的同学二是在生产环境踩过日志、磁盘、网段坑想系统梳理一遍的老手三是准备把一堆手工启动参数固化成标准配置的团队。我会按这层配置管什么、写在哪、怎么写、怎么验证的顺序一层层拆中间穿插我自己踩过的坑和可以直接抄走的配置片段。提示全程默认你已经装好了 Docker 引擎Engine 20.10 及以上并且用 systemd 管理服务。如果你用的是别的 init 系统或者桌面版客户端路径和控制方式会有差异后面对应章节会单独点出来。2. daemon.json守护进程的主控台2.1 文件在哪、谁在读、什么时候读daemon.json是dockerd这个守护进程的配置文件默认位置在 Linux 上是/etc/docker/daemon.jsonWindows Server 上是C:\ProgramData\docker\config\daemon.json。这个文件不是守护进程随时监听的它只在dockerd进程启动的那一刻被完整读取一次读完之后就固化到内存里。你改完文件但没重启服务等于什么都没做。还有一个常被忽略的事实daemon.json不是必须存在的。文件不存在时dockerd用一套内置默认值运行一切正常。所以新装的机器上/etc/docker/目录里可能空空如也这是正常的不是你装漏了东西。读取时机决定了排错方向。如果启动失败问题一定在解析或冲突阶段如果启动成功但表现不对问题在字段语义或生效范围阶段。这两类故障的定位手法完全不同后面第 6 章会仔细说。配置文件本身是标准 JSON不允许注释不允许尾随逗号。这一点劝退过无数人——写惯了 YAML 的人下意识加个#注释重启就报invalid character # looking for beginning of object key string。我的习惯是另建一个daemon.json.bak记录每行改动的理由主文件保持纯净。2.2 高频字段逐个拆解与取值逻辑字段几十个真正常改的就那么十几个。下面这张表是我这些年在生产环境反复用到的集合直接照着改基本能覆盖八成需求。字段作用典型取值踩坑提示># 停服务前先停 socket否则 systemd 的 socket 激活会把它重新拉起来 systemctl stop docker.socket systemctl stop docker # -aHAX 保留权限、属主、ACL 和扩展属性少了这几个参数容器会起不来 rsync -aHAX --numeric-ids /var/lib/docker/ /data/docker/ # 开了 SELinux 的机器必须补这一步否则容器访问存储会权限拒绝 restorecon -R /data/docker搬完再往daemon.json里写>{ log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 } }default-address-pools值得单独说。Docker 默认从172.17.0.0/16开始分配网桥网段容器一多会往上吃到172.18、172.19……如果你的宿主机或者内网正好用了这些地址段就会出现容器能起、但连不上某个内网服务的怪现象ping通一半、telnet不通排查起来极其痛苦。规范做法是显式声明一个专用池把base选在内网没用的段上size决定每个网段切多大。{ default-address-pools: [ { base: 10.201.0.0/16, size: 24 } ] }这里的算法很直白/16的地址池切成/24的子网一共2^(24-16) 256个网段每个网段可用地址 254 个。够几百个容器网络用同时和常见的内网/16完全不重叠。配置前建议先ip route看一眼宿主机路由表把已有的段都列出来避开。2.3 改完怎么生效才不出事正确顺序只有三步校验语法、重启服务、验证结果。第一节提到的语法校验很多人靠重启后看报错效率太低。较新版本的dockerd提供了--validate参数可以在不影响运行中服务的前提下检查配置dockerd --validate --config-file/etc/docker/daemon.json输出configuration OK就放心重启。如果版本不支持这个参数退而求其次用python3 -m json.tool /etc/docker/daemon.json检查 JSON 合法性至少能挡住逗号和注释这类低级错误。重启这一步要格外小心。systemctl restart docker默认会停掉所有容器线上环境等于一次全站中断。两个办法规避一是提前live-restore: true它能让守护进程重启时容器继续跑但注意它和 swarm 模式不能共用而且网络配置的部分变更仍然需要重建容器二是挑维护窗口或者干脆用滚动方式一台一台改。注意重启完成后别只看服务状态一定要验证。systemctl is-active docker只告诉你进程活着不代表配置生效了。我一般固定跑这几条docker info | grep -E Storage Driver|Logging Driver|Docker Root Dir|Cgroup Driver docker info --format {{json .}} | python3 -m json.tool | head -40对照你要改的字段逐个确认。曾经有一次我把exec-opts拼成了exec-opt少了 sdockerd 对未知字段是忽略而不是报错重启成功、状态正常直到某个编排组件报 cgroup 不一致才发现配置压根没生效。这个教训让我养成了改完必须 grep 验证的习惯。3. systemd 单元与环境文件开机那一刻谁说了算3.1 为什么不要直接改 docker.servicedaemon.json不是唯一的配置入口。dockerd是以 systemd 服务的形式启动的启动命令写在/usr/lib/systemd/system/docker.service里形如ExecStart/usr/bin/dockerd -H fd:// ...。理论上你可以直接改这个文件加启动参数但千万别这么干——包管理器升级 Docker 时会覆盖它你的改动无声无息地消失某次重启之后服务行为突变排查成本高得离谱。正确做法是用 systemd 的 drop-in 机制在/etc/systemd/system/docker.service.d/目录下建一个.conf文件。这个目录的优先级高于包自带的单元文件升级不会覆盖。# /etc/systemd/system/docker.service.d/override.conf [Service] # 必须先清空再重写否则会变成追加出现两个 ExecStart ExecStart ExecStart/usr/bin/dockerd -H fd:// --containerd/run/containerd/containerd.sock LimitNOFILE1048576 LimitNPROCinfinityExecStart后面空一行这个写法是 systemd 的语法规定给一个空值表示清空原有列表然后再追加。漏了这行最终生效的是两条ExecStart拼接服务直接起不来。我第一次写 drop-in 就犯过systemctl status docker报docker.service has more than one ExecStart看着莫名其妙实际就是少了那行空赋值。LimitNOFILE这个也别忽略。容器里跑高并发服务时文件描述符上限受守护进程继承的限制。即便你在daemon.json里配了default-ulimits宿主机层面的上限太低照样卡住。默认 1024 在老系统上很常见调到 1048576 是常规操作。改完单元文件必须systemctl daemon-reload否则 systemd 用的还是缓存里的旧定义。这个动作和restart是两回事很多人只 restart 不 reload改了等于没改。3.2 EnvironmentFile 与控制权优先级另一种常见写法是把变量塞进/etc/default/docker部分发行版是/etc/sysconfig/docker再在单元文件里通过EnvironmentFile引入。这种模式在早期版本里很流行现在用得少了但老机器上还会遇到。[Service] EnvironmentFile-/etc/default/docker ExecStart/usr/bin/dockerd $DOCKER_OPTS路径前面的-表示文件不存在也不报错属于防御性写法。这种方式的坑在于$DOCKER_OPTS是靠 shell 展开的参数里带空格、引号时容易解析异常而且和daemon.json同时配置同一个选项时行为不确定。关于优先级有一条实践结论值得记住同一个选项如果在daemon.json和命令行参数里同时出现较新版本的dockerd会直接启动失败报错信息大意是该指令同时以命令行参数和配置文件形式指定。这其实是好事比默默取其中一个更安全。真遇到了就去 drop-in 里把冲突的参数删掉统一收敛到daemon.json。我的团队定了一条规矩除-H和--containerd这类必须由单元文件决定的参数外所有可配项一律写进daemon.json。理由是单一真相源出问题只看一个文件新人接手也不用翻两处。4. 客户端与构建侧配置别把三层混成一锅4.1 ~/.docker/config.json 管的是客户端身份daemon.json管服务端~/.docker/config.json管客户端两者互不相干。这个文件在你第一次执行docker login时自动生成主要存认证信息和客户端行为。{ auths: { registry.internal:5000: {} }, credsStore: secretservice, psFormat: table {{.Names}}\t{{.Status}}\t{{.Ports}} }注意auths里的值是空对象{}实际凭据被交给credsStore指定的凭据助手保管。这是比把 base64 编码的账号密码明文放文件里安全得多的做法——base64 不是加密任何能读文件的人都能解开。团队协作时我的建议是统一启用credsStore别图省事往文件里写明文。还有一个容易被忽略的坑~/.docker/config.json是按用户隔离的。你sudo docker login之后普通用户docker pull私有镜像照样 401因为读的是另一个人的 home 目录。CI 环境里更明显跑构建的用户和登录的用户往往不是同一个。解决办法要么统一用同一个用户要么显式指定DOCKER_CONFIG环境变量指向共享目录。psFormat这类格式化配置属于锦上添花但真能提升效率。默认的docker ps输出太挤把Names、Status、Ports排一排一眼就能看出哪个容器在反复重启。4.2 .dockerignore 与 Dockerfile 的边界很多人不把.dockerignore当配置文件其实它的重要性一点不亚于前几个。docker build的第一步是把整个构建上下文打包发给守护进程上下文里有什么就传什么。我在一个前端项目里见过上下文 1.2G 的构建node_modules、.git、构建产物全被塞进去每条RUN前的传输都要等半分钟。.dockerignore的语法和.gitignore类似但不完全一样特别是例外规则。# 忽略一切非必要内容 .git node_modules dist *.log .env* # 但保留构建脚本需要的模板 !config/template.json!开头的例外规则要在忽略规则之后写才生效顺序有讲究。另外**在这里是可用的**/*.md能匹配任意层级的 Markdown 文件。提示改了.dockerignore之后如果构建产物看起来没变化先看是不是COPY . .把不该带的东西带进去了。用docker build --no-cache排除缓存干扰再用docker history看每层大小一眼就能定位是哪一层把上下文整个吃进去了。Dockerfile本身算不算配置文件我的理解是它更像构建脚本但它定义了镜像的最终形态是构建层配置的源头。几个和配置相关的经验基础镜像尽量固定到具体的小版本号别用latest否则今天能构建成功的镜像明天可能因为基础镜像更新而失败ENV设的值会在容器运行时一直生效用于配时区、字符集这类全局变量很合适ARG只在构建期有效别指望它在运行期读到。5. Compose 文件的组织方式与叠加技巧5.1 版本字段已经不需要了如果现在还有人让你在docker-compose.yml顶部写version: 3.8可以直接告诉他Compose V2 已经废弃这个字段写了只会得到一条the attribute version is obsolete的警告。老版本 V1 靠它判断语法特性V2 改成了按文件内容自动推断。这个变化很多人没跟上简历上还写着熟悉 Compose 3.x 语法其实已经过时两三年了。一个精简但完整的 Compose 文件长这样services: api: image: registry.internal:5000/app/api:1.4.2 restart: unless-stopped environment: APP_ENV: ${APP_ENV:-prod} DB_HOST: db ports: - 8080:8080 volumes: - app-data:/var/lib/app depends_on: db: condition: service_healthy healthcheck: test: [CMD, curl, -f, http://localhost:8080/healthz] interval: 15s timeout: 3s retries: 3 start_period: 30s db: image: registry.internal:5000/base/db:12.4 environment: DB_PASSWORD: ${DB_PASSWORD:?DB_PASSWORD is required} volumes: - db-data:/var/lib/db healthcheck: test: [CMD-SHELL, pg_isready -U app] interval: 10s retries: 5 volumes: app-data: db-data:几个值得说的细节。${VAR:-default}是给默认值${VAR:?message}是必填校验变量缺失时直接报错退出比悄悄用空字符串启动、跑到一半连接失败强太多。start_period是给慢启动服务的宽限时间避免启动阶段就被判定为 unhealthy 然后被depends_on拦住。restart: unless-stopped比always更符合直觉——手动停掉的容器不会在守护进程重启后自己爬起来。5.2 变量、叠加与配置分层真实项目很少只有一个 Compose 文件。开发环境的端口映射、调试开关和生产不一样但 90% 的内容是共用的。硬复制两份改一处忘一处早晚出事。正确姿势是基础文件 差异化叠加。# base.yml 放通用定义override 只写差异 docker compose -f base.yml -f prod.yml up -d叠加规则要记清楚标量值如image后者覆盖前者列表如ports默认是追加而不是替换映射如environment按 key 合并。这个列表追加的行为坑过很多人——以为覆盖端口映射结果两套都生效端口冲突起不来。变量来源的优先级也值得记住从高到低大致是docker compose命令行-e传入 当前 shell 环境变量 .env文件 文件内默认值。.env放在执行目录不是 Compose 文件所在目录这一点经常搞混。团队里我一般要求把.env加进.gitignore同时提交一份.env.example说明每个变量什么含义、什么范围合法。注意敏感信息别直接写进 Compose 文件然后提交。environment里的明文密码会出现在镜像层的docker inspect输出里任何能执行docker inspect的人都能看到。保险起见用文件挂载或者专门的密钥管理方式把凭据从 Compose 文件里彻底摘出去。6. 配置文件引发的典型故障与排查实录6.1 启动失败的定位顺序服务起不来先别急着重装。固定按这个顺序看八成问题一轮就能定位。第一步systemctl status docker --no-pager -l看退出码和最后几行日志。JSON 语法错误的报错会直接指出行号非常明确。第二步journalctl -u docker -n 100 --no-pager翻最近一百行。这里能看到dockerd的完整启动过程包括读到了哪个配置文件、哪个字段解析失败。第三步如果日志里出现同时以 flag 和配置文件指定这类字样去查 drop-in 目录和/etc/default/docker把冲突的参数删掉。第四步如果报的是存储驱动或者路径相关错误检查>{ data-root: /data/docker, storage-driver: overlay2, log-driver: json-file, log-opts: { max-size: 100m, max-file: 3 }, exec-opts: [native.cgroupdriversystemd], default-address-pools: [ { base: 10.201.0.0/16, size: 24 } ], default-ulimits: { nofile: { Name: nofile, Hard: 65536, Soft: 65536 } }, live-restore: true, max-concurrent-downloads: 6, max-concurrent-uploads: 6 }这中间每个字段都是被具体问题逼出来的>

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑