接手一台没摸过的服务器,或者要在工单里说清楚"出问题的机器是什么环境",第一件事通常不是排查,而是先把这台机器的底细摸出来。

这件事看着简单,出错率却不低。最常见的错误是把 uname -r 的输出当成系统版本报上去——那个数字是内核版本,跟发行版版本完全是两回事。CentOS 7.9 可以跑 3.10 内核,也可以跑 5.4 内核,只看 uname -r 你根本判断不出它到底是 7 还是 8。

再比如要给一批机器写个批量判断脚本,第一反应多半是去 grep PRETTY_NAME。但 os-release(5) 手册里明确写了:这个字段是"suitable for presentation to the user",给用户看的;要在脚本里判断系统或版本,应该用 ID 和 VERSION_ID。照着前一种写法做,遇到 Arch、openSUSE Tumbleweed 这类滚动发行版会直接取到空值——手册里同样写了,厂商可能不提供版本信息,“Applications should not rely on these fields to be set”。

下面按从外到内的顺序,把该看的几层一次说清。

一、发行版版本:先分清现在看的是哪一层

第一层是发行版,也就是 CentOS、Ubuntu、Debian、Alibaba Cloud Linux 这些。

最通用的查看方式是读这个文件(字段定义以 os-release(5) 手册 为准):

cat /etc/os-release

输出是一组形如 KEY="value" 的赋值行。关于这个文件,有几点是手册原文写明的,值得单独拎出来:

/etc/os-release 优先于 /usr/lib/os-release。 原文是 “Applications should check for the former, and exclusively use its data if it exists, and only fall back to /usr/lib/os-release if that is missing. Applications should not combine the data from both files.”——注意最后那句,两个文件的数据不要合并着用。按手册的说法,/etc/os-release 本来就是一个指向 /usr/lib/os-release 的相对符号链接,之所以保留前者,是为了兼容只认这个路径的老程序。

哪些字段给人看,哪些给脚本用,分得很清楚。 NAME 和 PRETTY_NAME 的定义里都写着 “suitable for presentation to the user”,是拿去显示的;ID 和 VERSION_ID 的定义里写着 “suitable for processing by scripts or usage in generated filenames”,是给脚本处理的。手册在注意事项里又把话说了一遍:

If you are using this file to determine the OS or a specific version of it, use the ID and VERSION_ID fields, possibly with ID_LIKE as fallback for ID. When looking for an OS identification string for presentation to the user use the PRETTY_NAME field.

ID_LIKE 这个后备字段容易被忽略。它解决的是衍生发行版的问题——Ubuntu 的 ID 是 ubuntu,但它的 ID_LIKE 是 debian;脚本里判断"是不是 Debian 系",靠 ID 会漏掉,得先看 ID 再退到 ID_LIKE。

它不是 shell 脚本。 虽然格式像,但手册明确写了除变量赋值外不支持任何 shell 特性,“this means variable expansion is explicitly not supported”。所以 . /etc/os-release 之后 $VERSION_ID 能读到值,但别指望里面写 $NAME 这种引用能展开。

各发行版还有自己的老文件,作为补充交叉验证很方便:

文件适用发行版
/etc/redhat-releaseRHEL、CentOS、Alibaba Cloud Linux 等 rpm 系
/etc/debian_versionDebian 系(只有版本号,没有名字)
/etc/centos-releaseCentOS

二、一份能贴着用的查看命令清单

有了上面的区分,再来看具体命令。这一批命令回答的都是同一个问题,但可靠程度不一样:

cat /etc/os-release          # 最通用,首选
hostnamectl                  # 一次给出 OS / Kernel / Architecture
lsb_release -a               # 需要装了 lsb 包,最小化安装常常没有
cat /etc/redhat-release      # rpm 系专用
cat /proc/version            # 内核编译信息,含 gcc 版本

hostnamectl 是这里最省事的一个,装了 systemd 的机器上一条命令就把操作系统、内核、架构三项一起列出来。反过来说,容器镜像里如果没有 systemd,这条就用不了——这时候回到第一行。

lsb_release 属于"能用但不是总有":它依赖单独的 lsb-release 包,很多精简镜像为了省空间没装。所以写脚本的时候别用这个。

至于 linux服务器操作系统查看命令 到底该挑哪一条,判断标准就两个:交给人看的用 hostnamectl,交给脚本的读 /etc/os-release 的 ID 和 VERSION_ID。

顺便说说 linux服务器操作系统有哪些 这个常被问到的点。云服务器控制台的镜像列表里,主流的是这几类:Alibaba Cloud Linux、TencentOS Server、CentOS 及其衍生版(Rocky / Alma)、Ubuntu Server、Debian,以及 RHEL。选镜像时真正影响后续工作量的不是名字,而是包管理器是 yum/dnf 还是 apt、以及这个版本距离 EOL 还有多久——CentOS 7 停止维护之后,存量机器上最常遇到的问题是软件源取不到包。

