Cinder 架構分析、高可用部署與核心功能解析


目錄

Cinder

操作系統獲得外部存儲空間一般有以下兩種方式:

  1. Block Storage:通過某種協議(e.g.SAS、SCSI、SAN、iSCSI)從后端存儲 Assigned、Attached 塊設備(Volume),然后分區格式化、創建文件系統並 mount 到操作系統,然后就可以在此文件系統之上存儲數據,或者也可以直接使用裸硬盤來存儲數據(e.g. 數據庫系統)

  2. FileSystem Storage:通過 NFS、CIFS 等協議,mount 遠程文件系統到本地操作系統。NAS、NFS 服務器,以及各種分布式文件系統提供的都是這種存儲。

  3. Object Storage:對象存儲是以對象形式存儲數據的存儲系統,最大的優勢就是可以讓用戶更加靈活的處理海量數據。操作系統客戶端可以通過對象存儲提供的存儲網關接口(一般是 HTTP/S)來上傳或下載存儲數據。

而本篇的主角就是 Cinder,OpenStack Block Storage as a Services(塊存儲即服務)。

Cinder is the OpenStack Block Storage service for providing volumes to Nova virtual machines, Ironic bare metal hosts, containers and more. Some of the goals of Cinder are to be/have:

  • Component based architecture: Quickly add new behaviors
  • Highly available: Scale to very serious workloads
  • Fault-Tolerant: Isolated processes avoid cascading failures
  • Recoverable: Failures should be easy to diagnose, debug, and rectify
  • Open Standards: Be a reference implementation for a community-driven api
    — — 官方文檔:https://docs.openstack.org/cinder/latest/

需要注意的是,正如 Nova 本身不提供 Hypervisor 技術一般,Cinder 自身也不提供存儲技術,而是作為一個抽象的中間管理層,北向提供穩定而統一的 Block Storage 資源模型、南向通過 Plug-ins&Drivers 模型對接多樣化的后端存儲設備(e.g. LVM、CEPH、NetApp、Datastore etc.)。所以 Cinder 的精華從不在於存儲技術,而是在於對 Block Storage as a Service 需求(創建、刪除、快照、掛載、分離、備份卷)的抽象與理解。

  • 存儲資源模型(e.g. Volume, Snapshot, Pool etc.)
  • 兼容多樣化存儲設備的 “邏輯驅動層”
  • 分布式架構
  • 高可用設計

Cinder 的軟件架構

在這里插入圖片描述
在這里插入圖片描述

cinder-api

a WSGI app that authenticates and routes requests throughout the Block Storage service. It supports the OpenStack APIs only, although there is a translation that can be done through Compute’s EC2 interface, which calls in to the Block Storage client.

對外提供穩定而統一的北向 RESTful API,cinder-api service 服務進程通常運行在控制節點,支持多 Workers 進程(通過配置項 osapi_volume_workers 設定)。接收到的合法請求會經由 MQ 傳遞到 cinder-volume 執行。Cinder API 現存 v2(DEPRECATED)、v3(CURRENT) 兩個版本,可以通過配置文件來啟用。

Cinder API 官方文檔https://developer.openstack.org/api-ref/block-storage/

NOTE:從 API 的官方文檔,或從 /opt/stack/cinder/cinder/db/sqlalchemy/models.py 都可以從側面對 Cinder 的資源模型定義有所了解。

cinder-scheduler

schedules and routes requests to the appropriate volume service. Depending upon your configuration, this may be simple round-robin scheduling to the running volume services, or it can be more sophisticated through the use of the Filter Scheduler. The Filter Scheduler is the default and enables filters on things like Capacity, Availability Zone, Volume Types, and Capabilities as well as custom filters.

如果說 cinder-api 接收的是關於 “創建” 的請求(e.g. Create Volume),那么該請求就會通過 MQ 轉發到 cinder-scheduler service 服務進程,cinder-scheduler 與 nova-scheduler 一般,顧名思義是調度的層面。通過 Filters 選擇最 “合適” 的 Storage Provider Node 來對請求資源(e.g. Volume)進行創建。不同的 Filters 具有不同的過濾(調度)算法,所謂的 “合適” 就是達到客戶預期的結果,用戶還可以自定義 Filter Class 來實現符合自身需求的過濾器,讓調度更加靈活。與 nova-scheduler 一般,cinder-scheduler 同樣需要維護調度對象(存儲節點)“實時” 狀態,cinder-volume service 會定期的向 cinder-scheduler service 上報存儲節點狀態(注:這實際上是通過后端存儲設備的驅動程序上報了該設備的狀態)。

  1. 首先判斷存儲節點狀態,只有狀態為 up 的存儲節點才會被考慮。
  2. 創建 Volume 時,根據 Filter 和 Weight 算法選出最優存儲節點。
  3. 遷移 Volume 時,根據 Filter 和 Weight 算法來判斷目的存儲節點是否符合要求。

現支持的 Filters 清單如下

  • AffinityFilter
  • AvailabilityZoneFilter:可以在 cinder-volume(存儲節點)的 cinder.conf 中設置 storage_availability_zone=az1 來指定該存儲節點的 Zone。配合 AvailabilityZoneFilter,用戶創建 Volume 時選擇期望的 AZ,就可以實現將 Volume 創建到指定的 AZ 中了。 默認 Zone 為 nova。
  • CapabilitiesFilter:不同的 Volume Provider 自然具有不同的特點,用戶可以通過設置 Volume Type 的 extra specs 來描述這些特性,該 Filter 就是為了通過這些特性來過濾存儲節點。
  • CapacityFilter:根據存儲節點上的剩余空間(free_capacity_gb)大小來進行過濾,存儲節點的 free_capacity_gb 正是由 cinder-volume service 上報的。
  • DriverFilter
  • IgnoreAttemptedHostsFilter
  • InstanceLocalityFilter
  • JsonFilter

通過 nova-scheduler service 的配置文件 cinder.conf 指定你需要使用的 Filters 列表,e.g.

[DEFAULT]
...
scheduler_driver =cinder.scheduler.filter_scheduler.FilterScheduler
scheduler_default_filters =AvailabilityZoneFilter,CapacityFilter,CapabilitiesFilter

現支持的 Weights 算法如下

  • AllocatedCapacityWeigher:有最先可使用空間的權重大,相關配置項有 allocated_capacity_weight_multiplier
  • CapacityWeigher:有最大可使用空間的權重大,相關配置項有 capacity_weight_multiplier
  • ChanceWeigher:隨意選取
  • VolumeNumberWeigher
  • GoodnessWeigher

當然了,在實際使用中也存在不需要或者說不希望進行調度的情況,還是正如 Nova 一般,創建虛擬機時可以通過 --availability-zone AZ:Host:Node 來強制指定計算節點,Cinder 也有這般手段。

cinder-volume

manages Block Storage devices, specifically the back-end devices themselves.

cinder-volume service 是 Cinder 的核心服務進程,運行該服務進程的節點都可以被稱之為存儲節點。cinder-volume 通過抽象出統一的 Back-end Storage Driver 層,讓不同存儲廠商得以通過提供各自的驅動程序來對接自己的后端存儲設備,實現即插即用(通過配置文件指定),多個這樣的存儲節點共同構成了一個龐大而復雜多樣的存儲資源池系統。

