CentOS7 安装 Docker Compose:版本选择、二进制部署与排错

发布时间:2026/10/27 13:13:28
CentOS7 安装 Docker Compose:版本选择、二进制部署与排错

1. 为什么CentOS7上装Docker Compose还值得单独说道

CentOS7安装Docker Compose这件事,放在今天看有点像一个老木匠还在用十年前那套刨子——系统老、生态旧,但架不住手上一堆机器还得靠它跑。CentOS7 在2024年6月底走完了官方维护周期,可现实是企业内网、实验室机柜、存量业务服务器里,它依然是主力。原因很实在:内核稳定、软件包依赖关系成熟、运维脚本早就固化,没人愿意为了一个新版本把整套部署链推倒重来。Docker Compose 在这里扮演的角色,就是把“我要跑三个容器还得记住一堆 run 参数”这种苦力活,压缩成一份 yml 文件。它解决的问题很具体:容器之间的编排、依赖顺序、网络互通、数据卷挂载,一条命令拉起一整套服务。

这篇内容写给谁看?三类人。第一类是新接手老机器的运维,需要在CentOS7上把Docker Compose装起来并跑通;第二类是自己搭测试环境的开发者,手里只有一台CentOS7的云主机;第三类是刚学容器编排的新手,想搞明白docker-compose这个命令到底从哪来、为什么有的机器上是docker compose有的是docker-compose。不同基础的人读完,至少能做到两件事:知道该选哪个版本、能把命令装到系统里并验证可用。我不打算讲什么容器编排的宏大叙事,就讲这台老系统上怎么把这件事办成,以及办成之后别踩哪些坑。

2. 装之前先把环境底子摸清楚

2.1 系统版本与内核参数的实际检查手法

动手之前先确认底子,这一步很多人跳过,结果装到一半发现Docker引擎版本对不上。CentOS7 的发行版本差异主要体现在 7.6、7.7、7.9 这几个小版本上,内核都是 3.10 系列。执行cat /etc/redhat-release看一眼,输出类似CentOS Linux release 7.9.2009 (Core)就说明系统是7.9。再看内核,uname -r返回3.10.0-1160这类结果属于正常范围。这里要说明一个常识:Docker Compose 本身是个独立的二进制程序,它不直接依赖内核版本,真正吃内核特性的是它调用的 Docker 引擎。所以内核检查的目的不是给Compose设门槛,而是确认底下的Docker能正常跑。

还有两个参数建议顺手看一眼。sysctl net.ipv4.ip_forward应该返回 1,容器网络转发靠它;返回 0 的话,后面容器之间通信会出莫名其妙的问题,临时改sysctl -w net.ipv4.ip_forward=1,要持久化就写进/etc/sysctl.conf。另外看下selinux状态,getenforce返回 Enforcing 时,容器挂载宿主目录偶尔会被拦,我一般的做法是不关SELinux,而是在挂载时加:z或:Z标签,比直接关掉安全策略稳妥得多。这些检查加起来不到两分钟,但能省掉后面半小时的排查。

2.2 Docker引擎版本与Compose版本的对应关系

这是最容易翻车的地方,必须讲透。Docker Compose 有两代:v1 是独立的docker-compose可执行文件,用Python写的,最后版本是 1.29.2;v2 是 Go 重写的,以 Docker CLI 插件形式存在,命令是docker compose(中间是空格)。两代对 Docker 引擎的要求不一样。v1 的 1.29.2 兼容性极好,Docker 引擎 1.13 以上基本都能跑,CentOS7 上通过 yum 装的 docker-ce 通常是 20.10 或 24.x,都没问题。v2 的 2.x 系列要求 Docker 引擎 19.03 以上,部分新版本还要求 20.10+,装之前docker version看一眼引擎版本,别装完发现插件加载失败。

版本选择的逻辑基于需求而不是追新。如果这套机器上跑的是老项目,yml 文件用的是version: '2'甚至version: '3'的语法,v1 完全够用,还不用改命令习惯。如果是新项目,想用profiles、depends_on的条件语法这些 v2 才完善支持的特性,那就上 v2。我的建议是:存量机器优先 v1 的 1.29.2,新搭环境直接上 v2 的 2.24 附近版本,别卡在中间版本上纠结。

