如何探测虚拟环境是物理机、虚拟机还是容器?
目前裸机物理机、虚拟机、容器是云计算提供计算服务的三种主流形式。那么如何判断一个虚拟shell环境到底是物理机、虚拟机还是容器呢更进一步如果是物理机这个物理机厂商是什么虚拟机到底是KVM还是XEN容器是Docker还是rkt、lxc等更进一步如果是虚拟机是否可以判断这个虚拟机是运行在AWS还是阿里或者OpenStack是否能够获取虚拟机的UUID、instance-type、vpc-id、安全组等信息?这有点像我们在开发中经常使用的反射(reflection)机制通过反射可以知道一个类实例(instance)的类(class)是什么更进一步可以知道这个类的父类是什么、实现了哪些方法、包含哪些属性等。以下是我用到的一些方法仅供参考。01 判断容器目前还没有什么方法能够100%准确判断虚拟环境是否是容器至少我没有找到相关文献。如果环境有systemd-detect-virt命令则可以直接通过systemd-detect-virt -c命令判断如果输出为none则不是容器否则会输出容器类型比如lxc。目前很少容器里面放systemd的我见过的就只有LXD的ubuntu镜像因此这种方法适用性不广。除此之外可通过其他tricks判断最简便的方法判断PID为1的进程如果该进程就是应用进程则判断是容器而如果是init进程或者systemd进程则不一定是容器当然不能排除是容器的情况比如LXD/lXC实例的进程就为/sbin/init。容器和虚拟机不一样的是容器和宿主机是共享内核的因此理论上容器内部是没有内核文件的除非挂载了宿主机的/boot目录另外我们知道容器是通过cgroup实现资源限制每个容器都会放到一个cgroup组中如果是Docker则cgroup的名称为docker-xxxx其中xxxx为Docker容器的UUID。而控制容器的资源本质就是控制运行在容器内部的进程资源因此我们可以通过查看容器内部进程为1的cgroup名称获取线索。如下是我通过Docker跑busybox的cgroup信息:我们不仅可以知道这是Docker容器还获取了Docker容器的UUID为9ba...11。根据如上的结论判断一个虚拟环境是否Docker的脚本为:当然如果仅仅判断是否Docker容器还能通过判断是否存在.dockerenv文件区分是否Docker容器:rkt容器类似输出结果如下:如上的\x2d为-号:因此判断一个虚拟环境是否rkt的脚本为好奇AWS lambda的运行环境是什么于是写了个函数输出/proc/1/cgroup结果为:猜测是一种叫sandbox的运行环境估计也是一种容器。判断虚拟环境是否为容器环境相对比较复杂目前没有完美的方案总结过程如下判断是否可运行systemd-detect-virt -c命令如果输出为none则不是容器否则可确定容器类型。判断PID 1如果为应用本身则该虚拟环境是容器否则不能确定是否是容器。判断是否存在加载的内核文件如果不存在则可判断为容器否则不能确定是否为容器。判断是否存在/.dockerenv文件如果存在则为Docker容器否则不能确定是否为容器。读取/proc/1/cgroup文件判断是否包含docker、rkt等关键字如果包含则说明为容器否则不能确定是否为容器。另外需要特别注意的是容器必须最先判断因为容器本身并没有任何的硬件虚拟化容器看到的硬件特性信息和宿主机看到的完全一样因此下面介绍的通过lscpu以及DMI信息判断是否是虚拟机或者物理机对容器并不适用。换句话说不能因为lscpu的Hypervisor vendor值为KVM就说明一定是KVM虚拟机因为它也有可能是容器。下文均假设已经排除为容器的情况。02 判断物理机如果使用了systemd则可以直接通过systemd-detect-virt命令判断是否物理机:如果输出为none则说明是物理机。当然也可根据lscpu命令输出看是否有Hypervisor vendor属性如果没有该属性则一般为物理机如果存在该属性则一定是虚拟机获取物理机的信息最直接的方式是查看DMI信息/sys/firmware/dmi/tables/DMI使用dmidecode命令解码:如上可以看出这是台物理机厂商为HP型号为ProLiant DL380 Gen9序列号为6CU6468KKD。通过ipmitool命令可以查看物理服务器的带外IP:当然如果是虚拟机如上命令会执行失败。另外也可以通过其他命令查看物理信息如lshw命令。03 判断虚拟机其实前面已经提到了如果使用了systemd则可以直接通过systemd-detect-virt命令判断是否虚拟机:如果是虚拟机则会输出虚拟机类型如kvm、oracle(virtualbox)、xen等。当然也可根据lscpu命令输出查看Hypervisor vendor属性值通过如上命令我的一台AWS虚拟机输出为Xen阿里云虚拟机为KVMVirtualBox虚拟机也输出为KVM这是因为我使用了KVM硬件加速虚拟化。我的搬瓦工虚拟机输出也为KVM可见搬瓦工主机也是KVM虚拟机。通过如上方法可以获取虚拟机的虚拟化类型能否获取更多信息呢参考物理机的获取方式我们可以通过dmidecode命令获取更多的虚拟机信息。比如我在一台OpenStack虚拟机运行如下命令:如上Manufacturer为OpenStack Foundation,说明运行在OpenStack平台Version为Nova版本根据OpenStack的releases可知15.0.1对应为OpenStack Ocata版本而UUID即虚拟机的UUID。AWS上的一台虚拟机输出为:在Version中标明了amazon字样。阿里云虚拟机如下感谢L神提供的输出可见虽然可以从system信息中获取云厂商的线索但其实虚拟机的system信息并没有统一的标准有的在version中体现有的在Product Name中表现完全取决于云厂商自己的配置。如上整合如下脚本初略判断如上也可以判断公有云是否基于OpenStack实现比如华为虚拟机输出为OpenStack可大致猜测华为的公有云是基于OpenStack实现的。AWS以及OpenStack系的虚拟机还可以通过metadata或者ConfigDrive获取更多信息以metadata为例获取虚拟机的ID:获取instance type(规格):获取虚拟机的公有IP弹性IP这个挺有用的因为在虚拟机没法通过ifconfig查看弹性IP经常登录虚拟机后忘记自己的公有IP其他的比如vpc-id、ami id(镜像id)、安全组、公钥名等都可以通过该方式获取。如果是OpenStack还可以使用OpenStack的metadata获取更多信息:如上可获取虚拟机的租户ID、volume type等信息。当然邪恶点可以通过查看userdata获取虚拟机初始化root密码。AWS甚至可以查看AccessKeyId以及SecretAccessKey。04 总结如上总结了几种判断虚拟化环境类型的方法不一定准确仅供参考当然也可能还有其他更好的方法。如下是根据前面的结论写的一个探测虚拟化类型的脚本不一定健壮完备仅供参考