一些k8s的理论知识

k8s架构

  1. 声明式api, 使用yaml定义需要k8s完成什么样的工作,而不是使用kubectl去调用api完成

对象描述

apiVersion: app/v1   #api版本
kind: Deployment  # 什么类型的对象
-----------typemeta 对象类型的元信息
metadata:
  name: nginx  # 对象名
  labels:  #label定义
    app: nginx
------------对象元信息
spec:
  selector:
    matchLabels:
      app: nginx
  replicas: 3 #副本数量
  template:
    metadata:
      labels: 
        app: nginx
     spec: 
       containers:
         name: nginx
         image: nginx:alpine  # 使用的镜像
         resources:
           requests:
            cpu:100m
            memory: 200Mi
         limits: 
           cpu: 100m
           memory: 200Mi
     imagePullSecrets:
       - name: default-secret
-----------spec 对象期望状态,用什么镜像,有什么副本
status: {}
--------- 对象实际状态,创建对象后可以看到,创建时不用指定

分层

  1. 内核层
  2. 应用层,可以替换成第三方的
  3. 治理层
  4. 接口层
  5. 生态系统层

核心组件

  1. api server
  2. etcd
  3. scheduler
  4. controller manager
  5. cloud manager 各个云服务商自己加入
  6. kubelet
  7. kube proxy
  8. containerd 容器

污点和容忍度

  1. 先在pod.spec中定义容忍度等,让pod去找合适的node,然后使用污点去配置,容忍度是指,可以容忍某些污点
    kubectl taint nodes node1 key1=value1:noschedule 增加污点,后面加个-为移除污点

pod

pod生命周期

  1. pod的status字段中podStatus.phase 表明了pod的声明周期
  2. pod生命周期: pending, running, failed succeed unknown

容器的三种状态
waiting、running、terminated
kubectl describe pod podname 查看容器状态
重启策略,never、always、onfailure

容器分类
基础容器,infra container,其他容器的父容器,为其容器提供共享的命名空间和网咯空间
初始化容器,init container 在容器启动前 需要做的准备工作的配置,例如查看网络和dns配置等,初始化容器可以定义多个,依次执行
业务容器 containers

pod启动

kubectl logs pod-name 查看日志
yaml配置文件中pod.spec中使用command和args来设置pod启动时的命令和参数,command会替代dockerfile中的entry point,args 用来传递参数

pod镜像相关配置

  1. image,指定镜像
  2. imagePullSecret,如果从私有仓库拉取镜像,这个字段设置仓库访问凭证
  3. imagePullPolicy 镜像拉去策略 ifNotPresent 优先本地 Always,从镜像库拉去,Never 仅用本地

pod接收外部内容

  1. 使用env。配置键值对
  2. 使用env.ValueFrom.SecretKeyRef 分别定义key和value
  3. 使用volme挂载

pod探针

探针诊断方式

  1. ExecAction, 在容器内执行命令,看是否执行成功
  2. TCPSocketAction 查看ip:port是否通
  3. HTTPGetAction,发送Get请求,查看返回状态码

探针的两种类型

  1. 存活探针,是否存活,livenessProbe
  2. 就绪探针,探测成功后,接收流量 readinessProbe
livenessProbe:   # 探针类型,存活还是就绪
  exec:  # 诊断方式
    command:
     - cat
     - /tmp/healthy
    initalDelaySeconds: 5  # 启动后多少秒,探针才初始化,默认10s
    periodSeconds: 5  # 探针执行的周期,默认1s
readinessProbe:
   tcpSocket:
     port:8080
livenessProbe:
   httpGet:
     path: /healthz
     port: 8080
     httpHeaders: 
       - name:cstom-header
          value: awesone

container 资源限制
container下的resource字段进行配置

resouses:
  requests: 
    memory: "100Mi"
    cpu: "250m"
  limit:
    memory: "100Mi"
    cpu: "250m"

Deployment
无状态工作负载

  1. replicaSet副本控制器,用于确保pod的副本数量,来实现所扩容
  2. deployment通过控制replicaSet实现水平缩扩容、滚动更新,而不是直接操作pod
    滚动更新
    允许使用新的实例,逐步更新,不停机进行Deployment更新
  3. 可以实现应用程序从一个环境提升至另一个
  4. 可以进行版本回滚
  5. 持续集成和交付,无需停机
