一、背景
eBay從2015年就開始適配kubernetes平臺並逐漸部署各個團隊的產品。然而eBay仍然部署着很大規模的OpenStack集羣。同時管理kubernetes集羣和OpenStack集羣需要耗費更多的人力和物力。但由於eBay內部還有一部分業務無法遷移到容器,我們能否用一套控制平面(control plane)同時管理容器和虛擬機呢?
二、已有的方案
在幾年的時間裏,kubernetes不斷髮展壯大,各個功能逐漸完善,越來越多的公司基於kubernetes構建雲平臺。在eBay內部,越來越多的產品基於kubernetes構建,因此用kubernetes來統一雲平臺是大勢所趨,我們需要一種方案來基於kubernetes管理虛擬機。
現在社區有兩套相對成熟的基於kubernetes來管理虛擬機的方案,分別是kubevirt和virtlet。
1. Kubevirt
Kubevirt是redhat發起的項目,它使用CRD去描述一個VM(Virtual Machine,虛擬機),通過它的控制器(controller)去把CRD轉換成一個POD。由於它使用的是CRD而不是POD,導致需要額外的控制器來實現kubernetes裏**原生的部署和服務(deployment,service)這些功能。VM的實例運行在容器(container)**裏,一個VM有對應的libvirt來管理。Kubevirt的社區比較活躍,但版本還在較早的階段。
2. Virtlet
Virtlet來自於Mirantis,跟kubevirt的不同之處在於它使用POD來描述一個VM。因爲kubelet是通過CRI接口跟下面的運行時交互的,virtlet實現了一套VM的CRI。因爲POD是kubernetes的一等公民,任何現有的kubernetes功能都可以用於virtlet管理的VM,且不需要額外的控制器,比如服務、部署等等,這樣幾乎不需要額外的學習和維護成本。但因爲一些VM特定的信息無法完全用POD來描述,virtlet藉助了POD的註解(annotation)來表達更多VM的信息。
三、我們的選擇
根據我們的評估以及eBay現有的架構,選擇了virtlet來作爲kube VM方案。首先kube VM入口是基於AZ(Availability Zone,可用區域)的,已經有了一套CRD來描述現有的kubernetes節點,只需要加一個provider(提供者)就可以重用現有AZ的各種控制器。其次,需要各種定製來達到跟OpenStack VM同樣的功能和性能。Virtlet因爲有一套單獨的CRI實現,可以更容易地加入各種定製。
1. Virtlet概覽
圖1是來自virtlet社區的架構圖。一個節點上virtlet POD包含三個容器:
- virtlet:接收CRI調用,管理VM
- libvirt:接收virtlet的請求創建、停止或銷燬VM
- VMs:所有virtlet管理的VM都會在這個容器的命名空間裏
2. 運行時
01 CRIProxy
如圖2所示,因爲virtlet有單獨的CRI實現,如果在一個節點上既需要支持VM又要支持容器,就需要有一個proxy來分發CRI調用:
- 鏡像:CRIProxy通過區分一個可配置的前綴來區分是一個容器鏡像還是一個VM鏡像。
- 運行時:CRIProxy通過特定的註解來區分容器或VM。
默認情況下所有的調用會分發給容器運行時。
02 改進
因爲引入了CRIProxy,多了一層中間調用,使這個架構看起來不夠統一,多了一層可能的出錯點,增加了出問題的機率以及調試的難度。所以我們下階段計劃開發一個VM shim,如圖3所示。它滿足containerd的shim規範。這樣單個節點的運行時看起來更統一和乾淨。
四、模型
剛有提到,操作VM的入口是在AZ層,而且已經有了CRD來描述一個節點。同一個AZ管理着一個或者多個kubernetes集羣。
這裏的provider是virtlet,專門管理VM的生命週期。如圖4所示,它主要的任務是根據ComputeNode,首先挑選一個kubernetes集羣,然後在這個集羣裏創建一個對應VM的POD,同步他們之間的狀態。對於用戶而言,下面的POD是不可見的,用戶通過創建或者刪除ComputeNode來管理VM。
五、定製
Virtlet已經有了大部分所需要的功能,但還不能完全滿足需求,我們對以下方面做了定製和改進:
- VM網絡
- NUMA Pin
- VM的重啓、停止
- OpenStack鏡像兼容
- Virtlet的可靠性
1. VM網絡
01 Virtlet網絡管理
對於現有的eBay VM,都是橋接(bridge)網絡,只需要在libvirt domain指定目標橋接器,libvirt就會在啓動VM的時候創建一個TAP接口,並把這個接口連接到指定的橋接器。
然而對於kubernetes的VM,網絡接口是調用CNI插件先配置好,然後纔會創建和啓動VM。Virtlet引入了vmwrapper,它是所有VM啓動的入口,virtlet會把vmwapper設置成libvirt domain的入口(emulator)。
從圖5可見,啓動一個libvirt實例後,vmwrapper會被首先執行:
- Virtlet偵聽在一個unix domain socket上,並且virtlet已經打開了相應VM的TAP接口。
- vmwrapper發起連接跟virtlet通信,拿到TAP接口的文件描述符(FD)。
- vmwrapper填寫好網絡相關的參數,最後啓動真正的qemu進程。
qemu網絡參數舉例如下:
這裏涉及到了進程間傳遞描述符(FD),virtlet使用的是Linux的SCM(Socket level control messages)方式。
02 網絡改進
每一次CNI插件配置網絡,都會建一個新的網絡命名空間(network namespace),一般需要有一對veth pair來連接主機的網絡和新建的網絡空間。Virtlet默認VM網絡如圖6:
連通一個VM,需要建一個bridge,一對veth pair,這樣網絡的性能會有一定的影響。
VM本身有強隔離性,我們又有自己的CNI插件實現,爲了減少網絡的路徑,創建VM的網絡不需要新的網絡命名空間。最終一個節點的網絡結構如圖7所示:
03 Vhost net
Virtlet不支持vhost net,而eBay OpenStack上使用virtio的VM都有vhost net。增加這個功能,對每一個TAP接口,都需要有一個vhost net的描述符,這個描述符是通過打開/dev/vhost-net而來的。描述符的傳遞跟前面提到過的TAP描述符傳遞類似。
04 支持TSO(TCP Segment Offload)
在對virtlet的VM進行網絡性能測試的時候,網絡吞吐量只有OpenStack VM的一半,在VM裏面,soft IRQ負載很高,最終發現virtlet VM的網絡沒有打開TSO和TX/RX校驗。
無論是Open Stack的VM還是virtlet的VM,都沒有顯示配置這個選項,但爲什麼OpenStack的VM默認是打開的呢?
前面提到,常規VM的網絡接口是libvirt創建和刪除的,而virtlet的VM網絡接口是由CNI創建、virtlet管理的。通過閱讀libvirt和qemu的源代碼,libvirt在打開TAP接口的時候會加一個IFF_VNET_HDR的選項,qemu檢查到這個選項後, VM實例的網絡接口就會默認打開TSO。通過這個小小的改動,virtlet VM的網絡吞吐量跟OpenStack的VM旗鼓相當。
2. NUMA Pin
eBay數據中心的服務器都有2個或以上的NUMA節點,在這些節點上運行的VM,需要把他們儘量固定在NUMA節點上,否則跨NUMA節點的訪問會帶來性能問題。Kubernetes有CPU manager這個功能,CPU的分配也顧及到了機器上的NUMA節點,但不能完全滿足要求,原因如下:
- CPU是獨佔的。也就是一個CPU被分配給一個POD後,其它任何POD就不能再分配到這個CPU了,這樣不能做到超售。
- CPU的分配可能會跨NUMA節點,但並不平衡。比如一個POD需要8個CPU,那就有可能2個CPU分配在一個NUMA節點,另外的6個CPU分配在另一個NUMA節點,導致了不平衡。
基於以上原因,我們沒有使用CPU manager這個功能,而是在virtlet裏增加了一個模塊來管理NUMA節點的分配和釋放(這也是virtlet有自己的CRI實現的好處之一):
- 固定VM所運行的NUMA節點,但不固定VM運行在NUMA節點對應的CPU。比如一臺機器有2個NUMA節點,CPU 0-15在NUMA 0,CPU 16-31在NUMA 1,如果申請一個4核的VM,那這個VM只會運行在其中的一個NUMA節點上,但VM能用到的CPU可以是這個NUMA節點上所有的CPU(當然這只是標準的VM,對於有高性能要求的VM,既要有NUMA固定也需要有CPU固定)。
- 對於NUMA的分配,採取的是CPU、內存最平衡的方式,也就是在一個NUMA節點上,在分配量不超過某個閾值的前提下,計算已經分配的CPU和內存,加上將要被分配的VM需要的CPU和內存,CPU分配量和內存分配量差值的絕對值哪個最小,哪個優先級就最高。
3. VM的重啓和停止
Virtlet沒有提供VM重啓(reset)和停止(stop),但eBay有些團隊需要這樣的功能。前面提到,virtlet的VM就是一個普通的POD,在kubernetes裏如果一個POD不是在運行(running)狀態,kubernetes就會不斷重試,去把POD帶回到運行狀態。因此需要一個單獨的模塊來管理VM的狀態。
- 綜合當前VM的實際狀態和用戶所期望的狀態,做到最後狀態一致。
- 增加一個POD的註解,裏面包含所**需要(request)的狀態和實際(status)**狀態。
- 在給kubelet彙報VM狀態的時候,如果用戶顯示的是該VM已停止,仍然要向kubelet彙報是運行狀態,這樣kubelet就不會反覆地調用啓動的接口。
4. OpenStack鏡像兼容
eBay運行着很大規模的OpenStack集羣,有許多已有的VM鏡像,virtlet必須能無縫地使用這些已有的鏡像。
在eBay,絕大部分OpenStack VM的網絡信息是通過configdrive靜態注入的方式,由cloutinit來完成網絡的配置的,雖然virtlet的文檔裏聲稱支持configdrive,但他們使用的是不同的configdirve的版本,大部分的現有鏡像都不能正確拿到configdrive所注入的信息。
因此需要實現另一個版本的configdrive,甚至還能兼容Windows VM。滿足這兩個需求,有兩個關鍵的地方:
- Configdrive盤必須有config-2的標籤。
- Configdrive必須是vfat格式的(雖然configdrive可以是vfat或者iso,但某些Windows的cloudinit不能識別ISO格式)。
六、可靠性
儘管VM的生命週期用kubernetes來管理,但VM和容器還是有不同的地方:在VM的生命週期之內,不管是POD重啓、virtlet重啓以及節點重啓,VM的所有數據不能有任何丟失;而容器在重啓之後數據就會消失(不包括host path)。
Virtlet要能用在生產環境,必須做到數據不丟失。還有節點上的VM能夠脫離virtlet運行良好,這樣才能給virtlet升級。從一開始,我們就非常關注virtlet的可靠性,也找到了virtlet可靠性所存在的一些問題:
- 升級virtlet本身,所有的VM也莫名其妙地消失了。
- 重啓節點,VM所有的數據丟失。
- 停止VM和virtlet,然後再啓動virtlet和VM,VM再也不能啓動。
如果以上的問題不能解決,virtlet就不能用在生產環境,需要用其它方案甚至重新開發一套來代替。幸運的是通過積極閱讀代碼和調試,我們解決了上面的問題,社區也接受了這些補丁。
對於基於virtlet的一些改進和定製,需要保證每次改動不影響已有的功能,從一開始我們就開發了集成測試和端到端測試來保證可靠性。
七、性能
有了可靠的保障,性能也必須達標,參照物就是已有的OpenStack VM。
- 我們使用各種基準測試工具對相同CPU、內存以及磁盤的virtlet VM和OpenStack VM進行測試。
- 使用生產環境的鏡像流量來比較兩者的CPU和內存使用率,以及在每秒時間內能夠處理的事務(TPS Transaction Per Second)。
比較下來的結果是兩者沒有差距,達到了我們的預期和要求。
八、結語
Kubernetes愈來愈受各家公司重視,這是未來幾年甚至數十年雲平臺的趨勢。但是從老的平臺過渡到新的平臺需要時間,一刀切的方式會帶來無法遇見的風險和額外的耗時,特別是像eBay這樣有多樣產品和大體量的雲平臺公司。用kubernetes來管理VM使這種過渡成爲了可能,既不需要同時維護兩套平臺,也減少了新老交替帶來的風險。
本文轉載自公衆號eBay技術薈(ID:eBayTechRecruiting)。
原文鏈接: