http://www.libvirt.org/formatnetwork.html


说明:下面的东西开始有顺序,逻辑,说明,以及解释

 

0x001 libvirt网络基本概念

     libvirt默认使用了一个名为default的nat网络,这个网络默认使用virbr0作为桥接接口,使用dnsmasq来为使用nat网络的虚拟机提供dns及dhcp服务,dnsmasq生效后的配置文件默认保存在以下路径:

/var/lib/libvirt/dnsmasq/default.hostsfile   mac&&ip绑定的配置文件

/var/lib/libvirt/dnsmasq/default.leases  dhcp分配到虚拟机的ip地址列表

/var/lib/libvirt/network/default.xml  default网络的配置文件

      dnsmasq服务的启动脚本在/etc/init.d/dnsmasq ,但是我们如果手动使用此脚本来启动服务将会导致dnsmasq读取其自己的配置文件来启动此服务,因此这么做是不推荐的,因为这个服务完全由libvirtd在接管,

当libvirtd服务启动的时候,它会将它管理的被标记为autostart的network一并启动起来,而启动network的时候就会自动调用dnsmasq并赋予其适宜的配置文件来运行服务。

     使用libvirt管理的网络都会用到dnsmasq来产生相应的配置,比如定义了一个名为route110的network,那么这个route110将使用一个新的桥接接口virbr1来接入网络,并使用dnsmasq产生名为route110.hostsfile和route110.leases的配置文件。

其实这里提到的virbr0和virbr1都是libvirt产生的虚拟网卡,其作用就相当于一个虚拟交换机,为虚拟机提供网络转发服务。

 

0x002 逐渐深入

    首先分析一下libvirt所能提供的网络类型:isolated 和forwarding,其中,isolated意为绝对隔离的网络,也就是说处于此网络内的虚拟机对于外界是隔离的,这种模式可以用到一些特殊的场合,比如

虚拟机只提供给内部使用,虚拟机只要求能相互通信而不需要与互联网通信。

另外一类,forwarding,就是把虚拟机的数据forward到物理网络实现与外部网络进行通讯,其中forwarding又分为两种:nat和routed。

nat,就是把虚拟机的网络数据在经过物理机网络的时候进行ip伪装,这样所有虚拟机出去的网络数据都相当于是物理机出去的数据,也就是说,我们可以分配给使用nat网络的虚拟机一个内网ip,而这

个内网ip的虚拟机访问出去的时候外部网络看到的是物理机的公网ip,这样做的用处就是实现多个虚拟机共享物理主机的公网ip,节省公网ip地址;如前所述,默认情况下libvirt已经提供了一个名为default的

nat网络,在不需要进行任何配置的情况下使用default网络的虚拟机即可访问互联网,但是互联网却无法访问虚拟机提供的服务,这是因为default网络只对虚拟机的数据包进行了伪装,而没有进行dnat和snat。

需要注意的是libvirt所实现的这种nat网络是通过物理机的iptables规则来实现的,也即是在虚拟机数据经过nat表的postrouting链出去的时候对其进行了伪装。

forwarding模式的另外一种,routed,就是将虚拟机的数据直接通过物理机route出去,和nat一样,也是需要一个virbr虚拟网卡接口来与外面进行通信,这种模式的不同之处在于虚拟机的数据没有经过伪装便直接

交给了外部网络,也就是说,使用route模式网络的虚拟机可以使用公网ip地址,而物理机却恰恰在这个时候完全可以使用一个内网ip而不对外提供访问,这样,虚拟机的网卡仅仅把物理机当作一个route数据的工具,

此模式应用的场合很多,比如需要让虚拟机运行在一个dmz网络中。但是使用route模式有诸多限制,例如物理机的网络接口不够用的情况下。

      这里需要注意的是,nat模式和route模式的区别仅仅在于前者使用了iptables对虚拟机的数据包进行了伪装,而后者没有。

 

0x003 Hack It

