OpenStack容器服務Zun初探與原理分析

int32bit發表於2022-12-07

01

Zun服務簡介

Zun是OpenStack的容器服務(Containers as Service),類似於AWS的ECS服務,但實現原理不太一樣,ECS是把容器啟動在EC2虛擬機器例項上,而Zun會把容器直接執行在compute節點上。

和OpenStack另一個容器相關的Magnum專案不一樣的是:Magnum提供的是容器編排服務,能夠提供彈性Kubernetes、Swarm、Mesos等容器基礎設施服務,管理的單元是Kubernetes、Swarm、Mesos叢集,而Zun提供的是原生容器服務,支援不同的runtime如Docker、Clear Container等,管理的單元是container。

Zun服務的架構如圖:

OpenStack容器服務Zun初探與原理分析


Zun服務和Nova服務的功能和結構非常相似,只是前者提供容器服務,後者提供虛擬機器服務,二者都是主流的計算服務交付模式。功能類似體現在如下幾點:

  • 透過Neutron提供網路服務。

  • 透過Cinder實現資料的持久化儲存。

  • 都支援使用Glance儲存映象。

  • 其他如quota、安全組等功能。

元件結構結構相似則表現在:

  • 二者都是由API、排程、計算三大元件模組構成,Nova由nova-api、nova-scheduler、nova-compute三大核心元件構成,而Zun由zun-api、zun-compute兩大核心元件構成,之所以沒有zun-scheduler是因為scheduler整合到zun-api中了。

  • nova-compute呼叫compute driver建立虛擬機器,如Libvirt。zun-compute呼叫container driver建立容器,如Docker。

  • Nova透過一系列的proxy代理實現VNC(nova-novncproxy)、Splice(nova-spiceproxy)等虛擬終端訪問,Zun也是透過proxy代理容器的websocket實現遠端attach容器功能。

02

Zun服務部署

Zun服務部署和Nova、Cinder部署模式類似,控制節點建立資料庫、Keystone建立service以及註冊endpoints等,最後安裝相關包以及初始化配置。計算節點除了安裝zun-compute服務,還需要安裝要使用的容器,比如Docker。詳細的安裝過程可以參考官方文件,如果僅僅是想進行POC測試,可以透過DevStack自動化快速部署一個AllInOne環境,供參考的local.conf配置檔案如下:

OpenStack容器服務Zun初探與原理分析

如上配置會自動透過DevStack安裝Zun相關元件、Kuryr元件以及Docker。

03

Zun服務入門

3.1 Dashboard

安裝Zun服務之後,可以透過zun命令列以及Dashboard建立和管理容器。

有一個非常讚的功能是如果安裝了Zun,Dashboard能夠支援Cloud Shell,使用者能夠在DashBoard中進行互動式輸入OpenStack命令列。

OpenStack容器服務Zun初探與原理分析

原理的話就是透過Zun啟動了一個gbraad/openstack-client:alpine容器。

透過Dashboard建立容器和建立虛擬機器的過程非常相似,都是透過panel依次選擇映象(image)、選擇規格(Spec)、選擇或者建立卷(volume)、選擇網路(network/port)、選擇安全組(SecuiryGroup)以及scheduler hint,如圖:

OpenStack容器服務Zun初探與原理分析

其中Miscellaneous雜項中則為針對容器的特殊配置,比如設定環境變數(Environment)、工作目錄(Working Directory)等。

3.2 命令列操作

透過命令列建立容器也非常類似,使用過nova以及docker命令列的基本不會有困難,下面以建立一個mysql容器為例:

OpenStack容器服務Zun初探與原理分析
  • 如上透過--mount引數指定了volume大小,由於沒有指定volume_id,因此Zun會新建立一個volume。需要注意的是,Zun建立的volume在容器刪除後,volume也會自動刪除(auto remove),如果需要持久化volume卷,則應該先透過Cinder建立一個volume,然後透過source選項指定volume_id,此時當容器刪除時不會刪除已有的volume卷。

  • 和虛擬機器不一樣,虛擬機器透過flavor配置規格,容器則直接指定cpu、memory、disk。

  • 如上沒有指定--image-driver引數,則預設從dockerhub下載映象,如果指定glance,則會往glance下載映象。

另外mysql容器初始化時資料卷必須為空目錄,掛載的volume新卷格式化時會自動建立lost+found目錄,因此需要手動刪除,否則mysql容器會初始化失敗:

