01
創(chuàng)新互聯(lián)公司從2013年創(chuàng)立,先為綦江等服務建站,綦江等地企業(yè),進行企業(yè)商務咨詢服務。為綦江企業(yè)網(wǎng)站制作PC+手機+微官網(wǎng)三網(wǎng)同步一站式服務解決您的所有建站問題。
Zun服務簡介
Zun是OpenStack的容器服務(Containers as Service),類似于AWS的ECS服務,但實現(xiàn)原理不太一樣,ECS是把容器啟動在EC2虛擬機實例上,而Zun會把容器直接運行在compute節(jié)點上。
和OpenStack另一個容器相關的Magnum項目不一樣的是:Magnum提供的是容器編排服務,能夠提供彈性Kubernetes、Swarm、Mesos等容器基礎設施服務,管理的單元是Kubernetes、Swarm、Mesos集群,而Zun提供的是原生容器服務,支持不同的runtime如Docker、Clear Container等,管理的單元是container。
Zun服務的架構如圖:
Zun服務和Nova服務的功能和結構非常相似,只是前者提供容器服務,后者提供虛擬機服務,二者都是主流的計算服務交付模式。功能類似體現(xiàn)在如下幾點:
通過Neutron提供網(wǎng)絡服務。
通過Cinder實現(xiàn)數(shù)據(jù)的持久化存儲。
都支持使用Glance存儲鏡像。
其他如quota、安全組等功能。
組件結構結構相似則表現(xiàn)在:
二者都是由API、調(diào)度、計算三大組件模塊構成,Nova由nova-api、nova-scheduler、nova-compute三大核心組件構成,而Zun由zun-api、zun-compute兩大核心組件構成,之所以沒有zun-scheduler是因為scheduler集成到zun-api中了。
nova-compute調(diào)用compute driver創(chuàng)建虛擬機,如Libvirt。zun-compute調(diào)用container driver創(chuàng)建容器,如Docker。
Nova通過一系列的proxy代理實現(xiàn)VNC(nova-novncproxy)、Splice(nova-spiceproxy)等虛擬終端訪問,Zun也是通過proxy代理容器的websocket實現(xiàn)遠程attach容器功能。
02
Zun服務部署
Zun服務部署和Nova、Cinder部署模式類似,控制節(jié)點創(chuàng)建數(shù)據(jù)庫、Keystone創(chuàng)建service以及注冊endpoints等,最后安裝相關包以及初始化配置。計算節(jié)點除了安裝zun-compute服務,還需要安裝要使用的容器,比如Docker。詳細的安裝過程可以參考官方文檔,如果僅僅是想進行POC測試,可以通過DevStack自動化快速部署一個AllInOne環(huán)境,供參考的local.conf配置文件如下:
如上配置會自動通過DevStack安裝Zun相關組件、Kuryr組件以及Docker。
03
Zun服務入門
3.1 Dashboard
安裝Zun服務之后,可以通過zun命令行以及Dashboard創(chuàng)建和管理容器。
有一個非常贊的功能是如果安裝了Zun,Dashboard能夠支持Cloud Shell,用戶能夠在DashBoard中進行交互式輸入OpenStack命令行。
原理的話就是通過Zun啟動了一個gbraad/openstack-client:alpine容器。
通過Dashboard創(chuàng)建容器和創(chuàng)建虛擬機的過程非常相似,都是通過panel依次選擇鏡像(image)、選擇規(guī)格(Spec)、選擇或者創(chuàng)建卷(volume)、選擇網(wǎng)絡(network/port)、選擇安全組(SecuiryGroup)以及scheduler hint,如圖:
其中Miscellaneous雜項中則為針對容器的特殊配置,比如設置環(huán)境變量(Environment)、工作目錄(Working Directory)等。
3.2 命令行操作
通過命令行創(chuàng)建容器也非常類似,使用過nova以及docker命令行的基本不會有困難,下面以創(chuàng)建一個MySQL容器為例:
如上通過--mount參數(shù)指定了volume大小,由于沒有指定volume_id,因此Zun會新創(chuàng)建一個volume。需要注意的是,Zun創(chuàng)建的volume在容器刪除后,volume也會自動刪除(auto remove),如果需要持久化volume卷,則應該先通過Cinder創(chuàng)建一個volume,然后通過source選項指定volume_id,此時當容器刪除時不會刪除已有的volume卷。
和虛擬機不一樣,虛擬機通過flavor配置規(guī)格,容器則直接指定cpu、memory、disk。
如上沒有指定--image-driver參數(shù),則默認從dockerhub下載鏡像,如果指定glance,則會往glance下載鏡像。
另外mysql容器初始化時數(shù)據(jù)卷必須為空目錄,掛載的volume新卷格式化時會自動創(chuàng)建lost+found目錄,因此需要手動刪除,否則mysql容器會初始化失敗:
創(chuàng)建完成后可以通過zun list命令查看容器列表:
可以看到mysql的容器fixed IP為192.168.233.80,和虛擬機一樣,租戶IP默認與外面不通,需要綁定一個浮動IP(floating ip),
zun命令行目前還無法查看floating ip,只能通過neutron命令查看,獲取到floatingip并且安全組入訪允許3306端口后就可以遠程連接mysql服務了:
當然在同一租戶的虛擬機也可以直接通過fixed ip訪問mysql服務:
可見,通過容器啟動mysql服務和在虛擬機里面部署mysql服務,用戶訪問上沒有什么區(qū)別,在同一個環(huán)境中,虛擬機和容器可共存,彼此可相互通信,在應用層上可以完全把虛擬機和容器透明化使用,底層通過應用場景選擇虛擬機或者容器。
3.3 關于capsule
Zun除了管理容器container外,還引入了capsule的概念,capsule類似Kubernetes的pod,一個capsule可包含多個container,這些container共享network、ipc、pid namespace等。
通過capsule啟動一個mysql服務,聲明yaml文件如下:
創(chuàng)建mysql capsule:
可見capsule的init container用的就是kubernetes的pause鏡像。
3.4 總結
OpenStack的容器服務本來是在Nova中實現(xiàn)的,實現(xiàn)了Nova ComputeDriver,因此Zun的其他的功能如容器生命周期管理、image管理、service管理、action管理等和Nova虛擬機非常類似,可以查看官方文檔,這里不再贅述。
04
Zun實現(xiàn)原理
4.1 調(diào)用容器接口實現(xiàn)容器生命周期管理
前面提到過Zun主要由zun-api和zun-compute服務組成,zun-api主要負責接收用戶請求、參數(shù)校驗、資源準備等工作,而zun-compute則真正負責容器的管理,Nova的后端通過compute_driver配置,而Zun的后端則通過container_driver配置,目前只實現(xiàn)了DockerDriver。因此調(diào)用Zun創(chuàng)建容器,最終就是zun-compute調(diào)用docker創(chuàng)建容器。
下面以創(chuàng)建一個container為例,簡述其過程。
4.1.1 zun-api
首先入口為zun-api,主要代碼實現(xiàn)在zun/api/controllers/v1/containers.py以及zun/compute/api.py,創(chuàng)建容器的方法入口為post()方法,其調(diào)用過程如下:
zun/api/controllers/v1/containers.py
policy enforce: 檢查policy,驗證用戶是否具有創(chuàng)建container權限的API調(diào)用。
check security group: 檢查安全組是否存在,根據(jù)傳遞的名稱返回安全組的ID。
check container quotas: 檢查quota配額。
build requested network: 檢查網(wǎng)絡配置,比如port是否存在、network id是否合法,最后構建內(nèi)部的network對象模型字典。注意,這一步只檢查并沒有創(chuàng)建port。
create container object:根據(jù)傳遞的參數(shù),構造container對象模型。
build requeted volumes: 檢查volume配置,如果傳遞的是volume id,則檢查該volume是否存在,如果沒有傳遞volume id只指定了size,則調(diào)用Cinder API創(chuàng)建新的volume。
zun/compute/api.py
schedule container: 使用FilterScheduler調(diào)度container,返回宿主機的host對象。這個和nova-scheduler非常類似,只是Zun集成到zun-api中了。目前支持的filters如CPUFilter、RamFilter、LabelFilter、ComputeFilter、RuntimeFilter等。
image validation: 檢查鏡像是否存在,這里會遠程調(diào)用zun-compute的image_search方法,其實就是調(diào)用docker search。這里主要為了實現(xiàn)快速失敗,避免到了compute節(jié)點才發(fā)現(xiàn)image不合法。
record action: 和Nova的record action一樣,記錄container的操作日志。
rpc cast container_create: 遠程異步調(diào)用zun-compute的container_create()方法,zun-api任務結束。
4.1.2 zun-compute
zun-compute負責container創(chuàng)建,代碼位于zun/compute/manager.py,過程如下:
wait for volumes avaiable: 等待volume創(chuàng)建完成,狀態(tài)變?yōu)閍vaiable。
attach volumes:掛載volumes,掛載過程后面再介紹。
checksupportdisk_quota: 如果使用本地盤,檢查本地的quota配額。
pull or load image: 調(diào)用Docker拉取或者加載鏡像。
創(chuàng)建docker network、創(chuàng)建neutron port,這個步驟下面詳細介紹。
create container: 調(diào)用Docker創(chuàng)建容器。
container start: 調(diào)用Docker啟動容器。
以上調(diào)用Dokcer拉取鏡像、創(chuàng)建容器、啟動容器的代碼位于zun/container/docker/driver.py,該模塊基本就是對社區(qū)Docker SDK for Python的封裝。
Zun的其他操作比如start、stop、kill等實現(xiàn)原理也類似,這里不再贅述。
4.2 通過websocket實現(xiàn)遠程容器訪問
我們知道虛擬機可以通過VNC遠程登錄,物理服務器可以通過SOL(IPMI Serial Over LAN)實現(xiàn)遠程訪問,容器則可以通過websocket接口實現(xiàn)遠程交互訪問。
Docker原生支持websocket連接,參考APIAttach to a container via a websocket,websocket地址為/containers/{id}/attach/ws,不過只能在計算節(jié)點訪問,那如何通過API訪問呢?
和Nova、Ironic實現(xiàn)完全一樣,也是通過proxy代理轉發(fā)實現(xiàn)的,負責container的websocket轉發(fā)的進程為zun-wsproxy。
當調(diào)用zun-compute的container_attach()方法時,zun-compute會把container的websocket_url以及websocket_token保存到數(shù)據(jù)庫中.
zun-wsproxy則可讀取container的websocket_url作為目標端進行轉發(fā):
通過Dashboard可以遠程訪問container的shell:
當然通過命令行zun attach也可以attach container。
4.3 使用Cinder實現(xiàn)容器持久化存儲
前面介紹過Zun通過Cinder實現(xiàn)container的持久化存儲,之前我的另一篇文章介紹了Docker使用OpenStack Cinder持久化volume原理分析及實踐,介紹了john griffith開發(fā)的docker-cinder-driver以及OpenStack Fuxi項目,這兩個項目都實現(xiàn)了Cinder volume掛載到Docker容器中。另外cinderclient的擴展模塊python-brick-cinderclient-ext實現(xiàn)了Cinder volume的local attach,即把Cinder volume掛載到物理機中。
Zun沒有復用以上的代碼模塊,而是重新實現(xiàn)了volume attach的功能,不過實現(xiàn)原理和上面的方法完全一樣,主要包含如下過程:
connect volume: connect volume就是把volume attach(映射)到container所在的宿主機上,建立連接的的協(xié)議通過initialize_connection信息獲取,如果是LVM類型則一般通過iscsi,如果是Ceph rbd則直接使用rbd map。
ensure mountpoit tree: 檢查掛載點路徑是否存在,如果不存在則調(diào)用mkdir創(chuàng)建目錄。
make filesystem:如果是新的volume,掛載時由于沒有文件系統(tǒng)因此會失敗,此時會創(chuàng)建文件系統(tǒng)。
do mount: 一切準備就緒,調(diào)用OS的mount接口掛載volume到指定的目錄點上。
Cinder Driver的代碼位于`zun/volume/driver.py的Cinder類中,方法如下:
其中cinder.attach_volume()實現(xiàn)如上的第1步,而_mount_device()實現(xiàn)了如上的2-4步。
4.4 集成Neutron網(wǎng)絡實現(xiàn)容器網(wǎng)絡多租戶
4.4.1 關于容器網(wǎng)絡
前面我們通過Zun創(chuàng)建容器,使用的就是Neutron網(wǎng)絡,意味著容器和虛擬機完全等同的共享Neutron網(wǎng)絡服務,虛擬機網(wǎng)絡具有的功能,容器也能實現(xiàn),比如多租戶隔離、floating ip、安全組、防火墻等。
Docker如何與Neutron網(wǎng)絡集成呢?根據(jù)官方Docker network plugin API介紹,插件位于如下目錄:
/run/docker/plugins
/etc/docker/plugins
/usr/lib/docker/plugins
由此可見Docker使用的是kuryr網(wǎng)絡插件。
Kuryr也是OpenStack中一個較新的項目,其目標是“Bridge between container framework networking and storage models to OpenStack networking and storage abstractions.”,即實現(xiàn)容器與OpenStack的網(wǎng)絡與存儲集成,當然目前只實現(xiàn)了網(wǎng)絡部分的集成。
而我們知道目前容器網(wǎng)絡主要有兩個主流實現(xiàn)模型:
CNM:Docker公司提出,Docker原生使用的該方案,通過HTTP請求調(diào)用,模型設計可參考The Container Network Model Design,network插件可實現(xiàn)兩個Driver,其中一個為IPAM Driver,用于實現(xiàn)IP地址管理,另一個為Docker Remote Drivers,實現(xiàn)網(wǎng)絡相關的配置。
CNI:CoreOS公司提出,Kubernetes選擇了該方案,通過本地方法或者命令行調(diào)用。
因此Kuryr也分成兩個子項目,kuryr-network實現(xiàn)CNM接口,主要為支持原生的Docker,而kury-kubernetes則實現(xiàn)的是CNI接口,主要為支持Kubernetes,Kubernetes service還集成了Neutron LBaaS,下次再單獨介紹這個項目。
由于Zun使用的是原生的Docker,因此使用的是kuryr-network項目,實現(xiàn)的是CNM接口,通過remote driver的形式注冊到Docker libnetwork中,Docker會自動向插件指定的socket地址發(fā)送HTTP請求進行網(wǎng)絡操作,我們的環(huán)境是http://127.0.0.1:23750,即kuryr-libnetwork.service監(jiān)聽的地址,Remote API接口可以參考Docker Remote Drivers。
4.4.2 kuryr實現(xiàn)原理
前面4.1節(jié)介紹到zun-compute會調(diào)用docker driver的create()方法創(chuàng)建容器,其實這個方法不僅僅是調(diào)用python docker sdk的create_container()方法,還做了很多工作,其中就包括網(wǎng)絡相關的配置。
首先檢查Docker的network是否存在,不存在就創(chuàng)建,network name為Neutron network的UUID,
然后會調(diào)用Neutron創(chuàng)建port,從這里可以得出結論,容器的port不是Docker libnetwork也不是Kuryr創(chuàng)建的,而是Zun創(chuàng)建的。
回到前面的Remote Driver,Docker libnetwork會首先POST調(diào)用kuryr的/IpamDriver.RequestAddressAPI請求分配IP,但顯然前面Zun已經(jīng)創(chuàng)建好了port,port已經(jīng)分配好了IP,因此這個方法其實就是走走過場。如果直接調(diào)用docker命令指定kuryr網(wǎng)絡創(chuàng)建容器,則會調(diào)用該方法從Neutron中創(chuàng)建一個port。
接下來會POST調(diào)用kuryr的/NetworkDriver.CreateEndpoint方法,這個方法最重要的步驟就是binding,即把port attach到宿主機中,binding操作單獨分離出來為kuryr.lib庫,這里我們使用的是veth driver,因此由kuryr/lib/binding/drivers/veth.py模塊的port_bind()方法實現(xiàn),該方法創(chuàng)建一個veth對,其中一個為tap-xxxx,xxxx為port ID前綴,放在宿主機的namespace,另一個為t_cxxxx放到容器的namespace,t_cxxxx會配置上IP,而tap-xxxx則調(diào)用shell腳本(腳本位于/usr/local/libexec/kuryr/)把tap設備添加到ovs br-int橋上,如果使用HYBRID_PLUG,即安全組通過Linux Bridge實現(xiàn)而不是OVS,則會創(chuàng)建qbr-xxx,并創(chuàng)建一個veth對關聯(lián)到ovs br-int上。
從這里可以看出,Neutron port綁定到虛擬機和容器基本沒有什么區(qū)別,如下所示:
唯一不同的就是虛擬機是把tap設備直接映射到虛擬機的虛擬設備中,而容器則通過veth對,把另一個tap放到容器的namespace中。
有人會說,br-int的流表在哪里更新了?這其實是和虛擬機是完全一樣的,當調(diào)用port update操作時,neutron server會發(fā)送RPC到L2 agent中(如neutron-openvswitch-agent),agent會根據(jù)port的狀態(tài)更新對應的tap設備以及流表。
因此其實kuryr只干了一件事,那就是把Zun申請的port綁定到容器中。
05
總結
OpenStack Zun項目非常完美地實現(xiàn)了容器與Neutron、Cinder的集成,加上Ironic裸機服務,OpenStack實現(xiàn)了容器、虛擬機、裸機共享網(wǎng)絡與存儲。未來我覺得很長一段時間內(nèi)裸機、虛擬機和容器將在數(shù)據(jù)中心混合存在,OpenStack實現(xiàn)了容器和虛擬機、裸機的完全平等、資源共享以及功能對齊,應用可以根據(jù)自己的需求選擇容器、虛擬機或者裸機,使用上沒有什么區(qū)別,用戶只需要關心業(yè)務針對性能的需求以及對硬件的特殊訪問,對負載(workload)是完全透明的。
參考文獻
docker python sdk: https://docker-py.readthedocs.io/en/stable/
Zun’s documentation: https://docs.openstack.org/zun/latest/
https://docs.docker.com/engine/api/v1.39/#operation/ContainerAttachWebsocket
http://int32bit.me/2017/10/04/Docker使用OpenStack-Cinder持久化volume原理分析及實踐/
https://specs.openstack.org/openstack/cinder-specs/specs/mitaka/use-cinder-without-nova.html
https://docs.docker.com/engine/extend/plugin_api/
https://github.com/docker/libnetwork/blob/master/docs/design.md
https://github.com/docker/libnetwork/blob/master/docs/ipam.md
https://github.com/docker/libnetwork/blob/master/docs/remote.md
https://docs.openstack.org/kuryr-libnetwork/latest/
https://docs.openstack.org/magnum/latest/user/
https://github.com/docker/libnetwork
https://www.nuagenetworks.net/blog/container-networking-standards/
http://blog.kubernetes.io/2016/01/why-Kubernetes-doesnt-use-libnetwork.html