还有一个场景要警觉:容器里读到的 /etc/os-release 是镜像自己的,不是宿主机的。 一个跑在 Ubuntu 宿主机上的 CentOS 容器,进去执行第一行得到的是 CentOS。要在容器编排的节点列表里核对宿主机系统,这个命令是不可靠的,得靠 uname -r——容器与宿主机共享同一个内核,所以内核版本反而是准的(这也解释了为什么容器里 uname -r 改不了)。

三、内核版本:uname 那几个参数别混着用

uname -a      # 全部信息
uname -r      # 内核 release,最常用
uname -m      # 硬件架构
uname -v      # 内核编译版本与时间

-a 的输出里包含主机名、内核 release、编译信息、架构等一整串,但多数时候你只需要其中一项,写脚本时建议明确指定参数而不要用 -a 再去做文本切割。

这里有个值得留意的坑:uname -r 报的是当前正在运行的那个内核,不是系统里装的最新内核。 执行过 yum update kernel 之后没有重启,新包已经在 /boot 里了,rpm -q kernel 也能查到,但跑着的还是旧内核。遇到"patch 明明打了怎么漏洞还在"这类问题,先对一下这项:

uname -r
rpm -q kernel        # rpm 系
dpkg -l | grep linux-image   # deb 系

两者不一致,说明缺一次重启。这在打完内核安全补丁后尤其常见。

另外,uname -o 输出操作系统的名字(通常是 GNU/Linux),uname -n 输出主机名。这两个参数单次使用率不高,但比从 -a 的一大串里肉眼挑要稳。

四、运行环境:glibc、架构和虚拟化类型

光知道发行版版本还不够,装二进制包的时候,下面这三项往往才是决定性的。

架构。 uname -m 输出 x86_64、aarch64 还是别的。x86 机器上误装 arm64 的包会报一个看着很莫名 “cannot execute binary file” 的错误——不是文件坏了,是架构不对。

glibc 版本。 很多预编译的二进制包会要求 glibc 最低版本,版本号差一点就跑不起来。三种查法:

ldd --version
getconf GNU_LIBC_VERSION
/lib/x86_64-linux-gnu/libc.so.6    # 路径按发行版调整

第三种看着奇怪,但 libc(7) 手册里就是这么写的:/lib/libc.so.6 这个路径本身通常是符号链接,“executing this pathname will cause glibc to display various information about the version installed on your system”。直接执行它,glibc 自己会把版本号打出来。

这里有一条不太有人提、但挺重要的警告。ldd(1) 手册的安全小节原文是:

Thus, you should never employ ldd on an untrusted executable, since this may result in the execution of arbitrary code.

原因在于某些情况下(比如程序指定了非标准的 ELF 解释器),ldd 会直接执行这个程序去获取依赖信息。对来历不明的二进制文件做检查时,应该用这个替代:

objdump -p /path/to/program | grep NEEDED

代价是只看得到直接依赖,拿不到 ldd 那种完整的依赖树,但至少不会赔进去一台机器。

虚拟化类型。

虚拟化类型在日常运维里存在感不强,但它能解释一些奇怪的现象。比如在容器里 systemd-detect-virt 返回 docker 或 containerd 时,就意味着这台"机器"和宿主机共享内核——此时容器内部看到的 /proc、/sys 并不是隔离的,某些依赖 /proc 的监控项取值会失真。另外像云盘热插拔、网卡多队列这些能力,也只有在识别出确实是虚拟机时才能预期支持。

systemd-detect-virt

云服务器上通常返回 kvm。这个命令能识别的类型有一张挺长的表,其中容易误解的一点是——kvm 这个返回值按手册定义专指 “Linux KVM in combination with QEMU”,同样基于 KVM 接口的 AWS Nitro 单独归类为 amazon。所以看到 kvm 只说明是 KVM + QEMU 的组合,不是所有用到 KVM 的都叫 kvm。

还有一条:多层嵌套时只报最内层。“only the innermost is detected and identified”——在云主机里跑 Docker,命令返回的是容器类型,想看外层的虚拟化要加 --vm。写监控脚本依赖这个返回值时,记得它是有退出码语义的:检测到返回 0,检测不到返回非 0。

五、系统自己不知道的信息:各家云的实例元数据

前面四层查的都是操作系统内部能拿到的东西。但有一类信息机器自己是不知道的——实例 ID、所在地域可用区、内网 IP、实例规格——这些只有云平台知道。好在每台云主机都能访问一个本机专用的地址去问,这就是实例元数据服务。

三家云都提供了,但入口各不相同,这是跨平台迁移脚本时最容易踩的一处:

