35

如何探测虚拟化环境是物理机、虚拟机还是容器?

 4 years ago
source link: https://www.tuicool.com/articles/e6FfUvR
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

目前裸机(物理机)、虚拟机、容器是云计算提供计算服务的三种主流形式。最近有人问,如何判断一个虚拟shell环境到底是物理机、虚拟机还是容器?

更进一步,如果是物理机,这个物理机厂商是什么,虚拟机到底是KVM还是XEN,容器是Docker还是rkt、LXC等?

更进一步,如果是虚拟机,是否可以判断这个虚拟机是运行在AWS还是阿里或者OpenStack,是否能够获取虚拟机的UUID、instance-type、vpc-id、安全组等信息?

这有点像我们在开发中经常使用的反射(reflection)机制,通过反射可以知道一个类实例(instance)的类(class)是什么,更进一步可以知道这个类的父类是什么、实现了哪些方法、包含哪些属性等。

以下是我用到的一些方法,仅供参考。

判断容器

目前应该还没有什么方法能够100%准确判断虚拟环境是否是容器,至少我没有找到相关文献。

如果环境有systemd-detect-virt命令,则可以直接通过systemd-detect-virt -c命令判断,如果输出为none则不是容器,否则会输出容器类型,比如lxc。目前很少容器里面放systemd的,我见过的就只有LXD的ubuntu镜像,因此这种方法适用性不广。 如果你想和更多Docker技术专家交流,可以加我微信liyingjiese,备注『加群』。群里每周都有全球各大公司的最佳实践以及行业最新动态

除此之外,可通过其他tricks判断,最简便的方法判断PID为1的进程,如果该进程就是应用进程则判断是容器,而如果是init进程或者systemd进程,则不一定是容器,当然不能排除是容器的情况,比如LXD实例的进程就为/sbin/init。

容器通过PID namespace实现与宿主机以及其他容器的进程PID隔离,但是所有容器的进程在宿主机是完全可见的,并且PID不一样。

容器和虚拟机不一样的是,容器和宿主机是共享内核的,因此理论上容器内部是没有内核文件的,除非挂载了宿主机的/boot目录:

KERNEL_PATH=$(cat /proc/cmdline | tr ' ' '\n' | awk -F '=' '/BOOT_IMAGE/{print $2}')

test -e $KERNEL_PATH && echo "Not Sure" || echo "Container"

另外,我们知道容器是通过Cgroup实现资源限制,每个容器都会放到一个Cgroup组中,如果是Docker,则Cgroup的名称为docker-xxxx,其中xxxx为Docker容器的UUID。而控制容器的资源,本质就是控制运行在容器内部的进程资源,因此我们可以通过查看容器内部进程为1的Cgroup名称获取线索。

如下是我通过Docker跑BusyBox的Cgroup信息:

# docker run -t -i --rm busybox cat /proc/1/cgroup

11:memory:/system.slice/docker-9da...11.scope

10:pids:/system.slice/docker-9da...11.scope

9:hugetlb:/system.slice/docker-9da...11.scope

8:blkio:/system.slice/docker-9da...11.scope

7:cpuset:/system.slice/docker-9da...11.scope

6:devices:/system.slice/docker-9da...11.scope

5:perf_event:/system.slice/docker-9da...11.scope

4:freezer:/system.slice/docker-9da...11.scope

3:cpuacct,cpu:/system.slice/docker-9da...11.scope

2:net_prio,net_cls:/system.slice/docker-9da...11.scope

1:name=systemd:/system.slice/docker-9da...11.scope

我们不仅可以知道这是Docker容器,还获取了Docker容器的UUID为9ba...11。

根据如上的结论,判断一个虚拟环境是否Docker的脚本为:

cat /proc/1/cgroup | grep -qi docker \

&& echo "Docker" \

|| echo "Not Docker"

当然如果仅仅判断是否Docker容器,还能通过判断是否存在.dockerenv文件区分是否Docker容器:

