天天看點

深入了解ceph-disk prepare 源碼邏輯

文章目錄

  • ​​CEPH-DISK代碼邏輯​​
  • ​​DEF MAIN:​​
  • ​​DEF PARSE_ARGS:​​
  • ​​DEF Prepare.set_subparser(subparsers)​​
  • ​​def _prepare(self):PrepareBluestore的_prepare函數​​
  • ​​def prepare(self, *to_prepare_list):PrepareData類中的prepare函數​​
  • ​​def prepare_device(self, *to_prepare_list): #prepare_device​​
  • ​​def prepare(self):prepare_device的prepare函數,執行基本分區的prepare工作`db,wal`​​
  • ​​def populate_data_path_device(self, *to_prepare_list):建立臨時檔案系統,寫入Ceph_fsid以及fsid,磁盤屬性等資訊​​

CEPH-DISK代碼邏輯

​ceph​

​​ L版本​

​12.2.1​

​​ 本文以最新的​

​BLUESTORE​

​為例進行邏輯梳理

主要分為:​

​prepare​

​​和​

​activate​

​​,本文主要從源碼描述prepare的工作過程

​​

​ceph-disk -v prepare /dev/sdb​

​ceph-disk -v activate /dev/sdb1​

DEF MAIN:

ceph-disk的main函數入口

def main(argv):
    #python強大的指令行解析子產品,将輸入指令解析為具體的對應項,并調用對應的函數做處理,如prepare,activate
    args = parse_args(argv)
    
    #啟動部署的日志級别,分為DEBUG,STDOUT,BASIC
    setup_logging(args.verbose, args.log_stdout)
    
    #設定python的全局變量,建立對應的檔案,包括prepare鎖檔案,activate鎖檔案,suppress_prefix抑制标記
    setup_statedir(dir)
    
    #同步配置檔案的目錄為/etc/ceph
    setup_sysconfdir(args.sysconfdir)   
    
    #設定ceph的全局使用者組和使用者權限
    global CEPH_PREF_USER
    CEPH_PREF_USER = args.setuser
    global CEPH_PREF_GROUP
    CEPH_PREF_GROUP = args.setgroup

    #啟動執行args傳入的函數
    if args.verbose:
        args.func(args)
    else:
        main_catch(args.func, args)      

DEF PARSE_ARGS:

通過強大的parser指令行解析器子產品對輸入的參數進行解析

def parse_args(argv):
    #建構一個ArgrmentParser對象
    parser = argparse.ArgumentParser(
        'ceph-disk',
    )
    
    #add_arguemnt添加指令行參數,`-`和`--`開頭的參數表示可選參數,其他的表示必須輸入的參數
    parser.add_argument(
        '-v', '--verbose',
        action='store_true', default=None,
        help='be more verbose',
    )
    
    #python強大的指令行解析子產品,添加一個子指令解析器
    subparsers = parser.add_subparsers(
        title='subcommands',
        description='valid subcommands',
        help='sub-command help',
    )
    
    #對prepare 子指令進行解析,并設定參數的函數屬性為Prepare.main
    Prepare.set_subparser(subparsers)
    
    #對activate子指令進行解析,并設定參數的函數屬性為main_activate
    make_activate_parser(subparsers)
    
    #對activate-lockbox子指令進行解析,并設定參數的函數屬性為main_activate_lockbox
    make_activate_lockbox_parser(subparsers)
    
    #對激活資料(data)子指令進行解析,設定參數的函數屬性main_activate_space(name, args)
    make_activate_block_parser(subparsers)
    
    #對激活日志(jounal)子指令進行解析,設定參數屬性為main_activate_space(name, args)
    make_activate_journal_parser(subparsers)
    
    #對激活所有的osd分區,激活所有的/dev/disk/by-parttypeuuid中找到的分區,使用main_activate_all函數
    make_activate_all_parser(subparsers)
    
    #對顯示系統上所有的分區以及與ceph有關聯的分區資訊,list指令進行解析,main_list
    make_list_parser(subparsers)
    
    #對supress-activate函數進行解析,使得磁盤不會被激活,使用main_suppress 
    make_suppress_parser(subparsers)
    
    #對deactivate子指令進行解析,停止osd服務并将osd标記為out,删除/var/lib/ceph/osd/ceph-id目錄
    make_deactivate_parser(subparsers)
    
    #破壞osd,破壞的前提是該osd程序必須是down狀态。如果完成破壞,那麼一個新的osd就可以在目前位置程序建立,并且可以使用相同的osd_id
    make_destroy_parser(subparsers)
    
    #對格式化指令進行解析,主要功能是使用sgdisk删除一個裝置的分區表和目錄,主要參數是‘--zap-all’可以破壞gpt和MBR資料結構,以便磁盤可以為下次分區做準備
    make_zap_parser(subparsers)
    
    #對觸發指令進行解析,激活給定的osd分區,主要是在系統重新開機時拉起一些系統程序或者systemd服務,通過異步執行該指令可以縮短udev動作的執行時間
    make_trigger_parser(subparsers)
    
    #解析修複指令,主要用來修複selinux,ceph相關标簽、系統檔案權限、修複ceph資料的可通路資源(selinux)
    make_fix_parser(subparsers)      

