虚拟化入门介绍

虚拟化使得在一台物理的服务器上可以跑多台虚拟机,虚拟机共享物理机的CPU、内存、IO硬件资源,但逻辑上虚拟机之间是相互隔离的。
物理机一般称为宿主机(Host),宿主机上面的虚拟机称为客户机(Guest),那么Host是如何将自己的硬件资源虚拟化,并提供给Guest使用的呢?
这个主要是通过一个叫做Hypervisor的程序实现的,根据Hypervisor的实现方式和所处的位置,虚拟化又分为两种:1型虚拟化和2型虚拟化。

虚拟化类型

1型虚拟化

Hypervisor直接安装在物理机上,多个虚拟机在Hypervisor上运行。
Hypervisor实现方式一般是一个特殊定制的Linux系统,Xen和VMWare的ESXi都属于这个类型,如下图所示。

2型虚拟化

物理机上首先安装常规的操作系统,比如Redhat、Ubuntu或Windows。
Hypervisor作为OS上的一个程序模块运行,并对虚拟机进行管理。KVM、和VMWare Workstation都属于这个类型,如下图所示。

KVM

基本概念

在x86平台上最热门、运用最广泛的虚拟化方案莫过于KVM了。
KVM全称是Kernel-Based Virtual Machine,也就是说KVM是基于Linux内核实现的。
KVM有一个内核模块叫kvm.ko,只用于管理虚拟CPU和内存。
那IO的虚拟化,比如存储和网络设备由谁实现呢?这个就交给Linux内核和Qemu来实现。
说白了,作为一个Hypemsor,KVM本身只关注虚拟机调度和内存管理这两个方面,IO外设的任务交给Linux内核和Qemu。

Libvirt

Libvirt是啥?简单地说就是KVM的管理工具。
其实,Libvirt除了能管理KVM这种Hypervisor,还能管理Xen,VirtualBox等,OpenStack底层也使用Libvirt。

Libvirt包含3个东西:后台daemon程序libvirtdAPI库和命令行工具virsh

  • libviltd是服务程序,接收和处理API请求;
  • API库使得其他人可以开发基于Libvirt的高级工具,比如virt-manager,这是个图形化的KVM管理工具;
  • virsh是经常要用的KVM命令行工具;

KVM实践

1.准备KVM实验环境

首先,需要安装Linux操作系统(本文的实验环境使用Ubuntu 14.04 LTS)。
然后,安装KVM需要的包:

$ sudo apt-get install qemu-kvm qemu-system libvirt-bin virt-manager bridge-utils vlan

小窍门:Ubuntu默认是不允许root通过ssh直接登录的,可以修改/etc/ssh/sshd-config设置:

$ PermitRootLogin yes

然后重启ssh服务:

$ service ssh restart

用以下命令确认CPU支持虚拟化:

$ egrep -c '(vmx|svm)' /proc/cpuinfo
4

如果返回的结果大于0,则表示CPU支持虚拟化。如果结果为0,则表示CPU不支持虚拟化,无法继续进行后续的设置。

确认Libvirtd服务己经启动:

$ service libvirt-bin status
libvirt-bin start/running, process 5285

2.启动第一个KVM虚拟机

首先通过命令virt-manager启动图形界面,新建第一个名称为kvm1的虚拟机,使用cirros镜像文件进行启动。



用命令virsh管理虚机,比如查看宿主机上的虚机:

$ virsh list --all
 Id    Name                           State
----------------------------------------------------
 5     km1                            running

至此,第一个虚机己经跑起来了,采用的都是默认设置。

3.远程管理KVM虚拟机

通过virt-manager可以在本地主机上创建并管理KVM虚机,其实virt-manager也可以管理其他宿主机上的虚机。只需要简单地将宿主机添加进来,填入宿主机的相关信息,确定即可。

接下来,就可以像管理本地虚机一样去管理远程宿主机上的虚机了。

这里有一个要配置的地方:因为KVM(准确说是Libvirt)默认不接受远程管理,需要按下面的内容配置被管理宿主机中的两个文件。

# /etc/default/libvirt-bin
start_libvirtd="yes"
libvirtd_opts="-d -l"

# /etc/libvirt/libvirtd.conf
listen_tls = 0
listen_tcp = 1
unix_sock_group = "libvirtd"
unix_sock_ro_perms = "0777"
unix_sock_rw_perms = "0770"
auth_unix_ro = "none"
auth_unix_rw = "none"
auth_tcp = "none"

