kolla-ceph 1:如何用kolla来部署容器化ceph集群

系列链接

  1. //www.greatytc.com/p/f18a1b3a4920 如何用kolla来部署容器化ceph集群
  2. //www.greatytc.com/p/a39f226d5dfb 修复一些部署中遇到的问题
  3. //www.greatytc.com/p/d520fed237c0 在kolla ceph中引入device classes特性
  4. //www.greatytc.com/p/d6e047e1ad06 支持bcache设备和iscsi及多路径设备
  5. //www.greatytc.com/p/ab8251fc991a 一个ceph容器化部署编排项目

简介

kolla 是openstack的容器化部署项目,主要目的是实现生产级别容器化openstack平台的部署,做到开箱即用。kolla利用ansible来编排相关容器的部署。

ceph作为开源的分布式存储,与openstack的联系很紧密,kolla实现了简单的ceph集群部署以及与openstack组件比如cinder,manila,nova,glance之间的交互。

kolla项目包含两个代码:

下面就简单说下用kolla来部署ceph的优缺点:

kolla部署ceph的优缺点

  • 优点
1. kolla-ansible的部署过程会比较环境的差异化,比如镜像的tag发生了变化,相关的配置发生了变化,这些变化的部分才会被应用到环境上,而没有发生的部分则会保持不变。反应到ceph的部署中,则是可以很方便的来部署ceph和升级ceph。

2. kolla中初始化ceph osd的过程很巧妙,主要工作是围绕disk的partname来实现,而后ceph-osd容器各自对应相应的磁盘,所以添加新的osd或者修复损坏的osd都很方便。

3. ansible自身的优点(方便定制化开发)
  • 缺点
1. 任意改动会对整个ceph集群的所有服务进行应用, 比如升级ceph的镜像,所有的osd会被重启(最小可以做到只升级一个节点的组件,使用--limit或者ANSIBLE_SERIAL=1,但是因为部署过程有一些问题,这些特性并不能很好的适用)。

2. 对ceph的新特性支持不足,kolla ceph现在支持luminous的部署,但是对于一些新特性比如device class和支持device class的pool创建这些都不支持。

3. 对于ceph的bluestore部署上,不支持bcache磁盘(磁盘分区名不支持)以及多路径磁盘(初始化流程不支持)

也就是说,kolla目前对ceph的支持,作为一个测试集群来说足够了,但是对于生产化的ceph集群,还是有许多方面需要改进。可能社区的本来目的就是作为一个测试集群,所以在cinder/manila等组件的部署中推出了对外接ceph集群的支持。

我接触kolla两年多了,主要是用kolla来部署openstack组件和ceph在生产环境,所以针对使用kolla来部署生产环境标准的ceph集群做了一些工作,下面我会写一些文章来讲解一些相关的改进工作。我也提交过一些改进到社区,可能是因为改动太大,而社区本身也缺乏对ceph了解的人,所以到最后就是一些改动大的commits一直搁置。

kolla ceph部署

先用社区的rocky稳定版来部署一个集群。因为kolla-ansible可以把ceph和其他openstack组件一起部署,但是这种方式不利于ceph集群的维护,所以比较推荐的方式是单独部署ceph,然后与openstack组件的对接用external_ceph的方式来使用.

节点初始化

kolla部署需要一个部署节点,最好是与其他ceph节点区分开

  • 节点介绍

节点要求,至少需要一个网卡。

ps:mon最好先设置成三个或者两个,可以后续再添加,多余三个会卡住,我提交了一个commit到社区来修复这个问题.

commit url : https://review.openstack.org/652606

usage hostname ip disks usage
deploy deploy-node 192.168.0.10 deploy, docker_registry
ceph ceph-node1 192.168.0.11 sdb,sdc,sdd mon, mgr, osd , mds ,rgw
ceph ceph-node2 192.168.0.12 sdb,sdc,sdd osd
ceph ceph-node3 192.168.0.13 sdb,sdc,sdd osd
  • 公共部分(所有节点都需要安装)
# yum 安装源及必要包
yum install epel-release -y
yum install python-pip -y
yum install -y python-devel libffi-devel gcc openssl-devel git

# 安装docker api
pip install docker

# 安装docker
sudo yum install -y https://download.docker.com/linux/centos/7/x86_64/stable/Packages/docker-ce-18.03.0.ce-1.el7.centos.x86_64.rpm