云服务商元数据入口路径风格是否支持凭证加固模式
阿里云 ECShttp://100.100.100.200/latest/meta-data/EC2 兼容支持
腾讯云 CVMhttp://metadata.tencentyun.com/latest/meta-data/EC2 兼容文档未见对应模式
华为云 ECShttp://169.254.169.254/openstack/latest/meta_data.jsonOpenStack 为主,兼容 EC2支持

上面这张表的出处分别是:阿里云《实例元数据》、腾讯云《查看实例元数据》、华为云 ECS 元数据说明。

三个差别要留意。

一、地址不是一个。 华为云用的是 169.254.169.254,这个 link-local 地址是多数云平台(含 AWS)沿用的习惯;阿里云用的是 100.100.100.200,不是标准的 link-local 段。照抄 AWS 的脚本到阿里云上是不通的。

二、腾讯云用的是域名,而且拼写要注意。 是 tencentyun.com,中间多了个 en。这个域名在腾讯云的时间同步配置里也是同一个写法,手打基本必错,现象是域名解析失败、所有元数据项取不到。

三、路径风格不一样。 阿里云和腾讯云的路径都以 /latest/meta-data/ 开头,取某一项就往后拼;华为云主推的是 OpenStack 风格,一次性返回一个 JSON(meta_data.json),同时也提供 EC2 兼容路径。要根据集群规格或实例 ID 做判断的话,华为云这边得从 JSON 里取 meta 字段。

华为云的文档还写明了安全组要求:出方向需要放行 TCP 80 到 169.254.0.0/16。用默认安全组的话这条默认满足,但凡自定义过出方向规则,元数据取不到时先查这里。

最后说说安全。元数据服务挂在实例内部、不走公网,但它有一个很典型的攻击面:如果机器上跑的应用有服务端请求伪造(SSRF)漏洞——比如一个"从外部 URL 下载图片"的功能没做限制——攻击者就能诱使这台机器替他去访问元数据服务。阿里云对此的描述相当直接,原文大意是:攻击者可利用这类应用漏洞窃取实例所绑定 RAM 角色的临时访问凭证,若该角色权限过高,“可能获得云资源控制权限,甚至接管整个云账号”。

所以阿里云和华为云都提供了带凭证的访问模式(阿里云叫加固模式、华为云叫 V2 加固模式),先申请一个临时 token 再带着它请求元数据。启用后普通模式的请求会被拒绝,返回 403。两家的差异在 token 请求方式:

# 阿里云
TOKEN=`curl -X PUT "http://100.100.100.200/latest/api/token" -H "X-aliyun-ecs-metadata-token-ttl-seconds:21600"`
curl -H "X-aliyun-ecs-metadata-token: $TOKEN" http://100.100.100.200/latest/meta-data/instance-id

# 华为云
TOKEN=`curl -X PUT http://169.254.169.254/meta-data/latest/api/token -H "X-Metadata-Token-Ttl-Seconds:21600"`
curl -X GET http://169.254.169.254/openstack/latest/meta_data.json -H "X-Metadata-Token:$TOKEN"

注意两家的请求头参数名不一样——X-aliyun-ecs-metadata-token-ttl-seconds 与 X-Metadata-Token-Ttl-Seconds,复制粘贴的时候别混。另外请求里不能带 X-Forwarded-For 头,带了会被拒。

你若是不想在元数据这块开这么大的面,华为云文档里给了一条挺实用的 iptables 写法,只允许 root 访问:

iptables --append OUTPUT --proto tcp --destination 169.254.169.254 \
  --match owner ! --uid-owner root --jump REJECT

六、一份上手就跑的清单

把上面几层串起来,接手一台新机器按顺序执行:

# 1. 发行版与版本(给人看用这行)
cat /etc/os-release | head -3
#   (写脚本取 ID 和 VERSION_ID,别取 PRETTY_NAME)

# 2. 内核与架构
uname -r && uname -m

# 3. 装了的内核包 vs 正在跑的内核(rpm 系)
rpm -q kernel

# 4. glibc 与虚拟化类型
getconf GNU_LIBC_VERSION
systemd-detect-virt

# 5. 云平台侧的实例信息
curl -s http://100.100.100.200/latest/meta-data/instance-id

如果是如何查看linux服务器操作系统这类一次性确认,执行完前两行基本就够回工单了。要做资产盘点或者配置核查,才有必要把第五层也做进去,并且记得把三家云的入口差异写成配置项,别硬编码。


有一点要说在前头:上面几层里,机器自己报的信息是可以被改写的——/etc/os-release 理论上能被人为编辑,hostname 更不用说。做合规审计或者跨机器一致性核对时,只有来自云平台元数据的那部分(实例 ID、规格、地域)是机器自身改不了的。这也是为什么第五节讲的元数据值得单独记一下,哪怕平时用不上。

文中的地址、参数与具体要求以各家云平台当期官方文档为准。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