做测试环境搭建这事儿看着不难但坑是真不少。同一个部署文档在 CentOS 7 上执行得顺顺利利换到 Ubuntu 20.04 上就报错或者反过来亦然——包管理器不同、软件源格式不同、防火墙规则不同、服务管理方式也不同。我见过太多次测试环境“薛定谔的可用”上午跑得好好的用例下午一跑就崩查到最后发现是某台机器的软件版本跟其他机器不一致。后来我下决心把测试环境的部署过程固化下来整理成一套覆盖 CentOS 7 和 Ubuntu 20.04 双版本的完整流程从基础系统配置、软件源替换、开发工具链到 Docker 容器化中间件部署全部脚本化、可复现。这篇手册就是这套流程的完整记录适合需要快速搭出稳定测试环境的人测试工程师、独立开发者、小团队运维或者刚转岗过来被环境问题折磨的新手照着操作就能少踩一大半的坑。1. 项目概述与设计思路1.1 为什么同时覆盖 CentOS 7 与 Ubuntu 20.04先说选型逻辑。目前各团队的在用系统基本被两大阵营瓜分一边是 RHEL 系CentOS 7 是典型代表存量服务器和不少传统项目的集成测试环境都跑在它上面另一边是 Debian 系Ubuntu 20.04 LTS 是长期支持版本企业级支撑到 2025 年之后很多新项目组直接拿它当基准环境。如果团队既要兼容老业务系统又要适配新项目测试环境就必须同时覆盖这两个系统。实测下来一些关键差异集中在这几点包管理器CentOS 7 用 yumUbuntu 20.04 用 apt命令习惯完全不同。软件源配置CentOS 的源文件在 /etc/yum.repos.d/ 下的 .repo 文件Ubuntu 则通过 /etc/apt/sources.list 管理。防火墙CentOS 默认启用的 firewalld 和 Ubuntu 的 ufw 操作方式差异明显。服务管理CentOS 7 使用 systemd 管理服务Ubuntu 20.04 同样使用 systemd这方面相对统一但个别服务的配置路径和默认行为仍有不同。我做这套手册时没有把两个系统拆开成两篇独立的文档而是放在同一套流程中对比推进。原因很简单测试环境不是单机孤立存在的同一个测试任务往往需要在两个系统上分别跑一遍分开写文档会导致执行标准不一致最后配置出来的环境还是对不齐。1.2 测试环境部署的整体分层整个部署过程我拆成了三个层次系统层、工具层、应用服务层。系统层负责基础系统配置比如时区、防火墙、软件源更新、编译依赖安装工具层负责开发与测试必需的软件比如 Git、JDK、Maven、Python应用服务层则负责被测系统依赖的中间件比如 MySQL、Redis这些我统一用 Docker 部署避免在机器上直接装一堆服务导致端口冲突和卸载不干净的问题。在资源规划上测试环境不需要像生产那样追求极高可用性但也不能太寒酸。我一般建议最低配置 4 核 CPU、8GB 内存、100GB 以上磁盘空间。如果跑的是集成测试最好 2 台机器组成最小集群如果是小项目单机测试一台也够用。所有虚拟机在安装完系统之后立刻做一次快照后面随便折腾都不怕。1.3 双系统安装阶段的前置准备安装系统前有几个细节值得提前注意。CentOS 7 的 ISO 体积较大包含的软件包也偏全安装时建议选择“最小化安装”模式不带图形界面Ubuntu 20.04 的 Server 版本默认就是无图形界面安装过程相对简洁。分区方面我习惯把 / 分区独立出来swap 单独分配 2GB 左右/home 如果不需要可以并入根分区避免后续扩容麻烦。安装完成后先不要急着配业务先把两台机器的网络互通测试一遍确认 IP 能通、DNS 能解析再进入下一步。测试环境最忌讳“装了一半发现基础不通”后面排查起来非常浪费时间。2. 系统初始化与基础配置2.1 最小化安装后的统一调整装完系统后第一件事不是装软件而是先把系统底子调平。我通常按这个顺序操作设置主机名、配置时区、关闭不必要的防火墙策略、检查 SELinux 状态。CentOS 7 上默认的 SELinux 状态是 enforcing这在测试环境下有时候会挡住一些调试操作。通过临时关闭或修改配置文件永久关闭后可以减少很多访问权限上的困惑。这里需要说明一点我只是在测试环境这样处理生产环境务必保持 SELinux 开启状态两者场景完全不同不要拿测试环境的配置方案直接套用到生产。Ubuntu 20.04 默认没有开启 SELinux主要注意 ufw 防火墙规则即可。如果测试机之间需要互通端口建议一开始就放开需要的端口范围避免逐个服务去配白名单。时区配置# CentOS 7 与 Ubuntu 20.04 通用 timedatectl set-timezone Asia/Shanghai systemctl enable --now chronyd # CentOS 7 使用 chrony timedatectl set-ntp true # Ubuntu 20.04 使用 systemd-timesyncd注意 CentOS 7 默认安装的 chrony 需要显式启动Ubuntu 20.04 则默认启用时间同步。我有一个习惯是设置完主机名后把两台机器的 /etc/hosts 加上彼此的内网 IP后续用主机名互相访问比记住 IP 方便且不容易连错机器。2.2 软件源替换与基础工具安装默认软件源在国外下载速度慢是普遍痛点。在实际操作中我会把系统源替换为国内开源镜像地址。这一步需要分别处理CentOS 7# 备份原配置 mv /etc/yum.repos.d/CentOS-Base.repo /etc/yum.repos.d/CentOS-Base.repo.bak # 下载对应镜像源配置实际操作中注意匹配系统版本 curl -o /etc/yum.repos.d/CentOS-Base.repo https://mirrors.internal.example.com/centos/7/base.repo yum clean all yum makecacheUbuntu 20.04cp /etc/apt/sources.list /etc/apt/sources.list.bak # 编辑 sources.list将 archive.ubuntu.com 与 security.ubuntu.com 替换为国内镜像地址 sed -i s/archive.ubuntu.com/mirrors.internal.example.com/g /etc/apt/sources.list apt update配置完成后先装一批基础工具保证后续编译和下载操作不卡壳# CentOS 7 yum install -y epel-release yum install -y vim wget curl net-tools unzip zip tree gcc gcc-c make # Ubuntu 20.04 apt install -y vim wget curl net-tools unzip zip tree build-essential我在这一步踩过的坑是CentOS 7 的 EPEL 源非常实用里面有不少默认源没有的软件包尤其后面装 Python 依赖时作用很大所以一定不要漏掉。Ubuntu 的 build-essential 包则一次性把 gcc、g、make 等编译工具都装齐了比逐个安装省心。2.3 测试账号与权限规划测试环境尽量别用 root 直接操作既容易误删文件也不利于权限管理。我习惯创建专用账号useradd tester echo Tester123456 | passwd --stdin tester usermod -aG wheel tester # CentOS 7wheel 组有 sudo 权限 usermod -aG sudo tester # Ubuntu 20.04sudo 组有 sudo 权限然后把 tester 账号的 sudo 配置成免密或者限免比如只允许执行特定命令。测试环境里频繁执行 sudo 操作如果每次都要输密码会让人非常烦躁但全免密又有安全风险。我的折衷方案是tester 账号临时免密 sudo但只能在测试网段内使用防止其他网段的人搭个顺风车。3. 核心工具链与开发环境部署3.1 Git 与代码协作工具链测试环境连 Git 是刚需。CentOS 7 自带版本偏低Ubuntu 20.04 自带的则比较新功能差异主要体现在一些命令和分支操作上。如果统一从源码编译安装可以保证两个环境版本完全一致。我实际使用的是源码编译方式虽然耗时稍长但能确保测试环境与生产环境的 Git 行为一致。编译安装前需要安装依赖库# CentOS 7 yum install -y curl-devel expat-devel gettext-devel openssl-devel perl-devel zlib-devel # Ubuntu 20.04 apt install -y libcurl4-openssl-dev libexpat1-dev gettext libssl-dev perl zlib1g-dev之后从官方源码包构建安装配置 PATH 和 man 路径后验证版本。这一步没必要写死参数,实际执行时按当前最新稳定版即可。装完 Git 顺手配置全局身份信息否则后续拉代码操作时经常出现身份未配置的提示git config --global user.name Tester git config --global user.email testerexample.com git config --global push.default simple3.2 JDK 与 Maven 构建环境测试环境里跑自动化脚本和编译项目JDK 基本绕不开。我以 OpenJDK 11 为例因为目前大部分 Web 项目的最低要求是 JDK 8但 JDK 11 的兼容性更稳很多团队已经迁移到 11 甚至 17测试环境没必要守旧。# CentOS 7 yum install -y java-11-openjdk java-11-openjdk-devel # Ubuntu 20.04 apt install -y openjdk-11-jdk装完后设置 JAVA_HOME 环境变量写入 /etc/profile.d/java.shexport JAVA_HOME/usr/lib/jvm/java-11-openjdk-amd64 export PATH$JAVA_HOME/bin:$PATHCentOS 7 上 java 目录路径不带 amd64 后缀写法稍有不同建议先执行readlink -f $(which java)查看实际安装路径再写进去。Maven 这边我选择手动下载二进制包解压到 /opt 目录然后配置软链接到 /usr/bin 下这样版本完全可控不需要依赖包管理器里可能过旧的版本。同时修改 settings.xml 里的本地仓库路径把默认的 ~/.m2 改为 /data/m2repo避免大量依赖缓存占用用户目录空间。如果项目是前端加后端一体化还需要 Node.js我的做法是使用 nvm 管理版本方便在多个 Node 版本间切换curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash source ~/.bashrc nvm install 16 nvm install 18 nvm alias default 183.3 Python 多版本管理测试环境里 Python 最常见的问题是“这脚本要 py3.8那个工具要 py3.10”直接改系统默认版本容易把依赖库搞乱。我使用 pyenv 做多版本隔离# 安装编译依赖CentOS 7 yum install -y zlib-devel bzip2-devel readline-devel sqlite-devel libffi-devel # Ubuntu 20.04 apt install -y zlib1g-dev libbz2-dev libreadline-dev libsqlite3-dev libffi-dev接着安装 pyenv然后编译指定版本 Pythonpyenv install 3.9.18 pyenv global 3.9.18注意编译过程比较慢要有耐心。如果编译失败绝大多数情况是某个依赖没装全把上面列出的依赖逐个对一遍即可。pyenv 的好处是每个项目目录可以写上.python-version文件切目录自动切换版本非常舒服。4. Docker 与中间件服务部署4.1 Docker 在双版本下的安装差异测试环境里最大变量就是中间件数量如果每个版本直接在宿主机上装一套两三个项目就能把机器端口占满卸载还卸载不干净副作用极大。所以我坚持用 Docker 部署所有中间件。CentOS 7 上安装 Docker 的常规流程是先装依赖再添加 Docker 官方源yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo yum install -y docker-ce docker-ce-cli containerd.io systemctl start docker systemctl enable dockerUbuntu 20.04 的安装方式相似但 APT 源格式不一样apt install -y apt-transport-https ca-certificates curl software-properties-common curl -fsSL https://download.docker.com/linux/ubuntu/gpg | gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg echo deb [archamd64 signed-by/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu focal stable | tee /etc/apt/sources.list.d/docker.list /dev/null apt update apt install -y docker-ce docker-ce-cli containerd.io systemctl start docker systemctl enable docker实测下来最容踩坑的地方是 CentOS 7 安装后 Docker 服务启动失败多数原因是系统内核版本和 overlay2 文件系统驱动的兼容性。处理方式是升级内核或者把存储驱动切换为 vfs但切换 vfs 后性能下降明显。比较靠谱的做法是先用docker info查看当前存储驱动如果报错再考虑调整。4.2 用 Docker Compose 一键拉起测试依赖单个中间件手工 docker run 还凑合一旦有 MySQL、Redis、RabbitMQ、MinIO 好几个一起启动时手工操作就会丢三落四。我统一用 docker-compose.yml 管理测试依赖。以下是一个我实际使用的编排示例version: 3.8 services: mysql: image: mysql:5.7 container_name: test-mysql restart: always ports: - 3306:3306 environment: MYSQL_ROOT_PASSWORD: root123 MYSQL_DATABASE: testdb volumes: - /data/mysql:/var/lib/mysql command: --character-set-serverutf8mb4 --collation-serverutf8mb4_unicode_ci redis: image: redis:6.2-alpine container_name: test-redis restart: always ports: - 6379:6379 volumes: - /data/redis:/data rabbitmq: image: rabbitmq:3.10-management container_name: test-rabbitmq restart: always ports: - 5672:5672 - 15672:15672 environment: RABBITMQ_DEFAULT_USER: test RABBITMQ_DEFAULT_PASS: test123 volumes: - /data/rabbitmq:/var/lib/rabbitmq启动和停止流程docker compose up -d docker compose ps docker compose logs -f docker compose down需要注意MySQL 5.7 的镜像在 Docker Hub 上的标签已经比较久远但测试环境用着没问题。如果项目要求 MySQL 8.0改镜像标签即可其他配置几乎不用动。数据卷挂载到宿主机 /data 目录下目的是容器重建后数据不会丢这个是测试环境最实用的一步。4.3 容器镜像管理与离线部署测试环境有时候会碰到网络受限的机房所有机器只能在内网通信。这种情况下如果每台机器都去拉公共镜像基本会卡死。我的做法是在一台可以联网的机器上把镜像拉下来然后导出成 tar 包拷贝到内网docker pull mysql:5.7 docker pull redis:6.2-alpine docker save mysql:5.7 redis:6.2-alpine -o test-images.tar # 也支持一条命令导入 docker load -i test-images.tar这里有个经验导出 tar 前先确认镜像标签不要把latest盲目导出。latest标签会随时间变化导致输出的 tar 包在不同时间内容不一致测试环境版本漂移往往就是这么产生的。最好在 compose 文件里锁定明确版本号。镜像多起来之后定期清理无效镜像和停止容器也是必要的docker system df docker image prune docker container prune不用太频繁一个月清一次就够。5. 自动化初始化脚本与环境校验5.1 编写幂等的一键初始化脚本当需要部署多台测试机时手动一条条命令敲非常容易出错而且每台机器状态不同有的人已经装了部分软件有的人是全新系统。最高效的方案是写一个幂等初始化脚本无论跑多少次结果都一样。我一般把脚本放在 /opt/init_env.sh核心思路是先检测系统发行版再执行对应分支#!/bin/bash set -e if [ -f /etc/redhat-release ]; then yum install -y epel-release yum update -y yum install -y vim wget curl net-tools git java-11-openjdk-devel elif [ -f /etc/lsb-release ]; then apt update -y apt install -y vim wget curl net-tools git openjdk-11-jdk else echo Unsupported OS exit 1 fi echo Basic environment initialized.执行前务必备份原配置文件比如 /etc/hosts 和软件源文件防止脚本跑了半路出错导致系统处于不可用状态。脚本里加上set -e可以让后续命令在前置失败时立刻退出不至于带病往下跑。幂等处理的关键是安装命令本身要具备“不重复装”的能力。yum install 和 apt install 在包已安装时会提示 alreay installed并不会报错所以基础安装命令天然具备幂等性。但如果是手动修改配置文件这种操作就需要在改动前做备份或者写判断语句先检查文件内容是否已满足条件。5.2 环境信息采集与验收检查环境部署完不意味着万事大吉验收环节同样重要。我自己写了一个环境信息采集脚本 deploy_check.sh输出每台机器的关键信息统一放到 /tmp/env_report 文件里#!/bin/bash echo 主机名: $(hostname) echo 系统版本: $(cat /etc/os-release | grep PRETTY_NAME) echo 内核版本: $(uname -r) echo CPU 核数: $(nproc) echo 内存(MB): $(free -m | awk NR2{print $2}) echo 磁盘可用(GB): $(df -h / | awk NR2{print $4}) echo Docker 版本: $(docker --version 2/dev/null || echo not installed) echo 运行中的容器: $(docker ps -q 2/dev/null | wc -l) echo 监听端口: $(ss -tlnp | awk {print $4} | grep -v ^$ | sort -u)把每台机器上跑出的报告汇总对比哪些端口不一样、哪些依赖版本有差异基本一眼就能看出环境漂移。这里我强烈建议环境部署完后把这份报告留存归档后续任何人接手这台机器时首先看报告而不是花半天时间去探测环境里到底有什么。6. 常见问题与排查经验6.1 软件源失效导致下载安装异常CentOS 7 官方软件源在系统停止维护后会出现部分源失效的情况表现为 yum 安装时提示 404 或者连接超时。处理思路是尽快把 Base 源和 epel 源切换到可用的镜像地址。如果镜像地址也暂时不可用可以临时改用本地缓存安装包或者从离线包仓库拷贝但这不是常规方案尽量很快恢复镜像配置。Ubuntu 20.04 相对好一些官方源还在正常存活但国内访问速度不稳定。遇到软件源问题先执行apt update查看报错的具体源地址再针对性替换不要盲目地去清理 entire sources.list。6.2 编译失败与依赖缺失编译安装 Python、Git 或者 Nginx 时最容易遇到的问题是某个头文件或者开发库没装。比如编译 Python 时少了 libffi-devel会报_ctypes模块找不到编译 Git 时少了 openssl-devel会导致 HTTPS 拉代码失败。这类问题排查相对简单直接看编译日志里的checking for ... not found再安装对应的-devel包即可。我在初始化脚本里会把常用编译依赖一次性装全省得每次都要重新编译。6.3 Docker 相关故障与日志膨胀Docker 容器运行一段时间后磁盘被日志文件占满是最常见的故障。默认情况下容器日志没有轮转机制长时间运行的容器能把一个日志文件写到几个 GB。解决办法是在 /etc/docker/daemon.json 里配置日志轮转{ log-driver: json-file, log-opts: { max-size: 50m, max-file: 5 } }修改后重启 Docker 服务使配置生效注意重启 Docker 会连带重启所有容器最好挑在测试空闲期操作。另外宿主机磁盘分区如果不够大建议把 /data 目录单独挂一块磁盘避免容器数据卷占满根分区导致系统异常。6.4 CentOS 7 与 Ubuntu 20.04 命令差异速查为了方便复制粘贴我把两个系统下的常用操作差异整理成一张对照表直接收藏备用操作目的CentOS 7Ubuntu 20.04安装软件包yum install 包名apt install 包名编译基础依赖yum groupinstall Development Toolsapt install build-essential防火墙开放端口firewall-cmd --add-port8080/tcp --permanentufw allow 8080/tcp关闭防火墙systemctl stop firewalldufw disable查看服务状态systemctl status 服务名systemctl status 服务名修改主机名hostnamectl set-hostname 主机名hostnamectl set-hostname 主机名查找安装包yum provides */文件路径apt-file search /文件路径清理缓存yum clean allapt autoclean另外CentOS 7 上处理 SELinux 相关权限问题时常见操作是临时用 setenforce 0 禁用验证完再恢复。Ubuntu 上对应的坑主要在 AppArmor 配置文件如果测试服务被拦截检查 /etc/apparmor.d/ 下对应的配置文件。最后一个我自己的习惯每次部署完环境把执行过的所有命令和配置文件内容追加到 /opt/deployment.log 里标注日期和操作人。这看起来只是个小动作但等到一个月后有人问“这台机器上的 MySQL 密码是什么”“Redis 端口为何改了”打开这份日志立刻就能找到答案效率翻倍。测试环境部署的核心不是“能跑就行”而是“可复现、可追溯、可审计”——这套双版本手册的最终目标也正在于此。