# docker的配置
配置docker的driver和对应的registry(如果有公共的registry,则不用配置)
例如:
tee /etc/docker/daemon.json <<-'EOF'
{
  "storage-driver": "devicemapper",
  "insecure-registries":["192.168.0.10:4000"]
}
EOF

# 新建部署用户kollasu
useradd -d /home/kollasu -m kollasu
passwd kollasu #修改密码
echo "kollasu ALL = (root) NOPASSWD:ALL" | sudo tee /etc/sudoers.d/kollasu
chmod 0440 /etc/sudoers.d/kollasu

# nscd(kolla自定义了一些用户,如manila,ceph等,用来与主机上的用户区分,需进行如下配置)
yum install -y nscd
sed -i 's/\(^[[:space:]]*enable-cache[[:space:]]*passwd[[:space:]]*\)yes/\1no/g' /etc/nscd.conf
sed -i 's/\(^[[:space:]]*enable-cache[[:space:]]*group[[:space:]]*\)yes/\1no/g' /etc/nscd.conf
systemctl restart nscd
  • 部署节点
# 安装ansible
pip install -U ansible==2.4.1 (rocky版本需要2.4以上版本ansible)

# 建立部署节点到其他节点的无密码访问,并且ssh用户具有对应节点的root权限(如kollasu用户)

#  建立自己的registry
docker run -d -p 4000:5000 --restart=always --name registry registry:2
  • ceph osd disk初始化
systemctl daemon-reload

sudo sgdisk --zap-all -- /dev/sdb
sudo sgdisk --zap-all -- /dev/sdc
sudo sgdisk --zap-all -- /dev/sdd

sudo /sbin/parted  /dev/sdb  -s  -- mklabel  gpt  mkpart KOLLA_CEPH_OSD_BOOTSTRAP_BS_FOO1  1 -1
sudo /sbin/parted  /dev/sdc  -s  -- mklabel  gpt  mkpart KOLLA_CEPH_OSD_BOOTSTRAP_BS_FOO2  1 -1
sudo /sbin/parted  /dev/sdd  -s  -- mklabel  gpt  mkpart KOLLA_CEPH_OSD_BOOTSTRAP_BS_FOO3  1 -1

ps: Kolla在初始化的时候会以partname来判断如何初始化该磁盘为osd, filestore的前缀是KOLLA_CEPH_OSD_BOOTSTRAP, bluestore的前缀是KOLLA_CEPH_OSD_BOOTSTRAP_BS, 前缀后面的名称来标识不同的OSD,比如KOLLA_CEPH_OSD_BOOTSTRAP_BS_FOO1和KOLLA_CEPH_OSD_BOOTSTRAP_BS_FOO2就分别对应两个不同的osd,名称随意,后面kolla会根据申请的osd id来修改磁盘名称。

对于bluestore, 一共有四类磁盘, osd data 分区, block分区, wal分区和db分区:

A small partition is formatted with XFS and contains basic metadata for the OSD. This data directory includes information about the OSD (its identifier, which cluster it belongs to, and its private keyring).
The rest of the device is normally a large partition occupying the rest of the device that is managed directly by BlueStore contains all of the actual data. This primary device is normally identifed by a block symlink in data directory.

It is also possible to deploy BlueStore across two additional devices:

A WAL device can be used for BlueStore’s internal journal or write-ahead log. It is identified by the block.wal symlink in the data directory. It is only useful to use a WAL device if the device is faster than the primary device (e.g., when it is on an SSD and the primary device is an HDD).
A DB device can be used for storing BlueStore’s internal metadata. BlueStore (or rather, the embedded RocksDB) will put as much metadata as it can on the DB device to improve performance. If the DB device fills up, metadata will spill back onto the primary device (where it would have been otherwise). Again, it is only helpful to provision a DB device if it is faster than the primary device.

kolla是通过磁盘名称的后缀来判断作为osd的哪个分区的, 在filestore中, "J" 用来表示日志盘,在bluestore中,"B"表示block,"W"表示wal,"D"表示db. 例如:

sudo /sbin/parted  /dev/sdb  -s  -- mklabel  gpt  mkpart  KOLLA_CEPH_OSD_BOOTSTRAP_BS_FOO1 1 200
sudo /sbin/parted /dev/sdb -s mkpart  KOLLA_CEPH_OSD_BOOTSTRAP_BS_FOO1_W  201  2249
sudo /sbin/parted /dev/sdb -s mkpart  KOLLA_CEPH_OSD_BOOTSTRAP_BS_FOO1_D  2250  4298
sudo /sbin/parted /dev/sdb -s mkpart  KOLLA_CEPH_OSD_BOOTSTRAP_BS_FOO1_B  4299  100%

