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服务的架构如图:
Zun服务和Nova服务的功能和结构非常相似,只是前者提供容器服务,后者提供虚拟机服务,二者都是主流的计算服务交付模式。功能类似体现在如下几点:
组件结构结构相似则表现在:
02 Zun服务部署
Zun服务部署和Nova、Cinder部署模式类似,控制节点创建数据库、Keystone创建service以及注册endpoints等,最后安装相关包以及初始化配置。计算节点除了安装zun-compute服务,还需要安装要使用的容器,比如Docker。详细的安装过程可以参考官方文档,如果仅仅是想进行POC测试,可以通过DevStack自动化快速部署一个AllInOne环境,供参考的local.conf配置文件如下:
如上配置会自动通过DevStack安装Zun相关组件、Kuryr组件以及Docker。
03 Zun服务入门
3.1 Dashboard
安装Zun服务之后,可以通过zun命令行以及Dashboard创建和管理容器。
有一个非常赞的功能是如果安装了Zun,Dashboard能够支持Cloud Shell,用户能够在DashBoard中进行交互式输入OpenStack命令行。
原理的话就是通过Zun启动了一个gbraad/openstack-client:alpine容器。
通过Dashboard创建容器和创建虚拟机的过程非常相似,都是通过panel依次选择镜像(image)、选择规格(Spec)、选择或者创建卷(volume)、选择网络(network/port)、选择安全组(SecuiryGroup)以及scheduler hint,如图:
其中Miscellaneous杂项中则为针对容器的特殊配置,比如设置环境变量(Environment)、工作目录(Working Directory)等。
3.2 命令行操作
通过命令行创建容器也非常类似,使用过nova以及docker命令行的基本不会有困难,下面以创建一个mysql容器为例:
另外mysql容器初始化时数据卷必须为空目录,挂载的volume新卷格式化时会自动创建lost+found目录,因此需要手动删除,否则mysql容器会初始化失败:
创建完成后可以通过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服务,用户访问上没有什么区别,在同一个环境中,虚拟机和容器可共存,彼此可相互通信,在应用层上可以完全把虚拟机和容器透明化使用,底层通过应用场景选择虚拟机或者容器。
3.3 关于capsule
Zun除了管理容器container外,还引入了capsule的概念,capsule类似Kubernetes的pod,一个capsule可包含多个container,这些container共享network、ipc、pid namespace等。
通过capsule启动一个mysql服务,声明yaml文件如下:
创建mysql capsule:
可见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
zun/compute/api.py
4.1.2 zun-compute
zun-compute负责container创建,代码位于zun/compute/manager.py,过程如下:
以上调用Dokcer拉取镜像、创建容器、启动容器的代码位于zun/container/docker/driver.py,该模块基本就是对社区Docker SDK for Python的封装。
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保存到数据库中。
zun-wsproxy则可读取container的websocket_url作为目标端进行转发:
通过Dashboard可以远程访问container的shell:
当然通过命令行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的功能,不过实现原理和上面的方法完全一样,主要包含如下过程:
Cinder Driver的代码位于`zun/volume/driver.py的Cinder类中,方法如下:
其中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介绍,插件位于如下目录:
由此可见Docker使用的是kuryr网络插件。
Kuryr也是OpenStack中一个较新的项目,其目标是“Bridge between container framework networking and storage models to OpenStack networking and storage abstractions.”,即实现容器与OpenStack的网络与存储集成,当然目前只实现了网络部分的集成。
而我们知道目前容器网络主要有两个主流实现模型:
因此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请求进行网络操作,我们的环境是http://127.0.0.1:23750,即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。
然后会调用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绑定到虚拟机和容器基本没有什么区别,如下所示:
唯一不同的就是虚拟机是把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)是完全透明的。
参考文献
如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!
赞3
添加新评论0 条评论