DEF Prepare.set_subparser(subparsers)

解析prepare指令,并擷取​

​prepare​

​​時的執行函數​

​Prepare.main​

​,prepare時的步驟如下

  1. 判定部署參數中是否有對osd目錄加密​

    ​--dmcrypt​

    ​,該參數用于ceph的​

    ​ansible​

    ​自動化工具
  • 有加密,核對目前裝置是否在正在使用中,如果正在使用中則傳回錯誤。(由于我們有共享盤技術,是以部分磁盤會在prepare之前被占用,我們會注釋掉這部分代碼)
  • 主要通過檢測裝置是否被挂載
  • 檢測是否有分區且分區被挂載
  • 裝置未被使用,則建立一個10M的五分區用來做加密的盒子​

    ​lockbox​

  • 格式化分區為ext4檔案系統并挂載到目錄​

    ​/var/lib/ceph/osd-lockbox​

    ​目錄下
  1. 部署參數中沒有對osd目錄加密,則開始增加prepare list内容
  • 先在磁盤裝置上做出資料分區1,預設是100MB
  • 根據設定的參數,如​

    ​ceph-disk -v prepare /deb/sdb --block.db /dev/sdb --block.wal /dev/sdb​

    ​指定了擁有db/wal分區的裝置,會先做出第一分區data,其次第三分區db,第四分區wal
  • 建立uuid類型裝置連結到分區,因為udev無法可靠地注意到對現有分區的GUID的更改
  • 使用partprobe更新分區表
  • mkfs使用預設設定的檔案系統格式化第一分區,并建立臨時檔案夾挂載到第一分區将ceph_fsid,fsid,裝置屬性等資訊寫入檔案系統
def set_subparser(subparsers):
 #設定幾個主要的參數解析器用來添加基礎prepare指令集選項
 parents = [
     Prepare.parser(),
     PrepareData.parser(),
     Lockbox.parser(),
 ]
 #添加prepare的子指令解析器
 subparsers.add_parser(
     'prepare',
     parents=parents,
     ...
 )
 
 #如果指令為prepare,最終主函數會執行如下prepare的主函數
 def main(args):
 Prepare.factory(args).prepare()#使用prepare類的工廠函數來執行

 #這裡我們預設選擇L版本之後bluestore的prepare函數
 def factory(args):
 if args.bluestore:
     return PrepareBluestore(args)
 else:
     return PrepareFilestore(args)      
