云主机创建、删除、关机、重启过程ovs tap网卡相关流程




写这篇的起因是前段时间同事处理了一个打开基于ovs的安全组功能后,重启云主机后ovs流表刷新太慢导致云主机网络不通的问题,这个问题最终定位是neutron已经修复的bug:https://bugs.launchpad.net/neutron/+bug/1645655(没有backport到M版本),但由于对整个云主机tap网卡的创建、清理生命周期流程不清楚,所以走了一些弯路,因此决定整体分析下相关流程。

nova相关流程

基于nova Mitaka版本源码

创建云主机

创建云主机的整个nova流程比较复杂,要全部介绍的话得把nova代码翻一遍了,这里只描述跟libvirt相关的部分。

之前创建云主机整理的流程图(基于ceph系统盘),很多细节没考虑比如nova-conductor、neutron、MQ、DB等,供参考吧:

 

nova准备好云主机启动所需的全部资源后,就调用libvirt接口(通过libvirt-python api调用C库最后转到libvirtd),正式进入libvirt的create流程:

重启云主机

重启分为两种类型,soft和hard,区别是soft调用的libvirt接口是virDomainShutdownFlags+virDomainCreateWithFlags(virsh命令对应shutdown+start),hard是virDomainDestroyFlags+virDomainCreateWithFlags(virsh命令对应destroy+start)。

这里仅分析soft类型,hard类似:

删除过程主要是destroy并清理资源,就不多介绍了。

上述流程分析可以看到,nova里面其实是不管理tap设备的,只是通过查询neutron接口,找到云主机上绑定的port信息,用来生成libvirt所需的xml文件,tap设备的生命周期管理交给libvirt维护。

libvirt相关流程

基于libvirt v3.2.0版本源码

nova创建、重启过程中的启动过程,类似virsh start vm,会执行add-port命令,libvirtd的debug日志如下:

gdb debug调用栈如下,流程比较清晰,没有回调之类的,因此不再分析:

 

nova删除、关机、重启过程中的关机流程,类似virsh shutdown/destroy vm(shutdown的话,还分为使用qemu guest agent关机和使用ACPI接口关机两种,优先使用前一种),会删除ovs tap设备执行del-port命令,libvirtd的debug日志如下:

调用栈如下:

qemuProcessEventHandler发现qemu monitor退出,执行processMonitorEOFEvent,qemuProcessEventHandler为回调函数,负责处理qemu进程状态变更事件,回调函数的流程一般分为注册和执行两部分,

  • 注册流程:libvirtd.c:main–>libvirtd.c:daemonInitialize(VIR_DAEMON_LOAD_MODULE(qemuRegister, “qemu”))–>src\qemu\qemu_driver.c:qemuRegister–>libvirt.c:virRegisterStateDriver(完成注册);
  • 执行流程:main–>daemonStateInit–>daemonRunStateInit–>virStateInitialize–>qemuStateInitialize(qemu_driver->workerPool = virThreadPoolNew(0, 1, 0, qemuProcessEventHandler, qemu_driver))(遍历所有注册的state driver并执行完成初始化调用)

processMonitorEOFEvent事件回调函数的处理流程的另一个分支是结束qemu进程,libvirt发现qemu monitor socket关闭就会kill qemu主进程(monitor是libvirtd跟qemu进程的通信通道,通道断了libvirtd就无法控制qemu进程了,也就无法响应用户的各种查询、热插拔等操作,因此libvirtd选择杀死qemu进程,一了百了,不留后患),正常情况下都是qemu进程退出导致的monitor socket关闭,所以这种情况下会打日志“2018-01-08 07:38:18.427+0000: 1560: debug : qemuProcessKill:5897 : VM ‘instance-0000000e’ not active”,提示vm已关机,return 0返回不执行任何操作;异常退出情况下会kill掉qemu进程:

gdb调试调用栈如下:

 

 

调用qemu monitor回调,shutdown/destroy时执行(发现qemu monitor服务退出调用eofNotify,发现异常调用errorNotify,其他monitor事件则调用对应的回调,比如qemu进程发送的SHUTDOWN事件会调用qemuMonitorJSONHandleShutdown回调,):

上面是destroy操作加的断点调试信息输出。qemuMonitorJSONHandleShutdown会调用注册好的关机事件回调,src\qemu\qemu_monitor_json.c:

src\qemu\qemu_monitor.c(最终调用到mon->cb的domainShutdown回调函数):

 

注册qemu monitor回调过程是在创建vm时执行的,如domainShutdown的回调是qemuProcessHandleShutdown:

全部回调定义在src\qemu\qemu_process.c:

qemu事件处理方法定义在src\qemu\qemu_monitor_json.c:

 

qemu事件定义,在qemu源码的qapi/run-state.json文件中,具体的事件发送流程还没分析,应该是封装好之后通过monitor socket传递的,类似qemu-guest-agent的命令结果传递流程:

 

Centos7 libvirtd gdb调试

简单描述下,有两种方法,一种是通过yum安装debuginfo包,好处是方便快捷,执行 yum install libvirt-debuginfo 即可,但是编译优化没关导致单步调试有些地方跟源码对应不起来,还有就是变量经常被优化掉看不到具体的值,我上面为了方便采用的这种方式。debuginfo的源一般没有镜像,直接使用官方的:

另外一种是自己从编译debug二进制,推荐第二种方式,但是编译依赖有点多,参考:https://libvirt.org/compiling.html

之前记录的debian7环境下老版本libvirt编译方法(应该是1.1.4版本的),可以参考依赖包和编译步骤:

apt-get install gcc  make pkg-config libxml2-dev libgnutls-dev libdevmapper-dev python-dev libnl-dev libyajl-dev libpciaccess-dev build-essential libhal-dev libudev-dev libtool(–with-hal=yes –with-udev=yes用到)
后面这两个用来解决这个问题:sudo virsh nodedev-list
error: Failed to count node devices
error: this function is not supported by the connection driver: virNodeNumOfDevices
./configure –prefix=/usr –libdir=/usr/lib –localstatedir=/var –sysconfdir=/etc –with-hal=yes –with-udev=yes
./configure –prefix=/usr –libdir=/usr/lib –localstatedir=/var –sysconfdir=/etc –with-selinux
######error: failed to get the hypervisor version
######error: internal error Cannot find suitable emulator for x86_64
解决方法:安装libyajl-dev之后重新./configure,make,make install
libvirt-1.1.4编译:
make报错:aclocal-1.13: command not found
解决方法:执行./autogen.sh –system(autoreconf),然后make

neutron相关流程

基于neutron Mitaka版本源码,VLAN type driver+ovs mechanism driver,ovs安全组未开启(所以也没有相关流表的操作)。

主要分析tap设备插入、删除操作后,neutron-openvswitch-agent都做了哪些事情。

在不清楚代码流程的情况下,翻日志是最快的方式,云主机启动tap设备插入操作的neutron-openvswitch-agent debug日志:

从日志可以简单分析出,ovs agent是通过注册ovsdb的回调发现ofport的insert操作的,也就是add-port。之后的流程就是该怎么做怎么做了。

云主机关机tap设备删除操作的neutron-openvswitch-agent debug日志:

 

代码流程分析(neutron-openvswitch-agent启动流程可参考之前写的这篇文章):

start:

使用subprocess.Popen创建进程,通过neutron-rootwrap执行ovsdb monitor命令,然后用eventlet协程执行进程标准输出和标准错误的监听任务:

ovsdb-client monitor功能验证:

参考资料:

 

ovsdb monitor的流程已经分析完了,下面要看下监听到有port变更之后的处理流程,主要是在rpc_loop方法里面进行的:

检查到ovsdb有新增或者删除的port就继续处理,当然条件有好几个,看日志输出可以发现每秒都会执行一次 LOG.debug("Agent rpc_loop - iteration:%d started", self.iter_num) ,但并不是每次都执行 LOG.debug("Agent rpc_loop - iteration:%(iter_num)d - starting polling. Elapsed:%(elapsed).3f", {'iter_num': self.iter_num, 'elapsed': time.time() - start}) ,只有端口变更才会执行。

process_port_info先获取所有port插入、删除事件,然后调用process_ports_events进行处理:

process_ports_events只是分析下哪些port被添加了、哪些port被删除了,然后返回给port_info,并没有执行实际的流表操作,经过调试流表操作是在下面的几个方法里面执行的。

nova start开机流程(neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent.OVSNeutronAgent#_bind_devices执行流表添加操作):

neutron-openvswitch-agent流表操作相关命令行日志:

nova stop关机流程(neutron.plugins.ml2.drivers.openvswitch.agent.ovs_neutron_agent.OVSNeutronAgent#update_stale_ofport_rules方法里面执行流表删除操作):

关机流程的日志就不贴了。

另外上面流表内容我现在也只有个模糊的概念,后面会把每条表项的意义搞清楚,这个会专门写一篇文章。