可以自己定义各分区的大小和对应的磁盘, kolla会根据partname自动匹配。如果没指定专门的block分区, 则KOLLA_CEPH_OSD_BOOTSTRAP_BS_FOO1所在的磁盘会被自动格式化成两个分区,osd data分区和block分区, 而该磁盘上的其他分区都会被清理掉。

构建ceph镜像

使用配置文件来单独构建ceph镜像.

[root@deploy-node rocky-ceph]# tree -L 1
.
├── build-test
├── ceph-test
├── kolla
└── kolla-ansible

先看一下我们的部署脚本, 这里我们直接下载的是kolla和kolla-ansible的源代码, 都是stable/rocky分支,用源码部署的好处就是你可以方便的进行定制化开发.

build-test是构建镜像的配置

.
├── build-test
│   └── kolla-build.conf

内容如下:

[DEFAULT]
base = centos
profile = image_ceph

namespace = kolla
install_type = source

retries = 1
push_threads = 4
maintainer = kolla Project

[profiles]
image_ceph = cron,kolla-toolbox,fluentd,ceph

cron和kolla-toolbox以及fluentd是公共镜像

构建命令

python kolla/kolla/cmd/build.py --config-file build-test/kolla-build.conf --push --registry 192.168.0.10:4000 --tag cephRocky-7.0.2.0001 --type source

部署ceph

kolla-ansible的部署过程中可以使用--tags来指定要部署的项目,当然为了单独部署ceph,我们可以在globals.yml文件中disable掉其他所有的项目,只保留ceph相关的.

├── ceph-test
│   ├── custom
│   ├── globals.yml
│   ├── multinode-inventory
│   └── passwords.yml
  • globals.yml 如下:
---
# The directory to merge custom config files the kolla's config files
node_custom_config: "{{ CONFIG_DIR }}/custom"

# The project to generate configuration files for
project: ""

# The directory to store the config files on the destination node
node_config_directory: "/home/kollasu/kolla/{{ project }}"

# The group which own node_config_directory, you can use a non-root
# user to deploy kolla
config_owner_user: "kollasu"
config_owner_group: "kollasu"

###################
# Kolla options
###################
# Valid options are ['centos', 'debian', 'oraclelinux', 'rhel', 'ubuntu']
kolla_base_distro: "centos"

# Valid options are [ binary, source ]
kolla_install_type: "source"

kolla_internal_vip_address: ""

####################
# Docker options
####################
### Example: Private repository with authentication

docker_registry: "192.168.0.10:4000"
docker_namespace: "kolla"

docker_registry_username: ""

####################
# OpenStack options
####################
openstack_release: "auto"
openstack_logging_debug: "False"

enable_glance: "no"
enable_haproxy: "no"
enable_keystone: "no"
enable_mariadb: "no"
enable_memcached: "no"
enable_neutron: "no"
enable_nova: "no"
enable_rabbitmq: "no"

enable_ceph: "yes"
enable_ceph_mds: "yes"
enable_ceph_rgw: "yes"
enable_ceph_nfs: "no"
enable_ceph_dashboard: "{{ enable_ceph | bool }}"
enable_chrony: "no"
enable_cinder: "no"
enable_fluentd: "yes"
enable_heat: "no"
enable_horizon: "no"
enable_manila: "no"

###################
# Ceph options
###################
# Valid options are [ erasure, replicated ]
ceph_pool_type: "replicated"

# Integrate Ceph Rados Object Gateway with OpenStack keystone
enable_ceph_rgw_keystone: "no"

ceph_erasure_profile: "k=2 m=1 ruleset-failure-domain=osd"

ceph_pool_pg_num: 32
ceph_pool_pgp_num: 32

osd_initial_weight: "auto"

# Set the store type for ceph OSD
# Valid options are [ filestore, bluestore]
ceph_osd_store_type: "bluestore"
  • multinode-inventory如下:
[storage-mon]
ceph-node1 ansible_user=kollasu network_interface=eth0 api_interface=eth0 storage_interface=eth0 cluster_interface=eth0
ceph-node2 ansible_user=kollasu network_interface=eth0 api_interface=eth0 storage_interface=eth0 cluster_interface=eth0

