两个发行版,一套测试环境,乍看像是自己给自己找麻烦。但做过兼容性验证的运维都知道,客户线上跑什么系统,测试环境就得跟着搭什么系统。客户那里还是 CentOS 7,你本地清一色 Ubuntu,功能在测试机跑得飞起,到了对方现场说崩就崩,连问题都复现不了。我这边测试机房里,CentOS 7 和 Ubuntu 20.04 长期各占一半,这套部署手册就是我在两套系统之间反复折腾之后沉淀下来的,覆盖了从系统装完那一刻到软件源、常用服务、防火墙、安全模块、验收检查的完整流程,每个环节都给了可以直接复制的命令和踩坑说明。适合运维工程师、测试开发、以及需要自己维护开发机和测试机的后端同学参考。
1. 双版本并存的现实考量:为什么测试环境比生产环境更“杂”
1.1 两种发行版在测试环境里的角色分工
很多新人会问:测试环境随便用一个系统不就好了吗,为什么要养两套?答案是测试环境的职责决定了它必须“杂”。
生产环境讲究统一、稳定,能用一套标准配置解决问题就绝不引入第二个变量;但测试环境的核心价值是最大程度模拟真实用户的运行条件。存量客户里面,CentOS 7 这批老服务器数量仍然不少,它们在正常跑业务,不会因为你换了发行版就立刻升级。另一边,开发人员自己搭的服务器、新项目的CI环境,很多都跑在 Ubuntu 20.04 这类新一点的系统上。如果测试环境只服务其中一边,那另一类问题就完全覆盖不到。
我在这套环境上做的典型测试是:同一个应用包,分别在 CentOS 7 和 Ubuntu 20.04 上部署一遍,验证安装脚本、依赖库、配置文件有没有写死系统路径,再跑一遍核心功能回归。这个过程帮我挡掉过很多“只在某一种系统上能跑”的隐蔽问题。
1.2 部署前的统一规划:主机名、IP、账号与网络段
两套系统命令不同、包管理不同,但基础规划千万不要各干各的,不然环境一多就乱。我这边用的是这套约定:
- 主机名采用
test-centos-01、test-ubuntu-01这种带系统标识的格式,一眼能看出机器用途。 - IP 段统一规划:例如内网用
192.168.10.0/24,CentOS 机器用192.168.10.11起,Ubuntu 机器用192.168.10.21起,避免两边 IP 撞车。 - 账号统一用
deploy用户,通过 sudo 提权,不直接用 root 操作日常命令。 - 关机、重启、服务启停都通过 systemd,不用旧的
service命令。
为什么要单独建一个 deploy 用户而不是 root 一把梭?因为测试环境经常要跑自动化脚本、定时任务、部署工具,如果所有操作都以 root 身份进行,脚本里一旦写下危险命令(比如误删目录、权限写错),代价就是整个环境报废。用普通用户加 sudo,相当于给这些操作加了道闸口,日志里也能看到是谁用 sudo 干了什么。
分区规划上,测试环境不需要像生产环境那样精细,但建议至少把/和/data分开。/data挂独立分区,后续装测试数据、日志、备份文件都放这里,系统盘被日志打满时不会拖垮整台机器。
1.3 镜像选择:用最终版和 LTS,别追新
CentOS 7 选 7.9,这是这个系列最后的版本;Ubuntu 20.04 选 20.04.6,也是该系列的最终维护版本。测试环境要的是可复现:今天装完是这个版本,半年后再装一批机器,版本依然一致,行为一致,这才是测试的价值。追新版本对测试环境是负资产。
安装镜像方面,我建议用 minimal / server 最小化安装包,不要在安装阶段勾选一堆图形组件,后面需要的软件全部走软件源安装。这样既能缩短装机时间,也方便沉淀成自动化交付流程。CentOS 7 的官方源已经进入 EOL 状态,装完系统后第一件事就是把 yum 源调整到可用的镜像状态,这个后面详细说。
2. CentOS 7 初始化全流程:yum 时代的部署习惯
2.1 装完系统后的第一件事:主机名和静态 IP
CentOS 7 装完后默认走 DHCP,IP 一变,后面所有基于 IP 的配置都得跟着改,所以我的习惯是第一步就改成静态地址。
hostnamectl set-hostname test-centos-01网卡配置文件在/etc/sysconfig/network-scripts/ifcfg-<接口名>,接口名可能是ens33、eth0,取决于你的虚拟化平台和网卡命名规则。改完后长这样:
TYPE=Ethernet BOOTPROTO=static IPADDR=192.168.10.11 NETMASK=255.255.255.0 GATEWAY=192.168.10.1 DNS1=192.168.10.2 ONBOOT=yes然后重启网络服务:
systemctl restart network这里有个新手高频踩坑点:ONBOOT=no忘了改成yes,导致机器重启后网卡直接不启动。另外 CentOS 7 的 NetworkManager 和 network 服务经常同时存在,如果你发现改了配置文件但 IP 没生效,可以看一下这个网卡是不是被 NetworkManager 接管了,必要时用nmcli进行确认和调整。
2.2 软件源与 epel:包管理的第一步
CentOS 7 的系统源现在处于 EOL 状态,如果yum install报错说找不到 mirrorlist,需要手动把 base 源切换到 vault 镜像。简单操作是把/etc/yum.repos.d/CentOS-Base.repo里的mirrorlist注释掉,把baseurl指向 vault 地址,然后执行:
yum clean all yum makecache装完基础源后,第一时间安装 EPEL 源。EPEL 里提供了大量 base 源没有的实用工具,比如htop、jq、tree,这类小工具在排查问题时会频繁用到。直接:
yum install -y epel-release安装基础工具包:
yum install -y vim wget git lsof net-tools unzip zipnet-tools虽然老旧,但里面包含ifconfig、netstat这些经典命令;很多老运维习惯了netstat -tlnp看端口,没装这个包会报 command not found。
2.3 测试环境常见组件的 yum 安装方式
测试环境通常需要 JDK、Nginx、MySQL/MariaDB、Redis 这几样。CentOS 7 默认源里的版本普遍偏老,这反而是测试环境需要的——测试环境永远应该优先复刻客户现场版本,而不是追逐新版本。
OpenJDK 直接:
yum install -y java-1.8.0-openjdk java-1.8.0-openjdk-develNginx 用源里自带版本即可:
yum install -y nginx systemctl enable --now nginxCentOS 7 源里的 MySQL 不是官方 MySQL,而是 MariaDB 5.5,版本比较老。如果被测应用依赖 MySQL 5.7 或 8.0,需要手动装官方 RPM 包再安装。这里要给个提示:装官方 RPM 包时记得确认它和当前系统架构匹配,不要复制了错误版本。
Redis 源里是 3.2,比较老但胜在稳定。服务名是redis:
yum install -y redis systemctl enable --now redis2.4 systemd 开机自启与单元文件要点
CentOS 7 的 systemd 版本偏老,在它上面写单元文件,有些新语法会被静默忽略。比如我在一个服务文件里写了MemoryHigh=512M,CentOS 7 不会报错,但它也不会真正生效,直到迁移到 Ubuntu 20.04 才意识到这个配置其实是可以工作的。
一个规范的测试服务单元文件长这样:
[Unit] Description=Test App Service After=network.target [Service] User=deploy ExecStart=/opt/app/bin/start.sh Restart=always RestartSec=5 Environment=APP_ENV=testing [Install] WantedBy=multi-user.target写完放到/etc/systemd/system/下,然后:
systemctl daemon-reload systemctl enable --now test-app.service注意ExecStart里的路径必须写成绝对路径,且不要带~,systemd 不会做变量展开。另外服务如果是脚本,脚本里涉及环境变量时尽量在单元文件里用Environment=显式声明,不要指望登录用户的.bashrc能加载到 systemd 进程里。
3. Ubuntu 20.04 初始化全流程:apt 时代的部署习惯
3.1 netplan 配置网络:从 ifcfg 到 YAML 的思维切换
Ubuntu 20.04 默认用 netplan 管理网络,配置文件在/etc/netplan/下面,常见文件名是00-installer-config.yaml。静态 IP 配置大概是这个样子:
network: version: 2 ethernets: ens160: dhcp4: no addresses: - 192.168.10.21/24 routes: - to: default via: 192.168.10.1 nameservers: addresses: - 192.168.10.2改完后应用:
sudo netplan apply这里有个非常实用的安全手段:netplan try。它会先检测配置合法性,应用后如果一段时间内没有确认,自动回滚到上一份配置。我第一次改 Ubuntu 网络配置时直接用了apply,缩进写错导致网卡全断,只能跑去机器前面接显示器。后来一律用try,再没出过这种事故。
还有一点:netplan 底层渲染器可能是networkd,也可能是NetworkManager。如果这台机器之后要做复杂的网卡绑定或者 Docker 网络,建议在部署时确认一下/etc/netplan/里的renderer字段,后面排查网络问题时能少走弯路。
3.2 apt 源与软件更新策略
Ubuntu 20.04 的 apt 源配置文件在/etc/apt/sources.list。默认源在绝大多数场景下都可用,如果内网有镜像源,可以换成内网地址加速。
完成系统更新:
sudo apt update sudo apt upgrade -y我的建议是刚装完系统时做一次全量升级,让系统和软件包保持在同一批版本上,之后就不需要频繁 upgrade,测试环境最重要的是版本一致性,频繁升级反而是麻烦来源。装基础工具:
sudo apt install -y vim wget git lsof net-tools unzip zipUbuntu 20.04 里的net-tools需要单独安装,如果不装,ifconfig和netstat都不存在,需要习惯用ip addr和ss -tlnp。
3.3 在 Ubuntu 上复刻同样的服务栈
同等服务在 Ubuntu 上的安装命令和 CentOS 很不一样,最直观的差异体现在包名上。
# JDK sudo apt install -y openjdk-8-jdk-headless openjdk-11-jdk-headless # Nginx sudo apt install -y nginx sudo systemctl enable --now nginx # MySQL(默认是 8.0) sudo apt install -y mysql-server sudo systemctl enable --now mysql # Redis sudo apt install -y redis-server sudo systemctl enable --now redis-server如果你需要的是 MariaDB,就装mariadb-server,服务名同样是mariadb。需要说明的是 Ubuntu 上安装 MySQL 后会默认监听本地 socket,远程连接前要手动确认 bind-address 配置,这个点和 CentOS 上装 MariaDB 的行为不太一样。
另外 Nginx 的配置目录结构差异比较明显:CentOS 7 的 nginx 默认会在/etc/nginx/conf.d/下加载所有.conf文件;Ubuntu 上的 nginx 包则用/etc/nginx/sites-available/和/etc/nginx/sites-enabled/两套目录,通过软链接激活站点。如果从 CentOS 迁移过来,记得先看一下nginx.conf里到底 include 了哪个目录,再决定配置文件放哪。
3.4 与 CentOS 7 的全方位差异对照表
| 对比项 | CentOS 7 | Ubuntu 20.04 |
|---|---|---|
| 包管理器 | yum | apt |
| 网络配置 | /etc/sysconfig/network-scripts/ifcfg-* | /etc/netplan/*.yaml |
| 防火墙 | firewalld | ufw |
| 安全模块 | SELinux | AppArmor |
| sudo 用户组 | wheel | sudo |
| 默认 Python | 2.7(python3 是 3.6) | 3.8 |
| Linux 内核 | 3.10 | 5.4 |
| Nginx 配置目录 | /etc/nginx/conf.d/ | /etc/nginx/sites-available/ |
| Redis 服务名 | redis | redis-server |
这张表建议保存在备忘录里,每次在两套系统间切换部署操作时先对着看一眼,能避免大量低级错误。
4. 双系统下最容易被坑的细节:从安全模块到服务管理
4.1 SELinux 与 AppArmor:同样的“安全模块”,不同的惩罚方式
CentOS 7 默认启用 SELinux,状态是 enforcing。这意味着如果一个服务进程想监听一个非常规端口,或者想读取某个被标记的文件,而策略没有放行,它会被静默拦截。最典型的症状是:服务状态显示 running,端口就是听不到,日志里也看不到明显报错。
遇到这种情况,先看审计日志:
ausearch -m avc -ts recent或者临时关掉 SELinux 验证是不是它在拦截:
setenforce 0如果关掉之后问题消失,那就确认是 SELinux 策略问题,需要针对性调整服务标签或端口策略,而不是一路setenforce 0关到底。测试环境也一样建议保持 enforcing,因为生产环境的 CentOS 通常是开着的,测试环境关掉就意味着你永远测不到权限问题。
Ubuntu 20.04 的 AppArmor 也在默默工作,行为方式更偏向限制具体程序的 profile。用aa-status可以查看当前加载的策略。对于常规的 Web 服务和数据库服务,AppArmor 的存在感通常比 SELinux 低,但如果你改了 MySQL 的数据目录,或者 Nginx 的 web 根目录,一样可能被 AppArmor 拒在门外。
4.2 firewalld 与 ufw:防火墙命令的对应关系
CentOS 7 默认用 firewalld,放行端口语法是:
systemctl start firewalld firewall-cmd --permanent --add-port=8080/tcp firewall-cmd --reloadUbuntu 20.04 默认装了 ufw,但默认不启用。启用前先放行需要的端口,否则 SSH 会断掉:
sudo ufw allow 22/tcp sudo ufw allow 8080/tcp sudo ufw enable这里要特别注意默认行为差异:firewalld 在 CentOS 7 默认对公共区域的入站流量限制较严,而 ufw 默认策略是允许出站、拒绝入站,但未启用时等于没有防火墙。所以我在双版本部署中养成一个习惯:不管是哪套系统,装完服务后都顺手确认一遍防火墙状态,用ss -tlnp确认端口在听,再用firewall-cmd --list-all或ufw status verbose确认放行规则。
4.3 systemd 版本差异和单元文件的细节坑
CentOS 7 自带 systemd 219,Ubuntu 20.04 自带 systemd 245,两者差了六年的功能演进。这个差距在写单元文件时会直接暴露出来。CentOS 7 时代比较有限,部分新指令不识别但不报错,默默不生效;Ubuntu 20.04 能支持更丰富的资源限制参数,比如MemoryMax、TasksMax这类。合理的做法是写单元文件时只用两边都支持的基础指令,特殊参数按系统分开管理。
另外,单元文件覆盖顺序也要知道:/etc/systemd/system/下的同名文件会覆盖/lib/systemd/system/下的包默认文件。我在自定义服务参数时都会复制一份到/etc/systemd/system/里再修改,而不是直接改包目录下的文件,这样软件包升级时不会把我的自定义配置冲掉。
4.4 用户、权限与 SSH 加固的常见不一致
创建 deploy 用户时,两边的命令细节不一样:
# CentOS 7 useradd -G wheel deploy # Ubuntu 20.04 useradd -m -s /bin/bash -G sudo deployCentOS 7 的 wheel 组默认有 sudo 权限;Ubuntu 20.04 的 sudo 组对应同样的能力。但两个系统的 useradd 默认行为有差别,稳妥的做法是显式加上-m -s /bin/bash,确保家目录和 shell 都符合预期。
SSH 加固方面,两边都建议修改/etc/ssh/sshd_config:关闭 root 密码登录、仅允许密钥登录。不同发行版对PermitRootLogin的默认值也不完全一样,CentOS 7 默认是yes,Ubuntu 20.04 默认是prohibit-password,所以这个文件一定要显式配置,不要依赖默认值。
5. 部署完成不等于结束:验收清单与一键巡检思路
5.1 手动验收:十组命令看遍系统状态
部署完一套环境,我会按下面的顺序快速过一遍:
cat /etc/os-release # 确认系统版本,防止连错机器 hostnamectl # 确认主机名 ip addr show # 确认 IP free -h # 确认内存 df -h / /data # 确认磁盘 nproc # 确认 CPU 核数 systemctl list-units --type=service --state=running | grep -E "nginx|mysql|redis" ss -tlnp # 确认关键端口 journalctl -p err -b # 看看本次开机有没有报错 yum check-update / apt list --upgradable前四项是最容易被忽略的:多台机器连着跳板机,经常出现想部署 CentOS 的机器结果 SSH 登到了 Ubuntu 上,一通操作全部白费。先看系统版本再动手,是测试环境部署的基本仪式感。
5.2 自动化巡检脚本:同一套脚本覆盖双版本
双版本维护最大的痛点是命令不统一,所以巡检脚本里需要先做发行版判断。一段最简版本的思路:
#!/bin/bash if grep -qi centos /etc/os-release; then echo "System: CentOS" firewall-cmd --list-all else echo "System: Ubuntu" sudo ufw status verbose fi echo "--- Key ports ---" ss -tlnp | grep -E ":80|:3306|:6379|:8080" echo "--- Running services ---" systemctl list-units --type=service --state=running | grep -E "nginx|mysql|redis" echo "--- Disk usage ---" df -h / /data 2>/dev/null把这个脚本放到每台机器的/usr/local/bin/healthcheck.sh,加上可执行权限,就能用一条命令快速巡检。核心逻辑就是按发行版分流,公共部分统一用 systemd 和ss命令。
5.3 时间同步、日志与后续更新策略
测试环境的时间不同步,会导致日志对不上、证书验证失败、数据库写入时间错乱。CentOS 7 默认用 chrony:
systemctl enable --now chronyd chronyc sources -vUbuntu 20.04 默认用 systemd-timesyncd:
timedatectl set-ntp true timedatectl status日志查看统一用 journalctl,两边行为基本一致。我习惯把关键服务的日志长期保留,避免测试跑了几天之后想查历史记录却发现被轮转掉了,这时就需要调整 journald 的保留策略。
关于系统更新,我的策略是装完机做一次全量更新,然后锁定版本,除非是为了验证某个具体缺陷,否则测试环境不频繁升级。频繁变动的环境不是好的测试环境。
6. 半年部署下来的踩坑记录与排查路线
6.1 Python 版本差异引发的脚本兼容问题
有一回把一套监控脚本从 Ubuntu 20.04 复制到 CentOS 7 上直接运行,结果报了ModuleNotFoundError: No module named 'requests'。检查才发现,CentOS 7 默认python指向 Python 2.7,yum install python-requests装的是 Python 2 版本,而脚本用的是 Python 3 语法和依赖。Ubuntu 20.04 默认python3指向 3.8,且没有提供python这个命令,两边的默认行为完全错位。
后来我在所有部署脚本里统一约定:脚本第一行写死解释器路径,要么/usr/bin/python3,要么通过 virtualenv 锁定依赖,绝不依赖系统默认python。同时把测试环境的基础镜像里都预装python3-pip,避免后续还要现场补包。
6.2 apt 的锁与依赖问题:Ubuntu 上的隐藏事故
Ubuntu 在执行 apt 安装时经常遇到:
Could not get lock /var/lib/dpkg/lock-frontend这个锁表示有另一个 apt 或 dpkg 进程在运行。可能是后台 unattended-upgrades 在自动更新,也可能是之前某个 apt 命令被中断没有释放锁。正确做法是先看进程:
ps aux | grep -E "apt|dpkg" lsof /var/lib/dpkg/lock-frontend确认确实没有 apt 进程残留,再移除 stale lock 文件。我见过有人看都不看直接rm -rf /var/lib/dpkg/lock*,然后整个 dpkg 数据库出问题。这个要特别提醒:锁文件不是不能删,但必须先确认没有相关进程持有它。
6.3 修改 netplan 导致的“掉网”事故
改造一台 Ubuntu 测试机时,我把00-installer-config.yaml里的缩进写错了,然后直接执行sudo netplan apply,网络立即断掉,SSH 连接卡死,最后只能通过管理台进系统改回来。后来我在部署手册里加了硬性规定:任何 netplan 改动必须先用sudo netplan try验证,确认网络正常后按回车确认,再执行apply或者干脆不执行apply。
CentOS 7 那边也有类似事故:修改ifcfg-ens33时,把UUID行删掉了,导致重启后网络服务起不来。原因是配置文件和 NetworkManager 记录的设备标识对不上。这类坑的共同点都是“以为只改了一个字段,实际上破坏了系统用于识别设备的元数据”。
6.4 排查方法论:用差异对照表缩小变量范围
双版本环境里遇到“这台机器有问题,那台机器没问题”的情况,我最常用也最有效的排查思路是:先把两边除了发行版之外的所有差异拉出来,逐个排除。
比如服务起不来,先看服务状态是不是都在运行;然后看端口是不是都在监听;再看防火墙有没有放行;再看安全模块有没有拦截;最后看文件权限和用户是否一致。大部分时候问题出在“某个你以为一样、其实不一样”的默认配置上,比如 CentOS 7 的redis服务名和 Ubuntu 的redis-server服务名、firewalld 和 ufw 的默认策略差异。每次出现新的诡异问题,我都会把原因补进上面的差异对照表里,这张表就是双版本维护最重要的资产。
最后一件事,跑完这套部署流程,我的感受是:双版本部署的难点从来不在某个单独的命令,而在于那些“默认不一样”的角落。只要每次部署都把发行版差异当作独立变量来对待,复杂的双环境维护完全可以做到有条不紊。