然后重启Libvirtd服务就可以远程管理了。

$ service libvirt-bin restart

KVM虚拟化原理

1.CPU虚拟化

KVM的虚拟化是需要CPU硬件支持,执行命令egrep -o '(vmx|svm)' /proc/cpuinfo可以确认CPU是否支持虚拟化。如果有输出vmx或者svm,就说明当前的CPU支持KVM。CPU厂商lntel和AMD都支持虚拟化了,除非是非常老的CPU。
一个KVM虚机在宿主机中其实是一个qemu-kvm进程,与其他Linux进程一样被调度。

$ virsh list
 Id    Name                           State
----------------------------------------------------
 2     km1                            running

$ ps uax |grep km1
libvirt+   3703 13.9  4.5 1033860 184972 ?      Sl   21:01   0:22 qemu-system-x86_64 -enable-kvm -name km1 -S -machine pc-i440fx-trusty,accel=kvm,usb=off -m 500 -realtime mlock=off -smp 1,sockets=1,cores=1,threads=1 -uuid d3a9ed5a-8c45-9884-c5f8-d4996cc225bb -no-user-config -nodefaults -chardev socket,id=charmonitor,path=/var/lib/libvirt/qemu/km1.monitor,server,nowait -mon chardev=charmonitor,id=monitor,mode=control -rtc base=utc -no-shutdown -boot strict=on -device piix3-usb-uhci,id=usb,bus=pci.0,addr=0x1.0x2 -drive file=/var/lib/libvirt/images/cirros-0.6.1-x86_64-disk.img,if=none,id=drive-ide0-0-0,format=qcow2 -device ide-hd,bus=ide.0,unit=0,drive=drive-ide0-0-0,id=ide0-0-0,bootindex=1 -netdev tap,fd=24,id=hostnet0 -device rtl8139,netdev=hostnet0,id=net0,mac=52:54:00:f2:4e:b8,bus=pci.0,addr=0x3 -chardev pty,id=charserial0 -device isa-serial,chardev=charserial0,id=serial0 -vnc 127.0.0.1:0 -device cirrus-vga,id=video0,bus=pci.0,addr=0x2 -device intel-hda,id=sound0,bus=pci.0,addr=0x4 -device hda-duplex,id=sound0-codec0,bus=sound0.0,cad=0 -device virtio-balloon-pci,id=balloon0,bus=pci.0,addr=0x5
chench     3938  0.0  0.0  15956  2448 pts/1    S+   21:04   0:00 grep --color=auto km1

虚机中的每一个虚拟vCPU则对应qemu-kvm进程中的一个线程,如下图所示:

如上图,宿主机有两个物理CPU,上面起了两个虚机VM1和VM2。
VM1有两个vCPU,VM2有4个vCPU。可以看到VM1和VM2分别有2个和4个线程在两个物理CPU上调度。
这里也演示了另一个知识点,即虚机的vCPU总数可以超过物理CPU数量,这个叫CPU超配(overcommit)。KVM允许overcommit,这个特性使得虚机能够充分利用宿主机的CPU资源,但前提是在同一时刻,不是所有的虚机都满负荷运行。当然,如果每个虚机都很忙,反而会影响整体性能,所以在使用overcommit的时候,需要对虚机的负载情况有所了解,需要测试。

2.内存虚拟化

KVM通过内存虚拟化共享物理系统内存,动态分配给虚拟机,如下图所示。

为了在一台机器上运行多个虚拟机,KVM需要实现VA(虚拟内存)-> PA(物理内存)-> MA(机器内存)之间的地址转换。虚机OS控制虚拟地址到客户机内存物理地址的映射(VA -> PA),但是虚机OS不能直接访问实际机器内存,因此KVM需要负责映射客户机物理内存到实际机器内存的映射(PA -> MA)。
内存也是可以overcommit的,即所有虚机的内存之和可以超过宿主机的物理内存。但使用时也需要充分测试,否则性能会受影响。

3.存储虚拟化

KVM的存储虚拟化是通过存储池(Storage Pool)和卷(Volume)来管理的。
Storage Pool是宿主机上可以看到的一片存储空间,可以是多种类型;Volume是在Storage Pool中划分出的一块空间,宿主机将Volume分配给虚拟机,Volume在虚拟机中看到的就是一块硬盘。

目录类型的Storage Pool