[storage-osd]
ceph-node1 ansible_user=kollasu network_interface=eth0 api_interface=eth0 storage_interface=eth0 cluster_interface=eth0
ceph-node2 ansible_user=kollasu network_interface=eth0 api_interface=eth0 storage_interface=eth0 cluster_interface=eth0
ceph-node3 ansible_user=kollasu network_interface=eth0 api_interface=eth0 storage_interface=eth0 cluster_interface=eth0

[storage-rgw]
ceph-node1 ansible_user=kollasu network_interface=eth0 api_interface=eth0 storage_interface=eth0 cluster_interface=eth0

[storage-mgr]
ceph-node1 ansible_user=kollasu network_interface=eth0 api_interface=eth0 storage_interface=eth0 cluster_interface=eth0

[storage-mds]
ceph-node1 ansible_user=kollasu network_interface=eth0 api_interface=eth0 storage_interface=eth0 cluster_interface=eth0

[storage-nfs]

[ceph-mon:children]
storage-mon

[ceph-rgw:children]
storage-rgw

[ceph-osd:children]
storage-osd

[ceph-mgr:children]
storage-mgr

[ceph-mds:children]
storage-mds

[ceph-nfs:children]
storage-nfs

这样定义group的好处就是可以随意的修改你的集群服务的节点,而针对每个节点指定相应的用户和interface也可以适应更复杂的情况,比如网卡名称不同的节点组成的集群,如果都配置默认的同一网卡名,安装中就会报错.

  • passwords.yml 如下(配置这些是因为在部署中利用模板生成配置文件会用到这些参数,没有会报错):
ceph_cluster_fsid: 804effd3-1013-4e57-93ca-983a13cfa133
docker_registry_password:
keystone_admin_password:
  • custom文件夹中放置的是你自己的ceph.conf文件,这个ceph.conf会跟kolla-ansible根据模板生成的ceph.conf合并,最终作为ceph集群的配置文件(优先级自定义的ceph.conf要高于自动生成的ceph.conf).
[global]
rbd_default_features = 1
public_network = 192.168.0.0/24
cluster_network = 192.168.0.0/24
osd_pool_default_size = 2
osd_pool_default_min_size = 1
osd_crush_update_on_start = false
osd_class_update_on_start = false
mon_max_pg_per_osd = 500
mon_allow_pool_delete = true

...
  • 部署ceph
chmod +x kolla-ansible/tools/kolla-ansible

# pull镜像到具体节点
kolla-ansible/tools/kolla-ansible pull --configdir ceph-test -i ceph-test/multinode-inventory --passwords ceph-test/passwords.yml --tags ceph -e openstack_release=cephRocky-7.0.2.0001

# 部署ceph集群
kolla-ansible/tools/kolla-ansible deploy --configdir ceph-test -i ceph-test/multinode-inventory --passwords ceph-test/passwords.yml --tags ceph -e openstack_release=cephRocky-7.0.2.0001

升级ceph集群

kolla-ansible对ceph升级既有方便的地方,即按顺序自动升级所有组件,mon-->mgr-->osd-->rgw-->mds-->nfs,可以自动化升级所有容器的镜像.

缺点就是升级是针对所有服务,不能具体指定升级某一项.而在osd的升级过程中缺乏一些对ceph集群状态的检测,kolla-ansible升级osd是同时升级所有节点上(一次最多可执行ANSIBLE_FORKS规定的节点数)的osd,单个节点上的osd是顺序升级的. 理论上来说只要镜像没有问题,osd升级过程中的重启是很快的,不会影响集群的状态.但是不怕一万就怕万一,同时升级好几个节点的osd出现问题的概率当然也大,我们理想的状态是升级的服务可以自动指定,osd的升级过程可以做到单节点上顺序升级,然后升级中伴随着ceph状态的检测.

  • 构建新镜像
python kolla/kolla/cmd/build.py --config-file build-test/kolla-build.conf --push --registry 192.168.0.10:4000 --tag cephRocky-7.0.2.0002 --type source
  • 升级ceph
kolla-ansible/tools/kolla-ansible upgrade --configdir ceph-test -i ceph-test/multinode-inventory --passwords ceph-test/passwords.yml --tags ceph -e openstack_release=cephRocky-7.0.2.0002

osd 修复

  • osd 挂载关系