Driver 框架

OpenStack 作為開放的 Infrastracture as a Service 雲操作系統,支持業界各種優秀的技術,這些技術可能是開源免費的,也可能是商業收費的。 這種開放的架構使得 OpenStack 保持技術上的先進性,具有很強的競爭力,同時又不會造成廠商鎖定(Lock-in)。

以 Cinder 為例,存儲節點支持多種 Volume Provider,包括 LVM、NFS、Ceph、GlusterFS 以及 EMC、IBM 等商業存儲系統。 cinder-volume 為這些 Volume Provider 抽象了統一的 Driver 接口,Volume Provider 只需要實現這些接口,就可以以 Driver 的形式即插即(volume_driver 配置項)用到 OpenStack 中。下面是 Cinder Driver 的架構示意圖:

在這里插入圖片描述
在 cinder-volume 的配置文件 /etc/cinder/cinder.conf 中設置該存儲節點使用的 Volume Provider Driver 類型:

# 啟用 LVM Provider(默認)
volume_driver=cinder.volume.drivers.lvm.LVMVolumeDriver

Plugin 框架

Driver 和 Plugin 通常不會分家,Driver 是由各存儲廠商提供的,那么 Plugin(插槽)就應該有 Cinder 的提供。
在這里插入圖片描述
根據 FileSystem Storage 和 Block Storage 兩個不同類型的外部存儲系統,Cinder Plugins 也提供了 FileSystem based 和 Block based 兩種不同類型 Plugin。除此之外,Cinder Plugins 還提供了 iSCSC、FC、NFS 等常用的數據傳輸協議 Plugin 框架,上傳邏輯得以根據實際情況來使用(Attached/Dettached)存儲資源。

cinder-backup

provides a means to back up a Block Storage volume to OpenStack Object Storage (swift).

提供 Volume 的備份功能,支持將 Volume 備份到對象存儲中(e.g. Swift、Ceph、IBM TSM、NFS),也支持從備份 Restore 成為 Volume。

Cinder Backup 架構實現
在這里插入圖片描述

Volume Provider

Volume Provider 不屬於 Cinder 架構的組件,其含義是真實的物理數據存儲設備,為 Volume 提供物理存儲空間,故又稱 Backend Storage。Cinder 支持多種 Volume Provider,每種 Volume Provider 都通過自己的 Driver 與 cinder-volume 通信。之所以單獨列出 Volume Provider,是為了強調 Cinder 本身並不具備存儲技術,真實的存儲資源,由后端 Volume Provider 提供。

Cinder 與 Volume Provider 之間的關系
在這里插入圖片描述

中間件

  • Messaging queue:Routes information between the Block Storage processes.
  • DB:sql database for data storage. Used by all components (LINKS NOT SHOWN).

創建 Volume 流程分析

創建 Volume 是 Cinder 的頭一號功能,自然花樣也就多了。

  • 創建 RAW 格式的卷
  • 從快照創建卷
  • 從已有卷創建卷(克隆)
  • 從 Image 創建卷
  • source_replica 創建卷
# /opt/stack/cinder/cinder/volume/flows/manager/create_volume.py

        # 根據不同的類型有不同的創建執行細節
        create_type = volume_spec.pop('type', None)
        LOG.info("Volume %(volume_id)s: being created as %(create_type)s "
                 "with specification: %(volume_spec)s",
                 {'volume_spec': volume_spec, 'volume_id': volume_id,
                  'create_type': create_type})
        if create_type == 'raw':
            model_update = self._create_raw_volume(
                context, volume, **volume_spec)
        elif create_type == 'snap':
            model_update = self._create_from_snapshot(context, volume,
                                                      **volume_spec)
        elif create_type == 'source_vol':
            model_update = self._create_from_source_volume(
                context, volume, **volume_spec)
        elif create_type == 'image':
            model_update = self._create_from_image(context,
                                                   volume,
                                                   **volume_spec)
        elif create_type == 'backup':
            model_update, need_update_volume = self._create_from_backup(
                context, volume, **volume_spec)
            volume_spec.update({'need_update_volume': need_update_volume})
        else:
            raise exception.VolumeTypeNotFound(volume_type_id=create_type)

這里我們先不考慮這些 “花樣” 底層細節的區別,主要關注 create_volume 在 Cinder 所有服務部件之間的流轉過程。
在這里插入圖片描述

cinder-api 階段

cinder-api service 接收 Post /v3/{project_id}/volumes 請求,經過了一系列的 Body 數據校驗、數據類型轉換(UUID => Object Model)和操作授權校驗(context.authorize)之后,啟動 volume_create_api flow,此 Flow 中包含了下列 Tasks:

  1. ExtractVolumeRequestTask:獲取(Extract)、驗證(Validates)create volume 在 cinder-api 階段相關的信息
  2. QuotaReserverTask:預留配額
  3. EntryCreateTask:在數據庫中創建 Volume 條目
  4. QuotaCommitTask:確認配額
  5. VolumeCastTask:發出一個 Cast 異步請求,將創建請求丟到 MQ,最終被 cinder-scheduler service 接收

自此 volume_create_api flow 完成了 pending -> running -> success 的整個流程,cinder-api 階段結束。

需要注意的是,正如上文提到過的 create volume 也存在不需要進入 cinder-scheduler 的情況:

# /opt/stack/cinder/cinder/volume/api.py

            # sched_rpcapi 可能為 None
            sched_rpcapi = (self.scheduler_rpcapi if (
                            not cgsnapshot and not source_cg and
                            not group_snapshot and not source_group)
                            else None)
            volume_rpcapi = (self.volume_rpcapi if (
                             not cgsnapshot and not source_cg and
                             not group_snapshot and not source_group)
                             else None)
            flow_engine = create_volume.get_flow(self.db,
                                                 self.image_service,
                                                 availability_zones,
                                                 create_what,
                                                 sched_rpcapi,
                                                 volume_rpcapi)

cinder-scheduler 階段

cinder-scheduler service 接收到了 create volume request 之后也會啟動一個 volume_create_scheduler flow,此 Flow 包含了一下 Tasks:

  1. ExtraceSchedulerSpecTask:將 request body 轉換成為 Scheduler Filter 中通用的 RequestSpec 數據結構(實例對象)。
  2. SchedulerCreateVolumeTask:完成 Filter 和 Weight 的調度算法。

最終在 cinder-scheduler service 選出一個最佳的存儲節點(cinder-volume),並繼續講 create volume request 通過 RPC 丟到選中的 cinder-volume service 接收。

cinder-volume 階段

同樣的,cinder-volume service 還是啟用了 TaskFlow:volume_create_manager,該 Flow 具有以下 Tasks:

  1. ExtractVolumeRefTask:Extracts volume reference for given volume id
  2. OnFailureRescheduleTask:Triggers a rescheduling request to be sent when reverting occurs
  3. ExtractVolumeSpecTask:Extracts a spec of a volume to be created into a common structure
  4. NotifyVolumeActionTask:Performs a notification about the given volume when called.
  5. CreateVolumeFromSpecTask:Creates a volume from a provided specification.
  6. CreateVolumeOnFinishTask:On successful volume creation this will perform final volume actions.

