360 OpenStack支持IP SAN存储实现
csdh11 2025-04-30 16:10 9 浏览
背景:
为了更多的满足TOB场景下的需求,360虚拟化团队一直不断丰富完善openstack侧的功能,近期对接过信创存储腾凌存储等商业存储,所以梳理一下整个流程,下面进入正文从架构以及源码了解openstack如何支持SAN存储。
一、相关概念
提到 IP SAN 必然会想到磁盘阵列,磁盘阵列有三种架构分别为:DAS,NAS,SAN。而SAN里面主要又分为IP SAN和FC SAN。
FC-SAN(Fibre Channel Storage Area Network)是一种基于光纤通道技术的存储网络,它将存储设备和服务器连接在一起,形成一个高速、高性能的存储区域网络。FC-SAN的核心是光纤通道交换机,它实现了光纤通道协议,使得存储设备和服务器之间的数据传输更加可靠和高效。
IP-SAN(Internet Protocol Storage Area Network)是一种基于IP协议的存储网络,它将存储设备、连接设备和接口集成在高速网络中。IP-SAN使用IP网络将存储设备连接在一起,实现数据的可靠传输和共享。由于IP网络具有广泛的普及性和互操作性,IP-SAN具有较好的扩展性和灵活性。
DAS(Direct Attached Storage)是一种直接附加存储技术,它将存储设备通过电缆直接连接到服务器上。DAS的优点是简单、成本低,适用于小型网络和单机环境。但是,DAS的缺点也很明显,如存储容量受限、扩展性差、数据共享困难等。
NAS(Network Attached Storage)是一种网络附加存储技术,它将存储设备连接到现有的网络上,提供数据和文件服务。NAS实际上是一个专门优化了的文件服务器,具有独立的操作系统和文件系统。NAS的优点是易于部署和管理,可以实现数据的集中存储和共享。但是,NAS的缺点是性能受限于网络带宽和稳定性,不适合大规模数据存储和高性能计算。
二、OpenStack实现
2.1 cinder侧实现
我们知道在openstack中cinder负责存储volume 的生命周期管理,而cinder中cinder-volume负责转发控制面请求从而对存储执行 action,对于每一种存储介质,cinder-volume需要调用对应的driver才可以,这里以最近对接过的信创腾凌ip san 存储为例讲解实现。
我们首先需要配置一个cinder 的volume backend tldriver
[tldriver]
volume_backend_name = tldriver
volume_driver = cinder.volume.drivers.tengling.tengling_driver.TenglingISCSIDriver
tengling_sanip = {{ tengling_sanip }}
tengling_username = {{ tengling_username }}
tengling_password = {{ tengling_password }}
tengling_storagepool = {{ tengling_storagepool }}
tengling_max_clone_depth = {{ tengling_max_clone_depth }}
tengling_flatten_volume_from_snapshot=True
我们以创建一个volume从源码刨析整个的流程,前面将新建的volume type指定capability 为backend tldriver,这样scheduler就能调度到我们的新存储上了。cinder scheduler 通过rpc 请求volume 调用 volumeManager 的create_volume函数
@objects.Volume.set_workers
def create_volume(self, context, volume, request_spec=None,
filter_properties=None, allow_reschedule=True):
..........
try:
# NOTE(flaper87): Driver initialization is
# verified by the task itself.
flow_engine = create_volume.get_flow(
context_elevated,
self,
self.db,
self.driver,
self.scheduler_rpcapi,
self.host,
volume,
allow_reschedule,
context,
request_spec,
filter_properties,
image_volume_cache=self.image_volume_cache,
)
except Exception:
msg = _("Create manager volume flow failed.")
LOG.exception(msg, resource={'type': 'volume', 'id': volume.id})
raise exception.CinderException(msg)
cinder volume创建volume时通过task flow执行了核心任务 CreateVolumeFromSpecTask,这里用户创建了一个系统盘,指定了image,所以执行了_create_from_image ,最终调用了
_create_from_image_cache_or_download 方法
class CreateVolumeFromSpecTask(flow_utils.CinderTask):
..........
def execute(self, context, volume, volume_spec):
..........
elif create_type == 'image':
model_update = self._create_from_image(context,
volume,
**volume_spec)
..........
def _create_from_image(self, context, volume,
image_location, image_id, image_meta,
image_service, **kwargs):
..........
if not cloned:
model_update = self._create_from_image_cache_or_download(
context,
volume,
image_location,
image_id,
image_meta,
image_service)
def _create_from_image_cache_or_download(self, context, volume,
image_location, image_id,
image_meta, image_service,
update_cache=False):
..........
try:
if not cloned:
try:
with image_utils.TemporaryImages.fetch(
image_service, context, image_id,
backend_name) as tmp_image:
if CONF.verify_glance_signatures != 'disabled':
# Verify image signature via reading content from
# temp image, and store the verification flag if
# required.
verified = \
image_utils.verify_glance_image_signature(
context, image_service,
image_id, tmp_image)
self.db.volume_glance_metadata_bulk_create(
context, volume.id,
{'signature_verified': verified})
# Try to create the volume as the minimal size,
# then we can extend once the image has been
# downloaded.
data = image_utils.qemu_img_info(tmp_image)
virtual_size = image_utils.check_virtual_size(
data.virtual_size, volume.size, image_id)
if should_create_cache_entry:
if virtual_size and virtual_size != original_size:
volume.size = virtual_size
volume.save()
model_update = self._create_from_image_download(
context,
volume,
image_location,
image_meta,
image_service
)
finally:
# If we created the volume as the minimal size, extend it back to
# what was originally requested. If an exception has occurred or
# extending it back failed, we still need to put this back before
# letting it be raised further up the stack.
if volume.size != original_size:
try:
self.driver.extend_volume(volume, original_size)
finally:
volume.size = original_size
volume.save()
..........
在
_create_from_image_cache_or_download 中会将镜像下载到本地临时文件,再通过 qemu 获取info信息,最终调用了
_create_from_image_download。在
_create_from_image_download中调用的本backend的driver执行create_volume 操作,并调用 copy_image_to_volume将镜像数据写入到volume
def _create_from_image_download(self, context, volume, image_location,
image_meta, image_service):
..........
model_update = self.driver.create_volume(volume) or {}
self._cleanup_cg_in_volume(volume)
model_update['status'] = 'downloading'
try:
volume.update(model_update)
volume.save()
except exception.CinderException:
LOG.exception("Failed updating volume %(volume_id)s with "
"%(updates)s",
{'volume_id': volume.id,
'updates': model_update})
try:
volume_utils.copy_image_to_volume(self.driver, context, volume,
image_meta, image_location,
image_service)
except exception.ImageTooBig:
with excutils.save_and_reraise_exception():
LOG.exception("Failed to copy image to volume "
"%(volume_id)s due to insufficient space",
{'volume_id': volume.id})
return model_update
def copy_image_to_volume(driver, context, volume, image_meta, image_location,
image_service):
..........
try:
image_encryption_key = image_meta.get('cinder_encryption_key_id')
if volume.encryption_key_id and image_encryption_key:
# If the image provided an encryption key, we have
# already cloned it to the volume's key in
# _get_encryption_key_id, so we can do a direct copy.
driver.copy_image_to_volume(
context, volume, image_service, image_id)
elif volume.encryption_key_id:
# Creating an encrypted volume from a normal, unencrypted,
# image.
driver.copy_image_to_encrypted_volume(
context, volume, image_service, image_id)
else:
driver.copy_image_to_volume(
context, volume, image_service, image_id)
这里调用了腾凌存储的
cinder.volume.drivers.tengling.tengling_driver.TenglingISCSIDriver.create_volume 创建云盘,并调用腾凌driver执行了copy_image_to_volume。这里TenglingISCSIDriver继承自了driver.ISCSIDriver ,所以调用了原生ISCSIDriver的
def create_volume(self, volume):
"""Create a volume."""
volume_type = self._get_volume_type(volume)
opts = self._get_volume_params(volume_type)
if (opts.get('hypermetro') == 'true'
and opts.get('replication_enabled') == 'true'):
err_msg = _("Hypermetro and Replication can not be "
"used in the same volume_type.")
LOG.error(err_msg)
raise exception.VolumeBackendAPIException(data=err_msg)
lun_params, lun_info, model_update = (
self._create_base_type_volume(opts, volume, volume_type))
model_update = self._add_extend_type_to_volume(opts, lun_params,
lun_info, model_update)
return model_update
原生ISCSIDriver 会通过os_brick模块远程iscsi挂载磁盘到本地
class ISCSIDriver(VolumeDriver):
.........
def copy_image_to_volume(self, context, volume, image_service, image_id):
"""Fetch image from image_service and write to unencrypted volume.
This does not attach an encryptor layer when connecting to the volume.
"""
self._copy_image_data_to_volume(
context, volume, image_service, image_id, encrypted=False)
.........
def _copy_image_data_to_volume(self, context, volume, image_service,
image_id, encrypted=False):
"""Fetch the image from image_service and write it to the volume."""
LOG.debug('copy_image_to_volume %s.', volume['name'])
use_multipath = self.configuration.use_multipath_for_image_xfer
enforce_multipath = self.configuration.enforce_multipath_for_image_xfer
properties = utils.brick_get_connector_properties(use_multipath,
enforce_multipath)
attach_info, volume = self._attach_volume(context, volume, properties) # 这里会挂载远程disk
try:
if encrypted:
encryption = self.db.volume_encryption_metadata_get(context,
volume.id)
utils.brick_attach_volume_encryptor(context,
attach_info,
encryption)
try:
image_utils.fetch_to_raw(
context,
image_service,
image_id,
attach_info['device']['path'],
self.configuration.volume_dd_blocksize,
size=volume['size']) #这里写入镜像数据
except exception.ImageTooBig:
with excutils.save_and_reraise_exception():
LOG.exception("Copying image %(image_id)s "
"to volume failed due to "
"insufficient available space.",
{'image_id': image_id})
finally:
if encrypted:
utils.brick_detach_volume_encryptor(attach_info,
encryption)
finally:
self._detach_volume(context, attach_info, volume, properties,
force=True)
fetch_to_volume_format 中会fetch镜像文件到本地临时目录,最后通过执行qemu-img convert 命令将镜像临时文件数据写入到本地iscsi远程磁盘中,至此虚机的系统盘数据写入完成。
def fetch_to_volume_format(context, image_service,
image_id, dest, volume_format, blocksize,
volume_subformat=None, user_id=None,
project_id=None, size=None, run_as_root=True):
.........
convert_image(tmp, dest, volume_format,
out_subformat=volume_subformat,
src_format=disk_format,
run_as_root=run_as_root)
def _convert_image(prefix, source, dest, out_format,
out_subformat=None, src_format=None,
run_as_root=True, cipher_spec=None, passphrase_file=None):
cmd = _get_qemu_convert_cmd(source, dest,
out_format=out_format,
src_format=src_format,
out_subformat=out_subformat,
cache_mode=cache_mode,
prefix=prefix,
cipher_spec=cipher_spec,
passphrase_file=passphrase_file) #拼接 qemu-img convert 命令
最后cinder侧更新数据库,至此cinder侧工作完成了,概括性的流程如下:
2.2 nova侧实现
nova侧在给虚机挂载磁盘时,nova-compute收到请求后attach volume的请求后调用nova.compute.manager.ComputeManager.attach_volume
def _attach_volume(self, context, instance, bdm):
context = context.elevated()
LOG.info('Attaching volume %(volume_id)s to %(mountpoint)s',
{'volume_id': bdm.volume_id,
'mountpoint': bdm['mount_device']},
instance=instance)
compute_utils.notify_about_volume_attach_detach(
context, instance, self.host,
action=fields.NotificationAction.VOLUME_ATTACH,
phase=fields.NotificationPhase.START,
volume_id=bdm.volume_id)
try:
bdm.attach(context, instance, self.volume_api, self.driver,
do_driver_attach=True)
................
nova.virt.block_device.
DriverVolumeBlockDevice.attach中会 调用 _legacy_volume_attach 进行挂载磁盘
@update_db
def attach(self, context, instance, volume_api, virt_driver,
do_driver_attach=False, **kwargs):
..................
# Check to see if we need to lock based on the shared_targets value.
# Default to False if the volume does not expose that value to maintain
# legacy behavior.
if volume.get('shared_targets', False):
# Lock the attach call using the provided service_uuid.
@utils.synchronized(volume['service_uuid'])
def _do_locked_attach(*args, **_kwargs):
self._do_attach(*args, **_kwargs)
_do_locked_attach(context, instance, volume, volume_api,
virt_driver, do_driver_attach)
else:
# We don't need to (or don't know if we need to) lock.
self._do_attach(context, instance, volume, volume_api,
virt_driver, do_driver_attach)
def _do_attach(self, context, instance, volume, volume_api, virt_driver,
do_driver_attach):
"""Private method that actually does the attach.
This is separate from the attach() method so the caller can optionally
lock this call.
"""
context = context.elevated()
connector = virt_driver.get_volume_connector(instance)
if not self['attachment_id']:
self._legacy_volume_attach(context, volume, connector, instance,
volume_api, virt_driver,
do_driver_attach)
else:
self._volume_attach(context, volume, connector, instance,
volume_api, virt_driver,
self['attachment_id'],
do_driver_attach)
legacyvolume_attach 会 调用cinder 的 initialize_connection 获取volume挂载的connection info,这里因为是iscsi 云盘,cinder会返回iSCSI的 connection info。
def _legacy_volume_attach(self, context, volume, connector, instance,
volume_api, virt_driver,
do_driver_attach=False):
volume_id = volume['id']
connection_info = volume_api.initialize_connection(context,
volume_id,
connector)
..................
# If do_driver_attach is False, we will attach a volume to an instance
# at boot time. So actual attach is done by instance creation code.
if do_driver_attach:
encryption = encryptors.get_encryption_metadata(
context, volume_api, volume_id, connection_info)
try:
virt_driver.attach_volume(
context, connection_info, instance,
self['mount_device'], disk_bus=self['disk_bus'],
device_type=self['device_type'], encryption=encryption)
except Exception:
with excutils.save_and_reraise_exception():
LOG.exception("Driver failed to attach volume "
"%(volume_id)s at %(mountpoint)s",
{'volume_id': volume_id,
'mountpoint': self['mount_device']},
instance=instance)
volume_api.terminate_connection(context, volume_id,
connector)
..................
if volume['attach_status'] == "detached":
# NOTE(mriedem): save our current state so connection_info is in
# the database before the volume status goes to 'in-use' because
# after that we can detach and connection_info is required for
# detach.
self.save()
try:
volume_api.attach(context, volume_id, instance.uuid,
self['mount_device'], mode=mode)
virt_driver.attach_volume 会调用libvirt 挂载磁盘 ,self._connect_volume 会依据connection info判断为iSCSI driver 会Calling os-brick to attach iSCSI Volume ,最终libvirt 在线添加了disk到虚机里面,至此虚机侧挂载完成!
def attach_volume(self, context, connection_info, instance, mountpoint,
disk_bus=None, device_type=None, encryption=None):
guest = self._host.get_guest(instance)
disk_dev = mountpoint.rpartition("/")[2]
bdm = {
'device_name': disk_dev,
'disk_bus': disk_bus,
'device_type': device_type}
..................
self._connect_volume(context, connection_info, instance,
encryption=encryption)
..................
try:
state = guest.get_power_state(self._host)
live = state in (power_state.RUNNING, power_state.PAUSED)
guest.attach_device(conf, persistent=True, live=live)
总结:
IP SAN 由于基于传统IP网络,所以优势是成本低、易维护且比较灵活,但同时也限制了他只适合应用于对于性能要求并不是太高的场景,性能上比较不如FS SAN以及nvmf等,因此IP SAN 适合于中小型应用场景,所以需要针对不同的使用场景下进行选择不同的存储协议。
本文借以兼容腾凌存储梳理了openstack侧虚机挂载使用商业 IP SAN存储的整个流程,对于理解包括支持其他类型的SAN/NVMF等存储有一定的参考意义。
相关推荐
- SpringBoot+LayUI后台管理系统开发脚手架
-
源码获取方式:关注,转发之后私信回复【源码】即可免费获取到!项目简介本项目本着避免重复造轮子的原则,建立一套快速开发JavaWEB项目(springboot-mini),能满足大部分后台管理系统基础开...
- Spring Boot+Vue全栈开发实战,中文版高清PDF资源
-
SpringBoot+Vue全栈开发实战,中文高清PDF资源,需要的可以私我:)SpringBoot致力于简化开发配置并为企业级开发提供一系列非业务性功能,而Vue则采用数据驱动视图的方式将程序...
- 2021年超详细的java学习路线总结—纯干货分享
-
本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础...
- 探秘Spring Cache:让Java应用飞起来的秘密武器
-
探秘SpringCache:让Java应用飞起来的秘密武器在当今快节奏的软件开发环境中,性能优化显得尤为重要。SpringCache作为Spring框架的一部分,为我们提供了强大的缓存管理能力,让...
- 3,从零开始搭建SSHM开发框架(集成Spring MVC)
-
目录本专题博客已共享在(这个可能会更新的稍微一些)https://code.csdn.net/yangwei19680827/maven_sshm_blog...
- Spring Boot中如何使用缓存?超简单
-
SpringBoot中的缓存可以减少从数据库重复获取数据或执行昂贵计算的需要,从而显著提高应用程序的性能。SpringBoot提供了与各种缓存提供程序的集成,您可以在应用程序中轻松配置和使用缓...
- 我敢保证,全网没有再比这更详细的Java知识点总结了,送你啊
-
接下来你看到的将是全网最详细的Java知识点总结,全文分为三大部分:Java基础、Java框架、Java+云数据小编将为大家仔细讲解每大部分里面的详细知识点,别眨眼,从小白到大佬、零基础到精通,你绝...
- 1,从零开始搭建SSHM开发框架(环境准备)
-
目录本专题博客已共享在https://code.csdn.net/yangwei19680827/maven_sshm_blog1,从零开始搭建SSHM开发框架(环境准备)...
- 做一个适合二次开发的低代码平台,把程序员从curd中解脱出来-1
-
干程序员也有好长时间了,大多数时间都是在做curd。现在想做一个通用的curd平台直接将我们解放出来;把核心放在业务处理中。用过代码生成器,在数据表设计好之后使用它就可以生成需要的controller...
- 设计一个高性能Java Web框架(java做网站的框架)
-
设计一个高性能JavaWeb框架在当今互联网高速发展的时代,构建高性能的JavaWeb框架对于提升用户体验至关重要。本文将从多个角度探讨如何设计这样一个框架,让我们一起进入这段充满挑战和乐趣的旅程...
- 【推荐】强&牛!一款开源免费的功能强大的代码生成器系统!
-
今天,给大家推荐一个代码生成器系统项目,这个项目目前收获了5.3KStar,个人觉得不错,值得拿出来和大家分享下。这是我目前见过最好的代码生成器系统项目。功能完整,代码结构清晰。...
- Java面试题及答案总结(2025版持续更新)
-
大家好,我是Java面试分享最近很多小伙伴在忙着找工作,给大家整理了一份非常全面的Java面试场景题及答案。...
- Java开发网站架构演变过程-从单体应用到微服务架构详解
-
Java开发网站架构演变过程,到目前为止,大致分为5个阶段,分别为单体架构、集群架构、分布式架构、SOA架构和微服务架构。下面玄武老师来给大家详细介绍下这5种架构模式的发展背景、各自优缺点以及涉及到的...
- 本地缓存GuavaCache(一)(guava本地缓存原理)
-
在并发量、吞吐量越来越大的情况下往往是离不开缓存的,使用缓存能减轻数据库的压力,临时存储数据。根据不同的场景选择不同的缓存,分布式缓存有Redis,Memcached、Tair、EVCache、Aer...
- 一周热门
- 最近发表
- 标签列表
-
- mydisktest_v298 (34)
- document.appendchild (35)
- 头像打包下载 (61)
- acmecadconverter_8.52绿色版 (39)
- word文档批量处理大师破解版 (36)
- server2016安装密钥 (33)
- mysql 昨天的日期 (37)
- parsevideo (33)
- 个人网站源码 (37)
- centos7.4下载 (33)
- mysql 查询今天的数据 (34)
- intouch2014r2sp1永久授权 (36)
- 先锋影音源资2019 (35)
- jdk1.8.0_191下载 (33)
- axure9注册码 (33)
- pts/1 (33)
- spire.pdf 破解版 (35)
- shiro jwt (35)
- sklearn中文手册pdf (35)
- itextsharp使用手册 (33)
- 凯立德2012夏季版懒人包 (34)
- 冒险岛代码查询器 (34)
- 128*128png图片 (34)
- jdk1.8.0_131下载 (34)
- dos 删除目录下所有子目录及文件 (36)