写点什么

OpenStack容器服务Zun初探与原理分析

2019 年 7 月 19 日

OpenStack容器服务Zun初探与原理分析

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 服务的功能和结构非常相似,只是前者提供容器服务,后者提供虚拟机服务,二者都是主流的计算服务交付模式。功能类似体现在如下几点:


  • 通过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 配置文件如下:



如上配置会自动通过 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 容器为例:



  • 如上通过–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 容器会初始化失败:



创建完成后可以通过 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


  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 的封装。



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 的功能,不过实现原理和上面的方法完全一样,主要包含如下过程:


  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 类中,方法如下:



其中 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



由此可见 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 请求进行网络操作,我们的环境是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)是完全透明的。


参考文献



本文转载自公众号 int32bit(ID:int32bit)


原文链接


https://mp.weixin.qq.com/s/aJsTSaCjoO2Fq_nqCkz0ZQ


2019 年 7 月 19 日 08:008557

评论

发布
暂无评论
发现更多内容

【Howe 学 JAVA】Java 类集框架1——List集合

Howe

Java List 集合

《Linux就该这么学》笔记(一)

编程随想曲

Linux

Using R for everything: 方差分解(Variation partition)变量筛选与显著性标注

洗衣机用户不会用洗衣机

数据分析 R

C语言if分支结构

C语言技术网-码农有道

C语言 C语言if分支结构

如何扩大我们的英语词汇量

七镜花园-董一凡

学习

保险知识梳理

魁拔

保险 生活质量

OceanBase原理与实现分析

ElvinYang

游戏夜读 | 游戏设计需要天赋?

game1night

【Howe 学 JAVA】Java 类集框架2——集合输出

Howe

Java 集合 输出 类集

CentOS7使用Iptables做网络转发

wong

Centos 7 iptables

给应届毕业生们的七点建议

Neco.W

大学生日常 工作 应届毕业

你还在这样使用MYSQL吗?

无箭的丘比特

MySQL 数据库 数据库规范 数据库设计

高仿瑞幸小程序 06 layout布局

曾伟@喵先森

小程序 微信小程序 前端

深入理解MDL元数据锁

Simon

MySQL

人生就是一场说走就走的旅行

kimmking

放假了,你还会打开钉钉么?

无箭的丘比特

高效工作 团队管理 企业文化 个人成长 技术管理

自助设备系列——技术应用

孙苏勇

产品 行业资讯 智能设备

【Howe 学 JAVA】Java 类集框架2——Set 集合

Howe

Java 集合 set

你觉得你是哪类人?

Janenesome

读书笔记 思考

JavaScript 学习笔记——数据类型

zjlulsum

Java 学习 前端 类型推断 入门

探寻融云多年领先的秘密:不断创新贴近开发者真实需求

DT极客

我跑步的时候会想些什么

养牛致富带头人

跑步 运动 锻炼

TL如何在团队中培养出更多前端技术专家

贵重

前端 团队建设 技术管理

面试官竟然一直和我聊线程的启动和终止

Simon郎

Java 大数据 后端 多线程

物联网资产整合架构

老任物联网杂谈

物联网架构

Mac 自带软件-聚焦搜索

Winann

macos Mac spotlight

办公人员的 python 妙用——抽签结果提取

Sicolas Flamel

Python 远程办公

前端开发的瓶颈与未来之路

keelii

node.js typescript ruby-on-rails 编程 前端

带你100% 地了解 Redis 6.0 的客户端缓存

程序员历小冰

redis 缓存 redis6.0.0

MacOS使用指南之我并不需要系统菜单栏

lmymirror

macos 高效工作 完美主义 操作系统 新手指南

Web3极客日报#136

谢锐 | Frozen

区块链 独立开发者 技术社区 Rebase Web3 Daily

OpenStack容器服务Zun初探与原理分析-InfoQ