其中最主要的我們關注 CreateVolumeFromSpecTask,該 Task 調用了后端存儲設備的 Driver 真正執行 Volume 創建的任務。

# /opt/stack/cinder/cinder/volume/flows/manager/create_volume.py

    def _create_raw_volume(self, context, volume, **kwargs):
        try:
            # 后端存儲設備的驅動程序
            ret = self.driver.create_volume(volume)
        except Exception as ex:
            with excutils.save_and_reraise_exception():
                self.message.create(
                    context,
                    message_field.Action.CREATE_VOLUME_FROM_BACKEND,
                    resource_uuid=volume.id,
                    detail=message_field.Detail.DRIVER_FAILED_CREATE,
                    exception=ex)
        finally:
            self._cleanup_cg_in_volume(volume)
        return ret

這里以 LVM Backend 為例,該 Task 的本質就是調用操作系統指令 lvcreate 從 Backend 對應的 VG 中划分出 LV(Volume)。

# /opt/stack/cinder/cinder/brick/local_dev/lvm.py

    def create_volume(self, name, size_str, lv_type='default', mirror_count=0):
        """Creates a logical volume on the object's VG. :param name: Name to use when creating Logical Volume :param size_str: Size to use when creating Logical Volume :param lv_type: Type of Volume (default or thin) :param mirror_count: Use LVM mirroring with specified count """

        if lv_type == 'thin':
            pool_path = '%s/%s' % (self.vg_name, self.vg_thin_pool)
            cmd = LVM.LVM_CMD_PREFIX + ['lvcreate', '-T', '-V', size_str, '-n',
                                        name, pool_path]
        else:
            cmd = LVM.LVM_CMD_PREFIX + ['lvcreate', '-n', name, self.vg_name,
                                        '-L', size_str]

        if mirror_count > 0:
            cmd.extend(['--type=mirror', '-m', mirror_count, '--nosync',
                        '--mirrorlog', 'mirrored'])
            terras = int(size_str[:-1]) / 1024.0
            if terras >= 1.5:
                rsize = int(2 ** math.ceil(math.log(terras) / math.log(2)))
                # NOTE(vish): Next power of two for region size. See:
                # http://red.ht/U2BPOD
                cmd.extend(['-R', str(rsize)])

        try:
            # 執行組裝好的 CLI
            self._execute(*cmd,
                          root_helper=self._root_helper,
                          run_as_root=True)
        except putils.ProcessExecutionError as err:
            LOG.exception('Error creating Volume')
            LOG.error('Cmd :%s', err.cmd)
            LOG.error('StdOut :%s', err.stdout)
            LOG.error('StdErr :%s', err.stderr)
            LOG.error('Current state: %s',
                      self.get_all_volume_groups(self._root_helper))
            raise

NOTE: 需要注意的是,雖然 LVM Driver 是通過 CLI 的方式來對 VG 進行操作的,但每種不同的后端存儲設備都有自己的實現方式,例如:通過 HTTP/HTTPS、TCP Socket 來進行 Driver 與存儲設備的連接。

TaskFlow

通過上述流程的分析,或者你會疑惑什么是 Flow?什么是 Task?這是因為 Cinder 在 create volume 的流程中采用了 TaskFlow 通用技術庫,其帶來的好處就是能夠有效的保證了實際存儲資源與代碼邏輯記錄的一致性,說白了就是避免了程序帳數據的出現。這不能算做一個小問題,因為存儲卷管理的流程往往是長線、長時間的操作,自動化流程脆弱,如果沒有一個有效的 “流程原子性” 機制,將導致程序的穩定性無法得到保證。

更多的 TaskFlow 資料請瀏覽:《Openstack 實現技術分解 (4) 通用技術 — TaskFlow

創建 Volume 失敗重試機制

可以在 cinder-volume service 的配置文件 cinder.conf 中 使用 scheduler_max_attempts 來配置 Volume 創建失敗重試的次數,默認值為 3,值為 1 則表示不啟用失敗重試機制。

# Maximum number of attempts to schedule an volume (integer value)
scheduler_max_attempts=3

cinder-sheduler 和 cinder-volume 之間會傳遞當前的失敗重試次數。當 Volume 創建失敗,cinder-volume 會通過 RPC 通知 cinder-scheduler 重新進行調度。

# /opt/stack/cinder/cinder/volume/manager.py

            # 確定需要重新調度
            if rescheduled:
                # NOTE(geguileo): Volume was rescheduled so we need to update
                # volume stats because the volume wasn't created here.
                # Volume.host is None now, so we pass the original host value.
                self._update_allocated_capacity(volume, decrement=True,
                                                host=original_host)

cinder-scheduler 檢查當前重試次數若沒有超出最大重試次數,則會重新進入調度環節,選擇當前最優存儲節點重新創建 Volume。否則,就觸發 No valid host was found 異常。Nova 同樣具有 RetryFilter 機制,這是為了防止 “實時資源缺失(調度時參考的資源存量與實際資源存量有差距)” 問題的出現,進一步保障了操作的成功率。

刪除 Volume 流程分析

還是從 REST API 看起:DELETE /v3/{project_id}/volumes/{volume_id}。刪除的請求比較簡單,指定要刪除的 UUID of Volume。在版本較新的 API 中還支持 cascade 和 force 兩個可選的 request query parameter。
在這里插入圖片描述

  • cascade:刪除所有相關的快照和卷。
  • force:無視卷狀態,強制刪除。

在經過 cinder-api service 一系列的刪除 “預准備(主要是看這個 Volume 刪了適合不適合)” 操作之后,delete volume request 依舊會進入 cinder-volume service。

# /opt/stack/cinder/cinder/volume/manager.py

        """Deletes and unexports volume. 1. Delete a volume(normal case) Delete a volume and update quotas. 2. Delete a migration volume If deleting the volume in a migration, we want to skip quotas but we need database updates for the volume. 3. Delete a temp volume for backup If deleting the temp volume for backup, we want to skip quotas but we need database updates for the volume. """

最后的最后實際上還是交由后端存儲設備的驅動程序來完成 “真·卷” 的刪除。當然了,在整個刪除的過程中還需要處理多種多樣的復雜場景的問題,這里就不一一列舉了,代碼會清晰的告訴你。

    @utils.retry(putils.ProcessExecutionError)
    def delete(self, name):
        """Delete logical volume or snapshot. :param name: Name of LV to delete """

        def run_udevadm_settle():
            cinder.privsep.lvm.udevadm_settle()

        # LV removal seems to be a race with other writers or udev in
        # some cases (see LP #1270192), so we enable retry deactivation
        LVM_CONFIG = 'activation { retry_deactivation = 1} '

        try:
            # 執行 CLI 完成 LV 的刪除
            self._execute(
                'lvremove',
                '--config', LVM_CONFIG,
                '-f',
                '%s/%s' % (self.vg_name, name),
                root_helper=self._root_helper, run_as_root=True)
        except putils.ProcessExecutionError as err:
            LOG.debug('Error reported running lvremove: CMD: %(command)s, '
                      'RESPONSE: %(response)s',
                      {'command': err.cmd, 'response': err.stderr})

            LOG.debug('Attempting udev settle and retry of lvremove...')
            run_udevadm_settle()

            # The previous failing lvremove -f might leave behind
            # suspended devices; when lvmetad is not available, any
            # further lvm command will block forever.
            # Therefore we need to skip suspended devices on retry.
            LVM_CONFIG += 'devices { ignore_suspended_devices = 1}'

            self._execute(
                'lvremove',
                '--config', LVM_CONFIG,
                '-f',
                '%s/%s' % (self.vg_name, name),
                root_helper=self._root_helper, run_as_root=True)
            LOG.debug('Successfully deleted volume: %s after '
                      'udev settle.', name)

