11 月 19 - 20 日 Apache Pulsar 社区年度盛会来啦,立即报名! 了解详情
写点什么

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

  • 2019-06-29
  • 本文字数:0 字

    阅读完需:约 1 分钟

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

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


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


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


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


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


1 判断容器

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


如果环境有systemd-detect-virt命令,则可以直接通过systemd-detect-virt -c命令判断,如果输出为none则不是容器,否则会输出容器类型,比如 lxc。目前很少容器里面放systemd的,我见过的就只有 LXD 的ubuntu镜像,因此这种方法适用性不广。


除此之外,可通过其他 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/cgroup11:memory:/system.slice/docker-9da...11.scope10:pids:/system.slice/docker-9da...11.scope9:hugetlb:/system.slice/docker-9da...11.scope8:blkio:/system.slice/docker-9da...11.scope7:cpuset:/system.slice/docker-9da...11.scope6:devices:/system.slice/docker-9da...11.scope5:perf_event:/system.slice/docker-9da...11.scope4:freezer:/system.slice/docker-9da...11.scope3:cpuacct,cpu:/system.slice/docker-9da...11.scope2:net_prio,net_cls:/system.slice/docker-9da...11.scope1: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'[ 1547.858418] busybox[6]: 11:perf_event:/[ 1547.858758] busybox[6]: 10:hugetlb:/[ 1547.859055] busybox[6]: 9:memory:/machine.slice/machine-rkt\x2dd6863650\x2da1fa\x2d4a12\x2db754\x2da4e6641023cd.scope[ 1547.859309] busybox[6]: 8:cpuset:/[ 1547.859549] busybox[6]: 7:pids:/machine.slice/machine-rkt\x2dd6863650\x2da1fa\x2d4a12\x2db754\x2da4e6641023cd.scope[ 1547.859781] busybox[6]: 6:net_cls,net_prio:/[ 1547.860038] busybox[6]: 5:blkio:/machine.slice/machine-rkt\x2dd6863650\x2da1fa\x2d4a12\x2db754\x2da4e6641023cd.scope[ 1547.860278] busybox[6]: 4:cpu,cpuacct:/machine.slice/machine-rkt\x2dd6863650\x2da1fa\x2d4a12\x2db754\x2da4e6641023cd.scope[ 1547.860509] busybox[6]: 3:freezer:/[ 1547.860756] busybox[6]: 2:devices:/machine.slice/machine-rkt\x2dd6863650\x2da1fa\x2d4a12\x2db754\x2da4e6641023cd.scope[ 1547.861026] busybox[6]: 1:name=systemd:/machine.slice/machine-rkt\x2dd6863650\x2da1fa\x2d4a12\x2db754\x2da4e6641023cd.scope/init.scope
复制代码


如上的\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-8a8cb27:hugetlb:/6:freezer:/sandbox-55f57b5:devices:/4:cpuset:/3:cpuacct:/sandbox-cf5b482:cpu:/sandbox-root-HaLK4d/sandbox-305dc71:blkio:/
复制代码


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


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


  • 判断是否可运行systemd-detect-virt -c命令,如果输出为none则不是容器,否则可确定容器类型。

  • 判断PID 1如果为应用本身,则该虚拟环境是容器,否则不能确定是否是容器。

  • 判断是否存在加载的内核文件,如果不存在,则可判断为容器,否则不能确定是否为容器。

  • 判断是否存在/.dockerenv文件,如果存在则为 Docker 容器,否则不能确定是否为容器。

  • 读取/proc/1/cgroup文件,判断是否包含dockerrkt等关键字,如果包含,则说明为容器,否则不能确定是否为容器。


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


2 判断物理机

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


systemd-detect-virtnone
复制代码


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


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


lscpu | grep -Piq 'Hypervisor vendor' \    && echo "Virtual Machine" \    || echo "Physical Machine"
复制代码


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


# dmidecode -t systemSystem InformationManufacturer: HPProduct Name: ProLiant DL380 Gen9Version: Not SpecifiedSerial Number: 6CU6468KKDUUID: 30393137-3136-4336-5536-3436384B4B44Wake-up Type: Power SwitchSKU Number: 719061-B21Family: 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命令。


3 判断虚拟机

其实前面已经提到了,如果使用了 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.1Getting SMBIOS data from sysfs.SMBIOS 2.8 present.
Handle 0x0100, DMI type 1, 27 bytesSystem 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 systemSystem 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 systemSystem 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 && echoi-060a8dca9edf35512
复制代码


获取内网 IP:


$ curl -L 169.254.169.254/latest/meta-data/local-ipv4 && echo192.168.0.111
复制代码


获取 instance type(规格):


$ curl -L 169.254.169.254/latest/meta-data/instance-type && echot2.medium
复制代码


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


$ curl -L 169.254.169.254/latest/meta-data/public-ipv4 && echo52.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


4 总结

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


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


#!/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 "$@"
复制代码


2019-06-29 08:0010697

评论

发布
暂无评论
发现更多内容

Spring 5(四)JdbcTemplate

浅辄

Java Spring5 11月月更

从源码角度看React-Hydrate原理

flyzz177

React

Python进阶(三十六)Web框架Django项目搭建全过程

No Silver Bullet

Python django 11月月更

20道前端高频面试题(附答案)

loveX001

JavaScript

问:React的useState和setState到底是同步还是异步呢?

beifeng1996

React

Python进阶(三十四)Python3多线程解读

No Silver Bullet

多线程 Python3 11月月更

React源码分析(一)Fiber

goClient1992

React

深入react源码看setState究竟做了什么?

flyzz177

React

高频react面试题自检

beifeng1996

React

【C语言】float 关键字

謓泽

11月月更

优化开发人员对 K8s 安全的影响

HummerCloud

Kubernetes DevSecOps 11月月更

图学习初探Paddle Graph Learning 构建属于自己的图【系列三】

汀丶

图神经网络 随机游走算法 异质图

谈谈前端性能优化-面试版

loveX001

JavaScript

经常会采坑的javascript原型应试题

loveX001

JavaScript

问:你是如何进行react状态管理方案选择的?

beifeng1996

React

【设计模式】原型模式:猴头,我叫你一声你敢答应吗?

游坦之

11月月更

说说Vue响应式系统中的Watcher和Dep的关系-面试进阶

bb_xiaxia1998

Vue

CSS知识框架(一)

默默的成长

CSS 前端 11月月更

React-Hooks源码深度解读

goClient1992

React

看透react源码之感受react的进化

goClient1992

React

前端高频面试题合集(中高级必备)

loveX001

JavaScript

说说你对Vue的keep-alive的理解

bb_xiaxia1998

Vue

一份vue面试考点清单

bb_xiaxia1998

Vue

Map接口概述和基本使用

共饮一杯无

Java map 11月月更

Git学习笔记

lxmoe

git 学习笔记 常用命令 11月月更

Spring 5(三)AOP

浅辄

Java Spring5 11月月更

Map接口的子类HashMap和LinkedHashMap

共饮一杯无

Java 11月月更 Map集合

中高级前端开发需要掌握的vue知识点

bb_xiaxia1998

Vue

Python进阶(三十五)Fiddler命令行和HTTP断点调试

No Silver Bullet

Python fiddler 11月月更

HTML知识框架 二

默默的成长

html 前端 11月月更

一天梳理完react面试题

beifeng1996

React

如何探测虚拟环境是物理机、虚拟机还是容器?_云计算_付广平_InfoQ精选文章