在实际的虚拟机使用过程中,我们可能会碰到下面的情况:

1 使用nat网络的虚拟机也需要对外提供服务,

2 物理机只有一个网卡和一个ip,而我们现在既需要通过这个网卡来管理虚拟机,又需要使用这个网卡来提供route网络。

当然你所能碰到的问题可能千奇百怪,也可能根本没有碰到过此类bt问题。下面的内容只作为分析和解决问题的思路,不能生搬。

在了解了libvirt的网络管理模式之后,就可以自己动手解决这些限制,下面重点解释第二种问题的解决方法:

首先假定route网络使用的是virbr1虚拟网卡,而虚拟机使用virbr1来为虚拟机提供服务,而我本机又有了一个br0作为em1的桥接网卡来对外提供网络服务,br0的ip是192.168.1.51

首先禁用br0:

ifdown br0

并配置br0的onboot为no

配置文件为onboot=no

然后我们定义了一个名为route的网络,virbr1的ip设置为192.168.1.51 ,这样做的目的是让virbr1取代之前的br0.

<network>
<name>route</name>
<uuid>6224b437-386b-f510-11d5-58d58b1ce87a</uuid>
<forward mode='route'/>
<bridge name='virbr1' stp='on' delay='0' />
<mac address='52:54:00:C8:9F:07'/>
<ip address='192.168.1.51' netmask='255.255.255.0'>
<dhcp>
<range start='192.168.1.128' end='192.168.1.254' />
</dhcp>
</ip>
</network>

virsh net-define route.xml

virsh net-start route

virsh net-autostart route

/etc/libvirt/qemu/networks/  virsh net-define的network会保存到这

/var/lib/libvirt/network/  net-start启动了的network同时也会会保存到这

/etc/libvirt/qemu/networks/autostart/  net-autostart的network同时也会保存到这

接下来,我们需要修改em1的配置并将其桥接到virbr1上

ifcfg-em1

DEVICE="em1"
ONBOOT="yes"
BRIDGE=virbr1

 接着启动em1

ifup em1

至此em1就被桥接到了virbr1上,可以使用下面的命令检查

brctl show

现在我们需要在本机添加一条默认路由,不然虚拟机是访问不了外面的:

route add default gw 192.168.1.1 dev virbr1

这里的192.168.1.1是真实的路由

ok,问题已经解决了。下面说说问题1的解决方法:

既然知道了nat出去的虚拟机只能访问外网而外网却不能访问进来,nat又是通过iptables来做的,也就是当libvirt每次启动的时候都会往iptables最前面插入自己的规则以保证nat的虚拟机能正常访问外网,

那么我们是不是可以通过修改iptables的规则来实现呢,比如我们需要一个内网ip的虚拟机对外提供80服务,那么我们就把物理机的80端口映射到这台虚拟机的80端口上,因为我们的物理机是可以直接和虚拟机通信的,

只是外网不能而已,下面添加规则:

iptables -t nat -A PREROUTING -p tcp -i virbr1 --dport 80  -j DNAT --to-destination 192.168.122.2:80

这样我们对外部访问80端口进来的数据进行了dnat,而出去的我们不用snat,只需要再添加如下规则:

iptables -I FORWARD -i virbr1 -o virbr0 -p tcp -m state --state NEW -j ACCEPT

至此问题看似得到解决,但是我们忽略了一个关键的问题,那就是每当libvirt启动的时候就会往表的最前面插入它自己的规则,而iptables的规则是有先后顺序的,也就是说,我们自己添加的规则在libvirtd服务重启之后即被

libvirt定义的规则所淹没,怎么办呢,我现在只想到了这么一个方法,直接修改libvirtd的启动脚本,在它的规则生效之后插入我们自定义的规则:

vi  /etc/init.d/libvirtd