Volume 的資源模型

Data Module

# /opt/stack/cinder/cinder/db/sqlalchemy/models.py

class Volume(BASE, CinderBase):
    """Represents a block storage device that can be attached to a vm."""
    __tablename__ = 'volumes'
    __table_args__ = (Index('volumes_service_uuid_idx',
                            'deleted', 'service_uuid'),
                      CinderBase.__table_args__)

    id = Column(String(36), primary_key=True)
    _name_id = Column(String(36))  # Don't access/modify this directly!

    @property
    def name_id(self):
        return self.id if not self._name_id else self._name_id

    @name_id.setter
    def name_id(self, value):
        self._name_id = value

    @property
    def name(self):
        return CONF.volume_name_template % self.name_id

    ec2_id = Column(Integer)
    user_id = Column(String(255))
    project_id = Column(String(255))

    snapshot_id = Column(String(36))

    cluster_name = Column(String(255), nullable=True)
    host = Column(String(255))  # , ForeignKey('hosts.id'))
    size = Column(Integer)
    availability_zone = Column(String(255))  # TODO(vish): foreign key?
    status = Column(String(255))  # TODO(vish): enum?
    attach_status = Column(String(255))  # TODO(vish): enum
    migration_status = Column(String(255))

    scheduled_at = Column(DateTime)
    launched_at = Column(DateTime)
    terminated_at = Column(DateTime)

    display_name = Column(String(255))
    display_description = Column(String(255))

    provider_location = Column(String(255))
    provider_auth = Column(String(255))
    provider_geometry = Column(String(255))
    provider_id = Column(String(255))

    volume_type_id = Column(String(36))
    source_volid = Column(String(36))
    encryption_key_id = Column(String(36))

    consistencygroup_id = Column(String(36), index=True)
    group_id = Column(String(36), index=True)

    bootable = Column(Boolean, default=False)
    multiattach = Column(Boolean, default=False)

    replication_status = Column(String(255))
    replication_extended_status = Column(String(255))
    replication_driver_data = Column(String(255))

    previous_status = Column(String(255))

    consistencygroup = relationship(
        ConsistencyGroup,
        backref="volumes",
        foreign_keys=consistencygroup_id,
        primaryjoin='Volume.consistencygroup_id == ConsistencyGroup.id')

    group = relationship(
        Group,
        backref="volumes",
        foreign_keys=group_id,
        primaryjoin='Volume.group_id == Group.id')

    service_uuid = Column(String(36), index=True)
    service = relationship(Service,
                           backref="volumes",
                           foreign_keys=service_uuid,
                           primaryjoin='Volume.service_uuid == Service.uuid')
    shared_targets = Column(Boolean, default=True)  # make an FK of service?

數據庫屬性

MariaDB [cinder]> desc volumes;
+-----------------------------+--------------+------+-----+---------+-------+
| Field                       | Type         | Null | Key | Default | Extra |
+-----------------------------+--------------+------+-----+---------+-------+
| created_at                  | datetime     | YES  |     | NULL    |       |
| updated_at                  | datetime     | YES  |     | NULL    |       |
| deleted_at                  | datetime     | YES  |     | NULL    |       |
| deleted                     | tinyint(1)   | YES  |     | NULL    |       |
| id                          | varchar(36)  | NO   | PRI | NULL    |       |
| ec2_id                      | varchar(255) | YES  |     | NULL    |       |
| user_id                     | varchar(255) | YES  |     | NULL    |       |
| project_id                  | varchar(255) | YES  |     | NULL    |       |
| host                        | varchar(255) | YES  |     | NULL    |       |
| size                        | int(11)      | YES  |     | NULL    |       |
| availability_zone           | varchar(255) | YES  |     | NULL    |       |
| status                      | varchar(255) | YES  |     | NULL    |       |
| attach_status               | varchar(255) | YES  |     | NULL    |       |
| scheduled_at                | datetime     | YES  |     | NULL    |       |
| launched_at                 | datetime     | YES  |     | NULL    |       |
| terminated_at               | datetime     | YES  |     | NULL    |       |
| display_name                | varchar(255) | YES  |     | NULL    |       |
| display_description         | varchar(255) | YES  |     | NULL    |       |
| provider_location           | varchar(256) | YES  |     | NULL    |       |
| provider_auth               | varchar(256) | YES  |     | NULL    |       |
| snapshot_id                 | varchar(36)  | YES  |     | NULL    |       |
| volume_type_id              | varchar(36)  | YES  |     | NULL    |       |
| source_volid                | varchar(36)  | YES  |     | NULL    |       |
| bootable                    | tinyint(1)   | YES  |     | NULL    |       |
| provider_geometry           | varchar(255) | YES  |     | NULL    |       |
| _name_id                    | varchar(36)  | YES  |     | NULL    |       |
| encryption_key_id           | varchar(36)  | YES  |     | NULL    |       |
| migration_status            | varchar(255) | YES  |     | NULL    |       |
| replication_status          | varchar(255) | YES  |     | NULL    |       |
| replication_extended_status | varchar(255) | YES  |     | NULL    |       |
| replication_driver_data     | varchar(255) | YES  |     | NULL    |       |
| consistencygroup_id         | varchar(36)  | YES  | MUL | NULL    |       |
| provider_id                 | varchar(255) | YES  |     | NULL    |       |
| multiattach                 | tinyint(1)   | YES  |     | NULL    |       |
| previous_status             | varchar(255) | YES  |     | NULL    |       |
| cluster_name                | varchar(255) | YES  |     | NULL    |       |
| group_id                    | varchar(36)  | YES  | MUL | NULL    |       |
| service_uuid                | varchar(36)  | YES  | MUL | NULL    |       |
| shared_targets              | tinyint(1)   | YES  |     | NULL    |       |
+-----------------------------+--------------+------+-----+---------+-------+
39 rows in set (0.00 sec)

多后端存儲(Multi-Backend Storage)

從 Havana 開始 Cinder 支持通過啟用多個不同類型的存儲后端(Backend),並為每一個 Backend 啟動一個 cinder-volume service 服務進程。對於用戶而言,只需要通過對配置文件的修改即可實現。

e.g. 同時啟用 LVM 和 GlusterFS 后端存儲

# /etc/cinder/cinder.conf