# 滚动更新的三种方式
kubectl edit deploy/nginx    # 修改
kubectl set image deploy/nginx nginx=nginx:1.9.1    # 升级,将镜像由1.7.9改为1.9.1的
kubectl apply -f nginx.yaml  # 修改yaml文件重新加载

kubectl rollout status deploy/nginx # 查看更新情况
kubectl rollout history deploy/nginx # 查看更新历史

回滚
kubectl rollout undo deployment/nginx --to-revision=2 指定回滚两个版本

暂停
kubectl rolout pause delopy/nginx
恢复
kubectl rollout resurme deploy/nginx

statefulSet
有状态工作负载
需要创建headless用于解析和pvc,用于数据存储
headless服务创建

  1. 服务名和statefulSet中定义的需要一致
  2. 选择器指定正确的Pod标签
  3. clusterIP设置为None
apiVersion: v1
kind: Service
metadata:
  name: nginx
....
....
....
spec
  selector:
    app: nginx
   clusterIP: None   # 不需要clusterip的负载均衡能力  

PVC创建

containers:
  volumeMounts:
    - name: www
      mountPath: /usr/share/nginx/html

# spec 下定义Mounts的www
volumeClaimTemplates:
  - metadata:
      name: www
     annotations:
       everest.io/disk-volume-type:SAS
     - spec:
       - ReadWriteOne
       resources:
         requests:
           Storage: 1G
      storageClassName:csi-diskS

kubectl get sts 查看stateful的所有

DeamonSet 守护进程集
节点上的守护进程,每创建一个新的node,就会建立一个pod,运行deamon进程,yaml配置中和Deployment相似,不需要设置replicas,如果手工强制停止deamonSet的pod,系统会自动创建新的

Job、CronJob
kind选Job
completions 当前任务需要几个pod执行
parallelism 最多几个并发执行
restartPolicy 选择Never或者OnFailure
backoffLimit 指定job失败后的重试次数
cronjob
kind为CronJob,在spec下定义schedule字段,字段值为linux crontab语法

应用配置

  1. 可变配置信息,不能写入镜像,防止每次修改重启
  2. 敏感信息存储和使用,密码 token
    k8s应用配置
    configMap 管理一般可变配置,解耦镜像和应用程序,不能用于大块数据,主要用于环境变量、命令行参数、配置文件
    Secret 信息采用base64进行编码
apiversion: v1
kind: ConfigMap
metadata:
  name: xxx
data: 
  a: 1
  

使用可以将整个secret或者configmap挂载到volume上,当做文件,或者是在env中使用

k8s持久化存储
pv 持久存储卷
pvc 持久化存储声明
sc 存储类,为存储商提供存储接入
dp 驱动
动态卷,不需要关心卷的类型等,只需要声明pvc和sc进行空间操作
根据用户的配置,pvc进行最合适的pv选择
size、volmeMode、label、sc、accessMode
最后给一个符合条件,空间最小的pv

pv定义

apiVersion: v1
kind: PersistentVolume
metadata:
  name: static-vol
spec: 
  accessModes:  # rwonce读写权限,只能被单个node挂载  readOnlyMany 只读权限,允许多个node挂载  rwMany,读写权限,允许多node
  - ReadWriteOnce
  capacity:
    storage: 10Gi  # 容量
  csi:  # out tree配置信息  厂家提供
    driver.disk.csi.everest.io
    fsType: ext4
    volmeAttributes:
      everest.io/disk-mode: SCSI
      everest.io/disk-volme-type: SAS
      storage.kbernetes.io/csiProvisionerldentity: everest-csi-provisioner
    volumeHandle: xxxx-xxx-xx-xx
    persistentVolmeReclaimPolicy: Delete # retain/Recycle/Delete  保留数据、回收空间、删除
    storageClassName: csi-disk  # storageClass名
    volumeMode: Filesystem  #  挂载成文件系统还是块

使用pvc

apiVersion: v1
kind: PersistentVolmeClaim
metadata: 
  annotations: xxx
  labels:
    app: my
    release: m
  name: xx
  namespace: default
spec:
  accessModes:
    - ReadWriteOnce  # 与pv匹配
  resources:
    requests: 
      storage: 10G
  volumeName/storageClassName: csi-disk  #pv或者storageClass名字
  volumeMode: Filesystem
  selector:   # 根据label 对系统所有的pv进行筛选
    matchLabels: 
      release: "stable"