start() {
echo -n $"Starting $SERVICE daemon: "
initctl_check

mkdir -p /var/cache/libvirt
rm -rf /var/cache/libvirt/*
KRB5_KTNAME=$KRB5_KTNAME daemon --pidfile $PIDFILE --check $SERVICE $PROCESS --daemon $LIBVIRTD_CONFIG_ARGS $LIBVIRTD_ARGS
RETVAL=$?
echo
[ $RETVAL -eq 0 ] && touch /var/lock/subsys/$SERVICE
sleep 1
iptables -D FORWARD -i virbr1 -o virbr0 -p tcp -m state --state NEW -j ACCEPT
iptables -I FORWARD -i virbr1 -o virbr0 -p tcp -m state --state NEW -j ACCEPT

。。。 。。。

至此问题基本解决。

再一个问题,我们前面有发现route和nat的网络区别仅仅是一个做了nat的iptables规则一个没有,那么我们可不可以自己在iptables里面添加相应的规则将route网络变身为nat网络呢?

答案肯定是可以的,只需要添加上下面的规则即可,原理还请观看本文的同学自己分析,这里假设我们route网络给虚拟机分配的ip是192.168.100.0/24网段:

iptables -t nat -A POSTROUTING -s 192.168.100.0/24 -d ! 192.168.100.0/24 -j MASQUERADE

iptables -A FORWARD --destination 192.168.100.0/24 -m state --state RELATED,ESTABLISHED -j ACCEPT

 这里再添加一个可以手工启动dnsmasq的小脚本

#!/bin/bash
brctl addbr routebr
ifconfig routebr 192.168.122.1 netmask 255.255.255.0
iptables -t nat -A POSTROUTING -s 192.168.122.0/24 -d ! 192.168.122.0/24 -j MASQUERADE
iptables -A FORWARD --destination 192.168.122.0/24 -m state --state RELATED,ESTABLISHED -j ACCEPT
/usr/sbin/dnsmasq \
--strict-order \
--bind-interfaces \
--pid-file=/usr/local/vps/network/default.pid \
--conf-file= \
--except-interface lo \
--listen-address 192.168.122.1 \
--dhcp-range 192.168.122.2,192.168.122.254 \
--dhcp-leasefile=/usr/local/vps/network/dnsmasq/default.leases \
--dhcp-lease-max=253 \
--dhcp-no-override \
--dhcp-hostsfile=/usr/local/vps/network/dnsmasq/default.hostsfile

接下来还会碰到更多问题,时间不早了,就到这吧,在今后的文章里再慢慢记录,慢慢分享。

原创内容,如需转载,保留署名


前一篇介绍了qemu的基本使用,使用virsh或者virtual manager来管理虚拟机,但没有涉及到libvirt API,这里就使用libvirt的python API来演示一下虚拟机的创建。

看nova的源码,关于虚拟机管理的模块是virt,libvirt就是其中的一个包,这个包中包含了使用libvirt管理虚拟机的所有API,看一下下面的示意图:


libvirt.driver这个模块中有一个全局的变量libvirt,其指向的就是libvirt的库函数,连接的获得_conn、虚拟机的创建等都是通过这个变量来调用的libvirtAPI。

在对虚拟机进行管理之前,先要和虚拟机的管理程序,即Hypervisor建立连接,在这里创建连接有两种方式,一个是只读的,一个是可读写的,创建连接在_connect()方法中:

[python] view plain copy
  1. @staticmethod  
  2. def _connect(uri, read_only):  
  3.     auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT],  
  4.             'root',  
  5.             None]  
  6.   
  7.     if read_only:  
  8.         return libvirt.openReadOnly(uri)  
  9.     else:  
  10.         return libvirt.openAuth(uri, auth, 0)  

创建虚拟机主要在_create_domain()方法中:
[python] view plain copy
  1. def _create_domain(self, xml=None, domain=None, launch_flags=0):  
  2.     """Create a domain. 
  3.  
  4.     Either domain or xml must be passed in. If both are passed, then 
  5.     the domain definition is overwritten from the xml. 
  6.     """  
  7.     if xml:  
  8.         domain = self._conn.defineXML(xml)  
  9.     domain.createWithFlags(launch_flags)  
  10.     return domain  

虚拟机相关的参数都在一个xml文件中配置,创建虚拟机时就使用这个xml文件中的内容作为参数,即上面的xml参数的类型是字符串。

defineXML()是用根据xml的内容定义了一个虚拟机,即创建了一个virDomain对象,但是并没有启动这个虚拟机。用这个方法定义的虚拟机是永久性的,会再生成一个和这个虚拟机相关的xml配置文件。还有一个方法是createXML(),它创建的虚拟机是临时性的。

createWithFlags()是启动之前定义的虚拟机,即让虚拟机的状态变为running。和它类似的方法还有一个create(),两者的区别暂时还不清楚。


根据上面的方法,就可以直接使用python来创建虚拟机了,简单的示例如下:

[python] view plain copy
  1. import libvirt  
  2.   
  3. auth = [[libvirt.VIR_CRED_AUTHNAME, libvirt.VIR_CRED_NOECHOPROMPT],'root',None]  
  4.   
  5. conn=libvirt.openAuth("qemu:///system",auth,0#这里要用读写的方式打开连接  
  6.   
  7. with open('/suo/domain/demo.xml') as f:  
  8.     xml=f.read()  
  9.   
  10. domain=conn.defineXML(xml)  
  11. domain.createWithFlags(0)  
  12.   
  13. try:  
  14.     dom0 = conn.lookupByName("instance-00000001")  
  15. except:  
  16.     print 'Failed to find the main domain'  
  17.     sys.exit(1)  
  18.   
  19. print "Domain 0: id %d running %s" % (dom0.ID(), dom0.OSType())  
  20. print dom0.info()  

因为使用python创建虚拟机,需要读写很多root权限的文件,所以要用openAuth()方法来创建连接,并且qemu使用的是system模式的。

demo.xml文件如下:

  1. <domain type='qemu'>  
  2.   <name>instance-00000001</name>  
  3.   <uuid>c7a5fdbd-cdaf-9455-926a-d65c16db1809</uuid>  
  4.   <memory>219200</memory>  
  5.   <currentMemory>219200</currentMemory>  
  6.   <vcpu>1</vcpu>  
  7.   <os>  
  8.     <type arch='i686' machine='pc'>hvm</type>  
  9.     <boot dev='hd'/><!--优先从硬盘启动-->  
  10.     <boot dev='cdrom'/><!--硬盘不能启动的话,从光驱启动-->  
  11.   </os>  
  12.   <devices>  
  13.     <emulator>/usr/local/bin/qemu</emulator>  
  14.     <disk type='file' device='cdrom'>  
  15.       <source file='/suo/domain/cflinux-1.0.iso'/>  
  16.       <target dev='hdc'/>  
  17.       <readonly/>  
  18.     </disk>  
  19.     <disk type='file' device='disk'>  
  20.       <source file='/suo/domain/precise-server-cloudimg-i386-disk1.img'/>  
  21.       <target dev='hda'/>  
  22.     </disk>  
  23.     <interface type='bridge'>  
  24.       <source bridge='br100'/><!--这里配置的虚拟机让它桥接到br100网桥上-->  
  25.     </interface>  
  26.     <graphics type='vnc' port='-1'/>  
  27.   </devices>  
  28. </domain>  

官方文档上介绍这个xml的配置文件是还有很多复杂的内容,这里仅仅配置了最简单的情况,方便理解。另外网络的配置现在还不太明白怎么配置,只是简单的把这个虚拟机桥接到了br100这个网桥上。运行上面的python文件,就可以创建虚拟机了(前提是创建好br100和root权限的libvirtd的守护进程正常运行),运行之后只是创建了虚拟机,但是对虚拟机的安装还是需要手动进行的,可以使用qemu的命令:qemu -hda instance-00000001.img来打开一个qemu的虚拟机窗口,也可以直接使用virtual manager来进行安装
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