OpenStack容器服務Zun初探與原理分析

建立完成後可以透過zun list命令檢視容器列表:

OpenStack容器服務Zun初探與原理分析

可以看到mysql的容器fixed IP為192.168.233.80,和虛擬機器一樣,租戶IP預設與外面不通,需要繫結一個浮動IP(floating ip),

OpenStack容器服務Zun初探與原理分析

zun命令列目前還無法檢視floating ip,只能透過neutron命令檢視,獲取到floatingip並且安全組入訪允許3306埠後就可以遠端連線mysql服務了:

OpenStack容器服務Zun初探與原理分析

當然在同一租戶的虛擬機器也可以直接透過fixed ip訪問mysql服務:

OpenStack容器服務Zun初探與原理分析

可見,透過容器啟動mysql服務和在虛擬機器裡面部署mysql服務,使用者訪問上沒有什麼區別,在同一個環境中,虛擬機器和容器可共存,彼此可相互通訊,在應用層上可以完全把虛擬機器和容器透明化使用,底層透過應用場景選擇虛擬機器或者容器。

3.3 關於capsule

Zun除了管理容器container外,還引入了capsule的概念,capsule類似Kubernetes的pod,一個capsule可包含多個container,這些container共享network、ipc、pid namespace等。

透過capsule啟動一個mysql服務,宣告yaml檔案如下:

OpenStack容器服務Zun初探與原理分析

建立mysql capsule:

OpenStack容器服務Zun初探與原理分析

可見capsule的init container用的就是kubernetes的pause映象。

3.4 總結

OpenStack的容器服務本來是在Nova中實現的,實現了Nova ComputeDriver,因此Zun的其他的功能如容器生命週期管理、image管理、service管理、action管理等和Nova虛擬機器非常類似,可以檢視官方文件,這裡不再贅述。

04

Zun實現原理

4.1 呼叫容器介面實現容器生命週期管理

前面提到過Zun主要由zun-api和zun-compute服務組成,zun-api主要負責接收使用者請求、引數校驗、資源準備等工作,而zun-compute則真正負責容器的管理,Nova的後端透過compute_driver配置,而Zun的後端則透過container_driver配置,目前只實現了DockerDriver。因此呼叫Zun建立容器,最終就是zun-compute呼叫docker建立容器。

下面以建立一個container為例,簡述其過程。

4.1.1 zun-api

首先入口為zun-api,主要程式碼實現在zun/api/controllers/v1/containers.py以及zun/compute/api.py,建立容器的方法入口為post()方法,其呼叫過程如下:

zun/api/controllers/v1/containers.py

  1. policy enforce: 檢查policy,驗證使用者是否具有建立container許可權的API呼叫。

  2. check security group: 檢查安全組是否存在,根據傳遞的名稱返回安全組的ID。

  3. check container quotas: 檢查quota配額。

  4. build requested network: 檢查網路配置,比如port是否存在、network id是否合法,最後構建內部的network物件模型字典。注意,這一步只檢查並沒有建立port。

  5. create container object:根據傳遞的引數,構造container物件模型。

  6. build requeted volumes: 檢查volume配置,如果傳遞的是volume id,則檢查該volume是否存在,如果沒有傳遞volume id只指定了size,則呼叫Cinder API建立新的volume。

zun/compute/api.py

  1. schedule container: 使用FilterScheduler排程container,返回宿主機的host物件。這個和nova-scheduler非常類似,只是Zun整合到zun-api中了。目前支援的filters如CPUFilter、RamFilter、LabelFilter、ComputeFilter、RuntimeFilter等。

  2. image validation: 檢查映象是否存在,這裡會遠端呼叫zun-compute的image_search方法,其實就是呼叫docker search。這裡主要為了實現快速失敗,避免到了compute節點才發現image不合法。

  3. record action: 和Nova的record action一樣,記錄container的操作日誌。

  4. rpc cast container_create: 遠端非同步呼叫zun-compute的container_create()方法,zun-api任務結束。

4.1.2 zun-compute

zun-compute負責container建立,程式碼位於zun/compute/manager.py,過程如下:

  1. wait for volumes avaiable: 等待volume建立完成,狀態變為avaiable。

  2. attach volumes:掛載volumes,掛載過程後面再介紹。

  3. checksupportdisk_quota: 如果使用本地盤,檢查本地的quota配額。

  4. pull or load image: 呼叫Docker拉取或者載入映象。

  5. 建立docker network、建立neutron port,這個步驟下面詳細介紹。

  6. create container: 呼叫Docker建立容器。

  7. container start: 呼叫Docker啟動容器。