根据pv来写pvc 相当于静态挂载

如果使用动态挂载,则需要sc

apiVersion: v1
kind: StorageClass
parameters: # 插件驱动
  csi.storage.k8s.io/csi-driver-name: disk.csi.everest.io
  csi.storage.k8s.io/fstype: ext4
  everest.io/disk-volume-type: SSD
  everest.io/passthrough: "true"
provisioner: everest-csi-provisioner  # 指定存储卷供应者
reclaimPolicy: Delete 
volumeBindingMode: immediate  # 立即绑定、waitForFirstConsumer 延迟绑定
allowVolumeExpansion: true  # 是佛允许扩容

kubectl get pv/pvc

api server

承担了关键数据通信部分,其性能高低决定了集群性能高低。主要负责pod service等的增删改查

  1. api。提供restful api
  2. 访问控制层,主要提供访问控制、鉴权、对资源进行控制,判断能否访问对象
  3. Registry,存放各种对象
  4. etcd,持久化存储数据
etcd

etcd,可以以集群方式部署

  1. 其有一个leader节点,处理写请求,保存到日志,同步给其他节点,半数节点成功,主节点则提交这个事务
etcd组成
  1. http server,客户端api和其他节点同步请求和心跳检测请求
  2. Store,处理etcd的各种事务,是大部分api功能的实现
  3. raft 节点选举和数据同步
  4. wal 预写式日志,用于数据持久化,记录了所有事务指令
  5. snapshot 记录etcd某个时刻的全部数据
    wal和snapshot结合实现了数据存储和节点故障恢复等操作

Service

有负载均衡的,管理一组pod

apiVersion: v1
kind: Service
metadata:
  name: nginx
spec:
  selector: 
    app: nginx
  ports:
  - name.service0
    targetPort: 80
    port: 8080
    protocol: TCP
  type: ClusterIp # service类型

kubectl run -i --tty --image nginx:alpine test --rm 进入image操作

可以使用service name直接访问service
需要安装coreDNS插件

然后还会创建endpoint,k8s根据endpoint进行负载均衡,使用kubectl describe svc nginx 可以产看到endpoint

kubectl get endpoints 查看所有的endpoints,pod重启,ip变化,endpoint也会变

全域名
service.namespace.svc.cluster.local

service是kube-proxy和iptabels实现的,kube-proxy list watch apiserver中的service和endpoints,然后会使用iptabels创建service和pod映射关系,并且watch pod的变化,更新规则

service类型

  1. ClusterIp, 用于集群内部访问,一个pod访问一个集群内相同功能的pod
  2. NodePort,集群外部访问,通过节点上的port访问
  3. LoadBalancer,用于外部访问,外部访问loadBalancer,loadbalancer转发给NodePort,外部只需要访问LB
  4. Headless,用于pod间互相发现,不需要使用service的负载均衡
# nodeport的配置
apiVersion: v1
kind: Service
metadata:
  name: nodeport-service
spec: 
  type: NodePort
  ports:
  - port:80
    targetPort: 80
    nodePort: 30120 # 不指定,k8s master会随机30000-32767
  selector: 
    app: nginx

使用nodeip:nodePort 访问 kubectl get xx -o wide -o wide代表显示外部信息

不同LB厂商,对接不一样,其不是k8s的组件

ingress

基于七层的http协议进行转发,根据域名和路径进行路由
ingress controller不是k8s的自带组件,需要自行安装,例如nginx ingress、cce ingress

apiVersion: networking.k8s.io/v1
kind: ingress
metadata:
  name: minimal-ingress
  annotations: # 描述,不同类型的ingress,不同
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
  - http:
    paths: 
    - path: /testpath
      pathType: prefix   # exact 精准  prefix 前缀 
      backend:
        service: 
          name: x
          port: 
            number: 80

华为云产品

cce

  1. 集群管理
  2. 弹性伸缩
  3. 持久卷存储
  4. 容器网络
  5. 亲和、反亲和调度、
  6. 工作负载
  7. 节点、节点池管理

cce敏捷版

提供高性能、可扩展。融合了很多高级功能。mcp多云管理,可以将华为云产品,统一管理

  1. 适用于峰值流量的智能分担
  2. 跨云应用自动容灾
  3. 业务地域策略运营