osd分区初始化之后,会根据osd data分区的uuid,把分区挂载到/var/lib/ceph/osd/${uuid}目录下,然后启动容器的时候把/var/lib/ceph/osd/${uuid}目录以docker volumes的方式挂载到容器中,/var/lib/ceph/osd/${uuid}:/var/lib/ceph/osd/ceph-${osd_id}.

以uuid的方式挂载,很适合带cache的disk磁盘和多路径磁盘,这两者的磁盘名称很容易变化,但是uuid是一直不变的.

  • osd 修复

kolla-ansible启动的osd容器有时候会因为磁盘的原因进入故障,修复也是很简单,重新格式化磁盘并重新部署, 当然修复之前要先踢出osd,修复后先置osd的weight值为0,然后慢慢添加.

举例

  • 以osd.7为例
(ceph-mon)[root@ceph-node1 /]# ceph osd tree
ID CLASS WEIGHT  TYPE NAME              STATUS REWEIGHT PRI-AFF
-1       0.44989 root default                                   
-4       0.14996     host 192.168.0.11                         
11       0.04999         osd.11             up  1.00000 1.00000
12       0.04999         osd.12             up  1.00000 1.00000
13       0.04999         osd.13             up  1.00000 1.00000
-2       0.14996     host 192.168.0.12                        
 0       0.04999         osd.0              up  1.00000 1.00000
 3       0.04999         osd.3              up  1.00000 1.00000
 6       0.04999         osd.6              up  1.00000 1.00000
-3       0.14996     host 192.168.0.13                          
 1       0.04999         osd.1              up  1.00000 1.00000
 4       0.04999         osd.4              up  1.00000 1.00000
 7       0.04999         osd.7            down  1.00000 1.00000
  • 去ceph-node3查看osd,可知disk为sdb
Disk /dev/sdb: 53.7 GB, 53687091200 bytes, 104857600 sectors
Units = sectors of 1 * 512 = 512 bytes
Sector size (logical/physical): 512 bytes / 512 bytes
I/O size (minimum/optimal): 512 bytes / 512 bytes
Disk label type: gpt
Disk identifier: FA31FD88-190E-4CA4-AF0D-E31AB1FCADDC


#         Start          End    Size  Type            Name
 1         2048       206847    100M  unknown         KOLLA_CEPH_DATA_BS_7
 2       206848    104857566   49.9G  unknown         KOLLA_CEPH_DATA_BS_7_B

  • 解绑磁盘
# df -h 命令查看

/dev/sdb1                 97M  5.3M   92M   6% /var/lib/ceph/osd/0ffdd2fc-41cd-429c-84ee-8150467c06ed

# 解绑
umount /var/lib/ceph/osd/0ffdd2fc-41cd-429c-84ee-8150467c06ed

# 清理/etc/fstab
# 删除sdb1对应的挂载信息
UUID=0ffdd2fc-41cd-429c-84ee-8150467c06ed /var/lib/ceph/osd/0ffdd2fc-41cd-429c-84ee-8150467c06ed xfs defaults,noatime 0 0
  • 清理osd
# 清理旧的osd

osd_number=7
ceph osd out ${osd_number}
ceph osd crush remove osd.${osd_number}
ceph auth del osd.${osd_number}
ceph osd rm ${osd_number}

docker stop ceph_osd_7
docker rm ceph_osd_7
  • 重新初始化磁盘
systemctl daemon-reload

sudo sgdisk --zap-all -- /dev/sdb

sudo /sbin/parted  /dev/sdb  -s  -- mklabel  gpt  mkpart KOLLA_CEPH_OSD_BOOTSTRAP_BS_FOO1  1 -1
  • 重新部署osd
kolla-ansible/tools/kolla-ansible deploy --configdir ceph-test -i ceph-test/multinode-inventory --passwords ceph-test/passwords.yml --tags ceph -e openstack_release=cephRocky-7.0.2.0001

确认镜像的tag没有发生变化,则这次部署只会重新添加一个新的osd,并不会影响之前的osd.

总结

这篇主要讲了一下社区中ceph的部署,下一篇会针对ceph集群的部署和维护中存在的问题,进一步的改进.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 198,322评论 5 465
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,288评论 2 375
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 145,227评论 0 327
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,015评论 1 268
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,936评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,534评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,995评论 3 389
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,616评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,907评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,923评论 2 315
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,741评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,525评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,016评论 3 301
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,141评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,453评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,054评论 2 343
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,249评论 2 339

推荐阅读更多精彩内容