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

目錄

Cinder

操做系統得到外部存儲空間通常有如下兩種方式:html

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

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

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

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

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 需求(建立、刪除、快照、掛載、分離、備份卷)的抽象與理解。sql

  • 存儲資源模型(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) 兩個版本,能夠經過配置文件來啓用。swift

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)。
在這裏插入圖片描述

參考文獻

http://www.javashuo.com/article/p-yjtteiev-mo.html

相關文章
相關標籤/搜索