mcp 云原生多云平台

  1. 混合云统一管理平台

osc,云原生服务中心

  1. 提供云原生服务的全生命周期管理

cie 云原生应用运维工具

  1. 可视化,一键故障诊断

cci云容器实例

  1. 使用kata 方案
  2. 用户值需要申请资源,不需要关心底层实现
  3. 智能调度(volcano)、按需计费、安全容器(运行在为虚拟机上,使用kata技术实现,实现虚拟机级安全隔离)、极速扩容(100ms内)四大特性
  4. 主要用于ai计算、高性能容器批量计算、长期稳定及扩容流量处理

使用cce,用户需要创建node节点,使用cci不需要,只需要创建工作负载

根据资源按小时计费 按s计费根据cpu和内存
适合大规模长期稳定的应用 批量计算,如法扩容,ci/cd

应用容器改造

容器改造步骤

  1. 容器选择
  2. 准备应用运行环境
  3. 编写开机脚本
  4. 编写dockerfile
  5. 制作并上传镜像
  6. 创建工作负载

cce集群选型

  1. cce
  2. cce-turbo 高性能的,大规模的,网络隔离、安全性高

高可用选型

  1. 多主节点跨az(区域)部署
  2. 节点高可用,node跨az部署
  3. 应用高可用

容器存储

  1. devicemapper,有一部分空间是容器共享空间,删除文件后不能立马回收空间,块存储,进行修改,只会修改对应快,适合文件大,修改少的场景
  2. overlayfs,文件存储,对文件进行修改,会修改整个文件,大并发,少io适合

节点池

  1. 一组有相同功能的节点集合,会统筹管理节点,修改配置会影响所有节点

网络选型

  1. 容器隧道网络,基于vxlan隧道封装,最大支持2000节点,
  2. vpc网络,无隧道封装,跨节点通过vpc转发,使用ipvlan(所有虚拟的网口mac地址相同)和vpc路由实现,默认200节点,受限于vpc路由表
  3. 云原生网络2.0

swr 镜像仓库

  1. 镜像扫描,可以扫描镜像中是否包含恶意漏洞等
  2. 跨区镜像同步,基于kafka,两个地区的镜像仓库可以同步,包含自动、手动同步
  3. 触发器、镜像老化,上传镜像自动触发版本更新等操作,老版本镜像自动删除等,自动cicd等

dockerfile

  1. 不要在docker file中升级软件
  2. 一个容器只运行一个进程,不要mysql和PHP等一起运行
  3. 使用exec from格式的cmd和entrypoint
  4. 变化频率相同的放在同一个run中
  5. 使用标签,具体到使用的基础镜像
  6. 删除过程文件,下载的apt包等
  7. 指定workdir
  8. 使用copy代替add

工作负载设计

  1. 需要对资源进行配置
    • besteffort,不设置request和limits,优先被kill
    • burstable,设置的request和limit不一致,oomadj为0-1000
    • guaranteed,request和limit设置的一致,oomadj为-998,越小存活要长久(最后被kill)

容器生命周期

  1. poststart 开始接收请求
  2. perstop 停止之前

健康检查

  1. livenessProbe 检测容器是否存活,如果不存活了,根据策略重启pod
  2. readinessProbe,服务是否可用检查,服务可用才发送请求道这个endpoint

静态动态存储

  1. 静态 pv对应一个pvc
  2. 动态pvc对应storage class,storageclass配置了怎么选择pv kubectl get sc

调度策略

  1. nodeName和NodeSelector(使用调度器选择)
  2. 亲和、反亲和调度,节点和pod亲和、反亲和

伸缩策略

  1. HPA 根据cpu、内存利用率,对无状态的负载进行弹性伸缩,对pod进行缩扩容
  2. customHPA,华为自研,增加了周期性策略,每周、每月或者特别日期进行扩容
  3. cluster autoscaler,监控调度失败的pod,使用最小浪费算法,在节点池中选择节点扩容

服务设计

  1. 根据四层转发,clusterIp、nodePort、Elb(仅此支持公网访问)
  2. 根据七层转发,elb(可以单独为每个监听器配置证书)、nginx,根据uri转发

DNS

容器运维

运维的范围

  1. 环境定义,包括开发、测试、预生产、生产环境
  2. 部署,将包有效的部署到不同坏境
  3. 监控,能监控系统和应用
  4. 告警响应,告警发生时,对告警的响应和处理
  5. 性能调优,包括对各种服务的调优
  6. SLA保障,自动化环境定义、自动化部署、自动化监控