文件目录是最常用的Storage Pool类型,KVM将宿主机目录/var/lib/libvirt/images/作为默认的Storage Pool,Volume就是该目录下面的文件了,一个文件就是一个Volume。
那KVM是怎么知道要把/var/lib/libvirt/images目录当作默认Storage Pool的呢?
实际上KVM所有可以使用的Storage Pool都定义在宿主机的/etc/libvirt/storage目录下,每个Pool一个xml文件,默认有一个default.xml

$ ls -l /etc/libvirt/storage/
total 8
drwxr-xr-x 2 root root 4096 1128 21:56 autostart
-rw------- 1 root root  647 1128 21:56 default.xml

default.xml文件内容如下:

$ cat /etc/libvirt/storage/default.xml 
<!--
WARNING: THIS IS AN AUTO-GENERATED FILE. CHANGES TO IT ARE LIKELY TO BE
OVERWRITTEN AND LOST. Changes to this xml configuration should be made using:
  virsh pool-edit default
or other application using the libvirt API.
-->

<pool type='dir'>
  <name>default</name>
  <uuid>7d6bd034-2a9a-0e12-c4c9-02f13de02ac3</uuid>
  <capacity unit='bytes'>0</capacity>
  <allocation unit='bytes'>0</allocation>
  <available unit='bytes'>0</available>
  <source>
  </source>
  <target>
    <path>/var/lib/libvirt/images</path>
    <permissions>
      <mode>0711</mode>
      <owner>-1</owner>
      <group>-1</group>
    </permissions>
  </target>
</pool>

注意: Storage Pool的类型是”dir”,目录的路径就是/var/lib/libvirt/images

如下为虚机km1添加一个新的磁盘,看看有什么变化。

在默认Pool中创建一个4GB的卷,在/var/lib/libvirt/images/下多了一个4GB的文件kml.img

$ ls -l /var/lib/libvirt/images/
total 4243268
-rw-r--r-- 1 root root   50135040 1129 21:22 cirros-0.6.1-x86_64-disk.img # 这个文件是虚拟机的启动磁盘
-rw------- 1 root root 4294967296 1129 21:24 km1.img # 这个文件就是新添加的虚拟机磁盘

使用文件做Volume有很多优点:存储方便、移植性好、可复制、可远程访问。
远程访问的意思是镜像文件不一定都放置到宿主机本地文件系统中,也可以存储在通过网络连接的远程文件系统,比如NFS,或者是分布式文件系统中,比如GlusterFS。这样镜像文件就可以在多个宿主机之间共享,便于虚机在不同宿主机之间做Live Migration;如果是分布式文件系统,多副本的特性还可以保证镜像文件的高可用。

KVM支持多种volume文件格式,在添加Volume时可以选择,如下图所示。

raw是默认格式,即原始磁盘镜像格式,移植性好,性能好,但大小固定,不能节省磁盘空间。
qcow2是推荐使用的格式,cow表示copy on write,能够节省磁盘空间,支持AES加密,支持zlib压缩,支持多快照,功能很多。
vmdk是VMWare的虚拟磁盘格式,也就是说VMWare虚机可以直接在KVM上运行。

LVM类型的Storage Pool

关于磁盘LVM管理技术,详见:使用RAID与LVM磁盘阵列技术
不仅一个文件可以分配给客户机作为虚拟磁盘,宿主机上VG(卷组)中的LV(逻辑卷)也可以作为虚拟磁盘分配给虚拟机使用。不过,LV由于没有磁盘的MBR引导记录,不能作为虚拟机的启动盘,只能作为数据盘使用。这种配置下,宿主机上的VG(卷组)就是一个Storage Pool,VG中的LV(逻辑卷)就是Volume。

如下举例具体说明。

首先,在宿主机上创建了一个容量为20GB的VG,命名为HostVG

$ vgdisplay 
  --- Volume group ---
  VG Name               HostVG  # 卷组名称
  System ID             
  Format                lvm2
  Metadata Areas        2
  Metadata Sequence No  1
  VG Access             read/write
  VG Status             resizable
  MAX LV                0
  Cur LV                0
  Open LV               0
  Max PV                0
  Cur PV                2
  Act PV                2
  VG Size               19.99 GiB  # 卷组大小
  PE Size               4.00 MiB
  Total PE              5118
  Alloc PE / Size       0 / 0   
  Free  PE / Size       5118 / 19.99 GiB
  VG UUID               xPk59u-sCWV-Yqdn-YqaP-icfT-37qg-39YRd6

