news 2026/10/26 3:44:50

Ubuntu 14.04 上基于 Docker 与 Compose 搭建 CI 测试环境实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Ubuntu 14.04 上基于 Docker 与 Compose 搭建 CI 测试环境实战

1. 为什么在 Ubuntu 14.04 上折腾这套环境

Ubuntu 14.04 这个版本放到今天来看确实有点年头了,内核是 3.13 系列,默认的 Docker 源里能装到的版本也停留在比较早期的阶段。但现实情况是,不少团队手里还压着一批跑着 14.04 的构建机或者测试机,硬件没坏、机器还能用,直接换系统成本太高,于是就得想办法在这个老底子上把持续集成测试环境搭起来。我这次要聊的,就是怎么用 Docker 加 Docker Compose 在这类机器上做一套能跑起来的 CI 测试环境。

先说清楚这套东西解决什么问题。传统的 CI 测试环境有个老毛病:测试依赖的服务(数据库、缓存、消息队列)都装在宿主机上,版本一升级、配置一改动,环境就漂移了,今天能过的测试明天莫名其妙挂掉,排查半天发现是某个服务的配置文件被人动过。用 Docker 把每个依赖服务装进独立容器,用 Docker Compose 把整套服务编排起来,测试跑之前一条命令拉起、跑完一条命令销毁,环境就是干净的、可复现的。这个思路在 14.04 上同样成立,只是工具版本和踩坑点跟新系统不太一样。

适合谁来参考这篇内容?如果你手上有 Ubuntu 14.04 的机器,需要给它配一套能自动跑测试的环境,或者你正在维护一套老系统的 CI 流水线,那这篇会比较对路。如果你用的是更新的系统,思路可以借鉴,但具体的版本号和命令得按新系统调整。下面我会从环境准备、Docker 安装的版本选择、Compose 编排设计、测试脚本接入、以及实际跑起来之后遇到的各种问题,一步步展开。

2. 老系统上的 Docker 安装:版本选择比命令本身更重要

2.1 先搞清楚 14.04 能装到什么版本

在 Ubuntu 14.04 上装 Docker,第一个要面对的现实是:官方 apt 源里能拿到的版本比较老。14.04 的代号是 trusty,Docker 官方为它维护的软件包在后期基本停在了 1.12 到 1.13 这个区间附近。这意味着你不能指望用上什么多阶段构建、BuildKit 之类的新特性,写 Dockerfile 的时候得按老版本的语法来。

我的建议是不要用apt-get install docker.io装 Ubuntu 仓库里那个版本,那个版本更老,而且和官方源的包容易冲突。正确做法是先把官方源加进来,锁定一个明确的小版本号安装,避免哪天源里更新了导致构建机行为变化。具体操作是先装依赖:

sudo apt-get update sudo apt-get install -y apt-transport-https ca-certificates curl software-properties-common

然后导入官方源的密钥并添加源地址。这里要注意,14.04 的源地址路径和后来的系统不一样,得用对应 trusty 的路径。添加完之后执行apt-get update,再用apt-cache madison docker-engine看一下有哪些可用版本。看到版本列表之后,别急着装最新的,挑一个团队里已经在用的、验证过的版本,比如 1.12.x 系列里的某个具体版本,用apt-get install docker-engine=版本号的方式精确安装。

提示:锁定版本这一步在老系统上尤其重要。老系统的内核和库都比较旧,某些 Docker 小版本在特定内核上会有兼容性问题,装之前最好在测试机上先验证一遍。

2.2 内核参数和存储驱动的调整

装完 Docker 之后,别急着跑容器,先检查两件事。第一是内核的 cgroup 支持,14.04 默认用的是 cgroup v1,这个没问题,但要确认memory和swap这两个 cgroup 子系统是挂载了的,否则容器内存限制会失效。用cat /proc/cgroups看一眼,如果 memory 那一行的 enabled 是 0,就得在启动参数里加上cgroup_enable=memory swapaccount=1,然后更新 grub 重启。