aom,应用运维管理

  1. prometheus—+grafana
  2. 注意grafana 数据十九华
  3. cie容器洞察引擎
    • 开放兼容云原生技术
    • 应用为中心
    • 数据无缝关联
    • 一键集群

敏捷版cie架构

  1. 由log-center和log-agent组成
  2. log-agent负责日志收集,转发
  3. log-center负责日志信息呈现、转存和老化

服务网格

一种云原生的(弹性、服务化、去中心的)、应用层(关注应用的发布、监控、恢复)网络技术

istio

  1. 控制平面,istiod
    • pliot 服务发现和流量管理
    • citadel 证书分发,提供角色控制
    • Galley 配置管理
  2. 数据平面

envoy流量代理

  1. LDS,监听器发现服务
  2. RDS,路由发现服务
  3. CDS,集群发现服务
  4. EDS,集群成员发现服务

gateway

指定可以进入或者离开网格的流量

virtualService 流量控制

  1. 由一组路由规则组成,用于对pod进行寻址,如果流量命中了某个路由规则,就会发送到对应的服务后盾

DestinationRule 路由控制

  1. 流量到达后端后的处理规则,流量先经过VirtualService分发到目标地址,然后调用DestinationRule来分发到该目标的流量

serviceEntry

  1. 将网格的外部服务,添加到服务表中

服务网格监控

metrics

为通过它的流量生成指标和统计,从而监控流量大小以及对流量就行预测

accessLog

会记录完整的日志,请求源、请求目标、请求元数据,用于观测和审计

Distributed Traces 分布式追踪

日志比较孤立,用服务追踪来查看前后因果,非零侵入,需要进行调用链埋点

ASM(应用服务网格)

  1. 基于istio进行了功能的增强
    • 流量,支持复杂入口流量定义
    • 可靠性、可维护性,提供托管控制面板,减少运维负担
    • 协议扩展,支持dubbo和spring cloud
    • 混合部署
    • 可观察性
    • 灰度发布
    • 跨集群治理
    • 大规模治理

灰度发布

  1. 基于请求内容灰度规则
  2. 基于流量比例灰度
  3. 金丝雀
  4. 蓝绿

流量治理

熔断机制

  1. 当某个后端服务连续错误达到某个阈值时,进行熔断,一段时间后重新加入,测试是否可用,如果仍不可用,增加不可用时间,再进行驱逐

故障注入

再系统中加入故障,从而评估系统的稳定性等,无需侵入式的修改代码来实现

  1. 时延故障
  2. 中断故障

服务监控

asm观察微服务调用的请求总数,错误次数、请求时延、最大时延
asm 提供非侵入式的微服务调用链监控

容器多集群管理

MCP

华为基于karmada开发的容器多云和混合云解决方案

karmada架构

  1. karmada api-server 包含了k8s的基本api还有扩展的api
  2. controller 来根据api创建对象
  3. karmada schedler 实现应用在多个容器里的调度
  4. 支持集中式管理,公有云、私有云
  5. 集群隔离
  6. 支持分布式和集中式的集群同步
  7. member集群,由一个或者多个集群组成
  8. 三个重要对象
    • resouse template 创建资源模板
    • propagation policy 多云调度策略,分发策略 分发给那个后端的member,根据resourceSelectors进行调度选择
    • override policy 多云 差异化配置,设置不同的环境变量、不同的镜像仓库等
apiVersion: types.kubefed.io/v1
kind: federatedDeployment
metadata:
  name: test-deployment
  namespace: test-namespace
spec:
  template:
    metadata:
      labels:
        app: nginx
    spec: 
      replicas: 
      selector:
placement:
  clusters:
    - name: member1
    - name: member2
overrides:
  - clusterName: member2
  clusterOverrides:
    - path: "/spec/replicas"
    value: 2

mcp介绍

  1. 天然多云
  2. 多云调度
  3. 多云治理
  4. 多云运维
  5. 多云安全
  6. 业务流量分担
  7. 数据与业务分离
  8. 开发和生产分离
  9. 计算资源合理调配
  10. 业务多活容灾

curl -fsSL get.docker.com -o get-docker.sh

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

推荐阅读更多精彩内容