天天看点

深入理解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)])