对比项Docker Compose v1Docker Compose v2
命令形式docker-composedocker compose
实现语言PythonGo
末版/常用版本1.29.2(终版)2.20 ~ 2.24
对引擎要求1.13+ 即可19.03+,新版要求 20.10+
安装方式二进制或 pip二进制放 cli-plugins 目录
适合场景存量老项目、语法简单新项目、需要 profiles 等特性

2.3 网络可达性与软件源的前置判断

CentOS7 官方源已经停更,yum 装东西经常卡住,这一步得提前想好。装 Docker Compose 的二进制包不需要 yum,但从官方地址下载二进制文件需要网络能通。先测试一下:curl -I https://github.com看看返回码,如果超时,就得提前准备一个能访问的下载途径,比如把二进制文件在别的机器上下好再 scp 传过来。我见过不少人卡在下载环节,以为是安装步骤错了,其实是网络根本连不上。

另外提醒一句,如果这台机器已经在跑 Docker 且配置了镜像仓库加速地址,那容器拉镜像会快很多,但这跟 Compose 的安装是两码事,别混在一起。Compose 的二进制文件只能从发布地址拿,加速地址对它没用。所以下载前先确认拿文件的路径是通的,别等到执行到一半才发现文件是0字节。一个实用的检查命令是curl -L -o /dev/null -w "%{http_code} %{size_download}\n" 下载地址,能看到状态码和实际下载字节数,比盲目执行install脚本稳。

3. 三种安装路子的取舍与代价

3.1 二进制包安装:我认为最省心的方式

官方推荐的思路就是下二进制文件、赋执行权限、丢进/usr/local/bin,三步完事。为什么我偏爱这个方式?原因有三。第一,不引入Python环境依赖,CentOS7 自带的 Python 2.7 早就不维护了,pip 装东西经常报各种依赖冲突,二进制方式绕开了整个 Python 生态。第二,版本可控,想装哪个版本下哪个版本,升级就是替换文件,回滚就是换回旧文件,没有包管理器那套依赖解析的干扰。第三,跟系统隔离性好,一个文件放在那里,不污染 yum 数据库,出了问题删掉就行。

代价也有,就是不享受 yum 的自动化管理,版本更新得手动做。但对于一台老系统上的工具来说,这恰恰是优点——你不想某天yum update顺手把编排工具升级到一个行为变了的新版本,导致生产环境的 yml 跑不起来。稳定性优先的场景下,手动控制版本是刚需,不是缺点。

3.2 pip方式:为什么不推荐,除非你被逼无奈

网上很多老教程写的是pip install docker-compose,这在CentOS7上能装成功,但有三个隐患。第一个隐患是Python版本,系统自带的是 2.7,pip 装的包默认进 Python 2 的 site-packages,而新版 pip 对老 Python 的支持越来越差,装的时候动不动就报 SSL 模块版本过低。第二个隐患是依赖树,compose 依赖 docker-py、requests、websocket-client 一堆包,pip 装的时候可能顺带把系统原有的 Python 库版本改掉,影响机器上其他 Python 脚本。第三个隐患是路径问题,pip 装的可执行文件默认在/usr/bin/docker-compose,跟 yum 管理的文件位置混在一起,日后排查很乱。

如果用 virtualenv 隔离出独立环境再 pip 装,能规避一部分问题,但这又引入了额外的环境维护成本。所以在有二进制包可选的前提下,pip 方式我基本不碰。只有一种情况会考虑:机器完全离线、手里只有一个 pip 的 whl 包合集,那是没办法的办法。

3.3 yum插件方式与v2的新形态

Docker Compose v2 的官方分发方式之一,是把二进制放进/usr/local/lib/docker/cli-plugins/或~/.docker/cli-plugins/目录,文件名改成docker-compose,Docker CLI 启动时会自动扫描这个目录并注册成子命令。这种方式的好处是跟 Docker 引擎整合得自然,docker compose version直接能查出插件版本。目录选择上有个细节:放/usr/local/lib/docker/cli-plugins/是全系统生效,放~/.docker/cli-plugins/只对当前用户生效。多用户共用的服务器,前者更合适。