以上呼叫Dokcer拉取映象、建立容器、啟動容器的程式碼位於zun/container/docker/driver.py,該模組基本就是對社群Docker SDK for Python的封裝。

OpenStack容器服務Zun初探與原理分析

Zun的其他操作比如start、stop、kill等實現原理也類似,這裡不再贅述。

4.2 透過websocket實現遠端容器訪問

我們知道虛擬機器可以透過VNC遠端登入,物理伺服器可以透過SOL(IPMI Serial Over LAN)實現遠端訪問,容器則可以透過websocket介面實現遠端互動訪問。

Docker原生支援websocket連線,參考APIAttach to a container via a websocket,websocket地址為/containers/{id}/attach/ws,不過只能在計算節點訪問,那如何透過API訪問呢?

和Nova、Ironic實現完全一樣,也是透過proxy代理轉發實現的,負責container的websocket轉發的程式為zun-wsproxy。

當呼叫zun-compute的container_attach()方法時,zun-compute會把container的websocket_url以及websocket_token儲存到資料庫中.

OpenStack容器服務Zun初探與原理分析

zun-wsproxy則可讀取container的websocket_url作為目標端進行轉發:

OpenStack容器服務Zun初探與原理分析

透過Dashboard可以遠端訪問container的shell:

OpenStack容器服務Zun初探與原理分析

當然透過命令列zun attach也可以attach container。

4.3 使用Cinder實現容器持久化儲存

前面介紹過Zun透過Cinder實現container的持久化儲存,之前我的另一篇文章介紹了Docker使用OpenStack Cinder持久化volume原理分析及實踐,介紹了john griffith開發的docker-cinder-driver以及OpenStack Fuxi專案,這兩個專案都實現了Cinder volume掛載到Docker容器中。另外cinderclient的擴充套件模組python-brick-cinderclient-ext實現了Cinder volume的local attach,即把Cinder volume掛載到物理機中。

Zun沒有複用以上的程式碼模組,而是重新實現了volume attach的功能,不過實現原理和上面的方法完全一樣,主要包含如下過程:

  1. connect volume: connect volume就是把volume attach(對映)到container所在的宿主機上,建立連線的的協議透過initialize_connection資訊獲取,如果是LVM型別則一般透過iscsi,如果是Ceph rbd則直接使用rbd map。

  2. ensure mountpoit tree: 檢查掛載點路徑是否存在,如果不存在則呼叫mkdir建立目錄。

  3. make filesystem:如果是新的volume,掛載時由於沒有檔案系統因此會失敗,此時會建立檔案系統。

  4. do mount: 一切準備就緒,呼叫OS的mount介面掛載volume到指定的目錄點上。