[DEFAULT]
...
enabled_backends =lvmdriver,glusterfs

[lvmdriver]
volume_driver = cinder.volume.drivers.lvm.LVMISCSIDriver
volume_group = cinder-volumes
volume_backend_name =LVM_iSCSI

[glusterfs]
volume_driver =cinder.volume.drivers.glusterfs.GlusterfsDriver
glusterfs_shares_config =/etc/cinder/glusterfs_shares
glusterfs_mount_point_base =/var/lib/nova
glusterfs_disk_util = df
glusterfs_qcow2_volumes = true
glusterfs_sparsed_volumes = true
volume_backend_name = GlusterFS
  • volume_backend_name:會被 Volume Type 功能用到,通過 --property volume_backend_name 屬性來設置。
  • 多個 backends 之間可以設置相同 volume_backend_name,這樣 cinder-scheduler 可以按照指定的調度算法在相同 volume_backend_name 的多個 backends 之內選擇一個最合適的 backend。
  • 啟用了多后端存儲后,cinder-volume service 服務進程會 fock 出相應數量的子進程,可以通過指令 openstack volume service list 查看。

NOTE:開發調試階段不建議開啟多后端存儲或者建議使用 remote_pdb 進行調試。

Tranfer Volume

Tranfer Volume 將 Volume 的擁有權從一個 Tenant 用戶轉移到另一個 Tenant 用戶。該功能的本質就是 修改了 volume 的 tenant id 屬性(os-vol-tenant-attr:tenant_id)而已。

  1. 在 Volume 所屬的 Tenant 用戶使用命令 cinder transfer-create 創建 tranfer 時候會產生 transfer idauthkey
  2. 在另一個期望接管該 Volume 的 Tenant 用戶使用命令 cinder transfer-accept 接受 transfer 時將 1 中的 transfer idauth_key 填入即可。

Migrate Volume

Migrate Volume 即將 Volume 從一個 Backend 遷移至另一個 Backend。

if 如果 Volume 沒有 Attach:

  • 遷移:如果是同一個存儲設備上不同 Backend 之間遷移,需要存儲 Driver 支持本地 Migrate。
  • 克隆:如果是不同存儲設備上的 Backend 之間的 Volume 遷移,或者存儲 Driver 不支持本地 Backend 之間的遷移,那么 Cinder 會啟用克隆操作進行遷移。
    • 首先創建一個新的 Volume
    • 然后從舊 Volume 的數據拷貝到新 Volume
    • 最后舊 Volume 刪除

else Volume 已經被 Attach 到虛擬機:

  • 克隆:同上

NOTEmigration-policy 有兩個選項 never 和 on-demand,如果設置了 never,是無法實現 Volume 遷移的。

Mutli-Attach

Mutli-Attach - Support for attaching a single Cinder volume to multile VM instances.

OpenStack’s Mutli-Attach Feature 在早前的版本的定義是:允許一個 RO volume 經 iSCSI/FC 被 Attached 到多個 VM;直到 Queens 版本之后,Mutil-Attach 的定義被更新為:「The ability to attach a volume to multiple hosts/servers simultaneously is a use case desired for active/active or active/standby scenarios.」,即 Volume 支持以 RO/RW 的方式被掛載到多個 VM。

官方文檔:Volume multi-attach: Enable attaching a volume to multiple servers

Multi-Attach 最直接的價值就是支撐核心業務數據盤的高可用冗余特性,比如說:一個 Volume 同時掛載在兩個 VM 上,當 ACTIVE VM 失聯時,可以由另外的 PASSIVE VM 繼續訪問(RO、R/W)這個卷。

Multi-Attach RO:以 RO 模式將 Volume Attach 到另一個主機/服務器。問題在於,要求使用者(e.g. KVM)知道如何在 Volume 上設置只讀模式和強制執行只讀模式。

Multi-Attach RW:以 RW 模式將 Volume Attach 到另一個主機/服務器。這種情況下,多個 Attachment(Volume 與 Instance 的隱射關系)之間沒有區別,它們都被視為獨立項目,並且都是讀寫卷。

NOTE:目前,所有 Volume 都可以以 RW 模式 Attach,包括 Boot From Volume 模式。

代碼實現原理

  • 首先通過定義一個 multiattach=True 標志的 Volume.
  • Attach 時,Nova 需要支持即使在 Volume in-use status 下仍然可以 Attach。Nova 為每一個 Attachment 指定 RO 或 RW 類型。Cinder 需要支持 Volume status 為 available 或 in-use 狀態下仍然可以 Attach。multiattach 字段設置可以被 Attach 多次。Attach 之后將 Volume 和 Instance 的關系(m:n)記錄到 Attachment 表中。Libvirt 需要將這個 Volume 設置為 shareable 標簽,這樣 Hypervisor 就不會在 Volume 上設置獨占鎖以及相關針對 VM 的 SELinux 隔離設置了。
  • Detach 時,Nova 需要傳遞 attachment_id 到 clinderclient,告知 Cinder 哪一個 Attahment 需要被 Detach。然后 Cinder 結合 instance_id 與 attachment_id 執行 Detach Volume。如果 Cinder 設置了 multiattach=True 但又沒有傳入 attachment_id 的話就應該 Detach 失敗。

使用

# 創建 Multi-Attach Volume Type
cinder type-create multiattach
cinder type-key multiattach set multiattach="<is> True"

# Create Volume
cinder create 10 --name multiattach-volume --volume-type <volume_type_uuid>

# multiattach-volume
nova volume-attach test01 <volume_uuid>
nova volume-attach test02 <volume_uuid>

對於版本比較舊的 Cinder(< Q)可以使用下述方式進行 Multi-Attach

[root@overcloud-controller-0 ~]# cinder create --allow-multiattach --name vol_shared 1
+--------------------------------+--------------------------------------+
| Property                       | Value                                |
+--------------------------------+--------------------------------------+
| attachments                    | []                                   |
| availability_zone              | nova                                 |
| bootable                       | false                                |
| consistencygroup_id            | None                                 |
| created_at                     | 2019-03-04T02:44:51.000000           |
| description                    | None                                 |
| encrypted                      | False                                |
| id                             | 240a94b6-6f8c-4797-9379-eebeda984c95 |
| metadata                       | {}                                   |
| migration_status               | None                                 |
| multiattach                    | True                                 |
| name                           | vol_shared                           |
| os-vol-host-attr:host          | None                                 |
| os-vol-mig-status-attr:migstat | None                                 |
| os-vol-mig-status-attr:name_id | None                                 |
| os-vol-tenant-attr:tenant_id   | f745745cebce4a609f074b0121ae3a53     |
| replication_status             | disabled                             |
| size                           | 1                                    |
| snapshot_id                    | None                                 |
| source_volid                   | None                                 |
| status                         | creating                             |
| updated_at                     | None                                 |
| user_id                        | 11b816e454384d038472c7c89d2544f4     |
| volume_type                    | None                                 |
+--------------------------------+--------------------------------------+