有部分发行版仓库里自带docker-compose-plugin这个包,yum 装一下也能用,但 CentOS7 官方源停更后这条路基本走不通,第三方源又不一定靠谱。所以我实际采用的还是手动放二进制的方式,本质跟 v1 的二进制安装一致,只是目标目录和文件名规则不同。认清这一点,就不会被网上各种五花八门的教程绕晕——核心动作永远是“拿到正确的二进制文件,放到正确的位置,赋予正确权限”。

4. 二进制方式安装的完整实操记录

4.1 下载文件与校验完整性

先确定版本和下载地址。v1 走https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-x86_64,v2 走https://github.com/docker/compose/releases/download/v2.24.6/docker-compose-linux-x86_64。注意两代的文件名大小写规则不一样,v1 是Linux-x86_64,v2 是linux-x86_64,直接照抄地址才不会 404。服务器架构必须是 x86_64,uname -m确认一下,如果是 aarch64 就得换对应架构的文件,这点在ARM服务器上很容易忽略。

# 确认架构 uname -m # 下载 v1 版本(示例版本号 1.29.2) curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-Linux-x86_64" \ -o /tmp/docker-compose # 检查下载文件的类型和大小,防止拿到一个 HTML 错误页 file /tmp/docker-compose ls -lh /tmp/docker-compose

file命令的输出应该是ELF 64-bit LSB executable,如果显示HTML document,说明下载地址错了或者被重定向到了一个提示页,这个文件绝对不能往系统里放。文件大小 v1 大约 12MB,v2 大约 50MB 左右,偏差太大就得重新下。

4.2 赋予执行权限并放置到系统路径

# 加执行权限 chmod +x /tmp/docker-compose # 移动到系统可执行目录 sudo mv /tmp/docker-compose /usr/local/bin/docker-compose # 确认路径被 PATH 包含 echo $PATH | tr ':' '\n' | grep -E '^/usr/local/bin$'

第三条命令很多人不做,结果装完了敲命令提示 not found。/usr/local/bin在绝大多数CentOS7的默认 PATH 里,但如果你用过自定义的 profile 或者是在某些精简镜像里,PATH 可能被改过。确认过再往下走。mv用sudo是因为目标目录只有 root 能写,普通用户直接 mv 会 permission denied,这个报错很常见,别以为是自己版本下错了。

另外提一个经验:不要用ln -s软链到/usr/bin图省事。软链在某些场景下(比如被 systemd 服务调用、被其他脚本用绝对路径调度)会出现解析歧义,直接放真实文件最稳。

4.3 安装后的版本验证与联动测试

# v1 的验证方式 docker-compose --version # 期望输出:docker-compose version 1.29.2, build 5becea4c # v2 插件的验证方式 docker compose version # 期望输出:Docker Compose version v2.24.6

验证不能只看版本号,还要确认它能跟 Docker 引擎正常对话。最直接的测试是docker-compose config在一个有 yml 文件的目录里执行,能正常解析就说明功能链路是通的。如果报Cannot connect to the Docker daemon,那是 Docker 服务没起或者当前用户没权限,跟 Compose 本身无关,systemctl status docker查一下服务状态。还有一种隐蔽情况:版本号能打出来,但一执行就报error while loading shared libraries,这是二进制文件跟系统 glibc 版本不匹配,多见于在更新的系统上编译的二进制拿到老系统上跑。CentOS7 的 glibc 是 2.17,下载的二进制版本必须确认支持这个环境,官方发布的 Linux 版本通常做了兼容处理,但如果是从别处拿的非官方构建,就容易踩这个坑。

5. 让第一个Compose项目真正跑起来

5.1 目录结构与yml文件的写法要点

装好工具只是开始,能不能把服务拉起来才见真章。我给测试环境准备一个标准的目录结构,后面所有项目都按这个套路来:

/opt/app-demo/ ├── docker-compose.yml ├── data/ └── logs/