然后创建一个Storage Pool的定义文件/etc/libvirt/storage/HostVG.xml,内容如下:

<pool type="logical">
    <name>HostVG</name>
    <source>
        <name>HostVG</name>
        <format type="lvm2"/>
    </source>
    <target>
        <path>/dev/HostVG</path>
    </target>
</pool>

然后通过virsh命令创建新的Storage Pool “HostVG”。

$ virsh pool-list --all
 Name                 State      Autostart 
-------------------------------------------
 default              active     yes

$ virsh pool-define /etc/libvirt/storage/HostVG.xml 
Pool HostVG defined from /etc/libvirt/storage/HostVG.xml

$ virsh pool-list --all
 Name                 State      Autostart 
-------------------------------------------
 default              active     yes       
 HostVG               inactive   no  

并启用这个“HostVG”:

$ virsh pool-start HostVG
Pool HostVG started

$ virsh pool-list --all
 Name                 State      Autostart 
-------------------------------------------
 default              active     yes       
 HostVG               active     no 

现在可以在virt-manager中为虚机km1添加LV的虚拟磁盘了。
可以看到“HostVG”己经在Stroage Poo1的列表中了,选择HostVG。

在虚拟机上新Volume添加成功:

同时在宿主机上也对应多了一个命名为“newlv”的LV:

$ lvdisplay HostVG
  --- Logical volume ---
  LV Path                /dev/HostVG/newlv
  LV Name                newlv # 这个逻辑卷是在virt-manager中给虚拟机添加的LVM类型的Volume
  VG Name                HostVG
  LV UUID                8ILUXm-rrCJ-6h0p-I3F0-jQuq-mn36-v73lMh
  LV Write Access        read/write
  LV Creation host, time chench-virtual-machine, 2023-11-29 22:15:24 +0800
  LV Status              available
  # open                 0
  LV Size                100.00 MiB
  Current LE             25
  Segments               1
  Allocation             inherit
  Read ahead sectors     auto
  - currently set to     256
  Block device           252:0
其他类型的Storage Pool

KVM还支持iSCSICeph等多种类型的storagePool,最常用的就是目录类型,其他类型可以参考文档Storage Management

4.网络虚拟化

在网络虚拟化中有2个非常重要的概念:Linux BridgeVLAN

Linux Bridge
1.基本概念

假设宿主机有1块与外网连接的物理网卡eth0,上面跑了1个虚机VM1,现在有个问题是:如何让VM1能够访问外网?
至少有两种方案:
(1)将物理网卡eth0直接分配给VM1,但实施这个方案随之带来的问题很多:宿主机就没有网卡,无法访问了;新的虚机,比如VM2也没有网卡。
(2)给VM1分配一个虚拟网卡vnet0,通过Linux Bridge br0eth0vnet0连接起来,这个是推荐的方案。

Linux Bridge是Linux上用来做TCP/IP二层协议交换的设备,其功能可以简单地理解为是一个二层交换机或者Hub。多个网络设备可以连接到同一个Linux Bridge,当某个设备发送数据包时,Linux Bridge会将数据转发给其他设各。如上图,当有数据到达eth0时,br0会将数据转发给vnet0,这样VM1就能接收到来自外网的数据;反过来,VM1发送数据给vnet0br0也会将数据转发到eth0,从而实现了VM1与外网的通信。

现在增加一个虚拟机VM2,VM2的虚拟网卡vnet1也连接到了br0上,现在VM1和VM2之间可以通信,同时VM1和VM2也都可以与外网通信。

2.配置Linux Bridge br0

编辑宿主机/etc/network/interfaces,配置br0。

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet manual

auto br0
iface br0 inet dhcp
bridge_stp off
bridge_waitport 0
bridge_fd 0
bridge_ports eth0

有两点需要注意:

  • 之前宿主机的IP是通过dhcp配置在eth0上的;创建Linux Bridge之后,IP就必须放到br0上了。
  • br0的配置信息中请注意最后一行bridge_ports eth0,其作用就是将eth0挂到br0上。

重启宿主机之前,查看IP地址信息(IP地址分配在eth0上):

