KubeVirt网络源码分析(1)

目录:(可以按w快捷键切换大纲视图)

kubevirt 以 CRD 形式将 VM 管理接口接入到kubernetes,通过一个pod去使用libvirtd管理VM方式,实现pod与VM的一对一对应,做到如同容器一般去管理虚拟机,并且做到与容器一样的资源管理、调度规划。

在解释Kubevirt如何执行VM网络配置之前,先将POD网络配置和VM网络配置概念分开。POD网络配置本篇不涉及。Kubernetes负责通过CNI根据其配置来设置POD网络,之后POD可以连通外面的世界。

Kubevirt的网络部分是VM网络配置,一般称为绑定。

virt-launcher virtwrap 准备虚拟机的网络

virt-launcher pod 和 虚拟机一一对应,在pod中运行一台虚拟机, virt-launcher pod负责提供运行虚拟机必要的组件。本篇文章是介绍网络相关的组件。下图是KubeVirt的网络。图中的Kubetnets的CNI网络插件部分不是本篇涉及内容。

三个包含关系的实线框,从外到里分别是:Kubernetes工作节点、工作节点上的POD、POD里运行的VM虚拟机
三个并列的虚线框,从下到上分别是:Kubernetes网络(Kubernetes CNI负责配置),libvirt网络,虚拟机网络
本篇不涉及Kubernetes网络,只涉及libvirt网络,虚拟机网络

\kubevirt\pkg\virt-launcher\virtwrap\manager.go 中的 func (l *LibvirtDomainManager) preStartHook(vm *v1.VirtualMachine, domain *api.Domain) (*api.Domain, error) 调用 SetupPodNetwork 方法给虚拟机准备网络。

\kubevirt\pkg\virt-launcher\virtwrap\network\network.go SetupPodNetwork → SetupDefaultPodNetwork 该方法做了三件事,对应下面三个方法

  • discoverPodNetworkInterface
  • preparePodNetworkInterface
  • StartDHCP

discoverPodNetworkInterface

该方法收集pod interface如下信息:

  • IP地址
  • 路由
  • 网关地址
  • MAC地址
    这些信息会传递给DHCP服务,DHCP服务会将这些信息传递给(在虚拟机启动后,现在是网络准备阶段)虚拟机(虚拟机作为DHCP客户端)。
    func discoverPodNetworkInterface(nic *VIF) (netlink.Link, error) {
    	nicLink, err := Handler.LinkByName(podInterface)
    	if err != nil {
    		log.Log.Reason(err).Errorf("failed to get a link for interface: %s", podInterface)
    		return nil, err
    	}
    
    	// get IP address
    	addrList, err := Handler.AddrList(nicLink, netlink.FAMILY_V4)
    	if err != nil {
    		log.Log.Reason(err).Errorf("failed to get an ip address for %s", podInterface)
    		return nil, err
    	}
    	if len(addrList) == 0 {
    		return nil, fmt.Errorf("No IP address found on %s", podInterface)
    	}
    	nic.IP = addrList[0]
    
    	// Get interface gateway
    	routes, err := Handler.RouteList(nicLink, netlink.FAMILY_V4)
    	if err != nil {
    		log.Log.Reason(err).Errorf("failed to get routes for %s", podInterface)
    		return nil, err
    	}
    	if len(routes) == 0 {
    		return nil, fmt.Errorf("No gateway address found in routes for %s", podInterface)
    	}
    	nic.Gateway = routes[0].Gw
    	if len(routes) > 1 {
    		dhcpRoutes := filterPodNetworkRoutes(routes, nic)
    		nic.Routes = &dhcpRoutes
    	}
    
    	// Get interface MAC address
    	mac, err := Handler.GetMacDetails(podInterface)
    	if err != nil {
    		log.Log.Reason(err).Errorf("failed to get MAC for %s", podInterface)
    		return nil, err
    	}
    	nic.MAC = mac
    	return nicLink, nil
    } 

preparePodNetworkInterfaces