第二是存储驱动。14.04 上默认可能是 aufs,这个驱动在老内核上稳定性一般,容器多了之后容易出现各种奇怪的文件系统错误。我一般会换成 devicemapper 的 direct-lvm 模式,虽然配置麻烦一点,但稳定性好很多。配置方法是给 Docker 单独分一块盘或者一个逻辑卷,然后在/etc/default/docker里加上--storage-driver=devicemapper和对应的--storage-opt参数。如果机器上没有多余的盘,用 loop-lvm 也能凑合,但生产环境不建议,性能和稳定性都差一些。

改完配置之后重启 Docker 服务,用docker info确认存储驱动和 cgroup 都生效了。这一步看起来琐碎,但它是后面所有操作的地基,地基不稳后面全是坑。

2.3 给非 root 用户配好权限

CI 环境里跑测试的通常不是 root 用户,所以得把执行测试的用户加进 docker 组,让它能直接操作 Docker。命令很简单:

sudo usermod -aG docker $USER

执行完要重新登录才生效。这里有个细节:加进 docker 组等于给了这个用户接近 root 的权限,因为容器可以挂载宿主机目录。在 CI 机器上这通常可以接受,但如果这台机器上还有别的敏感数据,得评估一下风险。我的做法是给 CI 单独建一个专用用户,只给它 docker 组权限,不给他 sudo,这样即使测试脚本出问题,影响范围也可控。

3. Docker Compose 的安装与编排文件设计

3.1 Compose 版本要和 Docker 版本匹配

Docker Compose 在老系统上装的时候,版本匹配是个容易被忽略的点。Compose 的文件格式有版本之分,v2 和 v3 的语法差异不小,而 Compose 二进制本身的版本又决定了它支持哪些文件格式。在 14.04 这种老环境里,我一般用 Compose 1.9 到 1.13 这个区间的版本,对应的文件格式用 version 2。别贪新用 v3 格式,v3 里有些字段在老版本 Compose 上不认,会直接报错。

安装方式推荐直接下二进制放到/usr/local/bin下,比用 pip 装省事,也不容易和系统 Python 的依赖打架:

sudo curl -L "https://github.com/docker/compose/releases/download/1.13.0/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose sudo chmod +x /usr/local/bin/docker-compose

装完用docker-compose --version确认一下。如果下载慢或者网络环境特殊,也可以从内网的文件服务器上取预先下好的二进制,这在企业内网里更常见。

3.2 编排文件里该放哪些服务

一套典型的 CI 测试环境,编排文件里通常包含这几类服务:被测应用本身、数据库、缓存、以及可能的消息中间件。我拿一个模拟项目举例,假设被测的是一个 Web 应用,依赖 MySQL 和 Redis。编排文件大概长这样:

version: '2' services: db: image: mysql:5.6 environment: MYSQL_ROOT_PASSWORD: testpass MYSQL_DATABASE: testdb volumes: - ./init.sql:/docker-entrypoint-initdb.d/init.sql cache: image: redis:3.2 app: build: . depends_on: - db - cache environment: DB_HOST: db REDIS_HOST: cache command: /bin/sh -c "wait-for-it.sh db:3306 && run-tests.sh"

这里有几个设计上的考量值得说清楚。第一,数据库用volumes挂一个初始化 SQL 进去,容器第一次启动时会自动执行,这样每次拉起环境数据库结构都是确定的,不用手动灌数据。第二,depends_on只能保证启动顺序,不能保证服务真的就绪,所以 app 服务的启动命令里加了一个等待脚本,轮询数据库端口通了再跑测试。这个等待脚本在老版本 Compose 里没有内置的健康检查依赖,只能自己写,虽然土但管用。

第三,镜像版本全部写死,不用 latest。CI 环境最忌讳的就是镜像悄悄更新导致测试结果变化,写死版本号是保证可复现的基本功。MySQL 用 5.6、Redis 用 3.2,都是和 14.04 时代匹配的稳定版本,用太新的镜像反而可能因为 glibc 之类的问题跑不起来。

3.3 网络和端口的管理策略

Compose 默认会给项目创建一个独立网络,服务之间用服务名互相访问,这个机制在老版本里也有,很好用。端口映射方面,我的原则是:只有需要从宿主机访问的服务才映射端口,内部服务一律不映射。比如数据库和缓存只在容器网络内部被 app 访问,就不写ports,避免和宿主机上已有的服务抢端口。app 如果需要暴露测试报告页面,再映射一个高位端口。