[[ -f /.dockerenv ]] && echo "Docker" || echo "Not Docker"

rkt容器类似,输出结果如下:

# rkt --insecure-options=image run docker://busybox  --exec cat -- '/proc/1/cgroup'

如上的\x2d为-号:

{{{# python -c 'print("\x2d".decode())'

-

# echo "machine-rkt\x2dd6863650\x2da1fa\x2d4a12\x2db754\x2da4e6641023cd.scope" \

| sed 's/\\x2d/-/g'

machine-rkt-d6863650-a1fa-4a12-b754-a4e6641023cd.scope

因此判断一个虚拟环境是否rkt的脚本为:

cat /proc/1/cgroup | \

grep -q 'machine-rkt' \

&& echo 'rkt' \

|| echo 'not rkt'

好奇AWS lambda的运行环境是什么,于是写了个函数输出/proc/1/cgroup,结果为:

9:perf_event:/ 8:memory:/sandbox-8a8cb2 7:hugetlb:/ 6:freezer:/sandbox-55f57b 5:devices:/ 4:cpuset:/ 3:cpuacct:/sandbox-cf5b48 2:cpu:/sandbox-root-HaLK4d/sandbox-305dc7 1:blkio:/

猜测是一种叫BusyBox的运行环境,估计也是一种容器。

判断虚拟环境是否为容器环境相对比较复杂,目前没有完美的方案,总结过程如下:

  • 判断是否可运行systemd-detect-virt -c命令,如果输出为none则不是容器,否则可确定容器类型。
  • 判断PID 1如果为应用本身,则该虚拟环境是容器,否则不能确定是否是容器。
  • 判断是否存在加载的内核文件,如果不存在,则可判断为容器,否则不能确定是否为容器。
  • 判断是否存在/.dockerenv文件,如果存在则为Docker容器,否则不能确定是否为容器。
  • 读取/proc/1/cgroup文件,判断是否包含Docker、rkt等关键字,如果包含,则说明为容器,否则不能确定是否为容器。

另外,需要特别注意的是,容器必须最先判断,因为容器本身并没有任何的硬件虚拟化,容器看到的硬件特性信息和宿主机看到的完全一样,因此下面介绍的通过lscpu以及DMI信息判断是否是虚拟机或者物理机,对容器并不适用。换句话说,不能因为lscpu的Hypervisor vendor值为KVM就说明一定是KVM虚拟机,因为它也有可能是容器。下文均假设已经排除为容器的情况。

判断物理机

如果使用了systemd,则可以直接通过systemd-detect-virt命令判断是否物理机:

systemd-detect-virt

none

如果输出为none,则说明是物理机。

当然也可根据lscpu命令输出,看是否有Hypervisor vendor属性,如果没有该属性,则一般为物理机,如果存在该属性则一定是虚拟机:

lscpu | grep -Piq 'Hypervisor vendor' \

&& echo "Virtual Machine" \

|| echo "Physical Machine"

获取物理机的信息最直接的方式是查看DMI信息/sys/firmware/dmi/tables/DMI,使用dmidecode命令解码:

# dmidecode -t system

System Information

Manufacturer: HP

Product Name: ProLiant DL380 Gen9

Version: Not Specified

Serial Number: 6CU6468KKD

UUID: 30393137-3136-4336-5536-3436384B4B44

Wake-up Type: Power Switch

SKU Number: 719061-B21

Family: ProLiant

如上可以看出这是台物理机,厂商为HP,型号为ProLiant DL380 Gen9,序列号为6CU6468KKD。

通过ipmitool命令可以查看物理服务器的带外IP:

ipmitool lan print \

| grep -Pi 'IP Address\s+:' \

| cut -d ':' -f 2 \

| tr -d ' '

192.168.0.35

当然如果是虚拟机,如上命令会执行失败。

另外也可以通过其他命令查看物理信息,如lshw命令。

判断虚拟机

其实前面已经提到了,如果使用了systemd,则可以直接通过systemd-detect-virt命令判断是否虚拟机:

systemd-detect-virt

如果是虚拟机,则会输出虚拟机类型,如KVM、Oracle(VirtualBox)、Xen等。

当然也可根据lscpu命令输出,查看Hypervisor vendor属性值:

lscpu | grep -i 'Hypervisor vendor' | cut -d ':' -f 2 | tr -d ' '

通过如上命令,我的一台AWS虚拟机输出为Xen,阿里云虚拟机为KVM,VirtualBox虚拟机也输出为KVM,这是因为我使用了KVM硬件加速虚拟化。

我的搬瓦工虚拟机输出也为KVM,可见搬瓦工主机也是KVM虚拟机。

通过如上方法可以获取虚拟机的虚拟化类型,能否获取更多信息呢?参考物理机的获取方式,我们可以通过dmidecode命令获取更多的虚拟机信息。比如我在一台OpenStack虚拟机运行如下命令:

# dmidecode -t system

# dmidecode 3.1

Getting SMBIOS data from sysfs.

SMBIOS 2.8 present.



Handle 0x0100, DMI type 1, 27 bytes

System Information

    Manufacturer: OpenStack Foundation

    Product Name: OpenStack Nova

    Version: 15.0.1

    Serial Number: 00310981-9899-e411-906e-00163566263e

    UUID: 1a48e29f-a023-48b8-b06b-afa63a9cff00

    Wake-up Type: Power Switch

    SKU Number: Not Specified

    Family: Virtual Machine

如上Manufacturer为OpenStack Foundation,说明运行在OpenStack平台,Version为Nova版本,根据OpenStack的releases可知15.0.1对应为OpenStack Ocata版本,而UUID即虚拟机的UUID。

AWS上的一台虚拟机输出为:

$ sudo dmidecode -t system

System Information

    Manufacturer: Xen

    Product Name: HVM domU

    Version: 4.2.amazon

    Serial Number: ec24ea61-ebbd-d428-e3d0-50ca37b49074

    UUID: ec24ea61-ebbd-d428-e3d0-50ca37b49074

    Wake-up Type: Power Switch

    SKU Number: Not Specified

    Family: Not Specified

在Version中标明了Amazon字样。

阿里云虚拟机如下:

# dmidecode -t system

System Information

Manufacturer: Alibaba Cloud

Product Name: Alibaba Cloud ECS

Version: pc-i440fx-2.1

Serial Number: f1099eb2-f7b6-4b8f-a02e-0a004b66dc6a

UUID: F1099EB2-F7B6-4B8F-A02E-0A004B66DC6A

Wake-up Type: Power Switch

SKU Number: Not Specified

Family: Not Specified

可见虽然可以从system信息中获取云厂商的线索,但其实虚拟机的system信息并没有统一的标准,有的在version中体现,有的在Product Name中表现,完全取决于云厂商自己的配置。

如上整合如下脚本初略判断:

#!/bin/bash



detect_cloud_provider()

{

if dmidecode -t system | grep -qi 'amazon'; then echo "AWS"

    return 0

fi if dmidecode -t system | grep -qi 'openstack'; then echo "OpenStack"

    return 0

fi if dmidecode -t system | grep -qi 'alibaba'; then echo "Aliyun"

    return 0

fi

# ...

echo "Unknown"

return 1

}



detect_cloud_provider $@

如上也可以判断公有云是否基于OpenStack实现,比如华为虚拟机输出为OpenStack,可大致猜测华为的公有云是基于OpenStack实现的。

AWS以及OpenStack系的虚拟机还可以通过metadata或者ConfigDrive获取更多信息,以metadata为例:

获取虚拟机的ID:

$ curl -L 169.254.169.254/latest/meta-data/instance-id && echo i-060a8dca9edf35512

获取内网IP:

$ curl -L 169.254.169.254/latest/meta-data/local-ipv4 && echo

192.168.0.111

获取instance type(规格):

$ curl -L 169.254.169.254/latest/meta-data/instance-type && echo

t2.medium

获取虚拟机的公有IP(弹性IP),这个挺有用的,因为在虚拟机没法通过ifconfig查看弹性IP,经常登录虚拟机后,忘记自己的公有IP:

$ curl -L 169.254.169.254/latest/meta-data/public-ipv4 && echo

52.82.103.54

其他的比如vpc-id、ami id(镜像id)、安全组、公钥名等都可以通过该方式获取。

如果是OpenStack,还可以使用OpenStack的metadata获取更多信息:

# curl -sL 169.254.169.254/openstack/latest/meta_data.json \ | python -m json.tool { "availability_zone": "nova", "devices": [], "hostname": "int32bit-test-1", "launch_index": 0, "meta": { "cinder_img_volume_type": "07c40142-f06e-483c-8c27-2970579e94b1", "volume_type": "07c40142-f06e-483c-8c27-2970579e94b1" }, "name": "int32bit-test-1", "project_id": "ad19a76ab29c4e448d6efc9645369d0e", "random_seed": "...", "uuid": "1a48e29f-a023-48b8-b06b-afa63a9cff00" } 

如上可获取虚拟机的租户ID、volume type等信息。当然邪恶点可以通过查看userdata获取虚拟机初始化root密码。AWS甚至可以查看AccessKeyId以及SecretAccessKey。

总结

如上总结了几种判断虚拟化环境类型的方法,不一定准确,仅供参考,当然也可能还有其他更好的方法。

如下是根据前面的结论写的一个探测虚拟化类型的脚本,不一定健壮完备,仅供参考:

#!/bin/sh



detect_container()

{

if which systemd-detect-virt >/dev/null 2>&1; then TYPE=$(systemd-detect-virt -c)

    if [ "$TYPE" = "none" ]; then return 1

    else echo "Container: $TYPE"

        return 0

    fi fi if [ -n "$container" ]; then echo "Container: $container"

    return 0

fi if grep -qi docker /proc/1/cgroup; then echo "Container: Docker"

    return 0

fi if test -f /.dockerenv; then echo "Container: Docker"

    return 0

fi if grep -qi 'machine-rkt' /proc/1/cgroup; then echo "Container: rkt"

    return 0

fi

# Other container type detect here

return 1

}



detect_physical()

{

if ! lscpu | grep -qi 'Hypervisor vendor'; then echo "Physical: $(cat /sys/class/dmi/id/product_name)"

    return 0

fi return 1

}



detect_virtual_machine()

{

if lscpu | grep -qi 'Hypervisor vendor'; then HYPER_TYPE=$(lscpu | grep -i "Hypervisor vendor" \

        | cut -d ':' -f 2 | sed 's/^ *//g')

    if dmidecode -t system | grep -qi 'amazon'; then echo "Virtual Machine: AWS/$HYPER_TYPE"

    elif dmidecode -t system | grep -qi 'openstack'; then echo "Virtual Machine: OpenStack/$HYPER_TYPE"

    elif dmidecode -t system | grep -qi 'alibaba'; then echo "Virtual Machine: Aliyun/$HYPER_TYPE"

    else Manufacturer=$(dmidecode -t system | grep 'Manufacturer' \

            | cut -d ':' -f 2 | sed 's/^ *//g')

        ProductName=$(dmidecode -t system | grep 'Product Name' \

            | cut -d ':' -f 2 | sed 's/^ *//g')

        Version=$(dmidecode -t system | grep 'Version' \

            | cut -d ':' -f 2 | sed 's/^ *//g')

        echo "Virtual Machine: $Manufacturer $ProductName($Version)/$HYPER_TYPE"

    fi return 0

fi return 1

}



detect_virtual_type()

{

detect_container || detect_physical \

|| detect_virtual_machine || echo "Unknown"

}



detect_virtual_type "$@"

原文链接: 如何探测虚拟化环境是物理机、虚拟机还是容器?


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK