云计算之OpenStack核心
- 一、OpenStack架构
- 二、OpenStack核心服务
- 2.1 认证服务Keystone
- 2.2 镜像服务Image
- 2.3 计算服务Nova
- 2.3.1 Nova架构
- 2.3.2 Nova 物理部署方案
- 2.3.3 从虚机创建流程看 nova-* 子服务如何协同工作
- 2.3.4 OpenStack 通用设计思路
- 2.3.5 Nova 组件详解
- 2.3.6 看懂 OpenStack 日志
- 2.3.7 虚拟机生命周期管理
- 2.3.7.1 Launch-部署虚拟机
- 2.3.7.2 Shut Off-关闭虚拟机
- 2.3.7.3 Start-启动虚拟机
- 2.3.7.4 Soft/Hard Reboot-软/硬重启虚拟机
- 2.3.7.5 Lock/Unlock-加锁/解锁虚拟机
- 2.3.7.6 Terminate -删除虚拟机
- 2.3.7.7 Pause/Resume-暂停(短时间)/恢复(暂停后的恢复,非故障时恢复)虚拟机
- 2.3.7.8 Suspend/Resume-暂停(长时间)/恢复(暂停后的恢复,非故障时恢复)虚拟机
- 2.3.7.9 Rescue/Unrescue-恢复(发生故障时恢复)/重新引导虚拟机
- 2.3.7.10 Snapshot -创建虚拟机快照
- 2.3.7.11 Rebuild -通过快照恢复虚拟机
- 2.3.7.12 Shelve-搁置虚拟机
- 2.3.7.13 Unshelve-取消搁置虚拟机
- 2.3.7.14 Migrate -迁移虚拟机
- 2.3.7.15 Resize-调整虚拟机的vCPU、内存和磁盘资源
- 2.3.7.16 Live Migrate-在线迁移虚拟机
- 2.3.7.17 Evacuate-撤离虚拟机
- 2.3.7.18 Nova操作总结
- 2.4 块存储服务Cinder:为 instance 提供虚拟磁盘
- 2.5 网络服务Neutron
一、OpenStack架构
1.1 OpenStack概念架构
- 中间菱形:是虚拟机,围绕 VM 的那些长方形代表 OpenStack 不同的模块或服务;
- Nova(计算服务,核心服务):管理 管理计算资源,是 OpenStack 中最核心的服务;
- Neutron(网络服务,核心服务):为 OpenStack 提供网络连接服务,负责创建和管理L2、L3 网络, 为 VM 提供虚拟网络和物理网络连接;
- Glance(镜像服务,核心服务):管理 VM 的启动镜像,Nova 创建 VM 时将使用 Glance 提供的镜像;
- Cinder(块存储,核心服务):为 VM 提供块存储服务。Cinder 提供的每一个 Volume 在 VM 看来是一块虚拟硬盘,一般用作数据盘;
- Swift(对象存储,可选服务):提供对象存储服务。VM 可以通过 RESTful API 存放对象数据;作为可选的方案,Glance 可以将镜像存放在 Swift 中;Cinder 也可以将 Volume 备份到 Swift 中;
- Keystone(认证服务,核心服务):为 OpenStack 的各种服务提供认证和权限管理服务。简单的说,OpenStack 上的每一个操作都必须通过 Keystone 的审核;
- Ceilometer(监控服务,可选服务):提供 OpenStack监控和计量服务,为报警、统计或计费提供数据;
- Horizon:为 OpenStack 用户提供一个 Web操作界面;
1.2 OpenStack逻辑架构
- 在逻辑架构中,可以看到每个服务又由若干组件组成:
- 上图逻辑架构中,以 Neutron 服务为例,描述了各个组成部分以及各组件之间的逻辑关系。 而在实际的部署方案上,各个组件可以部署到不同的物理节点上。
- OpenStack 本身是一个分布式系统,不但各个服务可以分布部署,服务中的组件也可以分布部署。 这种分布式特性让 OpenStack 具备极大的灵活性、伸缩性和高可用性。
1.3 拓扑部署
- OpenStack 是一个分布式系统,由若干不同功能的节点(Node)组成:
1)控制节点(Controller Node):管理 OpenStack,其上运行的服务有 Keystone、Glance、Horizon 以及 Nova 和 Neutron 中管理相关的组件。 控制节点也运行支持 OpenStack 的服务,例如 SQL 数据库(通常是 MySQL)、消息队列(通常是 RabbitMQ)和网络时间服务 NTP。
2)计算节点(Compute Node):其上运行 Hypervisor(默认使用 KVM)。 同时运行 Neutron 服务的 agent,为虚拟机提供网络支持。
3) 网络节点(Network Node):其上运行的服务为 Neutron。 为 OpenStack 提供 L2 和 L3 网络。 包括虚拟机网络、DHCP、路由、NAT 等。
4) 存储节点(Storage Node):提供块存储(Cinder)或对象存储(Swift)服务。 - 这几类节点是从功能上进行的逻辑划分,在实际部署时可以根据需求灵活配置,比如:在大规模OpenStack生产环境中,每类节点都分别部署在若干台物理服务器上,各司其职并互相协作。 这样的环境具备很好的性能、伸缩性和高可用性。在最小的实验环境中,可以将 4 类节点部署到一个物理的甚至是虚拟服务器上,通常也称为 All-in-One 部署。
- 在我们的实验环境中,为了使得拓扑简洁同时功能完备,我们用两个虚拟机:
devstack-controller: 控制节点 + 网络节点 + 块存储节点 + 计算节点
devstack-compute: 计算节点
- 物理资源需求,可根据实际需求动态调整:
- 网络规划:
网络上规划了三个网络
1)Management Network:用于 OpenStack 内部管理用,比如各服务之间通信。 这里使用 eth0;
2)VM(Tenant)Network:OpenStack 部署的虚拟机所使用的网络。OpenStack 支持多租户(Tenant),虚机是放在 Tenant 下的,所以叫 Tenant Network。 这里使用 eth1;
3)External Network:一般来说,Tenant Network 是内部私有网络,只用于 VM 之间通信,与其他非 VM 网络是隔离的。这里我们规划了一个外部网络(External Network),通过 devstak-controller 的 eth2 连接。 Neutron 通过 L3 服务让 VM 能够访问到 External Network。对于公有云,External Network 一般指的是 Internet。对于企业私有云,External Network 则可以是 Intranet 中的某个网络。 - 创建虚拟机:按照物理资源需求创建 devstack-controller 和 devstak-compute 虚拟机:
安装操作系统
安装 Ubuntu 14.04,并配置 eth0 的 IP,如下所示:
控制节点 devstack-controller 192.168.104.10
计算节点 devstak-compute 192.168.104.11
参考:https://www.xjimmy.com/openstack-5min-17.html
1.4 使用OpenStack CLI
1.4.1 OpenStack 服务都有自己的 CLI
- 命令很好记,就是服务的名字,比如 Glance 就是 glance,Nova 就是 nova。但 Keystone 比较特殊,现在是用 openstack 来代替老版的 keystone 命令。比如查询用户列表,如果用
keystone user-list
:
会提示 keystone 已经 deprecated 了,用
openstack user list
命令代替:
- 执行命令之前,需要设置环境变量:
这些变量包含用户名、Project、密码等; 如果不设置,每次执行命令都必须设置相关的命令行参数 - 各个服务的命令都有增、删、改、查的操作,其格式是:
CMD <obj>-create [parm1] [parm2]…
CMD <obj>-delete [parm]
CMD <obj>-update [parm1] [parm2]…
CMD <obj>-list
CMD <obj>-show [parm]
- 例如 glance 管理的是 image,那么:CMD 就是 glance;obj 就是 image,对应的命令就有:
glance image-create
glance image-delete
glance image-update
glance image-list
glance image-show
- 再比如 neutron 管理的是网络和子网等,那么: CMD 就是 neutron;obj 就是 net 和 subnet :
# 网络相关操作
neutron net-create
neutron net -delete
neutron net -update
neutron net -list
neutron net –show
# 子网相关操作
neutron subnet-create
neutron subnet -delete
neutron subnet -update
neutron subnet -list
neutron subnet–show
- 有的命令 可以省略,比如 nova,下面的操作都是针对 instance:
nova boot
nova delete
nova list
nova show
- 没个对象都有ID:delete,show 等操作都以 ID 为参数,例如:
- 可用 help 查看命令的用法,除了 delete,show 等操作只需要 ID 一个参数,其他操作可能需要更多的参数,用 help 查看所需的参数,格式是:
CMD help [SUB-CMD]
例如查看 glance 都有哪些 SUB-CMD:
查看 glance image-update 的用法:
二、OpenStack核心服务
2.1 认证服务Keystone
2.1.1 基本功能
- 作为 OpenStack 的基础支持服务,Keystone 做下面这几件事情:
1)管理用户及其权限;
2)维护 OpenStack Services 的 Endpoint;
3)Authentication(认证)和 Authorization(鉴权)。
2.1.2 基本概念
- user:指代任何使用 OpenStack 的实体,可以是真正的用户,其他系统或者服务,当 User 请求访问 OpenStack 时,Keystone 会对其进行验证;
Horizon 在 Identity->Users 管理 User:
除了 admin 和 demo,OpenStack 也为 nova、cinder、glance、neutron 服务创建了相应的 User。 admin 也可以管理这些 User。
- Credentials:Credentials 是 User 用来证明自己身份的信息,可以是: 1. 用户名/密码 2. Token 3. API Key 4. 其他高级方式。
- Authentication: 是 Keystone 验证 User 身份的过程。User 访问 OpenStack 时向 Keystone 提交用户名和密码形式的 Credentials,Keystone 验证通过后会给 User 签发一个 Token 作为后续访问的 Credential。
- Token:Token 是由数字和字母组成的字符串,User 成功 Authentication 后由 Keystone 分配给 User。Token 用做访问 Service 的 Credential;Service 会通过 Keystone 验证 Token 的有效性;Token 的有效期默认是 24 小时。
- Project: 用于将 OpenStack 的资源(计算、存储和网络)进行分组和隔离。根据 OpenStack 服务的对象不同,Project 可以是一个客户(公有云,也叫租户)、部门或者项目组(私有云)。这里请注意:资源的所有权是属于 Project 的,而不是 User。在 OpenStack 的界面和文档中,Tenant / Project / Account 这几个术语是通用的,但长期看会倾向使用 Project。每个 User(包括 admin)必须挂在 Project 里才能访问该 Project 的资源。 一个User可以属于多个 Project。admin 相当于 root 用户,具有最高权限
Horizon 在 Identity->Projects 中管理 Project:
通过 Manage Members 将 User 添加到 Project 中:
- Service:OpenStack 的 Service 包括 Compute (Nova)、Block Storage (Cinder)、Object Storage (Swift)、Image Service (Glance) 、Networking Service (Neutron) 等。每个 Service 都会提供若干个 Endpoint,User 通过 Endpoint 访问资源和执行操作。
- Endpoint:Endpoint 是一个网络上可访问的地址,通常是一个 URL。Service 通过 Endpoint 暴露自己的 API。Keystone 负责管理和维护每个 Service 的 Endpoint。
可以使用下面的命令来查看 Endpoint:
root@devstack-controller:~# source devstack/openrc admin admin //切换用户
root@devstack-controller:~# openstack catalog list //查询服务的Endpoint
- Role:安全包含两部分:Authentication(认证)和 Authorization(鉴权)。Keystone 是借助 Role 来实现 Authorization 的。
Keystone定义Role:
可以为 User 分配一个或多个 Role,Horizon 的菜单为 Identity->Project->Manage Members:
Service 决定每个 Role 能做什么事情 Service 通过各自的 policy.json 文件对 Role 进行访问控制。下面是 Nova 服务 /etc/nova/policy.json 中的示例:
上面配置的含义是:对于 create、attach_network 和 attach_volume 操作,任何Role的 User 都可以执行; 但只有 admin 这个 Role 的 User 才能执行 forced_host 操作。OpenStack 默认配置只区分 admin 和非 admin Role。 如果需要对特定的 Role 进行授权,可以修改 policy.json。
2.1.3 举例说明:admin用户查看Project中的image
- 登录:账号密码为admin/admin,点击“Connect”:
此时OpenStack 内部发生了哪些事情?请看下面:Token 中包含了 User 的 Role 信息。
- 显示操作界面:请注意,顶部显示 admin 可访问的 Project 为 “admin” 和 “demo”。
在此之前发生了什么事情:
同时,admin 可以访问 Intance, Volume, Image 等服务
因为 admin 已经从 Keystone 拿到了各 Service 的 Endpoints
- 显示image列表:点击 “Images”,会显示 image 列表;
背后发生了这些事:首先,admin 将请求发送到 Glance 的 Endpoint:
Glance 向 Keystone 询问 admin 身份的有效性:
接下来 Glance 会查看 /etc/glance/policy.json,判断 admin 是否有查看 image 的权限:
权限判定通过,Glance 将 image 列表发给 admin。 - Troubleshoot :OpenStack 排查问题的方法主要是通过日志,每个 Service 都有自己的日志文件。Keystone 主要有两个日志:keystone.log 和 keystone_access.log,保存在 /var/log/apache2/ 目录里。
devstack 的 screen 窗口已经帮我们打开了这两个日志。可以直接查看:
如果需要得到最详细的日志信息,可以在 /etc/keystone/keystone.conf 中打开 debug 选项:
在非 devstack 安装中,日志可能在 /var/log/keystone/ 目录里。
2.2 镜像服务Image
2.2.1 基本概念
- 在传统 IT 环境下,安装一个系统是要么从安装 CD 从头安装,要么用 Ghost 等克隆工具恢复,这两种方式有如下几个问题:
1)如果要安装的系统多了效率就很低;
2)时间长,工作量大;
3)安装完还要进行手工配置,比如安装其他的软件,设置 IP 等;
4)备份和恢复系统不灵活。 - 云环境下需要更高效的解决方案,这就是 Image:
Image 是一个模板,里面包含了基本的操作系统和其他的软件。 举例来说,有家公司需要为每位员工配置一套办公用的系统,一般需要一个 Win7 系统再加 MS office 软件。
OpenStack 是这么玩的,先手工安装好这么一个虚机;然后对虚机执行 snapshot,这样就得到了一个 image;当有新员工入职需要办公环境时,立马启动一个或多个该 image 的 instance(虚机)就可以了。 - 在这个过程中,第 1 步跟传统方式类似,需要手工操作和一定时间。但第 2、3 步非常快,全自动化,一般都是秒级别。而且 2、3 步可以循环做。比如公司新上了一套 OA 系统,每个员工的 PC 上都得有客户端软件。 那么可以在某个员工的虚机中手工安装好 OA 客户端,然后执行snapshot ,得到新的 image,以后就直接使用新 image 创建虚机就可以了。
- 另外,snapshot 还有备份的作用,能够非常方便的恢复系统。
- Image Service 的功能是管理 Image,让用户能够发现、获取和保存 Image。在 OpenStack 中,提供 Image Service 的是 Glance,其具体功能如下:
1)提供 REST API 让用户能够查询和获取 image 的元数据和 image 本身;
2)支持多种方式存储 image,包括普通的文件系统、Swift、Amazon S3 等;
3)对 Instance 执行 Snapshot 创建新的 image。
2.2.2 Glance架构
2.2.2.1 glance-api
- glance-api 是系统后台运行的服务进程。 对外提供 REST API,响应 image 查询、获取和存储的调用。
- glance-api 不会真正处理请求。
- 如果是与 image metadata(元数据)相关的操作,glance-api 会把请求转发给 glance-registry;
- 如果是与 image 自身存取相关的操作,glance-api 会把请求转发给该 image 的 store backend。
- 在控制节点上可以查看 glance-api 进程:
2.2.2.2 glance-registry
- glance-registry 是系统后台运行的服务进程。负责处理和存取 image 的 metadata,例如 image 的大小和类型。在控制节点上可以查看 glance-registry 进程:
- Glance 支持多种格式的 image,包括:
2.2.2.3 Database
- Image 的 metadata 会保存到 database 中,默认是 MySQL。在控制节点上可以查看 glance 的 database 信息:
2.2.2.4 Store backend
- Glance 自己并不存储 image。 真正的 image 是存放在 backend 中的。 Glance 支持多种 backend,包括:
1)A directory on a local file system(这是默认配置);
2)GridFS;
3)Ceph RBD;
4)Amazon S3;
5)Sheepdog;
6)OpenStack Block Storage (Cinder);
7)OpenStack Object Storage (Swift);
8)VMware ESX。
具体使用哪种 backend,是在/etc/glance/glance-api.conf
中配置的,在我们的 devstack 环境中,image 存放在控制节点本地目录 /opt/stack/data/glance/images/ 中:
其他 backend 的配置可参考:https://docs.openstack.org/liberty/config-reference/content/configuring-image-service-backends.html - 查看目前已经存在的 image使用
glance image-list
:
- 查看保存目录:每个 image 在目录下都对应有一个文件,文件以 image 的 ID 命名。
2.2.3 Glance操作
- OpenStack 为终端用户提供了 Web UI(Horizon)和命令行 CLI 两种方式创建Image。
2.2.3.1 Web UI操作
2.2.3.1.1 Web UI 创建 image
-
admin 登录后,Project -> Compute -> Images:
-
点击右上角“Create Image”按钮,为新 image 命名:
这里我们上传一个 image。 点击“浏览”,选择镜像文件 cirros-0.3.4-x86_64-disk.img。cirros 是一个很小的 linux 镜像,非常适合测试用。 可以到http://download.cirros-cloud.net/下载。
-
格式选择 QCOW2:
如果勾选“Public”,该 image 可以被其他 Project 使用;如果勾选“Protected”,该 image 不允许被删除。
-
点击“Create Image”,文件上传到 OpenStack 并创建新的 image:
-
点击 image 的“cirros链接”:
显示详细信息
2.2.3.1.2 Web UI 删除 image
- admin 登录后,Project -> Compute -> Images:
点击“Delete Images”确认删除,操作成功:
2.2.3.2 CLI操作
2.2.3.2.1 CLI 创建 image
- cirros 这个 linux 镜像很小,通过 Web UI 上传很快,操作会很顺畅。 但如果我们要上传的镜像比较大(比如好几个 G ),那么操作会长时间停留在上传的 Web 界面,我们也不知道目前到底处于什么状态。对于这样的操作,CLI 是更好的选择。
- 将 image 上传到控制节点的文件系统中,例如 /tmp/cirros-0.3.4-x86_64-disk.img:
- 设置环境变量:
Devstack 的安装目录下有个 openrc 文件。source 该文件就可以配置 CLI 的环境变量。这里我们传入了两个参数,第一个参数是 OpenStack 用户名 admin;第二个参数是 Project 名 admin。
- 执行 image 创建命令:
glance image-create --name cirros --file /tmp/cirros-0.3.4-x86_64-disk.img --disk-format qcow2 --container-format bare --progress
在创建 image 的 CLI 参数中我们用 –progress 让其显示文件上传的百分比 %。在 /opt/stack/data/glance/images/ 下查看新的 Image:
2.2.3.2.2 CLI 删除 image
- 设置环境变量:
- 查询现有image:
- 删除image采用
glance image-delete 镜像ID
:
2.2.4 如何 Troubleshooting
- OpenStack 排查问题的方法主要是通过日志,Service 都有自己单独的日志。Glance 主要有两个日志,glance_api.log 和 glance_registry.log,保存在 /opt/stack/logs/ 目录里。devstack 的 screen 窗口已经帮我们打开了这两个日志,可以直接查看:
- g-api 窗口显示 glance-api 日志,记录 REST API 调用情况;g-reg 窗口显示 glance-registry 日志,记录 Glance 服务处理请求的过程以及数据库操作。如果需要得到最详细的日志信息,可以在 /etc/glance/*.conf 中打开 debug 选项。devstack 默认已经打开了 debug。
- 在非 devstack 安装中,日志在 /var/log/glance/ 目录里。
2.3 计算服务Nova
- Compute Service Nova 是 OpenStack 最核心的服务,负责维护和管理云环境的计算资源。OpenStack 作为 IaaS 的云操作系统,虚拟机生命周期管理也就是通过 Nova 来实现的。
在上图中可以看到,Nova 处于 Openstak 架构的中心,其他组件都为 Nova 提供支持。Glance 为 VM 提供 image;Cinder 和 Swift 分别为 VM 提供块存储和对象存储 ;Neutron 为 VM 提供网络连接。
2.3.1 Nova架构
2.3.1.1 API
- nova-api:接收和响应客户的 API 调用。
除了提供 OpenStack 自己的API,nova-api 还支持 Amazon EC2 API。也就是说,如果客户以前使用 Amazon EC2,并且用 EC2 的 API 开发工具来管理虚机,那么如果现在要换成 OpenStack,这些工具可以无缝迁移到OpenStack,因为 nova-api 兼容 EC2 API,无需做任何修改。
2.3.1.2 Compute Core
- nova-scheduler:虚机调度服务,负责决定在哪个计算节点上运行虚机;
- nova-compute:管理虚机的核心服务,通过调用 Hypervisor API 实现虚机生命周期管理;
- Hypervisor:计算节点上跑的虚拟化管理程序,虚机管理最底层的程序。 不同虚拟化技术提供自己的 Hypervisor。 常用的 Hypervisor 有 KVM,Xen,VMWare 等;
- nova-conductor:nova-compute 经常需要更新数据库,比如更新虚机的状态。出于安全性和伸缩性的考虑,nova-compute 并不会直接访问数据库,而是将这个任务委托给 nova-conductor。
2.3.1.3 Console Interface
- nova-console:用户可以通过多种方式访问虚机的控制台:
1)nova-novncproxy,基于 Web 浏览器的VNC 访问;
2)nova-spicehtml5proxy,基于HTML5 浏览器的 SPICE 访问;
3)nova-xvpnvncproxy,基于 Java 客户端的 VNC 访问; - nova-consoleauth:负责对访问虚机控制台请求提供 Token 认证;
- nova-cert:提供 x509 证书支持。
2.3.1.4 Database
- Nova 会有一些数据需要存放到数据库中,一般使用 MySQL。数据库安装在控制节点上。 Nova 使用命名为 “nova” 的数据库。
2.3.1.5 Message Queue
- 在前面我们了解到 Nova 包含众多的子服务,这些子服务之间需要相互协调和通信。 为解耦各个子服务,Nova 通过 Message Queue 作为子服务的信息中转站。 所以在架构图上我们看到了子服务之间没有直接的连线,它们都通过 Message Queue 联系。
OpenStack 默认是用 RabbitMQ 作为 Message Queue。MQ 是 OpenStack 的核心基础组件。
2.3.2 Nova 物理部署方案
- 对于 Nova,这些服务会部署在两类节点上:计算节点和控制节点。计算节点上安装了 Hypervisor,上面运行虚拟机,由此可知:只有 nova-compute 需要放在计算节点上。其他子服务则是放在控制节点上的。
- 实验环境的具体部署情况 :
通过在计算节点和控制节点上运行ps -elf|grep nova
来查看运行的 nova 子服务:
1)计算节点上只运行了nova-compute自服务:
2)控制节点上运行了若干 nova-* 子服务,RabbitMQ 和 MySQL 也是放在控制节点上的,我们发现的控制节点上也运行了 nova-compute:这实际上也就意味着controller 既是一个控制节点,同时也是一个计算节点,也可以在上面运行虚机。
这也向我们展示了 OpenStack 这种分布式架构部署上的灵活性: 可以将所有服务都放在一台物理机上,作为一个 All-in-One 的测试环境; 也可以将服务部署在多台物理机上,获得更好的性能和高可用。
另外,可以用nova service-list
查看 nova-* 子服务都分布在哪些节点上:
2.3.3 从虚机创建流程看 nova-* 子服务如何协同工作
- 从学习 Nova 的角度看,虚机创建是一个非常好的场景,涉及的 nova-* 子服务很全,下面是流程图:
1)客户(可以是 OpenStack 最终用户,也可以是其他程序)向 API(nova-api)发送请求:“帮我创建一个虚机”;
2)API 对请求做一些必要处理后,向 Messaging(RabbitMQ)发送了一条消息:“让 Scheduler 创建一个虚机”;
3)Scheduler(nova-scheduler)从 Messaging 获取到 API 发给它的消息,然后执行调度算法,从若干计算节点中选出节点 A;
4)Scheduler 向 Messaging 发送了一条消息:“在计算节点 A 上创建这个虚机”;
5)计算节点 A 的 Compute(nova-compute)从 Messaging 中获取到 Scheduler 发给它的消息,然后在本节点的 Hypervisor 上启动虚机;
5)在虚机创建的过程中,Compute 如果需要查询或更新数据库信息,会通过 Messaging 向Conductor(nova-conductor)发送消息,Conductor 负责数据库访问。
2.3.4 OpenStack 通用设计思路
2.3.4.1 API 前端服务
- 每个 OpenStack 组件可能包含若干子服务,其中必定有一个 API 服务负责接收客户请求。以 Nova 为例,nova-api 作为 Nova 组件对外的唯一窗口,向客户暴露 Nova 能够提供的功能。当客户需要执行虚机相关的操作,能且只能向 nova-api 发送 REST 请求。这里的客户包括终端用户、命令行和 OpenStack 其他组件。
- 设计 API 前端服务的好处在于:对外提供统一接口,隐藏实现细节;API 提供 REST 标准调用服务,便于与第三方系统集成;可以通过运行多个 API 服务实例轻松实现 API 的高可用,比如运行多个 nova-api 进程。
2.3.4.2 Scheduler 调度服务
- 对于某项操作,如果有多个实体都能够完成任务,那么通常会有一个scheduler 负责从这些实体中挑选出一个最合适的来执行操作。在前面的例子中,Nova 有多个计算节点。当需要创建虚机时,nova-scheduler 会根据计算节点当时的资源使用情况选择一个最合适的计算节点来运行虚机。除了 Nova,块服务组件 Cinder 也有 scheduler 子服务,后面我们会详细讨论。
2.3.4.3 Worker工作服务
- 调度服务只管分配任务,真正执行任务的是 Worker 工作服务。在 Nova 中,这个 Worker 就是 nova-compute 了。 将 Scheduler 和 Worker 从职能上进行划分使得OpenStack 非常容易扩展:1)当计算资源不够了无法创建虚机时,可以增加计算节点(增加 Worker);2)当客户的请求量太大调度不过来时,可以增加 Scheduler。
2.3.4.4 Driver 框架
- OpenStack 作为开放的 Infrastracture as a Service 云操作系统,支持业界各种优秀的技术。
- 那 OpenStack 的这种开放性体现在哪里呢:一个重要的方面就是采用基于 Driver 的框架。以 Nova 为例,OpenStack 的计算节点支持多种 Hypervisor。 包括 KVM, Hyper-V, VMWare, Xen, Docker, LXC 等。Nova-compute 为这些 Hypervisor 定义了统一的接口,hypervisor 只需要实现这些接口,就可以 driver 的形式即插即用到 OpenStack 中。
- 下面是 nova driver 的架构示意图:
在 nova-compute 的配置文件 /etc/nova/nova.conf 中由 compute_driver 配置项指定该计算节点使用哪种 Hypervisor 的 driver。
在我们的环境中因为是 KVM,所以配置的是 Libvirt 的 driver。不知大家是否还记得我们在学习 Glance 时谈到: OpenStack 支持多种 backend 来存放 image。可以是本地文件系统,Cinder,Ceph,Swift 等。其实这也是一个 driver 架构。 只要符合 Glance 定义的规范,新的存储方式可以很方便的加入到 backend 支持列表中。再后面 Cinder 和 Neutron 中我们还会看到 driver 框架的应用。
2.3.4.5 Messaging 服务
- 在前面创建虚机的流程示意图中,我们看到 nova-* 子服务之间的调用严重依赖 Messaging。Messaging 是 nova-* 子服务交互的中枢。
- 以前没接触过分布式系统的同学可能会不太理解为什么不让 API 直接调用Scheduler,或是让Scheuler 直接调用 Compute,而是非要通过 Messaging 进行中转。
- 程序之间的调用通常分两种:同步调用和异步调用:
1)同步调用: API 直接调用 Scheduler 的接口就是同步调用。 其特点是 API 发出请求后需要一直等待,直到 Scheduler 完成对 Compute 的调度,将结果返回给 API 后 API 才能够继续做后面的工作。
2)异步调用: API 通过 Messaging 间接调用 Scheduler 就是异步调用。 其特点是 API 发出请求后不需要等待,直接返回,继续做后面的工作。Scheduler 从 Messaging 接收到请求后执行调度操作,完成后将结果也通过 Messaging 发送给 API。 - 在 OpenStack 这类分布式系统中,通常采用异步调用的方式,其好处是:解耦各子服务:子服务不需要知道其他服务在哪里运行,只需要发送消息给 Messaging 就能完成调用;提高性能:异步调用使得调用者无需等待结果返回。这样可以继续执行更多的工作,提高系统总的吞吐量;提高伸缩性:子服务可以根据需要进行扩展,启动更多的实例处理更多的请求,在提高可用性的同时也提高了整个系统的伸缩性。而且这种变化不会影响到其他子服务,也就是说变化对别人是透明的。
2.3.4.6 Database
- OpenStack 各组件都需要维护自己的状态信息。比如 Nova 中有虚机的规格、状态,这些信息都是在数据库中维护的。每个 OpenStack 组件在 MySQL 中有自己的数据库。
2.3.5 Nova 组件详解
2.3.5.1 nova-api
-
Nova-api 是整个 Nova 组件的门户,所有对 Nova 的请求都首先由 nova-api 处理。
Nova-api 向外界暴露若干 HTTP REST API 接口。在 keystone 中我们采用openstack endpoint show nova
命令查询 nova-api 的 endponits。客户端就可以将请求发送到 endponits 指定的地址,向 nova-api 请求操作。
当然,作为最终用户的我们不会直接发送 Rest AP I请求。OpenStack CLI,Dashboard 和其他需要跟 Nova 交换的组件会使用这些 API。 -
Nova-api 对接收到的 HTTP API 请求会做如下处理:
1)检查客户端传入的参数是否合法有效 ;
2)调用 Nova 其他子服务的处理客户端 HTTP 请求 ;
3)格式化 Nova 其他子服务返回的结果并返回给客户端。 -
nova-api 接收哪些请求?
简单的说,只要是跟虚拟机生命周期相关的操作,nova-api 都可以响应。大部分操作都可以在 Dashboard 上找到。打开Instance管理界面:
点击下拉箭头,列表中就是 nova-api 可执行的操作。
OpenStack 用术语 “Instance” 来表示虚拟机。
2.3.5.2 nova-conductor
- nova-compute 需要获取和更新数据库中 instance 的信息。但 nova-compute 并不会直接访问数据库,而是通过 nova-conductor 实现数据的访问。
- 这样做有两个显著好处:更高的系统安全性;更好的系统伸缩性 。
- 更高的安全性:
在 OpenStack 的早期版本中,nova-compute 可以直接访问数据库,但这样存在非常大的安全隐患。因为 nova-compute 这个服务是部署在计算节点上的,为了能够访问控制节点上的数据库,就必须在计算节点的 /etc/nova/nova.conf 中配置访问数据库的连接信息,比如:
试想任意一个计算节点被黑客入侵,都会导致部署在控制节点上的数据库面临极大风险。为了解决这个问题,从 G 版本开始,Nova 引入了一个新服务 nova-conductor,将 nova-compute 访问数据库的全部操作都放到 nova-conductor 中,而且 nova-conductor 是部署在控制节点上的。这样就避免了 nova-compute 直接访问数据库,增加了系统的安全性。 - 更好的伸缩性:
nova-conductor 将 nova-compute 与数据库解耦之后还带来另一个好处:提高了 nova 的伸缩性。nova-compute 与 conductor 是通过消息中间件交互的。这种松散的架构允许配置多个 nova-conductor 实例。在一个大规模的 OpenStack 部署环境里,管理员可以通过增加 nova-conductor 的数量来应对日益增长的计算节点对数据库的访问。
2.3.5.3 nova-scheduler
-
重点介绍 nova-scheduler 的调度机制和实现方法:即解决如何选择在哪个计算节点上启动 instance 的问题。
-
创建 Instance 时,用户会提出资源需求,例如 CPU、内存、磁盘各需要多少。OpenStack 将这些需求定义在 flavor(配额) 中,用户只需要指定用哪个 flavor 就可以了。
-
可用的 flavor 在 System->Flavors 中管理:
Flavor 主要定义了 VCPU,RAM,DISK 和 Metadata 这四类。 nova-scheduler 会按照 flavor 去选择合适的计算节点。VCPU,RAM,DISK 比较好理解,而 Metatdata 比较有意思,我们后面会具体讨论。下面介绍 nova-scheduler 是如何实现调度的
在 /etc/nova/nova.conf 中,nova 通过 scheduler_driver,scheduler_available_filters 和 scheduler_default_filters这三个参数来配置 nova-scheduler。 -
Filter scheduler: Filter scheduler 是 nova-scheduler 默认的调度器,调度过程分为两步。1)通过过滤器(filter)选择满足条件的计算节点(运行 nova-compute);2)通过权重计算(weighting)选择在最优(权重值最大)的计算节点上创建 Instance。
Nova 允许使用第三方 scheduler,配置 scheduler_driver 即可。这又一次体现了OpenStack的开放性。Scheduler 可以使用多个 filter 依次进行过滤,过滤之后的节点再通过计算权重选出最适合的节点。
上图是调度过程的一个示例:最开始有 6 个计算节点 Host1-Host6;通过多个 filter 层层过滤,Host2 和 Host4 没有通过,被刷掉了;Host1,Host3,Host5,Host6 计算权重,结果 Host5 得分最高,最终入选。 -
Filter: 当 Filter scheduler 需要执行调度操作时,会让 filter 对计算节点进行判断,filter 返回 True 或 False。Nova.conf 中的 scheduler_available_filters 选项用于配置 scheduler 可用的 filter,默认是所有 nova 自带的 filter 都可以用于过滤操作。
另外还有一个选项 scheduler_default_filters,用于指定 scheduler 真正使用的 filter,默认值如下 :
Filter scheduler 将按照列表中的顺序依次过滤。 下面依次介绍每个 filter。
1)RetryFilter:
RetryFilter 的作用是刷掉之前已经调度过的节点。举个例子方便大家理解: 假设 A,B,C 三个节点都通过了过滤,最终 A 因为权重值最大被选中执行操作。但由于某个原因,操作在 A 上失败了。 默认情况下,nova-scheduler 会重新执行过滤操作(重复次数由 scheduler_max_attempts 选项指定,默认是 3)。那么这时候 RetryFilter 就会将 A 直接刷掉,避免操作再次失败。RetryFilter 通常作为第一个 filter。
2)AvailabilityZoneFilter:
为提高容灾性和提供隔离服务,可以将计算节点划分到不同的Availability Zone中。例如把一个机架上的机器划分在一个 Availability Zone 中。OpenStack 默认有一个命名为 “Nova” 的 Availability Zone,所有的计算节点初始都是放在 “Nova” 中。用户可以根据需要创建自己的 Availability Zone。
创建 Instance 时,需要指定将 Instance 部署到在哪个 Availability Zone中。
nova-scheduler 在做 filtering 时,会使用 AvailabilityZoneFilter 将不属于指定 Availability Zone 的计算节点过滤掉。
3)RamFilter:
RamFilter 将不能满足 flavor 内存需求的计算节点过滤掉。对于内存有一点需要注意: 为了提高系统的资源使用率,OpenStack 在计算节点可用内存时允许 overcommit(超配),也就是可以超过实际内存大小。 超过的程度是通过 nova.conf 中 ram_allocation_ratio 这个参数来控制的,默认值为 1.5。
其含义是:如果计算节点的内存有 10GB,OpenStack 则会认为它有 15GB(10*1.5)的内存。
4)DiskFilter:
DiskFilter 将不能满足 flavor 磁盘需求的计算节点过滤掉。Disk 同样允许 overcommit,通过 nova.conf 中 disk_allocation_ratio 控制,默认值为 1。
5)CoreFilter:
CoreFilter 将不能满足 flavor vCPU 需求的计算节点过滤掉。vCPU 同样允许 overcommit,通过 nova.conf 中 cpu_allocation_ratio 控制,默认值为 16。
这意味着一个 8 vCPU 的计算节点,nova-scheduler 在调度时认为它有 128 个 vCPU。需要提醒的是: nova-scheduler 默认使用的 filter 并没有包含 CoreFilter。 如果要用,可以将 CoreFilter 添加到 nova.conf 的 scheduler_default_filters 配置选项中。
6)ComputeFilter:
ComputeFilter 保证只有 nova-compute 服务正常工作的计算节点才能够被 nova-scheduler调度。ComputeFilter 显然是必选的 filter。
7)ComputeCapabilitiesFilter:ComputeCapabilitiesFilter 根据计算节点的特性来筛选。这个比较高级,我们举例说明。例如我们的节点有 x86_64 和 ARM 架构的,如果想将 Instance 指定部署到 x86_64 架构的节点上,就可以利用到ComputeCapabilitiesFilter。还记得 flavor 中有个 Metadata 吗,Compute 的 Capabilities就在 Metadata中 指定。
“Compute Host Capabilities” 列出了所有可设置 Capabilities。
点击 “Architecture” 后面的 “+”,就可以在右边的列表中指定具体的架构。
配置好后,ComputeCapabilitiesFilter 在调度时只会筛选出 x86_64 的节点。如果没有设置 Metadata,ComputeCapabilitiesFilter 不会起作用,所有节点都会通过筛选。
8)ImagePropertiesFilter:
ImagePropertiesFilter 根据所选 image 的属性来筛选匹配的计算节点。 跟 flavor 类似,image 也有 metadata,用于指定其属性。
例如希望某个 image 只能运行在 kvm 的 hypervisor 上,可以通过 “Hypervisor Type” 属性来指定。
点击 “+”,然后在右边的列表中选择 “kvm”。
配置好后,ImagePropertiesFilter 在调度时只会筛选出 kvm 的节点。如果没有设置 Image 的Metadata,ImagePropertiesFilter 不会起作用,所有节点都会通过筛选。
9)ServerGroupAntiAffinityFilter:
ServerGroupAntiAffinityFilter 可以尽量将 Instance 分散部署到不同的节点上。例如有 inst1,inst2 和 inst3 三个 instance,计算节点有 A,B 和 C。为保证分散部署,进行如下操作:
创建一个 anti-affinity 策略的 server group “group-1” :
请注意,这里的 server group 其实是 instance group,并不是计算节点的 group。
依次创建 Instance,将inst1, inst2和inst3放到group-1中:
因为 group-1 的策略是 AntiAffinity,调度时 ServerGroupAntiAffinityFilter 会将inst1, inst2 和 inst3 部署到不同计算节点 A, B 和 C。目前只能在 CLI 中指定 server group 来创建 instance。创建 instance 时如果没有指定 server group,ServerGroupAntiAffinityFilter 会直接通过,不做任何过滤。
10)ServerGroupAffinityFilter:
与 ServerGroupAntiAffinityFilter 的作用相反,ServerGroupAffinityFilter 会尽量将 instance 部署到同一个计算节点上。方法类似:
创建一个 affinity 策略的 server group “group-2”:
依次创建 instance,将 inst1, inst2 和 inst3 放到 group-2 中:
因为 group-2 的策略是 Affinity,调度时 ServerGroupAffinityFilter 会将 inst1, inst2 和 inst3 部署到同一个计算节点。创建 instance 时如果没有指定 server group,ServerGroupAffinityFilter 会直接通过,不做任何过滤。 -
Weight:
经过前面一堆 filter 的过滤,nova-scheduler 选出了能够部署 instance 的计算节点。如果有多个计算节点通过了过滤,那么最终选择哪个节点呢?Scheduler 会对每个计算节点打分,得分最高的获胜。打分的过程就是 weight,翻译过来就是计算权重值,那么 scheduler 是根据什么来计算权重值呢?目前 nova-scheduler 的默认实现是根据计算节点空闲的内存量计算权重值:空闲内存越多,权重越大,instance 将被部署到当前空闲内存最多的计算节点上。 -
日志:
1)整个过程都被记录到 nova-scheduler 的日志中。比如当我们部署一个 instance 时,打开 nova-scheduler 的日志 /opt/stack/logs/n-sch.log(非 devstack 安装其日志在 /var/log/nova/scheduler.log):
日志显示初始有两个 host(在我们的实验环境中就是 devstack-controller 和 devstack-compute1),依次经过 9 个 filter 的过滤(RetryFilter,AvailabilityZoneFilter,RamFilter,DiskFilter,ComputeFilter,ComputeCapabilitiesFilter,ImagePropertiesFilter,ServerGroupAntiAffinityFilter, ServerGroupAffinityFilter),两个计算节点都通过了。
2)那么接下来就该 weight 了:
可以看到因为 devstack-controller 的空闲内存比 devstack-compute1 多(7466 > 3434),权重值更大(1.0 > 0.4599),最终选择 devstack-controller。
注:要显示 DEBUG 日志,需要在/etc/nova/nova.conf
中打开 debug 选项:
2.3.5.4 nova-compute
- nova-compute 在计算节点上运行,负责管理节点上的 instance。OpenStack 对 instance 的操作,最后都是交给 nova-compute 来完成的。nova-compute 与 Hypervisor 一起实现 OpenStack 对 instance 生命周期的管理。
- 通过 Driver 架构支持多种 Hypervisor:现在市面上有这么多 Hypervisor,nova-compute 如何与它们配合呢?这就是我们之前讨论过的 Driver 架构。nova-compute 为这些 Hypervisor 定义了统一的接口,Hypervisor 只需要实现这些接口,就可以 Driver 的形式即插即用到 OpenStack 系统中。
下面是Nova Driver的架构示意图:
- 我们可以在 /opt/stack/nova/nova/virt/ 目录下查看到 OpenStack 源代码中已经自带了上面这几个 Hypervisor 的 Driver:
- 某个特定的计算节点上只会运行一种 Hypervisor,只需在该节点 nova-compute 的配置文件 /etc/nova/nova.conf 中配置所对应的 compute_driver 就可以了。在我们的环境中因为是 KVM,所以配置的是 Libvirt 的 driver:
- nova-compute 的功能可以分为两类:1)定时向 OpenStack 报告计算节点的状态 ;2)实现 instance 生命周期的管理。
1)定期向 OpenStack 报告计算节点的状态 :
前面我们看到 nova-scheduler 的很多 Filter 是根据算节点的资源使用情况进行过滤的。比如 RamFilter 要检查计算节点当前可以的内存量;CoreFilter 检查可用的 vCPU 数量;DiskFilter 则会检查可用的磁盘空间。
那这里有个问题:OpenStack 是如何得知每个计算节点的这些信息呢? 答案就是:nova-compute 会定期向 OpenStack 报告。从 nova-compute 的日志 /opt/stack/logs/n-cpu.log 可以发现:每隔一段时间,nova-compute 就会报告当前计算节点的资源使用情况和 nova-compute 服务状态。
如果我们再深入问一个问题:nova-compute 是如何获得当前计算节点的资源使用信息的?
要得到计算节点的资源使用详细情况,需要知道当前节点上所有 instance 的资源占用信息。这些信息谁最清楚?
当然是 Hypervisor。大家还记得之前我们讨论的 Nova Driver 架构吧,nova-compute 可以通过 Hypervisor 的 driver 拿到这些信息。举例来说,在我们的实验环境下 Hypervisor 是 KVM,用的 Driver 是 LibvirtDriver。LibvirtDriver 可以调用相关的 API 获得资源信息,这些 API 的作用相当于我们在 CLI 里执行virsh nodeinfo
、virsh dominfo
等命令。
2)实现 instance 生命周期的管理:
OpenStack 对 instance 最主要的操作都是通过 nova-compute 实现的:包括 instance 的 launch(部署)、shutdown(关机)、reboot(重启)、suspend(挂起)、resume(恢复)、terminate(终止)、resize(重新分配配额)、migration(迁移)、snapshot(快照) 等。
本小节重点学习 nova-compute 如何实现 instance launch(部署)操作。当 nova-scheduler 选定了部署 instance 的计算节点后,会通过消息中间件 rabbitMQ 向选定的计算节点发出 launch instance 的命令。该计算节点上运行的 nova-compute 收到消息后会执行 instance 创建操作。
日志/opt/stack/logs/n-cpu.log
记录了整个操作过程。nova-compute 创建 instance 的过程可以分为 4 步:为 instance 准备资源;创建 instance 的镜像文件;创建 instance 的 XML 定义文件 ;创建虚拟网络并启动虚拟机。
第1步-为instance 准备资源:
nova-compute 首先会根据指定的 flavor 依次为 instance 分配内存、磁盘空间和 vCPU。可以在日志中看到这些细节:
网络资源也会提前分配:
第2步-创建 instance 的镜像文件 :
资源准备好之后,nova-compute 会为 instance 创建镜像文件,OpenStack 启动一个 instance 时,会选择一个 image,这个 image 由 Glance 管理。nova-compute会:首先将该 image 下载到计算节点;然后将其作为 backing file 创建 instance 的镜像文件。
从 Glance 下载 image:nova-compute 首先会检查 image 是否已经下载(比如之前已经创建过基于相同 image 的 instance)。
如果没有,就从 Glance 下载 image 到本地。由此可知,如果计算节点上要运行多个相同 image 的 instance,只会在启动第一个 instance 的时候从 Glance 下载 image,后面的 instance 启动速度就大大加快了。日志如下:
可以看到:image(ID为 917d60ef-f663-4e2d-b85b-e4511bb56bc2)是 qcow2格式,nova-compute 将其下载,然后通过 qemu-img 转换成 raw 格式。 转换的原因是下一步需要将其作为 instance的镜像文件的 backing file,而 backing file不能是 qcow2 格式。image 的存放目录是 /opt/stack/data/nova/instances/_base,这是由 /etc/nova/nova.conf 的下面两个配置选项决定的。
instances_path = /opt/stack/data/nova/instances
base_dir_name = _base
下载的 image 文件被命名为 60bba5916c6c90ed2ef7d3263de8f653111dd35f,这是 image id 的 SHA1 哈希值。
为 instance 创建镜像文件:有了 image 之后,instance 的镜像文件直接通过 qemu-img 命令创建,backing file 就是下载的 image:
这里 instance 的镜像文件位于 /opt/stack/data/nova/instances/f1e22596-6844-4d7a-84a3-e41e6d7618ef/disk,格式为 qcow2,其中 f1e22596-6844-4d7a-84a3-e41e6d7618ef 就是 instance 的 id。
可以通过 qemu-info 查看 disk 文件的属性:
这里有两个容易搞混淆的术语,在此特别说明一下
image,指的是 Glance 上保存的镜像,作为 instance 运行的模板。 计算节点将下载的 image 存放在 /opt/stack/data/nova/instances/_base 目录下。 镜像文件,指的是 instance 启动盘所对应的文件。二者的关系是:image 是镜像文件 的 backing file。image 不会变,而镜像文件会发生变化。 比如安装新的软件后,镜像文件会变大。因为英文中两者都叫 “image”,为避免混淆,我们用 “image” 和 “镜像文件” 作区分。
第3步-创建 instance 的XML 定义文件 : 创建的 XML 文件会保存到该 instance 目录 /opt/stack/data/nova/instances/f1e22596-6844-4d7a-84a3-e41e6d7618ef,命名为 libvirt.xml:
第4步-创建虚拟网络并启动 instance :
本环境用的是 linux-bridge 实现的虚拟网络,在 Neutron 章节我们会详细讨论 OpenStack 虚拟网络的不同实现方式。一切就绪,接下来可以启动 instance 了:
至此,instance 已经成功启动。OpenStack 图形界面和 KVM CLI 都可以查看到 instance 的运行状态:
在计算节点上,instance 并不是以 OpenStack上 的名字命名,而是用 instance-xxxxx 的格式。
2.3.6 看懂 OpenStack 日志
2.3.6.1 日志位置
- 我们实验环境使用的是 devstack,日志都统一放在 /opt/stack/logs 目录下,非 devstack日志在var/log目录下。每个服务有自己的日志文件,从命名上很容易区分。
2.3.6.2 日志类型
- 比如 nova-* 各个子服务的日志都以 “n-” 开头。n-api.log 是 nova-api 的日志;n-cpu.log 是 nova-compute 的日志。
- Glance 的日志文件都是 “g-” 开头。g-api.log 是 glance-api 的日志;g-reg.log 是 glance-registry 的日志。
- Cinder、Neutron 的日志分别以 “c-” 和 “q-” 开头。对于非 devstack 安装的 OpenStack,日志一般放在 /var/log/xxx/ 目录下
比如 Nova 放在 /var/log/nova/ 下,Glance 放在/var/log/glance下……。各个子服务的日志文件也是单独保存,命名也很规范,容易区分。比如 nova-api 的日志一般就命名为 /var/log/nova/api.log,其他日志类似。
2.3.6.3 日志格式
- OpenStack 的日志格式都是统一的,如下:
<时间戳><日志等级><代码模块><日志内容><源代码位置>
简单说明一下:
1)时间戳:日志记录的时间,包括年、月、日、时、分、秒、毫秒;
2)日志等级:有INFO、WARNING、ERROR、DEBUG等;
3)代码模块:当前运行的模块Request ID,日志会记录连续不同的操作,为了便于区分和增加可读性,每个操作都被分配唯一的Request ID,便于查找;
4)日志内容:这是日志的主体,记录当前正在执行的操作和结果等重要信息;
5)源代码位置:日志代码的位置,包括方法名称,源代码文件的目录位置和行号。这一项不是所有日志都有。 - 下面举例说明:
2015-12-10 20:46:49.566 DEBUG nova.virt.libvirt.config [req-5c973fff-e9ba-4317-bfd9-76678cc96584 None None] Generated XML ('<cpu>\n <arch>x86_64</arch>\n <model>Westmere</model>\n
<vendor>Intel</vendor>\n <topology sockets="2" cores="3" threads="1"/>\n
<feature name="avx"/>\n <feature name="ds"/>\n <feature name="ht"/>\n
<feature name="hypervisor"/>\n <feature name="osxsave"/>\n <feature name="pclmuldq"/>\n
<feature name="rdtscp"/>\n <feature name="ss"/>\n <feature name="vme"/>\n
<feature name="xsave"/>\n</cpu>\n',) to_xml /opt/stack/nova/nova/virt/libvirt/config.py:82
这条日志我们可以得知:代码模块是 nova.virt.libvirt.config,由此可知应该是 Hypervisor Libvirt 相关的操作;日志内容是生成 XML;如果要跟踪源代码,可以到/opt/stack/nova/nova/virt/libvirt/config.py 的 82 行,方法是 to_xml。
- 又例如下面这条日志:
2015-12-10 20:46:49.671 ERROR nova.compute.manager[req-5c973fff-e9ba-4317-bfd9-76678cc96584 None None]No compute node record for host devstack-controller
这条日志我们可以得知:这是一个 ERROR 日志;具体内容是 “No compute node record for host devstack-controller”;该日志没有指明源代码位置。
2.3.6.4 日志说明
- 学习 OpenStack 需要看日志吗?这个问题的答案取决于你是谁。果你只是 OpenStack 的最终用户,那么日志对你不重要。你只需要在 GUI上 操作,如果出问题直接找管理员就可以了。
但如果你是 OpenStack 的运维和管理人员,日志对你就非常重要了。因为 OpenStack 操作如果出错,GUI 上给出的错误信息是非常笼统和简要的,日志则提供了大量的线索,特别是当 debug 选项打开之后。如果你正处于 OpenStack 的学习阶段,正如我们现在的状态,那么也强烈建议你多看日志。日志能够帮助你更加深入理解 OpenStack 的运行机制。 - 日志能够帮助我们深入学习 OpenStack 和排查问题。但要想高效的使用日志还得有个前提:必须先掌握 OpenStack 的运行机制,然后针对性的查看日志。 就拿 Instance Launch 操作来说,如果之前不了解 nova-* 各子服务在操作中的协作关系,如果没有理解流程图,面对如此多和分散的日志文件,我们也很难下手不是。
- 对于 OpenStack 的运维和管理员来说,在大部分情况下,我们都不需要看源代码。因为 OpenStack 的日志记录得很详细了,足以帮助我们分析和定位问题。 但还是有一些细节日志没有记录,必要时可以通过查看源代码理解得更清楚。 即便如此,日志也会为我们提供源代码查看的线索,不需要我们大海捞针。
2.3.7 虚拟机生命周期管理
2.3.7.1 Launch-部署虚拟机
- 客户(可以是 OpenStack 最终用户,也可以是其他程序)向 API(nova-api)发送请求:“帮我创建一个 Instance”:
1)API对请求做一些必要处理后,向 Messaging(RabbitMQ)发送了一条消息:“让 Scheduler 创建一个 Instance”;
2)Scheduler(nova-scheduler)从 Messaging 获取 API 发给它的消息,然后执行调度算法,从若干计算节点中选出节点 A;
3)Scheduler 向 Messaging 发送了一条消息:“在计算节点 A 上创建这个 Instance”;
4)计算节点 A 的 Compute(nova-compute)从 Messaging 中获取到 Scheduler 发给它的消息,然后通过本节点的 Hypervisor Driver 创建 Instance;
5)在 Instance 创建的过程中,Compute 如果需要查询或更新数据库信息,会通过 Messaging 向 Conductor(nova-conductor)发送消息,Conductor 负责数据库访问。
2.3.7.2 Shut Off-关闭虚拟机
- shut off instance 的流程图如下所示:1)向 nova-api 发送请求;2)nova-api 发送消息;3)nova-compute 执行操作。
1)向 nova-api 发送请求:
客户(可以是 OpenStack 最终用户,也可以是其他程序)向 API(nova-api)发送请求:“帮我关闭这个 Instance”:
查看日志 /opt/stack/logs/n-api.log:
对于初学者,这不是一件容易的事情,因为日志里条目和内容很多,特别是 debug 选项打开之后,容易让人眼花缭乱,无从下手。这里给大家几个小窍门:
a)先确定大的范围,比如在操作之前用 tail -f 打印日志文件,这样需要查看的日志肯定在操作之后的打印输出的这些内容里。 另外也可以通过时间戳来确定需要的日志范围。
b)利用 “代码模块” 快速定位有用的信息。 nova-* 子服务都有自己特定的代码模块:
nova-api :
nova.api.openstack.compute.servers
nova.compute.api
nova.api.openstack.wsgi
nova-compute:
nova.compute.manager
nova.virt.libvirt.*
nova-scheduler:
nova.scheduler.*
c)利用 Request ID 查找相关的日志信息。 在上面的日志中,我们可以利用 “req-1758b389-a2d0-44cc-a95a-6f75e4dc07fd” 这个 Request ID 快速定位 n-api.log 中相与 shut off 操作的其他日志条目。 需要补充说明的是,Request ID 是跨日志文件的,这一个特性能帮助我们在其他子服务的日志文件中找到相关信息,我们后面马上将会看到这个技巧的应用。
2)nova-api 发送消息:
nova-api 向 Messaging(RabbitMQ)发送了一条消息:“关闭这个 Instance”。nova-api 没有将发送消息的操作记录到日志中,不过我们可以通过查看源代码来验证。 一提到源代码,大家可能以为要大海捞针了。其实很简单,上面日志已经清楚地告诉我们需要查看的源代码在 /opt/stack/nova/nova/compute/api.py 的 1977 行,方法是 force_stop。
force_stop 方法最后调用的是对象 self.compute_rpcapi 的 stop_instance 方法。 在 OpenStack 源码中,以 xxx_rpcapi 命名的对象,表示的就是 xxx 的消息队列。 xxx_rpcapi.yyy() 方法则表示向 xxx 的消息队列发送 yyy 操作的消息。 所以 self.compute_rpcapi.stop_instance() 的作用就是向 RabbitMQ 上 nova-compute 的消息队列里发送一条 stop instance 的消息。这里补充说明一下: 关闭 instance 的前提是 instance 当前已经在某个计算节点上运行,所以这里不需要 nova-scheduler 再帮我们挑选合适的节点,这个跟 launch 操作不同。
3)nova-compute 执行操作:
查看计算节点上的日志 /opt/stack/logs/n-cpu.log
这里我们利用了 Request ID “req-1758b389-a2d0-44cc-a95a-6f75e4dc07fd”在 n-cpu.log 中快速定位到 nova-compute 关闭 instance 的日志条目。 - 小结:
分析某个操作时,我们首先要理清该操作的内部流程,然后再到相应的节点上去查看日志
例如shut off 的流程为:
1)向 nova-api 发送请求;
2)nova-api 发送消息;
3)nova-compute 执行操作。
1,2 两个步骤是在控制节点上执行的,查看 nova-api 的日志。第 3 步是在计算节点上执行的,查看 nova-compute 的日志。
2.3.7.3 Start-启动虚拟机
- 下图是 start instance 的流程图,包含1) 向 nova-api 发送请求;2)nova-api 发送消息;3)nova-compute 执行操作。
1)向 nova-api 发送请求:
客户(可以是 OpenStack 最终用户,也可以是其他程序)向API(nova-api)发送请求:“帮我启动这个 Instance”:
查看日志 /opt/stack/logs/n-api.log :
2)nova-api 发送消息:
nova-api 向 Messaging(RabbitMQ)发送了一条消息:“启动这个 Instance”。查看源代码 /opt/stack/nova/nova/compute/api.py 的 2002 行,方法是 start:
self.compute_rpcapi.start_instance() 的作用就是向 RabbitMQ 上 nova-compute 的消息队列里发送一条start instance 的消息。
3)nova-compute 执行操作:
查看日志 /opt/stack/logs/n-cpu.log。开始启动:
准备虚拟网卡:
准备 instance 的 XML 文件:
准备 instance 镜像文件:
成功启动:
2.3.7.4 Soft/Hard Reboot-软/硬重启虚拟机
- Soft/Hard Reboot区别:
1)soft reboot 只是重启操作系统,整个过程中,instance 依然处于运行状态。相当于在 linux 中执行 reboot 命令;
2)hard reboot 是重启 instance,相当于关机之后再开机。 - 提示:soft/hard reboot 在 nova-api 的日志里找不到,这是因为 /opt/stack/nova/nova/compute/api.py 的 reboot 方法中没有输出 log。 可以通过关键字 “nova.api.openstack.wsgi” 或者 “reboot” 搜索;在 nova-compute 的日志中可以看到 “soft reboot” 和 “hard reboot” 二者有明显的区别。
2.3.7.5 Lock/Unlock-加锁/解锁虚拟机
- 为了避免误操作,比如意外重启或删除 instance,可以将 instance 加锁;对被加锁(Lock)的 instance 执行重启等改变状态的操作会提示操作不允许。执行解锁(Unlock)操作后恢复正常。
- Lock/Unlock 操作都是在 nova-api 中进行的。操作成功后 nova-api 会更新 instance 加锁的状态。执行其他操作时,nova-api 根据加锁状态来判断是否允许。Lock/Unlock 不需要 nova-compute 的参与。
- 提示:admin 角色的用户不受 lock 的影响,即无论加锁与否都可以正常执行操作;根据默认 policy 的配置,任何用户都可以 unlock。也就是说如果发现 instance 被加锁了,可以通过 unlock 解锁,然后在执行操作。
2.3.7.6 Terminate -删除虚拟机
- 下面是 terminate instance 的流程图,包含:1)向nova-api 发送请;2)nova-api 发送消息;3)nova-compute 执行操作。
1)向 nova-api 发送请求 :
客户(可以是 OpenStack 最终用户,也可以是其他程序)向 API(nova-api)发送请求:“帮我删除这个 Instance”:
查看日志 /opt/stack/logs/n-api.log:
2)nova-api 发送消息:
nova-api 向 Messaging(RabbitMQ)发送了一条消息:“删除这个 Instance”。源代码在 /opt/stack/nova/nova/compute/api.py,方法是 _do_force_delete:
3)nova-compute 执行操作 :
查看日志 /opt/stack/logs/n-cpu.log,关闭 instance:
删除 instance 的镜像文件:
释放虚拟网络等其他资源:
2.3.7.7 Pause/Resume-暂停(短时间)/恢复(暂停后的恢复,非故障时恢复)虚拟机
- 有时需要短时间暂停 instance,可以通过 Pause 操作将 instance 的状态保存到宿主机的内存中。当需要恢复的时候,执行 Resume 操作,从内存中读回 instance 的状态,然后继续运行 instance。
- 下面是 pause instance 的流程图,包含:1)向 nova-api 发送请求;2)nova-api 发送消息;3)nova-compute 执行操作。
1)向nova-api发送请求:
客户(可以是 OpenStack 最终用户,也可以是其他程序)向 API(nova-api)发送请求:“帮我暂停这个 Instance”:
查看日志 /opt/stack/logs/n-api.log:
注:对于 Pause 操作,日志没有前面 Start 记录得那么详细。例如这里就没有记录 nova.api.openstack.compute.servers 和 nova.compute.api 代码模块的日志,这可能是因为这个操作逻辑比较简单,开发人员在编码时没有加入日志。
2)nova-api 发送消息:
nova-api 向 Messaging(RabbitMQ)发送了一条消息:“暂停这个 Instance”。查看源代码 /opt/stack/nova/nova/compute/api.py,方法是 pause:
3)nova-compute 执行操作:
查看日志 /opt/stack/logs/n-cpu.log:
暂停操作成功执行后,instance 的状态变为 Paused:
Resume 操作的日志分析留给大家练习。 提示:这里的 Resume 操作实际上是 Unpause 操作,可以通过关键字“unpause”定位日志。
2.3.7.8 Suspend/Resume-暂停(长时间)/恢复(暂停后的恢复,非故障时恢复)虚拟机
- 有时需要长时间暂停 instance,可以通过 Suspend 操作将 instance 的状态保存到宿主机的磁盘上。当需要恢复的时候,执行 Resume 操作,从磁盘读回 instance 的状态,使之继续运行。
- 这里需要对 Suspend 和 Pause 操作做个比较:
1)相同点:两者都是暂停 instance 的运行,并保存当前状态,之后可以通过 Resume 操作恢复;
2)不同点:
a)Suspend 将 instance 的状态保存在磁盘上;Pause 是保存在内存中,所以 Resume 被 Pause 的 instance 要比 Suspend 快。
b)Suspend 之后的 instance,其状态是 Shut Down;而被 Pause 的 instance 状态是Paused。
c)虽然都是通过 Resume 操作恢复,Pause 对应的 Resume 在 OpenStack 内部被叫作 “Unpause”;Suspend 对应的 Resume 才是真正的 “Resume”。这个在日志中能体现出来。
2.3.7.9 Rescue/Unrescue-恢复(发生故障时恢复)/重新引导虚拟机
- 从这节开始,我们将讨论几种 instance 故障恢复的方法,不同方法适用于不同的场景。
- 首先我们考虑操作系统故障:有时候由于误操作或者突然断电,操作系统重启后却起不来了。为了最大限度挽救数据,我们通常会使用一张系统盘将系统引导起来,然后在尝试恢复。问题如果不太严重,完全可以通过这种方式让系统重新正常工作。比如某个系统文件意外删除, root 密码遗忘等。
- Nova 也提供了这种故障恢复机制,叫做 Rescue。
Rescue 用指定的 image 作为启动盘引导 instance,将 instance 本身的系统盘作为第二个磁盘挂载到操作系统上。 - 下面是 rescue instance 的流程图:包含1)向 nova-api 发送请求;2)nova-api 发送消息;3)nova-compute 执行操作。
1)向 nova-api 发送请求:
目前 Rescue 操作只能通过 CLI 执行。这里我们没有指明用哪个 image 作为引导盘,nova 将使用 instance 部署时使用的 image:
查看日志 /opt/stack/logs/n-api.log:
2)nova-api 发送消息:
nova-api 向 Messaging(RabbitMQ)发送了一条消息:“Rescue 这个 Instance”。源代码在 /opt/stack/nova/nova/compute/api.py,方法是 rescue:
3)nova-compute执行操作:
查看日志 /opt/stack/logs/n-cpu.log。关闭 instance:
通过 image 创建新的引导盘,命名为 disk.rescue:
启动 instance:
Rescue 执行成功后,可以通过 virsh edit <instance_name> 查看 instance 的 XML 定义。disk.rescue 作为启动盘 vda,真正的启动盘 disk 作为第二个磁盘 vdb:
登录 instance,通过 fdisk 也可确认,fdisk -l是以列表的形式列出磁盘分区情况
:
此时,instance 处于 Rescue 状态:
Rescue 操作让我们有机会修复损坏的操作系统。修好之后,使用 Unrescue 操作从原启动盘重新引导 instance。
2.3.7.10 Snapshot -创建虚拟机快照
- 有时候操作系统损坏得很严重,通过 Rescue 操作无法修复,那么我们就得考虑通过备份恢复了。当然前提是我们之前对instance做过备份。Nova 备份的操作叫 Snapshot,其工作原理是对 instance 的镜像文件(系统盘)进行全量备份,生成一个类型为 snapshot 的 image,然后将其保存到 Glance 上。从备份恢复的操作叫 Rebuild,将在下一节重点讨论。
- 下面是 snapshot instance 的流程图,包含1)向 nova-api 发送请求;2)nova-api 发送消息;3)nova-compute 执行操作。
1)向 nova-api 发送请求:
客户(可以是 OpenStack 最终用户,也可以是其他程序)向 API(nova-api)发送请求:“对这个 Instance做个快照”:
查看日志 /opt/stack/logs/n-api.log:
2)nova-api 发送消息:
nova-api 向 Messaging(RabbitMQ)发送了一条消息:“对这个 Instance 做快照”,源代码在 /opt/stack/nova/nova/compute/api.py,方法是 snapshot:
3)nova-compute 执行操作:
查看日志 /opt/stack/logs/n-cpu.log。暂停 instance:
对 instance 的镜像文件做快照:
恢复 instance:
将快照上传到 Glance:
Snapshot 成功保存在 Glance 中:
instance 备份成功,下节我们讨论如何通过 snapshot 恢复。
2.3.7.11 Rebuild -通过快照恢复虚拟机
- 上一节我们讨论了 snapshot,snapshot 的一个重要作用是对 instance 做备份。如果 instance 损坏了,可以通过 snapshot 恢复,这个恢复的操作就是 Rebuild。
Rebuild 会用 snapshot 替换 instance 当前的镜像文件,同时保持 instance 的其他诸如网络,资源分配属性不变。 - 下面是 rebuild instance 的流程图 :1)向 nova-api 发送请求;2)nova-api 发送消息;3)nova-compute 执行操作。
1)向 nova-api 发送请求:
客户(可以是 OpenStack 最终用户,也可以是其他程序)向 API(nova-api)发送请求:“Rebuild 这个 Instance”:
选择用于恢复的 image:
查看日志/opt/stack/logs/n-api.log :
2)nova-api 发送消息:
nova-api 向 Messaging(RabbitMQ)发送了一条消息:“Rebuild 这个 Instance”,源代码在 /opt/stack/nova/nova/compute/api.py,方法是 rebuild:
3)nova-compute 执行操作:
查看日志 /opt/stack/logs/n-cpu.log
关闭 instance:
下载新的 image,并准备 instance 的镜像文件 :
启动 instance :
Rebuild 后,GUI 显示 instance 已经使用新的 image :
2.3.7.12 Shelve-搁置虚拟机
- Instance 被 Suspend 后虽然处于 Shut Down 状态,但 Hypervisor 依然在宿主机上为其预留了资源,以便在以后能够成功 Resume。如果希望释放这些预留资源,可以使用 Shelve 操作。Shelve 会将 instance 作为 image 保存到 Glance 中,然后在宿主机上删除该 instance。
- 下面是 shelve instance 的流程图 :1)向 nova-api 发送请求;2)nova-api 发送消息;3)nova-compute 执行操作。
1)客户(可以是 OpenStack 最终用户,也可以是其他程序)向API(nova-api)发送请求:“帮我 shelve 这个 Instance”:
查看日志 /opt/stack/logs/n-api.log:
2)nova-api 发送消息:
nova-api 向 Messaging(RabbitMQ)发送了一条消息:“shelve 这个 Instance”。查看源代码 /opt/stack/nova/nova/compute/api.py,方法是 shelve:
3)nova-compute 执行操作 :
查看日志 /opt/stack/logs/n-cpu.log。首先,关闭 instance:
然后对 instance 执行 snapshot 操作 :
成功后,snapshot 生成的 image 会保存在 Glance 上,命名为 -shelved:
最后删除 instance 在宿主机上的资源 :
暂停操作成功执行后,instance 的状态变为 Shelved Offloaded,电源状态是 Shut Down:
2.3.7.13 Unshelve-取消搁置虚拟机
- 上一节我们 shelve instance 到 Glance,本节讨论如何通过 unshelve 操作恢复该 instance。因为 Glance 中保存了 instance 的 image,unshelve 的过程其实就是通过该 image launch 一个新的 instance,nova-scheduler 也会调度合适的计算节点来创建该 instance。instance unshelve 后可能运行在与 shelve 之前不同的计算节点上,但 instance 的其他属性(比如 flavor,IP 等)不会改变。
- 下面是 Unshelve instance 的流程图 :1)向 nova-api 发送请求;2)nova-api 发送消息;3)nova-scheduler 执行调度;4)nova-scheduler 发送消息;5)nova-compute 执行操作。
1)向 nova-api 发送请求 :
客户(可以是 OpenStack 最终用户,也可以是其他程序)向 API(nova-api)发送请求:“帮我 Unshelve 这个 Instance”:
查看日志 /opt/stack/logs/n-api.log:
2)nova-api 发送消息:
nova-api 向 Messaging(RabbitMQ)发送了一条消息:“unshelve 这个 Instance”。查看源代码 /opt/stack/nova/nova/compute/api.py,方法是 unshelve:
3)nova-scheduler 执行调度:
nova-scheduler 收到消息后,会为 instance 选择合适的计算节点;查看日志 /opt/stack/logs/n-sch.log:
经过筛选,最终 devstack-controller 被选中 launch instance。
4)nova-scheduler 发送消息 :
nova-scheduler 发送消息,告诉被选中的计算节点可以 launch instance 了。源代码在/opt/stack/nova/nova/scheduler/filter_scheduler.py 第 95 行,方法为 select_destinations:
5) nova-compute 执行操作 :
nova-compute 执行 unshelve 的过程与 launch instance 非常类似。一样会经过如下几个步骤:a)为 instance 准备 CPU、内存和磁盘资源;b)创建 instance 镜像文件;c)创建 instance 的 XML 定义文件;d)创建虚拟网络并启动 instance。
2.3.7.14 Migrate -迁移虚拟机
- Migrate 操作的作用是将 instance 从当前的计算节点迁移到其他节点上。Migrate 不要求源和目标节点必须共享存储,当然共享存储也是可以的。Migrate 前必须满足一个条件:计算节点间需要配置 nova 用户无密码访问。
- 下面是 Migrate instance 的流程图:1)向 nova-api 发送请求;2)nova-api 发送消息;3)nova-scheduler 执行调度;4)nova-scheduler 发送消息;5)nova-compute 执行操作。
1)向 nova-api 发送请求 :
客户(可以是 OpenStack 最终用户,也可以是其他程序)向 API(nova-api)发送请求:“帮我迁移这个 Instance”;Migrate 操作是特权操作,只能在 Admin 的 instance 菜单中执行:
查看日志 /opt/stack/logs/n-api.log:
2) nova-api 发送消息:
nova-api 向 Messaging(RabbitMQ)发送了一条消息:“迁移这个 Instance”;查看源代码/opt/stack/nova/nova/compute/api.py,方法是 resize。 没错,是 resize 而非 migrate。 这是由于 migrate 实际上是通过 resize 操作实现的,至于为什么要这样设计,我们会在下一节 resize 中详细分析。
3)nova-scheduler 执行调度
nova-scheduler 收到消息后,会为 instance 选择合适的目标计算节点。查看日志 /opt/stack/logs/n-sch.log可以看到,因为 devstack-compute1 的权值比 devstack-controller 大,最终选择 devstack-compute1 作为目标节点。
看到上面的日志,大家发现什么问题没有?
在分析这段日志的时候,我发现 scheduler 选出来的计算节点有可能是当前节点源节点!因为 scheduler 并没在初始的时候将源节点剔除掉,而是与其他节点放在一起做 filter,按照这个逻辑,只要源节点的权值足够大,是有可能成为目标节点的。
那紧接着的问题是:如果源节点和目标节点是同一个,migrate 操作会怎样进行呢?
实验得知,nova-compute 在做 migrate 的时候会检查目标节点,如果发现目标节点与源节点相同,会抛出 UnableToMigrateToSelf 异常。Nova-compute 失败之后,scheduler 会重新调度,由于有 RetryFilter,会将之前选择的源节点过滤掉,这样就能选到不同的计算节点了。 在上面的操作中 sheduler 选择的目标节点是 devstack-compute1,意味着 instance 将从 devstack-controller 迁移到 devstack-compute1。
4)nova-scheduler 发送消息:
nova-scheduler 发送消息,通知计算节点可以迁移 instance 了
源代码在/opt/stack/nova/nova/scheduler/filter_scheduler.py 第 95 行,方法为 select_destinations(选择目标节点):
4)nova-compute 执行操作 :
nova-compute 会在源计算节点和目标计算节点上分别执行操作。
源计算节点 devstack-controller:
迁移操作在源节点上首先会关闭 instance,然后将 instance 的镜像文件传到目标节点上。日志在 /opt/stack/logs/n-cpu.log,具体步骤如下:
a)开始 migrate
b)在目标节点上创建 instance 的目录:
nova-compute 首先会尝试通过 ssh 在目标节点上的 instance 目录里 touch 一个临时文件,日志如下:
如果 touch 失败,说明目标节点上还没有该 instance 的目录,也就是说,源节点和目标节点没有共享存储。
那么接下来就要在目标节点上创建 instance 的目录,日志如下:
关闭 instance:
将 instance 的镜像文件通过 scp 传到目标节点上:
目标计算节点 devstack-compute1:
在目标节点上启动 instance,过程与 launch instance 非常类似。会经过如下几个步骤:为 instance 准备 CPU、内存和磁盘资源;创建 instance 镜像文件;创建 instance 的 XML 定义文件;创建虚拟网络并启动 instance。
5)Confirm
这时,instance 会处于 “Confirm or Revert Resize/Migrate”状态,需要用户确认或者回退当前的迁移操作,实际上给了用户一个反悔的机会。
当我们按下 Confirm 按钮后,会发生如下事情,nova-api 接收到 confirm 的消息:
源计算节点删除 instance 的目录,并在 Hypervisor 上删除 instance:
目标计算节点不需要做任何事情。
6)Revert还原
如果执行的是 Revert 操作会发生什么事情呢?
nova-api 接收到 revert 的消息:
在目标计算节点上关闭 instance,删除 instance 的目录,并在 Hypervisor 上删除 instance:
源计算节点上启动 instance:
因为之前迁移的时候只是在源节点上关闭了该 instance,revert 操作只需重新启动 instance。
以上是 Migrate 操作的完整流程,这里有一点需要特别注意:迁移过程中源和目标节点之前需要使用 ssh 和 scp,为了使操作顺利进行,必须要保证 nova-compute 进程的启动用户(通常是 nova,也可能是 root,可以通过 ps 命令确认)能够在计算节点之间无密码访问。否则 nova-compute 会等待密码输入,但后台服务是无法输入密码的,迁移操作会一直卡在那里。
2.3.7.15 Resize-调整虚拟机的vCPU、内存和磁盘资源
- Resize 的作用是调整 instance 的 vCPU、内存和磁盘资源。Instance 需要多少资源是定义在 flavor 中的,resize 操作是通过为 instance 选择新的 flavor 来调整资源的分配。
- 有了前面对 Migrate 的分析,再来看 Resize 的实现就非常简单了。因为 instance 需要分配的资源发生了变化,在 resize 之前需要借助 nova-scheduler 重新为 instance 选择一个合适的计算节点,如果选择的节点与当前节点不是同一个,那么就需要做 Migrate。所以本质上讲:Resize 是在 Migrate 的同时应用新的 flavor。 Migrate 可以看做是 resize 的一个特例:flavor 没发生变化的 resize,这也是为什么我们在上一节日志中看到 migrate 实际上是在执行 resize 操作。
- 下面是 Resize instance 的流程图:1)向 nova-api 发送请求;2)nova-api 发送消息;3)nova-scheduler 执行调度;4)nova-scheduler 发送消息;5)nova-compute 执行操作。
- Resize 分两种情况:
1)nova-scheduler 选择的目标节点与源节点是不同节点。操作过程跟上一节 Migrate 几乎完全一样,只是在目标节点启动 instance 的时候按新的 flavor 分配资源。 同时,因为要跨节点复制文件,也必须要保证 nova-compute 进程的启动用户(通常是 nova,也可能是 root,可以通过 ps 命令确认)能够在计算节点之间无密码访问。 对这一种情况我们不再赘述,请参看前面 Migrate 小节。
2)目标节点与源节点是同一个节点。则不需要 migrate。下面我们重点讨论这一种情况。
a) 向 nova-api 发送请求
客户(可以是 OpenStack 最终用户,也可以是其他程序)向 API(nova-api)发送请求:“帮我 Resize 这个 Instance”:
选择新的 flavor:
点击 Resize 按钮:
查看日志 /opt/stack/logs/n-api.log:
b)nova-api 发送消息:
nova-api 向 Messaging(RabbitMQ)发送了一条消息:“Resize 这个 Instance”。查看源代码/opt/stack/nova/nova/compute/api.py,方法是 resize_instance:
c)nova-scheduler 执行调度:
nova-scheduler 收到消息后,会为 instance 选择合适的目标计算节点。查看日志 /opt/stack/logs/n-sch.log:
在本例中,nova-scheduler 选择了 devstack-compute1 作为的目节点,与源节点相同。
d)nova-scheduler 发送消息 :
nova-scheduler 发送消息,通知计算节点可以迁移 instance 了。源代码在 /opt/stack/nova/nova/scheduler/filter_scheduler.py 第 95 行,方法为 select_destinations:
e)nova-compute 执行操作:
在目标节点上启动 instance,过程与 launch instance 非常类似。日志记录在 /opt/stack/logs/n-cpu.log。会经过如下几个步骤:
按新的 flavor 为 instance 准备 CPU、内存和磁盘资源:
关闭 instance:
创建 instance 镜像文件:
将 instance 的目录备份一份,命名为<instance_id>_resize,以便 revert:
创建 instance 的 XML 定义文件:
准备虚拟网络:
启动 instance:
f) Confirm
这时,instance 的状态处于“Confirm or Revert Resize/Migrate”状态,需要用户确认或者回退当前的迁移操作,实际上给了用户一个反悔的机会。
当我们按下 Confirm 按钮后,会发生如下事情:nova-api 接收到 confirm 的消息:
删除计算节上备份的 instance 目录 <instance_id>_resize:
g)revert恢复
反过来,如果执行 Revert 操作会发生什么事情呢?
nova-api 接收到 revert 的消息:
在计算节点上关闭 instance:
通过备份目录 <instance_id>_resize 恢复 instance 目录:
重新启动 instance:
2.3.7.16 Live Migrate-在线迁移虚拟机
-
Migrate 操作会先将 instance 停掉,也就是所谓的“冷迁移”。
而 Live Migrate 是“热迁移”,也叫“在线迁移”,instance不会停机。 -
Live Migrate 分两种:
1)源和目标节点没有共享存储,instance 在迁移的时候需要将其镜像文件从源节点传到目标节点,这叫做 Block Migration(块迁移);
2)源和目标节点共享存储,instance 的镜像文件不需要迁移,只需要将 instance 的状态迁移到目标节点。 -
源和目标节点需要满足一些条件才能支持 Live Migration:
1)源和目标节点的 CPU 类型要一致;
2)源和目标节点的 Libvirt 版本要一致;
3)源和目标节点能相互识别对方的主机名称,比如可以在 /etc/hosts 中加入对方的条目;
4)在源和目标节点的 /etc/nova/nova.conf 中指明在线迁移时使用 TCP 协议;
5)Instance 使用 config driver 保存其 metadata。在 Block Migration 过程中,该 config driver 也需要迁移到目标节点。由于目前 libvirt 只支持迁移 vfat 类型的 config driver,所以必须在 /etc/nova/nova.conf 中明确指明 launch instance 时创建 vfat 类型的 config driver;
6)源和目标节点的 Libvirt TCP 远程监听服务得打开,需要在下面两个配置文件中做一点配置;
-
非共享存储 Block Migration:
我们先讨论非共享存储的 Block Migration流程图如下:1)向 nova-api 发送请求;2)nova-api 发送消息;3)nova-compute 执行操作。
1)向nova-api发送请求 :
客户(可以是 OpenStack 最终用户,也可以是其他程序)向API(nova-api)发送请求:“帮我将这个 Instance 从节点 A Live Migrate 到节点 B”
这里源节点是 devstack-compute1,目标节点是 devstack-controller,因为是非共享存储,记得将“Block Migration”勾选上。这里还有一个“Disk Over Commit”选项,如果勾选了此选项,nova 在检查目标节点的磁盘空间是否足够时,是以 instance 磁盘镜像文件定义的最大容量为准;否则,以磁盘镜像文件当前的实际大小为准。
查看日志 /opt/stack/logs/n-api.log:
2)nova-api 发送消息 :
nova-api 向 Messaging(RabbitMQ)发送了一条消息:“Live Migrate 这个 Instance” ;源代码在/opt/stack/nova/nova/compute/api.py,方法是 live_migrate:
3)nova-compute 执行操作 :
源和目标节点执行 Live Migrate 的操作过程如下:目标节点执行迁移前的准备工作,首先将 instance 的数据迁移过来,主要包括镜像文件、虚拟网络等资源,日志在 devstack-controller:/opt/stack/logs/n-cpu.log:
源节点启动迁移操作,暂停 instance:
在目标节点上 Resume instance:
在源节点上执行迁移的后处理工作,删除 instance:
在目标节点上执行迁移的后处理工作,创建 XML,在 Hypervisor 中定义 instance,使之下次能够正常启动。
Instance 在 Live Migrate 的整个过程中不会停机,我们通过 Ping 操作来观察:
可见在迁移过程中,Ping 进程没有中断,只是有一个 ping 包的延迟增加了。 -
共享存储 Live Migration:
有多种方式可以实现共享存储,比如可以将 instance 的镜像文件放在 NFS 服务器上,或者使用 NAS 服务器,或者分布式文件系统。作为学习和实验,这里我们采用 NFS 方案。其他共享存储方案对于 Live Migration 本质上是一样的,只是在性能和高可用性上更好。
1)搭建 NFS 环境
将 devstack-controller 作为 NFS 服务器,共享其目录 /opt/stack/data/nova/instances。devstack-compute1 作为 NFS 客户端将此目录 mount 到本机,如下所示:
这样,OpenStack 的 instance 在 devstack-controller 和 devstack-compute1 上就实现共享存储了。共享存储的迁移过程与 Block Migrate 基本上一样,只是几个环节有点区别:向 nova-api 提交请求的时候,不能勾选“Block Migrate”:
因为源和目标节点都能直接访问 instance 的镜像,所以目标节点在准备阶段不需要传输镜像文件,源节点在迁移后处理阶段也无需删除 instance 的目录。只有 instance 的状态需要从源节点传输到的目标节点,整个迁移速递比 Block Migration 快很多。
2.3.7.17 Evacuate-撤离虚拟机
- Rebuild 可以恢复损坏的 instance。那如果是宿主机坏了怎么办呢?比如硬件故障或者断电造成整台计算节点无法工作,该节点上运行的 instance 如何恢复呢?用 Shelve 或者 Migrate 可不可以?很不幸,这两个操作都要求 instance 所在计算节点的 nova-compute 服务正常运行。幸运的是,还有 Evacuate 操作。
- Evacuate 可在 nova-compute 无法工作的情况下将节点上的 instance 迁移到其他计算节点上。但有个前提是 Instance 的镜像文件必须放在共享存储上。
- 下面是 Evacuate instance 的流程图:1)向 nova-api 发送请求;2)nova-api 发送消息;3)nova-scheduler 执行调度;4)nova-scheduler 发送消息;5)nova-compute 执行操作。
1)向 nova-api 发送请求 :
我们的实验场景如下: Instance c2 运行在 devstack-compute1 上,通过断电模拟计算节点故障,然后执行 Evacuate 操作恢复 instance c2,目前 Evacuate 只能通过 CLI 执行。
这里需要指定 –on-shared-storage 这个参数。
查看日志 /opt/stack/logs/n-api.log:
2)nova-api 发送消息
nova-api 向 Messaging(RabbitMQ)发送了一条消息:“Evacuate 这个 Instance”;查看源代码 /opt/stack/nova/nova/compute/api.py,方法是 evacuate:
大家注意到没有,evacuate 实际上是通过 rebuild 操作实现的。这是可以理解的,因为 evacuate 是用共享存储上 instance 的镜像文件重新创建虚机
3) nova-scheduler 执行调度 :
nova-scheduler 收到消息后,会为 instance 选择合适的计算节点
查看日志 /opt/stack/logs/n-sch.log:
nova-scheduler 最后选择在 devstack-controller 计算节点上重建 instance。
4) nova-compute 执行操作 :
计算节点上的工作是用共享存储上的镜像文件重建 instance。日志在 devstack-controller:/opt/stack/logs/n-cpu.log。为instance分配资源:
使用共享存储上的镜像文件:
启动 instance:
Evacuate 操作完成后,instance 在 devstack-controller 上运行。
2.3.7.18 Nova操作总结
如上图所示,我们把对 Instance 的管理按运维工作的场景分为两类:常规操作和故障处理。
- 常规操作:常规操作中,Launch、Start、Reboot、Shut Off 和 Terminate 都很好理解。 下面几个操作重点回顾一下:
1)Resize:通过应用不同的 flavor 调整分配给 instance 的资源。
2)Lock/Unlock:可以防止对 instance 的误操作。
3)Pause/Suspend/Resume:暂停当前 instance,并在以后恢复。 Pause 和 Suspend 的区别在于 Pause 将 instance 的运行状态保存在计算节点的内存中,而 Suspend 保存在磁盘上。
Pause 的优点是 Resume 的速度比 Suspend 快;缺点是如果计算节点重启,内存数据丢失,就无法 Resume 了,而 Suspend 则没有这个问题。
4)Snapshot:备份 instance 到 Glance。产生的 image 可用于故障恢复,或者以此为模板部署新的 instance。 - 故障处理 :故障处理有两种场景:计划内和计划外。计划内是指提前安排时间窗口做的维护工作,比如服务器定期的微码升级,添加更换硬件等。计划外是指发生了没有预料到的突发故障,比如强行关机造成 OS 系统文件损坏,服务器掉电,硬件故障等。
1) 计划内故障处理:对于计划内的故障处理,可以在维护窗口中将 instance 迁移到其他计算节点。 涉及如下操作:
a)Migrate:将 instance 迁移到其他计算节点。迁移之前,instance 会被 Shut Off,支持共享存储和非共享存储。
b)Live Migrate:与 Migrate 不同,Live Migrate 能不停机在线地迁移 instance,保证了业务的连续性。也支持共享存储和非共享存储(Block Migration)。
c)Shelve/Unshelve:Shelve 将 instance 保存到 Glance 上,之后可通过 Unshelve 重新部署。Shelve 操作成功后,instance 会从原来的计算节点上删除。Unshelve 会重新选择节点部署,可能不是原节点。
2) 计划外故障处理:计划外的故障按照影响的范围又分为两类:Instance 故障和计算节点故障;
a)Instance 故障:Instance故障只限于某一个 instance 的操作系统层面,系统无法正常启动。可以使用如下操作修复 instance:
i)Rescue/Unrescue:用指定的启动盘启动,进入 Rescue 模式,修复受损的系统盘。成功修复后,通过 Unrescue 正常启动 instance。
ii)Rebuild:如果 Rescue 无法修复,则只能通过 Rebuild 从已有的备份恢复。Instance 的备份是通过 snapshot 创建的,所以需要有备份策略定期备份。
b)计算节点故障:Instance 故障的影响范围局限在特定的 instance,计算节点本身是正常工作的。如果计算节点发生故障,OpenStack 则无法与节点的 nova-compute 通信,其上运行的所有 instance 都会受到影响。这个时候,只能通过 Evacuate 操作在其他正常节点上重建 Instance。
i)Evacuate:利用共享存储上 Instance 的镜像文件在其他计算节点上重建 Instance。 所以提前规划共享存储是关键。
2.4 块存储服务Cinder:为 instance 提供虚拟磁盘
- 理解 Block Storage
操作系统获得存储空间的方式一般有两种:1)通过某种协议(SAS,SCSI,SAN,iSCSI 等)挂接裸硬盘,然后分区、格式化、创建文件系统;或者直接使用裸硬盘存储数据(数据库);2)通过 NFS、CIFS 等 协议,mount 远程的文件系统。
第一种裸硬盘的方式叫做 Block Storage(块存储),每个裸硬盘通常也称作 Volume(卷);
第二种叫做文件系统存储。NAS 和 NFS 服务器,以及各种分布式文件系统提供的都是这种存储。 - 理解 Block Storage Service
Block Storage Service提供对 volume 从创建到删除整个生命周期的管理。从 instance 的角度看,挂载的每一个 Volume 都是一块硬盘。OpenStack 提供 Block Storage Service 的是 Cinder,其具体功能是:1)提供 REST API 使用户能够查询和管理 volume、volume snapshot 以及 volume type;2)提供 scheduler 调度 volume 创建请求,合理优化存储资源的分配;3)通过 driver 架构支持多种 back-end(后端)存储方式,包括 LVM,NFS,Ceph 和其他诸如 EMC、IBM 等商业存储产品和方案。
2.4.1 Cider架构
- 上图是cider的逻辑架构,包含如下组件:
1)cinder-api:接收 API 请求,调用 cinder-volume 执行操作;
2)cinder-volume:管理 volume 的服务,与 volume provider 协调工作,管理 volume 的生命周期。运行 cinder-volume 服务的节点被称作为存储节点;
3)cinder-scheduler:scheduler 通过调度算法选择最合适的存储节点创建 volume;
4)volume provider:数据的存储设备,为 volume 提供物理存储空间。cinder-volume 支持多种 volume provider,每种 volume provider 通过自己的 driver 与cinder-volume 协调工作。
5)Message Queue:Cinder 各个子服务通过消息队列实现进程间通信和相互协作。因为有了消息队列,子服务之间实现了解耦,这种松散的结构也是分布式系统的重要特征。
6)Database:Cinder 有一些数据需要存放到数据库中,一般使用 MySQL。数据库是安装在控制节点上的,比如在我们的实验环境中,可以访问名称为“cinder”的数据库。
2.4.2 物理部署方案
- Cinder 的服务会部署在两类节点上,控制节点和存储节点。我们来看看控制节点 devstack-controller 上都运行了哪些 cinder-* 子服务:
inder-api 和 cinder-scheduler 部署在控制节点上,这个很合理。至于 cinder-volume 也在控制节点上可能有些同学就会迷糊了:cinder-volume 不是应该部署在存储节点上吗?
要回答这个问题,首先要搞清楚一个事实:OpenStack 是分布式系统,其每个子服务都可以部署在任何地方,只要网络能够连通。无论是哪个节点,只要上面运行了 cinder-volume,它就是一个存储节点,当然,该节点上也可以运行其他 OpenStack服务。
cinder-volume 是一顶存储节点帽子,cinder-api 是一顶控制节点帽子。在我们的环境中,devstack-controller 同时戴上了这两顶帽子,所以它既是控制节点,又是存储节点。当然,我们也可以用一个专门的节点来运行 cinder-volume。这再一次展示了 OpenStack 分布式架构部署上的灵活性:可以将所有服务都放在一台物理机上,用作一个 All-in-One 的测试环境;而在生产环境中可以将服务部署在多台物理机上,获得更好的性能和高可用。RabbitMQ 和 MySQL 通常是放在控制节点上的。 - 另外,也可以用 cinder service list 查看 cinder-* 子服务都分布在哪些节点上:
- 还有一个问题:volume provider 放在那里:一般来讲,volume provider 是独立的。cinder-volume 使用 driver 与 volume provider 通信并协调工作。所以只需要将 driver 与 cinder-volume 放到一起就可以了。在 cinder-volume 的源代码目录下有很多 driver,支持不同的 volume provider。
后面我们会以 LVM 和 NFS 这两种 volume provider 为例讨论 cinder-volume 的使用,其他 volume provider 可以查看 OpenStack 的 configuration 文档。
2.4.3 Cinder设计思想
- 从 volume 创建流程看 cinder-* 子服务如何协同工作 ,对于 Cinder 学习来说,Volume 创建是一个非常好的场景,涉及各个 cinder-* 子服务,下面是流程图:
1)客户(可以是 OpenStack 最终用户,也可以是其他程序)向 API(cinder-api)发送请求:“帮我创建一个volume”;
2)API 对请求做一些必要处理后,向 Messaging(RabbitMQ)发送了一条消息:“让 Scheduler 创建一个 volume” ;
3)Scheduler(cinder-scheduler)从 Messaging 获取到 API 发给它的消息,然后执行调度算法,从若干计存储点中选出节点 A ;
4)Scheduler 向 Messaging 发送了一条消息:“让存储节点 A 创建这个 volume”;
5)存储节点 A 的 Volume(cinder-volume)从 Messaging 中获取到 Scheduler 发给它的消息,然后通过 driver 在 volume provider 上创建 volume。 - Cinder设计思想:Cinder 延续了 Nova 的以及其他组件的设计思想。
1)API 前端服务:
cinder-api 作为 Cinder 组件对外的唯一窗口,向客户暴露 Cinder 能够提供的功能,当客户需要执行 volume 相关的操作,只能向 cinder-api 发送 REST 请求。这里的客户包括终端用户、命令行和 OpenStack 其他组件。
设计 API 前端服务的好处在于:
a)对外提供统一接口,隐藏实现细节;
b)API 提供 REST 标准调用服务,便于与第三方系统集成;
c)可以通过运行多个 API 服务实例轻松实现 API 的高可用,比如运行多个 cinder-api 进程。
2)Scheduler 调度服务:
Cinder 可以有多个存储节点,当需要创建 volume 时,cinder-scheduler 会根据存储节点的属性和资源使用情况选择一个最合适的节点来创建 volume。
3)Worker 工作服务:
调度服务只管分配任务,真正执行任务的是 Worker 工作服务。 在 Cinder 中,这个 Worker 就是 cinder-volume 了。这种 Scheduler 和 Worker 之间职能上的划分使得 OpenStack 非常容易扩展:当存储资源不够时可以增加存储节点(增加 Worker)。当客户的请求量太大调度不过来时,可以增加 Scheduler。
4)Driver 框架:
以 Cinder 为例,存储节点支持多种 volume provider,包括 LVM, NFS, Ceph, GlusterFS,以及 EMC, IBM 等商业存储系统。cinder-volume 为这些 volume provider 定义了统一的 driver 接口,volume provider 只需要实现这些接口,就可以 driver 的形式即插即用到 OpenStack 中。
下面是 cinder driver 的架构示意图:
在 cinder-volume 的配置文件 /etc/cinder/cinder.conf 中 volume_driver 配置项设置该存储节点使用哪种 volume provider 的 driver,下面的示例表示使用的是 LVM。
2.4.4 Cinder 组件详解
2.4.4.1 cinder-api
- cinder-api 是整个 Cinder 组件的门户,所有 cinder 的请求都首先由 cinder-api 处理。cinder-api 向外界暴露若干 HTTP REST API 接口。在 keystone 中我们可以查询 cinder-api 的 endponits。
客户端可以将请求发送到 endponits 指定的地址,向 cinder-api 请求操作。当然,作为最终用户的我们不会直接发送 Rest API 请求。OpenStack CLI,Dashboard 和其他需要跟 Cinder 交换的组件会使用这些 API。 - cinder-api 对接收到的 HTTP API 请求会做如下处理:1)检查客户端传人的参数是否合法有效;2)调用 cinder 其他子服务的处理客户端请求;3)将 cinder 其他子服务返回的结果序列号并返回给客户端。
- cinder-api 接受哪些请求呢?
简单的说,只要是 Volume 生命周期相关的操作,cinder-api 都可以响应。大部分操作都可以在 Dashboard 上看到。打开 Volume 管理界面:
点击下拉箭头,列表中就是 cinder-api 可执行的操作:
2.4.4.2 cinder-scheduler
- 创建 Volume 时,cinder-scheduler 会基于容量、Volume Type 等条件选择出最合适的存储节点,然后让其创建 Volume。
- 在 /etc/cinder/cinder.conf 中,cinder 通过 scheduler_driver, scheduler_default_filters 和 scheduler_default_weighers 这三个参数来配置 cinder-scheduler。
1)Filter scheduler:
Filter scheduler 是 cinder-scheduler 默认的调度器
与 Nova 一样,Cinder 也允许使用第三方 scheduler,配置 scheduler_driver 即可。
scheduler 调度过程如下:
i)通过过滤器(filter)选择满足条件的存储节点(运行 cinder-volume);
ii)通过权重计算(weighting)选择最优(权重值最大)的存储节点。
可见,cinder-scheduler 的运行机制与 nova-scheduler 完全一样。
2)Filter :
当 Filter scheduler 需要执行调度操作时,会让 filter 对存储节点进行判断,filter 返回 True 或者 False。cinder.conf 中 scheduler_default_filters 选项指定 filter scheduler 使用的 filter,默认值如下:
a)AvailabilityZoneFilter:
为提高容灾性和提供隔离服务,可以将存储节点和计算节点划分到不同的 Availability Zone 中。例如把一个机架上的机器划分在一个 Availability Zone 中。OpenStack 默认有一个命名为“Nova” Availability Zone 的,所有的节点初始都是放在“Nova”中。用户可以根据需要创建自己的 Availability Zone。
创建 Volume 时,需要指定 Volume 所属的 Availability Zone。
cinder-scheduler 在做 filtering 时,会使用 AvailabilityZoneFilter 将不属于指定 Availability Zone 的存储节点过滤掉。
b)CapacityFilter:
创建 Volume 时,用户会指定 Volume 的大小。CapacityFilter 的作用是将存储空间不能满足 Volume 创建需求的存储节点过滤掉。
c)CapabilitiesFilter:
不同的 Volume Provider 有自己的特性(Capabilities),比如是否支持 thin provision 等。Cinder 允许用户创建 Volume 时通过 Volume Type 指定需要的 Capabilities。
Volume Type 可以根据需要定义若干 Capabilities,详细描述 Volume 的属性。VolumeVolume Type 的作用与 Nova 的 flavor 类似。Volume Type 在 Admin -> System -> Volume 菜单里管理:
通过 Volume Type 的 Extra Specs 定义 Capabilities:
Extra Specs 是用 Key-Value 的形式定义。不同的 Volume Provider 支持的 Extra Specs 不同,需要参考 Volume Provider 的文档。
上图所示的 Volume Type 只有一个 Extra Specs “volume_backend_name”,这是最重要也是必须的 Extra Specs。cinder-volume 会在自己的配置文件 /etc/cinder/cinder.conf 中设置“volume_backend_name”这个参数,其作用是为存储节点的 Volume Provider 命名。这样,CapabilitiesFilter 就可以通过 Volume Type 的“volume_backend_name”筛选出指定的 Volume Provider。不同的存储节点可以在各自的 cinder.conf 中配置相同的 volume_backend_name,这是允许的。因为虽然存储节点不同,但它们可能使用的是一种 Volume Provider。如果在第一步 filtering 环节选出了多个存储节点,那么接下来的 weighting 环节会挑选出最合适的一个节点。
3)Weighter :
Filter Scheduler 通过 scheduler_default_weighers 指定计算权重的 weigher,默认为 CapacityWeigher。
如命名所示,CapacityWeigher 基于存储节点的空闲容量计算权重值,空闲容量最大的胜出。
2.4.4.3 cinder-volume
- cinder-volume 在存储节点上运行,OpenStack 对 Volume 的操作,最后都是交给 cinder-volume 来完成的。cinder-volume 自身并不管理真正的存储设备,存储设备是由 volume provider 管理的。cinder-volume 与 volume provider 一起实现 volume 生命周期的管理。
- 通过 Driver 架构支持多种 Volume Provider
接着的问题是:现在市面上有这么多块存储产品和方案(volume provider),cinder-volume 如何与它们配合呢? - 这就是我们之前讨论过的 Driver 架构。cinder-volume 为这些 volume provider 定义了统一的接口,volume provider 只需要实现这些接口,就可以 Driver 的形式即插即用到 OpenStack 系统中。
下面是 Cinder Driver 的架构示意图:
我们可以在 /opt/stack/cinder/cinder/volume/drivers/ 目录下查看到,OpenStack 源代码中已经自带了很多 volume provider 的 Driver:
存储节点在配置文件 /etc/cinder/cinder.conf 中用 volume_driver 选项配置使用的driver:配置使用的driver
这里 LVM 是我们使用的 volume provider。 - 定期向 OpenStack 报告计算节点的状态
在前面 cinder-scheduler 会用到 CapacityFilter 和 CapacityWeigher,它们都是通过存储节点的空闲容量来做筛选。那这里有个问题:Cinder 是如何得知每个存储节点的空闲容量信息的呢?答案就是:cinder-volume 会定期向 Cinder 报告。 - 从 cinder-volume 的日志 /opt/stack/logs/c-vol.log 可以发现每隔一段时间,cinder-volume 就会报告当前存储节点的资源使用情况。
因为在我们的实验环境中存储节点使用的是 LVM,所以在上面的日志看到存储节点通过“vgs”和”lvs”这两个命令获取 LVM 的容量使用信息。 - 实现 volume 生命周期管理
Cinder 对 volume 的生命周期的管理最终都是通过 cinder-volume 完成的,包括 volume 的 create、extend、attach、snapshot、delete 等,后面我们会详细讨论。
2.4.5 通过场景学习Cinder
2.4.5.1 准备LVM Volume Provider
- Cinder 真正负责 Volume 管理的组件是 volume provider。cinder 支持多种 volume provider,LVM 是默认的 volume provider。
- Devstack 安装之后,/etc/cinder/cinder 已经配置好了 LVM,如下图所示:
上面的配置定义了名为“lvmdriver-1”的 volume provider,也称作 back-end。其 driver 是 LVM,LVM 的 volume group 名为“stack-volumes-lvmdriver-1”。 - Devstack 安装时并没有自动创建 volume group,所以需要我们手工创建。如下步骤演示了在 /dev/sdb 上创建 VG “stack-volumes-lvmdriver-1”:
1)首先创建 physical volume /dev/sdb:
Linux 的 lvm 默认配置不允许在 /dev/sdb 上创建 PV,需要将 sdb 添加到 /etc/lvm.conf 的 filter 中。
2)然后创建 VG stack-volumes-lvmdriver-1:
打开 Web GUI,可以看到 OpenStack 已经创建了 Volume Type “lvmdriver-1”:
其 Extra Specs volume_backend_name 为 lvmdriver-1:
2.4.5.2 创建volume
- Create 操作流程如下:
1)客户(可以是 OpenStack 最终用户,也可以是其他程序)向 API(cinder-api)发送请求:“帮我创建一个 volume”。
2)API 对请求做一些必要处理后,向 Messaging(RabbitMQ)发送了一条消息:“让 Scheduler 创建一个 volume”。
3)Scheduler(cinder-scheduler)从 Messaging 获取到 API 发给它的消息,然后执行调度算法,从若干计存储点中选出节点 A。
4)Scheduler 向 Messaging 发送了一条消息:“让存储节点 A 创建这个 volume”。
5)存储节点 A 的 Volume(cinder-volume)从 Messaging 中获取到 Scheduler 发给它的消息,然后通过 driver 在 volume provider 上创建 volume。 - 向 cinder-api 发送请求:
客户(可以是 OpenStack最终用户,也可以是其他程序)向 cinder-api发送请求:“帮我创建一个 volume。GUI 上操作的菜单为 Project -> Compute -> Volumes -> Create Volume:
设置 volume 的名称,volume type,大小,Availability Zone 等基本信息。这里我们没有设置 Volume Source,这样会创建一个空白的 volume。点击“Create Volume” 按钮,cinder-api 将接收到创建 volume 的请求。
查看 cinder-api 日志 /opt/stack/logs/c-api.log:
日志显示 cinder-api 接收到一个 POST 类型的 REST API,经过对 HTTP body 的分析,该请求是:创建一个 1GB 的 volume。紧接着,cinder-api 启动了一个 Flow(工作流)volume_create_api。Flow 的执行状态依次为 PENDING(待定), RUNNING 和 SUCCESS。volume_create_api 当前的状态由 PENDING 变为 RUNNING。
volume_create_api 工作流包含若干 Task,每个 Task 完成特定的任务
这些任务依次为 ExtractVolumeRequestTask, QuotaReserveTask, EntryCreateTask, QuotaCommitTask, VolumeCastTask。Task 的执行状态也会经历 PENDING, RUNNING 和 SUCCESS 三个阶段。Task 的名称基本上说明了任务的工作内容,前面几个 Task 主要是做一些创建 volume 的准备工作,比如:
1)ExtractVolumeRequestTask 获取 request 信息:
2)QuotaReserveTask 预留配额:
3)EntryCreateTask 在数据库中创建 volume 条目:
4)QuotaCommitTask 确认配额:
5)最后 VolumeCastTask 是向 cinder-sheduler 发送消息,开始调度工作:
6)至此,Flow volume_create_api 已经完成,状态由 RUNNING 变为 SUCCESS,volume 创建成功。日志如下:
需要特别注意的是,“volume 创建成功”只是指 cinder-api 已经成功处理了 volume create 请求,将消息发给了 cinder-scheduler,但并不意味 volume 在存储节点上已经成功创建, 这一点是容易引起误解的。我们可以通过 cinder-volume 创建 volume 日志的时间戳验证。
cinder-api 发送消息:
cinder-api 向 RabbitMQ 发送了一条消息:“让cinder-scheduler 创建一个 volume”。前面我们提到消息是由 VolumeCastTask 发出的,因为 VolumeCastTask 没有打印相关日志,我们只能通过源代码查看
/opt/stack/cinder/cinder/volume/flows/api/create_volume.py ,方法为 create_volume。
- cinder-scheduler 执行调度:
cinder-scheduler 执行调度算法,通过 Filter 和 Weigher 挑选最优的存储节点,日志为 /opt/stack/logs/c-sch.log。
1)cinder-scheduler 通过 Flow volume_create_scheduler 执行调度工作:
2)该 Flow 依次执行 ExtractSchedulerSpecTask 和 ScheduleCreateVolumeTask:
3)主要的 filter 和 weighting 工作由 ScheduleCreateVolumeTask 完成
ScheduleCreateVolumeTask:
经过 AvailabilityZoneFilter, CapacityFilter, CapabilitiesFilter 和 CapacityWeigher 的层层筛选,最终选择了存储节点 devstack-controller@lvmdriver-1#lvmdriver-1。
4)Flow volume_create_scheduler 完成调度,状态变为 SUCCESS:
5)cinder-scheduler 发送消息 :
cinder-scheduler 发送消息给 cinder-volume,让其创建 volume。源码 /opt/stack/cinder/cinder/scheduler/filter_scheduler.py,方法为 schedule_create_volume:
- cinder-volume 的处理过程:
cinder-volume 通过 driver 创建 volume,日志为 /opt/stack/logs/c-vol.log。
1)与 cinder-api 和 cinder-scheduler 执行方式类似,cinder-volume 也启动了一个 Flow 来完成 volume 创建工作。Flow 的名称为 volume_create_manager:
2)volume_create_manager 执行操作:
首先执行ExtractVolumeRefTask, OnFailureRescheduleTask, ExtractVolumeSpecTask, NotifyVolumeActionTask 为 volume 创建做准备。
3)接下来 CreateVolumeFromSpecTask 执行 volume 创建任务
创建任务:
因为 volume provider 为 LVM, CreateVolumeFromSpecTask 通过 lvcreate 命令在 VG stack-volumes-lvmdriver-1 中创建了一个 1G 的 LV,cinder-volume 将这个 LV 作为volume。新创建的 LV 命名为“volume-1e7f6bd7-ce11-4a73-b95e-aabd65a5b188”,其格式为“volume-”。
4)最后,CreateVolumeOnFinishTask 完成扫尾工作:
5)至此,volume 成功创建,Flow volume_create_manager 结束:
2.4.5.3 Attach附加磁盘
本节讨论 cinder-volume 和 nova-compute 如何将 volume attach 到 Instance。
- cinder-volume 初始化 volume 的连接:
cinder-volume 接收到 initialize_connection 消息后,会通过 tgt 创建 target,并将 volume 所对应的LV 通过 target export 出来。日志为 /opt/stack/logs/c-vol.log:
下面的日志显示:
通过命令tgtadm –lld iscsi –op show –mode target
看到已经将 1GB(1074MB)的 LV /dev/stack-volumes-lvmdriver-1/volume-1e7f6bd7-ce11-4a73-b95e-aabd65a5b188 通过 Target 1 export 出来了。
Initialize connection 完成:
- nova-compute 将 volume attach 到 instance:
1)iSCSI是由IBM发明的基于以太网的存储协议,该协议与SUN的NFS协议都是为了解决存储资源共享问题的解决方案。两者意图一致,只不过两者是不同的实现方式,前者在客户机上呈现的是一个块设备,概括的说,iSCSI是一种存储设备远程映射技术,它可以将一个远程服务器上的存储设备映射到本地,并呈现为一个块设备(大白话就是磁盘)。从普通用户的角度,映射过来的磁盘与本地安装的磁盘毫无差异。这种映射方式基于是基于SCSI协议的,SCSI协议是计算机与外围设备(例如硬盘、光盘等)通信的协议。而iSCSI则是通过TCP协议对SCSI进行封装的一种协议,也就是通过以太网传输SCSI协议的内容。
2)计算节点作为 iSCSI initiator 访问存储节点 Iscsi Target 上的 volume,并将其 attach 到 instance。日志文件为 /opt/stack/logs/n-cpu.log:
3)nova-compute 依次执行 iscsiadm 的 new, update, login, rescan 操作访问 target 上的 volume:(iscsiadm是基于命令行的iscsi管理工具,提供了对iSCSI节点、会话、连接以及发现记录的操作。)
4)计算节点将 iSCSI target 上的 volume 识别为一个磁盘文件:
5)然后通过更新 instance 的 XML 配置文件将 volume 映射给 instance:
6)我们也可以通过 virsh edit 查看更新后的 XML:
可以看到,instance 增加了一个类型为 block 的虚拟磁盘,source 就是要 attach 的 volume,该虚拟磁盘的设备名为 vdb。
7)手工 Shut off 并 Start instance,通过fdisk -l
查看到 volume 已经 attach 上来,设备为 vdb:
8)GUI 界面也会更新相关 attach 信息:
现在如果我们在存储节点执行tgt-admin –show –mode target
,会看到计算节点作为 initiator 已经连接到 target 1。cinder-volume 刚刚创建 target 的时候是没有 initiator 连接的,大家可以将下面的截图与之前的日志做个对比。
2.4.5.4 Detach卸载磁盘
- 下图是Detach操作流程图,包含1)向 cinder-api 发送 detach 请求;2)cinder-api 发送消息;3)nova-compute detach volume;4)cinder-volume 删除 target。
1)向 cinder-api 发送 attach 请求:
客户(可以是 OpenStack 最终用户,也可以是其他程序)向 cinder-api 发送请求:“请 detach 指定 instance 上的 volume。这里我们将 detach instance “c2”上的 volume “vol-1” 。进入 GUI 操作菜单Project -> Compute -> Volumes:
选择 volume “vol-1”,点击“Manage Attachments” :
点击 “Detach Volume”:
再次确认:
cinder-api 将接收到 detach volume 的请求。日志文件在 /opt/stack/logs/c-api.log:
2)cinder-api 发送消息
cinder-api 发送消息 detach 消息。cinder-api 没有打印发送消息的日志,只能通过源代码查看 /opt/stack/cinder/cinder/volume/api.py,方法为 detach:
Detach 的操作由 nova-compute 和 cinder-volume 共同完成。首先 nova-compute 将 volume 从 instance 上 detach,然后断开与 iSCSI target 的连接;最后 cinder-volume 删除 volume 相关的 iSCSI target。
a)nova-compute detach volume:
nova-compute 首先将 volume 从 instance 上 detach,日志为 /opt/stack/logs/n-cpu.log。
这时通过 virsh edit 可以看到 XML 配置文件中已经不在有 volume 的虚拟磁盘:
接下来断开与 iSCSI target 的连接:
具体步骤如下:
i)将缓存中的数据 Flush 到 volume。
ii)删除计算节点上 volume 对应的 SCSI 设备。
iii)通过 iscsiadm 的 logout,delete 操作断开与 iSCSI target 的连接。
compue-nova 完成了 detach 工作,接下来 cinder-volume 就可以删除 volume 相关的 target 了。
b)cinder-volume 删除 target:
存储节点 cinder-volume 通过tgt-admin
命令删除 volume 对应的 target;日志文件为 /opt/stack/logs/c-vol.log。
至此 detach volume 操作已经完成,GUI 也会更新 volume 的 attach 信息:
2.4.5.5 Extend扩展磁盘
- 为了保护现有数据,cinder 不允许缩小 volume。Extend 操作用于扩大 Volume 的容量,状态为 Available 的 volume 才能够被 extend。如果 volume 当前已经 attach 给 instance,需要先 detach 后才能 extend。
- Extend 实现比较简单,流程图如下所示:1)向 cinder-api 发送 extend 请求;2)cinder-api 发送消息;3)cinder-volume 执行 extend 操作。
1)向 cinder-api 发送 extend 请求:
客户(可以是 OpenStack 最终用户,也可以是其他程序)向 cinder-api 发送请求:“请 extend 指定的 volume。这里我们将 extend volume “vol-2”。进入 GUI 操作菜单 Project -> Compute -> Volumes:
vol-2 当前大小为 1GB。其在存储节点上对应的 LV 信息如下
LV 信息如下:
选择 volume “vol-2”,点击 “Extend Volume”:
指定新的容量为 3GB,点击 “Extend Volume”:
cinder-api 将接收到 extend volume 的请求,日志文件在 /opt/stack/logs/c-api.log:
2)cinder-api 发送消息:
cinder-api 发送extend 消息。cinder-api 没有打印发送消息的日志,只能通过源代码查看。/opt/stack/cinder/cinder/volume/api.py,方法为 extend:
3) cinder-volume extend volume:
cinder-volume 执行 lvextend 命令 extend volume。日志为 /opt/stack/logs/c-vol.log:
LV 被 extend 到 3GB:
Extend 操作完成后,GUI 也会更新 volume 的状态信息:
2.4.5.6 Delete删除磁盘
- 今天讨论 cinder 如何删除 volume ,状态为 Available 的 volume 才能够被 delete,如果 volume 当前已经 attach 到 instance,需要先 detach 后才能 delete。
- Delete操作实现比较简单,流程图如下:1)向 cinder-api 发送delete 请求;2)cinder-api 发送消息;3)cinder-volume 执行 delete 操作。
1)向 cinder-api 发送 delete 请求:
客户(可以是 OpenStack 最终用户,也可以是其他程序)向 cinder-api 发送请求:“请 delete 指定的 volume。这里我们将 delete volume “vol-2”。进入 GUI 操作菜单 Project -> Compute -> Volumes:
选择volume “vol-2”,点击“Delete Volume”:
再次确认:
cinder-api 将接收到 delete volume 的请求。日志文件在 /opt/stack/logs/c-api.log:
2)cinder-api 发送消息:
cinder-api 发送消息 delete 消息。cinder-api 没有打印发送消息的日志,只能通过源代码查看。opt/stack/cinder/cinder/volume/api.py,方法为 extend。
3)cinder-volume delete volume:
cinder-volume 执行 lvremove 命令 delete volume,日志为 /opt/stack/logs/c-vol.log。
这里比较有意思的是:cinder-volume 执行的是“安全”删除。所谓“安全”实际上就是将 volume 中的数据抹掉,LVM driver 使用的是 dd 操作将 LV 的数据清零,日志如下:
然后删除 LV:
2.4.5.7 Snapshot磁盘快照
- Snapshot 可以为 volume 创建快照,快照中保存了 volume 当前的状态,以后可以通过 snapshot 回溯。
- snapshot 操作实现比较简单,流程图如下所示:1)向 cinder-api 发送 snapshot 请求;2)cinder-api 发送消息;3)cinder-volume 执行 snapshot 操作。
1)向 cinder-api 发送 snapshot 请求:
客户(可以是 OpenStack 最终用户,也可以是其他程序)向 cinder-api 发送请求:“请 snapshot 指定的 volume。这里我们将 snapshot volume “vol-1”。进入 GUI 操作菜单 Project -> Compute -> Volumes:
选择 volume “vol-1”,点击 “Create Snapshot”:
为 snapshot 命名:
这里我们看到界面提示当前 volume 已经 attach 到某个 instance,创建 snapshot 可能导致数据不一致。我们可以先 pause instance,或者确认当前 instance 没有大量的磁盘 IO,处于相对稳定的状态,则可以创建 snapshot,否则还是建议先 detach volume 再做 sanpshot。
cinder-api 将接收到 snapshot volume 的请求,日志文件在 /opt/stack/logs/c-api.log:
2)cinder-api 发送消息:
cinder-api 发送消息 snapshot 消息。cinder-api 没有打印发送消息的日志,只能通过源代码查看 /opt/stack/cinder/cinder/volume/api.py,方法为 _create_snapshot:
3)cinder-volume 执行 snapshot 操作:
cinder-volume 执行 lvcreate 命令创建 snapshot。日志为 /opt/stack/logs/c-vol.log:
对于 LVM volume provider,snapshot 实际上也是一个 LV,同时记录了与源 LV 的 snapshot 关系,可以通过 lvdisplay 查看:
GUI 的 Volume Snapshots 标签中可以看到新创建的 “vol-1-snapshot”:
有了 snapshot,我们就可以将 volume 回溯到创建 snapshot 时的状态,方法是通过 snapshot 创建新的 volume:
新创建的 volume 容量必须大于或等于 snapshot 的容量:
其过程与 Create Volume 类似,不同之处在于 LV 创建之后会通过 dd 将 snapshot 的数据 copy 到新的 volume。如果一个 volume 存在 snapshot,则这个 volume 是无法删除的。这是因为 snapshot 依赖于 volume,snapshot 无法独立存在。 在 LVM 作为 volume provider 的环境中,snapshot 是从源 volume 完全 copy 而来,所以这种依赖关系不强。但在其他 volume provider(比如商业存储设备或者分布式文件系统),snapshot 通常是源 volume 创建快照时数据状态的一个引用(指针),占用空间非常小,在这种实现方式里 snapshot 对源 volume 的依赖就非常明显了。
2.4.5.8 Backup磁盘备份
- 本节我们讨论 volume 的 Backup 操作。Backup 是将 volume 备份到别的地方(备份设备),将来可以通过 restore 操作恢复。
- Backup VS Snapshot:
初看 backup 功能好像与 snapshot 很相似,都可以保存 volume 的当前状态,以备以后恢复。但二者在用途和实现上还是有区别的,具体表现在:
1)Snapshot 依赖于源 volume,不能独立存在;而 backup 不依赖源 volume,即便源 volume 不存在了,也可以 restore。
2)Snapshot 与源 volume 通常存放在一起,都由同一个 volume provider 管理;而 backup 存放在独立的备份设备中,有自己的备份方案和实现,与 volume provider 没有关系。
3)上面两点决定了 backup 具有容灾功能;而 snapshot 则提供 volume provider 内便捷的回溯功能。 - 配置 cinder-backup:
Cinder 的 backup 功能是由 cinder-backup 服务提供的,devstack 默认没有启用该服务,需要手工启用
与 cinder-volume 类似,cinder-backup 也通过 driver 架构支持多种备份 backend,包括 POSIX 文件系统、NFS、Ceph、GlusterFS、Swift 和 IBM TSM。支持的driver 源文件放在 /opt/stack/cinder/cinder/backup/drivers/:
- 本节我们将以 NFS 为 backend 来研究 backup 操作 :
在实验环境中,存放 volume backup 的 NFS 远程目录为 192.168.104.11:/backup,cinder-backup 服务节点上 mount point 为 /backup_mount。需要在 /etc/cinder/cinder.conf 中作相应配置:
然后手工启动 cinder-backup 服务:
- 一切准备就绪,下面我们来看 backup 操作的流程:1)向 cinder-api 发送 backup 请求;2)cinder-api 发送消息;3)cinder-backup 执行 backup 操作。
1)向 cinder-api 发送 backup 请求:
客户(可以是 OpenStack 最终用户,也可以是其他程序)向 cinder-api 发送请求:“请 backup 指定的 volume。这里我们将 backup volume “vol-1”,目前 backup 只能在 CLI 中执行,采用cinder backup-create vol-1 --force
命令。
这里因为 vol-1 已经 attach 到 instance,需要使用–force
选项。cinder-api 接收到 backup volume 的请求。日志文件在 /opt/stack/logs/c-api.log。
2)cinder-api 发送消息:
cinder-api 发送 backup 消息。cinder-api 没有打印发送消息的日志,只能通过源代码查看
/opt/stack/cinder/cinder/backup/api.py,方法为 create:
3)cinder-backup 执行 backup 操作:
cinder-backup 收到消息后,通过如下步骤完成 backup 操作,日志为 /opt/stack/logs/c-vol.log。
i)启动 backup 操作,mount NFS。
ii)创建 volume 的临时快照。
iii)创建存放 backup 的 container 目录。
iv)对临时快照数据进行压缩,并保存到 container 目录。
v)创建并保存 sha256(加密)文件和 metadata 文件。
vi)删除临时快照。
Backup 完成后,我们可以查看一下 container 目录的内容:里面有三个文件,根据前面的日志我们可以知道:backup-00001,压缩后的 backup 文件。backup_metadata,metadata 文件。backup_sha256file,加密文件。
可以通过 cinder backup-list
查看当前存在的 backup:
另外我们采用cinder help backup-create
命令可以查看一下 cinder backup-create 的用法:
这里有--incremental
选项,表示可以执行增量备份。如果之前做过普通(全量)备份,之后可以通过增量备份大大减少需要备份的数据量,是个很不错的功能。
2.4.5.9 restore磁盘恢复
- 前面我们 backup 了 voluem,今天我们将讨论如何 restore volume。restore 的过程其实很简单,两步走:1)在存储节点上创建一个空白 volume。2)将 backup 的数据 copy 到空白 voluem 上。
- 下面我们来看 restore 操作的详细流程:1)向 cinder-api 发送 restore 请求;2)cinder-api 发送消息;3)cinder-scheduler 挑选最合适的 cinder-volume;4)cinder-volume 创建空白 volume;5)cinder-backup 将 backup 数据 copy 到空白 volume 上。
1) 向 cinder-api 发送 restore 请求:
客户(可以是 OpenStack 最终用户,也可以是其他程序)向 cinder-api 发送请求:“请 restore 指定的 backup”。这里我们将 restore 之前创建的 backup,目前 restore 只能在 CLI 中执行,先采用cinder backup-list
命令查看backup,采用cinder backup-restore 备份volume的ID
命令进行恢复:
cinder-api 接收到 restore 请求,日志文件在 /opt/stack/logs/c-api.log:
这里看到 cinder-api 转发请求,为 restore 创建 volume。 之后 cinder-scheduler 和 cinder-volume 将创建空白 volume,这个过程与 create volume 一样,不再赘述。
2)接下来分析数据恢复的过程
a)首先,在 cinder-api 日志中可以看到相关信息:
这里注意日志中的 volume_id 和 backup_id 与前面 backup-restore 命令的输出是一致的。
b)下面来看 cinder-backup 是如何恢复数据的。cinder-backup 执行 restore 操作:日志为 /opt/stack/logs/c-vol.log。
i)启动 restore 操作,mount NFS。
ii)读取 container 目录中的 metadata。
iii)将数据解压并写到 volume 中。
iv)恢复 volume 的 metadata,完成 restore 操作。
c)此时,在 GUI 中已经可以看到 restore 创建的 volume:
2.4.5.10 Boot From Volume 将Volume作为虚拟机的启动盘
- Volume 除了可以用作 instance 的数据盘,也可以作为启动盘(Bootable Volume)。
那么如何使 volume 成为 bootable 呢?现在我们打开 instance 的 launch 操作界面:
这里有一个下拉菜单“Instance Boot Source”。以前我们 launch(部署)instance 要么直接从 image launch(Boot from image),要么从 instance 的 snapshot launch(Boot from snapshot)。 这两种 launch 方式下,instance 的启动盘 vda 均为镜像文件,存放路径为计算节点/opt/stack/data/nova/instances/<Instance ID>/disk
,例如:
下拉列表的后三项则可以将 volume 作为 instance 的启动盘 vda,分别为:
1)Boot from volume:直接从现有的 bootable volume launch;
2)Boot from image (create a new volume):创建一个新的 volume,将 image 的数据 copy 到 volume,然后从该 volume launch;
3)Boot from volume snapshot (create a new volume):通过指定的 volume snapshot 创建 volume,然后从该 volume launch,当然前提是该snapshot 对应的源 volume 是 bootable 的。
下面我们以 Boot from image (create a new volume)为例,看如何从 volume 启动:
选择 cirros 作为 image,instance 命名为“c3” ,如果希望 terminate instance 的时候同时删除 volume,可以勾选“Delete on Terminate”。
c3 成功 Launch 后,volume 列表中可以看到一个新 bootable volume,以 volume ID 命名,并且已经 attach 到 c3。
该 volume 已经配置为 c3 的启动盘 vda:
如果用该 volume 创建 snapshot,之后就可以通过 Boot from volume snapshot (create a new volume) 部署新的instance。boot from volume 的 instance 也可以执行 live migrate。前面的实验使用的是 LVM provider,cinder 当然也支持其他 provider。
2.4.5.11 NFS Volume Provider
-
cinder-volume 支持多种 volume provider,前面我们一直使用的是默认的 LVM,本节我们将增加 NFS volume provider。虽然 NFS 更多地应用在实验或小规模 cinder 环境,由于性能和缺乏高可用的原因在生产环境中不太可能使用。
-
但是学习 NFS volume provider 的意义在于:
1)理解 cinder-volume 如何支持多 backend;
2)更重要的,可以理解 cinder-volume,nova-compute 和 volume provider 是如何协同工作,共同为 instance 提供块存储;
3)举一反三,能够快速理解并接入其他生产级 backend ,比如 Ceph,商业存储等。 -
下图展示了 cinder、nova 是如何与 NFS volume provider 协调工作的:
1)NFS Volume Provider:就是我们通常说的 NFS Server,提供远程 NFS 目录,NFS Clinet 可以 mount 这些远程目录到本地,然后像使用本地目录一样创建、读写文件以及子目录。
2)cinder-volume:存储节点通过 NFS driver 管理 NFS volume provider 中的 volume,这些 volume 在 NFS 中实际上是一个个文件。
3)nova-compute:计算节点将 NFS volume provider 存放 volume 的目录 mount 到本地,然后将 volume 文件作为虚拟硬盘映射给 instance。 -
这里有几点需要强调
1)在 Cinder 的 driver 架构中,运行 cinder-volume 的存储节点和 Volume Provider 可以是完全独立的两个实体。cinder-volume 通过 driver 与 Volume Provider 通信,控制和管理 volume。
2)Instance 读写 volume 时,数据流不需要经过存储节点,而是直接对 Volume Provider 中的 volume 进行读写。 正如上图所示,存储节点与 NFS Volume Provider 的连接只用作 volume 的管理和控制(绿色连线);真正的数据读写,是通过计算节点和 NFS Volume Proiver 之间的连接完成的(紫色连线)。这种设计减少了中间环节,存储节点不直接参与数据传输,保证了读写效率。
3)其他 Volume Provider(例如 ceph,swift,商业存储等)均遵循这种控制流与数据流分离的设计。 -
配置 NFS Volume Provider:
在实验环境中,NFS volume provider 的 NFS 远程目录为 192.168.104.11:/storage,cinder-volume 服务节点上 mount point 为 /nfs_storage。在 /etc/cinder/cinder.conf 中添加 nfs backend:
配置enabled_backends = lvmdriver-1,nfs
是 让 cinder-volume 使用 nfs backend。[nfs]中详细配置 nfs backend。包括:
a) 使用nfs_mount_point_base = /nfs_storage
命令,指定存储节点上 /nfs_storage 为 nfs 的 mount point。
b) 查看 /etc/cinder/nfs_shares 活动 nfs 共享目录列表。nfs_shares_config = /etc/cinder/nfs_shares
,其内容为:
列表中只有 192.168.104.11:/storage。如果希望有多个 nfs 共享目录存放 volume,则可以添加到该文件中。
c) nfs volume driver。
volume_driver=cinder.volume.drivers.nfs.NfsDriver
d) 设置 volume backend name。在 cinder 中需要根据这里的 volume_backend_name 创建对应的 volume type,这个非常重要,volume_backend_name = nfs
。 -
重启 cinder-volume,cinder service-list 确认 nfs cinder-volume 服务正常工作:
-
创建 nfs volume type
打开GUI页面Admin -> System -> Volumes -> Volume Types,点击 “Create Volume Type”:
命名 nfs,点击“Create Volume Type”:
选择 nfs volume type,点击下拉菜单“View Extra Specs”:
点击“Create”,Key 输入 volume_backend_name ;Value 输入 nfs:
NFS volume provider 准备就绪,下一节我们将创建 NFS 为 backend 的 volume。 -
创建volume
1)创建 NFS volume 操作方法与 LVM volume 一样,唯一区别是在 volume type 的下拉列表中选择“nfs”:
点击“Create Volume”,cinder-api,cinder-scheduler 和 cinder-volume 共同协作创建 volume “nfs-vol-1”。这个流程与 LVM volume 一样。
2)分析 cinder-volume 的日志:
看看 NFS volume provider 是如何创建 volume 的,日志在 /opt/stack/logs/c-vol.log。
i)cinder-volume 也会启动 Flow 来完成 volume 创建工作,Flow 的名称为 volume_create_manager:
volume_create_manager 首先执行 ExtractVolumeRefTask, OnFailureRescheduleTask, ExtractVolumeSpecTask, NotifyVolumeActionTask 为 volume创建做准备。
ii)然后由 CreateVolumeFromSpecTask 真正创建 volume:
iii)首先 mount 远程 NFS 目录:
iv)执行 stat、du 命令检查 NFS 目录:
v)执行 truncate 创建 volume 文件:
vi)采用chmod ugo+rw
命令设置 volume 文件为可读写:
vii)create 操作完成:
viii)Volume 在 NFS 上以文件存在,命名为“volume-”:
ix)GUI volume 列表中可以看到新创建的 volume:
GUI volume 列表中可以看到新创建的 volume。 -
nova-compute 如何将“nfs-vol-1” attach 到“c2” :
通过日志分析,nova-compute 会将存放 volume 文件的 NFS 目录 mount 到本地 /opt/stack/data/nova/mnt 目录下,然后修改 instance 的 XML 将 volume 文件配置为虚拟磁盘,日志为 /opt/stack/logs/n-cpu.log。
1)通过 findmnt 和 mkdir 测试和创建 mount point:
2)mount NFS 目录:
3)更新 instance 的XML,将 volume 文件映射给 instance:
4)我们也可以通过 virsh edit 查看更新后的XML:
5)GUI 界面也会更新相关 attach 信息:
2.5 网络服务Neutron
2.5.1 Neutron概述
2.5.1.1 Neutron功能
- 二层交换 Switching
1)Nova 的 Instance 是通过虚拟交换机连接到虚拟二层网络的。
2)Neutron 支持多种虚拟交换机,包括 Linux 原生的 Linux Bridge 和 Open vSwitch。Open vSwitch(OVS)是一个开源的虚拟交换机,它支持标准的管理接口和协议。
3)利用 Linux Bridge 和 OVS,Neutron 除了可以创建传统的 VLAN 网络,还可以创建基于隧道技术的 Overlay 网络,比如 VxLAN 和 GRE(Linux Bridge 目前只支持 VxLAN)。 - 三层路由 Routing
1)Instance 可以配置不同网段的 IP,Neutron 的 router(虚拟路由器)实现 instance 跨网段通信。
2)router 通过 IP forwarding,iptables 等技术来实现路由和 NAT。(IP forwarding是IP转发,一种路由协议。IP转发是操作系统的一种选项,支持主机起到路由器的功能。在一个系统中含有两块以上的网卡,并将IP转发选项打开,这样该系统就可以作为路由器进行使用了)。 - 负载均衡 Load Balancing
1)Openstack 在 Grizzly 版本第一次引入了 Load-Balancing-as-a-Service(LBaaS),提供了将负载分发到多个 instance 的能力。LBaaS 支持多种负载均衡产品和方案,不同的实现以 Plugin(插件) 的形式集成到 Neutron,目前默认的 Plugin 是 HAProxy。 - 防火墙 Firewalling
Neutron 通过下面两种方式来保障 instance 和网络的安全性。
1)Security Group:通过 iptables 限制进出 instance 的网络包。
2)Firewall-as-a-Service:FWaaS,限制进出虚拟路由器的网络包,通过 iptables 实现。
2.5.1.2 Neutron网络基本概念
- network :
network 是一个隔离的二层广播域。Neutron 支持多种类型的 network,包括 local, flat, VLAN, VxLAN 和 GRE。
1)local
local 网络与其他网络和节点隔离。local 网络中的 instance 只能与位于同一节点上同一网络的 instance 通信,local 网络主要用于单机测试。
2)flat
flat 网络是无 vlan tagging 的网络。flat 网络中的 instance 能与位于同一网络的 instance 通信,并且可以跨多个节点。
3)vlan
vlan 网络是具有 802.1q tagging 的网络。vlan 是一个二层的广播域,同一 vlan 中的 instance 可以通信,不同 vlan 只能通过 router 通信。vlan 网络可以跨节点,是应用最广泛的网络类型。
4)vxlan
vxlan 是基于隧道技术的 overlay 网络。vxlan 网络通过唯一的 segmentation ID(也叫 VNI)与其他 vxlan 网络区分。vxlan 中数据包会通过 VNI 封装成 UDP 包进行传输。因为二层的包通过封装在三层传输,能够克服 vlan 和物理网络基础设施的限制。
5)gre
gre 是与 vxlan 类似的一种 overlay 网络。主要区别在于使用 IP 包而非 UDP 进行封装。
6)不同 network 之间在二层上是隔离的
以 vlan 网络为例,network A 和 network B 会分配不同的 VLAN ID,这样就保证了 network A 中的广播包不会跑到 network B 中。当然,这里的隔离是指二层上的隔离,借助路由器不同 network 是可能在三层上通信的。network 必须属于某个 Project( Tenant 租户),Project 中可以创建多个 network。 network 与 Project 之间是 1对多 关系。 - subnet:
subnet 是一个 IPv4 或者 IPv6 地址段。instance 的 IP 从 subnet 中分配。每个 subnet 需要定义 IP 地址的范围和掩码。subnet 与 network 是 1对多 关系。一个 subnet 只能属于某个 network;一个 network 可以有多个 subnet,这些 subnet 可以是不同的 IP 段,但不能重叠。
下面的配置是有效的:
但下面的配置则无效,因为 subnet 有重叠:
这里不是判断 IP 是否有重叠,而是 subnet 的 CIDR 重叠(都是 10.10.1.0/24)。
但是,如果 subnet 在不同的 network 中,CIDR 和 IP 都是可以重叠的,比如:
这里大家不免会疑惑: 如果上面的IP地址是可以重叠的,那么就可能存在具有相同 IP 的两个 instance,这样会不会冲突? 简单的回答是:不会!具体原因: 因为 Neutron 的 router 是通过 Linux network namespace 实现的。network namespace 是一种网络的隔离机制。通过它,每个 router 有自己独立的路由表。
上面的配置有两种结果:
1)如果两个 subnet 是通过同一个 router 路由,根据 router 的配置,只有指定的一个 subnet 可被路由。
2)如果上面的两个 subnet 是通过不同 router 路由,因为 router 的路由表是独立的,所以两个 subnet 都可以被路由。 - port:
1)port 可以看做虚拟交换机上的一个端口。port 上定义了 MAC 地址和 IP 地址,当 instance 的虚拟网卡 VIF(Virtual Interface)绑定到 port 时,port 会将 MAC 和 IP 分配给 VIF。
2)port 与 subnet 是 1对多 关系。一个 port 必须属于某个 subnet;一个 subnet 可以有多个 port。 - 小节
下面总结了 Project,Network,Subnet,Port 和 VIF 之间关系。Project 1 : m Network 1 : m Subnet 1 : m Port 1 : 1 VIF m : 1 Instance
,即一个租户有多个网络,一个网络有多个子网,一个子网有多个端口,一个端口有一个虚拟网卡,一个虚拟机有多个虚拟网卡。
2.5.2 Neutron架构
2.5.2.1 Neutron组件概括
- 与 OpenStack 的其他服务的设计思路一样,Neutron 也是采用分布式架构,由多个组件(子服务)共同对外提供网络服务。
- Neutron由如下组件构成:
1)Neutron Server:对外提供 OpenStack 网络 API,接收请求,并调用 Plugin 处理请求。
2)Plugin:处理 Neutron Server 发来的请求,维护 OpenStack 逻辑网络的状态, 并调用 Agent 处理请求。
3)Agent:处理 Plugin 的请求,负责在 network provider 上真正实现各种网络功能。
4)network provider:提供网络服务的虚拟或物理网络设备,例如 Linux Bridge,Open vSwitch 或者其他支持 Neutron 的物理交换机。
5)Queue:Neutron Server,Plugin 和 Agent 之间通过 Messaging Queue 通信和调用。
6)Database:存放 OpenStack 的网络状态信息,包括 Network, Subnet, Port, Router 等。
- 以创建一个 VLAN100 的 network 为例,假设 network provider 是 linux bridge, 流程如下:
1)Neutron Server 接收到创建 network 的请求,通过 Message Queue(RabbitMQ)通知已注册的 Linux Bridge Plugin。
2)Plugin 将要创建的 network 的信息(例如名称、VLAN ID等)保存到数据库中,并通过 Message Queue 通知运行在各节点上的 Agent。
3)Agent 收到消息后会在节点上的物理网卡(比如 eth2)上创建 VLAN 设备(比如 eth2.100),并创建 bridge (比如 brqXXX) 桥接 VLAN 设备。 - 这里进行几点说明:
1)plugin 解决的是 What 的问题,即网络要配置成什么样子?而至于如何配置 How 的工作则交由 agent 完成。
2)plugin,agent 和 network provider 是配套使用的,比如上例中 network provider 是 linux bridge,那么就得使用 linux bridge 的 plungin 和 agent;如果 network provider 换成了 OVS 或者物理交换机,plugin 和 agent 也得替换。
3)plugin 的一个主要的职责是在数据库中维护 Neutron 网络的状态信息,这就造成一个问题:所有 network provider 的 plugin 都要编写一套非常类似的数据库访问代码。为了解决这个问题,Neutron 在 Havana 版本实现了一个 ML2(Modular Layer 2)plugin,对 plgin 的功能进行抽象和封装。有了 ML2 plugin,各种 network provider 无需开发自己的 plugin,只需要针对 ML2 开发相应的 driver 就可以了,工作量和难度都大大减少。ML2 会在后面详细讨论。
4)plugin 按照功能分为两类: core plugin 和 service plugin。core plugin 维护 Neutron 的 netowrk, subnet 和 port 相关资源的信息,与 core plugin 对应的 agent 包括 linux bridge, OVS 等; service plugin 提供 routing, firewall, load balance 等服务,也有相应的 agent。
2.5.2.2 Neutron物理部署方案
- 方案1:控制节点 + 计算节点:
1)控制节点:部署的服务包括:neutron server, core plugin 的 agent 和 service plugin 的 agent。
2)计算节点:部署 core plugin 的agent,负责提供二层网络功能。
NOTE:这里有几点需要说明:
1)core plugin 和 service plugin 已经集成到 neutron server,不需要运行独立的 plugin 服务。
2)控制节点和计算节点都需要部署 core plugin 的 agent,因为通过该 agent 控制节点与计算节点才能建立二层连接。
3)可以部署多个控制节点和计算节点。
- 方案1:控制节点 +网络节点+ 计算节点:
1)控制节点:部署 neutron server 服务。
2)网络节点:部署的服务包括:core plugin 的 agent 和 service plugin 的 agent。
3)计算节点:部署 core plugin 的agent,负责提供二层网络功能。
这个方案的要点是将所有的 agent 从控制节点分离出来,部署到独立的网络节点上。控制节点只负责通过 neutron server 响应 API 请求。由独立的网络节点实现数据的交换,路由以及 load balance等高级网络服务。可以通过增加网络节点承担更大的负载。可以部署多个控制节点、网络节点和计算节点。该方案特别适合规模较大的 OpenStack 环境。
2.5.3 Neutron组件详细说明
2.5.3.1 Neutron Server
- 下图是 Neutron Server 的分层结构,至上而下依次为:
1)Core API:对外提供管理 network, subnet 和 port 的 RESTful API。
2)Extension API:对外提供管理 router, load balance, firewall 等资源 的 RESTful API。
3)Commnon Service:认证和校验 API 请求。
4)Neutron Core:Neutron server 的核心处理程序,通过调用相应的 Plugin 处理请求。
5)Core Plugin API:定义了 Core Plgin 的抽象功能集合,Neutron Core 通过该 API 调用相应的 Core Plgin。
6)Extension Plugin API:定义了 Service Plgin 的抽象功能集合,Neutron Core 通过该 API 调用相应的 Service Plgin。
7)Core Plugin:实现了 Core Plugin API,在数据库中维护 network, subnet 和 port 的状态,并负责调用相应的 agent 在 network provider 上执行相关操作,比如创建 network。
8)Service Plugin:实现了 Extension Plugin API,在数据库中维护 router, load balance, security group 等资源的状态,
并负责调用相应的 agent 在 network provider 上执行相关操作,比如创建 router。 - 归纳起来,Neutron Server 包括两部分:1)提供 API 服务。2)运行 Plugin。
即 Neutron Server = API + Plugins
2.5.3.2 Neutron如何支持多种network provider
- 根据我们上一节讨论的 Neutron Server 的分层模型,我们需要实现两个东西:linux bridge core plugin 和 linux bridge agent。
- linux bridge core plugin
1)与 neutron server 一起运行。
2)实现了 core plugin API。
3)负责维护数据库信息。
4)通知 linux bridge agent 实现具体的网络功能。 - linux bridge agent
1)在计算节点和网络节点(或控制节点)上运行。
2)接收来自 plugin 的请求。
3)通过配置本节点上的 linux bridge 实现 neutron 网络功能。
- 同样的道理,如果要支持 open vswitch,只需要实现 open vswitch plugin 和 open vswitch agent
- 由此可见:Neutron 可以通过开发不同的 plugin 和 agent 支持不同的网络技术。这是一种相当开放的架构。不过随着支持的 network provider 数量的增加,开发人员发现了两个突出的问题:只能在 OpenStack 中使用一种 core plugin,多种 network provider 无法共存。不同 plugin 之间存在大量重复代码,开发新的 plugin 工作量大。
2.5.3.3 ML2 Core Plugin
- Moduler Layer 2(ML2)是 Neutron 在 Havana 版本实现的一个新的 core plugin,用于替代原有的 linux bridge plugin 和 open vswitch plugin。
- ML2 能解决传统 core plugin 的问题。ML2 作为新一代的 core plugin,提供了一个框架,允许在 OpenStack 网络中同时使用多种 Layer 2 网络技术,不同的节点可以使用不同的网络实现机制。
如上图所示,采用 ML2 plugin 后,可以在不同节点上分别部署 linux bridge agent, open vswitch agent, hyper-v agent 以及其他第三方 agent。(Hyper-V 是 Microsoft 的硬件虚拟化产品。 它用于创建并运行虚拟机。 每个虚拟机都像一台完整的计算机一样运行操作系统和程序。) - ML2 不但支持异构部署方案,同时能够与现有的 agent 无缝集成:以前用的 agent 不需要变,只需要将 Neutron server 上的传统 core plugin 替换为 ML2。有了 ML2,要支持新的 network provider 就变得简单多了:无需从头开发 core plugin,只需要开发相应的 mechanism driver,大大减少了要编写和维护的代码。
- ML2 对二层网络进行抽象和建模,引入了 type driver 和 mechanism driver。这两类 driver 解耦了 Neutron 所支持的网络类型(type)与访问这些网络类型的机制(mechanism),其结果就是使得 ML2 具有非常好的弹性,易于扩展,能够灵活支持多种 type 和 mechanism。
- Type Driver:Neutron 支持的每一种网络类型都有一个对应的 ML2 type driver。type driver 负责维护网络类型的状态,执行验证,创建网络等。ML2 支持的网络类型包括 local, flat, vlan, vxlan 和 gre。
- Mechanism Driver:Neutron 支持的每一种网络机制都有一个对应的 ML2 mechanism driver。mechanism driver 负责获取由 type driver 维护的网络状态,并确保在相应的网络设备(物理或虚拟)上正确实现这些状态。
- type 和 mechanisim 都太抽象,现在我们举一个具体的例子:type driver 为 vlan,mechanism driver 为 linux bridge,我们要完成的操作是创建 network vlan100,那么:vlan type driver 会确保将 vlan100 的信息保存到 Neutron 数据库中,包括 network 的名称,vlan ID 等。linux bridge mechanism driver 会确保各节点上的 linux brige agent 在物理网卡上创建 ID 为 100 的 vlan 设备 和 brige 设备,并将两者进行桥接。
- mechanism driver 有三种类型
1)Agent-based:包括 linux bridge, open vswitch 等。
2)Controller-based:包括 OpenDaylight, VMWare NSX 等。
3)基于物理交换机:包括 Cisco Nexus, Arista, Mellanox 等。
比如前面那个例子如果换成 Cisco 的 mechanism driver,则会在 Cisco 物理交换机的指定 trunk 端口上添加 vlan100。 - linux bridge 和 open vswitch 的 ML2 mechanism driver 的作用是配置各节点上的虚拟交换机。linux bridge driver 支持的 type 包括 local, flat, vlan, and vxlan。open vswitch driver 除了这 4 种 type 还支持 gre。L2 population driver 作用是优化和限制 overlay 网络中的广播流量。vxlan 和 gre 都属于 overlay 网络。
2.5.3.4 Service Plugin/Agent
- Core Plugin/Agent 负责管理核心实体:net, subnet 和 port。而对于更高级的网络服务,则由 Service Plugin/Agent 管理。
Service Plugin 及其 Agent 提供更丰富的扩展功能,包括路由,load balance,firewall等。 - Neutron架构如图所示:
1)DHCP:dhcp agent 通过 dnsmasq 为 instance 提供 dhcp 服务。
2)Routing:l3 agent 可以为 project(租户)创建 router,提供 Neutron subnet 之间的路由服务。路由功能默认通过 IPtables 实现。
3)Firewall:l3 agent 可以在 router 上配置防火墙策略,提供网络安全防护。
另一个与安全相关的功能是 Security Group,也是通过 IPtables 实现。Firewall 与 Security Group 的区别在于:Firewall 安全策略位于 router,保护的是某个 project 的所有 network。Security Group 安全策略位于 instance,保护的是单个 instance。
4)Load Balance:Neutron 默认通过 HAProxy 为 project 中的多个 instance 提供 load balance 服务。
2.5.3.5 小结
- 与 OpenStack 其他服务一样,Neutron 采用的是分布式架构,包括 Neutorn Server、各种 plugin/agent、database 和 message queue。
1)Neutron server 接收 api 请求。
2)plugin/agent 实现请求。
3)database 保存 neutron 网络状态。
4)message queue 实现组件之间通信。 - metadata-agent 之前没有讲到,这里做个补充:
instance 在启动时需要访问 nova-metadata-api 服务获取 metadata 和 userdata,这些 data 是该 instance 的定制化信息,比如 hostname, ip, public key 等。但 instance 启动时并没有 ip,如何能够通过网络访问到 nova-metadata-api 服务呢?
答案就是 neutron-metadata-agent。该 agent 让 instance 能够通过 dhcp-agent 或者 l3-agent 与 nova-metadata-api 通信 - 如果我们将 Neutron 架构展开,则会得到下面第二张图:
1)Neutron 通过 plugin 和 agent 提供的网络服务。
2)plugin 位于 Neutron server,包括 core plugin 和 service plugin。
3)agent 位于各个节点,负责实现网络服务。
4)core plugin 提供 L2 功能,ML2 是推荐的 plugin。
5)使用最广泛的 L2 agent 是 linux bridage 和 open vswitch。
6)service plugin 和 agent 提供扩展功能,包括 dhcp, routing, load balance, firewall, vpn 等。
2.5.4 为Neutron准备底层基础设施
- 1个控制节点 +1个计算节点的部署方案:控制节点合并了网络节点的功能,同时也是一个计算节点 。
- 配置多个网卡区分不同类型的网络数据:Management、API、VM、External。
1)Management 网络
用于节点之间 message queue 内部通信以及访问 database 服务,所有的节点都需要连接到 management 网络。
2)API 网络
OpenStack 各组件通过该网络向用户暴露 API 服务。Keystone, Nova, Neutron, Glance, Cinder, Horizon 的 endpoints 均配置在 API 网络上。通常,管理员也通过 API 网络 SSH 管理各个节点。
3)VM 网络
VM 网络也叫 tenant 网络,用于 instance 之间通信。 VM 网络可以选择的类型包括 local, flat, vlan, vxlan 和 gre。 VM 网络由 Neutron 配置和管理。
4)External网络
External 网络指的是 VM 网络之外的网络,该网络不由 Neutron 管理。 Neutron 可以将 router attach 到 External 网络,为 instance 提供访问外部网络的能力。 External 网络可能是企业的 intranet,也可能是 internet。 - 我们可以为每种网络分配单独的网卡; 也可以多种网络共同使用一个网卡;为提高带宽和硬件冗余,可以使用 bonding 技术将多个物理网卡绑定成一个逻辑的网卡,我们的实验环境采用下面的网卡分配方式:
- 网络拓扑:(节点安装配置参考:https://www.xjimmy.com/openstack-5min-76.html)
2.5.5 Linux Bridge实现Neutron网络
2.5.5.1 配置 Linux-Bridge Mechanism Driver
- 下图中,br0 是 linux bridge,br0 充当虚拟交换机的作用,负责将物理网卡 eth0 和虚拟网卡 tap 设备 vnet0/vent1 连接到同一个二层网络,实现虚拟机 VM1 和 VM2,以及虚拟机与外网之间的通信。
- 配置linux-bridge mechanism driver
1)要在 Neutron 中使用 linux bridge,首先需要配置 linux-bridge mechanism driver:
Neutron 默认使用 ML2 作为 core plugin,其配置位于 /etc/neutron/neutron.conf。控制节点和计算节点都需要在各自的 neutron.conf 中配置 core_plugin 选项。
2)然后需要让 ML2 使用 linux-bridge mechanism driver:
ML2 的配置文件位于 /etc/neutron/plugins/ml2/ml2_conf.ini。
mechanism_drivers 选项指明当前节点可以使用的 mechanism driver,这里可以指定多种 driver,ML2 会负责加载。上面的配置指明我们只使用 linux-bridge driver。控制节点和计算节点都需要在各自的 ml2_conf.ini 中配置 mechanism_drivers 选项。
3)Neutron 服务正常启动后,所有节点上都会运行 neutron-linuxbridge-agent:
2.5.5.2 初始网络状态
- 控制节点:
- 计算节点:
- 在 linux bridge 环境中,一个数据包从 instance 发送到物理网卡会经过下面几个类型的设备:
1)tap interface:命名为 tapN (N 为 0, 1, 2, 3……)。
2)linux bridge:命名为 brqXXXX。
3)vlan interface:命名为 ethX.Y(X 为 interface 的序号,Y 为 vlan id)。
4)vxlan interface:命名为 vxlan-Z(z 是 VNI)。
5)物理 interface:命名为 ethX(X 为 interface 的序号)。
linux-bridge 支持 local, flat, vlan 和 vxlan 四种 network type,目前不支持 gre。
2.5.5.3 local network
- local 网络与其他网络和节点隔离。local 网络中的 instance 只能与位于同一节点上同一网络的 instance 通信,local 网络主要用于单机测试。
- local network 的特点是不会与宿主机的任何物理网卡相连,也不关联任何的 VLAN ID。对于每个 local netwrok,ML2 linux-bridge 会创建一个 bridge,instance 的 tap 设备会连接到 bridge。位于同一个 local network 的 instance 会连接到相同的 bridge,这样 instance 之间就可以通信了。因为 bridge 没有与物理网卡连接,所以 instance 无法与宿主机之外的网络通信。同时因为每个 local network 有自己的 bridge,bridge 之间是没有连通的,所以两个 local network 之间也不能通信,即使它们位于同一宿主机上。
- 下图是 local network 的示例:
1)创建了两个 local network,分别对应两个网桥 brqXXXX 和 brqYYYY。
2)VM0 和 VM1 通过 tap0 和 tap1 连接到 brqXXXX。
3)VM2 通过 tap2 连接到 brqYYYY。
4)VM0 与 VM1 在同一个 local network中,它们之间可以通信。
5)VM2 位于另一个 local network,由于 brqXXXX 和 brqYYYY 没有联通,所以 VM2 无法与 VM0 和 VM1 通信。 - 在 ML2 配置中 enable local network:
1)创建 local 网络之前请先确保 ML2 已经加载了 local type driver,ML2 的配置文件位于 /etc/neutron/plugins/ml2/ml2_conf.ini:
2)type_drivers 告诉 ML2 加载所有 5 种网络的 type driver。
3)普通用户和 admin 都可以通过 CLI 或者 Web GUI 创建网络,但只有 amdin 才能指定网络的 type,所以需要用 tenant_network_types 告诉 ML2 当普通用户在自己的 Tenant(Project)中创建网络时,默认创建哪种 type 的网络,这里 type 是 local。tenant_network_types 可以指定多种 type,比如:
4)其作用是先创建 vlan 网络,当没有 vlan 可创建时(比如 vlan id 用完),便创建 local 网络。当配置文件发生了变化,需要重启 Neutron 相关服务使之生效。 - 创建第一个 Local Network:
1)首先确保各个节点上的 neutorn agent 状态正常。GUI 菜单 为 Admin -> System -> System Infomation -> Neutron Agents:
2)GUI 中有两个地方可以创建 network :1)Project -> Network -> Networks这是普通用户在自己的 tenant 中创建 network 的地方。2)Admin -> Networks 这是 admin 创建 network 的地方。我们先用第一种方式创建,点击 “Create Network” 按钮:
3)在创建网络的向导页面给 network 命名为 “first_local_net”,点击 “Next”:
4)创建 subnet,命名为 “subnet_172_16_1_0”,地址为 “172.16.1.0/24”,点击 “Next”。如果 Gateway IP 不设置,默认为 subnet 的第一个 IP,即 172.16.1.1:
5)设置 subnet 的 IP 地址范围为 172.16.1.2-172.16.1.100,instance 的 IP 会从这里分配,点击 “Create”。默认会“Enable DHCP”,同时还可以设置 subnet 的 DNS 和添加静态路由条目。
6)network 创建成功:
7)点击 “first_local_net” 链接,显示 network 的 subnet 和 port 信息:
8)在 Ports 列表中已经创建了一个 port,名称为 “(a5bd3746-3f89)”,IP 为 172.16.1.2, Attached Device 是 network:dhcp。打开控制节点的 shell 终端,用 brctl show 查看当前 linux bridge 的状态:
9)可以看到 Neutron 自动创建了如下两个设备:
brqbb9b6d21-c6 对应 local network “first_local_net”,命名规则为 brqXXX,XXX 为 network ID 的前 11 个字符。tapa5bd3746-3f 对应 port (a5bd3746-3f89),命名规则为 tapYYY, YYY 为 port ID 的前 11 个字符。该 tap 设备已经连接到 bridge,即连接到该 local 网络。 - 将 instance 连接到 first_local_net:
1)launch 一个 instance,在“Networking”标签页面选择 first_local_net 网络:
2)instance 部署成功,分配的 IP 地址为 172.16.1.3:
3)对于 instance “cirros-vm1”,Neutron 会在 subnet 中创建一个 port,分配 IP 和 MAC 地址,并将 port 分配给 cirros-vm1:
4)如上图所示,port 列表中增加了一个 port “(fa7e090e-a29c)”,IP 为 172.16.1.3。点击 port 名称查看 MAC 信息:
5)当 cirros-vm1 启动时,宿主机上的 neutron-linuxbridge-agent 会根据 port 信息创建 tap 设备,并连接到 local 网络所在的 bridge;同时该 tap 会映射成 cirros-vm1 的虚拟网卡,即 virtual interface (VIF)。cirros-vm1 部署到了控制节点,通过 brctl show 查看 bridge 的配置:
6)可以看到 bridge brqbb9b6d21-c6 上连接了一个新的 tap 设备 tapfa7e090e-a2 从命名上可知 tapfa7e090e-a2 对应着 port “(fa7e090e-a29c)”。virsh list 中显示的虚拟机 instance-00000001 即为 “cirros-vm1”,命名方式有所不同,需注意。通过 virsh edit 命令查看 cirros-vm1 的配置,确认 VIF 就是 tapfa7e090e-a2:
7)另外,VIF 的 MAC 地址为 fa:16:3e:c1:66:a5,这个数据就是从 port “(fa7e090e-a29c)” 取过来的。在 cirros-vm1 中执行 ifconfig,通过 MAC 地址可以确认 eth0 与 tapfa7e090e-a2 对应。
8)下图展示了创建 cirros-vm1 后宿主机当前的网络结构 :
- 连接第二个 Insance 到 First_local_net:
1)以同样的方式 launch instance “cirros-vm2”分配的 IP 为 172.16.1.4:
2)cirros-vm2 也被 schedule 到控制节点。virsh list
和brctl show
输出如下 cirros-vm2 对于的 tap 设备为 tapa5bd3746-3f。
3)在 cirros-vm2 的控制台运行 ifconfig,instance 已经拿到了 DCHP 的 IP 地址:
4)能够 Ping 通 cirros-vm1 的 IP 地址 172.16.1.3:
5)前宿主机的网络结构如下 :
6)两个 instance 的 VIF 挂在同一个 linux bridge 上,可以相互通信。这里请大家思考一个问题:如果 cirros-vm2 launch 时被 schedule 到计算节点而非控制节点,它能获得 DHCP 的 IP 吗?答案:不能。 因为 DHCP agent 在控制节点上运行,cirros-vm2 在计算节点的 local 网络上,两者位于不同物理节点。由于 local 网络的流量只能局限在本节点之内,发送的 DHCP 请求无法到达控制节点。 - 创建第二个 Local Network:
1)GUI 中有两个地方可以创建 network:Project -> Network -> Networks这是普通用户在自己的 tenant 中创建 network 的地方、Admin -> Networks 这是 admin 创建 network 的地方。前面我们已经用第一种方式创建了 “first_local_net”。本节将以第二种方式创建 local network “second_local_net” 。菜单路径为 Admin -> Networks,此菜单只有 admin 用户才能够访问,点击 “Create Network” 按钮:
2)显示创建页面,点击 “Create Network”,second_local_net 创建成功:
3)可以看到几个与普通用户创建 network 不同的地方:
a)可以选择该 network 属于哪个 Project(租户)。
b)可以选择 network type。
c)可以指定 network 是否与其他 Project 共享。
d)可以指定是否为 external network。
可见,这种方式赋予 admin 用户创建 network 更大的灵活性,后面我们都将采用这种方式创建 network。
4)点击 second_local_net 链接,进入 network 配置页面:
5)目前还没有 subnet,点击 “Create Subnet” 按钮:
6)设置 IP 地址为 “172.16.1.0/24”,点击 “Next”:
7)设置 IP 地址范围为 172.16.1.101-172.16.1.200,点击 “Create”:
8)subnet 创建成功:
9)查看控制节点的网络结构。增加了 second_local_net 对应的网桥 brq161e0b25-58,以及 dhcp 的 tap 设备 tapae547b6b-2a:
- 将 Instance 连接到 Second_local_net:
1)launch 新的 instance “cirros-vm3”,网络选择 second_local_net:
2)cirros-vm3 分配到的 IP 为 172.16.1.102:
3)cirros-vm3 被 schedule 到控制节点,对应的 tap 设备为 tap5395d19b-ed:
4)控制台显示 cirros-vm3 已经成功从 DHCP 拿到 IP 地址 172.16.1.102:
5)但是 cirros-vm3 无法 Ping 到 cirros-vm1:
6)这是在预料之中的,因为 cirros-vm3 和 cirros-vm1 位于不同的 local network,之间没有连通,即使都位于同一个宿主机也不能通信。网络结构如下:
小结:位于同一 local network 的 instance 可以通信; 位于不同 local network 的 instance 无法通信;一个 local network 只能位于一个物理节点,无法跨节点。
2.5.5.4 flat network
- flat network 是不带 tag 的网络,要求宿主机的物理网卡直接与 linux bridge 连接,这意味着:每个 flat network 都会独占一个物理网卡。
- 下图中 eth1 桥接到 brqXXX,为 instance 提供 flat 网络。
- 如果需要创建多个 flat network,就得准备多个物理网卡,如下图所示:
- Flat Network 原理与配置:
1)在 ML2 配置中 enable flat network,在 /etc/neutron/plugins/ml2/ml2_conf.ini 设置 flat network 相关参数:
指定普通用户创建的网络类型为 flat。 需要注意的是:因为 flat 网络与物理网卡一一对应,一般情况下租户网络不会采用 flat,这里只是示例。
2)接着需要指明 flat 网络与物理网卡的对应关系:
如上所示:a)在 [ml2_type_flat] 中通过 flat_networks 定义了一个 flat 网络,label 为 “default”。b)在 [linux_bridge] 中通过 physical_interface_mappings 指明 default 对应的物理网卡为 eth1。
3)理解 label 与 ethX 的关系:label 是 flat 网络的标识,在创建 flat 时需要指定 label(后面演示)。label 的名字可以是任意字符串,只要确保各个节点 ml2_conf.ini 中的 label 命名一致就可以了。各个节点中 label 与物理网卡的对应关系可能不一样。这是因为每个节点可以使用不同的物理网卡将 instance 连接到 flat network。
例如对于 label 为 “default” 的 flat network,节点 A 可能使用 eth1,配置为:
而节点 B 则可能使用 eth2,配置为:
4)支持多个 flat :如果要创建多个 flat 网络,需要定义多个 label,用逗号隔开,当然也需要用到多个物理网卡,如下所示:
- 创建 Flat Network:
1)打开菜单 Admin -> Networks,点击 “Create Network” 按钮:
2)显示创建页面, 点击 “Create Network”,flat_net 创建成功。Provider Network Type 选择 “Flat”。Physical Network 填写 “default”,与 ml2_conf.ini 中 flat_networks 参数保持一致:
3)点击 flat_net 链接,进入 network 配置页面:
4)目前还没有 subnet,点击 “Create Subnet” 按钮:
5)设置 IP 地址为 “172.16.1.0/24”,点击 “Next”:
6)设置 IP 地址范围为 172.16.1.101-172.16.1.200,点击 “Create”:
7)subnet 创建成功:
8)底层网络发生了什么变化 。执行 brctl show,查看控制节点当前的网络结构:
Neutron 自动新建了 flat_net 对应的网桥 brqf153b42f-c3,以及 dhcp 的 tap 设备 tap19a0ed3d-fe。另外,tap19a0ed3d-fe 和物理网卡 eth1 都已经连接到 bridge。此时 flat_net 结构如图所示:
- 将 Instance 连接到 Flat_net:
1)launch 新的 instance “cirros-vm1”,选择网络 falt_net:
2)cirros-vm1 分配到的 IP 为 172.16.1.103:
3)cirros-vm1 被 schedule 到控制节点,对应的 tap 设备为 tapc1875c7f-cb,并且已经连接到 bridge :
4)当前 flat_net 的结构如下:
5)继续用同样的方式 launch instance cirros-vm2,分配到的 IP 为 172.16.1.104:
6)cirros-vm2 被 schedule 到计算节点,对应的 tap 设备为 tapfb3fb197-24,并且连接到 bridge:
这里有两点需要提醒:因为计算节点上没有 dhcp 服务,所以 brctl show 中没有 dhcp 对应的 tap 设备。计算节点上 bridge 的名称与控制节点上一致,都是 brqf153b42f-c3,表明是同一个 network。
7)当前 flat_net 的结构如下:
8)cirros-vm1(172.16.1.103) 与 cirros-vm2(172.16.1.104) 位于不同节点,通过 flat_net 相连,下面执行 PING 验证连通性。 在 cirros-vm1 控制台中执行 ping 172.16.1.104:
2.5.5.5 DHCP服务
- Neutron 提供 DHCP 服务的组件是 DHCP agent。 DHCP agent 在网络节点运行上,默认通过 dnsmasq 实现 DHCP 功能。
- 配置 DHCP agent:
1)DHCP agent 的配置文件位于 /etc/neutron/dhcp_agent.ini:
dhcp_driver :使用 dnsmasq 实现 DHCP。
interface_driver :使用 linux bridge 连接 DHCP namespace interface。
2)当创建 network 并在 subnet 上 enable DHCP:
网络节点上的 DHCP agent 会启动一个dnsmasq 进程为该network 提供 DHCP 服务,dnsmasq 是一个提供 DHCP 和 DNS 服务的开源软件。dnsmasq 与 network 是一对一关系,一个 dnsmasq 进程可以为同一 netowrk 中所有 enable 了 DHCP 的 subnet 提供服务。
3)回到我们的实验环境,之前创建了 flat_net,并且在 subnet 上启用了 DHCP,执行 ps 查看 dnsmasq 进程,如下图所示:
DHCP agent 会为每个 network 创建一个目录 /opt/stack/data/neutron/dhcp/,用于存放该 network 的 dnsmasq 配置文件。
4)讨论 dnsmasq 重要的启动参数:
--dhcp-hostsfile
:存放 DHCP host 信息的文件,这里的 host 在我们这里实际上就是 instance。dnsmasq 从该文件获取 host 的 IP 与 MAC 的对应关系。每个 host 对应一个条目,信息来源于 Neutron 数据库。对于 flat_net,hostsfile 是 /opt/stack/data/neutron/dhcp/f153b42f-c3a1-4b6c-8865-c09b5b2aa274/host,记录了 DHCP,cirros-vm1 和 cirros-vm2 的 interface 信息。
--interface
:指定提供 DHCP 服务的 interface。dnsmasq 会在该 interface 上监听 instance 的 DHCP 请求。对于 flat_net,interface 是 ns-19a0ed3d-fe。或许大家还记得,之前我们看到的 DHCP interface 叫 tap19a0ed3d-fe(如下图所示),并非 ns-19a0ed3d-fe。
从名称上看,ns-19a0ed3d-fe 和 tap19a0ed3d-fe 应该存在某种联系,但那是什么呢?要回答这个问题,需要先搞懂一个概念:Linux Network Namespace,我们下一节详细讨论。 - 用 Namspace 隔离 DHCP 服务:
1)Neutron 通过 dnsmasq 提供 DHCP 服务,而 dnsmasq 如何独立的为每个 network 服务呢?
2)答案是通过 Linux Network Namespace 隔离。在二层网络上,VLAN 可以将一个物理交换机分割成几个独立的虚拟交换机。 类似地,在三层网络上,Linux network namespace 可以将一个物理三层网络分割成几个独立的虚拟三层网络。
3)每个 namespace 都有自己独立的网络栈,包括 route table,firewall rule,network interface device 等。
4)Neutron 通过 namespace 为每个 network 提供独立的 DHCP 和路由服务,从而允许租户创建重叠的网络。如果没有 namespace,网络就不能重叠,这样就失去了很多灵活性。 - 每个 dnsmasq 进程都位于独立的 namespace,命名为
qdhcp-<network id>
,例如 flat_net,我们有:
1)ip netns list
命令列出所有的 namespace。
qdhcp-f153b42f-c3a1-4b6c-8865-c09b5b2aa274 就是 flat_net 的 namespace。
2)其实,宿主机本身也有一个 namespace,叫 root namespace,拥有所有物理和虚拟 interface device。物理interface 只能位于 root namespace。新创建的 namespace 默认只有一个 loopback device。管理员可以将虚拟 interface,例如 bridge,tap 等设备添加到某个 namespace。对于 flat_net 的 DHCP 设备 tap19a0ed3d-fe,需要将其放到 namespace qdhcp-f153b42f-c3a1-4b6c-8865-c09b5b2aa274 中,但这样会带来一个问题: tap19a0ed3d-fe 将无法直接与 root namespace 中的 bridge 设备 brqf153b42f-c3 连接。 - Neutron 使用 veth pair 解决了这个问题:
1)veth pair 是一种成对出现的特殊网络设备,它们象一根虚拟的网线,可用于连接两个 namespace。向 veth pair 一端输入数据,在另一端就能读到此数据。
2)tap19a0ed3d-fe 与 ns-19a0ed3d-fe 就是一对 veth pair,它们将 qdhcp-f153b42f-c3a1-4b6c-8865-c09b5b2aa274 连接到 brqf153b42f-c3。
- 可以通过
ip netns exec <network namespace name> <command>
管理 namespace:
1)例如查看 ns-19a0ed3d-fe 的配置:
- 分析instance如何从dnsmasq获取IP:
1)在创建 instance 时,Neutron 会为其分配一个 port,里面包含了 MAC 和 IP 地址信息,这些信息会同步更新到 dnsmasq 的 host 文件。如下图所示:
2)同时 nova-compute 会设置 cirros-vm1 VIF 的 MAC 地址:
3)一切准备就绪,instance 获取 IP 的过程如下:
a)cirros-vm1 开机启动,发出 DHCPDISCOVER 广播,该广播消息在整个 flat_net 中都可以被收到。
b)广播到达 veth tap19a0ed3d-fe,然后传送给 veth pair 的另一端 ns-19a0ed3d-fe。dnsmasq 在它上面监听,dnsmasq 检查其 host 文件,发现有对应项,于是dnsmasq 以 DHCPOFFER 消息将 IP(172.16.1.103)、子网掩码(255.255.255.0)、地址租用期限等信息发送给 cirros-vm1。
c)cirros-vm1 发送 DHCPREQUEST 消息确认接受此 DHCPOFFER。
d)dnsmasq 发送确认消息 DHCPACK,整个过程结束。 - 这个过程我们可以在 dnsmasq 日志中查看,dnsmasq 默认将日志记录到 /var/log/syslog:
2.5.5.6 vlan network
- 下图是 vlan100 网络的示例,三个 instance 通过 TAP 设备连接到名为 “brqXXXX” linux bridge。在物理网卡 eth1 上创建了 eth1.100 的 vlan interface,eth1.100 连接到 brqXXXX。instance 通过 eth1.100 发送到 eth1 的数据包就会打上 vlan100 的 tag。
- 如果再创建一个 network vlan101,eth1 上会相应的创建 vlan interface eth1.101,并且连接的新的 lingux bridge “brqYYYY”。
每个 vlan network 有自己的 bridge,从而也就实现了基于 vlan 的隔离。
- 这里有一点要特别提醒:因为物理网卡 eth1 上面可以走多个 vlan 的数据,那么物理交换机上与 eth1 相连的的 port 要设置成 trunk 模式,而不是 access 模式。
- 在 ML2 中配置 Vlan Network:
1)首先在 /etc/neutron/plugins/ml2/ml2_conf.ini 中设置 vlan network 相关参数,指定普通用户创建的网络类型为 vlan:
2)然后指定 vlan 的范围:
上面配置定义了 label 为 “default” 的 vlan network,vlan id 的范围是 3001 – 4000。这个范围是针对普通用户在自己的租户里创建 network 的范围。因为普通用户创建 network 时并不能指定 vlan id,Neutron 会按顺序自动从这个范围中取值。对于 admin 则没有 vlan id 的限制,admin 可以创建 id 范围为 1-4094 的 vlan network。
3)接着需要指明 vlan network 与物理网卡的对应关系:
如上所示: 在 [ml2_type_vlan] 中定义了 lable “default”,[linux_bridge] 中则指明 default 对应的物理网卡为 eth1。这里 label 的作用与前面 flat network 中的 label 一样,只是一个标识,可以是任何字符串。配置完成,重启 Neutron 服务后生效。 - 创建第一个 Vlan Network Vlan100:
1)打开菜单 Admin -> Networks,点击 “Create Network” 按钮:
2)显示创建页面,点击 “Create Network”,vlan100 创建成功:
Provider Network Type 选择 “VLAN”。Physical Network 填写 “default”,必须与 ml2_conf.ini中network_vlan_ranges 参数值保持一致。Segmentation ID 即 VLAN ID,设置为 100。
3)点击 vlan100 链接,进入 network 配置页面:
4)目前还没有 subnet,点击 “Create Subnet” 按钮:
5)subnet_172_16_100_0,IP 地址为 172.16.100.0/24:
6)底层网络发生了什么变化:
在控制节点上执行 brctl show,查看当前网络结构:
Neutron 自动新建了三个设备:- vlan100 对应的网桥 brq3fcfdb98-9d。
- vlan interface eth1.100。
- dhcp 的 tap 设备 tap1180bbe8-06。
eth1.100 和 tap19a0ed3d-fe 已经连接到了 brq3fcfdb98-9d,VLAN 100 的二层网络就绪
7)此时 vlan100 结构如图所示:
- 将instance连接到vlan100:
1)launch 新的 instance “cirros-vm1”,网络选择 vlan100:
2)cirros-vm1 分配到的 IP 为 172.16.100.3:
3)cirros-vm1 被 schedule 到控制节点,对应的 tap 设备为 tapc1875c7f-cb,并且连接到 bridge:
4)当前 vlan100 的结构如下 :
5)继续用同样的方式 launch instance cirros-vm2,分配到的 IP 为 172.16.100.104:
6)cirros-vm2 被 schedule 到计算节点,对应的 tap 设备为 tap238437b8-50,并且连接到 bridge:
因为计算节点上没有 hdcp 服务,所以没有相应的 tap 设备。 另外,bridge 的名称与控制节点上一致,都是 brq3fcfdb98-9d,表明是同一个 network。
7)当前 vlan100 的结构如下:
8)cirros-vm1(172.16.100.3) 与 cirros-vm2(172.16.100.4) 位于不同节点,通过 vlan100 相连,下面执行 PING 验证连通性。 在 cirros-vm1 控制台中执行 ping 172.16.100.4:
- 创建第二个 Vlan Network Vlan101:
1)创建第二个 vlan network vlan101:
2)subnet IP 地址为 172.16.101.0/24:
3) 底层网络发生了什么变化:
Neutron 自动创建了 vlan101 对应的网桥 brq1d7040b8-01,vlan interface eth1.101,以及 dhcp 的 tap 设备 tap5b1a2247-32。eth1.101 和 tap5b1a2247-32 已经连接到 brq1d7040b8-01,VLAN 101 的二层网络就绪。
4)网络结构如下:
- 连接 Insance 到 Vlan101:
1)launch 新的 instance “cirros-vm3”,网络选择 vlan101:
2)cirros-vm3 分配到的 IP 为 172.16.101.103:
3)cirros-vm3 被 schedule 到计算节点,对应的 tap 设备为 tapadb5cc6a-7a,并且连接到 bridge:
4)当前网络结构如下:
cirros-vm1 位于控制节点,属于 vlan100。
cirros-vm2 位于计算节点,属于 vlan100。
cirros-vm3 位于计算节点,属于 vlan101。
cirros-vm1 与 cirros-vm2 都在 vlan100,它们之间能通信。
cirros-vm3 在 vlan101,不能与 cirros-vm1 和 cirros-vm2 通信。
那怎么样才能让 vlan100 与 vlan101 中的 instance 通信呢?单靠二层 vlan 是不行的,还得在三层通过路由器转发。
2.5.5.7 Routing
-
Routing 功能概述:
1)路由服务(Routing)提供跨 subnet 互联互通功能。例如前面我们搭建了实验环境:
cirros-vm1 172.16.100.3 vlan100
cirros-vm3 172.16.101.3 vlan101
这两个 instance 要通信必须借助 router。 可以是物理 router 或者虚拟 router。
2)物理路由:
接入的物理 router 有两个 interface ip:
172.16.100.1 对应 vlan100 的网关。
172.16.101.1 对应 vlan101 的网关。
当 cirros-vm1 要跟 cirros-vm3 通信时,数据包的流向是这样的:a)因为 cirros-vm1 的默认网关指向 172.16.100.1,cirros-vm1 发送到 cirros-vm3 的数据包首先通过 vlan100 的 interface 进入物理 router。b)router 发现目的地址 172.16.101.3 与 172.16.101.1 为同一个 vlan,则从 vlan101 的 interface 发出。c)数据包经过 brq1d7040b8-01 最终到达 cirros-vm3。
3)虚拟路由:
虚拟 router 的路由机制与物理 router 一样,只是由软件实现。Neutron 两种方案都支持。如果要使用虚拟 router,需要启用 L3 agent。L3 agent 会在控制节点或者网络节点上运行虚拟 router,为 subnet 提供路由服务。 -
配置 L3 Agent:
1)Neutron 的路由服务是由 l3 agent 提供的,除此之外,l3 agent 通过 iptables 提供 firewall 和 floating ip 服务。
2)l3 agent 需要正确配置才能工作,配置文件为 /etc/neutron/l3_agent.ini,位于控制节点或网络节点上:
interface_driver 是最重要的选项,如果 mechanism driver 是 linux bridge,则:
interface_driver = neutron.agent.linux.interface.BridgeInterfaceDriver
;如果选用 open vswitch,则:interface_driver = neutron.agent.linux.interface.OVSInterfaceDriver
。
3)l3 agent 运行在控制或网络节点上:
可以看到 l3 agnet 已经正常启动。 -
创建 Router 连通 Subnet:
1)创建虚拟路由器“router_100_101”,打通 vlan100 和 vlan101。打开操作菜单 Project -> Network -> Routers:
2)点击 “Create Router” 按钮:
3)router 命名为 “router_100_101”,点击 “Create Router” 按钮确认:
4)router_100_101 创建成功:
5)接下来需要将 vlan100 和 vlan101 连接到 router_100_101 。点击 “router_100_101” 链接进入 router 的配置页面,在 “Interfaces” 标签中点击 “Add Interface” 按钮
6)选择 vlan101 的 subnet_172_16_101_0,点击 “Add Interface” 确认:
7)用同样的方法添加 vlan100 的 subnet_172_16_100_0:
完成后,可以看到 router_100_101 有了两个 interface,其 IP 正好是 subnet 的 Gateway IP 172.16.100.1 和 172.16.101.1。
8)router_100_101 已经连接了 subnet_172_16_100_0 和 subnet_172_16_101_0。router_100_101 上已经设置好了两个 subnet 的 Gateway IP。cirros-vm1 和 cirros-vm3 应该可以通信了。通过 PING 测试一下:
9)查看 cirros-vm1 的路由表,默认网关为 172.16.100.1。同时 traceroute 告诉我们,cirros-vm1 确实是通过 router_100_101 访问到 cirros-vm3 的:
-
虚拟 Router 原理分析:
1)首先我们查看控制节点的 linux bridge 结构发生了什么变化:
vlan101 的 bridge 上多了一个 tape17162c5-00,从命名上可以推断该 TAP 设备对应 router_100_101 的 interface (e17162c5-00fa)。vlan100 的 bridge 上多了一个 tapd568ba1a-74,从命名上可以推断该 TAP 设备对应 router_100_101 的 interface (d568ba1a-740e)。
2)当前网络结构如图所示:
3)两个 TAP 设备上并没有配置相应的 Gateway IP:
4)如果没有 Gateway IP,router_100_101 是如何完成路由的呢?
答案是:l3 agent 会为每个 router 创建了一个 namespace,通过 veth pair 与 TAP 相连,然后将 Gateway IP 配置在位于 namespace 里面的 veth interface 上,这样就能提供路由了。
5)通过ip netns
查看 namespace:router 对应的 namespace 命名为qrouter-<router id>
。
6)通过ip netns exec <namespace name> ip a
命令查看 router_100_101 namespace 中的 veth interface 配置:
namespace 中有两个 interface:qr-e17162c5-00 上设置了 Gateway IP 172.16.101.1,与 root namespace 中的 tape17162c5-00 组成 veth pair。qr-d568ba1a-74 上设置了 Gateway IP 172.16.100.1,与 root namespace 中的 tapd568ba1a-74 组成 veth pair。
7)网络结构如图所示:
8)namespace 中的路由表也保证了 subnet_172_16_100_0 和 subnet_172_16_101_0 之间是可以路由的,命令为ip nets exec <namespace name> IP routing table
:
-
为什么要使用Namespace:
1)为什么不直接在 tape17162c5-00 和 tapd568ba1a-74 上配置 Gateway IP,而是引入一个 namespace,在 namespace 里面配置 Gateway IP 呢?首先考虑另外一个问题:如果不用 namespace,直接 Gareway IP 配置到 tape17162c5-00 和 tapd568ba1a-74 上,能不能连通 subnet_172_16_100_0 和 subnet_172_16_101_0 呢?
答案是可以的,只要控制节点上配置了类似下面的路由:
既然不需要 namespace 也可以路由,为什么还要加一层 namespace 增加复杂性呢?其根本原因是:增加namespace是为了支持网络重叠。 云环境下,租户可以按照自己的规划创建网络,不同租户的网络是可能重叠的。 将路由功能放到 namespace 中,就能隔离不同租户的网络,从而支持网络重叠。
2)通过例子进一步解释:A,B 两个租户定义了完全相同的两个 subnet,网络完全重叠。如下图所示:
3)不使用namespace的场景:
其特征是网关 IP 配置在 TAP interface 上。因为没有 namespace 隔离,router_100_101 和 router_102_103 的路由条目都只能记录到控制节点操作系统(root namespace)的路由表中,内容如下:
这样的路由表是无法工作的。按照路由表优先匹配原则,Tenant B 的数据包总是错误地被 Tenant A 的 router 路由。例如 vlan102 上有数据包要发到 vlan103。选择路由时,会匹配路由表的第二个条目,结果数据被错误地发到了 vlan101。
4) 使用 namespace 的场景:如果使用 namespace,网络结构如下:
其特征是网关 IP 配置在 namespace 中的 veth interface 上。每个 namespace 拥有自己的路由表。router_100_101 的路由表内容如下:
router_102_103 的路由表内容如下:
这样的路由表是可以工作的。例如 vlan102 上有数据包要发到 vlan103。选择路由时,会查看 router_102_103 的路由表, 匹配第二个条目,数据通过 qr-4 被正确地发送到 vlan103。同样当 vlan100 上有数据包要发到 vlan101时,会匹配 router_100_101 路由表的第二个条目,数据通过 qr-2 被正确地发送到 vlan101。可见,namespace 使得每个 router 有自己的路由表,而且不会与其他 router 冲突,所以能很好地支持网络重叠。 -
instance访问外网 ML2 的配置:
1)这里的外部网络是指的租户网络以外的网络。租户网络是由 Neutron 创建和维护的网络。 外部网络不由 Neutron 创建。如果是私有云,外部网络通常指的是公司 intranet;如果是公有云,外部网络通常指的是 internet。具体到我们的实验网络环境: 计算节点和控制节点 eth1 提供的是租户网络,IP 段租户可以自由设置。 控制节点 eth2 连接的就是外部网络,IP 网段为 10.10.10.0/24。如下图所示:
2)配置准备:为了连接外部网络,需要在配置文件中告诉 Neutron 外部网络的类型以及对应的物理网卡
因为外部网络是已经存在的物理网络,一般都是 flat 或者 vlan 类型。这里我们将外部网络的 label 命名为 “external”。
如果类型为 flat,控制节点 /etc/neutron/plugins/ml2/ml2_conf.ini 配置如下:
如果类型为 vlan,配置如下
修改配置后,需要重启 neutron 的相关服务。在我们的网络环境中,外部网络是 flat 类型。 -
创建外网 Ext_net:
1)进入 Admin -> Networks 菜单,点击 “Create Network” 按钮:
2)显示创建页面,点击 “Create Network”,ext_net 创建成功,其中Provider Network Type 选择 “Flat”;Physical Network 填写 “external”,与 ml2_conf.ini 中 flat_networks 参数的设置保持一致;勾选 External Network 选择框。
3)点击 ext_net 链接,进入 network 配置页面:
4)目前还没有 subnet,点击 “Create Subnet” 按钮:
5)创建 subnet_10_10_10_0,IP 地址为 10.10.10.0/24,点击 “Next”。这里 Gateway 我们使用默认地址 10.10.10.1。
通常我们需要询问网络管理员外网 subnet 的 Gateway IP,然后填在这里:
6)因为我们不会直接为 instance 分配外网 IP,所以一般不需要 enable DHCP,点击 “Create”:
7)subnet 创建成功,网关为 10.10.10.1:
8)查看控制节点网络结构的变化,执行 brctl show:
增加了一个网桥 brqe496d3d2-53,物理网卡 eth2 已经连接到该 bridge。 -
外网访问原理分析:
1)将外网连接到 Neutron 的虚拟路由器,这样 instance 才能访问外网 。点击菜单 Project -> Network -> Routers 进入 router 列表,点击 router_100_101 的 “Set Gateway” 按钮:
2) “External Network” 下拉列表中选择 ext_net,点击 “Set Gateway” :
3)外网设置成功:
4) 我们需要看看 router 发生了什么变化 。点击 “router_100_101” 链接,打开 “Interfaces” 标签页:router 多了一个新的 interface,IP 为 10.10.10.2。 该 interface 用于连接外网 ext_net。
5)查看控制节点的网络结构,外网 bridge 上已经连接了 router 的 tap 设备 tapb8b32a88-03:
6)ip netns list
命令列出所有的 namespace。在 router 的 namespace 中查看 tapb8b32a88-03 的 veth pair 设备:
该 veth pair 命名为 qg-b8b32a88-03,上面配置了 IP 10.10.10.2。router 的每个 interface 在 namespace 中都有对应的 veth。如果 veth 用于连接租户网络,命名格式为 qr-xxx,比如 qr-d568ba1a-74 和 qr-e17162c5-00。如果 veth 用于连接外部网络,命名格式为 qg-xxx,比如 qg-b8b32a88-03。
7)查看 router 的路由表信息:
可以看到默认网关为 10.10.10.1。 意味着对于访问 vlan100 和 vlan101 租户网络以外的所有流量,router_100_101 都将转发给 ext_net 的网关 10.10.10.1。
8)现在 router_100_101 已经同时连接了 vlan100, vlan101 和 ext_net 三个网络,如下图所示:
9)下面我们在 cirros-vm3 上测试一下:irros-vm3 位于计算节点,现在已经可以 Ping 到 ext_net 网关 10.10.10.1 了。
10)通过 traceroute 查看一下 cirros-vm3 到 10.10.10.1 的路径 :
数据包经过两跳到达 10.10.10.1 网关。数据包首先发送到 router_100_101 连接 vlan101 的 interface(172.16.101.1)。然后通过连接 ext_net 的 interface(10.10.10.2) 转发出去,最后到达 10.10.10.1。
当数据包从 router 连接外网的接口 qg-b8b32a88-03 发出的时候,会做一次 Source NAT,即将包的源地址修改为 router 的接口地址 10.10.10.2,这样就能够保证目的端能够将应答的包发回给 router,然后再转发回源端 instance。
11)可以通过 iptables 命令查看 SNAT 的规则:
当 cirros-vm3(172.16.101.3) Ping 10.10.10.1 时,可用通过 tcpdump 分别观察 router 两个 interface 的icmp 数据包来验证 SNAT 的行为。
12)vlan101 interface qr-e17162c5-00 的 tcpdump 输出:
13)ext_net interface qg-b8b32a88-03 的 tcpdump 输出:
SNAT 让 instance 能够直接访问外网,但外网还不能直接访问 instance。因为 instance 没有外网 IP。这里 “直接访问 instance” 是指通信连接由外网发起,例如从外网 SSH cirros-vm3。 这个问题可以通过 floating IP 解决,下一节我们将讨论浮动 IP。 -
创建 Floating IP:
1)先复习一下前面我们讨论的知识:
当租户网络连接到 Neutron router,通常将 router 作为默认网关。当 router 接收到 instance 的数据包,并将其转发到外网时:a)router 会修改包的源地址为自己的外网地址,这样确保数据包转发到外网,并能够从外网返回。b)router 修改返回的数据包,并转发给真正的 instance。这个行为被称作 Source NAT。
如果需要从外网直接访问 instance,则可以利用 floating IP。下面是关于 floating IP 必须知道的事实:a)floating IP 提供静态 NAT 功能,建立外网 IP 与 instance 租户网络 IP 的一对一映射。b)floating IP 是配置在 router 提供网关的外网 interface 上的,而非 instance 中。c)router 会根据通信的方向修改数据包的源或者目的地址。
2)下面我们通过实验深入学习 floating IP 。点击 Project -> Compute -> Access & Security 菜单,打开 Floating IPs 标签页点击 “Allocate IP To Project” 按钮:
3)floating IP Pool 为 ext_net,点击 “Allocate IP” 按钮:
从 Pool 中成功分配了一个 IP 10.10.10.3。
4)下面我们将它分配给 cirror-vm3,点击 “Associate” 按钮:
5)在下拉列表中选择 cirror-vm3,点击 “Associate” 按钮:
6)分配成功,floating IP 10.10.10.3 已经对应到 cirros-vm3 的租户 IP 172.16.101.3:
7)执行ip netns exec <namespace> ip a
首先查看 router 的 interface 配置:
可以看到,floating IP 已经配置到 router 的外网 interface qg-b8b32a88-03 上。
8)执行ip netns exec <namespace> iptables -t nat -S
查看 router 的 NAT 规则:
iptables 增加了两条处理 floating IP 的规则: a)当 router 接收到从外网发来的包,如果目的地址是 floating IP 10.10.10.3,将目的地址修改为 cirros-vm3 的 IP 172.16.101.3。这样外网的包就能送达到 cirros-vm3。b)当 cirros-vm3 发送数据到外网,源地址 172.16.101.3 将被修改为 floating IP 10.10.10.3。
9)在我的实验环境中,10.10.10.1 是外网中的物理交换机,现在让它 PING cirros-vm3,能PING通:
10)ext_net interface qg-b8b32a88-03 的 tcpdump 输出:
可见,在外网接口 qg-b8b32a88-03 上,始终是通过 floating IP 10.10.10.3 与外网通信。
11)vlan101 interface qr-e17162c5-00 的 tcpdump 输出:
当数据转发到租户网络,地址已经变为 cirros-vm3 的租户 IP 172.16.101.3 了。
12)小结:
a)floating IP 能够让外网直接访问租户网络中的 instance。这是通过在 router 上应用 iptalbes 的 NAT 规则实现的。
b)floating IP 是配置在 router 的外网 interface 上的,而非 instance,这一点需要特别注意。
至此,我们已经完成了 Neutron L3 服务连接不同 subnet,访问外网,以及 floating IP 的学习。
2.5.5.8 vxlan network
- 前言: 除了前面讨论的 local, flat, vlan 这几类网络,OpenStack 还支持 vxlan 和 gre 这两种 overlay network。overlay network 是指建立在其他网络上的网络。 该网络中的节点可以看作通过虚拟(或逻辑)链路连接起来的。 overlay network 在底层可能由若干物理链路组成,但对于节点,不需要关心这些底层实现。例如 P2P 网络就是 overlay network,隧道也是。 vxlan 和 gre 都是基于隧道技术实现的,它们也都是 overlay network。目前 linux bridge 只支持 vxlan,不支持 gre;open vswitch 两者都支持。 vxlan 与 gre 实现非常类似,而且 vxlan 用得较多,所以本教程只介绍 vxlan。
- VXLAN 全称 Virtual eXtensible Local Area Network(虚拟扩展局域网),VXLAN 提供与 VLAN 相同的以太网二层服务,但是拥有更强的扩展性和灵活性。与 VLAN 相比,VXLAN 有下面几个优势:
1)支持更多的二层网段。 VLAN 使用 12-bit 标记 VLAN ID,最多支持 4094 个 VLAN,这对于大型云部署会成为瓶颈。VXLAN 的 ID (VNI 或者 VNID)则用 24-bit 标记,支持 16777216 个二层网段;
2)VXLAN 的数据包是封装到 UDP 通过三层传输和转发的;
3)避免物理交换机 MAC 表耗尽。 由于采用隧道机制, 交换机无需在 MAC 表中记录虚拟机的信息。 - VXLAN 封装和包格式:
1)VXLAN 是将二层建立在三层上的网络。 通过将二层数据封装到 UDP 的方式来扩展数据中心的二层网段数量;
2)VXLAN 是一种在现有物理网络设施中支持大规模多租户网络环境的解决方案。 VXLAN 的传输协议是 IP + UDP;
3)VXLAN 定义了一个 MAC-in-UDP 的封装格式。 在原始的 Layer 2 网络包前加上 VXLAN header,然后放到 UDP 和 IP 包中。 通过 MAC-in-UDP 封装,VXLAN 能够在 Layer 3 网络上建立起了一条 Layer 2 的隧道;
4)VXLAN 包的格式如下:
如上图所示,VXLAN 引入了 8-byte VXLAN header,其中 VNI 占 24-bit。VXLAN 和原始的 L2 frame 被封装到 UDP 包中。这 24-bit 的 VNI 用于标示不同的二层网段,能够支持 16777216 个 LAN。 - VXLAN Tunnel Endpoint
1)VXLAN 使用 VXLAN tunnel endpoint (VTEP) 设备处理 VXLAN 的封装和解封。每个 VTEP 有一个 IP interface,配置了一个 IP 地址。VTEP 使用该 IP 封装 Layer 2 frame,并通过该 IP interface 传输和接收封装后的 VXLAN 数据包。
2)下面是 VTEP 的示意图:
3)VXLAN 独立于底层的网络拓扑;反过来,两个 VTEP 之间的底层 IP 网络也独立于 VXLAN。 VXLAN 数据包是根据外层的 IP header 路由的,该 header 将两端的 VTEP IP 作为源和目标 IP。 - VXLAN包转发流程:
VXLAN 在 VTEP 间建立隧道,通过 Layer 3 网络传输封装后的 Layer 2 数据。数据传输过程如下:
1)Host-A 向 Host-B 发送数据时,Host-B 的 MAC 和 IP 作为数据包的目标 MAC 和 IP,Host-A 的 MAC 作为数据包的源 MAC 和 IP,然后通过 VTEP-1 将数据发送出去。
2)VTEP-1 从自己维护的映射表中找到 MAC-B 对应的 VTEP-2,然后执行 VXLAN 封装,加上 VXLAN头,UDP 头,以及外层 IP 和 MAC 头。此时的外层 IP 头,目标地址为 VTEP-2 的 IP,源地址为 VTEP-1 的IP。同时由于下一跳是 Router-1,所以外层 MAC 头中目标地址为 Router-1 的 MAC。
3)数据包从 VTEP-1 发送出去后,外部网络的路由器会依据外层 IP 头进行包路由,最后到达与 VTEP-2 连接的路由器 Router-2。
4)Router-2将数据包发送给 VTEP-2。VTEP-2 负责解封数据包,依次去掉外层 MAC 头,外层 IP 头,UDP 头 和 VXLAN 头。
5)VTEP-2 依据目标 MAC 地址将数据包发送给 Host-B。
上面的流程我们看到 VTEP 是 VXLAN 的最核心组件,负责数据的封装和解封。隧道也是建立在 VTEP 之间的,VTEP 负责数据的传送。 - 在 ML2 中配置 VXLAN:
1)在 /etc/neutron/plugins/ml2/ml2_conf.ini 设置 vxlan network 相关参数:
2)然后指定 vxlan 的范围:
上面的配置定义了 vxlan vni 的范围是 1001 – 2000。这个范围是针对普通用户在自己的租户里创建 vxlan network 的范围。因为普通用户创建 network 时并不能指定 vni,Neutron 会按顺序自动从这个范围中取值。对于 admin 则没有 vni 范围的限制,admin 可以创建 vni 范围为 1-16777216 的 vxlan network。
3)接着需要在 [VXLAN] 中配置 VTEP:
控制节点 devstack_controller 的 ml2_conf.ini 配置如下:
计算节点 devstack_compute01 的 ml2_conf.ini 配置如下:
local_ip 指定节点上用作 VTEP 的 IP 地址。devstack_controller 的 VTEP IP 是 166.66.16.10,网卡为 eth1。devstack_compute01 的 VTEP IP 是 166.66.16.11,网卡为 eth1。注意:作为准备工作,这两个 VTEP IP 需要提前配置到节点的 eht1 上,Neutron 并不会帮我们分配这个 IP。 - 创建 VXLAN:
1)打开菜单 Admin -> Networks,点击 “Create Network” 按钮
2)显示创建页面,Provider Network Type 选择 “VXLAN” Segmentation ID 即 VNI,设置为 100。点击 “Create Network”,vxlan100 创建成功:
3)点击 vxlan100 链接,进入 network 配置页面:
4)目前还没有 subnet,点击 “Create Subnet” 按钮:
5)创建 subnet_172_16_100_0,IP 地址为 172.16.100.0/24:
6)底层网络发生了什么变化 。在控制节点上执行brctl show
,查看当前的网络结构:
Neutron 创建了:
a. vxlan100 对应的网桥 brq1762d312-d4
b. vxlan interface vxlan-100
c. dhcp 的 tap 设备 tap4df76d0e-59
d. vxlan-100 和 tap4df76d0e-59 已经连接到 brq1762d312-d4,vxlan100 的二层网络就绪。
7)执行ip -d link show dev vxlan-100
查看 vxlan interface 的详细配置
可见,vxlan-100 的 VNI 是 100,对应的 VTEP 网络接口为 eth1。
8)此时 vxlan100 结构如图所示:
- 部署 Instance 到 VXLAN:
1)launch 新的 instance “cirros-vm1”,网络选择vxlan100:
2)cirros-vm1 分配到的 IP 为 172.16.100.3:
3)cirros-vm1 被 schedule 到控制节点,对应的 tap 设备为 tap099caa87-cd,并且连接到 bridge brq1762d312-d4:
4)当前 vxlan100 的结构如下:
5)继续用同样的方式 launch instance cirros-vm2,分配到的 IP 为 172.16.100.4:
6)cirros-vm2 被 schedule 到计算节点,对应的 tap 设备为 tap457cc048-aa,并且连接到 bridge brq1762d312-d4:
7)因为计算节点上没有 dhcp 服务,所以没有相应的 tap 设备。另外,bridge 的名称与控制节点上一致,都是 brq1762d312-d4,表明是同一个 network。
8)当前vxlan100的结构如下:
9)cirros-vm1(172.16.100.3) 与 cirros-vm2(172.16.100.4) 位于不同节点,通过 vxlan100 相连,下面执行 PING 验证连通性。 在 cirros-vm1 控制台中执行 ping 172.16.100.4:
如我们预料,ping 成功。对于多 vxlan 之间的 routing 以及 floating ip,实现方式与 vlan 非常类似。 - L2 Population 原理:
1)作用:L2 Population 是用来提高 VXLAN 网络 Scalability 的。通常我们说某个系统的 Scalability 好,其意思是:当系统的规模变大时,仍然能够高效地工作。
2)L2 Population 到底解决了怎样的 Scalability 问题,请看下图:
3)这是一个包含 5 个节点的 VXLAN 网络,每个节点上运行了若干 VM。现在假设 Host 1 上的 VM A 想与 Host 4 上的 VM G 通信。VM A 要做的第一步是获知 VM G 的 MAC 地址。于是 VM A 需要在整个 VXLAN 网络中广播 APR 报文:“VM G 的 MAC 地址是多少?”
4)如果 VXLAN 网络的节点很多,广播的成本会很大,这样 Scalability 就成问题了,幸好 L2 Population 出现了:L2 Population 的作用是在 VTEP 上提供 Porxy ARP 功能,使得 VTEP 能够预先获知 VXLAN 网络中如下信息:VM IP — MAC 对应关系、VM — VTEP 的对应关系:
5)当 VM A 需要与 VM G 通信时:a)Host 1 上的 VTEP 直接响应 VM A 的 APR 请求,告之 VM G 的 MAC 地址。b)因为 Host 1 上的 VTEP 知道 VM G 位于 Host 4,会将封装好的 VXLAN 数据包直接发送给 Host 4 的 VTEP。这样就解决了 MAC 地址学习和 APR 广播的问题,从而保证了 VXLAN 的 Scalability。
6)那么下一个关键问题是:VTEP 是如何提前获知 IP — MAC — VTEP 相关信息的呢?
答案是:a)Neutron 知道每一个 port 的状态和信息; port 保存了 IP,MAC 相关数据。b)instance 启动时,其 port 状态变化过程为:down -> build -> active。c)每当 port 状态发生变化时,Neutron 都会通过 RPC 消息通知各节点上的 Neutron agent,使得 VTEP 能够更新 VM 和 port 的相关信息。VTEP 可以根据这些信息判断出其他 Host 上都有哪些 VM,以及它们的 MAC 地址,这样就能直接与之通信,从而避免了不必要的隧道连接和广播。 - 配置 L2 Population:
1)目前 L2 Population 支持 VXLAN with Linux bridge 和 VXLAN/GRE with OVS;
2)在 /etc/neutron/plugins/ml2/ml2_conf.ini 设置 l2population mechanism driver:
3)同时在 [VXLAN] 中配置 enable L2 Population:
4)L2 Population 生效后,输入ip -d link show dev vxlan-100
命令,发现创建的 vxlan-100 会多一个 Proxy ARP 功能:
5)输入bridge fdb show dev vxlan-100
命令查看控制节点上的 forwarding database,可以看到 VTEP 保存了 cirros-vm2 的 port 信息:
6)cirros-vm2 的 MAC 为 fa:16:3e:1d:23:a3。 VTEP IP 为 166.66.16.11。当需要与 cirros-vm2 通信时,控制节点 VTEP 166.66.16.10 会将封装好的 VXLAN 数据包直接发送给计算节点的 VTEP 166.66.16.11。
7)输入bridge fdb show dev vxlan-100
命令查看计算节点上的 forwarding database:
8)fdb 中保存了 cirros-vm1 和 dhcp 的 port 信息。当需要与它们通信时,计算节点 VTEP 知道应该将数据包直接发送给控制节点的 VTEP。
2.5.5.9 Security Group
- Neutron 为 instance 提供了两种管理网络安全的方法:安全组(Security Group)和虚拟防火墙。安全组的原理是通过 iptables 对 instance 所在计算节点的网络流量进行过滤。虚拟防火墙则由 Neutron Firewall as a Service(FWaaS)高级服务提供。其底层也是使用 iptables,在 Neutron Router 上对网络包进行过滤。
- 默认安全组:每个 Project(租户)都有一个命名为 “default” 的默认安全组。
1)点击菜单 Project -> Compute -> Access & Security,查看 Security Group 列表:
2)点击Manage Rules
按钮,查看 “default” 安全组的规则:
3)“default” 安全组有四条规则,其作用是:允许所有外出(Egress)的流量,但禁止所有进入(Ingress)的流量。
4)当我们创建 instance 时,可以在 “Access & Security” 标签页中选择安全组。如果当前只有 “default” 这一个安全组,则会强制使用 “default” :
5)当前在 devstack-controller 上有 instance “cirros-vm1”:
6)在 devstack-controller 上执行iptables-save
命令查看相关规则。iptables 的规则较多,这里我们节选了 cirros-vm1 相关的规则。这些规则是 Neutron 根据安全组自动生成的。cirros-vm1 的 TAP interface 为 tap8bca5b86-23,可以看到:
7)iptables 的规则是应用在 Neutron port 上的,port 在这里是 cirros-vm1 的虚拟网卡 tap8bca5b86-23。ingress 规则集中定义在命名为 “neutron-linuxbri-i8bca5b86-2” 的 chain 中。egress 规则集中定义在命名为 “neutron-linuxbri-o8bca5b86-2” 的 chain 中。
8)我们通过 dhcp namespace 对 cirros-vm1 进行 ping 和 ssh 测试:
9)无法 ping 和 ssh cirros-vm1,可见当前的规则实现了 “default” 安全组,所有 ingress 流量都被禁止。 - 应用新的安全组:Neutron 默认的安全组规则会禁止掉所有从外面访问 instance 的流量。本节我们会修改安全组的配置,允许 ping 和 ssh instance。有两种方法可以达到这个目的:a)修改 “default” 安全组。b)为 cirros-vm1 添加新的安全组。
1)添加新的安全组,在安全组列表页面点击按钮:
2)为安全组命名并点击 “Create Security Group”:
3)新的安全组 “allow ping & ssh” 创建成功:
4)点击Manage Group
按钮,查看 “allow ping & ssh” 的规则:
5)系统默认定义了两条规则,运行所有的外出流量。点击Add Rule
按钮,添加允许 ping 的规则,“Rule” 选择 “All ICMP”,“Direction” 选择 “Ingress”,然后点击 “Add” 按钮:
6)同样的方式添加 ssh 规则:
7)在列表中查看添加成功的规则:
8)接下来设置 cirros-vm1,使用新的安全组 。进入 instance 列表页面,点击 cirros-vm1 下拉操作列表中的 “Edit Security Groups”:
9)可以看到 cirros-vm1 当前使用的安全组为 “default”,可选安全组为 “allow ping & ssh”。点击安全组 “allow ping & ssh” 后面的 “+” 按钮:
10)点击 “Save” 保存:
11)iptables 会立即更新,下面通过 vimdiff 查看 iptables 前后的变化:
12)“allow ping & ssh” 安全组引入了下面两条 iptables 规则。作用是运行 ingress 的 ssh 和 ping 流量:
13)测试一下,现在能够 ping 和 ssh cirros-vm1 了:
- 小结-安全组有以下特性:
1)通过宿主机上 iptables 规则控制进出 instance 的流量;
2)安全组作用在 instance 的 port 上;
3)安全组的规则都是 allow,不能定义 deny 的规则;
4)instance 可应用多个安全组叠加使用这些安全组中的规则。
2.5.5.10 Firewall as a Service
- 理解概念:Firewall as a Service(FWaaS)是 Neutron 的一个高级服务。用户可以用它来创建和管理防火墙,在 subnet 的边界上对 layer 3 和 layer 4 的流量进行过滤。传统网络中的防火墙一般放在网关上,用来控制子网之间的访问。FWaaS 的原理也一样,是在 Neutron 虚拟 router 上应用防火墙规则,控制进出租户网络的数据。
- FWaaS 有三个重要概念: Firewall、Policy 和 Rule:
1)Firewall:租户能够创建和管理的逻辑防火墙资源。 Firewall 必须关联某个 Policy,因此必须先创建 Policy。
2)Firewall Policy:Policy 是 Rule 的集合,Firewall 会按顺序应用 Policy 中的每一条 Rule。
3)Firewall Rule:Rule 是访问控制的规则,由源与目的子网 IP、源与目的端口、协议、allow 或 deny 动作组成。例如,我们可以创建一条 Rule,允许外部网络通过 ssh 访问租户网络中的 instance,端口为 22。 - 与 FWaaS 容易混淆的概念是安全组(Security Group):
1)安全组的应用对象是虚拟网卡,由 L2 Agent 实现,比如 neutron_openvswitch_agent 和neutron_linuxbridge_agent;
2)安全组会在计算节点上通过 iptables 规则来控制进出 instance 虚拟网卡的流量。也就是说:安全组保护的是 instance;
3)FWaaS 的应用对象是 router,可以在安全组之前控制外部过来的流量,但是对于同一个 subnet 内的流量不作限制。也就是说:FWaaS 保护的是 subnet。所以,可以同时部署 FWaaS 和安全组实现双重防护。 - 启用 FWaaS:
1)因为 FWaaS 是在 router 中实现的,所以 FWaaS 没有单独的 agent。已有的 L3 agent 负责提供所有 FWaaS 功能。要启用 FWaaS,必须在 Neutron 的相关配置文件中做些设置。
2)配置 firewall driver。Neutron 在 /etc/neutron/fwaas_driver.ini 文件中设置 FWaaS 使用的 driver:
这里 driver 为 iptables。如果以后支持更多的 driver,可以在这里替换。
3)配置 Neutron。在 Neutron 配置文件 /etc/neutron/neutron.conf 中启用 FWaaS plugin:
- 进行实践,默认规则:
1)环境:在我们的实验环境中,有两个 instance: cirros-vm1(172.16.100.3) 和 cirros-vm2(172.16.101.3)
2)cirros-vm1 和 cirros-vm2 分别位于网络 vlan100 和 vlan101。vlan100 和 vlan101 之间由虚拟路由器 test_router 连接。
3)网络拓扑如下所示:
4)在 test_router 没有应用任何 FWaaS 的情况下,cirros-vm1 可以通过 ping 和 ssh 跨网络访问 cirros-vm2:
5)下面我们将进行如下实验:a)创建一个不包含任何 rule 的 firewall “test_firewall” 并应用到 test_router。此时 FWaaS 生效,默认情况下会阻止任何跨子网的流量。b)创建 rule 允许 ssh,并将其添加到 test_firewall。此时 cirros-vm1 应该能够 ssh cirros-vm2。
6)应用无 rule 的 firewall ,点击菜单 Project -> Network -> Firewalls,打开 Firewall Policies 标签页面,目前没有定义任何 Policy:
7)点击Add Policy
显示Policy创建页面,将 Policy 命名为 “test_policy”,直接点击 “Add” 按钮:
8)这样我们创建的 test_policy 不包含任何 Rule:
9)进入 “Firewalls” 标签页,点击 “Create Firewall” 按钮:
10)将新的 Firewall 命名为 “test_firewall”,并关联 “test_policy”:
11)在 “Routers” 标签页中选择 “test_router”,点击 “Add” 创建 firewall:
12)等待 test_firewall 的 Status 变为 “Active”,此时 test_router 已经成功应用 test_policy:
13)通过 iptables-save 查看 router namespace 的 iptables 规则:
14)为了让大家了解底层到底发生了什么变化,下面用 vimdiff 显示了应用 test_firewall 前后 iptables 规则的变化:
15)分析一下规则:
route 在转发数据包时会使用 chain:
neutron-vpn-agen-FORWARD 的规则如下:
我们以第一条为例,其含义是:从 router namespace 任何一个 qr-* interface 发出的流量都会应用 chain neutron-vpn-agen-iv4e85f4601,该 chain 定义如下:
其规则为:如果数据包的状态为 INVALID,则 DROP;如果数据包的状态为 RELATED 或 ESTABLISHED,则 ACCEPT。
其他正常传输的数据怎么处理呢? 回到 neutron-vpn-agen-FORWARD chain 的下一条关于 router 外出数据的规则:
neutron-vpn-agen-fwaas-defau 内容为:
可见,数据会被丢弃。同样的道理,router 上所有进入 qr-* interface 的数据也会被丢弃。其结论是:在没有定义任何 firewall rule 的情况下,进出 router 的数据包都会被丢弃。
16)ping 和 ssh 测试表明目前 cirros-vm1 确实已经无法与 cirros-vm2 通信: - 进行实践,允许 SSH:
1)添加一条 firewall rule,允许 ssh,在 Firewall Rules 标签页面点击 “Add Rule” 按钮:
2)将新 rule 命名为 “allow ssh”, Protocal 选择 “TCP”, Action 为 “ALLOW”,Destination Port/Port Range 为 “22”:
3)点击 “Add” ,rule 创建成功:
4)接下来将 rule 添加到 policy 中 。点击 Firewall Policies 标签页面,然后点击 “test_policy” 后面的 “Insert Rule” 按钮:
5)在下拉框中选择 Rule “allow ssh”,点击 “Save Changes”,可以看到,“allow ssh” 已经成功添加到 “test_policy” 中:
6)通过 vimdiff 查看 router namespace 的 iptables-save 发生了什么变化:
7)iptables 添加了两条规则:
其含义是进出 router 的 tcp 数据包,如果目的端口为 22(ssh)ssh,则一律 ACCEPT。
8)测试一下,cirros-vm1 已经可以 ssh cirros-vm2,但 ping 还是不通,这与预期一致:
“allow ssh” 已经起作用。同时我们也发现,firewall rule 对进出流量同时生效,不区分方向。 - FWaaS 用于加强 Neutron 网络的安全性,与安全组可以配合使用,FWaaS 和安全组做个比较:
1)相同点:底层都是通过 iptables 实现。
2)不同点:a)FWaaS 的 iptables 规则应用在 router 上,保护整个租户网络。安全组则应用在虚拟网卡上,保护单个 instance; b)FWaaS 可以定义 allow 或者 deny 规则,安全组只能定义 allow 规则;c)目前 FWaaS 规则不能区分进出流量,对双向流量都起作用,安全组规则可以区分 ingress 和 egress。
2.5.5.11 Load Balancing as a Service
- Load Balance as a Service(LBaaS)是 Neutron 提供的一项高级网络服务。LBaaS 允许租户在自己的网络中创建和管理 load balancer。load balancer 可以说是分布式系统中比较基础的组件。它接收前端发来的请求,然后将请求按照某种均衡策略转发给后端资源池中的某个处理单元,以完成处理。load balancer 可以实现系统高可用和横向扩展。
- LBaaS 有三个主要的概念: Pool Member,Pool 和 Virtual IP:
1)Pool Member:Pool Member 是 layer 4 的实体,拥有 IP 地址并通过监听端口对外提供服务。例如 Pool Member 可以是一个 web server,IP 为 172.16.100.9 并通过 80 端口提供 HTTP 服务。
2)Pool:Pool 由一组 Pool Member 组成。 这些 Pool Member 通常提供同一类服务。例如一个 web server pool,包含:web1:172.16.100.9:80、web2:172.16.100.10:80;
3)Virtual IP:Virtual IP 也称作 VIP,是定义在 load balancer 上的 IP 地址。每个 pool member 都有自己的 IP,但对外服务则是通过 VIP。load balancer 负责监听外部的连接,并将连接分发到 pool member。外部 client 只知道 VIP,不知道也不需要关心是否有 pool 或者有多少个 pool member。OpenStack Neutron 目前默认通过 HAProxy 软件来实现 LBaaS。HAProxy 是一个流行的开源 load balancer。Neutron 也支持其他一些第三方 load balancer。 - 下图展示了 HAProxy 实现 load balancer 的方式:
1)左图是 client 发送请求到 web server 的数据流:
a)Client 10.10.10.4 通过浏览器访问服务器的外网 IP 10.10.10.7。
b)请求首先到达路由器,将目的地址设置为服务器的内网 VIP 172.16.100.11
c)VIP 设置在 load balancer 上,load balancer 收到请求后选择 pool member WEB1,将数据包的目的 IP 设为 WEB1 的地址 172.16.100.9。
d)在将数据包转发给 WEB1 之前,load balancer 将数据包的源 IP 修改为自己的 VIP 地址 172.16.100.11,其目的是保证 WEB1 能够将应答数据发送回 load balancer。
e) WEB1 收到请求数据包。
2)右图是 web server 应答的数据流:
a)WEB1 将数据包发送给 load balancer。
b)load balancer 收到 WEB1 发回的数据后,将目的 IP 修改为 Client 的地址 10.10.10.4。时也将数据包的源 IP 修改为 VIP 地址 172.16.100.11,保证 Client 能够将后续的数据发送给自己。
c)load balancer 将数据发送给路由器。
d) 路由器将数据包的源地址恢复成服务器的外网 IP 10.10.10.7,然后发送给 Client。
e)Client 收到应答数据。
3)以上就是 Load Balance as a Service 的工作原理。 - 实践LBaaS
1)Neutron 通过 lbaas plugin 和 lbaas agent 提供 LBaaS 服务。lbaas plugin 与 Neutron Server 一起运行在控制节点上。lbaas agent 运行在网络节点上。对于我们的实验环境,控制节点和网络节点是一个,都是 devstack-controller。
2)首先在配置中启用LBaaS服务,配置LBaaS agent。Neutron 配置 LBaaS agent 的地方是 /etc/neutron/services/loadbalancer/haproxy/lbaas_agent.ini。定义 interface_driver:
interface_driver 的作用是设置 load balancer 的网络接口驱动,可以有两个选项:
Linux Bridge:
Open vSwitch:
3)配置 LBaaS plugin,在 /etc/neutron/neutron.conf 中设置启用 LBaaS plugin:
在 /etc/neutron/neutron_lbaas.conf 中设置 service provider:
从注释信息可以看到,除了默认的 HAProxy,Neutron 也支持第三方 provider,比如 radware,VMWareEdge 等。
4)重启 neutron 服务,确保 LBaaS 正常运行:
5)开始实现如下LBaaS环境:
环境描述如下:a)创建一个 Pool “web servers”;b)两个 pool member “WEB1” 和 “WEB2”,均为运行 Ubuntu cloud image 的 instance;3)load balancer VIP 与 floating IP 关联;4)位于外网的 client 通过 floating IP 外网访问 web server。
6)第一步开始,创建 Pool 。点击菜单 Project -> Network -> Load Balancers,点击 Pools 标签页中的 “Add Pool” 按钮:
7)显示 Pool 创建页面:
将 Pool 命名为“web servers”。
Provider 选择默认的 “haproxy”。
Subnet 选择 “172.16.100.0/24”。
Protocol 选择 “HTTP”。
Load Balancing Method 选择 “ROUND_ROBIN”。
8)点击 “Add” 按钮,“web servers” 创建成功:
9)几个属性说明:
LBaaS 支持如下几种 Protocol:
因为我们用 web server 做实验,所以这里需要选择 “HTTP”。
LBaaS 支持多种 load balance method:
a)ROUND_ROUBIN:如果采用 round robin 算法,load balancer 按固定的顺序从 pool 中选择 member 响应 client的连接请求。 这种方法的不足是缺乏机制检查 member 是否负载过重。 有可能出现某些 member由于处理能力弱而不得不继续处理新连接的情况。 如果所有 pool member
具有相同处理能力、内存容量,并且每个连接持续的时间大致相同,这种情况非常适合 round robin,每个 member 的负载会很均衡。
b)LEAST_CONNECTIONS:如果采用 least connections 算法,load balancer 会挑选当前连接数最少的 pool member。这是一种动态的算法,需要实时监控每个 member 的连接数量和状态。 计算能力强的 member 能够更快的处理连接进而会分配到更多的新连接。
c)SOURCE_IP:如果采用 source IP 算法,具有相同 source IP 的连接会被分发到同一个 pool member。 source IP算法对于像购物车这种需要保存状态的应用特别有用,因为我们希望用同一 server 来处理某个 client 连续的在线购物操作。
在我们的实验中选择的是 ROUND_ROUBIN 算法。
10)为 Pool 添加 VIP:
在 “web servers” 的操作列表中点击 “Add VIP”
VIP 命名为 “VIP for web servers”。
VIP Subnet 选择 “172.16.100.0/24”,与 pool 一致。
指定 VIP 为 172.16.100.11,如果不指定,系统会自动从 subnet 中分配。
指定 HTTP 端口 80。
Session Persistence 选择 “SOURCE IP”。
可以通过 Connection Limit 限制连接的数量,如果不指定则为不加限制。
11)点击 “Add”,VIP 创建成功:
通常我们希望让同一个 server 来处理某个 client 的连续请求。 否则 client 可能会由于丢失 session 而不得不重新登录。这个特性就是 Session Persistence。
VIP 支持如下几种 Session Persistence 方式:
a)SOURCE_IP:这种方式与前面 load balance 的 SOURCE_IP 效果一样。 初始连接建立后,后续来自相同 source IP 的 client请求会发送给同一个 member。 当大量 client 通过同一个代理服务器访问 VIP 时(比如在公司和学校上网),SOURCE_IP方式会造成 member 负载不均。
b)HTTP_COOKIE:工作方式如下: 当 client 第一次连接到 VIP 时,HAProxy 从 pool 中挑选出一个 member。 当此 member响应请求时,HAProxy 会在应答报文中注入命名为 “SRV” 的 cookie,这个 cookie 包含了该 member 的唯一标识。client 的后续请求都会包含这个 “SRV” cookie。 HAProxy 会分析 cookie 的内容,并将请求转发给同一个member。HTTP_COOKIE 优于 SOURCE_IP,因为它不依赖 client 的 IP。
c)APP_COOKIE:app cookie 依赖于服务器端应用定义的 cookie。 比如 app 可以通过在 session 中创建 cookie 来区分不同的 client。HAProxy 会查看报文中的 app cookie,确保将包含 app cookie 的请求发送到同一个 member。如果没有 cookie(新连接或者服务器应用不创建 cookie),HAProxy 会采用 ROUND_ROUBIN 算法分配 member。
12)比较 Load Balance Method 和 Session Persistence:
前面我们介绍了三种 Load Balance Method:
这里还有三种 Session Persistence:
因为两者都涉及到如何选择 pool member,所以很容易混淆。 它们之间的最大区别在于选择 pool member 的阶段不同:a)Load Balance Method 是为新连接选择 member 的方法;b)Session Persistence 是为同一个 client 的后续连接选择 member 的方法。
例如这里我们的设置为:Load Balance Method — ROUND_ROUBIN;Session Persistence — SOURCE_IP;当 client A 向 VIP 发送第一个请求时,HAProxy 通过 ROUND_ROUBIN 选择 member1。对于 client A 后续的请求,HAProxy 则会应用 SOURCE_IP 机制,仍然选择 member1 来处理请求。
13)使用 Ubuntu Cloud Image,由于 cirros 镜像不能运行 HTTP 服务,我们将使用 Ubuntu Cloud Image,下载地址为 http://uec-images.ubuntu.com/trusty/current/trusty-server-cloudimg-amd64-disk1.img:
与以前的 instance 不同,Web1 和 Web2 使用了命名为“cloud”的 Key Pair。这是因为 Ubuntu Cloud Image 默认用户 “ubuntu” 的密码是随机生成的,通过 Key Pair 访问是一个比较方便的方法。
下面是 Key Pair 的配置方法:
a)通过ssh-keygen -t rsa -f cloud.key
生成 Key Pair:
其中 cloud.key.pub 是公钥,需要添加到 instance 的 ~/.ssh/authorized_keys 文件中。
cloud.key 是私钥,用于访问 instance。
b)将 Pair Key 导入 OpenStack:
进入 Project -> Compute -> Access & Security 菜单,点击 Key Pairs 标签页的 “Import Key Pair” 按钮。
复制 cloud.key.pub 的内容。
粘贴到 Public Key 输入框中,给 Key Pair 命名为 “cloud”,并点击 “Import Key Pair”。
Key Pair “cloud” 成功导入。
c)使用 Key Pair “cloud”。
launch instance 的时候,在 Access & Security 标签页中选择 Key Pair “cloud”。OpenStack 会自动将公钥添加进 instance 的 ~/.ssh/authorized_keys 文件。
d)ssh instance:
用ssh -i cloud.key ubuntu@172.16.100.9
指定私钥,并以 ubuntu 用户 ssh “Web1” 和 “Web2”。
无需密码直接登录 instance。注:为了便于演示,这里我们是在 router 的 namespace 中执行 ssh,主要目的是网络可达。 “Web1” 和 “Web2” 准备就绪了,可以添加到 Pool 中了。
14)在 Project -> Network -> Load Balancers 的 Members 标签页中 点击 “Add Member” 按钮:
Pool 选择 “web servers”。Member(s) 中多选 “Web1” 和 “Web2”。Protocol Port 设置为 “80”。
点击 “Add”,member 添加成功:
15)创建 Monitor,监控 Pool Member 健康状态:如果某个 member 不能正常工作,monitor 会将其状态设置为 down,从而避免将后续请求转发给它。
在 Monitors 标签页中点击 “Add Monitor” 按钮:
Type 选择 “HTTP”,含义是通过 HTTP 检查 member 的健康状态。
Delay 设置为 “10”,含义是 10 秒检查一次 member 的状态。
Timeout 设置为 “5”,含义是如果 member 在 5 秒内无法应答,则超时。
Max Reties 设置为 “3”,含义是如果尝试 3 次都超时或者失败,则将 member 状态设置为 down。
HTTP Method 设置为 “GET”
URL 设置为 “/”
Expected HTTP Status Codes 设置为 “200”
上面三项的含义是通过 HTTP GET 请求 member “/” URL,如果返回码为 200,则认为 member 状态正常。
点击 “Add”,monitor 创建成功:
将新建的 monitor 添加到 pool,在 “web servers” 的操作列表中点击 “Associate Monitor” :
选择我们刚刚创建的 monitor
点击 “Associate”。
16)测试 LBaaS :
a)经过上面的设置,我们创建了包含 member “Web1” 和 “Web2” 的 Pool “web servers”,并添加了 monitor。 准备就绪,可以测试 load balancer 是否正常工作了。首先在 Web1 和 Web2 中启动 HTTP 服务,在 80 端口监听:
这里我们使用 python 提供的 SimpleHTTPServer 模块启动了 HTTP 服务(sudo python -m SimpleHTTPServer 80
)。web server 的 index.html 显示当前访问的是哪个 member。
b)在 router 的 namespace 上多次执行 curl 172.16.100.11(VIP):
测试结果显示每次访问的都是 Web2 这个 member。
c)为什么没有访问到 Web1 呢?
还记得我们前面讨论的内容吗:
Load Balance Method — ROUND_ROUBIN
Session Persistence — SOURCE_IP
在这种配置下,第一个 curl 请求 HAProxy 通过 ROUND_ROUBIN 选择了 Web2。而后续的请求,HAProxy 则会应用 SOURCE_IP 机制,仍然选择 Web2。
d)下面我们修改一下配置。在“web servers” 的操作列表中点击 “Edit VIP”:
选择 “No session persistence” 并保存:
再进行 curl 测试
可以看到已经在 “Web1” 和 “Web2” 之间 round robin 了。
17)重点分析 Neutron 是如何用 Haproxy 来实现负责均衡:
a)在控制节点上运行ip netns
,我们发现 Neutron 创建了新的 namespace qlbaas-xxx:
该 namespace 对应我们创建的 pool “web servers”。其命名格式为 qlbaas-< pool ID>。
b)可以通过 ip a 查看其设置:
VIP 172.16.100.11 已经配置在 namespace interface 上。
c)在 subnet 的 Port 列表中也可以找到该 interface 的相应配置:
对于每一个 pool,Neutron 都会启动一个 haproxy 进程提供 load balancering 功能。
d)通过 ps 命令查找 haproxy 进程:
haproxy 配置文件保存在 /opt/stack/data/neutron/lbaas/< pool ID>/conf 中。
e)查看 “web servers” 的配置内容:
可以看到:
frontend 使用的 HTTP 地址为 VIP:80;
backend 使用的 HTTP 地址为 172.16.100.10:80 和 172.16.100.9:80;
balance 方法为 roundrobin;
18)通过 Floating IP 访问 VIP:
a)访问 Project -> Compute -> Access & Security,打开 Floating IPs 标签页,点击 “Allocate IP to Project” 按钮:
b)在下拉列表中选择 “ext_net”,Neutron 将从该网络中分配 floating IP:
c)点击 “Allocate IP”:
分配到的 IP 为 “10.10.10.7”。
d)点击 “Associate” 按钮:
e)在 “Port to be associated” 列表中选择 “VIP for web servers: 172.16.100.11” 并点击 “Associate”:
成功将外网 IP 10.10.10.7 关联到 VIP。
f)下面是在 IP 为 10.10.10.4 的 instance 中进行 curl 测试:
floating IP 生效,load balaner 工作正常。 - LBaaS 小节:
1)LBaaS 为租户提供了横向扩展应用的能力;
2)租户可以将外部请求 balancing 到多个 instance 上,并通过 monitor 实现应用的高可用;
3)LBaaS 当前的实现是基于 HAProxy,其功能已经能够满足普通业务需求。
2.5.6 Open vSwitch实现Neutron网络
- 基础环境:
实验环境两节点的网卡分配方式与 Linux Bridge 一致,如下所示:
1)控制节点三个网卡(eth0, eth1, eth2),计算节点两网卡(eth0, eth1)。
2)合并 Management 和 API 网络,使用 eth0,IP 段为 192.168.104.0/24。
3)VM 网络使用 eht1。
4)控制节点的 eth2 与 External 网络连接,IP 段为 10.10.10.0/24。 - 网络拓扑:
这个图在 Linux Bridge 实现中也看到过,唯一的区别是:对于节点中的 “Virtual Network Switch” 我们将用 Open vSwitch 替换掉 Linux Bridge。 - 配置 openvswitch mechanism driver:
要将 Liunx Bridge 切换成 Open vSwitch,首先需要安装 Open vSwitch 的 agent,修改 devstack 的 local.conf:
重新运行 ./stack,devstack 会自动下载并安装 Open vSwitch。接下来就可以修改 ML2 的配置文件 /etc/neutron/plugins/ml2/ml2_conf.ini,设置使用 openvswitch mechanism driver:
控制节点和计算节点都需要按照上面的方法安装并配置 Open vSwitch。Neutron 服务重启后,可以通过neutron agent-list
命令查看到 neutron-openvswitch-agent 已经在两个节点上运行:
- 初始网络状态:
1)控制节点:
ifconfig 显示控制节点上有三个网桥 br-ex,br-int 和 br-tun。从命名上看我们大致能猜出他们的用途:
a)br-ex:连接外部(external)网络的网桥
b)br-int:集成(integration)网桥,所有 instance 的虚拟网卡和其他虚拟网络设备都将连接到该网桥。
c)br-tun:隧道(tunnel)网桥,基于隧道技术的 VxLAN 和 GRE 网络将使用该网桥进行通信。
这些网桥都是 Neutron 自动为我们创建的,但是通过 brctl show 命令却看不到它们。 这是因为我们使用的是 Open vSwitch 而非 Linux Bridge,需要用 Open vSwitch 的命令ovs-vsctl show
查看,如下图所示:
2)计算节点
计算节点上也有 br-int 和 br-tun,但没有 br-ext。这是合理的,因为发送到外网的流量是通过网络节点上的虚拟路由器转发出去的,所以 br-ext 只会放在网络节点(devstack-controller)上。
- 了解 Open vSwitch 环境中的各种网络设备。在 Open vSwitch 环境中,一个数据包从 instance 发送到物理网卡大致会经过下面几个类型的设备:
1)tap interface:命名为 tapXXXX;
2)linux bridge:命名为 qbrXXXX;
3)veth pair:命名为 qvbXXXX, qvoXXXX;
4)OVS integration bridge:命名为 br-int;
5)OVS patch ports:命名为 int-br-ethX 和 phy-br-ethX(X 为 interface 的序号);
6)OVS provider bridge:命名为 br-ethX(X 为 interface 的序号);
7)物理 interface:命名为 ethX(X 为 interface 的序号);
8)OVS tunnel bridge:命名为 br-tun,OVS provider bridge:会在 flat 和 vlan 网络中使用;OVS tunnel bridge 则会在 vxlan 和 gre 网络中使用。 - Open vSwitch 支持 local, flat, vlan, vxlan 和 gre 所有五种 network type。
2.5.6.1 local network
- local network 的特点是不会与宿主机的任何物理网卡相连,也不关联任何的 VLAN ID。对于每个 local netwrok,ML2 linux-bridge 会创建一个 bridge,instance 的 tap 设备会连接到 bridge。位于同一个 local network 的 instance 会连接到相同的 bridge,这样 instance 之间就可以通信了。因为 bridge 没有与物理网卡连接,所以 instance 无法与宿主机之外的网络通信。同时因为每个 local network 有自己的 bridge,bridge 之间是没有连通的,所以两个 local network 之间也不能通信。
- 创建第一个local network:
1)进入菜单 Admin -> Networks,点击 “Create Network” 按钮:
2)显示创建页面:
“Provider Network Type” 选择 “Local”,点击 “Create Network”,first_local_net 创建成功;
3)点击 first_local_net 链接,进入 network 配置页面,目前还没有 subnet,点击 “Create Subnet” 按钮:
4)设置 IP 地址为 “172.16.1.0/24”:
5)点击 “Next”,勾选 “Enable DHCP”,IP 池设置为 “172.16.1.2,172.16.1.99”:
6)点击 “Create”,subnet 创建成功:
7)同时 devstack-controler 针对此 subnet 的 DHCP 服务也已经 Active:
8)底层网络发生了什么变化,打开控制节点的 shell 终端,用ovs-vsctl show
查看当前 Open vSwitch 的状态:
9)可以看到 Neutron 自动在 br-int 网桥上创建了 port “tap7970bdcd-f2”。从命名可知,该 port 对应 local_net 的 dhcp 接口。与 linux bridge driver 一样,dhcp 设备也是放在命名空间里的:
10)目前网络结构如下图所示:
- 将 Instance 部署到 OVS Local Network:
1)launch 一个 instance,选择 first_local_net 网络:
2)instance 部署成功,分配的 IP 地址为 172.16.1.3:
3)如上图所示,port 列表中增加了一个 port “(fc1c6ebb-719d)”,IP 为 172.16.1.3,点击 port 名称查看 MAC 信息:
4)Open vSwitch driver 会如何将 cirros-vm1 连接到 first_local_net? 如果采用类似的实现方法,neutron-openvswitch-agent 会根据 port 信息创建 tap 设备 tapfc1c6ebb-71,并将其连接到 br-int 网桥,tapfc1c6ebb-71 就是 cirros-vm1 的虚拟网卡。cirros-vm1 部署到了控制节点,通过ovs-vsctl show
查看 bridge 的配置:
非常遗憾,在 br-int 上并没有看到 tapfc1c6ebb-71,而是多了一个 qvofc1c6ebb-71。目前我们并不知道 qvofc1c6ebb-71 是什么,我们再用brctl show
查看一下 linux bridge 的配置:
这里我们看到有一个新建的网桥 qbrfc1c6ebb-71,上面连接了两个设备 qvbfc1c6ebb-71 和 tapfc1c6ebb-71。从命名上看,他们都应该与 cirros-vm1 的虚拟网卡有关。
5)通过virsh edit
查看 cirros-vm1 的配置:
确实 tapfc1c6ebb-71 是 cirros-vm1 的虚拟网卡。那么 linux bridge qbrfc1c6ebb-71 上的 qvbfc1c6ebb-71 设备与 Open vSwitch br-int 上的 qvofc1c6ebb-71 是什么关系呢?
6)我们用ethtool -S
分别查看 qvbfc1c6ebb-71 和 qvofc1c6ebb-71 的 statistics:
原来 qvbfc1c6ebb-71 和 qvofc1c6ebb-71 都是 veth 设备,它们对应的另一端 veth 设备 的 index 分别是 12 和 13。
7)通过ip a
命令找到 index 12 和 13 的设备:
其实qvbfc1c6ebb-71 和 qvofc1c6ebb-71 组成了一个 veth pair。veth pair 是一种成对出现的特殊网络设备,它们像一根虚拟的网线连接两个网络设备。这里 qvbfc1c6ebb-71 和 qvofc1c6ebb-71 的作用就是连接网桥 qbrfc1c6ebb-71 和 br-int。
8)拓扑结构如下所示:
由图所示,tapfc1c6ebb-71 通过 qbrfc1c6ebb-71 间接连接到 br-int。
9)为什么 tapfc1c6ebb-71 不能像左边的 DHCP 设备 tap7970bdcd-f2 那样直接连接到 br-int 呢?
其原因是: Open vSwitch 目前还不支持将 iptables 规则放在与它直接相连的 tap 设备上。如果做不到这一点,就无法实现 Security Group 功能。为了支持 Security Group,不得不多引入一个 Linux Bridge 支持 iptables。这样的后果就是网络结构更复杂了,路径上多了一个 linux bridge 和 一对 veth pair 设备。 - 再部署一个 Instance 和 Local Network:
1)再部署一个 Instance 到first_local_network,以同样的方式 launch instance “cirros-vm2”,分配的 IP 为 172.16.1.4:
2)cirros-vm2 也被 schedule 到控制节点,ovs-vsctl show
的输出如下:
3)cirros-vm2 对于的 tap 设备为 tapddbbb728-93:
4)从 cirros-vm2 能够 Ping 通 cirros-vm1 的 IP 地址 172.16.1.3:
5)当前宿主机的网络结构如下 :
两个 instance 都挂在 br-int 上,可以相互通信。
6)创建第二个 local network:
为了分析 local network 的连通性,我们再创建一个 “second_local_net”,second_local_net 的绝大部分属性与 first_local_net 相同,除了 IP 池范围为 172.16.1.101-172.16.1.200:
second_local_net 的 DHCP 设备也已经就绪:
DHCP 对应的 tap 设备为 tap2c1b3c58-4e,已经连接到 br-int:
- 部署instance到second_local_network
1)launch 新的 instance “cirros-vm3”,网络选择 second_local_net:
2)cirros-vm3 分配到的 IP 为 172.16.1.102:
3)cirros-vm3 被 schedule 到控制节点,其虚拟网卡也连接到 br-int:
4)当前的控制节点上的网络结构如下 :
5)下面我们讨论一个有趣的问题:cirros-vm3 能否 Ping 到 cirros-vm1 呢?根据我们在 linux bridge 中学到的知识,既然 cirros-vm3 和 cirros-vm1 都连接到同一个网桥 br-int,那么它们之间应该是可以 Ping 通的。但另一方面,根据 Neutron 的设计,不同 local 网络之间是无法通信的。那么事实到底是如何呢?
实验证明 cirros-vm3 无法 Ping 到 cirros-vm1。
6)下面我们需要解释同一个网桥上的 port 为什么不能通信,让我们重新审视一下 br-int 上各个 port 的配置。
这次我们注意到,虚拟网卡和 DHCP 对应的 port 都有一个特殊的 tag 属性。first_local_net 相关 port 其 tag 为 1;second_local_net 相关 port 其 tag 为 2。玄机就在这里了: Open vSwitch 的每个网桥都可以看作一个真正的交换机,可以支持 VLAN,这里的 tag 就是 VLAN ID。br-int 中标记 tag 1 的 port 和 标记 tag 2 的 port 分别属于不同的 VLAN,它们之间是隔离的。需要特别说明的是: Open vSwitch 中的 tag 是内部 VLAN,用于隔离网桥中的 port,与物理网络中的 VLAN 没有关系。
7)我们将 tag 信息添加到网络结构图中,如下所示:
2.5.6.2 flat network
- flat network 是不带 tag 的网络,要求宿主机的物理网卡直接与 linux bridge 连接,这意味着:每个 flat network 都会独占一个物理网卡。
- 在ML2中配置 enable flat network
1)在控制节点 /etc/neutron/plugins/ml2/ml2_conf.ini 中设置 flat network 相关参数:
指定普通用户创建的网络类型为 flat。需要注意的是:因为 flat 网络与物理网卡一一对应,一般情况下租户网络不会采用 flat,这里只是示例。接着需要指明 flat 网络与物理网络的对应关系:
如上所示:在 [ml2_type_flat] 中通过 flat_networks 定义了一个 flat 网络,label 为 “default”。在 [ovs] 中通过 bridge_mappings 指明 default 对应的 Open vSwitch 网桥为 br-eth1。
label 是 flat 网络的标识,在创建 flat 时会用到,label 的名字可以是任意字符串,只要确保各个节点 ml2_conf.ini 中的 label 命名一致就可以了。各个节点中 label 与物理网卡的对于关系可能不一样。这是因为每个节点可以使用不同的物理网卡将 instance 连接到 flat network。与 linux bridge 实现的 flat 网络不同,ml2 中并不会直接指定 label 与物理网卡的对应关系,而是指定 label 与 ovs bridge 的对应关系。
2)这里的 ovs bridge 是 br-eth1,我们需要提前通过ovs-vsctl add-br br-eth1
和ovs-vsctl add-port br-eth1 eth1
命令创建 br-eth1。将物理网卡 eth1 桥接在 br-eth1 上。
3)如果要创建多个 flat 网络,需要定义多个 label,用逗号隔开,当然也需要用到多个 ovs bridge,如下所示:
4)计算节点也需要做相同的配置,然后重启所有节点的 Neutron 服务,通过ovs-vsctl show
检视一下当前的网络结构:
对于 ovs bridge “br-eth1” 和其上桥接的 port “eth1” 我们应该不会感到意外,这是前面配置的结果。然而除此之外,br-int 和 br-eth1 分别多了一个 port “int-br-eth1” 和 “phy-br-eth1”,而且这两个 port 都是 “patch” 类型,同时通过 “peer” 指向对方。上面的配置描述了这样一个事实:br-int 与 br-eht1 这两个网桥通过 int-br-eth1 和 phy-br-eth1 连接在一起了。
5)目前控制节点网络结构如下:
6)veth pair VS patch port:
在前面 local network 我们看到,br-int 与 linux bridge 之间可以通过 veth pair 连接。
而这里两个 ovs bridge 之间是用 patch port 连接的。看来 veth pair 和 patch port 都可以连接网桥,使用的时候如何选择呢?patch port 是 ovs bridge 自己特有的 port 类型,只能在 ovs 中使用。如果是连接两个 ovs bridge,优先使用 patch port,因为性能更好。所以:a)连接两个 ovs bridge,优先使用 patch port。技术上veth pair 也能实现,但性能不如 patch port。b)连接 ovs bridge 和 linux bridge,只能使用 veth pair。c)连接两个 linux bridge,只能使用 veth pair。 - 创建 OVS Flat Network:
1)Admin -> Networks,点击 “Create Network” 按钮:
显示创建页面:
Provider Network Type 选择 “Flat”。
Physical Network 填写 “default”,与 ml2_conf.ini 中 flat_networks 参数值保持一致。
2)点击 “Create Network”,flat_net 创建成功:
3)点击 flat_net 链接,进入 network 配置页面,目前还没有 subnet。点击 “Create Subnet” 按钮:
4)设置 IP 地址为 “172.16.1.0/24”:
5)点击 “Next”,勾选 “Enable DHCP”:
6)点击 “Create”,subnet 创建成功:
7)查看控制节点的网络结构,执行ovs-vsctl show
:
Neutron 自动在 br-int 网桥上创建了 flat-net dhcp 的接口 “tap83421c44-93”。
8)此时 flat_net 结构如图所示:
- 部署 Instance 到ovs flat network:
1)launch 新的 instance “cirros-vm1”,网络选择 falt_net:
2)cirros-vm1 分配到的 IP 为 172.16.1.3:
3)cirros-vm1 被 schedule 到控制节点,其虚拟网卡也连接到 br-int:
虚拟网卡与 br-int 的连接方式与 local 网络是一样的,不再赘述。
4)当前 flat_net 的结构如下:
5)继续用同样的方式 launch instance cirros-vm2,分配到的 IP 为 172.16.1.4:
6)cirros-vm2 被 schedule 到计算节点,虚拟网卡已经连接到 br-int:
因为计算节点上没有 hdcp 服务,所以没有相应的 tap 设备。
7)当前 flat_net 的结构如下:
cirros-vm1(172.16.1.3) 与 cirros-vm2(172.16.1.4) 位于不同节点,通过 flat_net 相连,下面验证连通性。
8)在 cirros-vm2 控制台中 ping 172.16.1.3:
如我们预料两台虚拟机位于同一个flat network中,所以可以ping 成功。
2.5.6.3 vlan network
- vlan network 是带 tag 的网络。在 Open vSwitch 实现方式下,不同 vlan instance 的虚拟网卡都接到 br-int 上。这一点与 linux bridge 非常不同,linux bridge 是不同 vlan 接到不同的网桥上。
- 在我们的实验环境中,收发 vlan 数据的物理网卡为 eth1,上面可以走多个 vlan,所以物理交换机上与 eth1 相连的的 port 要设置成 trunk 模式,而不是 access 模式。
- 在 ML2 配置中 enable vlan network:
1)在 /etc/neutron/plugins/ml2/ml2_conf.ini 设置 vlan network 相关参数,指定普通用户创建的网络类型为 vlan:
2)指定 vlan 的范围:
上面配置定义了 label 为 “default” 的 vlan network,vlan id 的范围是 3001 – 4000。
这个范围是针对普通用户在自己的租户里创建 network 的范围。因为普通用户创建 network 时并不能指定 vlan id,Neutron 会按顺序自动从这个范围中取值。对于 admin 则没有 vlan id 的限制,admin 可以创建 id 范围为 1-4094 的 vlan network。
3)接着需要指明 vlan 网络与物理网络的对应关系:
如上所示:在 [ml2_type_vlan] 中定义了 lable “default”,[ovs] 中则通过 bridge_mappings 指明 default 对应的 Open vSwitch 网桥为 br-eth1。这里 label 的作用与前面 flat network 中的 label 一样,只是一个标示,可以是任何字符串
4)我们需要提前通过ovs-ovctl
命令创建 br-eth1,并将物理网卡 eth1 桥接在 br-eth1 上。
- 创建 OVS Vlan100 Netwrok:
1)打开菜单 Admin -> Networks,点击 “Create Network” 按钮:
显示创建页面:
Provider Network Type 选择 “VLAN”。
Physical Network 填写 “default”,与 ml2_conf.ini 中 network_vlan_ranges 参数值保持一致。
Segmentation ID 即 VLAN ID,设置为 100。
2)点击 “Create Network”,vlan100 创建成功:
3)点击 vlan100 链接,进入 network 配置页面,目前还没有 subnet,点击 “Create Subnet” 按钮:
4)创建 subnet_172_16_100_0,IP 地址为 172.16.100.0/24:
5)在控制节点上执行ovs-vsctl show
,查看网络结构:
Neutron 自动在 br-int 网桥上创建了 vlan100 dhcp 的接口 “tap43567363-50”。
6)此时 vlan100 结构如图所示:
- 部署 Instance 到vlan100:
1)launch 新的 instance “cirros-vm1”,网络选择 vlan100:
2)cirros-vm1 分配到的 IP 为 172.16.100.3:
3)cirros-vm1 被 schedule 到控制节点,其虚拟网卡也连接到 br-int:
虚拟网卡与 br-int 的连接方式与 local 和 flat 网络一样。
4)当前 vlan100 的结构如下:
5)继续用同样的方式 launch instance cirros-vm2,分配到的 IP 为 172.16.100.104:
6)cirros-vm2 被 schedule 到计算节点,虚拟网卡已经连接到 br-int:
因为计算节点上没有 hdcp 服务,所以没有相应的 tap 设备。
7)当前 vlan100 的结构如下:
8)cirros-vm1(172.16.100.3) 与 cirros-vm2(172.16.100.4) 位于不同节点,通过 vlan100 相连,下面执行 PING 验证连通性。在 cirros-vm1 控制台中执行 ping 172.16.100.4:
- 创建vlan 101并部署instance:
1)创建vlan 101:
2)subnet IP 地址为 172.16.101.0/24:
3)Neutron 自动在 br-int 网桥上创建了 vlan100 dhcp 的接口 “tap1820558c-0a”:
4)现在,网络结构如下:
5)将 instance 连接到 vlan101,unch 新的 instance “cirros-vm3”,网络选择 vlan101:
6)cirros-vm3 分配到的 IP 为 172.16.101.103 :
7)cirros-vm3 被 schedule 到计算节点,虚拟网卡已经连接到 br-int:
8)当前网络结构如下:
cirros-vm1 位于控制节点,属于 vlan100。 cirros-vm2 位于计算节点,属于 vlan100。cirros-vm3 位于计算节点,属于 vlan101。cirros-vm1 与 cirros-vm2 都在 vlan100,它们之间能通信。cirros-vm3 在 vlan101,不能与 cirros-vm1 和 cirros-vm2 通信。 - 分析 OVS 如何实现 vlan 隔离:
1)之前完成的OVS vlan 环境的搭建,当前拓扑结构如下所示:
a)cirros-vm1 位于控制节点,属于 vlan100;
b)cirros-vm2 位于计算节点,属于 vlan100;
c)cirros-vm3 位于计算节点,属于 vlan101。
2)详细分析 OVS 如何实现 vlan100 和 vlan101 的隔离:
与 Linux Bridge driver 不同,Open vSwitch driver 并不通过 eth1.100, eth1.101 等 VLAN interface 来隔离不同的 VLAN。
所有的 instance 都连接到同一个网桥 br-int,Open vSwitch 通过 flow rule(流规则)来指定如何对进出 br-int 的数据进行转发,进而实现 vlan 之间的隔离。具体来说:当数据进出 br-int 时,flow rule 可以修改、添加或者剥掉数据包的 VLAN tag,Neutron 负责创建这些 flow rule 并将它们配置到 br-int,br-eth1 等 Open vSwitch 上。
3)下面我们就来研究一下当前的 flow rule,查看 flow rule 的命令是ovs-ofctl dump-flow <bridge>
:
br-eth1 上配置了四条 rule,每条 rule 有不少属性,其中比较重要的属性有:
a)priority:rule 的优先级,值越大优先级越高。Open vSwitch 会按照优先级从高到低应用规则。
b)in_port:inbound 端口编号,每个 port 在 Open vSwitch 中会有一个内部的编号。可以通过命令ovs-ofctl show <bridge>
查看 port 编号。
比如 br-eth1:
eth1 编号为 1;phy-br-eth1 编号为 2。
c)dl_vlan:数据包原始的 VLAN ID。
d)actions:对数据包进行的操作。
br-eth1 跟 VLAN 相关的 flow rule 是前面两条,下面我们来详细分析。清晰起见,我们只保留重要的信息,如下:
第一条的含义是:从 br-eth1 的端口 phy-br-eth1(in_port=2)接收进来的包,如果 VLAN ID 是 1(dl_vlan=1),那么需要将 VLAN ID 改为 100(actions=mod_vlan_vid:100)
从上面的网络结构我们可知,phy-br-eth1 连接的是 br-int,phy-br-eth1 的 inbound 包实际上就是 instance 通过 br-int 发送给物理网卡的数据。
4)那么怎么理解将 VLAN ID 1 改为 VLAN ID 100 呢?
请看下面计算节点ovs-vsctl show
的输出:
br-int 通过 tag 隔离不同的 port,这个 tag 可以看成内部的 VLAN ID。从 qvo4139d09b-30(对应 cirros-vm2,vlan100)进入的数据包会被打上 1 的 VLAN tag。从 qvo98582dc9-db(对应 cirros-vm3,vlan101)进入的数据包会被打上 5 的 VLAN tag。因为 br-int 中的 VLAN ID 跟物理网络中的 VLAN ID 并不相同,所以当 br-eth1 接收到 br-int 发来的数据包时,需要对 VLAN 进行转换。Neutron 负责维护 VLAN ID 的对应关系,并将转换规则配置在 flow rule 中。
5)理解了 br-eth1 的 flow rule,我们再来分析 br-int 的 flow rule:
最关键的是下面两条:
port 1 为 int-br-eth1,那么这两条规则的含义就应该是:
a)从物理网卡接收进来的数据包,如果 VLAN 为 100,则改为内部 VLAN 1。
b)从物理网卡接收进来的数据包,如果 VLAN 为 101,则将为内部 VLAN 5。
简单的说,数据包在物理网络中通过 VLAN 100 和 VLAN 101 隔离,在计算节点 OVS br-int 中则是通过内部 VLAN 1 和 VLAN 5 隔离。
2.5.6.4 Routing
- Neutron Routing 服务提供跨 subnet 互联互通的能力。
例如前面我们搭建了实验环境:
cirros-vm1 172.16.100.3 vlan100
cirros-vm3 172.16.101.3 vlan101
这两个 instance 要通信必须借助 router。可以是物理 router 或者虚拟 router。 - 详细讨论 Neutron 的虚拟 router 实现:
1)配置 l3 agent,Neutron 的路由服务是由 l3 agent 提供的。
l3 agent 需要正确配置才能工作,配置文件为 /etc/neutron/l3_agent.ini,位于控制节点或网络节点:
external_network_bridge 指定连接外网的网桥,默认是 br-ex。
interface_driver 是最重要的选项,如果 mechanism driver 是 open vswitch,则:
如果选用 linux bridge,则:
2)l3 agent 运行在控制或网络节点,采用neutron agent-list
命令查询:
3)创建虚拟 router “router_100_101”,打通 vlan100 和 vlan101:
进入操作菜单 Project -> Network -> Routers:
4)点击 “Create Router” 按钮:
5)outer 命名为 “router_100_101”,点击 “Create Router” 按钮确认:
6)router_100_101 创建成功:
7)接下来需要将 vlan100 和 vlan101 连接到 router_100_101。点击 “router_100_101” 链接进入 router 的配置页面,在 “Interfaces” 标签中点击 “Add Interface” 按钮:
8)选择 vlan100 的 subnet_172_16_100_0,点击 “Add Interface” 确认:
9)用同样的方法添加 vlan101 的 subnet_172_16_101_0:
完成后,可以看到 router_100_101 有了两个 interface,其 IP 正好是 subnet 的 Gateway IP 172.16.100.1 和 172.16.101.1。
10)到这里,router_100_101 已经连接了subnet_172_16_100_0 和subnet_172_16_101_0。router_100_101 上已经设置好了两个 subnet 的 Gateway IP。cirros-vm1 和 cirros-vm3 应该可以通信了。
不出所料,cirros-vm1 和 cirros-vm3 能通信了。 - Neutron Router 工作原理:
1)先查看控制节点的网络结构发生了什么变化:
br-int 上多了两个 port:
a)qr-d295b258-45,从命名上可以推断该 interface 对应 router_100_101 的 interface (d295b258-4586),是subnet_172_16_100_0 的网关。
b)qr-2ffdb861-73,从命名上可以推断该 interface 对应 router_100_101 的 interface (2ffdb861-731c),是subnet_172_16_101_0 的网关。
2)与 linux bridge 实现方式一样, router_100_101 运行在自己的 namespace 中:
如上所示,qrouter-a81cc110-16f4-4d6c-89d2-8af91cec9714 为 router 的 namespace,两个 Gateway IP 分别配置在 qr-2ffdb861-73 和 qr-d295b258-45 上。
3)当前网络结构如图所示:
route_101_101 上配置了 vlan100 和 vlan101 的网关,两个网络在三层上就通了。
- 访问外网,这里的外部网络是指的租户网络以外的网络。租户网络是由 Neutron 创建和维护的网络。 外部网络不由 Neutron 创建。如果是私有云,外部网络通常指的是公司 intranet;如果是公有云,外部网络通常指的是 internet
1)具体到我们的实验网络环境,计算节点和控制节点 eth1 提供的是租户网络,IP 段租户可以自由设置。控制节点 eth2 连接的就是外部网络,IP 网段为 10.10.10.2/24。如下图所示:
2)配置准备:
为了连接外部网络,需要预先在配置文件中告诉 Neutron 外部网络的类型以及对应的 Open vSwitch 网桥。外部网络是已经存在的物理网络,一般都是 flat 或者 vlan 类型。这里我们将外部网络的 label 命名为 “external”,网桥为 br-ex。
a)如果类型为 flat,控制节点/etc/neutron/plugins/ml2/ml2_conf.ini 配置如下:
b)如果类型为 vlan,配置如下:
在我们的网络环境中,外部网络是 flat 类型。 修改配置后,需要重启 neutron 的相关服务。另外,我们需要提前准备好 br-ex,将 eth2 添加到 br-ex:
br-ex 已经存在,我们只需要添加 eth2。
3)创建ext_net:
a)进入 Admin -> Networks 菜单,点击 “Create Network” 按钮:
显示创建页面:
Provider Network Type 选择 “Flat”。
Network 填写 “external”,与 ml2_conf.ini 中 flat_networks 的参数值保持一致。
勾选 External Network 选择框。
b)点击 “Create Network”,ext_net 创建成功:
c)点击 ext_net 链接,进入 network 配置页面,目前还没有 subnet。点击 “Create Subnet” 按钮:
d)创建 subnet_10_10_10_0,IP 地址为 10.10.10.0/24:
这里 Gateway 我们使用默认地址 10.10.10.1。
通常我们需要询问网络管理员外网 subnet 的 Gateway IP,然后填在这里。
e)点击 “Next”:
因为我们不会直接为 instance 分配外网 IP,所以不需要 enable DHCP。
f)点击 “Create”:
subnet 创建成功,网关为 10.10.10.1。
g)下面查看控制节点网络结构的变化,执行ovs-vsctl show
命令:
上图所示,br-ex 与 br-int 通过 patch port “phy-br-ex” 和 “int-br-ex” 连接。
3)接下来需要将ext_net连接到 Neutron 的router,这样 instance 才能访问外网:
a)点击菜单 Project -> Network -> Routers 进入 router 列表:
b)点击 router_100_101 的 “Set Gateway” 按钮 :
c)在 “External Network” 下拉列表中选择 ext_net,点击 “Set Gateway”:
d)外网设置成功。点击 “router_100_101” 链接,打开 “Interfaces” 标签页:
router 多了一个新 interface,IP 为 10.10.10.2。
该 interface 用于连接外网 ext_net,对应的 br-ex 的 port “qg-cf54d3ea-6a”。
e)在 router 的 namespace 中查看到 qg-cf54d3ea-6a 已经配置了 IP 10.10.10.2:
router interface 的命名规则如下:
i)如果 interface 用于连接租户网络,命名格式为 qr-xxx。
ii)如果 interface 用于连接外部网络,命名格式为 qg-xxx。
f)查看 router 的路由表信息:
可以看到默认网关为 10.10.10.1。
意味着对于访问 vlan100 和 vlan101 租户网络以外的所有流量,router_100_101 都将转发给 ext_net 的网关 10.10.10.1。
g)现在 router_100_101 已经同时连接了 vlan100, vlan101 和 ext_net 三个网络,如下图所示:
h)我们在 cirros-vm3 上测试一下:
cirros-vm3 位于计算节点,现在已经可以 Ping 到 ext_net 网关 10.10.10.1 了。
i)通过traceroute <IP Address>
命令 查看一下 cirros-vm3 到 10.10.10.1 的路径:
数据包经过两跳到达 10.10.10.1 网关。
i)数据包首先发送到 router_100_101 连接 vlan101 的 interface(172.16.101.1)。
ii)然后通过连接 ext_net 的 interface(10.10.10.2) 转发出去,最后到达 10.10.10.1。
当数据包从 router 连接外网的接口 qg-cf54d3ea-6a 发出的时候,会做一次 Source NAT,将包的源地址修改为 router的接口地址 10.10.10.2,这样就能够保证目的端能够将应答的包发回给 router,然后再转发回源端 instance。 - floating IP
1)通过 SNAT 使得 instance 能够直接访问外网,但外网还不能直接访问 instance。
2)直接访问 instance 指的是通信连接由外网发起,例如从外网 SSH instance。
如果需要从外网直接访问 instance,可以利用 floating IP。
3)Open vSwitch driver 环境中 floating IP 的实现与 Linux Bridge driver 完全一样:都是通过在 router 提供网关的外网 interface 上配置 iptables NAT 规则实现。
4)有关 floating IP 的详细分析可以参考 Linux Bridge 中 floating IP 的相关章节。
2.5.6.5 vxlan network
- Open vSwitch 支持 VXLAN 和 GRE 这两种 overlay network。因为 OpenStack 对于 VXLAN 与 GRE 配置和实现差别不大,这里只讨论如何实施 VXLAN。
- ML2配置ovs vxlan:
1)在 ML2 配置中 enable vxlan network,在 /etc/neutron/plugins/ml2/ml2_conf.ini 设置 vxlan network 相关参数:
2)指定普通用户创建的网络类型为 vxlan,同时 enable l2 population mechanism driver,然后指定 vxlan 的范围(L2 Population 是用来提高 VXLAN 网络 Scalability 的。通常我们说某个系统的 Scalability 好,其意思是:当系统的规模变大时,仍然能够高效地工作。):
上面配置定义了 vxlan vni 的范围是 1001 – 2000,这个范围是针对普通用户在自己的租户里创建 vxlan network 的范围。 因为普通用户创建 network 时不能指定 vni,Neutron 会按顺序自动从这个范围中取值。对于 admin 则没有 vni 范围的限制,admin 可以创建 vni 范围为 1-16777216 的 vxlan network。
3)在 [agent] 中配置启用 vxlan 和 l2population:
4)最后在 [ovs] 中配置 VTEP:
vxlan tunnel 对应的网桥为 br-tun。
local_ip 指定 VTEP 的 IP 地址。
devstack-controller 使用 166.66.16.10,此 IP 配置在网卡 eth1 上。
devstack-compute01 则使用 166.66.16.11,此 IP 配置在网卡 eth1 上。
- 初始网络结构:
Neutron 服务重启后,通过ovs-vsctl show
查看网络配置:
br-int 与 br-tun 通过 patch port “patch-tun” 和 “br-tun” 连接。 - 目前网络结构如下所示:
- 创建vxlan 100_net,并将instance连接到此网络上:
1)创建 vxlan100_net:
打开菜单 Admin -> Networks,点击 “Create Network” 按钮:
2)显示创建页面:
Provider Network Type 选择 “VXLAN”。
Segmentation ID 即 VNI,设置为 100。
3)点击 “Create Network”,vxlan100 创建成功:
4)点击 vxlan100 链接,进入 network 配置页面,目前还没有 subnet。点击 “Create Subnet” 按钮:
5)创建 subnet_172_16_100_0,IP 地址为 172.16.100.0/24:
6)将 instance 连接到 vxlan100_net。launch 新的 instance “cirros-vm1”,“cirros-vm2” 网络选择 vxlan100:
7)cirros-vm1,cirros-vm2 分别部署到控制节点和计算节点,IP 如下:
8)测试 cirros-vm1 和 cirros-vm2 的连通性:
- 底层网络结构分析:
1)在控制节点,执行ovs-vsctl show
:
a)br-int:
br-int 连接了如下 port:
i)tap0d4cb13a-7a 是 vxlan100_net 的 DHCP 服务对应的 interface。
ii)qvoa2ac3b9a-24 将 cirros-vm1 虚拟网卡连接到 vxlan100_net。
b)br-tun:
br-tun 上创建了一个特殊的 port “vxlan-a642100b”,它是 VXLAN 的隧道端点,指定了本地(devstack-controller)节点和远端(devstack-compute1)节点 VTEP 的 IP
2)在计算节点,执行ovs-vsctl show
:
a)br-int:
br-int 上 qvoab219616-01 将 cirros-vm2 虚拟网卡连接到 vxlan100_net。
b)br-tun:
br-tun 上也创建了 port “vxlan-a642100b”,配置内容与控制节点相对,指定了本地(devstack-compute1)节点和远端(devstack-controller)节点 VTEP 的 IP。
3)当前网络结构如下:
需要特别注意的是:无论存在多少个 VXLAN,devstack-controller 与 devstack-compute1 之间所有的数据都只通过 “vxlan-a642100b” 这对 port 上建立的隧道传输。 - flow rule分析:
1)br-int 的 flow rule:
br-int 的 rule 看上去虽然多,其实逻辑很简单,br-int 被当作一个二层交换机,其重要的 rule 是下面这条:
此规则的含义是:根据 vlan 和 mac 进行转发。 - br-tun 的 flow rule:
这些才是真正处理 VXLAN 数据包的 rule,流程如下:
上图各方块中的数字对应 rule 中 table 的序号,比如编号为0的方块对应下面三条 rule。
1)table 0:
结合如下 port 编号:
table 0 flow rule 的含义为:
a)从 port 1(patch-int)进来的包,扔给 table 2 处理:actions=resubmit(,2)
b)从 port 2(vxlan-a642100b)进来的包,扔给 table 4 处理:actions=resubmit(,4)
即第一条 rule 处理来自内部 br-int(这上面挂载着所有的网络服务,包括路由、DHCP 等)的数据;
第二条 rule 处理来自外部 VXLAN 隧道的数据。
2)table 4:
table 4 flow rule 的含义为: 如果数据包的 VXLAN tunnel ID 为 100(tun_id=0x64),action 是添加内部 VLAN ID 1(tag=1),然后扔给 table 10 去学习。
3)table 10:
table 10 flow rule 的含义为: 学习外部(从 tunnel)进来的包,往 table 20 中添加对返程包的正常转发规则,然后从 port 1(patch-int)扔给 br-int。
4)table2:
table 2 flow rule 的含义为:
a)br-int 发过来数据如果是单播包,扔给 table 20 处理:resubmit(,20)
b)br-int 发过来数据如果是多播或广播包,扔 table 22 处理:resubmit(,22)
5)table20:
table 20 flow rule 的含义为:
a)第一条规则就是 table 10 学习来的结果。内部 VLAN 号为 1(tag=1),目标 MAC 是
fa:16:3e:fd:8a:ed(virros-vm2)的数据包,即发送给 virros-vm2 的包,action 是去掉 VLAN
号,添加 VXLAN tunnel ID 100(十六进制 0x64),并从 port 2 (tunnel 端口 vxlan-a642100b)
发出。
b)对于没学习到规则的数据包,则扔给 table 22 处理。
6)table22:
table 22 flow rule 的含义为: 如果数据包的内部 VLAN 号为 1(tag=1),action 是去掉 VLAN
号,添加 VXLAN tunnel ID 100(十六进制 0x64),并从 port 2 (tunnel 端口 vxlan-a642100b)
发出。 - VXLAN 的路由和 floating IP 支持
对于多 VXLAN 之间的 routing 以及 floating IP,实现方式与 vlan 非常类似,这里不再赘述,请参看前面 vlan 相关章节。