数据目录和日志目录提前建好,yml 里挂载的时候直接指过去,避免容器内写数据把容器层撑大。yml 文件写法上,v1 和 v2 的语法基本互通,但文件头的version字段在 v2 里已经是可选的、官方建议不写。如果你用的是 v1,写version: '3.8'最保险。

services: web: image: nginx:1.24-alpine container_name: demo-web ports: - "8080:80" volumes: - ./data:/usr/share/nginx/html:ro - ./logs:/var/log/nginx restart: unless-stopped depends_on: - cache cache: image: redis:7-alpine container_name: demo-cache command: redis-server --appendonly yes volumes: - ./data/redis:/data restart: unless-stopped

这里有几个参数的选择逻辑值得说。端口映射写8080:80而不是80:80,是为了不跟宿主机已有的 nginx 抢端口,宿主机端口选 8080 这类高位端口,冲突概率低。restart: unless-stopped比always更合适,手动docker-compose stop之后容器不会自己爬起来,便于维护窗口操作。depends_on只保证启动顺序,不保证依赖服务“就绪”,这点很多新手误解,以为写了它 Redis 就一定在 nginx 之前可用,实际是“先启动”,不代表“能用”,后面排查部分会细说。卷挂载加:ro是让 web 容器只读,防止它误改静态资源文件。

5.2 拉起服务与日常命令的实操节奏

cd /opt/app-demo # 前台方式先跑一次,观察启动日志有没有报错 docker-compose up # 确认没问题后,改后台方式跑 docker-compose up -d # 看状态 docker-compose ps # 看某个服务的日志,指定服务名和末尾行数 docker-compose logs --tail=100 web # 配置改动后重建 docker-compose up -d --force-recreate

第一次一定用前台方式跑,这是我踩过坑之后的固定习惯。前台能看到完整的启动输出,镜像拉没拉下来、端口绑没绑上、容器有没有立刻退出,全在眼前。直接-d跑后台,容器启动失败了你只能看ps里一个 Exited 状态,还得再去翻日志,多绕一圈。命令里的--force-recreate也值得记住,改了 yml 里的端口或者环境变量之后,普通up -d有时察觉不到变化,强制重建才生效,这个参数救过我不少次。

5.3 开机自启与资源限制的落地配置

服务器重启后服务自动起来,这件事要在 yml 里解决,不能靠手工补。前面写的restart: unless-stopped就承担这个职责,前提是 Docker 服务本身开机自启,systemctl is-enabled docker确认返回 enabled。两层自启都到位,断电重启后容器才会自动恢复。这一点我专门验证过:只配容器 restart 策略、Docker 服务没设自启,机器重启后容器是起不来的,两件事必须同时成立。

资源限制在单机多服务场景下很重要。加deploy.resources.limits在 v1 里部分生效,v2 的独立部署模式下才完全支持,所以老环境更实用的做法是用mem_limit和cpus这类字段:

cache: image: redis:7-alpine mem_limit: 512m cpus: 0.5

mem_limit: 512m的含义是容器最多用 512MB 内存,超了会被 OOM killer 干掉,这个数值根据服务实际占用往上留 30% 余量来定。Redis 空载很小,但开了持久化之后内存会涨,512MB 对一个测试环境够用。cpus: 0.5限制最多用半个核,防止一个容器把整机 CPU 吃满影响其他服务。这些参数不是越多越好,加之前先想清楚这个服务会不会失控,不会失控的服务加限制反而增加复杂度。

6. 那些年踩过的坑与排查套路

6.1 命令找不到与权限不足的两类报错

docker-compose: command not found排第一。排查顺序是固定三步:which docker-compose看有没有找到,ls -l /usr/local/bin/docker-compose看文件在不在、有没有执行权限,echo $PATH看路径包不包含。三步里任何一步断了都会报这个错。曾经遇到一个案例,文件明明在,权限也是 755,就是敲不出来,最后发现是用户 shell 的 PATH 被一个自定义脚本覆盖了,/usr/local/bin被挤掉了。这种问题不看 PATH 永远找不到原因。