def _prepare(self):PrepareBluestore的_prepare函數
def _prepare(self)
    #對部署參數中是否有osd目錄的加密進行判斷,有的話則需要prepare單獨的lockbox分區
    if self.data.args.dmcrypt:
        self.lockbox.prepare()
    to_prepare_list = []
    if getattr(self.data.args, 'block.db'):
        to_prepare_list.append(self.blockdb)
    if getattr(self.data.args, 'block.wal'):
        to_prepare_list.append(self.blockwal)
    to_prepare_list.append(self.block)
    #如果沒有則增加完prepare_list中的内容之後開始進行osd主要分區的準備工作
    #目前prepare中的函數prepare_device()被PrepareBluestoreData繼承
    self.data.prepare(*to_prepare_list)      

def prepare(self, *to_prepare_list):PrepareData類中的prepare函數

def prepare(self, *to_prepare_list)
    if self.type == self.DEVICE:
        #目前使用的prepare_device函數為PrepareBluestoreData類中的函數
        self.prepare_device(*to_prepare_list)
    elif self.type == self.FILE:
        self.prepare_file(*to_prepare_list)
    else:
        raise Error('unexpected type ', self.type)      

def prepare_device(self, *to_prepare_list): #prepare_device

def prepare_device(self, *to_prepare_list): 
    super(PrepareBluestoreData, self).prepare_device(*to_prepare_list)
    #先做出第一分區,預設100MB
    self.set_data_partition()
    for to_prepare in to_prepare_list:
        #目前prepare根據PrepareBluestore的_prepare函數def _prepare(self):中的prepare_list中的對象prepare對應的分區,此時先prepare db分區,其次wal分區
        to_prepare.prepare()
    #使用預設檔案系統格式化第一分區并建立臨時檔案
    self.populate_data_path_device(*to_prepare_list)      

def prepare(self):prepare_device的prepare函數,執行基本分區的prepare工作​

​db,wal​

def prepare(self)
    if self.type == self.DEVICE:#如果是Device,一般是bluestore,否則為filestore
        #根據傳入的裝置類型進行prepare_device,先是db高速裝置,其次是wal超高速裝置
        self.prepare_device()
    elif self.type == self.FILE:
        self.prepare_file()
    elif self.type == self.NONE:
        pass
    else:
        raise Error('unexpected type ', self.type)
        
    #上一個函數的prepare_device,在該函數過程中會根據裝置類型建立uuid連結到分區,并使用partprobe更新分區表
    def prepare_device(self):
    reusing_partition = False
    #判定目前裝置是否有分區
    if is_partition(getattr(self.args, self.name)):
        LOG.debug('%s %s is a partition',
                  self.name.capitalize(), getattr(self.args, self.name))
        partition = DevicePartition.factory(
            path=None, dev=getattr(self.args, self.name), args=self.args)
        if isinstance(partition, DevicePartitionCrypt):
            raise Error(getattr(self.args, self.name) +
                        ' partition already exists'
                        ' and --dmcrypt specified')
        LOG.warning('OSD will not be hot-swappable' +
                    ' if ' + self.name + ' is not' +
                    ' the same device as the osd data')
        if partition.get_ptype() == partition.ptype_for_name(self.name):
            LOG.debug('%s %s was previously prepared with '
                      'ceph-disk. Reusing it.',
                      self.name.capitalize(),
                      getattr(self.args, self.name))
            reusing_partition = True
            # Read and reuse the partition uuid from this journal's
            # previous life. We reuse the uuid instead of changing it
            # because udev does not reliably notice changes to an
            # existing partition's GUID.  See
            # http://tracker.ceph.com/issues/10146
            setattr(self.args, self.name + '_uuid', partition.get_uuid())
            LOG.debug('Reusing %s with uuid %s',
                      self.name,
                      getattr(self.args, self.name + '_uuid'))
        else:
            LOG.warning('%s %s was not prepared with '
                        'ceph-disk. Symlinking directly.',
                        self.name.capitalize(),
                        getattr(self.args, self.name))
            self.space_symlink = getattr(self.args, self.name)
            return

    self.space_symlink = '/dev/disk/by-partuuid/{uuid}'.format(
        uuid=getattr(self.args, self.name + '_uuid'))
    
    #如果有對裝置映射表的加密标記,則對加密分區建立連結
    if self.args.dmcrypt:
        self.space_dmcrypt = self.space_symlink
        self.space_symlink = '/dev/mapper/{uuid}'.format(
            uuid=getattr(self.args, self.name + '_uuid'))

    if reusing_partition:
        # confirm that the space_symlink exists. It should since
        # this was an active space
        # in the past. Continuing otherwise would be futile.
        assert os.path.exists(self.space_symlink)
        return

    num = self.desired_partition_number()

    if num == 0:
        LOG.warning('OSD will not be hot-swappable if %s '
                    'is not the same device as the osd data',
                    self.name)

    device = Device.factory(getattr(self.args, self.name), self.args)
    #建立對應分區
    num = device.create_partition(
        uuid=getattr(self.args, self.name + '_uuid'),
        name=self.name,
        size=self.space_size,
        num=num)

    partition = device.get_partition(num)

    LOG.debug('%s is GPT partition %s',
              self.name.capitalize(),
              self.space_symlink)

    if isinstance(partition, DevicePartitionCrypt):
        partition.format()
        partition.map()
     
    command_check_call(
        [
            'sgdisk',
            '--typecode={num}:{uuid}'.format(
                num=num,
                uuid=partition.ptype_for_name(self.name),
            ),
            '--',
            getattr(self.args, self.name),
        ],
    )
    #partprobe更新分區表
    update_partition(getattr(self.args, self.name), 'prepared')

    LOG.debug('%s is GPT partition %s',
              self.name.capitalize(),
              self.space_symlink)      