因容器的IP和MAC地址都将来会通过DHCP传给容器里的虚拟机,所以要用preparePodNetworkInterfaces方法对原容器的网路做如下操作:

  • 删除POD的的interface的IP地址
  • 将POD的interface down
  • 修改POD的interface mac地址,换一个MAC任意的MAC地址,只要和原来的MAC不一样,因为原来的MAC地址要给虚拟机,同一个网桥的不同端口的MAC地址不能一样
  • 将POD的interface up
  • 创建网桥,名称代码里固定死了,为br1
  • 将POD的interface绑到br1上,将来虚拟机创建出来后也会绑到br1网桥上
    func preparePodNetworkInterfaces(nic *VIF, nicLink netlink.Link) error {
    	// Remove IP from POD interface
    	err := Handler.AddrDel(nicLink, &nic.IP)
    
    	if err != nil {
    		log.Log.Reason(err).Errorf("failed to delete link for interface: %s", podInterface)
    		return err
    	}
    
    	// Set interface link to down to change its MAC address
    	err = Handler.LinkSetDown(nicLink)
    	if err != nil {
    		log.Log.Reason(err).Errorf("failed to bring link down for interface: %s", podInterface)
    		return err
    	}
    
    	_, err = Handler.ChangeMacAddr(podInterface)
    	if err != nil {
    		return err
    	}
    
    	err = Handler.LinkSetUp(nicLink)
    	if err != nil {
    		log.Log.Reason(err).Errorf("failed to bring link up for interface: %s", podInterface)
    		return err
    	}
    
    	// Create a bridge
    	bridge := &netlink.Bridge{
    		LinkAttrs: netlink.LinkAttrs{
    			Name: api.DefaultBridgeName,
    		},
    	}
    	err = Handler.LinkAdd(bridge)
    	if err != nil {
    		log.Log.Reason(err).Errorf("failed to create a bridge")
    		return err
    	}
    	netlink.LinkSetMaster(nicLink, bridge)
    
    	err = Handler.LinkSetUp(bridge)
    	if err != nil {
    		log.Log.Reason(err).Errorf("failed to bring link up for interface: %s", api.DefaultBridgeName)
    		return err
    	}
    
    	// set fake ip on a bridge
    	fakeaddr, err := Handler.ParseAddr(bridgeFakeIP)
    	if err != nil {
    		log.Log.Reason(err).Errorf("failed to bring link up for interface: %s", api.DefaultBridgeName)
    		return err
    	}
    
    	if err := Handler.AddrAdd(bridge, fakeaddr); err != nil {
    		log.Log.Reason(err).Errorf("failed to set macvlan IP")
    		return err
    	}
    
    	return nil
    }

StartDHCP → DHCPServer → SingleClientDHCPServer

DHCP服务只能给DHCP客户端(将来创建的虚拟机)提供1个IP地址,除了IP,网关信息,路由信息都会提供。SingleClientDHCPServer该方法启动一个只提供一个DHCP Client的DHCP服务端。

func (h *NetworkUtilsHandler) StartDHCP(nic *VIF, serverAddr *netlink.Addr) {
	nameservers, searchDomains, err := getResolvConfDetailsFromPod()
	if err != nil {
		log.Log.Errorf("Failed to get DNS servers from resolv.conf: %v", err)
		panic(err)
	}

	// panic in case the DHCP server failed during the vm creation
	// but ignore dhcp errors when the vm is destroyed or shutting down
	if err = DHCPServer(
		nic.MAC,
		nic.IP.IP,
		nic.IP.Mask,
		api.DefaultBridgeName,
		serverAddr.IP,
		nic.Gateway,
		nameservers,
		nic.Routes,
		searchDomains,
	); err != nil {
		log.Log.Errorf("failed to run DHCP: %v", err)
		panic(err)
	}
}

上面的源码是KubeVirt 0.4.1版本的,以后再对最新的代码的 KubeVirt virt-lancher 网络部分做一次分析。

参考 - qemu 创建传统虚拟机以及虚拟机网络流程

# 创建一个虚拟机镜像,大小为 8G,其中 qcow2 格式为动态分配,raw 格式为固定大小
qemu-img create -f qcow2 ubuntutest.img 8G# 创建虚拟机(可能与下面的启动虚拟机操作重复)
qemu-system-x86_64 -enable-kvm -name ubuntutest -m 2048 -hda ubuntutest.img -cdrom ubuntu-14.04-server-amd64.iso -boot d -vnc :19# 在 Host 机器上创建 bridge br0
brctl addbr br0
# 将 br0 设为 up
ip link set br0 up
# 创建 tap device
tunctl -b# 将 tap0 设为 up
ip link set tap0 up
# 将 tap0 加入到 br0 上
brctl addif br0 tap0
# 启动虚拟机, 虚拟机连接 tap0、tap0 连接 br0
qemu-system-x86_64 -enable-kvm -name ubuntutest -m 2048 -hda ubuntutest.qcow2 -vnc :19 -net nic,model=virtio -nettap,ifname=tap0,script=no,downscript=no# ifconfig br0 192.168.57.1/24
ifconfig br0 192.168.57.1/24# VNC 连上虚拟机,给网卡设置地址,重启虚拟机,可 ping 通 br0# 要想访问外网,在 Host 上设置 NAT,并且 enable ip forwarding,可以 ping 通外网网关。# sysctl -p
net.ipv4.ip_forward = 1
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
# 如果 DNS 没配错,可以进行 apt-get update

转载请注明来源,欢迎指出任何有错误或不够清晰的表达。可以邮件至 backendcloud@gmail.com