Ceph集群分布式集群

本篇教程针对外部ceph集群介绍教程,如果是基于K8S内部ceph请前往基于K8S1.28.2实验rook部署ceph - 严千屹博客 (qianyios.top)

机器拓扑

主机名 ip 硬盘1 硬盘2 内存 cpu 软件 OS
ceph1 192.168.48.101 100G 100G 2G 1v Docker 24.0.7
epel最新版本
ceph(reef、18.2.0)
Centos stream 9
ceph2 192.168.48.102 100G 100G 2G 1v Docker 24.0.7
epel最新版本
ceph(reef、18.2.0)
Centos stream 9
ceph3 192.168.48.103 100G 100G 2G 1v Docker 24.0.7
epel最新版本
ceph(reef、18.2.0)
Centos stream 9

已经测试过了,最小配置,可以满足毕设需求,后面可自行根据需要调整

image-20231222141636094

基础配置

系统基础配置

配置主机名

ceph1

1
hostnamectl set-hostname ceph1 && bash

ceph2

1
hostnamectl set-hostname ceph2 && bash

ceph3

1
hostnamectl set-hostname ceph3 && bash

操作节点[所有节点]

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
#关闭防火墙和selinux
sed -i "s#SELINUX=enforcing#SELINUX=disabled#g" /etc/selinux/config
setenforce 0
systemctl disable --now firewalld

#设置时间同步
yum install chrony -y
systemctl start chronyd
systemctl enable chronyd
chronyc sources
chronyc sources

#配置hosts
cat << EOF > /etc/hosts
127.0.0.1 localhost localhost.localdomain localhost4 localhost4.localdomain4
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6
192.168.48.101 ceph1
192.168.48.102 ceph2
192.168.48.103 ceph3
EOF

