给服务器配网卡这件事,看着简单,翻车的人一点都不少。我经常遇到的情况是:机器在机房,手边只有一条SSH通道,网卡参数一时半会儿对不上,网络直接断掉,整个人只能对着控制台干瞪眼。这种场景下,nmcli是救命的工具,而/etc/sysconfig/network-scripts/ 下的 ifcfg 网卡配置文件则是理解一切网络管理行为的根基。这篇文章就把两条配置路径完整讲一遍:NetworkManager 自带的命令行工具 nmcli,以及手工编辑网卡配置文件的全过程,最后附上一份可以直接套用的配置脚本。适合刚入门运维的朋友、经常搭测试环境的人,以及正准备规划批量网卡配置脚本的同学。
1. 为什么网络配置要同时掌握两条路线:nmcli与ifcfg的互补关系
1.1 一次救场的经历:命令行是最后一道保命手段
先说一个我自己的经历。有一回接手一台新到的服务器,系统装好之后,发现管理网卡没有自动获取到地址。整个机房没有图形界面可用,唯一能操作的是机器上的物理控制台。这种情况下,想打开图形化的网络设置工具是痴人说梦,唯一能快速把网络拉起来的办法,就是敲命令。
当时我先用nmcli device status看了一眼网卡状态,再用nmcli connection add临时建了一个静态IP连接,网络通了,后面才有余力慢慢核对网卡配置。但如果你只会 nmcli,会陷入另一个麻烦:批量部署几十台机器时,难道要一台一台手敲 nmcli 命令?这时候直接生成规范、统一的 ifcfg 配置文件再批量下发,效率高出一个量级。
所以这两条路线不是二选一,而是互相补位的。nmcli 适合现场快速操作,ifcfg 文件适合做基线管理、模板复用和脚本批量处理。真正干过运维的人都知道,两条腿走路才稳。
1.2 nmcli适合什么场景,手写文件又适合什么场景
拿日常场景来划分:临时调个IP、测试一下新网段通不通、快速看当前连接状态,这些用 nmcli 最方便。它自带的输出格式化、自动补全和对象化管理,让单台机器的操作成本极低。尤其是nmcli connection modify这种命令,改完立刻生效,还顺手把配置写回文件,省去手工编辑和重载的步骤。
手写 ifcfg 文件的场景则更偏向"工程化"。比如给新环境做标准化镜像,要求所有网卡配置文件格式统一、字段齐全;或者要做自动化脚本,准备在装机后自动生成配置,这时候如果不懂文件结构,脚本根本写不出来。还有一类情况是故障排查:nmcli 显示的参数终究是读取配置文件之后的结果,很多诡异问题,最后都要打开 ifcfg 文件才能找到根因。
一句话总结:nmcli 负责"快速改变现状",ifcfg 文件负责"定义稳定状态"。理解了这层关系,后面学的所有命令和字段都有落点。
2. nmcli从查看到配置:命令对象与实操链路
2.1 device和connection不是一回事
很多新手一上来就懵,因为 nmcli 里有两个高频词:device 和 connection。这俩不是同一层概念。device 是物理网卡,比如 eth0、ens33;connection 是保存在这个网卡上的一套配置方案,包括IP、网关、DNS、自动连接开关等等。一个网卡上可以挂多套 connection,比如一套DHCP方案、一套静态IP方案,但同一时刻只有一个 connection 处于 active 状态。
你可以把 device 理解成墙上的插座,connection 则是插在上面的各种电器。插座始终在那,你只需要切换插哪个电器。
区分清楚后,命令就很好理解了。nmcli device status看的是网卡设备状态,nmcli connection show看的是所有配置方案,nmcli connection show --active看的是当前正在生效的方案。实际工作中,我养成一个习惯:凡是涉及"改配置"的操作,一律在 connection 层面做;凡是涉及"查状态"的操作,device 和 connection 两个命令结合着看。
2.2 静态IP配置的完整命令链
给一块网卡配置静态IP,最稳妥的命令顺序是先新建一个 connection,再激活它,而不是直接在原来那个连接上改。原因很实际:原连接可能是系统装好时自动生成的,带有各种默认行为,改成静态IP时一个参数没写全,反而会把原有配置搞得更乱。新建一个命名清晰的连接,出问题可以随时切回去。
一条完整的命令链大致是这样:
# 创建名为 eth0-static 的以太网连接,绑定 eth0 网卡 nmcli connection add type ethernet con-name eth0-static ifname eth0 \ ipv4.method manual \ ipv4.addresses 192.168.100.10/24 \ ipv4.gateway 192.168.100.1 \ ipv4.dns "10.10.10.2 10.10.10.3" # 激活这个连接 nmcli connection up eth0-static这里有一个关键点:ipv4.method manual必须显式写出来。如果不写,新建的连接可能默认还是 DHCP 或自动协商方式,后面即便填了 IP 地址,系统也不会乖乖用它。写完之后,可以顺手验证一下:
ip -4 addr show eth0 ip route实际修改已有连接时,用的则是nmcli connection modify,写法和 add 差不多。比如把 DNS 改掉、把自动连接打开:
nmcli connection modify eth0-static ipv4.dns "10.10.10.4 10.10.10.5" nmcli connection modify eth0-static autoconnect yes nmcli connection up eth0-static需要特别说明的是,nmcli connection modify在连接处于 active 状态时,通常会把改动直接应用到当前网络配置,而不是只改文件等重启。这就是它比手改文件"快"的原因。
2.3 排障时最高频的查询命令
配置做完不等于万事大吉,查询命令在排障时才是主角。我实际用得最多的几组命令,基本覆盖了 90% 的网卡状态判断需求:
# 查看设备整体状态 nmcli device status # 查看某个网卡当前生效的具体配置 nmcli device show eth0 # 查看连接列表(含非活动连接) nmcli connection show # 只看关键字段,脚本里也方便解析 nmcli -t -f NAME,DEVICE,TYPE connection show其中-t是 terse 模式,输出不含多余表头,纯字段内容,适合写脚本时 grep 或 awk;-f是指定字段。比如我想知道所有活动连接的名字和对应网卡,一条命令搞定:
nmcli -t -f NAME,DEVICE connection show --active还有一个容易忽略的命令是nmcli general status,它能看到 NetworkManager 整体运行状态、网络是否被全局禁用。有次机器上不了网,我查了半天网卡配置,最后发现是 NetworkManager 被整体停用了,nmcli general status一看便知。这类全局状态,平时排障必须排在第一步。
3. ifcfg配置文件逐字段拆解与手工修改全流程
3.1 最小可用的静态IP配置文件长什么样
以 RHEL 系发行版为例,网卡配置文件集中在/etc/sysconfig/network-scripts/,命名规则通常是ifcfg-网卡名,比如ifcfg-eth0。一个最小可用的静态IP配置,大概是这个模样:
DEVICE=eth0 NAME=eth0-static TYPE=Ethernet UUID=xxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx ONBOOT=yes BOOTPROTO=none IPADDR=192.168.100.10 PREFIX=24 GATEWAY=192.168.100.1 DNS1=10.10.10.2 DNS2=10.10.10.3注意这里的写法差别:在 nmcli 里,IP地址写成192.168.100.10/24这种 CIDR 格式,但在 ifcfg 文件里,必须拆成IPADDR=192.168.100.10和PREFIX=24两个字段。很多人直接写IPADDR=192.168.100.10/24,结果是地址根本不被识别。这个坑特别常见,我见过身边同事在这上面耗过小半天。
另外,BOOTPROTO=none表示不使用任何动态地址协商协议,纯手工指定地址。有的发行版也接受BOOTPROTO=manual,效果等同,但写成none更通用。DHCP 环境的机器,这个字段要写成dhcp,并删掉 IPADDR/GATEWAY 等字段。
3.2 关键字段的作用与容易出现的问题
ifcfg 文件里的字段不少,但真正高频接触的就那么十来个,我把它们分成几类说明。
连接识别类字段包括 DEVICE、NAME、UUID、TYPE。DEVICE 对应物理网卡名,NAME 是连接名,UUID 是连接的唯一标识。三个字段必须配合正确。有个容易出的问题:复制别人的配置文件时,DEVICE 忘了改,导致新配置挂到了错误的网卡上;或者 UUID 和现有连接冲突,NetworkManager 加载时出现奇怪行为。复制文件后第一件事,就是用uuidgen生成新 UUID 改掉,这个习惯能帮你躲掉很多莫名其妙的故障。
地址类字段是 IPADDR、PREFIX、GATEWAY。如果网段是传统分类掩码,也可以写NETMASK=255.255.255.0,但现代系统更推荐 PREFIX,两者写一个就行,两个都写时以 PREFIX 为准。
DNS与行为类字段同样关键。DNS1/DNS2 是首选和备选 DNS;ONBOOT=yes 表示开机激活该连接;DEFROUTE=yes 表示把该连接上的网关写入默认路由。多网卡机器尤其要小心 DEFROUTE:两块网卡都配了网关,默认路由就会冲突,流量往哪走全看路由表顺序,结果往往不稳定。至于 NM_CONTROLLED 这个字段,建议保持yes,让 NetworkManager 统一管理,否则可能出现"设备在、但连接不受管"的尴尬状态。
3.3 手工修改之后如何安全生效
手工改了文件,不能直接重启机器,也不能简单跑systemctl restart network就完事。正确的生效路径依赖 NetworkManager 的重载机制:
# 让 NetworkManager 重新读取配置文件 nmcli connection reload # 激活对应连接 nmcli connection up eth0-staticreload负责把磁盘上的新配置读进 NetworkManager 缓存,up让配置正式生效。很多新手漏掉 reload,直接 up,发现 nmcli 里看到的还是旧内容,其实不是文件没改对,而是缓存没刷新。
这里还要提醒一个高风险点:如果修改的是当前正在使用的网卡,nmcli connection up执行后可能瞬间断网,SSH 会话随之断开。所以在生产机器上操作,我强烈建议先确认有没有带外管理通道,或者在变更前准备一条延时回滚命令。资深的做法是:把旧配置备份好,变更失败时能在几分钟内恢复,而不是握着断开的 SSH 窗口束手无策。
4. 两条配置路线混用时的常见坑与完整排查链路
4.1 改完文件NM不认:缓存与reload时序
一次真实的排查过程是这样。同事新加了一个 ifcfg 文件,nmcli connection show里怎么都看不到,他在文件边上反复确认格式,还对照别的机器复制粘贴,依然无效。我看了一眼他的操作流程:他编辑文件后直接执行了nmcli connection up 新连接名,但 NetworkManager 此时完全不知道这个文件的存在。
问题就出在时序上。NetworkManager 启动时扫描一次配置文件目录,运行期间不会每秒盯着目录看。手动添加的文件,必须用nmcli connection reload通知它重新扫描。这时再看nmcli connection show,新连接才出现。所以正确顺序一定是:先改文件、再 reload、最后 up。缺少 reload 这一步,后续所有操作都会对着不存在的缓存操作,自然报错。
我在多次踩坑之后归纳了一条检查链路:先nmcli device status确认网卡内核态可见,再nmcli connection show确认配置文件有没有被 NetworkManager 加载,最后用ip addr核对实际生效地址。三步走完,九成问题都能定位到具体环节。
4.2 复制配置文件忘记改UUID导致连接状态诡异
另一个高频坑来自"复用配置文件"。为了省事,有人喜欢把一台机器的 ifcfg-eth0 直接 scp 到另一台机器,改个 IP 就完事。结果新机器上nmcli connection show里出现了两条一模一样的连接,或者旧的连接突然失联。
根因十有八九是 UUID 重复。UUID 在 NetworkManager 内部是连接的主键,两个文件共享同一个 UUID,系统无法区分它们。很多衍生问题也因此变得诡异:配置没错、文件没错,但状态怎么都对不上。
所以我处理这类任务时,有个固定动作:复制文件后立即执行uuidgen并替换 UUID,同时检查 DEVICE、NAME 是否匹配目标机器。脚本化批量部署时,每个节点的配置文件都要动态生成 UUID,绝不能直接复用模板里的固定值。顺便说一句,模板里可以用变量占位,由脚本在部署时把变量替换成新生成的 UUID,这样既保证了格式统一,又避免了主键冲突。
4.3 DNS被覆盖和默认路由冲突的根因
配置层面还有一个隐蔽问题,表现为"IP 通了但 DNS 解析总是不对"。我之前遇到一台机器,明明 /etc/resolv.conf 里写的是内网 DNS,隔一段时间就被改回别的地址。打开 ifcfg 文件一看,原来少了PEERDNS=no这个字段。DHCP 环境下,客户端默认会从 DHCP 服务器获取 DNS 并覆盖本地配置,PEERDNS=no就是阻止这种行为。
至于默认路由冲突,表现更为直接。双网卡机器,每块网卡各自配了 GATEWAY,默认路由被多次写入,实际上只有一条能生效,另一条链路就成了"能通但没流量走"的摆设。解决办法是让其中一块网卡承担默认路由,另一块只做内网通信,在 ifcfg 文件中通过 DEFROUTE=no 关闭非核心网卡的默认路由,或者给两条路由设置不同的 metric 权重。
排查这类问题时,不用急着改文件,先执行ip route看当前默认路由指向,再对照 ifcfg 文件确认哪个 GATEWAY 参与了默认路由生成,基本一眼定位。
5. 可套用的网卡配置脚本参考
5.1 脚本要解决什么问题
到这一步,我把日常经验收敛成一个可以直接使用的脚本。这个脚本解决的核心问题就是:一台新机器装好系统后,如何用一条命令、一套参数,快速生成规范的静态IP配置,并完成生效和验证。它的设计目标有三个:参数化、安全备份、结果可验证。
所谓参数化,就是网卡名、IP地址、网关、DNS全部通过命令行参数传入,不需要打开文件手动改。所谓安全备份,是脚本执行前自动把当前配置文件目录压缩备份,给变更上一道保险。所谓结果可验证,是脚本最后会自动显示 IP 地址和路由信息,让你立刻判断配置是否正确生效。
5.2 完整脚本
把下面的内容保存为configure-nic.sh,加执行权限后就能用。
#!/usr/bin/env bash # # 网卡静态IP配置脚本 # 用法: # ./configure-nic.sh -i eth0 -a 192.168.100.10 -p 24 \ # -g 192.168.100.1 -d "10.10.10.2 10.10.10.3" # # -i 网卡名 -a IP地址 -p 前缀长度 # -g 网关 -d DNS服务器(多个用空格隔开) # set -euo pipefail CONN_NAME="" DEVICE_NAME="" IP_ADDR="" PREFIX_LEN="" GATEWAY_IP="" DNS_SERVERS="" BACKUP_DIR="/root/nic-backup-$(date +%Y%m%d%H%M%S)" usage() { cat <<'EOF' 用法: configure-nic.sh -i <网卡名> -a <IP> -p <前缀> [-g <网关>] [-d <DNS>] EOF exit 1 } while getopts "c:i:a:p:g:d:h" opt; do case "$opt" in c) CONN_NAME="$OPTARG" ;; i) DEVICE_NAME="$OPTARG" ;; a) IP_ADDR="$OPTARG" ;; p) PREFIX_LEN="$OPTARG" ;; g) GATEWAY_IP="$OPTARG" ;; d) DNS_SERVERS="$OPTARG" ;; h) usage ;; *) usage ;; esac done # 参数校验:网卡名、IP、前缀是必填项 [ -n "$DEVICE_NAME" ] || usage [ -n "$IP_ADDR" ] || usage [ -n "$PREFIX_LEN" ] || usage # 必须用root执行,否则写不了配置文件 if [ "$EUID" -ne 0 ]; then echo "请使用root用户执行该脚本" exit 1 fi # 备份当前全部网卡配置文件 mkdir -p "$BACKUP_DIR" cp -a /etc/sysconfig/network-scripts/ifcfg-* "$BACKUP_DIR"/ 2>/dev/null || true echo "配置文件已备份到: $BACKUP_DIR" # 如果未指定连接名,默认使用 ethX-static 的命名方式 if [ -z "$CONN_NAME" ]; then CONN_NAME="${DEVICE_NAME}-static" fi # 为连接生成唯一UUID NIC_UUID=$(uuidgen) # 处理DNS,支持最多4个DNS服务器 DNS_LINES="" if [ -n "$DNS_SERVERS" ]; then idx=1 for dns in $DNS_SERVERS; do [ "$idx" -gt 4 ] && break DNS_LINES="${DNS_LINES}DNS${idx}=${dns}"$'\n' idx=$((idx + 1)) done fi # 写入配置文件 CONF_FILE="/etc/sysconfig/network-scripts/ifcfg-${DEVICE_NAME}" cat > "$CONF_FILE" <<EOF DEVICE=${DEVICE_NAME} NAME=${CONN_NAME} TYPE=Ethernet UUID=${NIC_UUID} ONBOOT=yes BOOTPROTO=none IPADDR=${IP_ADDR} PREFIX=${PREFIX_LEN} EOF # 网关和DNS只有在传入时才写入 if [ -n "$GATEWAY_IP" ]; then cat >> "$CONF_FILE" <<EOF GATEWAY=${GATEWAY_IP} DEFROUTE=yes EOF fi if [ -n "$DNS_LINES" ]; then cat >> "$CONF_FILE" <<EOF ${DNS_LINES}PEERDNS=no EOF fi echo "配置已写入: $CONF_FILE" cat "$CONF_FILE" # 让NetworkManager重新读取配置并激活 nmcli connection reload nmcli connection up "$CONN_NAME" # 验证结果 echo "---- 当前IP地址 ----" ip -4 addr show "$DEVICE_NAME" echo "---- 当前路由 ----" ip route5.3 脚本流程解读
脚本虽然不长,但每一段都有实际用途。备份动作放在最前面,因为配置变更最容易出现"改坏了却回不去"的情况,自动备份能让你少长很多记性。UUID 用uuidgen生成,每次执行都是全新主键,避免复制模板导致的主键冲突。
DNS 处理那块,先用循环把传入的多个DNS拆成 DNS1、DNS2 这样的独立行,再追加PEERDNS=no,保证后续DHCP不会覆盖手工指定的DNS。配置文件写入主体后,网关字段和DNS字段用追加方式写入,这样做的好处是:不带-g或-d参数时,文件里不会留下一堆空字段,保持配置尽量干净。
最后nmcli connection reload和nmcli connection up顺序不能颠倒,这是前面反复强调过的时序问题。验证部分把 IP 地址和路由表直接打印出来,不用再额外敲命令。
6. 实测经验小结与高频问题速查
6.1 我习惯的变更前检查清单
配置网卡踩过的坑多了以后,我给自己立了几条规矩,这里一并分享。
第一条,永远先备份。不管是手改文件还是跑脚本,改之前花十秒钟备份整个 network-scripts 目录,成本极低,收益极高。第二条,变更前确认带外通道。如果当前网卡是 SSH 连接在用的,改 IP 前务必确认有控制台或带外管理可用,否则配置一失效,你可能连恢复的机会都没有。第三条,一个网卡只用一种管理方式。要么完全交给 NetworkManager,用 nmcli 管;要么在 ifcfg 文件中保持 NM_CONTROLLED=yes。很多"灵异问题"其实就是 nmcli 改完、又手动改文件,两边覆盖来覆盖去导致的。
6.2 高频问题速查表
| 现象 | 可能原因 | 处理思路 |
|---|---|---|
| 改完ifcfg文件 nmcli 看不到新连接 | 未执行 reload,NetworkManager 还在用旧缓存 | 执行nmcli connection reload后重试 |
| 新配置执行 up 后网卡地址没变 | BOOTPROTO 仍是 dhcp,或 method 未设为 manual | 把 BOOTPROTO 改为 none 并确保字段完整 |
| 复制其他机器的配置后状态诡异 | UUID 冲突或 DEVICE 未改 | uuidgen生成新UUID,核对DEVICE和NAME |
| 静态IP配好了,DNS解析总被改 | DHCP 客户端覆盖了 resolv.conf | 在配置文件中添加PEERDNS=no |
| 双网卡都配了网关,默认路由总错 | 多块网卡的 GATEWAY 竞争默认路由 | 非核心网卡设置 DEFROUTE=no 或调整路由 metric |
| 网卡显示设备存在,但无连接管理 | NM_CONTROLLED=no 或 NetworkManager 未接管该设备 | 设置 NM_CONTROLLED=yes 后重载配置 |
最后再分享一个小经验:如果你和我一样要经常跟网络配置打交道,建议在测试环境里把 nmcli 的常用命令反复敲熟,再故意制造几个小故障练手——改错网关、把 DNS 指向一个不存在的地址、把两个连接设为同 IP,然后靠命令排查出问题。整个流程跑一遍之后,你对两张配置路线的理解会通透很多,遇到真实故障时心态也稳得多。