• Ceph相关部署应用(博客)


    Ceph相关部署应用

    一.存储基础

    1.单机存储设备

    DAS(直接附加存储,是直接接到计算机的主板总线上去的存储)
    IDE、SATA、SCSI、SAS、USB 接口的磁盘
    所谓接口就是一种存储设备驱动下的磁盘设备,提供块级别的存储

    ●NAS(网络附加存储,是通过网络附加到当前主机文件系统之上的存储)
    NFS、CIFS、FTP
    文件系统级别的存储,本身就是一个做好的文件系统,通过nfs接口在用户空间输出后,客户端基于内核模块与远程主机进行网络通信,把它转为好像本地文件系统一样来使用,这种存储服务是没办法对它再一次格式化创建文件系统块的

    ●SAN(存储区域网络)
    SCSI协议(只是用来传输数据的存取操作,物理层使用SCSI线缆来传输)、FCSAN(物理层使用光纤来传输)、iSCSI(物理层使用以太网来传输)
    也是一种网络存储,但不同之处在于SAN提供给客户端主机使用的接口是块级别的存储

    #单机存储的问题
    ●存储处理能力不足
    传统的IDE的IO值是100次/秒,SATA固态磁盘500次/秒,固态硬盘达到2000-4000次/秒。即使磁盘的IO能力再大数十倍,也不够抗住网站访问高峰期数十万、数百万甚至上亿用户的同时访问,这同时还要受到主机网络IO能力的限制。

    ●存储空间能力不足
    单块磁盘的容量再大,也无法满足用户的正常访问所需的数据容量限制。

    ●单点故障问题
    单机存储数据存在单点故障问题

    2.商业存储解决方案

    EMC、NetAPP、IBM、DELL、华为、浪潮

    3.分布式存储(软件定义的存储 SDS)

    Ceph、TFS、FastDFS、MooseFS(MFS)、HDFS、GlusterFS(GFS)
    存储机制会把数据分散存储到多个节点上,具有高扩展性、高性能、高可用性等优点。

    #分布式存储的类型
    ●块存储(例如硬盘,一般是一个存储被一个服务器挂载使用,适用于容器或虚拟机存储卷分配、日志存储、文件存储)
    就是一个裸设备,用于提供没有被组织过的存储空间,底层以分块的方式来存储数据

    ●文件存储(例如NFS,解决块存储无法共享问题,可以一个存储被多个服务器同时挂载,适用于目录结构的存储、日志存储)
    是一种数据的组织存放接口,一般是建立在块级别的存储结构之上,以文件形式来存储数据,而文件的元数据和实际数据是分开存储的

    ●对象存储(例如OSS,一个存储可以被多服务同时访问,具备块存储的高速读写能力,也具备文件存储共享的特性,适用图片存储、视频存储)
    基于API接口提供的文件存储,每一个文件都是一个对象,且文件大小各不相同的,文件的元数据和实际数据是存放在一起的

    二.Ceph 简介

    1.Ceph

    Ceph使用C++语言开发,是一个开放、自我修复和自我管理的开源分布式存储系统。具有高扩展性、高性能、高可靠性的优点。

    Ceph目前已得到众多云计算厂商的支持并被广泛应用。RedHat及OpenStack,Kubernetes都可与Ceph整合以支持虚拟机镜像的后端存储。
    粗略估计,我国70%—80%的云平台都将Ceph作为底层的存储平台,由此可见Ceph俨然成为了开源云平台的标配。目前国内使用Ceph搭建分布式存储系统较为成功的企业有华为、阿里、中兴、华三、浪潮、中国移动、网易、乐视、360、星辰天合存储、杉岩数据等。

    2.Ceph 优势

    ●高扩展性:去中心化,支持使用普通X86服务器,支持上千个存储节点的规模,支持TB到EB级的扩展。
    ●高可靠性:没有单点故障,多数据副本,自动管理,自动修复。
    ●高性能:摒弃了传统的集中式存储元数据寻址的方案,采用 CRUSH 算法,数据分布均衡,并行度高。
    ●功能强大:Ceph是个大一统的存储系统,集块存储接口(RBD)、文件存储接口(CephFS)、对象存储接口(RadosGW)于一身,因而适用于不同的应用场景。

    3.Ceph 架构

    自下向上,可以将Ceph系统分为四个层次:
    ●RADOS 基础存储系统(Reliab1e,Autonomic,Distributed object store,即可靠的、自动化的、分布式的对象存储)
    RADOS是Ceph最底层的功能模块,是一个无限可扩容的对象存储服务,能将文件拆解成无数个对象(碎片)存放在硬盘中,大大提高了数据的稳定性。它主要由OSD和Monitor两个组件组成,OSD和Monitor都可以部署在多台服务器中,这就是ceph分布式的由来,高扩展性的由来。

    ●LIBRADOS 基础库
    Librados提供了与RADOS进行交互的方式,并向上层应用提供Ceph服务的API接口,因此上层的RBD、RGW和CephFS都是通过Librados访问的,目前提供PHP、Ruby、Java、Python、Go、C和C++支持,以便直接基于RADOS(而不是整个Ceph)进行客户端应用开发。

    ●高层应用接口:包括了三个部分
    1)对象存储接口 RGW(RADOS Gateway)
    网关接口,基于Librados开发的对象存储系统,提供S3和Swift兼容的RESTful API接口。

    2)块存储接口 RBD(Reliable Block Device)
    基于Librados提供块设备接口,主要用于Host/VM。

    3)文件存储接口 CephFS(Ceph File System)
    Ceph文件系统,提供了一个符合POSIX标准的文件系统,它使用Ceph存储集群在文件系统上存储用户数据。基于Librados提供的分布式文件系统接口。

    ●应用层:基于高层接口或者基础库Librados开发出来的各种APP,或者Host、VM等诸多客户端

    4.Ceph 核心组件

    Ceph是一个对象式存储系统,它把每一个待管理的数据流(如文件等数据)切分为一到多个固定大小(默认4兆)的对象数据(Object),并以其为原子单元(原子是构成元素的最小单元)完成数据的读写。

    ●OSD(Object Storage Daemon,守护进程 ceph-osd)
    是负责物理存储的进程,一般配置成和磁盘一一对应,一块磁盘启动一个OSD进程。主要功能是存储数据、复制数据、平衡数据、恢复数据,以及与其它OSD间进行心跳检查,负责响应客户端请求返回具体数据的进程等。通常至少需要3个OSD来实现冗余和高可用性。

    ●PG(Placement Group 归置组)
    PG 是一个虚拟的概念而已,物理上不真实存在。它在数据寻址时类似于数据库中的索引:Ceph 先将每个对象数据通过HASH算法固定映射到一个 PG 中,然后将 PG 通过 CRUSH 算法映射到 OSD。

    ●Pool
    Pool 是存储对象的逻辑分区,它起到 namespace 的作用。每个 Pool 包含一定数量(可配置)的 PG。Pool 可以做故障隔离域,根据不同的用户场景统一进行隔离。

    #Pool中数据保存方式支持两种类型:
    ●多副本(replicated):类似 raid1,一个对象数据默认保存 3 个副本,放在不同的 OSD
    ●纠删码(Erasure Code):类似 raid5,对 CPU 消耗稍大,但是节约磁盘空间,对象数据保存只有 1 个副本。由于Ceph部分功能不支持纠删码池,此类型存储池使用不多

    #Pool、PG 和 OSD 的关系:
    一个Pool里有很多个PG;一个PG里包含一堆对象,一个对象只能属于一个PG;PG有主从之分,一个PG分布在不同的OSD上(针对多副本类型)

    ●Monitor(守护进程 ceph-mon)
    用来保存OSD的元数据。负责维护集群状态的映射视图(Cluster Map:OSD Map、Monitor Map、PG Map 和 CRUSH Map),维护展示集群状态的各种图表, 管理集群客户端认证与授权。一个Ceph集群通常至少需要 3 或 5 个(奇数个)Monitor 节点才能实现冗余和高可用性,它们通过 Paxos 协议实现节点间的同步数据。

    ●Manager(守护进程 ceph-mgr)
    负责跟踪运行时指标和 Ceph 集群的当前状态,包括存储利用率、当前性能指标和系统负载。为外部监视和管理系统提供额外的监视和接口,例如 zabbix、prometheus、 cephmetrics 等。一个 Ceph 集群通常至少需要 2 个 mgr 节点实现高可用性,基于 raft 协议实现节点间的信息同步。

    ●MDS(Metadata Server,守护进程 ceph-mds)
    是 CephFS 服务依赖的元数据服务。负责保存文件系统的元数据,管理目录结构。对象存储和块设备存储不需要元数据服务;如果不使用 CephFS 可以不安装。

    5.OSD 存储后端

    OSD 有两种方式管理它们存储的数据。在 Luminous 12.2.z 及以后的发行版中,默认(也是推荐的)后端是 BlueStore。在 Luminous 发布之前, 默认是 FileStore, 也是唯一的选项。
    ●Filestore
    FileStore是在Ceph中存储对象的一个遗留方法。它依赖于一个标准文件系统(只能是XFS),并结合一个键/值数据库(传统上是LevelDB,现在BlueStore是RocksDB),用于保存和管理元数据。
    FileStore经过了良好的测试,在生产中得到了广泛的应用。然而,由于它的总体设计和对传统文件系统的依赖,使得它在性能上存在许多不足。

    ●Bluestore
    BlueStore是一个特殊用途的存储后端,专门为OSD工作负载管理磁盘上的数据而设计。BlueStore 的设计是基于十年来支持和管理 Filestore 的经验。BlueStore 相较于 Filestore,具有更好的读写性能和安全性。

    #BlueStore 的主要功能包括:
    1)BlueStore直接管理存储设备,即直接使用原始块设备或分区管理磁盘上的数据。这样就避免了抽象层的介入(例如本地文件系统,如XFS),因为抽象层会限制性能或增加复杂性。
    2)BlueStore使用RocksDB进行元数据管理。RocksDB的键/值数据库是嵌入式的,以便管理内部元数据,包括将对象名称映射到磁盘上的块位置。
    3)写入BlueStore的所有数据和元数据都受一个或多个校验和的保护。未经验证,不会从磁盘读取或返回给用户任何数据或元数据。
    4)支持内联压缩。数据在写入磁盘之前可以选择性地进行压缩。
    5)支持多设备元数据分层。BlueStore允许将其内部日志(WAL预写日志)写入单独的高速设备(如SSD、NVMe或NVDIMM),以提高性能。如果有大量更快的可用存储,则可以将内部元数据存储在更快的设备上。
    6)支持高效的写时复制。RBD和CephFS快照依赖于在BlueStore中有效实现的即写即复制克隆机制。这将为常规快照和擦除编码池(依赖克隆实现高效的两阶段提交)带来高效的I/O。

    6.Ceph 数据的存储过程

    1)客户端从 mon 获取最新的 Cluster Map

    2)在 Ceph 中,一切皆对象。Ceph 存储的数据都会被切分成为一到多个固定大小的对象(Object)。Object size 大小可以由管理员调整,通常为 2M 或 4M。
    每个对象都会有一个唯一的 OID,由 ino 与 ono 组成:
    ●ino :即是文件的 FileID,用于在全局唯一标识每一个文件
    ●ono :则是分片的编号
    比如:一个文件 FileID 为 A,它被切成了两个对象,一个对象编号0,另一个编号1,那么这两个文件的 oid 则为 A0 与 A1。
    OID 的好处是可以唯一标示每个不同的对象,并且存储了对象与文件的从属关系。由于 Ceph 的所有数据都虚拟成了整齐划一的对象,所以在读写时效率都会比较高。

    3)通过对 OID 使用 HASH 算法得到一个16进制的特征码,用特征码与 Pool 中的 PG 总数取余,得到的序号则是 PGID 。
    即 Pool_ID + HASH(OID) % PG_NUM 得到 PGID

    4)PG 会根据设置的副本数量进行复制,通过对 PGID 使用 CRUSH 算法算出 PG 中目标主和次 OSD 的 ID,存储到不同的 OSD 节点上(其实是把 PG 中的所有对象存储到 OSD 上)。
    即通过 CRUSH(PGID) 得到将 PG 中的数据存储到各个 OSD 组中
    CRUSH 是 Ceph 使用的数据分布算法,类似一致性哈希,让数据分配到预期的地方。

    7.Ceph 版本发行生命周期

    Ceph从Nautilus版本(14.2.0)开始,每年都会有一个新的稳定版发行,预计是每年的3月份发布,每年的新版本都会起一个新的名称(例如,“Mimic”)和一个主版本号(例如,13代表Mimic,因为“M”是字母表的第13个字母)。

    版本号的格式为 x.y.z,x 表示发布周期(例如,13 代表 Mimic,17 代表 Quincy),y 表示发布版本类型,即
    ● x.0.z :y等于 0,表示开发版本
    ● x.1.z :y等于 1,表示发布候选版本(用于测试集群)
    ● x.2.z :y等于 2,表示稳定/错误修复版本(针对用户)

    8.Ceph 集群部署

    目前 Ceph 官方提供很多种部署 Ceph 集群的方法,常用的分别是 ceph-deploy,cephadm 和 二进制:
    ●ceph-deploy :一个集群自动化部署工具,使用较久,成熟稳定,被很多自动化工具所集成,可用于生产部署。

    ●cephadm :从 Octopus 和较新的版本版本后使用 cephadm 来部署 ceph 集群,使用容器和 systemd 安装和管理 Ceph 集群。目前不建议用于生产环境。

    ●二进制:手动部署,一步步部署 Ceph 集群,支持较多定制化和了解部署细节,安装难度较大。

    三.基于 ceph-deploy 部署 Ceph 集群

    1.Ceph 生产环境推荐:

    1)存储集群全采用万兆网络
    2)集群网络(cluster-network,用于集群内部通讯)与公共网络(public-network,用于外部访问Ceph集群)分离
    3)mon、mds 与 osd 分离部署在不同主机上(测试环境中可以让一台主机节点运行多个组件)
    4)OSD 使用 SATA 亦可
    5)根据容量规划集群
    6)至强E5 2620 V3或以上 CPU,64GB或更高内存
    7)集群主机分散部署,避免机柜的电源或者网络故障

    2.Ceph 环境规划

    主机名Public网络Cluster网络角色
    admin192.168.82.100admin(管理节点负责集群整体部署)、client
    node01192.168.82.101192.168.100.101mon、mgr、osd(/dev/sdb、/dev/sdc、/dev/sdd)
    node02192.168.82.102192.168.100.102mon、mgr、osd(/dev/sdb、/dev/sdc、/dev/sdd)
    node03192.168.82.103192.168.100.103mon、osd(/dev/sdb、/dev/sdc、/dev/sdd)
    client192.168.82.104client

    3.环境准备

    可选步骤:创建 Ceph 的管理用户
    useradd cephadm
    passwd cephadm
    
    visudo
    cephadm ALL=(root) NOPASSWD:ALL
    
    1、关闭 selinux 与防火墙
    systemctl disable --now firewalld
    setenforce 0
    sed -i 's/enforcing/disabled/' /etc/selinux/config
    
    
    2、根据规划设置主机名
    hostnamectl set-hostname admin
    hostnamectl set-hostname node01
    hostnamectl set-hostname node02
    hostnamectl set-hostname node03
    hostnamectl set-hostname client
    
    
    3、配置 hosts 解析
    cat >> /etc/hosts << EOF
    192.168.82.100 admin
    192.168.82.101 node01
    192.168.82.102 node02
    192.168.82.103 node03
    192.168.82.104 client
    EOF
    
    
    4、安装常用软件和依赖包
    yum -y install epel-release
    yum -y install yum-plugin-priorities yum-utils ntpdate python-setuptools python-pip gcc gcc-c++ autoconf libjpeg libjpeg-devel libpng libpng-devel freetype freetype-devel libxml2 libxml2-devel zlib zlib-devel glibc glibc-devel glib2 glib2-devel bzip2 bzip2-devel zip unzip ncurses ncurses-devel curl curl-devel e2fsprogs e2fsprogs-devel krb5-devel libidn libidn-devel openssl openssh openssl-devel nss_ldap openldap openldap-devel openldap-clients openldap-servers libxslt-devel libevent-devel ntp libtool-ltdl bison libtool vim-enhanced python wget lsof iptraf strace lrzsz kernel-devel kernel-headers pam-devel tcl tk cmake ncurses-devel bison setuptool popt-devel net-snmp screen perl-devel pcre-devel net-snmp screen tcpdump rsync sysstat man iptables sudo libconfig git bind-utils tmux elinks numactl iftop bwm-ng net-tools expect snappy leveldb gdisk python-argparse gperftools-libs conntrack ipset jq libseccomp socat chrony sshpass
    
    
    5、在 admin 管理节点配置 ssh 免密登录所有节点
    ssh-keygen -t rsa -P '' -f ~/.ssh/id_rsa
    sshpass -p '123123' ssh-copy-id -o StrictHostKeyChecking=no root@admin
    sshpass -p '123123' ssh-copy-id -o StrictHostKeyChecking=no root@node01
    sshpass -p '123123' ssh-copy-id -o StrictHostKeyChecking=no root@node02
    sshpass -p '123123' ssh-copy-id -o StrictHostKeyChecking=no root@node03
    
    
    6、配置时间同步
    systemctl enable --now chronyd
    timedatectl set-ntp true					#开启 NTP
    timedatectl set-timezone Asia/Shanghai		#设置时区
    chronyc -a makestep							#强制同步下系统时钟
    timedatectl status							#查看时间同步状态
    chronyc sources -v							#查看 ntp 源服务器信息
    timedatectl set-local-rtc 0					#将当前的UTC时间写入硬件时钟
    
    #重启依赖于系统时间的服务
    systemctl restart rsyslog 
    systemctl restart crond
    
    #关闭无关服务
    systemctl disable --now postfix
    
    
    7、配置 Ceph yum源
    wget https://download.ceph.com/rpm-nautilus/el7/noarch/ceph-release-1-1.el7.noarch.rpm --no-check-certificate
    
    rpm -ivh ceph-release-1-1.el7.noarch.rpm --force
    
    
    8、执行完上面所有的操作之后重启所有主机(可选)
    sync
    reboot
    
    
    //部署 Ceph 集群
    1、为所有节点都创建一个 Ceph 工作目录,后续的工作都在该目录下进行
    mkdir -p /etc/ceph
    
    
    2、安装 ceph-deploy 部署工具
    cd /etc/ceph
    yum install -y ceph-deploy
    
    ceph-deploy --version
    
    
    3、在管理节点为其它节点安装 Ceph 软件包
    #ceph-deploy 2.0.1 默认部署的是 mimic 版的 Ceph,若想安装其他版本的 Ceph,可以用 --release 手动指定版本
    cd /etc/ceph
    ceph-deploy install --release nautilus node0{1..3} admin
    
    #ceph-deploy install 本质就是在执行下面的命令:
    yum clean all
    yum -y install epel-release
    yum -y install yum-plugin-priorities
    yum -y install ceph-release ceph ceph-radosgw
    
    #也可采用手动安装 Ceph 包方式,在所有其它节点上执行下面的命令将 Ceph 的安装包都部署上:
    sed -i 's#download.ceph.com#mirrors.tuna.tsinghua.edu.cn/ceph#' /etc/yum.repos.d/ceph.repo
    yum install -y ceph-mon ceph-radosgw ceph-mds ceph-mgr ceph-osd ceph-common ceph
    
    
    4、生成初始配置
    #在管理节点运行下述命令,告诉 ceph-deploy 哪些是 mon 监控节点
    cd /etc/ceph
    ceph-deploy new --public-network 192.168.82.0/24 --cluster-network 192.168.100.0/24 node01 node02 node03
    
    #命令执行成功后会在 /etc/ceph 下生成配置文件
    ls /etc/ceph
    ceph.conf					#ceph的配置文件
    ceph-deploy-ceph.log		#monitor的日志
    ceph.mon.keyring			#monitor的密钥环文件
    
    
    5、在管理节点初始化 mon 节点
    cd /etc/ceph
    ceph-deploy mon create node01 node02 node03			#创建 mon 节点,由于 monitor 使用 Paxos 算法,其高可用集群节点数量要求为大于等于 3 的奇数台
    
    ceph-deploy --overwrite-conf mon create-initial		#配置初始化 mon 节点,并向所有节点同步配置
    													# --overwrite-conf 参数用于表示强制覆盖配置文件
    
    ceph-deploy gatherkeys node01						#可选操作,向 node01 节点收集所有密钥
    
    #命令执行成功后会在 /etc/ceph 下生成配置文件
    ls /etc/ceph
    ceph.bootstrap-mds.keyring			#引导启动 mds 的密钥文件
    ceph.bootstrap-mgr.keyring			#引导启动 mgr 的密钥文件
    ceph.bootstrap-osd.keyring			#引导启动 osd 的密钥文件
    ceph.bootstrap-rgw.keyring			#引导启动 rgw 的密钥文件
    ceph.client.admin.keyring			#ceph客户端和管理端通信的认证密钥,拥有ceph集群的所有权限
    ceph.conf
    ceph-deploy-ceph.log
    ceph.mon.keyring
    
    #在 mon 节点上查看自动开启的 mon 进程
    ps aux | grep ceph
    root        1823  0.0  0.2 189264  9216 ?        Ss   19:46   0:00 /usr/bin/python2.7 /usr/bin/ceph-crash
    ceph        3228  0.0  0.8 501244 33420 ?        Ssl  21:08   0:00 /usr/bin/ceph-mon -f --cluster ceph --id node03 --setuser ceph --setgroupceph
    root        3578  0.0  0.0 112824   988 pts/1    R+   21:24   0:00 grep --color=auto ceph
    
    #在管理节点查看 Ceph 集群状态
    cd /etc/ceph
    ceph -s
      cluster:
        id:     7e9848bb-909c-43fa-b36c-5805ffbbeb39
        health: HEALTH_WARN
                mons are allowing insecure global_id reclaim
     
      services:
        mon: 3 daemons, quorum node01,node02,node03
        mgr: no daemons active
        osd: 0 osds: 0 up, 0 in
     
      data:
        pools:   0 pools, 0 pgs
        objects: 0 objects, 0 B
        usage:   0 B used, 0 B / 0 B avail
        pgs:
    
    #查看 mon 集群选举的情况
    ceph quorum_status --format json-pretty | grep leader
    "quorum_leader_name": "node01",
    
    #扩容 mon 节点
    ceph-deploy mon add <节点名称>
    
    
    6、部署能够管理 Ceph 集群的节点(可选)
    #可实现在各个节点执行 ceph 命令管理集群
    cd /etc/ceph
    ceph-deploy --overwrite-conf config push node01 node02 node03		#向所有 mon 节点同步配置,确保所有 mon 节点上的 ceph.conf 内容必须一致
    
    ceph-deploy admin node01 node02 node03			#本质就是把 ceph.client.admin.keyring 集群认证文件拷贝到各个节点
    
    #在 mon 节点上查看
    ls /etc/ceph
    ceph.client.admin.keyring  ceph.conf  rbdmap  tmpr8tzyc
    
    cd /etc/ceph
    ceph -s
    
    
    7、部署 osd 存储节点
    #主机添加完硬盘后不要分区,直接使用
    lsblk 
    NAME   MAJ:MIN RM  SIZE RO TYPE MOUNTPOINT
    sda      8:0    0   60G  0 disk 
    ├─sda1   8:1    0  500M  0 part /boot
    ├─sda2   8:2    0    4G  0 part [SWAP]
    └─sda3   8:3    0 55.5G  0 part /
    sdb      8:16   0   20G  0 disk 
    sdc      8:32   0   20G  0 disk 
    sdd      8:48   0   20G  0 disk 
    
    #如果是利旧的硬盘,则需要先擦净(删除分区表)磁盘(可选,无数据的新硬盘可不做)
    cd /etc/ceph
    ceph-deploy disk zap node01 /dev/sdb
    ceph-deploy disk zap node02 /dev/sdb
    ceph-deploy disk zap node03 /dev/sdb
    
    #添加 osd 节点
    ceph-deploy --overwrite-conf osd create node01 --data /dev/sdb
    ceph-deploy --overwrite-conf osd create node02 --data /dev/sdb
    ceph-deploy --overwrite-conf osd create node03 --data /dev/sdb
    
    #查看 ceph 集群状态
    ceph -s
      cluster:
        id:     7e9848bb-909c-43fa-b36c-5805ffbbeb39
        health: HEALTH_WARN
                no avtive mgr
     
      services:
        mon: 3 daemons, quorum node01,node02,node03 (age 119m)
        mgr: no daemons active
        osd: 3 osds: 3 up (since 35s), 3 in (since 35s)
     
      data:
        pools:   0 pools, 0 pgs
        objects: 0 objects, 0 B
        usage:   3.0 GiB used, 57 GiB / 60 GiB avail
        pgs: 
    
    
    ceph osd stat
    ceph osd tree
    rados df
    ssh root@node01 systemctl status ceph-osd@0
    ssh root@node02 systemctl status ceph-osd@1
    ssh root@node03 systemctl status ceph-osd@2
    
    ceph osd status    #查看 osd 状态,需部署 mgr 后才能执行
    +----+--------+-------+-------+--------+---------+--------+---------+-----------+
    | id |  host  |  used | avail | wr ops | wr data | rd ops | rd data |   state   |
    +----+--------+-------+-------+--------+---------+--------+---------+-----------+
    | 0  | node01 | 1025M | 18.9G |    0   |     0   |    0   |     0   | exists,up |
    | 1  | node02 | 1025M | 18.9G |    0   |     0   |    0   |     0   | exists,up |
    | 2  | node03 | 1025M | 18.9G |    0   |     0   |    0   |     0   | exists,up |
    +----+--------+-------+-------+--------+---------+--------+---------+-----------+
    
    ceph osd df    #查看 osd 容量,需部署 mgr 后才能执行
    ID CLASS WEIGHT  REWEIGHT SIZE   RAW USE DATA    OMAP META  AVAIL  %USE VAR  PGS STATUS 
     0   hdd 0.01949  1.00000 20 GiB 1.0 GiB 1.8 MiB  0 B 1 GiB 19 GiB 5.01 1.00   0     up 
     1   hdd 0.01949  1.00000 20 GiB 1.0 GiB 1.8 MiB  0 B 1 GiB 19 GiB 5.01 1.00   0     up 
     2   hdd 0.01949  1.00000 20 GiB 1.0 GiB 1.8 MiB  0 B 1 GiB 19 GiB 5.01 1.00   0     up 
                        TOTAL 60 GiB 3.0 GiB 5.2 MiB  0 B 3 GiB 57 GiB 5.01                 
    MIN/MAX VAR: 1.00/1.00  STDDEV: 0
    
    
    #扩容 osd 节点
    cd /etc/ceph
    ceph-deploy --overwrite-conf osd create node01 --data /dev/sdc
    ceph-deploy --overwrite-conf osd create node02 --data /dev/sdc
    ceph-deploy --overwrite-conf osd create node03 --data /dev/sdc
    ceph-deploy --overwrite-conf osd create node01 --data /dev/sdd
    ceph-deploy --overwrite-conf osd create node02 --data /dev/sdd
    ceph-deploy --overwrite-conf osd create node03 --data /dev/sdd
    
    添加 OSD 中会涉及到 PG 的迁移,由于此时集群并没有数据,因此 health 的状态很快就变成 OK,如果在生产环境中添加节点则会涉及到大量的数据的迁移。
    
    
    8、部署 mgr 节点
    #ceph-mgr守护进程以Active/Standby模式运行,可确保在Active节点或其ceph-mgr守护进程故障时,其中的一个Standby实例可以在不中断服务的情况下接管其任务。根据官方的架构原则,mgr至少要有两个节点来进行工作。
    cd /etc/ceph
    ceph-deploy mgr create node01 node02
    
    ceph -s
      cluster:
        id:     7e9848bb-909c-43fa-b36c-5805ffbbeb39
        health: HEALTH_WARN
                mons are allowing insecure global_id reclaim
     
      services:
        mon: 3 daemons, quorum node01,node02,node03
        mgr: node01(active, since 10s), standbys: node02
        osd: 0 osds: 0 up, 0 in
     
    
    #解决 HEALTH_WARN 问题:mons are allowing insecure global_id reclaim问题:
    禁用不安全模式:ceph config set mon auth_allow_insecure_global_id_reclaim false
    
    #扩容 mgr 节点
    ceph-deploy mgr create <节点名称>
    
    
    9、开启监控模块
    #在 ceph-mgr Active节点执行命令开启
    ceph -s | grep mgr
    
    yum install -y ceph-mgr-dashboard
    
    cd /etc/ceph
    
    ceph mgr module ls | grep dashboard
    
    #开启 dashboard 模块
    ceph mgr module enable dashboard --force
    
    #禁用 dashboard 的 ssl 功能
    ceph config set mgr mgr/dashboard/ssl false
    
    #配置 dashboard 监听的地址和端口
    ceph config set mgr mgr/dashboard/server_addr 0.0.0.0
    ceph config set mgr mgr/dashboard/server_port 8000
    
    #重启 dashboard
    ceph mgr module disable dashboard
    ceph mgr module enable dashboard --force
    
    #确认访问 dashboard 的 url
    ceph mgr services
    
    #设置 dashboard 账户以及密码
    echo "12345678" > dashboard_passwd.txt
    ceph dashboard set-login-credentials admin -i dashboard_passwd.txt
      或
    ceph dashboard ac-user-create admin administrator -i dashboard_passwd.txt
    
    浏览器访问:http://192.168.82.101:8000 ,账号密码为 admin/12345678
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318

    4.资源池 Pool 管理

    上面我们已经完成了 Ceph 集群的部署,但是我们如何向 Ceph 中存储数据呢?首先我们需要在 Ceph 中定义一个 Pool 资源池。Pool 是 Ceph 中存储 Object 对象抽象概念。我们可以将其理解为 Ceph 存储上划分的逻辑分区,Pool 由多个 PG 组成;而 PG 通过 CRUSH 算法映射到不同的 OSD 上;同时 Pool 可以设置副本 size 大小,默认副本数量为 3。

    Ceph 客户端向 monitor 请求集群的状态,并向 Pool 中写入数据,数据根据 PGs 的数量,通过 CRUSH 算法将其映射到不同的 OSD 节点上,实现数据的存储。 这里我们可以把 Pool 理解为存储 Object 数据的逻辑单元;当然,当前集群没有资源池,因此需要进行定义。

    #创建一个 Pool 资源池,其名字为 mypool,PGs 数量设置为 64,设置 PGs 的同时还需要设置 PGP(通常PGs和PGP的值是相同的):
    PG (Placement Group),pg 是一个虚拟的概念,用于存放 object,PGP(Placement Group for Placement purpose),相当于是 pg 存放的一种 osd 排列组合
    cd /etc/ceph
    ceph osd pool create mypool 64 64
    
    #查看集群 Pool 信息
    ceph osd pool ls    或    rados lspools
    ceph osd lspools
    
    #查看资源池副本的数量
    ceph osd pool get mypool size
    
    #查看 PG 和 PGP 数量
    ceph osd pool get mypool pg_num
    ceph osd pool get mypool pgp_num
    
    #修改 pg_num 和 pgp_num 的数量为 128
    ceph osd pool set mypool pg_num 128
    ceph osd pool set mypool pgp_num 128
    
    ceph osd pool get mypool pg_num
    ceph osd pool get mypool pgp_num
    
    #修改 Pool 副本数量为 2
    ceph osd pool set mypool size 2
    
    ceph osd pool get mypool size
    
    #修改默认副本数为 2
    vim ceph.conf
    ......
    osd_pool_default_size = 2
    
    ceph-deploy --overwrite-conf config push node01 node02 node03
    
    #删除 Pool 资源池
    1)删除存储池命令存在数据丢失的风险,Ceph 默认禁止此类操作,需要管理员先在 ceph.conf 配置文件中开启支持删除存储池的操作
    vim ceph.conf
    ......
    [mon]
    mon allow pool delete = true
    
    2)推送 ceph.conf 配置文件给所有 mon 节点
    ceph-deploy --overwrite-conf config push node01 node02 node03
    
    3)所有 mon 节点重启 ceph-mon 服务
    systemctl restart ceph-mon.target
    
    4)执行删除 Pool 命令
    ceph osd pool rm pool01 pool01 --yes-i-really-really-mean-it
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    四.ceph应用

    1.创建 CephFS 文件系统 MDS 接口

    #服务端操作
    1)在管理节点创建 mds 服务
    cd /etc/ceph
    ceph-deploy mds create node01 node02 node03
    
    2)查看各个节点的 mds 服务
    ssh root@node01 systemctl status ceph-mds@node01
    ssh root@node02 systemctl status ceph-mds@node02
    ssh root@node03 systemctl status ceph-mds@node03
    
    3)创建存储池,启用 ceph 文件系统
    ceph 文件系统至少需要两个 rados 池,一个用于存储数据,一个用于存储元数据。此时数据池就类似于文件系统的共享目录。
    ceph osd pool create cephfs_data 128					#创建数据Pool
    
    ceph osd pool create cephfs_metadata 128				#创建元数据Pool
    
    ####################
    一个Pool资源池应该包含多少PG数?
    OSD总数  小于 5 个,PG数 推荐为 128
             5~10 个,  PG数 推荐为 512
    		 10~50 个, PG数 推荐为 4096
    		 大于 50 个,参考公式 ( Target PGs per OSD ) x ( OSD # ) x ( %Data ) / ( Size )
    		             Target PGs per OSD:每个OSD对应的PG数(近期不扩容OSD数量设置为100,近期要扩容OSD数量设置为200)
    					 OSD #:OSD总数
    					 %Data:当前Pool会占用Ceph集群总空间的百分比
    					 Size:当前Pool的副本数
    		 
    		            100 * 60 * 50% / 3 = 1000 ->取最接近2的次方值 1024
    
    ####################
    
    #创建 cephfs,命令格式:ceph fs new   
    ceph fs new mycephfs cephfs_metadata cephfs_data		#启用ceph,元数据Pool在前,数据Pool在后
    
    ceph fs ls					#查看cephfs
    
    4)查看mds状态,一个up,其余两个待命,目前的工作的是node01上的mds服务
    ceph -s
    mds: mycephfs:1 {0=node01=up:active} 2 up:standby
    
    ceph mds stat
    mycephfs:1 {0=node01=up:active} 2 up:standby
    
    5)创建用户
    语法格式:ceph fs authorize  <fs_name>  client.<client_id>  <path-in-cephfs>  rw
    
    #账户为 client.zhangsan,用户 name 为 zhangsan,zhangsan 对ceph文件系统的 / 根目录(注意不是操作系统的根目录)有读写权限
    ceph fs authorize mycephfs client.zhangsan / rw | tee /etc/ceph/zhangsan.keyring
    
    # 账户为 client.lisi,用户 name 为 lisi,lisi 对文件系统的 / 根目录只有读权限,对文件系统的根目录的子目录 /test 有读写权限
    ceph fs authorize mycephfs client.lisi / r /test rw | tee /etc/ceph/lisi.keyring
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    #客户端操作
    1)客户端要在 public 网络内
    
    2)在客户端创建工作目录
    mkdir /etc/ceph
    
    3)在 ceph 的管理节点给客户端拷贝 ceph 的配置文件 ceph.conf 和账号的秘钥环文件 zhangsan.keyring、lisi.keyring
    scp ceph.conf zhangsan.keyring lisi.keyring root@client:/etc/ceph
    
    4)在客户端安装 ceph 软件包
    cd /opt
    wget https://download.ceph.com/rpm-nautilus/el7/noarch/ceph-release-1-1.el7.noarch.rpm --no-check-certificate
    rpm -ivh ceph-release-1-1.el7.noarch.rpm
    yum install -y ceph 
    
    5)在客户端制作秘钥文件
    cd /etc/ceph
    ceph-authtool -n client.zhangsan -p zhangsan.keyring > zhangsan.key			#把 zhangsan 用户的秘钥导出到 zhangsan.keyl
    ceph-authtool -n client.lisi -p lisi.keyring > lisi.key						#把 lisi 用户的秘钥导出到 lisi.key
    
    6)客户端挂载
    ●方式一:基于内核
    语法格式:
    mount -t ceph node01:6789,node02:6789,node03:6789:/  <本地挂载点目录>  -o name=<用户名>,secret=<秘钥>
    mount -t ceph node01:6789,node02:6789,node03:6789:/  <本地挂载点目录>  -o name=<用户名>,secretfile=<秘钥文件>
    
    示例一:
    mkdir -p /data/zhangsan
    mount -t ceph node01:6789,node02:6789,node03:6789:/ /data/zhangsan -o name=zhangsan,secretfile=/etc/ceph/zhangsan.key
    
    示例二:
    mkdir -p /data/lisi
    mount -t ceph node01:6789,node02:6789,node03:6789:/ /data/lisi -o name=lisi,secretfile=/etc/ceph/lisi.key
    
    #验证用户权限
    cd /data/lisi
    echo 123 > 2.txt
    -bash:2.txt:权限不够
    
    echo 123 > test/2.txt
    cat test/2.txt
    123
    
    示例三:
    #停掉 node02 上的 mds 服务
    ssh root@node02 "systemctl stop ceph-mds@node02"
    
    ceph -s
    
    #测试客户端的挂载点仍然是可以用的,如果停掉所有的 mds,客户端就不能用了
    
    
    ●方式二:基于 fuse 工具
    1)在 ceph 的管理节点给客户端拷贝 ceph 的配置文件 ceph.conf 和账号的秘钥环文件 zhangsan.keyring、lisi.keyring
    scp ceph.client.admin.keyring root@client:/etc/ceph
    
    2)在客户端安装 ceph-fuse
    yum install -y ceph-fuse
    
    3)客户端挂载
    cd /data/aa
    ceph-fuse -m node01:6789,node02:6789,node03:6789 /data/aa [-o nonempty]			#挂载时,如果挂载点不为空会挂载失败,指定 -o nonempty 可以忽略
    
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    2.创建 Ceph 块存储系统 RBD 接口

    1、创建一个名为 rbd-demo 的专门用于 RBD 的存储池
    ceph osd pool create rbd-demo 64 64
    
    2、将存储池转换为 RBD 模式
    ceph osd pool application enable rbd-demo rbd
    
    3、初始化存储池
    rbd pool init -p rbd-demo			# -p 等同于 --pool
    
    4、创建镜像
    rbd create -p rbd-demo --image rbd-demo1.img --size 10G
    
    可简写为:
    rbd create rbd-demo/rbd-demo2.img --size 10G
    
    5、镜像管理
    //查看存储池下存在哪些镜像
    rbd ls -l -p rbd-demo
    
    //查看镜像的详细信息
    rbd info -p rbd-demo --image rbd-demo1.img
    rbd image 'rbd-demo.img':
    	size 10 GiB in 2560 objects							#镜像的大小与被分割成的条带数
    	order 22 (4 MiB objects)							#条带的编号,有效范围是12到25,对应4K到32M,而22代表2的22次方,这样刚好是4M
    	snapshot_count: 0
    	id: 5fc98fe1f304									#镜像的ID标识
    	block_name_prefix: rbd_data.5fc98fe1f304			#名称前缀
    	format: 2											#使用的镜像格式,默认为2
    	features: layering, exclusive-lock, object-map, fast-diff, deep-flatten			#当前镜像的功能特性
    	op_features: 																	#可选的功能特性
    	flags: 
    
    //修改镜像大小
    rbd resize -p rbd-demo --image rbd-demo1.img --size 20G
    
    rbd info -p rbd-demo --image rbd-demo1.img
    
    #使用 resize 调整镜像大小,一般建议只增不减,如果是减少的话需要加选项 --allow-shrink
    rbd resize -p rbd-demo --image rbd-demo1.img --size 5G --allow-shrink
    
    //删除镜像
    #直接删除镜像
    rbd rm -p rbd-demo --image rbd-demo2.img
    rbd remove rbd-demo/rbd-demo2.img
    
    #推荐使用 trash 命令,这个命令删除是将镜像移动至回收站,如果想找回还可以恢复
    rbd trash move rbd-demo/rbd-demo1.img
    
    rbd ls -l -p rbd-demo
    
    rbd trash list -p rbd-demo
    5fc98fe1f304 rbd-demo1.img
    
    #还原镜像
    rbd trash restore rbd-demo/5fc98fe1f304
    
    rbd ls -l -p rbd-demo
    
    
    6、Linux客户端使用
    客户端使用 RBD 有两种方式:
    ●通过内核模块KRBD将镜像映射为系统本地块设备,通常设置文件一般为:/dev/rbd*
    ●另一种是通过librbd接口,通常KVM虚拟机使用这种接口。
    
    本例主要是使用Linux客户端挂载RBD镜像为本地磁盘使用。开始之前需要在所需要客户端节点上面安装ceph-common软件包,因为客户端需要调用rbd命令将RBD镜像映射到本地当作一块普通硬盘使用。并还需要把ceph.conf配置文件和授权keyring文件复制到对应的节点。
    
    //在管理节点创建并授权一个用户可访问指定的 RBD 存储池
    #示例,指定用户标识为client.osd-mount,对另对OSD有所有的权限,对Mon有只读的权限
    ceph auth get-or-create client.osd-mount osd "allow * pool=rbd-demo" mon "allow r" > /etc/ceph/ceph.client.osd-mount.keyring
    
    //修改RBD镜像特性,CentOS7默认情况下只支持layering和striping特性,需要将其它的特性关闭
    rbd feature disable rbd-demo/rbd-demo1.img object-map,fast-diff,deep-flatten
    
    //将用户的keyring文件和ceph.conf文件发送到客户端的/etc/ceph目录下
    cd /etc/ceph
    scp ceph.client.osd-mount.keyring ceph.conf root@client:/etc/ceph
    
    //linux客户端操作
    #安装 ceph-common 软件包
    yum install -y ceph-common
    
    #执行客户端映射
    cd /etc/ceph
    rbd map rbd-demo/rbd-demo1.img --keyring /etc/ceph/ceph.client.osd-mount.keyring --user osd-mount
    
    #查看映射
    rbd showmapped
    rbd device list
    
    #断开映射
    rbd unmap rbd-demo/rbd-demo1.img
    
    #格式化并挂载
    mkfs.xfs /dev/rbd0
    
    mkdir -p /data/bb
    mount /dev/rbd0 /data/bb
    
    #在线扩容
    在管理节点调整镜像的大小
    rbd resize rbd-demo/rbd-demo1.img --size 30G
    
    在客户端刷新设备文件
    xfs_growfs /dev/rbd0		#刷新xfs文件系统容量
    resize2fs /dev/rbd0			#刷新ext4类型文件系统容量
    
    
    7、快照管理
    对 rbd 镜像进行快照,可以保留镜像的状态历史,另外还可以利用快照的分层技术,通过将快照克隆为新的镜像使用。
    
    //在客户端写入文件
    echo 1111 > /data/bb/11.txt
    echo 2222 > /data/bb/22.txt
    echo 3333 > /data/bb/33.txt
    
    //在管理节点对镜像创建快照
    rbd snap create --pool rbd-demo --image rbd-demo1.img --snap demo1_snap1
    
    可简写为:
    rbd snap create rbd-demo/rbd-demo1.img@demo1_snap1
    
    //列出指定镜像所有快照
    rbd snap list rbd-demo/rbd-demo1.img
    
    #用json格式输出:
    rbd snap list rbd-demo/rbd-demo1.img --format json --pretty-format
    
    //回滚镜像到指定
    在回滚快照之前,需要将镜像取消镜像的映射,然后再回滚。
    
    #在客户端操作
    rm -rf /data/bb/*
    umount /data/bb
    rbd unmap rbd-demo/rbd-demo1.img
    
    #在管理节点操作
    rbd snap rollback rbd-demo/rbd-demo1.img@demo1_snap1
    
    #在客户端重新映射并挂载
    rbd map rbd-demo/rbd-demo1.img --keyring /etc/ceph/ceph.client.osd-mount.keyring --user osd-mount
    mount /dev/rbd0 /data/bb
    ls /data/bb				#发现数据还原回来了
    
    //限制镜像可创建快照数
    rbd snap limit set rbd-demo/rbd-demo1.img --limit 3
    
    #解除限制:
    rbd snap limit clear rbd-demo/rbd-demo1.img
    
    //删除快照
    #删除指定快照:
    rbd snap rm rbd-demo/rbd-demo1.img@demo1_snap1
    
    #删除所有快照:
    rbd snap purge rbd-demo/rbd-demo1.img
    
    
    //快照分层
    快照分层支持用快照的克隆生成新镜像,这种镜像与直接创建的镜像几乎完全一样,支持镜像的所有操作。唯一不同的是克隆镜像引用了一个只读的上游快照,而且此快照必须要设置保护模式。
    
    #快照克隆
    1)将上游快照设置为保护模式:
    rbd snap create rbd-demo/rbd-demo1.img@demo1_snap666
    
    rbd snap protect rbd-demo/rbd-demo1.img@demo1_snap666
    
    2)克隆快照为新的镜像
    rbd clone rbd-demo/rbd-demo1.img@demo1_snap666 --dest rbd-demo/rbd-demo666.img
    
    rbd ls -p rbd-demo
    
    3)命令查看克隆完成后快照的子镜像
    rbd children rbd-demo/rbd-demo1.img@demo1_snap666
    
    
    //快照展平
    通常情况下通过快照克隆而得到的镜像会保留对父快照的引用,这时候不可以删除该父快照,否则会有影响。
    rbd snap rm rbd-demo/rbd-demo1.img@demo1_snap666
    #报错 snapshot 'demo1_snap666' is protected from removal.
    
    如果要删除快照但想保留其子镜像,必须先展平其子镜像,展平的时间取决于镜像的大小
    1) 展平子镜像
    rbd flatten rbd-demo/rbd-demo666.img
    
    2)取消快照保护
    rbd snap unprotect rbd-demo/rbd-demo1.img@demo1_snap666
    
    3)删除快照
    rbd snap rm rbd-demo/rbd-demo1.img@demo1_snap666
    
    rbd ls -l -p rbd-demo			#在删除掉快照后,查看子镜像依然存在
    
    
    8、镜像的导出导入
    //导出镜像
    rbd export rbd-demo/rbd-demo1.img  /opt/rbd-demo1.img
    
    //导入镜像
    #卸载客户端挂载,并取消映射
    umount /data/bb
    rbd unmap rbd-demo/rbd-demo1.img
    
    #清除镜像下的所有快照,并删除镜像
    rbd snap purge rbd-demo/rbd-demo1.img
    rbd rm rbd-demo/rbd-demo1.img
    
    rbd ls -l -p rbd-demo
    
    #导入镜像
    rbd import /opt/rbd-demo1.img  rbd-demo/rbd-demo1.img
    
    rbd ls -l -p rbd-demo
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213

    3.创建 Ceph 对象存储系统 RGW 接口

    1)对象存储概念

    对象存储(object storage)是非结构数据的存储方法,对象存储中每一条数据都作为单独的对象存储,拥有唯一的地址来识别数据对象,通常用于云计算环境中。
    不同于其他数据存储方法,基于对象的存储不使用目录树。

    虽然在设计与实现上有所区别,但大多数对象存储系统对外呈现的核心资源类型大同小异。从客户端的角度来看,分为以下几个逻辑单位:
    ●Amazon S3:
    提供了
    1、用户(User)
    2、存储桶(Bucket)
    3、对象(Object)

    三者的关系是:
    1、User将Object存储到系统上的Bucket
    2、存储桶属于某个用户并可以容纳对象,一个存储桶用于存储多个对象
    3、同一个用户可以拥有多个存储桶,不同用户允许使用相同名称的Bucket,因此User名称即可做为Bucket的名称空间

    ●OpenStack Swift:
    提供了user、container和object分别对应于用户、存储桶和对象,不过它还额外为user提供了父级组件account,用于表示一个项目或用户,因此一个account中可以包含一到多个user,它们可共享使用同一组container,并为container提供名称空间

    ●RadosGW:
    提供了user、subuser、bucket和object,其中的user对应于S3的user,而subuser则对应于Swift的user,不过user和subuser都不支持为bucket提供名称空间,因此不同用户的存储桶不允许同名;不过,自jewel版本起,RadosGW引入了tenant(租户)用于为user和bucket提供名称空间,但他是个可选组件

    从上可以看出大多数对象存储的核心资源类型大同小异,如 Amazon S3、OpenStack Swift 与 RadosGw。其中 S3 与 Swift 互不兼容,RadosGw 为了兼容 S3 与 Swift, Ceph 在 RadosGW 集群的基础上提供了 RGW(RadosGateway)数据抽象层和管理层,它可以原生兼容 S3 和 Swift 的 API。
    S3和Swift它们可基于http或https完成数据交换,由RadosGW内建的Civetweb提供服务,它还可以支持代理服务器包括nginx、haproxy等以代理的形式接收用户请求,再转发至RadosGW进程。
    RGW 的功能依赖于对象网关守护进程实现,负责向客户端提供 REST API 接口。出于冗余负载均衡的需求,一个 Ceph 集群上通常不止一个 RadosGW 守护进程。

    2)创建 RGW 接口
    如果需要使用到类似 S3 或者 Swift 接口时候才需要部署/创建 RadosGW 接口,RadosGW 通常作为对象存储(Object Storage)使用,类于阿里云OSS。
    
    //在管理节点创建一个 RGW 守护进程(生产环境下此进程一般需要高可用,后续介绍)
    cd /etc/ceph
    ceph-deploy rgw create node01
    
    ceph -s
      services:
        mon: 3 daemons, quorum node01,node02,node03 (age 3h)
        mgr: node01(active, since 12h), standbys: node02
        mds: mycephfs:1 {0=node02=up:active} 2 up:standby
        osd: 6 osds: 6 up (since 12h), 6 in (since 25h)
        rgw: 1 daemon active (node01)
    
    #创建成功后默认情况下会自动创建一系列用于 RGW 的存储池
    ceph osd pool ls
    rgw.root 
    default.rgw.control			#控制器信息
    default.rgw.meta			#记录元数据
    default.rgw.log				#日志信息
    default.rgw.buckets.index	#为 rgw 的 bucket 信息,写入数据后生成
    default.rgw.buckets.data	#是实际存储的数据信息,写入数据后生成
    
    
    #默认情况下 RGW 监听 7480 号端口
    ssh root@node01 netstat -lntp | grep 7480
    
    curl node01:7480
    <?xml version="1.0" encoding="UTF-8"?><ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
    <ListAllMyBucketsResult xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
      <Owner>
        <ID>anonymous</ID>
        <DisplayName/>
      </Owner>
      <Buckets/>
    </ListAllMyBucketsResult>
    
    
    //开启 http+https ,更改监听端口
    RadosGW 守护进程内部由 Civetweb 实现,通过对 Civetweb 的配置可以完成对 RadosGW 的基本管理。
    
    #要在 Civetweb 上启用SSL,首先需要一个证书,在 rgw 节点生成证书
    1)生成CA证书私钥:
    openssl genrsa -out civetweb.key 2048
    
    2)生成CA证书公钥:
    openssl req -new -x509 -key civetweb.key -out civetweb.crt -days 3650 -subj "/CN=192.168.80.11"
    
    #3、将生成的证书合并为pem
    cat civetweb.key civetweb.crt > /etc/ceph/civetweb.pem
    
    #更改监听端口
    Civetweb 默认监听在 7480 端口并提供 http 协议,如果需要修改配置需要在管理节点编辑 ceph.conf 配置文件
    cd /etc/ceph
    
    vim ceph.conf
    ......
    [client.rgw.node01]
    rgw_host = node01
    rgw_frontends = "civetweb port=80+443s ssl_certificate=/etc/ceph/civetweb.pem num_threads=500 request_timeout_ms=60000"
    
    ------------------------------------------------------------
    ●rgw_host:对应的RadosGW名称或者IP地址
    ●rgw_frontends:这里配置监听的端口,是否使用https,以及一些常用配置:
    •port:如果是https端口,需要在端口后面加一个s。
    •ssl_certificate:指定证书的路径。
    •num_threads:最大并发连接数,默认为50,根据需求调整,通常在生产集群环境中此值应该更大
    •request_timeout_ms:发送与接收超时时长,以ms为单位,默认为30000
    •access_log_file:访问日志路径,默认为空
    •error_log_file:错误日志路径,默认为空
    ------------------------------------------------------------
    
    #修改完 ceph.conf 配置文件后需要重启对应的 RadosGW 服务,再推送配置文件
    ceph-deploy --overwrite-conf config push node0{1..3}
    
    ssh root@node01 systemctl restart ceph-radosgw.target
    
    #在 rgw 节点上查看端口
    netstat -lntp | grep -w 80
    netstat -lntp | grep 443
    
    #在客户端访问验证
    curl http://192.168.80.11:80
    curl -k https://192.168.80.11:443
    
    
    //创建 RadosGW 账户
    在管理节点使用 radosgw-admin 命令创建 RadosGW 账户
    
    radosgw-admin user create --uid="rgwuser" --display-name="rgw test user"
    ......
        "keys": [
            {
                "user": "rgwuser",
                "access_key": "ER0SCVRJWNRIKFGQD31H",
                "secret_key": "YKYjk7L4FfAu8GHeQarIlXodjtj1BXVaxpKv2Nna"
            }
        ],
    
    
    #创建成功后将输出用户的基本信息,其中最重要的两项信息为 access_key 和 secret_key 。用户创建成后功,如果忘记用户信息可以使用下面的命令查看
    radosgw-admin user info --uid="rgwuser"
    
    
    //S3 接口访问测试
    1)在客户端安装 python3、python3-pip
    yum install -y python3 python3-pip
    
    python3 -V
    Python 3.6.8
    
    pip3 -V
    pip 9.0.3 from /usr/lib/python3.6/site-packages (python 3.6)
    
    2)安装 boto 模块,用于测试连接 S3
    pip3 install boto
    
    3)测试访问 S3 接口
    echo 123123 > /opt/123.txt
    
    vim test.py
    #coding:utf-8
    import ssl
    import boto.s3.connection
    from boto.s3.key import Key
    try:
        _create_unverified_https_context = ssl._create_unverified_context
    except AttributeError:
        pass
    else:
        ssl._create_default_https_context = _create_unverified_https_context
    	
    #test用户的keys信息
    access_key = "ER0SCVRJWNRIKFGQD31H"                          #输入 RadosGW 账户的 access_key
    secret_key = "YKYjk7L4FfAu8GHeQarIlXodjtj1BXVaxpKv2Nna"      #输入 RadosGW 账户的 secret_key
    
    #rgw的ip与端口
    host = "192.168.80.11"        #输入 RGW 接口的 public 网络地址
    
    #如果使用443端口,下述链接应设置is_secure=True
    port = 443
    #如果使用80端口,下述链接应设置is_secure=False
    #port = 80
    conn = boto.connect_s3(
        aws_access_key_id=access_key,
        aws_secret_access_key=secret_key,
        host=host,
        port=port,
        is_secure=True,
        validate_certs=False,
        calling_format=boto.s3.connection.OrdinaryCallingFormat()
    )
    
    #一:创建存储桶
    #conn.create_bucket(bucket_name='bucket01')
    #conn.create_bucket(bucket_name='bucket02')
    
    #二:判断是否存在,不存在返回None
    exists = conn.lookup('bucket01')
    print(exists)
    #exists = conn.lookup('bucket02')
    #print(exists)
    
    #三:获得一个存储桶
    #bucket1 = conn.get_bucket('bucket01')
    #bucket2 = conn.get_bucket('bucket02')
    
    #四:查看一个bucket下的文件
    #print(list(bucket1.list()))
    #print(list(bucket2.list()))
    
    #五:向s3上存储数据,数据来源可以是file、stream、or string
    #5.1、上传文件
    #bucket1 = conn.get_bucket('bucket01')
    # name的值是数据的key
    #key = Key(bucket=bucket1, name='myfile')
    #key.set_contents_from_filename('/opt/123.txt')
    # 读取 s3 中文件的内容,返回 string 即文件 123.txt 的内容
    #print(key.get_contents_as_string())
    
    #5.2、上传字符串
    #如果之前已经获取过对象,此处不需要重复获取
    bucket2 = conn.get_bucket('bucket02')
    key = Key(bucket=bucket2, name='mystr')
    key.set_contents_from_string('hello world')
    print(key.get_contents_as_string())
    
    #六:删除一个存储桶,在删除存储桶本身时必须删除该存储桶内的所有key
    bucket1 = conn.get_bucket('bucket01')
    for key in bucket1:
        key.delete()
    bucket1.delete()
    
    
    4)按照以上步骤执行 python 脚本测试
    python3 test.py
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98
    • 99
    • 100
    • 101
    • 102
    • 103
    • 104
    • 105
    • 106
    • 107
    • 108
    • 109
    • 110
    • 111
    • 112
    • 113
    • 114
    • 115
    • 116
    • 117
    • 118
    • 119
    • 120
    • 121
    • 122
    • 123
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    #coding:utf-8
    #boto s3手册:http://boto.readthedocs.org/en/latest/ref/s3.html
    #boto s3快速入门:http://boto.readthedocs.org/en/latest/s3_tut.html
    #如果脚本长时间阻塞,请检查集群状态,开启的端口等
    import ssl
    import boto.s3.connection
    from boto.s3.key import Key
    #异常抛出
    try:
        _create_unverified_https_context = ssl._create_unverified_context
    except AttributeError:
        pass
    else:
        ssl._create_default_https_context = _create_unverified_https_context
    #test用户的keys信息
    access_key = "ER0SCVRJWNRIKFGQD31H"
    secret_key = "YKYjk7L4FfAu8GHeQarIlXodjtj1BXVaxpKv2Nna"
    #rgw的ip与端口
    host = "192.168.82.101"
    #如果使用443端口,下述链接应设置is_secure=True
    port = 443
    #如果使用80端口,下述链接应设置is_secure=False
    #port = 80
    conn = boto.connect_s3(
        aws_access_key_id=access_key,
        aws_secret_access_key=secret_key,
        host=host,
        port=port,
        is_secure=True,
        validate_certs=False,
        calling_format=boto.s3.connection.OrdinaryCallingFormat()
    )
    
    #一:创建存储桶
    conn.create_bucket(bucket_name='bucket01')
    conn.create_bucket(bucket_name='bucket02')
    
    #二:判断是否存在,不存在返回None
    exists = conn.lookup('bucket01')
    print(exists)
    exists = conn.lookup('bucket02')
    print(exists)
    
    #三:获得一个存储桶
    bucket1 = conn.get_bucket('bucket01')
    bucket2 = conn.get_bucket('bucket02')
    
    #四:查看一个bucket下的内容
    print(list(bucket1.list()))
    print(list(bucket2.list()))
    
    #五:向s3上存储数据,数据来源可以是file、stream、or string
    #5.1、上传文件
    bucket1 = conn.get_bucket('bucket01')
    # name的值是数据的key
    key = Key(bucket=bucket1, name='myfile')
    key.set_contents_from_filename(r'D:\PycharmProjects\ceph\123.txt')
    # 读取 s3 中文件的内容,返回 string 即文件 123.txt 的内容
    print(key.get_contents_as_string())
    
    #5.2、上传字符串
    #如果之前已经获取过对象,此处不需要重复获取
    bucket2 = conn.get_bucket('bucket02')
    key = Key(bucket=bucket2, name='mystr')
    key.set_contents_from_string('hello world')
    print(key.get_contents_as_string())
    
    #六:删除一个存储桶,在删除存储桶本身时必须删除该存储桶内的所有key
    bucket1 = conn.get_bucket('bucket01')
    for key in bucket1:
        key.delete()
    bucket1.delete()
    #bucket1.get_all_keys()[0].delete() #删除某一个 key
    
    #迭代遍历删除 buckets and keys
    for bucket in conn:
        for key in bucket:
            print(key.name,key.get_contents_as_string())
    #—个判断文件夹中是否有文件的方法
    bucket1 = conn.get_bucket('bucket01')
    res = bucket1.get_all_keys()
    if len(res) > 0:
        print('有文件')
    else:
        print('为空')
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86

    4.OSD 故障模拟与恢复

    1、模拟 OSD 故障
    如果 ceph 集群有上千个 osd,每天坏 2~3 个太正常了,我们可以模拟 down 掉一个 osd
    
    #如果 osd 守护进程正常运行,down 的 osd 会很快自恢复正常,所以需要先关闭守护进程
    ssh root@node01 systemctl stop ceph-osd@0
    
    #down 掉 osd
    ceph osd down 0
    
    ceph osd tree
    
    
    2、将坏掉的 osd 踢出集群
    //方法一:
    #将 osd.0 移出集群,集群会开始自动同步数据
    ceph osd out osd.0
    
    #将 osd.0 移除 crushmap
    ceph osd crush remove osd.0
    
    #删除守护进程对应的账户信息
    ceph auth rm osd.0
    
    ceph auth list
    
    #删掉 osd.0
    ceph osd rm osd.0
    
    ceph osd stat
    ceph -s
    
    //方法二:
    ceph osd out osd.0
    
    #使用综合步骤,删除配置文件中针对坏掉的 osd 的配置
    ceph osd purge osd.0 --yes-i-really-mean-it
    
    
    3、把原来坏掉的 osd 修复后重新加入集群
    #在 osd 节点创建 osd,无需指定名,会按序号自动生成
    cd /etc/ceph
    
    ceph osd create
    
    #创建账户
    ceph-authtool --create-keyring /etc/ceph/ceph.osd.0.keyring --gen-key -n osd.0 --cap mon 'allow profile osd' --cap mgr 'allow profile osd' --cap osd 'allow *'
    
    #导入新的账户秘钥
    ceph auth import -i /etc/ceph/ceph.osd.0.keyring
    
    ceph auth list
    
    #更新对应的 osd 文件夹中的密钥环文件
    ceph auth get-or-create osd.0 -o /var/lib/ceph/osd/ceph-0/keyring
    
    #加入 crushmap
    ceph osd crush add osd.0 1.000 host=node01		#1.000 代表权重
    
    #加入集群
    ceph osd in osd.0
    
    ceph osd tree
    
    #重启 osd 守护进程
    systemctl restart ceph-osd@0
    
    ceph osd tree		#稍等片刻后 osd 状态为 up	
    
    //如果重启失败
    报错:
    Job for ceph-osd@0.service failed because start of the service was attempted too often. See "systemctl  status ceph-osd@0.service" and "journalctl -xe" for details.
    To force a start use "systemctl reset-failed ceph-osd@0.service" followed by "systemctl start ceph-osd@0.service" again.
    
    #运行
    systemctl reset-failed ceph-osd@0.service && systemctl restart ceph-osd@0.service
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
  • 相关阅读:
    python基于django药房药品销售进销存管理系统
    分享一下商城小程序怎么设置分销功能
    数据结构:共用体+枚举
    React-1 基础知识
    交互入门题瞎做
    MySQL日志系列(1):MySQL日志
    使用ClickHouse分析COS的清单和访问日志
    【JWT】快速了解什么是jwt及如何使用jwt
    -完全数-
    v-model 简单理解
  • 原文地址:https://blog.csdn.net/qq_54235967/article/details/133796476