这样做的好处是多个 CI 任务并行跑的时候不会端口冲突。如果确实需要并行跑多套环境,可以在docker-compose up的时候用-p参数指定不同的项目名,Compose 会为每个项目创建独立的网络和容器,互不干扰。这个技巧在多分支同时构建的场景里特别有用。

4. 把测试流程接进这套环境

4.1 测试脚本怎么在容器里跑

测试代码放在哪、怎么跑,有两种常见做法。一种是把测试代码打进 app 镜像,容器启动直接执行测试命令;另一种是把代码目录挂载进容器,镜像只提供运行环境。我倾向于第二种,因为 CI 里代码是每次构建时拉下来的,挂载进去能避免每次改代码都重新构建镜像,速度快很多。编排文件里给 app 服务加一行:

volumes: - ./:/app working_dir: /app

这样容器里的/app就是宿主机的代码目录,测试脚本改了立刻生效。不过要注意文件权限问题,容器里的用户和宿主机用户的 UID 可能不一致,挂载进去的文件可能出现权限错误。解决办法是在构建镜像时创建一个 UID 和宿主机 CI 用户一致的普通用户,用这个用户跑测试。这个细节在老环境里尤其容易踩,因为很多基础镜像默认用 root 跑,挂载出来的文件属主就乱了。

4.2 退出码和结果收集

CI 环境判断测试成功失败,靠的是命令的退出码。docker-compose up默认在前台跑,测试结束容器退出,Compose 会返回一个退出码,但这个退出码不一定等于测试脚本的退出码。更可靠的做法是用docker-compose run单独跑测试服务:

docker-compose run --rm app run-tests.sh

--rm保证跑完自动清理容器,run的退出码就是容器内命令的退出码,测试失败时 CI 能正确感知。测试报告、覆盖率文件这些产物,通过挂载目录直接写到宿主机上,跑完从宿主机取就行,不用额外从容器里拷。

4.3 清理和隔离

每次测试跑完,环境要清理干净,否则残留的容器和数据卷会越积越多,磁盘迟早爆掉。清理命令是:

docker-compose down -v

-v会把数据卷一起删掉,保证下次启动是全新的数据库。这里有个坑:如果测试中途失败,脚本可能没走到清理那一步,残留的容器会占着端口和名字,下次跑就起不来。所以 CI 脚本里要用 trap 或者 try-finally 的机制,保证无论测试成功失败都执行清理。我一般会在 CI 的构建脚本里把docker-compose down -v放在 finally 块里,这是血泪教训换来的习惯。

5. 实际跑起来之后遇到的几个典型问题

5.1 镜像拉取慢和超时

老机器上第一个拦路虎往往是拉镜像。14.04 时代的网络环境和现在不一样,从公共仓库拉镜像经常超时。解决办法是在内网搭一个镜像缓存,或者提前把需要的镜像docker save成 tar 包,在构建机上docker load进去。CI 环境里我倾向于后者,把基础镜像预先加载到机器上,编排文件里用的镜像都从本地找,不依赖网络。这样构建速度稳定,也不会因为网络抖动导致流水线随机失败。

5.2 容器内时间不同步

这个坑比较隐蔽。14.04 的宿主机如果时间同步没配好,容器内的时间和宿主机可能不一致,导致测试里涉及时间戳的断言随机失败。排查的时候看日志发现时间对不上,才意识到是宿主机的问题。解决办法是确保宿主机装了时间同步服务并且正常运行,容器默认继承宿主机时间,宿主机准了容器就准了。如果宿主机时间没法改,可以在容器启动时挂载宿主机的/etc/localtime,至少保证时区一致。

5.3 磁盘空间被镜像和卷吃光

Docker 用久了磁盘空间会悄悄被吃掉,尤其是 devicemapper 的 loop-lvm 模式,默认的池子很小,跑几次构建就满了。表现是构建时报 "no space left on device",但df -h看根分区还有空间。这时候要查的是 Docker 自己的存储池,用docker info看 Data Space 的剩余量。解决办法要么是扩大池子,要么定期清理不用的镜像和卷:

docker system prune -a

不过老版本 Docker 可能没有system prune这个命令,那就得用docker rmi和docker volume rm手动清。我一般会在 CI 机器上加一个定时任务,每天凌晨清理一次悬空镜像和停止的容器,防患于未然。