def populate_data_path_device(self, *to_prepare_list):建立臨時檔案系統,寫入Ceph_fsid以及fsid,磁盤屬性等資訊

def populate_data_path_device(self, *to_prepare_list):
    partition = self.partition

    if isinstance(partition, DevicePartitionCrypt):
        partition.map()

    try:
        args = [
            'mkfs',
            '-t',
            self.args.fs_type,
        ]
        if self.mkfs_args is not None:
            args.extend(self.mkfs_args.split())
            #如果預設檔案系統為xfs,則使用mkfs -t xfs -f強制執行,否則不需要-f參數
            if self.args.fs_type == 'xfs':
                args.extend(['-f'])  # always force
        else:
            args.extend(MKFS_ARGS.get(self.args.fs_type, []))
        args.extend([
            '--',
            partition.get_dev(),
        ])
        LOG.debug('Creating %s fs on %s',
                  self.args.fs_type, partition.get_dev())
        command_check_call(args, exit=True)

        #執行挂載
        path = mount(dev=partition.get_dev(),
                     fstype=self.args.fs_type,
                     options=self.mount_options)

        try:
            self.populate_data_path(path, *to_prepare_list)
        finally:
            path_set_context(path)
            unmount(path)
    finally:
        if isinstance(partition, DevicePartitionCrypt):
            partition.unmap()

    if not is_partition(self.args.data):
        command_check_call(
            [
                'sgdisk',
                '--typecode=%d:%s' % (partition.get_partition_number(),
                                      partition.ptype_for_name('osd')),
                '--',
                self.args.data,
            ],
            exit=True,
        )
        #parprobe更新分區表,且在此過程中會執行兩次udevadm settle --timeout=600,第一次是為了解決正在等待進行的udev事件完成,以防其中有事件依賴于裝置上的現有分區;第二次執行是為了向調用者保證已經處理了與分區表變化相關的所有udev事件(95-ceph-osd.rules動作和模式改變,組改變等)已完成。
        update_partition(self.args.data, 'prepared')
        command_check_call(['udevadm', 'trigger',
                            '--action=add',
                            '--sysname-match',
                            os.path.basename(partition.rawdev)])