[root@overcloud-controller-0 ~]# openstack volume show vol_shared
+--------------------------------+------------------------------------------------+
| Field                          | Value                                          |
+--------------------------------+------------------------------------------------+
| attachments                    | []                                             |
| availability_zone              | nova                                           |
| bootable                       | false                                          |
| consistencygroup_id            | None                                           |
| created_at                     | 2019-03-04T02:44:51.000000                     |
| description                    | None                                           |
| encrypted                      | False                                          |
| id                             | 240a94b6-6f8c-4797-9379-eebeda984c95           |
| migration_status               | None                                           |
| multiattach                    | True                                           |
| name                           | vol_shared                                     |
| os-vol-host-attr:host          | hostgroup@tripleo_iscsi#tripleo_iscsi_fanguiju |
| os-vol-mig-status-attr:migstat | None                                           |
| os-vol-mig-status-attr:name_id | None                                           |
| os-vol-tenant-attr:tenant_id   | f745745cebce4a609f074b0121ae3a53               |
| properties                     |                                                |
| replication_status             | disabled                                       |
| size                           | 1                                              |
| snapshot_id                    | None                                           |
| source_volid                   | None                                           |
| status                         | available                                      |
| type                           | None                                           |
| updated_at                     | 2019-03-04T02:44:52.000000                     |
| user_id                        | 11b816e454384d038472c7c89d2544f4               |
+--------------------------------+------------------------------------------------+


[root@overcloud-controller-0 ~]# openstack server add volume VM1 vol_shared
[root@overcloud-controller-0 ~]# openstack server add volume VM2 vol_shared

[root@overcloud-controller-0 ~]# openstack volume show 87213d56-8bb3-494e-9477-5cea3f0fed83
+--------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Field                          | Value                                                                                                                                                                                                                                       |
+--------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| attachments                    | [{u'server_id': u'6cd7f5e2-9782-446c-b9fe-c9423341ac23', u'attachment_id': u'136ce8d6-e82e-4db6-8742-6c4d0d4fa410', u'attached_at': u'2019-03-04T07:34:58.000000', u'host_name': None, u'volume_id':                                        |
|                                | u'87213d56-8bb3-494e-9477-5cea3f0fed83', u'device': u'/dev/vdb', u'id': u'87213d56-8bb3-494e-9477-5cea3f0fed83'}, {u'server_id': u'e9131a29-c32e-4f8f-a1ac-7e8d09bb09d3', u'attachment_id': u'502cc9b7-cc3e-4796-858b-940ba6423788',        |
|                                | u'attached_at': u'2019-03-04T07:30:12.000000', u'host_name': None, u'volume_id': u'87213d56-8bb3-494e-9477-5cea3f0fed83', u'device': u'/dev/vdb', u'id': u'87213d56-8bb3-494e-9477-5cea3f0fed83'}, {u'server_id': u'120d49e5-8942-4fec-     |
|                                | a54c-6e2ab5ab4bf2', u'attachment_id': u'd0fd3744-6135-485f-a358-c5e333658a32', u'attached_at': u'2019-03-04T07:36:25.000000', u'host_name': None, u'volume_id': u'87213d56-8bb3-494e-9477-5cea3f0fed83', u'device': u'/dev/vdb', u'id':     |
|                                | u'87213d56-8bb3-494e-9477-5cea3f0fed83'}]                                                                                                                                                                                                   |
| availability_zone              | nova                                                                                                                                                                                                                                        |
| bootable                       | false                                                                                                                                                                                                                                       |
| consistencygroup_id            | None                                                                                                                                                                                                                                        |
| created_at                     | 2019-03-04T07:20:40.000000                                                                                                                                                                                                                  |
| description                    | None                                                                                                                                                                                                                                        |
| encrypted                      | False                                                                                                                                                                                                                                       |
| id                             | 87213d56-8bb3-494e-9477-5cea3f0fed83                                                                                                                                                                                                        |
| migration_status               | None                                                                                                                                                                                                                                        |
| multiattach                    | True                                                                                                                                                                                                                                        |
| name                           | vol_shared                                                                                                                                                                                                                                  |
| os-vol-host-attr:host          | hostgroup@tripleo_iscsi#tripleo_iscsi_fanguiju                                                                                                                                                                                              |
| os-vol-mig-status-attr:migstat | None                                                                                                                                                                                                                                        |
| os-vol-mig-status-attr:name_id | None                                                                                                                                                                                                                                        |
| os-vol-tenant-attr:tenant_id   | f745745cebce4a609f074b0121ae3a53                                                                                                                                                                                                            |
| properties                     | attached_mode='rw', readonly='False'                                                                                                                                                                                                        |
| replication_status             | disabled                                                                                                                                                                                                                                    |
| size                           | 1                                                                                                                                                                                                                                           |
| snapshot_id                    | None                                                                                                                                                                                                                                        |
| source_volid                   | None                                                                                                                                                                                                                                        |
| status                         | in-use                                                                                                                                                                                                                                      |
| type                           | None                                                                                                                                                                                                                                        |
| updated_at                     | 2019-03-04T07:36:26.000000                                                                                                                                                                                                                  |
| user_id                        | 11b816e454384d038472c7c89d2544f4                                                                                                                                                                                                            |
+--------------------------------+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+

VolumeType

Volume Type 常被用來滿足用戶對 Volume 的一些自定義需求,例如:選擇存儲后端,開啟 Thin/Thick Provisioning(精簡置備與厚置備),Deduplication(去重)或 Compression(壓縮)等高級功能。

EXAMPLE 1:假設有兩個 Cinder 存儲池,后端名(backend_name)分別是 ssd_pool 與 hdd_pool。如何實現創建 Volume 時指定后端的存儲池?

# 1. 分別創建兩個 volume type:ssd-type 與 hdd-type
cinder type create "ssd-type"
cinder type create "hdd-type"

# 2. 為這兩個 volume type 指定后端名
cinder type-key "ssd-type" set volume_backend_name=ssd_pool
cinder type-key "hdd-type" set volume_backend_name=hdd_pool

# 3. 指定 volume type 創建 volume
cinder create --name --volume-type ssd-type
cinder create --name --volume-type hdd-type

如此 Cinder 就會根據 volume type 所具有的 extra spec key(e.g. volume_backend_name:ssd_pool),將 Volume 創建到 backend_name 所對應的 Cinder Backend 上(Backend Section 會定義 backend_name 配置項)。

EXAMPLE 2:開啟 Thin Provisioning(精簡置備),首先需要注意的是,Thin Provisioning 需要后端存儲設備支持。如果使用 Ceph 作為存儲后端,則不需要考慮開啟此功能,因為 Ceph 的 rbd device 默認就是 Thin Provisioning 且無法選擇。

# 1. 創建一個 volume type:thin-type
cinder type create "thin-type"

# 2. 為 thin-type 設定 extra-spec:
cinder type-key "thin-type" set provisioning:type=thin
cinder type-key "thin-type" set thin_provisioning_support="True"

# 3. 使用 thin-type 創建 volume
cinder create --name --volume-type thin-type

EXAMPLE:如果切換 Volume 的 Thin/Thick provisioning?使用 Cinder Retype 功能,Retype 常用來改變 Volume 的 volume type,從而達到切換或開關某項功能(特性)的作用。