5.4 Compose 文件格式的兼容性报错

前面提过版本匹配的问题,实际踩的时候表现是docker-compose up报 "Unsupported config option for services" 之类的错误。原因通常是文件里用了当前 Compose 版本不认识的字段,比如在 version 2 的文件里写了 v3 才有的deploy配置。排查方法是先用docker-compose config校验一下文件,它会指出哪一行有问题。养成写完编排文件先跑一遍config的习惯,能省掉很多启动时的困惑。

6. 几个让环境更稳的实操习惯

第一,编排文件里的所有镜像都写明确版本,不用 latest,这一点前面说过,但值得再强调一次,它是可复现性的底线。第二,把等待服务就绪的逻辑做成通用脚本,不要在每个服务的启动命令里重复写,维护起来更省心。第三,CI 脚本里清理动作一定要放在异常也能执行到的位置,别让残留容器拖垮后续构建。第四,定期检查 Docker 存储池的剩余空间,老机器上这个特别容易出问题。第五,测试用的数据初始化脚本要纳入版本管理,和代码一起走,保证任何一次构建用的初始数据都是一致的。

这套环境在 14.04 上跑起来之后,日常维护的工作量其实不大,主要就是盯着磁盘和镜像版本。真正花时间的是第一次搭建时的版本选型和踩坑排查,把这几步走稳了,后面就是一条命令拉起、一条命令销毁的顺畅流程。我在几台老构建机上用这套方案跑了挺长时间,稳定性比预想的好,关键就在于把版本锁死、把清理做扎实、把等待逻辑写对,剩下的交给 Docker 和 Compose 本身就行。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/26 3:44:17

Cesium三维GIS可视化在‘一带一路‘数据整合中的应用

1. 项目背景与核心价值十年前我第一次接触地理信息系统时,就被三维可视化技术深深吸引。如今基于Web的三维GIS平台中,Cesium凭借其开源特性和强大的三维渲染能力,已成为行业标杆。这个项目要解决的,正是如何利用Cesium平台整合&qu…

作者头像 李华
网站建设 2026/10/26 3:44:16

EasyAnimate 工程化实战:消费级显卡跑通类SORA文生视频

文生视频这个赛道,从去年到现在,几乎每隔几周就有新模型冒出来。但真正能让普通开发者在一台消费级显卡上跑起来的,其实没几个。EasyAnimate 是我最近花了不少时间折腾的一个项目,它定位很明确——把类 SORA 那套文生视频能力&…

作者头像 李华
网站建设 2026/10/26 3:43:42

大模型作为数学推理编排器:从问题分解到形式化验证

1. 项目概述:当大模型真正“思考”数学问题时,它在做什么?“OpenAI 模型解决重要数学问题”——这个标题乍看像新闻通稿,但作为一线持续跟踪大模型在科学计算领域落地的从业者,我更愿意把它理解为一个信号:…

作者头像 李华
网站建设 2026/10/26 3:43:20

RabbitMQ核心架构与五种消息模型实践指南

1. RabbitMQ 核心架构解析RabbitMQ作为AMQP协议的标杆实现,其架构设计体现了消息中间件的经典范式。核心组件包括:Broker:消息代理服务器,负责接收、路由和投递消息Virtual Host:虚拟隔离环境,类似命名空间…

作者头像 李华
网站建设 2026/10/26 3:43:15

Docker+Spring Boot+Redis+RabbitMQ部署BI看板

1. 这套四件套到底在解决什么问题先把话说在前面:Docker Spring Boot Redis RabbitMQ 这个组合,很多人第一眼看到会觉得是"堆技术栈",尤其是刚接手一个数据看板类项目的时候,很容易产生一种错觉——是不是把能加的中…

作者头像 李华
网站建设 2026/10/26 3:43:15

Kali Linux 2026.1新特性与渗透测试优化指南

1. Kali Linux 2026.1版本深度解析Kali Linux作为渗透测试和安全审计领域的标杆发行版,其2026.1版本的发布无疑给安全从业者带来了新的工具链和工作环境。这次更新最引人注目的变化当属内核版本升级至Linux 6.18,这不仅仅是数字上的变化,更意…

作者头像 李华