ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Docker Compose文件属性:version与name的深度解析

Docker Compose文件属性:version与name的深度解析 有阵子没整理 docker-compose 相关的东西了。这次开个小系列叫docker-compose 文件属性准备把配置文件里的各种顶层元素逐个掰开讲一遍。第一篇先从最容易被误解的两个说起version和name。起因是上周帮朋友排查一个老项目。他的docker-compose.yml顶部还写着version: 3.8在近期的 Docker 环境里执行docker compose up终端弹出一行 warning说 version 属性已经过时、会被忽略。他有点慌问我到底该不该改。我把整个文件看完之后发现真正埋雷的还真不是 version而是项目名——他有两个服务目录都叫 app第二个项目启动时网络冲突不断。你看这俩看似不起眼的顶部字段一个管格式兼容一个管资源命名出起问题来都是连环坑。这篇文章就把version和name掰开揉碎讲清楚它们各自解决什么问题、新旧版本之间的差异、YAML 解析里那些让人抓狂的细节以及一套可以直接照抄的推荐写法。1. 先说结论version和name如今的真实处境1.1 version从必填退化成冗余很多新手看到老教程里写version: 3又看到新官方文档里坚决不写第一反应是懵的。要理解这件事得先梳理一下 Compose 文件的演进过程。早期的 docker-compose现在常叫 Compose V1也就是docker-compose这个 Python 写的老二进制在 2015 年左右用过一种格式 1的文件那会儿文件里压根没有 version 字段顶上直接写 services、build、ports 就完事。后来 Docker Engine 的网络能力升级了比如networks、volumes这些独立声明块出现了Compose 文件需要一种方式告诉解析器我这份文件按哪套规则理解。于是 version 字段应运而生version: 2代表支持独立网络声明version: 3代表支持 deploy、configs、secrets 等面向 Swarm 的功能。换句话说在 Compose V1 时代version 是解析器决定启不启用某些语法的开关写错真的会拒绝启动。转折点在 2019 到 2020 年左右。Docker 开始推广 Compose Specification也就是把 2.x 和 3.x 的差异合并成一份功能全集不再靠版本号区分能力。Docker Compose V2也就是现在的docker compose插件Go 重写不再读取 version 字段来决定行为。只要文件内容符合规范它就能解析。官方为了避免误导干脆把 version 标记为 obsolete看见了就警告但不会因为你写了就多给你什么能力。所以结论很直接新项目不要写 version它没有任何功能增益。老项目里那行 version 留着不影响启动但会持续输出警告。唯一的例外是如果你还在用老版 Python 写的docker-composeV1或者你的 CI 里固定了老镜像里的 docker-compose 二进制那就得继续沿用老写法。这一点后文还会展开。1.2 name是后补的项目命名入口name字段的来头比 version 晚得多。在 Compose Specification 正式推出之前项目名只能通过三种方式指定启动目录的文件夹名、-p/--project-name命令行参数、COMPOSE_PROJECT_NAME环境变量。目录名作为默认值看起来很省事但实际很坑同一份代码在两个目录里各拉一遍项目名就可能不一样CI 里目录名经常是workspace、app-1这种流水线随机产物资源名也跟着飘。name顶层字段就是在这个背景下加入规范的。它的作用是把项目名这件本来依赖外部环境的事固化到文件本身。只要文件里写了name: order-system不管这份 yml 放在哪个路径下、在谁的电脑上执行项目名都是一致的。这个字段对多环境部署和团队协作的价值非常大后文我会用一个专门的小节拉通讲项目名的完整逻辑。1.3 顶层元素全家福既然这个系列叫docker-compose 文件属性我先把 Compose 文件顶层的完整元素清单摆出来方便后面几篇对号入座顶层元素状态一句话作用services必填定义服务、镜像、依赖关系name可选指定项目名覆盖路径推导version已废弃老格式标识新版忽略networks可选声明自定义网络volumes可选声明命名卷configs可选配置对象常用于 Swarmsecrets可选敏感信息对象include可选引入其他 Compose 文件x-开头字段可选自定义扩展字段便于复用配置这一篇先把 version 和 name 讲透后续文章再逐个拆 services、networks、volumes。理解了顶层字段的职责边界后面看 services 里那些琐碎配置会轻松很多。2. version字段的细节与YAML引号陷阱2.1 该写什么值什么时候必须写如果你确定要写 version那到底写多少合适老教程里最常见的值有2、2.4、3、3.8。我的建议是如果非写不可一律写3.8这种带引号的字符串别裸写数字。原因在下一节展开这里先把规则记住。什么时候非写不可我归纳下来就两类场景。第一类你还在用 Compose V1 老二进制。很多生产环境的服务器还装着docker-compose1.29.x这些环境解析文件时依然认 version。你把 version 删了它不一定报错但某些老版本会把无 version 的文件当成格式 1处理导致 networks、volumes 等声明失效服务起得来但网络完全不对。这种问题极其隐蔽。第二类你依赖docker stack deploy部署到 Swarm。stack 命令要求 Compose 文件符合 3.x 规范老版本甚至明确要求文件里有 version。虽然新版本 Docker 对这种情况宽容了一些但为了保险走 Swarm 这条链路的仓库通常都会保留version: 3.8。这里还有个容易被忽略的判断点docker compose是插件版V2docker-compose是独立二进制V1两者在命令行里的横杠不一样。你可以在终端分别跑一下docker compose version和docker-compose version看清楚自己手上到底是什么工具。很多明明按教程写的却起不来的案例根源就是教程面向 V2你的环境却在跑 V1或者反过来。2.2 version: 3.10为什么非加引号不可这是全篇我最想让你记住的一个细节。YAML 在解析version: 3.10时会把它当成浮点数浮点数 3.10 在计算机里就是 3.1。你原本想表达版本 3.10解析器拿到手的却是 3.1。在老版 Compose V1 里比对版本号时3.1 显然低于某些功能的门槛于是就会冒出莫名其妙的 Version in ... is unsupported 或者更诡异的配置不支持错误。同理version: 3.8不写引号解析成浮点数 3.8某些 Python 老版本中对浮点数做字符串拼接时还可能变成3.8看起来没毛病但version: 3.10是绕不开的坑不写引号一定出问题。更别提version: 2这种写法会被解析成整数 2老格式判断逻辑直接把它归到无 version 的分类里。所以规则只有一条写版本号就用双引号包住version: 3.8。别依赖 YAML 的自动类型推断也别信某些编辑器帮你去掉引号的智能修正。我实际见过有人把别人配好的 compose 文件复制到编辑器里引号被美化掉了服务起不来还以为是环境坏了排查了半天。顺带说一句YAML 里这种类型陷阱不只在 version 字段出现。端口映射里ports: 8080:80这种写法如果被解析成数值某些严格模式下也会报错所以官方示例都写成8080:80。只是 version 是最容易踩的那颗雷因为它出现在文件最顶部一错全错。2.3 关于version的常见报错与误解网上搜 version 相关的报错很容易混进来一些看起来像、其实完全不相干的错。比如invalidversionspecerror: invalid version spec: 2.7这其实是 pip/git 工具链的报错跟 docker-compose 半毛钱关系没有。再比如docker-compose: error while loading shared libraries那一串是 Compose V1 二进制依赖的系统库缺失也不是 version 字段的问题。我建议大家查资料时先看报错前缀和上下文别被搜索引擎的联想带跑。真正的 Compose version 报错主要有三种新版docker compose遇到文件里写了 version输出the attribute version is obsolete, it will be ignored, please remove it to avoid potential confusion。这只是警告不影响启动但你要是用 CI 里的命令做了输出匹配这行字可能让小助手脚本判断失败。老版docker-compose遇到一个它不认识的版本号输出Version in ./docker-compose.yml is unsupported. You might be seeing this error because youre using the wrong Compose file version...。这种就得改成它认得的3.8之类或者干脆升级工具。手滑写了version: 4之类的未来版本号新版目前会忽略老版大概率直接拒绝。所以我在排查这类问题时的套路是先确认自己用的是 V1 还是 V2再决定要不要管 version。V2 环境里最干脆的解法就是删掉那行 version世界清净。3. name字段项目名从哪来、影响谁、优先级怎么排3.1 项目名决定哪些资源的前缀项目名这个概念很多人用了很久 docker-compose 也没搞清楚。简单说每套 compose 文件启动后创建的容器、网络、命名卷都会以项目名为前缀用来区分不同项目防止资源互相打架。举个例子一个项目名叫order-system的 compose 文件里面定义了一个服务web一个命名卷db-data默认网络叫default。执行docker compose up -d之后你会在docker ps里看到容器名是order-system-web-1网络叫order-system_default卷叫order-system_db-data。这一整套命名规则前缀全都来自项目名。为什么要强调这个因为项目名一旦变化所有资源名跟着变。以前你用docker compose down能清理干净网络和卷但如果中途改过项目名旧网络和旧卷可能残留在 Docker 里不被新项目引用时间一长就是一批孤儿资源。更麻烦的是如果两个项目都叫app它们的容器名和默认网络名完全相同第二个项目启动时要么盯着 container name conflict 报错要么网络串台服务之间可能访问到完全错误的副本。所以name字段真正的价值是把项目名从目录名随机变量变成声明式固定值。有了它团队里每个人、每台 CI 机器启动出来的资源命名都一致排查问题少一大半。3.2 项目名的四级推导顺序官方文档里把项目名的确定顺序写得很清楚从高到低优先级如下命令行参数-p/--project-name比如docker compose -p myapp up -d环境变量COMPOSE_PROJECT_NAMECompose 文件里的顶层name字段从文件路径推断出的默认值通常是当前目录的 basename这个顺序里最反直觉的一点是文件里的name字段并不是最高优先级它会被COMPOSE_PROJECT_NAME环境变量覆盖更会被-p参数压住。我见过不少人在文件里写了name: order-system然后发现实际项目名不是这个一脸懵。原因多半是他们在.env或 shell 里设置了COMPOSE_PROJECT_NAME而环境变量优先级更高。第 4 级的路径推断也有一些隐藏规则。如果当前目录名全小写且只含字母、数字、下划线、连字符直接用目录名含大写字母会被强制转成小写目录名里要是有点号、空格、中文这类非法字符Docker 会往上找父目录名父目录也不合法就退化成基于目录路径 hash 生成的随机名。Windows 用户尤其容易踩到这个目录叫my.app(2024)结果项目名变成一串 hash跟预期完全不搭边。我自己的实践准则是项目名这辈子只从一处来。要么统一用-p参数要么统一用name字段绝不同时依赖多个来源。否则今天这台机器被环境变量干扰、明天那台机器目录名变了排查的时候你根本想不起来项目名是哪儿冒出来的。3.3 name字段的命名规则与跨平台问题name字段的值不是随便写的Compose Specification 要求项目名匹配一个严格模式以小写字母或数字开头后面只能跟小写字母、数字、下划线、连字符。用正则表达大致就是[a-z0-9][a-z0-9_-]*。实际使用中我额外总结了三条经验第一别在项目名里用点号。虽然某些版本的解析器允许点号但 Docker 底层很多资源命名规则对点号并不友好Windows 环境下尤其容易出问题。为了跨平台一致全用下划线和连字符最稳。第二project 名里别用大写。就算解析器不拦你Docker 在创建资源时也会强制小写化你以为的项目名和实际项目名对不上日志里全是这种 trailing 式困惑。第三name 字段可以引用环境变量比如name: ${PROJECT_NAME:-order-system}这在 CI 里很有用。但要注意插值出来的结果也必须满足命名规则否则启动时会在解析阶段直接报错。还有一个很容易被忽视的点网络和卷也可以在各自声明里用name:属性显式指定最终名称。一旦显式指定了项目名前缀就不参与该资源的命名了。比如你写networks: default: name: order-net那么网络名就是order-net而不是order-system_default。如果你在多个项目里都声明了同一个显式网络名这些资源会共享同一个网络这是一种跨项目联通的常见手法。不过这就是后续 networks 篇的正文内容了这里先留个线索。4. 实战配置参考与高频报错排查4.1 推荐的现代写法与兼容旧环境写法先给出一份可以直接照抄的新项目模板# 新项目推荐写法不写version明确name name: blog-platform services: web: image: nginx:1.25 ports: - 8080:80 db: image: mysql:8.0 volumes: - db-data:/var/lib/mysql volumes: db-data:这份文件里我特意保留了name: blog-platform而不是依赖目录名。这么写之后不管你把仓库 clone 到blog-platform还是project-copy目录启动出来的资源都统一叫blog-platform-web-1、blog-platform_default。如果你的环境不幸还停留在 Compose V1 老二进制强行写name字段会直接报错Additional properties are not allowed (name was unexpected)。这种场景下我建议你把项目名收敛到环境变量里文件里什么都不写export COMPOSE_PROJECT_NAMEblog-platform docker-compose -f docker-compose.yml up -d然后在文件顶部保留version: 3.8。这套组合拳能兼容绝大多数老环境代价是项目名不写在文件里有漂移风险。所以如果你能推动工具链升级还是尽早迁到 V2。4.2 多compose文件时name怎么处理真实项目里很少只有一个docker-compose.yml通常还有docker-compose.override.yml用于本地开发或者按环境拆出docker-compose.prod.yml。多文件合并时name 字段很容易引起混乱。我的建议极其简单name 只在主文件docker-compose.yml里声明一次覆盖文件里坚决不写。因为多个文件合并时services、volumes、networks 这些字段是深度合并的但顶层name属于标量字段覆盖行为在不同版本的实现里表现并不直观。与其赌解析器的行为不如靠约定消除隐患。另外提醒一句如果你在 CI 脚本里手动拼过网络名比如test_default迁移到name字段之后这个拼写大概率就失效了。项目名一变所有依赖旧资源名的脚本、监控、清理任务都要同步检查。我遇到过最典型的错误是脚本里写死了network: app_default但新的name字段把项目名改成了别的启动时报network app_default declared as external, but could not be found。翻日志时如果看到这句先怀疑项目名漂移别急着去重建网络。4.3 高频报错速查表与docker compose config验证最后给一份我在实战中总结的报错速查表遇到类似问题可以按图索骥报错或提示常见根因建议处理the attribute version is obsolete新版 V2 遇到 version 字段删掉 version 或忽略警告Version in ... is unsupported老版 V1 遇到不支持的版本号改成3.8或升级工具Additional properties are not allowed (name was unexpected)老版 V1 不支持 name 字段升级 V2 或改用COMPOSE_PROJECT_NAMEproject name ... contains invalid charactersname 或目录名含非法字符按[a-z0-9][a-z0-9_-]*规则改名network {project}_default declared as external, but could not be found项目名或网络显式名称变了确认 project name手动清理旧网络container name conflict多个项目容器名前缀相同用固定 name 区分项目或检查 container_name排查这类问题我有个百试不爽的习惯先docker compose config再docker compose up。docker compose config会把解析后的最终配置完整打印出来包括 project name、所有变量的插值结果、网络的最终名称。你在文件里写了name: blog-platform看到的却是另一个项目名那立刻就能断定有环境变量或-p参数在高层压着你。同理docker compose ls可以列出当前机器上所有 active 项目一眼看出谁的项目名串了。个人体会是这类顶部元素问题看着小但因为它决定了整套资源的命名边界和格式兼容性往往是项目里最先爆雷的地方。我拿到任何不熟悉的 compose 文件第一件事永远是跑docker compose config确认解析出的项目名、网络名、卷名都符合预期再放行启动。这习惯救过我太多次了。
返回列表