$ ifconfig
eth0      Link encap:Ethernet  HWaddr 00:0c:29:ad:92:29  
          inet addr:192.168.3.146  Bcast:192.168.3.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fead:9229/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:2057 errors:0 dropped:0 overruns:0 frame:0
          TX packets:1221 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:210632 (210.6 KB)  TX bytes:197569 (197.5 KB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:481 errors:0 dropped:0 overruns:0 frame:0
          TX packets:481 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1 
          RX bytes:29066 (29.0 KB)  TX bytes:29066 (29.0 KB)

virbr0    Link encap:Ethernet  HWaddr 2e:96:6d:e2:c2:ef  
          inet addr:192.168.122.1  Bcast:192.168.122.255  Mask:255.255.255.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

重启宿主机,查看IP配置,可以看到IP地址己经放到br0上了。

$ ifconfig
br0       Link encap:Ethernet  HWaddr 00:0c:29:ad:92:29  
          inet addr:192.168.3.146  Bcast:192.168.3.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fead:9229/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:99 errors:0 dropped:0 overruns:0 frame:0
          TX packets:135 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:9952 (9.9 KB)  TX bytes:18654 (18.6 KB)

eth0      Link encap:Ethernet  HWaddr 00:0c:29:ad:92:29  
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:104 errors:0 dropped:0 overruns:0 frame:0
          TX packets:137 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:11662 (11.6 KB)  TX bytes:20360 (20.3 KB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:6 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1 
          RX bytes:300 (300.0 B)  TX bytes:300 (300.0 B)

virbr0    Link encap:Ethernet  HWaddr 16:e5:5b:5f:d7:b2  
          inet addr:192.168.122.1  Bcast:192.168.122.255  Mask:255.255.255.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

brctl show查看当前Linux Bridge的配置,eth0己经挂到br0上了。

$ brctl show
bridge name	  bridge id		      STP enabled	  interfaces
br0		      8000.000c29ad9229	  no		      eth0
virbr0		  8000.000000000000	  yes		

除了br0,注意到还有一个virbr0的Bridge,而且virbr0上己经配置了IP地址:192.168.122.1。

目前宿主机中已经创建了2台虚拟机,并且都处于关机状态:

$ virsh list --all
 Id    Name                           State
----------------------------------------------------
 -     VM1                            shut off
 -     VM2                            shut off
3.配置VM1

在virt-manager中查看VM1的网卡配置,看到虚拟网卡的source,选择br0
启动VM1,看看会发生什么?

$ virsh start VM1
Domain VM1 started
$ brctl show
bridge name	  bridge id		      STP enabled	  interfaces
br0		      8000.000c29ad9229	  no		      eth0
							                      vnet0
virbr0		  8000.000000000000	  yes		

brctl show告诉我们,br0下面添加了一个vnet0设备,通过virsh确认这就是VM1的虚拟网卡。

$ virsh domiflist VM1
Interface  Type       Source     Model       MAC
-------------------------------------------------------
vnet0      bridge     br0        rtl8139     52:54:00:f2:4e:b8

VM1的IP是DHCP获得的(设置静态IP当然也可以),通过virt-manager控制台登录VM1,查看其IP。

VM1通过DHCP拿到的IP是192.168.3.148,与宿主机(IP为192.168.3.146)是同一个网段,Ping一下外网也是可以访问的。

另外,在VM1中虚拟网卡是eth0,并不是vnet0vnet0是该虚拟网卡在宿主机中对应的设备名称,其类型是TAP设备,需要注意。

4.配置VM2

跟VM1一样,VM2的虚拟网卡也挂在br0上,启动VM2,查看网卡信息。

$ virsh start VM2
Domain VM2 started

$ brctl show
bridge name	bridge id		    STP     enabled	interfaces
br0		    8000.000c29ad9229	no		eth0
							            vnet0
							            vnet1
virbr0		8000.000000000000	yes

br0下面多了vnetl,通过virsh确认这就是VM2的虚拟网长。

$ virsh domiflist VM2
Interface  Type       Source     Model       MAC
-------------------------------------------------------
vnet1      bridge     br0        rtl8139     52:54:00:eb:50:25

VM2通过DHCP拿到的IP是192.168.3.149,登录VM2,验证网络的连通性。
Ping VM1的IP地址:192.168.3.148。

Ping 宿主机的IP地址:192.168.3.146。

Ping外网:

可见,通过br0这个Linux Bridge,实现了VM1、VM2、宿主机和外网这四者之间的数据通信。

5.理解virbr0

virbr0是KVM默认创建的一个Bridge,其作用是为连接其上的虚机网卡提供NAT访问外网的功能。
virbr0默认分配了一个IP地址192.168.122.1,并为连接其上的其他虚拟网卡提供DHCP服务。

如下演示如何使用virbr0

在virt-manager打开VM1的配置界面,网卡Source device选择”default”,将VM1的网卡挂在virbr0上。
启动VM1,使用brctl show可以查看到vnet0己经挂在了virbr0上。

$ brctl show
bridge name	  bridge id		      STP enabled	  interfaces
br0		      8000.000c29ad9229	  no		      eth0
virbr0		  8000.fe5400f24eb8	  yes		      vnet0

virsh命令确认vnet0就是VM1的虚拟网卡。

$ virsh domiflist VM1
Interface  Type       Source     Model       MAC
-------------------------------------------------------
vnet0      network    default    rtl8139     52:54:00:f2:4e:b8

virbr0使用dnsmasq提供DHCP服务,可以在宿主机中查看该进程信息。

$ ps -elf |grep dnsmasq
5 S libvirt+   2616      1  0  80   0 -  7054 -      11月30 ?      00:00:00 /usr/sbin/dnsmasq --conf-file=/var/lib/libvirt/dnsmasq/default.conf
0 S chench    26781   2679  0  80   0 -  3990 pipe_w 21:57 pts/1    00:00:00 grep --color=auto dnsmasq

/var/lib/libvirt/dnsmasq/目录下有一个default.leases文件,当VM1成功获得DHCP的IP后,可以在该文件中查看到相应的信息。

$ cat /var/lib/libvirt/dnsmasq/default.leases 
1701442358 52:54:00:f2:4e:b8 192.168.122.94 * 01:52:54:00:f2:4e:b8

上面显示192.168.122.94已经分配给了MAC地址为52:54:00:f2:4e:b8的网卡了,这正是vnet0的MAC地址,之后就可以使用该IP访问VM1了。

$ ssh cirros@192.168.122.94
cirros@192.168.122.94's password: 

Ping以下外网:

$ ping baidu.com
PING 110.242.68.66 (110.242.68.66) 56(84) bytes of data.
64 bytes from 110.242.68.66 (110.242.68.66): icmp_seq=1 ttl=127 time=16.0 ms
64 bytes from 110.242.68.66 (110.242.68.66): icmp_seq=2 ttl=127 time=15.0 ms

没有问题,可以访问外网,说明NAT起作用了。

需要说明的是,使用NAT的虚机VM1可以访问外网,但外网无法直接访问VM1。
因为VM1发出的网络包源地址并不是192.168.122.94,而是被NAT替换为宿主机的IP地址了。这个与使用br0不一样,在br0的情况下,VM1通过自己的IP直接与外网通信,不会经过NAT地址转换。

VLAN
1.基本概念

LAN表示Local Area Network,本地局域网,通常使用Hub和Switch来连接LAN中的计算机。一般来说,两台计算机连入同一个Hub或者Switch时,它们就在同一个LAN中。一个LAN表示一个广播域,其含义是:LAN中的所有成员都会收到任意一个成员发出的广播包。
VLAN表示Virtual LAN,一个带有VLAN功能的Switch能够将自己的端口划分出多个LAN。计算机发出的广播包可以被同一个LAN中其他计算机收到,但位于其他LAN的计算机则无法收到。
简单地说,VLAN将一个交换机分成了多个交换机,限制了广播的范围,在二层上将计算机隔离到不同的LAN中。
比方说,有两组机器,GroupA和GroupB。想配置成GroupA中的机器可以相互访问,GroupB中的机器也可以相互访问,但是A和B中的机器无法互相访问。
一种方法是使用两个交换机,A和B分别接到一个交换机。
另一种方法是使用一个带VLAN功能的交换机,将A和B的机器分别放到不同的VLAN中。

请注意,VLAN的隔离是二层上的隔离,A和B无法相互访问指的是二层广播包(比如arp)无法跨越VLAN的边界。但在三层上(比如IP)是可以通过路由器让A和B互通的。

现在的交换机几乎都是支持VLAN的,通常交换机的端口有两种配置模式:Access和Trunk,如下图所示。

  • Access口

这些端口被打上了VLAN的标签,表明该端口属于哪个VLAN。
不同VLAN用VLAN ID来区分,VLAN ID的范围是1 ~ 4096。

Access口都是直接与计算机网卡相连的,这样从该网卡出来的数据包流入Access口后,就会被打上了所在VLAN的标签。

  • Trunk口

假设有两个交换机A和B。
A上有VLAN1(红)、VLAN2(黄)、VLAN3(蓝),B上也有VLAN1、VLAN2、VLAN3。那如何让A和B上相同VLAN之间能够通信呢?办法是将A和B连起来,而且连接A和B的端口要允许VLAN1、2、3三个VLAN的数据都能够通过。这样的端口就是Trunk口了。
VLAN1、2、3的数据包在通过Trunk口到达对方交换机的过程中始终带着自己的VLAN标签。

下图展示了KVM是如何实现VLAN的。

eth0是宿主机上的物理网卡,有一个命名为eth0.10的子设备与之相连。
eth0.10就是VLAN设备了,其VLAN ID就是VLAN 10。
eth0.10挂在命名为brvlan10的Linux Bridge上,虚机VM1的虚拟网卡vnet0也挂在brvlan10上。

这样的配置,其效果就是:

  • 宿主机用软件实现了一个交换机(当然是虚拟的),上面定义了一个VLAN 10。
  • eth0.10,brvlan10和vnet0都分别接到VLAN 10的Access口上,而eth0就是一个Trunk口。
  • VM1通过vnet0发出来的数据包会被打上VLAN 10的标签。

eth0.10的作用是:定义了VLAN 10。
brvlan10的作用是:Bridge上的其他网络设备自动加入到VLAN 10中。

再增加一个VLAN 20,如下图所示。

这样虚拟交换机就有两个VLAN了,VM1和VM2分别属于VLAN 10和VLAN 20。
对于新创建的虚机,只需要将其虚拟网卡放入相应的Bridge,就能控制其所属的VLAN。
VLAN设备总是以母子关系出现,母子设备之间是一对多的关系。一个母设备(eth0)可以有多个子设备(eth0.10,eth0.20,……),而一个子设备只有一个母设备。

2.配置VLAN

编辑/etc/network/interfaces,配置eth0.10brvlan10eth0.20brvlan20

auto lo
iface lo inet loopback

auto eth0
iface eth0 inet dhcp

auto eth0.10
iface eth0.10 inet manual
vlan-raw-device eth0

auto brvlan10
iface brvlan10 inet manual
bridge_stp off
bridge_waitport 0
bridge_fd 0
bridge_ports eth0.10

auto eth0.20
iface eth0.20 inet manual
vlan-raw-device eth0

auto brvlan20
iface brvlan20 inet manual
bridge_stp off
bridge_waitport 0
bridge_fd 0
bridge_ports eth0.20

重启宿主机,查看各个网络接口。

$ ifconfig
brvlan10  Link encap:Ethernet  HWaddr 00:0c:29:ad:92:29  
          inet6 addr: fe80::20c:29ff:fead:9229/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:26 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:4321 (4.3 KB)

brvlan20  Link encap:Ethernet  HWaddr 00:0c:29:ad:92:29  
          inet6 addr: fe80::20c:29ff:fead:9229/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:27 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:4391 (4.3 KB)

eth0      Link encap:Ethernet  HWaddr 00:0c:29:ad:92:29  
          inet addr:192.168.3.146  Bcast:192.168.3.255  Mask:255.255.255.0
          inet6 addr: fe80::20c:29ff:fead:9229/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:214 errors:0 dropped:0 overruns:0 frame:0
          TX packets:323 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:22362 (22.3 KB)  TX bytes:45140 (45.1 KB)

eth0.10   Link encap:Ethernet  HWaddr 00:0c:29:ad:92:29  
          inet6 addr: fe80::20c:29ff:fead:9229/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:51 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:8532 (8.5 KB)

eth0.20   Link encap:Ethernet  HWaddr 00:0c:29:ad:92:29  
          inet6 addr: fe80::20c:29ff:fead:9229/64 Scope:Link
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:53 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:8712 (8.7 KB)

lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          inet6 addr: ::1/128 Scope:Host
          UP LOOPBACK RUNNING  MTU:65536  Metric:1
          RX packets:6 errors:0 dropped:0 overruns:0 frame:0
          TX packets:6 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1 
          RX bytes:300 (300.0 B)  TX bytes:300 (300.0 B)

virbr0    Link encap:Ethernet  HWaddr c6:60:f5:f3:c2:32  
          inet addr:192.168.122.1  Bcast:192.168.122.255  Mask:255.255.255.0
          UP BROADCAST MULTICAST  MTU:1500  Metric:1
          RX packets:0 errors:0 dropped:0 overruns:0 frame:0
          TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

brctl show查看当前Linux Bridge的配置。

# brctl show
bridge name	    bridge id		    STP enabled	  interfaces
brvlan10		8000.000c29ad9229	no		      eth0.10
brvlan20		8000.000c29ad9229	no		      eth0.20
virbr0		    8000.000000000000	yes		

eth0.10eth0.20分别挂在brvlan10brvlan20上了。

在宿主机中己经提前创建好了虚机VM1和VM2,现在都处于关机状态。

$ virsh list --all
 Id    Name                           State
----------------------------------------------------
 -     VM1                            shut off
 -     VM2                            shut off
3.配置VM1

在virt-manager中,将VM1的虚拟网卡挂到brvlan10上。

启动VM1:

$ virsh start VM1
Domain VM1 started

查看Bridge,发现brvlan10己经连接了一个vnet0设备:

$ brctl show
bridge name	    bridge id		    STP enabled	  interfaces
brvlan10		8000.000c29ad9229	no		      eth0.10
							                      vnet0
brvlan20		8000.000c29ad9229	no		      eth0.20
virbr0		    8000.000000000000	yes		

通过virsh确认这就是VM1的虚拟网卡:

$ virsh domiflist VM1
Interface  Type       Source     Model       MAC
-------------------------------------------------------
vnet0      bridge     brvlan10   rtl8139     52:54:00:f2:4e:b8
4.配置VM2

类似的,将VM2的网长挂在brvlan20上。

启动VM2:

$ virsh start VM2
Domain VM2 started

查看Bridge,发现brvlan20己经连接了一个vnetl设备:

$ brctl show
bridge name	    bridge id		    STP enabled	  interfaces
brvlan10		8000.000c29ad9229	no		      eth0.10
							                      vnet0
brvlan20		8000.000c29ad9229	no		      eth0.20
							                      vnet1
virbr0		    8000.000000000000	yes		

通过virsh确认这就是VM2的虚拟网卡:

$ virsh domiflist VM2
Interface  Type       Source     Model       MAC
-------------------------------------------------------
vnet1      bridge     brvlan20   rtl8139     52:54:00:eb:50:25
5.验证VLAN的隔离性

为了验证VLAN 10和VLAN 20之间的隔离,为VM1和VM2配置同一网段的IP。
配置VM1的IP:

$ ifconfig eth0 192.168.100.10 netmask 255.255.255.0 up

配置VM2的IP:

$ ifconfig eth0 192.168.100.20 netmask 255.255.255.0 up

Ping测试结果(在VM2中Ping VM1):VM1与VM2是不通的。

原因如下:
(1)VM2向VM1发Ping包之前,需要知道VM1的IP“192.168.100.10”所对应的MAC地址。VM2会在网络上广播ARP包,其作用就是询问“谁知道192.168.100.10的MAC地址是多少?”
(2)ARP是二层协议,VLAN的隔离作用使得ARP只能在VLAN 20范围内广播,只有brvlan20eth0.20能收到,VLAN 10里的设备是收不到的,所以VM1无法应答VM2发出的ARP包。
(3)VM2拿不到VM1 vnet0的MAC地址,也就Ping不到VM1。

Linux Bridge+VLAN=虚拟交换机

现在对KVM的网络虚拟化做个总结。

(1)物理交换机存在多个VLAN,每个VLAN拥有多个端口。
同一VLAN端口之间可以交换转发,不同VLAN端口之间隔离。所以交挾机包含两层功能:交换与隔离。

(2)Linux的VLAN设备实现的是隔离功能,但没有交换功能。
一个VLAN母设备(比如eth0)不能拥有两个相同ID的VLAN子设备,因此也就不可能出现数据交换情况。

(3)Linux Bridge专门实现交换功能。
将同一VLAN的子设备都挂载到一个Bridge上,设备之间就可以交换数据了。

总结起来,Linux Bridge加VLAN在功能层面可以完整模拟现实世界里的二层交换机。
eth0相当于虚拟交换机上的trunk口,允许Vlan 10和Vlan 20的数据通过。
eth0.10vnet0brvlan10都可以看作Vlan 10的access口。
eth0.20vnet1brvlan20都可以看作Vlan 20的access口。

【参考】
每天5分钟玩转openstack跟学(四)KVM虚拟化原理之存储虚拟化
深入理解虚拟化
Hyper-V和Virtual PC的不同
Xen和KVM等四大虚拟化架构对比分析
请问下,目前国内大部分公有云厂商的底层是基于OpenStack的,还是自研的?


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,在下面评论区告诉我^_^^_^