第二类报错是permission denied while trying to connect to the Docker daemon socket。这个跟 Compose 无关,是当前用户没加进 docker 组。解决方式是sudo usermod -aG docker 当前用户名,然后必须重新登录才生效,newgrp docker只能临时在当前终端生效。提醒一句,加进 docker 组等于给了准 root 权限,这个操作在生产机上要有意识,不是随手就做的。

6.2 版本不兼容引发的隐蔽故障

有一类故障最难受:命令能跑,版本号也对,但执行up的时候报Unsupported config option for services或者version is not supported。原因通常是 yml 文件里写的version字段跟工具版本对不上。比如 yml 写version: '3.9',而工具是 1.29.2,这个组合是支持的;但如果 yml 里用了 v2 才有的语法(像profiles),拿 v1 去解析就会直接报错。判断方法很直接:把 yml 里可疑的语法段落注释掉,能跑通就说明是语法支持问题,再对照工具版本文档确认。

还有一种情况是docker compose命令报docker: 'compose' is not a docker command,说明插件目录没被扫描到。检查文件是否在/usr/local/lib/docker/cli-plugins/docker-compose,文件名必须严格是docker-compose,带版本号后缀或者名字拼错都不会被识别。目录权限也看一眼,root 可读可执行即可,不需要给 777,权限过宽反而有安全隐患。

6.3 网络拉取与依赖就绪的疑难速查

镜像拉取超时是高频问题,报错形如net/http: TLS handshake timeout或connection reset by peer。这个跟 Compose 版本无关,是拉镜像的网络链路问题。可行的处理是先单独docker pull nginx:1.24-alpine手动拉一次,看错误是否复现,能复现就是网络层面的问题,跟编排工具无关。手动拉成功后再up,就绕过了 Compose 拉镜像这一步。

另一个高频误区是depends_on的期待落空。写了依赖,容器都起来了,但应用还是报连不上数据库。原因是depends_on只控制启动顺序,不等待服务真正可用。稳妥做法是在应用侧加重试逻辑,或者用健康检查配合条件依赖(v2 才完整支持):

cache: image: redis:7-alpine healthcheck: test: ["CMD", "redis-cli", "ping"] interval: 5s timeout: 3s retries: 5

健康检查跑通之后,depends_on配合condition: service_healthy才能做到“等它真的能用再启动下一个”。这个组合在 v1 里支持有限,用 v1 的话就老老实实在应用里做重试,别指望编排工具兜底。

现象最可能原因处理动作
command not found路径不在 PATH 或文件未放对位置检查/usr/local/bin与 PATH
连接 docker daemon 被拒用户不在 docker 组usermod 加组后重新登录
version is not supportedyml 版本字段与工具不匹配对照版本文档调整 yml
compose 不是 docker 命令插件目录或文件名不对核对 cli-plugins 路径与命名
TLS handshake timeout拉镜像网络链路问题先手动 pull 验证
应用连不上依赖服务depends_on 只保证启动顺序加健康检查或应用侧重试

6.4 升级与回滚的一点点经验

工具升级我坚持一个原则:先备份旧文件再替换。cp /usr/local/bin/docker-compose /usr/local/bin/docker-compose.bak-$(date +%Y%m%d),一条命令的事,出问题时mv回来就是回滚,比重新下载快得多。升级后不要只看--version,一定要拿一个真实的 yml 跑一次config解析和一次up,确认行为没变。我遇到过一次小版本升级后restart策略的默认行为变化,测试环境没验证,上线后才发现容器停止策略跟预期不一致,这种坑只有真正跑一遍才暴露得出来。

另外,老系统的 glibc 版本限制要长期记在心上。下载任何新的二进制工具之前,先看它的发布说明里有没有最低系统要求,CentOS7 的 glibc 2.17 是个硬门槛,新编译的二进制如果链接了更新的库,在这台机器上就是跑不起来,报错还特别隐晦。判断方法:ldd /usr/local/bin/docker-compose看有没有not found的库依赖,有的话就说明这个文件跟这台机器不匹配,赶紧换一个兼容的版本,别在库依赖上死磕。