# retype              Changes the volume type for a volume.

usage: cinder retype [--migration-policy <never|on-demand>]
                     <volume> <volume-type>

Volume Type 可以用來選擇后端存儲,那 retype 就可以用來改變 Volume 的后端存儲池,所以 retype 可以被用來實現 Volume 的遷移功能。e.g. 用戶有一個屬於 ssd_pool 的 Volume,想把其遷移至 hdd_pool 里,可以用 retype 命令實現:

cinder retype --migration-policy on-demand hdd-type

存儲 QoS

《[Cinder] 存儲 Qos》一文中已經說到,這里不再贅述。簡單演示一個示例:

# 1. 創建兩個 QoS 規格與 volume type:
cinder qos-create qos1 write_iops_sec=500
cinder qos-create qos2 write_iops_sec=50000
cinder type create "qos1-type"
cinder type create "qos2-type"

# 2. 將 QoS 規格與 volume type 關聯:
cinder qos-associate 

# 3. 創建一個 volume,並設置其 QoS 規格為 qos1
cinder create --name --volume-type qos1-type 

# 4. 重新設置 volume 的 QoS 規格為 qos2
cinder retype qos2-type

設置或更改 Volume 的 QoS 需要注意兩點:

  1. QoS 規格可以設置 consumer 為 front-end、back-end 或 both,front-end 表示 Volume 的 QoS 是在 Hypervisor(QEMU-KVM)設置並生效的,這種情況下改變 Volume 的 QoS 規格后,需要重新 Attach 才能生效。
  2. 更改 Volume 的 QoS,為什么要用 retype?這是因為如果直接修改 QoS 的設置項(e.g. write_iops_sec),會直接影響到所有使用了該 QoS 的 Volumes。所以建議通過 retype 來更改 Volume 的 QoS,避免造成群體性傷害。

Replication

Replication(復制)、Snapshot(快照)、Backup(備份)是存儲領域最為常見的數據保護三劍客。Cinder 從 Juno 版開始支持 Replication。

在衡量數據保護技術優劣時,通常使用 RPO(Recovery Point Objective,復原點目標,當服務恢復后得來的數據所對應的時間點)與 RTO(Recovery Time Objective,復原時間目標,企業可容許服務中斷的時間長度)兩個指標。優秀的數據保護技術可以使得 RPO=0 且 RTO 趨近於 0。Replication 與 Snapshot 相比,它不依賴於數據源,可以做到異地容災;而與 Backup 相比,它輕量,可以恢復到任意時間點數據源。Replication 技術因其可靠的數據保護作用,常常被用來構建高可用數據中心。
簡單來說,

Replication 通過在兩個 Volume(卷)之間創建一個 Session(會話),並進行數據同步。這兩個 Volume 可以處於同一台存儲陣列中,也可以處於兩個異地的存儲陣列中。但同時只有一個 Volume 會提供生產服務,當提供生產服務的 Primary Volume 發生故障時,立即切換至備用的 Secondary Volume,從而保證業務的連續性。Replication 根據數據同步的方式分為 Sync(同步)與 Async(異步)兩種模式。Sync 模式下,數據在寫入 Primary Volume 與 Secondary Volume 后才可用;Async 模式反之,響應時間更短。因此,只有 Sync Replication 可以提供 RPO=0 的保障,但 Volume 性能卻見不得是最高的。

Cinder Replication 操作對象:

  • Failover:是一個具體的 Backend,我們成為 Replication Backend(以區別於承載業務的 Cinder Backend)。當用於生產的某個 Cinder Backend 發生故障時,其中所有的 Volume 將不可訪問,從而導致業務中斷。這時可以考慮其進行 Cinder Replication 的 failover-host 操作以恢復被中斷的業務。在進行 failover-host 操作時需要指定的 backend-id 就是 Replication 目的端(Replication Backend),當 failover-host 操作完成后,Cinder Backend 上所有的 Volume 將由目的端上的 Volume 替代,提供生產服務。當然了,前提是運管人員為 Cinder Backend 上所有的 Volume 都啟用了 Replication 功能,如果 Cinder Backend 上的某些 Volume 沒有開啟 Replication 功能,那么這些 Volume 將不會 Failover 到目的端。

  • Replication 目的端:Cinder 支持一對多的 Replication,即一個 Cinder Backend 可以 Replicate 到多個目的端,但並非所有后端存儲設備都支持一對多的 Relication。Replication 目的端的信息需要在 cinder.conf 里配置,其中 backend_id 是必須配置的,該參數用於進行 failover-host 操作時指定目的端。Replication 目的端一般是災備數據中心的存儲陣列,用來做主業務數據中心的冗余設備。

# Multi opt of dictionaries to represent the aggregate mapping between source
# and destination back ends when using whole back end replication. For every
# source aggregate associated with a cinder pool (NetApp FlexVol), you would
# need to specify the destination aggregate on the replication target device. A
# replication target device is configured with the configuration option
# replication_device. Specify this option as many times as you have replication
# devices. Each entry takes the standard dict config form:
# netapp_replication_aggregate_map =
# backend_id:<name_of_replication_device_section>,src_aggr_name1:dest_aggr_name1,src_aggr_name2:dest_aggr_name2,...
  • Failback:當 Cinder Backend 源端的故障排除后,可以對該 Cinder Backend 執行 Failback 操作,即將生產業務從 Replication 目的端重新接管回阿里。這樣 Cinder Backend 源端的 Volume 就可以繼續提供服務了。而且在故障處理期間,向 Replication 目的端的 Volumes 寫入的數據也會同步回源端。

  • Sync/Async:目前 Cinder 並不支持顯示的設置 Replication 的同步模式,這是因為 Replication 極度依賴實際的后端存儲設備功能集以及驅動程序的實現。一般而言,各存儲廠商的具體實現中,會使用 volume type 中的 extra spec key 來設置 Volume 的 Replication Sync/Async 模式。

Step 1. 創建開啟 Replication 的 Volume

  • 首先需要創建支持 Replication 的 volume type
cinder type-create replication-type
cinder type-key replication-type set replication_enabled=True
  • 使用 replication volume type 創建 volume
cinder create --volume-type replication_type --name vol001 100

Step 2. 發生故障時對指定的 Cinder Backend 執行 Failover 操作

cinder failover-host --backend-id storage-node2@replication2

Step 3. 待 Cinder Backend 源端故障清理完畢,執行 Failback 操作

cinder failover-host --backend-id <cinder_backend> <replication_backend>
# e.g.
cinder failover-host --backend-id default storage-node2@replication2

Cinder 的高可用部署架構

  • 無狀態服務(cinder-api、cinder-scheduler、cinder-volume)使用多活(無狀態服務利於橫向擴展,高並發):Active/Active(A/A)
  • 有狀態服務使用主備:Active/Passive(A/P)
  • cinder-api + cinder-scheduler 都部署在 Controller,3 個 Controller 同時共享同一個 VIP 實現多活
  • 一個存儲設備可以對應多個 cinder-volume,結合 cinder-volume 分布式鎖實現多活,分布式鎖可以避免不同的 cinder-scheduler 同時調用到同一個 cinder-volume,從而避免多活部署的 cinder-volume 同時操作后端存儲設備,簡而言之就是避免並發操作帶來(cinder-volume 與后端存儲設備之間的)數據不一致性。鎖是為了通過互斥訪問來保證共享資源在並發環境中的數據一致性。分布式鎖主要解決的是分布式資源訪問沖突的問題,保證數據的一致性(Etcd、Zookeeper)。