#配置yum
mkdir repo.bak
mv /etc/yum.repos.d/* repo.bak/
cat > /etc/yum.repos.d/centos9.repo<< "EOF"
[BaseOS]
name=Baseos
baseurl=https://mirrors.aliyun.com/centos-stream/9-stream/BaseOS/x86_64/os
gpgcheck=0
enabled=1

[AppStream]
name=AppStream
baseurl=https://mirrors.aliyun.com/centos-stream/9-stream/AppStream/x86_64/os/
gpgcheck=0
enabled=1

[extras-common]
name=extras-common
baseurl=https://mirrors.aliyun.com/centos-stream/SIGs/9-stream/extras/x86_64/extras-common/
gpgcheck=0
enabled=0

[ceph-reef]
name=ceph-reef
baseurl=https://mirrors.aliyun.com/ceph/rpm-reef/el9/x86_64/
gpgcheck=0
enabled=1

[epel]
name=epel
baseurl=https://mirrors.aliyun.com/epel/9/Everything/x86_64/
gpgcheck=0
enabled=1

[ceph-reef-noarch]
name=ceph-reef-noarch
baseurl=https://mirrors.aliyun.com/ceph/rpm-reef/el9/noarch/
gpgcheck=0
enabled=1

EOF

# 内核参数设置:开启IP转发,允许iptables对bridge的数据进行处理
cat << EOF > /etc/sysctl.conf
net.ipv4.ip_nonlocal_bind = 1
net.ipv4.ip_forward = 1
EOF

cat << EOF > /etc/sysctl.d/ceph.conf
kernel.pid_max = 4194303
vm.swappiness = 0
EOF

sysctl -p

#更新yum源
dnf clean all && dnf makecache

#安装软件
dnf install vim net-tools wget lsof python3 yum-utils device-mapper-persistent-data lvm2 -y

配置ssh免密

操作节点[ceph1]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
yum install -y sshpass 
cat > sshmianmi.sh << "EOF"
#!/bin/bash
# 目标主机列表
hosts=("ceph1" "ceph2" "ceph3") #就改这个
# 主机密码
password="123456" #就改这个
# 生成 SSH 密钥对
ssh-keygen -t rsa -N "" -f ~/.ssh/id_rsa

# 循环遍历目标主机
for host in "${hosts[@]}"
do
# 复制公钥到目标主机
sshpass -p "$password" ssh-copy-id -o StrictHostKeyChecking=no "$host"

# 验证免密登录
sshpass -p "$password" ssh -o StrictHostKeyChecking=no "$host" "echo '免密登录成功'"
done
EOF

sh sshmianmi.sh

安装docker

操作节点[所有节点]

1
2
3
4
5
6
7
8
9
10
11
12
yum-config-manager --add-repo https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.repo
dnf install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
systemctl enable --now docker
mkdir -p /etc/docker
tee /etc/docker/daemon.json <<-'EOF'
{
"registry-mirrors": ["https://pw860av8.mirror.aliyuncs.com"]
}
EOF
systemctl daemon-reload
systemctl restart docker
docker info

安装cephadm

操作节点[所有节点]

1
2
dnf install --assumeyes cephadm
which cephadm

安装ceph集群

启动一个集群

操作节点[ceph1]

在 ceph1 上启动 cephadm bootstrap

1
cephadm bootstrap --mon-ip 192.168.48.101

上述指令会为我们完成以下工作:

  • 创建mon
  • 创建ssh key并且添加到 /root/.ssh/authorized_keys 文件
  • 将集群间通信的最小配置写入/etc/ceph/ceph.conf
  • 将client.admin管理secret密钥的副本写入/etc/ceph/ceph.client.admin.keyring。
  • 将公用密钥的副本写入/etc/ceph/ceph.pub

执行结果如下:

框框中的是ceph页面的账号密码

image-20231222140050214

访问页面并用上面的账号密码登入,就会提示你修改密码,修改成你自己的密码

1
https://192.168.48.101:8443/

image-20231222140150112

运行ceph查看集群健康状态

1
2
[root@ceph1 ~]# ceph -s
bash: ceph: command not found

这里会显示报错,我们要在所有节点需要安装ceph-common

操作节点[所有节点]

1
2
3
4
5
6
cephadm add-repo --release reef
dnf install -y liburing
# 安装ceph-common包
dnf install -y librbd1 ceph-common
# 查看ceph-common是否正常安装
ceph -v

再次在ceph1查看集群健康状况

1
ceph -s

image-20231222140331471

这里有报错提示HEALTH_WARN暂时不用管

添加ceph节点

操作节点[ceph1]

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
#拷贝ceph1的Ceph 集群的公钥至其他节点,以便加入集群
ssh-copy-id -f -i /etc/ceph/ceph.pub root@ceph2
ssh-copy-id -f -i /etc/ceph/ceph.pub root@ceph3
# 添加ceph节点
ceph orch host add ceph2 192.168.48.102
ceph orch host add ceph3 192.168.48.103

删除可以用
ceph orch host rm ceph3
# 查看现在的节点情况
ceph orch host ls
# 添加label后可以允许该节点运行ceph cli(比如cephadm shell命令)
ceph orch host label add ceph2 _admin
ceph orch host label add ceph3 _admin
# 设置 mon 节点
ceph orch apply mon 3
ceph orch apply mon ceph1,ceph2,ceph3
# 查看 mon 详情
[root@ceph1 ~]# ceph mon dump
epoch 3
fsid 233c64c8-9e76-11ee-86fc-000c2965f5d0
last_changed 2023-12-19T14:13:02.000188+0000
created 2023-12-19T13:56:10.829244+0000
min_mon_release 18 (reef)
election_strategy: 1
0: [v2:192.168.48.101:3300/0,v1:192.168.48.101:6789/0] mon.ceph1
1: [v2:192.168.48.103:3300/0,v1:192.168.48.103:6789/0] mon.ceph3
2: [v2:192.168.48.102:3300/0,v1:192.168.48.102:6789/0] mon.ceph2
dumped monmap epoch 3

# 设置 mgr 节点
ceph orch apply mgr 3
ceph orch apply mgr ceph1,ceph2,ceph3
[root@ceph1 ~]# ceph -s
cluster:
id: 233c64c8-9e76-11ee-86fc-000c2965f5d0
health: HEALTH_WARN
OSD count 0 < osd_pool_default_size 3

services:
mon: 3 daemons, quorum ceph1,ceph3,ceph2 (age 7m)
mgr: ceph1.gtejkn(active, since 18m), standbys: ceph2.rvzimn, ceph3.pbajtu
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:

添加OSD

操作节点[ceph1]

image-20231222141243608

1
2
3
ceph orch daemon add osd ceph1:/dev/nvme0n2
ceph orch daemon add osd ceph2:/dev/nvme0n2
ceph orch daemon add osd ceph3:/dev/nvme0n2

查看状态

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[root@ceph1 ~]# ceph -s
cluster:
id: 233c64c8-9e76-11ee-86fc-000c2965f5d0
health: HEALTH_OK

services:
mon: 3 daemons, quorum ceph1,ceph3,ceph2 (age 25m)
mgr: ceph1.gtejkn(active, since 36m), standbys: ceph2.rvzimn, ceph3.pbajtu
osd: 3 osds: 3 up (since 20s), 3 in (since 33s)

data:
pools: 1 pools, 1 pgs
objects: 2 objects, 449 KiB
usage: 81 MiB used, 300 GiB / 300 GiB avail
pgs: 1 active+clean

[root@ceph1 ~]#

至此ceph集群部署成果

ceph存储的使用

存储类型 特征 应用场景 典型设备
块存储(RBD) 存储速度较快 不支持共享存储 [ReadWriteOnce] 虚拟机硬盘 硬盘 Raid
文件存储(CephFS) 存储速度慢(需经操作系统处理再转为块存储) 支持共享存储 [ReadWriteMany] 文件共享 FTP NFS
对象存储(Object) 具备块存储的读写性能和文件存储的共享特性 操作系统不能直接访问,只能通过应用程序级别的API访问 图片存储 视频存储 OSS

块存储(RBD)

配置rbd

操作节点[ceph1]

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
------------------------------------------------------
#创建名为qianyios-rbd的存储池pool
ceph osd pool create qianyios-rbd 128 128

[root@ceph1 ~]# ceph osd pool create qianyios-rbd 128 128
pool 'qianyios-rbd' created


#删除pool用这个
ceph osd pool delete qianyios-rbd qianyios-rbd --yes-i-really-really-mean-it
------------------------------------------------------
#查看列表
ceph osd pool ls

[root@ceph1 ~]# ceph osd pool ls
.mgr
qianyios-rbd
------------------------------------------------------
#将类型为存储池的pool(qianyios-rbd)转换为rbd类型
ceph osd pool application enable qianyios-rbd rbd

[root@ceph1 ~]# ceph osd pool application enable qianyios-rbd rbd
enabled application 'rbd' on pool 'qianyios-rbd'

------------------------------------------------------
#初始化qianyios-rbd存储池
rbd pool init qianyios-rbd
------------------------------------------------------
#创建一个名为 "qy-rbd-img" 的 RBD 镜像,存储在 "qianyios-rbd" 存储池中,镜像的大小为 10GB。
rbg create -p qianyios-rbd --image qy-rbd-img --size 10G
或者(二选一)
rbd create --size 10G qianyios-rbd/qy-rbd-img
#删除镜像用这个
rbd rm qianyios-rbd/qy-rbd-img

------------------------------------------------------
#查看创建状态
rbd ls qianyios-rbd

rbd info qianyios-rbd/qy-rbd-img

[root@ceph1 ~]# rbd ls qianyios-rbd
qy-rbd-img
[root@ceph1 ~]# rbd info qianyios-rbd/qy-rbd-img
rbd image 'qy-rbd-img':
size 10 GiB in 2560 objects
order 22 (4 MiB objects)
snapshot_count: 0
id: 8589e2720f1f
block_name_prefix: rbd_data.8589e2720f1f
format: 2
features: layering, exclusive-lock, object-map, fast-diff, deep-flatten
op_features:
flags:
create_timestamp: Thu Dec 21 23:09:33 2023
access_timestamp: Thu Dec 21 23:09:33 2023
modify_timestamp: Thu Dec 21 23:09:33 2023

------------------------------------------------------
# 创建rbd 用户key(验证文件)
#创建一个名为 "qianyios-user" 的客户端,并为其授予读写qianyios-rbd存储池的权限
ceph auth get-or-create client.qianyios-user mon 'profile rbd' osd 'profile rbd pool=qianyios-rbd' mgr 'profile rbd pool=qianyios-rbd'
# output会输出以下内容
[client.qianyios-user]
key = AQDKVYRl1nZ8ARAAvhXZojmroyy+YIb9dnTGSw==

ceph auth get client.qianyios-user -o /root/ceph.client.qianyios-user.keyring

[root@ceph1 ~]# cat /root/ceph.client.qianyios-user.keyring
[client.qianyios-user]
key = AQDKVYRl1nZ8ARAAvhXZojmroyy+YIb9dnTGSw==
caps mgr = "profile rbd pool=qianyios-rbd"
caps mon = "profile rbd"
caps osd = "profile rbd pool=qianyios-rbd"
------------------------------------------------------

!讲解一下这个创建用户key(验证文件)

  1. 讲解:通过这个命令,会创建一个用户,并分别指定这个用户对mon、osd、mgr的权限。如果不加pool=xxx,则这个用户能管理整个守护进程。

  2. mon的权限必须是mon ‘profile rbd’,因为Mon服务本身就是只读的,你再限制一个只读权限,脱裤子放屁。

  3. 假设你写了 osd ‘profile rbd’,则用户能管理所有pool的osd设备。

  4. 假设你写了osd ‘profile rbd pool=xxx’,则这个用户可以读写名叫xxx的pool的osd设备。

  5. 假设你写了osd ‘profile rbd-read-only pool=xxx’,则这个用户只能读取名叫xxx的pool中的osd数据,不能写入。

  6. mgr同理,但是最好不要写mgr ‘profile rbd’,否则A池的管理员能够关闭B池的librados接口,那就糟糕了。

举例:

  1. 创建一个volumes用户,该用户对volumes池拥有读写权限
1
ceph auth get-or-create client.volumes mon 'profile rbd' osd 'profile rbd pool=volumes' mgr 'profile rbd pool=volumes'
  1. 创建一个volumes用户,该用户对volumes池拥有只读权限。
1
ceph auth get-or-create client.volumes mon 'profile rbd' osd 'profile rbd-read-only pool=volumes' mgr 'profile rbd pool=volumes'

客户端挂载

这里新开一台客户机centos stream 9,这个客户端可以是你的任何一台机要挂载的机子

添加yum

操作节点[test]测试机

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
mkdir repo.bak
mv /etc/yum.repos.d/* repo.bak/
cat > /etc/yum.repos.d/centos9.repo<< "EOF"
[BaseOS]
name=Baseos
baseurl=https://mirrors.aliyun.com/centos-stream/9-stream/BaseOS/x86_64/os
gpgcheck=0
enabled=1

[AppStream]
name=AppStream
baseurl=https://mirrors.aliyun.com/centos-stream/9-stream/AppStream/x86_64/os/
gpgcheck=0
enabled=1

[extras-common]
name=extras-common
baseurl=https://mirrors.aliyun.com/centos-stream/SIGs/9-stream/extras/x86_64/extras-common/
gpgcheck=0
enabled=0

[ceph-reef]
name=ceph-reef
baseurl=https://mirrors.aliyun.com/ceph/rpm-reef/el9/x86_64/
gpgcheck=0
enabled=1

[epel]
name=epel
baseurl=https://mirrors.aliyun.com/epel/9/Everything/x86_64/
gpgcheck=0
enabled=1

[ceph-reef-noarch]
name=ceph-reef-noarch
baseurl=https://mirrors.aliyun.com/ceph/rpm-reef/el9/noarch/
gpgcheck=0
enabled=1

EOF
dnf clean all && dnf makecache
客户端安装cephadm和ceph-common

操作节点[test]测试机

1
dnf install -y cephadm ceph-common
拷贝ceph1的证书至客户端

把ceph1创建的ceph.client.qianyios-user.keyring ceph.conf复制到客户端(192.168.48.128)

1
scp /root/ceph.client.qianyios-user.keyring /etc/ceph/ceph.conf 192.168.48.128:/etc/ceph/

image-20231221234526458

1
2
3
4
5
6
7
[root@test ~]# ll /etc/ceph/
total 12
-rw-r--r-- 1 root root 185 Dec 21 23:45 ceph.client.qianyios-user.keyring
-rw-r--r-- 1 root root 283 Dec 21 23:45 ceph.conf
-rw-r--r-- 1 root root 92 Dec 12 08:01 rbdmap
[root@test ~]#

开始挂载
1
lsblk

image-20231221234732781

1
2
3
4
5
6
#挂载块存储
rbd map qianyios-rbd/qy-rbd-img --keyring /etc/ceph/ceph.client.qianyios-user.keyring --id qianyios-user
-----
/dev/rbd0
-----
lsblk

image-20231221235206886

此时的/dev/rbd0并不能使用,我们需要格式化,并且挂载到本机

1
2
#格式化
mkfs.xfs /dev/rbd0

image-20231221235300388

1
2
3
4
5
#创建挂载点
mkdir /opt/qianyios-rbd
mount /dev/rbd0 /opt/qianyios-rbd
echo "qianyios-rbd" > /opt/qianyios-rbd.txt
cat /opt/qianyios-rbd.txt

image-20231221235710235

取消挂载
1
2
3
umount /opt/qianyios-rbd
rbd unmap qianyios-rbd/qy-rbd-img
lsblk

image-20231221235845353

文件存储(CephFS)

创建cephfs

部署元数据服务器,创建一个CephFS(文件系统), 名字为qianyios-fs

1
2
3
4
5
6
ceph fs volume create qianyios-fs --placement="3 ceph1 ceph2 ceph3"
# 查看状态
ceph fs volume ls
ceph orch ps --daemon-type mds
ceph fs status qianyios-fs
ceph mds stat

image-20231222130830482

设置nfs高可用

先生成nfs和nfs_ingress配置文件。其中nfs_ingress是为了nfs高可用配置的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
cat <<EOF > nfs.yaml
service_type: nfs
service_id: nkonfs
placement:
hosts:
- ceph1
- ceph2
- ceph3
spec:
port: 2048
EOF

cat <<EOF >nfs_ingress.yaml
service_type: ingress
service_id: nfs.nkonfs
placement:
count: 3
spec:
backend_service: nfs.nkonfs
frontend_port: 2049
monitor_port: 9001
virtual_ip: 192.168.48.200/24#高可用vip
EOF
1
2
3
4
5
6
7
ceph orch apply -i nfs.yaml
ceph orch apply -i nfs_ingress.yaml

ceph orch ls --service_name=nfs.nkonfs
ceph orch ls --service_name=ingress.nfs.nkonfs
ceph nfs cluster ls
ceph nfs cluster info nkonfs

image-20231222131458220

导出nfs

1
2
3
ceph nfs export create cephfs --cluster-id nkonfs --pseudo-path /qy-fs --fsname qianyios-fs
# 查看详情
ceph nfs export info nkonfs /qy-fs

image-20231222131516871

客户端挂载NFS

注意:只支持 NFS v4.0+ 的协议。
由于showmount 不支持 NFS v4,纯 NFSv4又不提供其他获取导出列表的方法,只能期望挂载的时候命令不要输错了。
在客户端执行以下命令:

1
2
3
dnf install -y nfs-utils
mkdir /qy-fs-data
mount -t nfs -o nfsvers=4.1,proto=tcp 192.168.48.200:/qy-fs /qy-fs-data

image-20231222131709705

测试文件写入

1
2
3
4
5
6
7
8
9
10
11
tar zcf etc.tar.gz /etc

#测试文件写入
cp etc.tar.gz /qy-fs-data/
ll /qy-fs-data/

#生成500M大文件写入
dd if=/dev/zero of=/qy-fs-data/testfile bs=1M count=500

#查看分区使用
df -h /fsdata

image-20231222132148957

对象存储(RGW)

RGW(RADOS Gateway)是Ceph存储系统的一部分,它提供了一个对象存储服务

RGW可以作为一个独立的服务运行也可以与Ceph集群的其他组件(如OSD和MON)一起部署

区域(zone): 一个ceph集群可以包含多个区域,一个区域只属于一个集群,一个区域可以有多个RGW

区域组(zonegroup):由一个或多个区域组成,包含一个主区域(master zone),其他区域称为Secondary Zone,区域组内的所有区域之间同步数据

域(realm): 同一个或多个区域组组成,包含一个主区域组,其他都次区域组。域中的所有rados网关都从位于主区域组和主区域中的rados网关拉取配置

设置radosgw区域和用户

image-20231220233812600

所有节点部署qianyios领域和qianyios-shenzhen 区域的rgw守护程序:

操作节点[所有节点]

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
1.# 如果尚未创建 realm(领域),请首先创建一个名字为qianyios的 realm(领域) :
radosgw-admin realm create --rgw-realm=qianyios
radosgw-admin realm list

2.# 接下来创建一个新的 zonegroup(区域组):
radosgw-admin zonegroup create --rgw-zonegroup=qianyios-shenzhen --master
radosgw-admin zonegroup list

3.#接下来创建一个 zone:
radosgw-admin zone create --rgw-zonegroup=qianyios-shenzhen --rgw-zone=qianyios-shenzhen-zone1 --master
#这个命令用于创建一个名为 qianyios-shenzhen-zone1 的 RadosGW 区域(Zone),并将它归属于 qianyios-shenzhen 的区域组(Zone Group)。
radosgw-admin zone list

4.#这个命令用于更新指定的 RadosGW 域(Realm)的周期(Period)。--rgw-realm=qianyios 参数指定要更新的 RadosGW 域的名称为 qianyios
radosgw-admin period update --rgw-realm=qianyios --commit


5.#在 Ceph 集群中部署一个名为 qianyios-rgw 的 RadosGW 实例,并将其关联到指定的域(Realm)、区域组(Zone Group)和区域(Zone),并指定了存储位置。
ceph orch apply rgw qianyios-rgw --realm=qianyios --zonegroup=qianyios-shenzhen --zone=qianyios-shenzhen-zone1 --placement="3 ceph1 ceph2 ceph3" --port=8080

#删除实例用这个
ceph orch rm rgw.qianyios-rgw

6.# 查看各节点 rgw 是否启动
ceph orch ps --daemon-type rgw

7.# 创建创建 radosgw 用户名为admin,显示名称为admin user
radosgw-admin user create --uid="admin" --display-name="admin user"

8.# 创建完成之后需要把access_key和secret_key保存下来,也可以使用下面的命令来查看
radosgw-admin user info --uid=admin

"keys": [
{
"user": "admin",
"access_key": "LAI14X0RJVJG3QGR16FP",
"secret_key": "t11PVzqYtEvb22ajctD0U5tCN5d0CaDPkZENiqTb"
}
],

使用s3 browser点击这里访问官网下载软件,将以上access_key,secret_key填上,可以正常连接,但是没有桶,手动创建一个桶,并上传文件,正常。

image-20231221001128450

此时什么都没有,创建一个桶

image-20231221001200312

image-20231221001228678

在桌面创建一个test文件移进去

image-20231221001351913

特别声明
千屹博客旗下的所有文章,是通过本人课堂学习和课外自学所精心整理的知识巨著
难免会有出错的地方
如果细心的你发现了小失误,可以在下方评论区告诉我,或者私信我!
非常感谢大家的热烈支持!