Cinder Driver的程式碼位於`zun/volume/driver.py的Cinder類中,方法如下:

OpenStack容器服務Zun初探與原理分析

其中cinder.attach_volume()實現如上的第1步,而_mount_device()實現瞭如上的2-4步。

4.4 整合Neutron網路實現容器網路多租戶

4.4.1 關於容器網路

前面我們透過Zun建立容器,使用的就是Neutron網路,意味著容器和虛擬機器完全等同的共享Neutron網路服務,虛擬機器網路具有的功能,容器也能實現,比如多租戶隔離、floating ip、安全組、防火牆等。

Docker如何與Neutron網路整合呢?根據官方Docker network plugin API介紹,外掛位於如下目錄:

  • /run/docker/plugins

  • /etc/docker/plugins

  • /usr/lib/docker/plugins

OpenStack容器服務Zun初探與原理分析

由此可見Docker使用的是kuryr網路外掛。

Kuryr也是OpenStack中一個較新的專案,其目標是“Bridge between container framework networking and storage models to OpenStack networking and storage abstractions.”,即實現容器與OpenStack的網路與儲存整合,當然目前只實現了網路部分的整合。

而我們知道目前容器網路主要有兩個主流實現模型:

  • CNM:Docker公司提出,Docker原生使用的該方案,透過HTTP請求呼叫,模型設計可參考The Container Network Model Design,network外掛可實現兩個Driver,其中一個為IPAM Driver,用於實現IP地址管理,另一個為Docker Remote Drivers,實現網路相關的配置。

  • CNI:CoreOS公司提出,Kubernetes選擇了該方案,透過本地方法或者命令列呼叫。

因此Kuryr也分成兩個子專案,kuryr-network實現CNM介面,主要為支援原生的Docker,而kury-kubernetes則實現的是CNI介面,主要為支援Kubernetes,Kubernetes service還整合了Neutron LBaaS,下次再單獨介紹這個專案。

由於Zun使用的是原生的Docker,因此使用的是kuryr-network專案,實現的是CNM介面,透過remote driver的形式註冊到Docker libnetwork中,Docker會自動向外掛指定的socket地址傳送HTTP請求進行網路操作,我們的環境是,即kuryr-libnetwork.service監聽的地址,Remote API介面可以參考Docker Remote Drivers。

4.4.2 kuryr實現原理

前面4.1節介紹到zun-compute會呼叫docker driver的create()方法建立容器,其實這個方法不僅僅是呼叫python docker sdk的create_container()方法,還做了很多工作,其中就包括網路相關的配置。

首先檢查Docker的network是否存在,不存在就建立,network name為Neutron network的UUID,

OpenStack容器服務Zun初探與原理分析

然後會呼叫Neutron建立port,從這裡可以得出結論,容器的port不是Docker libnetwork也不是Kuryr建立的,而是Zun建立的。

回到前面的Remote Driver,Docker libnetwork會首先POST呼叫kuryr的/IpamDriver.RequestAddressAPI請求分配IP,但顯然前面Zun已經建立好了port,port已經分配好了IP,因此這個方法其實就是走走過場。如果直接呼叫docker命令指定kuryr網路建立容器,則會呼叫該方法從Neutron中建立一個port。

接下來會POST呼叫kuryr的/NetworkDriver.CreateEndpoint方法,這個方法最重要的步驟就是binding,即把port attach到宿主機中,binding操作單獨分離出來為kuryr.lib庫,這裡我們使用的是veth driver,因此由kuryr/lib/binding/drivers/veth.py模組的port_bind()方法實現,該方法建立一個veth對,其中一個為tap-xxxx,xxxx為port ID字首,放在宿主機的namespace,另一個為t_cxxxx放到容器的namespace,t_cxxxx會配置上IP,而tap-xxxx則呼叫shell指令碼(指令碼位於/usr/local/libexec/kuryr/)把tap裝置新增到ovs br-int橋上,如果使用HYBRID_PLUG,即安全組透過Linux Bridge實現而不是OVS,則會建立qbr-xxx,並建立一個veth對關聯到ovs br-int上。

從這裡可以看出,Neutron port繫結到虛擬機器和容器基本沒有什麼區別,如下所示:

OpenStack容器服務Zun初探與原理分析

唯一不同的就是虛擬機器是把tap裝置直接對映到虛擬機器的虛擬裝置中,而容器則透過veth對,把另一個tap放到容器的namespace中。

有人會說,br-int的流表在哪裡更新了?這其實是和虛擬機器是完全一樣的,當呼叫port update操作時,neutron server會傳送RPC到L2 agent中(如neutron-openvswitch-agent),agent會根據port的狀態更新對應的tap裝置以及流表。

因此其實kuryr只幹了一件事,那就是把Zun申請的port繫結到容器中。

05

總結

OpenStack Zun專案非常完美地實現了容器與Neutron、Cinder的整合,加上Ironic裸機服務,OpenStack實現了容器、虛擬機器、裸機共享網路與儲存。未來我覺得很長一段時間內裸機、虛擬機器和容器將在資料中心混合存在,OpenStack實現了容器和虛擬機器、裸機的完全平等、資源共享以及功能對齊,應用可以根據自己的需求選擇容器、虛擬機器或者裸機,使用上沒有什麼區別,使用者只需要關心業務針對效能的需求以及對硬體的特殊訪問,對負載(workload)是完全透明的。

參考文獻

  • docker python sdk:

  • Zun’s documentation:

  • 使用OpenStack-Cinder持久化volume原理分析及實踐/

  • https://www.nuagenetworks.net/blog/container-networking-standards/

  • http://blog.kubernetes.io/2016/01/why-Kubernetes-doesnt-use-libnetwork.html

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69940582/viewspace-2658766/,如需轉載,請註明出處,否則將追究法律責任。

相關文章