Cinder 的分布式鎖

在了解 Cinder 的分布式鎖之前,先了解 Cinder 的本地鎖。顯然 Volume 資源本身也是一種共享資源,也需要處理並發訪問沖突的問題,比如:刪除一個 Volume 時,另一個線程正在基於該 Volume 創建快照;又或者同時有兩個線程都在執行 Volume 掛載操作。cinder-volume 也是使用鎖機制來實現 Volume 資源的並發訪問的,Volume 的刪除、掛載、卸載等操作都會對 Volume 上鎖。在 Newton 版本以前,cinder-volume 的鎖實現是基於本地文件實現的,使用了 Linux 的 flock 工具進行鎖的管理。Cinder 執行加鎖操作默認會從配置指定的 lockpath 目錄下創建一個命名為 cinder-volume_uuid-{action} 的空文件,並對該文件使用 flock 加鎖。flock 只能作用於同一個操作系統的文件鎖。即使使用共享存儲,另一個操作系統也不能通過 flock 工具判斷該空文件是否有鎖。可見 Cinder 使用的是本地鎖。

本地鎖的局限,只能夠保證同一個 cinder-volume 下的共享資源(e.g. Volume)的數據一致性,也就只能使用 A/P 主備的模式進行高可用,不能管理分布式 cinder-volume 下共享資源,導致了 cinder-volume 不支持多實例高可用的問題。所以為了避免 cinder-volume 服務宕機,就需要引入自動恢復機制來進行管理。比如: Pacemaker,Pacemaker 輪詢判斷 cinder-volume 的存活狀態,一旦發現掛了,Pacemaker 會嘗試重啟服務。如果 Pacemaker 依然重啟失敗,則嘗試在另一台主機啟動該服務,實現故障的自動恢復。

顯然,這種方式是初級而原始的。你或許會想到引入分布式鎖,比如 Zookeeper、Etcd 等服務,但這需要用戶自己部署和維護一套 DLM,無疑增加了運維的成本,並且也不是所有的存儲后端都需要分布式鎖。Cinder 社區為了滿足不同用戶、不同場景的需求,並沒有強制用戶部署固定的 DLM,而是采取了非常靈活的可插除方式,就是 Tooz 庫。

引入了 Tooz 庫之后,當用戶不需要分布式鎖時,只需要指定后端為本地文件即可,此時不需要部署任何 DLM,和引入分布式鎖之前的方式保持一致,基本不需要執行大的變更。當用戶需要 cinder-volume 支持 AA 時,可以選擇部署一種 DLM,比如 Zookeeper 服務。Cinder 對 Tooz 又封裝了一個單獨的 coordination 模塊,其源碼位於 cinder/coordination.py,當代碼需要使用同步鎖時,只需要在函數名前面加上 @coordination.synchronized 裝飾器即可,方便易用,並且非常統一。

Tooz 庫

社區為了解決項目中的分布式問題,開發了一個非常靈活的通用框架,項目名為 Tooz,它是一個 Python 庫,提供了標准的 coordination API,其主要目標是解決分布式系統的通用問題,比如節點管理、主節點選舉以及分布式鎖等。簡而言之,Tooz 實現了非常易用的分布式鎖接口。

Tooz 封裝了一套鎖管理的庫或者框架,只需要簡單調用 lock、trylock、unlock 即可完成實現,不用關心底層細節,也不用了解后端到底使用的是 Zookeeper、Redis 還是 Etcd。使用 Tooz 非常方便,只需要三步:

  1. 與后端 DLM 建立連接,獲取 coordination 實例。
  2. 聲明鎖名稱,創建鎖實例。
  3. 使用鎖:
coordinator = coordination.get_coordinator('zake://', b'host-1) 
coordinator.start() 
#Create a lock
lock = coordinator.get_lock("foobar”) 
with lock:print("Do something that is distributed”) 
coordinator.stop()

Cinder 與 Nova 的協同

從 Cinder 架構圖可以看出,Nova 和 Cinder 是交互最密切的兩個項目。Cinder 的功能集涵蓋了 Nova 虛擬機的整個生命周期。

在這里插入圖片描述
Cinder 與 Nova 的協同方式
在這里插入圖片描述

以 LVM 與 iSCSI 為例

下面以 LVMVolumeDriver + iSCSI target_driver 的組合來說明 Cinder 是如何通過 iSCSI 協議將 LVM LV(Volume) 掛載到計算節點中供虛擬機當作數據盤或系統盤(Boot from volume)使用的。
在這里插入圖片描述

LVM(邏輯卷管理)

在這里插入圖片描述

LVM backend 的配置

[lvmdriver-1]
image_volume_cache_enabled = True
volume_clear = zero
lvm_type = auto
target_helper = lioadm
volume_group = stack-volumes-lvmdriver-1
volume_driver = cinder.volume.drivers.lvm.LVMVolumeDriver
volume_backend_name = lvmdriver-1

從上述配置可以看出一個 VG 就對應一個 LVM Backend,一個 VG(Backend)有可以划分為多個 LV(Volume),Volume 通過 iSCSI 的方式掛載到計算節點作為一個塊設備(/dev/sdx),然后這個塊設備會被 qemu-kvm 提供給虛擬機使用。

iSCSI

iSCSI 協議和工具非常常用,這里不再贅述,只是簡單說明 iSCSI 掛載卷的過程:

  • 每個 Hyperviosor(計算機節點)作為一個 iSCSI initiator(客戶端),具有唯一的 Initiatorname,e.g.
Initiator: iqn.1993-08.org.debian:01:8d794081cd6a alias: compute1
  • iSCSI Server(存儲設備)上的每個 Lun(Volume)作為一個 iSCSI Target
  • 將 Target Assign 並 Attached 到 iSCSI Initiator之后,Initiator 和 Target 之間會建立 TCP Session,直至 Target 給 Detached。可以在計算節點上查看,e.g.
iscsiadm -m session

一般的,在划分網絡的時候應該考慮是否需要將 OpenStack Management Network 與 OpenStack Storage Network 分開,避免網絡阻塞(e.g. Download/Upload 大數據)。此時可以通過 cinder-volume service 的配置文件 cinder.conf 中的 iscsi_ip_addressiscsi_port 來指定 iSCSI Session 所使用的網卡,從而做到管理網絡(ControlPath)和數據網絡(DataPath)分離的效果。
在這里插入圖片描述
當然,除了 LVMISCSIDriver 之外,Cinder 還支持非常豐富的后端存儲類型(e.g. IBM SVC、DS8K、XIV)以及數據傳輸方式(e.g. FC、iSCSI)。
在這里插入圖片描述

參考文獻

https://www.cnblogs.com/sammyliu/p/4219974.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM