虚拟化使得在一台物理的服务器上可以跑多台虚拟机,虚拟机共享物理机的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程序libvirtd
、API库
和命令行工具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 11月 28 21:56 autostart
-rw------- 1 root root 647 11月 28 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 11月 29 21:22 cirros-0.6.1-x86_64-disk.img # 这个文件是虚拟机的启动磁盘
-rw------- 1 root root 4294967296 11月 29 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还支持iSCSI、Ceph等多种类型的storagePool,最常用的就是目录类型,其他类型可以参考文档Storage Management。
4.网络虚拟化
在网络虚拟化中有2个非常重要的概念:Linux Bridge和VLAN。
Linux Bridge
1.基本概念
假设宿主机有1块与外网连接的物理网卡eth0,上面跑了1个虚机VM1,现在有个问题是:如何让VM1能够访问外网?
至少有两种方案:
(1)将物理网卡eth0直接分配给VM1,但实施这个方案随之带来的问题很多:宿主机就没有网卡,无法访问了;新的虚机,比如VM2也没有网卡。
(2)给VM1分配一个虚拟网卡vnet0
,通过Linux Bridge br0
将eth0
和vnet0
连接起来,这个是推荐的方案。
Linux Bridge是Linux上用来做TCP/IP二层协议交换的设备,其功能可以简单地理解为是一个二层交换机或者Hub。多个网络设备可以连接到同一个Linux Bridge,当某个设备发送数据包时,Linux Bridge会将数据转发给其他设各。如上图,当有数据到达eth0
时,br0
会将数据转发给vnet0
,这样VM1就能接收到来自外网的数据;反过来,VM1发送数据给vnet0
,br0
也会将数据转发到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
,并不是vnet0
。vnet0
是该虚拟网卡在宿主机中对应的设备名称,其类型是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.10
、brvlan10
、eth0.20
和brvlan20
。
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.10
和eth0.20
分别挂在brvlan10
和brvlan20
上了。
在宿主机中己经提前创建好了虚机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范围内广播,只有brvlan20
和eth0.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.10
,vnet0
和brvlan10
都可以看作Vlan 10的access口。eth0.20
,vnet1
和brvlan20
都可以看作Vlan 20的access口。
【参考】
每天5分钟玩转openstack跟学(四)KVM虚拟化原理之存储虚拟化
深入理解虚拟化
Hyper-V和Virtual PC的不同
Xen和KVM等四大虚拟化架构对比分析
请问下,目前国内大部分公有云厂商的底层是基于OpenStack的,还是自研的?
转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达,在下面评论区告诉我^_^^_^