爲什麼需要強隔離容器
我們在生產環境中運行容器已久,第一次對強隔離容器訴求是java類應用引起的,如果不配置jvm參數,java虛擬機會根據系統資源信息進行內存gc線程數等配置,在不給容器配額的情況下問題不大,一旦配額了。。。
普通的容器在容器中看到的資源還是宿主機的資源,那麼假設宿主機128G而你給容器配額2G,此時堆內存按照128G去分,可想而知後果,同理還有gc線程數等
<!--more-->
給jvm配置參數就行了唄
我們很難改變用戶行爲,讓用戶都去改動參數不太現實。
lxcfs一定程度上解決了這個問題
lxcfs可以讓容器有更好的資源可視性,如內存,cpuset等,原理也非常簡單,就是把proc下的一些文件還在給容器,容器內進程讀取資源信息時系統調用會被lxcfs攔截,然後到cgroup下去查該進程資源配額信息進行計算,大部分場景可以通過這個方式修修補補
然鵝,lxcfs的缺陷
第一,支持lxcfs的運行時甚少
第二,用戶使用時不透明,需要自行掛載很多文件不友好
第三,由於第二點,你就得去開發一些特性去支持它,主流方式有幾種
1.k8s上監聽一些對象的創建,進行修改
2.修改kubelet,在volume裏默認加上,我們就是這樣做的,正在把這個特性PR給社區
3.修改runtime,或者直接選擇支持這個特性的運行時,如pouch
第四,cpushare的方式,我們也正在把這個特性pr給社區,通過計算佔比把計算後的cpu核數上報給進程
第五,很多應用從system下面去讀取資源信息,而非proc,這樣又是一大波定製需求。。。還有remout等等問題
總體來說都是修修補補,不能從徹底上解決問題
這讓我越來越看好輕量級虛擬化技術
kata runv等技術的出現真的是把虛擬機容器的優勢強強結合,容器的調度編排管理生態,鏡像標準,再加上虛擬機的強隔離
下面開始一大波名詞解釋以及他們之間的關係
containerd地位難以撼動,真正管理容器的守護進程,k8s和docker都可以通過unix socket去調用它,然後每起一個容器containerd會去調用runc runv kata等
kata runv qemu firecracker rust-vmm都是啥關係
kata和runv都是可以被containerd調用然後調用qemu命令去啓動虛擬機
qemu 和firecracker是一個級別,真正去啓動虛擬機的,和張磊大佬交流時這裏引用大佬一句話:qemu是在一大坨功能上做減法,firecracker是在非常核心簡單的功能上做加法。
那麼我們到底因該選qemu還是firecracker呢,那肯定是與場景相關了,比如我們希望用重量級虛擬機,有狀態,需要遷移,需要systemd sshd等,那麼肯定還是走qemu libvirt, 如果我們走輕量級虛擬機firecracker是個非常不錯的選擇,而且潛力巨大,畢竟是來跑亞馬遜函數計算的,不是蓋的。看下firecracker api就發現真簡單,再去看qemu文檔。。。。什麼**鳥玩意兒。。。
qemu大神別噴我,我承認其強大,但是很多時候遇到問題有點無從下手,很多使用方法我也是從源碼中摸索出來的,個人還是喜歡更輕量級的東西。不過我依然還是對學習qemu有很大熱情。
順便提一下libvirt,既然重,那不如再重一點,libvirt能讓你更方便的管理qemu虛擬機和qemu開發,細節不贅述了
rust-vmm是個更底層的一系列組件,大佬說是政治產物,自己如果對寫hypervisor有興趣可以抱着學習態度去開發玩,生產中直接firecracker就好了,所以rust的潛力還是巨大的,爲了寫虛擬機爲了寫操作系統,和我一起學rust🤪🤪
鋪墊的差不多了,下面正式開始:
因爲kata能支持firecracker和qemu,所以針對kata這個技術來做個具體點的介紹
進程模型
所以kata runtime替代掉的是runc部分的東西,因爲中間有containerd,所以上層如docker k8s感知不到運行時的變化。
containerd會與kata的shim進程通信,shim與agent通信,agent在虛擬機裏面做一些事情,如配置網卡,啓動容器等。
虛擬化方式
這個圖虛線左邊不用看,本質就是調用qemu命令創建虛擬機,右邊實際上kata是把k8s pod這個殼本來是容器,換成了虛擬機,但是有很多細節:
- 網絡任然在一個ns中,下文會講
- kata agent依然會在虛擬機中啓動容器
kata網絡
熟悉docker默認網絡模式的親都比較清楚設備對還沒變,設備對的另外一端與虛擬機連接是由kata負責,用的技術叫macvtap,它可以讓一個接口擁有多個mac地址。
創建macvtap設備:
ip link add link eth0 name macvtap0 type macvtap mode bridge
ip link set macvtap0 address 1a:46:0b:ca:bc:7b up
cat /sys/class/net/macvtap0/ifindex
cat /sys/class/net/macvtap0/address
通過qemu啓動:
qemu-system-x86_64 -enable-kvm centos.qcow2 \
-cdrom CentOS-7-x86_64-Minimal-1810.iso \
-netdev tap,fd=30,id=hostnet0,vhost=on,vhostfd=4 30<>/dev/tap2 4<>/dev/vhost-net \
-device virtio-net-pci,netdev=hostnet0,id=net0,mac=1a:46:0b:ca:bc:7b \
-monitor telnet:127.0.0.1:5801,server,nowait
VNC server running on ::1:5900
注意網絡參數,這塊很少資料介紹的比較清楚都是啥含義,我也是通過學習kata源碼問了很多大牛才徹底理解的。
/dev/tap2 這個2 是通過上面的 /sys/class/net/macvtap0/ifindex 差得的。
vhost是虛擬機網絡虛擬化的一種模式,性能比較高,我們需要把vhost的fd傳入給qemu
對應kata的代碼,本質就是打開了這兩文件,把fd傳入:
func createMacvtapFds(linkIndex int, queues int) ([]*os.File, error) {
tapDev := fmt.Sprintf("/dev/tap%d", linkIndex)
return createFds(tapDev, queues)
}
fds := make([]*os.File, numFds)
for i := 0; i < numFds; i++ {
f, err := os.OpenFile(device, os.O_RDWR, defaultFilePerms)
if err != nil {
utils.CleanupFds(fds, i)
return nil, err
}
fds[i] = f
}
return fds, nil
事情還沒結束,進入虛擬機會發現網卡沒有地址:
[root@localhost ~]# ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
inet 127.0.0.1/8 scope host lo
valid_lft forever preferred_lft forever
inet6 ::1/128 scope host
valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP group default qlen 1000
link/ether 52:54:00:59:ee:01 brd ff:ff:ff:ff:ff:ff
inet6 fe80::5054:ff:fe59:ee01/64 scope link
valid_lft forever preferred_lft forever
因爲虛擬機的eth0的地址是kata-agent去配置的,所以這裏需要自己在虛擬機配置一下,ip一定要與設備對另一端的eth0一樣。
網絡其它的部分就是兼容CNI標準了,本文不做過多介紹了。
文件系統DAX(Direct Access filesystem)
內核DAX功能有效地將一些主機端文件映射到來賓VM空間。特別是Kata Containers使用QEMU NVDIMM功能提供內存映射的虛擬設備,可用於將虛擬機的根文件系統DAX映射到guest內存地址空間。
看rootfs是這樣過去的
QEMU配置了NVDIMM內存設備,內存文件後端在主機端文件中映射到虛擬NVDIMM空間。
guest虛擬機內核命令行安裝此NVDIMM設備並啓用DAX功能,允許直接頁面映射和訪問,從而繞過guest虛擬機頁面緩存。這樣虛擬機的根文件系統就來了。
內核文件
kata kernel 此連接有詳細介紹
- kata對內核做了一些patch,如內存熱插拔,9pfs緩存優化,arm架構的更好支持等
- patch完了後把編譯好的內核放到kata指定的目錄
make -j $(nproc) ARCH="${arch_target}"
docker鏡像轉化成虛擬機鏡像
osbuilder項目專門去做這個事情,這裏要解釋的一個概念是initrd(或“initramfs”)壓縮cpio(1)歸檔,由rootfs創建,加載到內存中並用作Linux啓動過程的一部分。在啓動期間,內核將其解壓縮到一個特殊的實例中,該實例tmpfs將成爲初始的根文件系統。
使用方法也比較簡單,這裏不再贅述。
firecracker簡介
爲什麼我這麼喜歡firecracker,因爲你們一看它API就知道的,簡單到讓你懷疑人生:
以下是個網絡的例子:
- 宿主機上創建tap設備
sudo ip tuntap add tap0 mode tap
sudo ip addr add 172.16.0.1/24 dev tap0
sudo ip link set tap0 up
sudo sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
sudo iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
sudo iptables -A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
sudo iptables -A FORWARD -i tap0 -o eth0 -j ACCEPT
- 調用API創建虛擬機網卡
curl -X PUT \
--unix-socket /tmp/firecracker.socket \
http://localhost/network-interfaces/eth0 \
-H accept:application/json \
-H content-type:application/json \
-d '{
"iface_id": "eth0",
"guest_mac": "AA:FC:00:00:00:01",
"host_dev_name": "tap0"
}'
- 配置虛擬機網卡
ip addr add 172.16.0.2/24 dev eth0
ip route add default via 172.16.0.1 dev eth0
清清楚楚,乾乾淨淨
輕量級虛擬機其它
現在輕量級虛擬機還是有些問題沒解決,比如監控,就不能像cadvisor那樣去監控容器了,所以這塊kubelet採集的地方就需要定製。kubevirt簡介
以上都是輕量級虛擬機,然而對於亡openstack之心不死的人還是希望搞出個能管理重量級虛擬機的東西,kubevirt應運而生。
我們如果去基本kata去管理有狀態的重量級虛擬機其實還是有很多事要去做的:
生命週期管理,k8s可沒有啓動停止容器這些概念,所以想要支持虛擬機的啓動重啓就得自己去定義CRD,然後還不夠,因爲kubelet不會去調用CRI的啓動停止的接口,所以還得修改kubelet...
網絡,一般的CNI是滿足不了IP漂移以及VPC這種需求的,所以你需要ovn CNI之類的東西
虛擬機的系統盤數據盤放本地是不行了,改。。。
兼容openstack那些系統鏡像,改。。。
kubevirt正是因爲這個問題所以採用了這樣的架構:
僅資源調度時走k8s,虛擬機的生命週期管理基本已經與CRI沒關係了,全走自己的agent管理,這樣上面的那些問題都可以在virt-handler virt-laucher上解決,不用再去對k8s組件動刀。
本質就是在容器裏起了個虛擬機,不過啓動方式與kata有所不同,它使用了libvirt,qemu更上層的一個封裝,當然玩重量級虛擬機有這個還是方便很多的,很多時候我們需要調試,或者找錯誤,libvirt給了一系列的工具集,同時也對編程友好。
不過每個虛擬機都會去起一個libvirtd進程的做法我覺得還是有待商榷。
總結
本文雖然扯了很多,但是虛擬機還是遠比容器複雜,本文也只能提個冰山一角,希望大家讀完能有個整體的認識。我是希望能用一個統一的技術棧搞定容器,虛擬機,輕量級虛擬機,這樣能極大的節省企業的成本,尤其是人力維護成本。
掃碼關注sealyun
探討可加QQ羣:98488045