IT评测·应用市场-qidao123.com

标题: 一文读懂K8S的PV和PVC以及实践攻略 [打印本页]

作者: 伤心客    时间: 2024-10-19 10:48
标题: 一文读懂K8S的PV和PVC以及实践攻略

博客主页:StevenZeng学堂
本文专栏: 一文读懂Kubernetes | 一文读懂Harbor | 云原生安全实战指南



❤️ 择要:作为当今最火的开源云原生体系,Kubernetes 是拥有一系列创新的组件和概念,其中最重要的组件和概念之一是 Kubernetes存储卷。Volume 组件是实现了数据持久性和共享的功能核心。本篇文章将详细介绍 Kubernetes(K8S)中的存储卷(Volume)机制,包括 Persistent Volume (PV)Persistent Volume Claim (PVC) 和后端存储的使用,为帮助大家更好地理解三者之间的关系。

目次


1 概念

1.1 什么是存储卷?


在容器化环境中,因为容器的生命周期是临时的,所以伴随产生的数据默认也是临时的。当容器重启或瓦解后,其内部数据将丢失。因此,Kubernetes 引入了存储卷(Volume),为应用提实现数据持久化的能力。
1.2 存储卷的类型与使用场景

存储类型描述适用场景emptyDirPod 生命周期内的临时存储适合临时数据存储,如缓存、临时文件等。hostPath将宿主机上的文件体系目次挂载到Pod中。适用于需要访问宿主机文件体系的场景,但大概会带来宿主机与Pod间的紧耦合问题,影响Pod的调度机动性。NFS使用网络文件体系协议进行数据共享,适合多Pod之间共享数据。多个 Pod 共享访问同一文件,适合数据共享、日志网络等。块存储直接使用云服务提供的块存储,支持动态创建和挂载。适合在云上部署的生产环境,支持持久性和主动扩展。分布式文件体系这些分布式文件体系适合在高可用性和大规模集群中使用,提供更好的性能和冗余支持。企业级应用、大数据处理等场景。 1.3 存储类(Storage Class)


在 Kubernetes 中,存储类是一个抽象,让用户无需提前预备 PV,而是根据需要由集群主动分配存储。StorageClass 决定了怎样创建、设置和管理存储卷(如云盘、本地盘、NFS 等),实现按需动态分配
简单来说,StorageClass就是一个存储策略模板,用户通过它告诉 Kubernetes:“我需要的存储资源符合这些规则,请帮我动态生成合适的存储卷。”
1.4 PV(Persistent Volume)


PV 是集群中的一个存储资源,可以由管理员创建或使用存储类动态创建,并定义了存储容量、访问模式、后端存储等规则。
以下是PV的特性:

1.5 PVC (Persistent Volume Claim)


PVC 是用户对持久存储的哀求声明,它规定了存储容量和访问权限等需求,并以一种抽象方法关照Kubernetes 集群,再由Kubernetes 主动匹共同适的 PV,并进行绑定。这样对用户屏蔽了后端存储,实现存储同一管理。
以下是PVC的特性:

1.6 动态卷(Dynamic Volume Provisioning)

PV由管理员提前创建提供给用户使用,称为静态卷
当用户在提交 PVC 时,K8S 根据 StorageClass 主动创建 PV,而不需要管理员提前预备好存储卷,称为动态卷
固然,如果要实现动态卷, 必须设置存储类,否则Kubernetes无法创建PV,如果PVC设置storageClassName字段为“”,也不会主动创建动态卷。
1.7 PV、PVC 与后端存储的关系

以“租房场景”来说明StorageClass、PV、PVC 与后端存储之间的关系。


2 实战:PV 和 PVC 的部署攻略

   ❔ 说明:以NFS作为后端存储,验证PV和PVC相关策略。
  2.1 实验预备


2.2 部署NFS Server

安装nfs-server
  1. apt install -y nfs-kernel-server nfs-common
复制代码
设置nfs, 把数据目次挂着到ssd磁盘
  1. sudo mkdir -p /ssd/data
  2. sudo chown nobody:nogroup /ssd/data
  3. sudo chmod 777 /ssd/data
复制代码
编辑/etc/exports文件
  1. /ssd/data  *(rw,sync)
复制代码
  ❔ 参数说明:
  

重启nfs服务
  1. systemctl enable nfs-server
  2. systemctl restart nfs-server
复制代码
查看nfs设置
  1. exportfs -rv
复制代码
输出如下:
  1. exportfs: /etc/exports [1]: Neither 'subtree_check' or 'no_subtree_check' specified for export "*:/ssd/data".
  2.   Assuming default behaviour ('no_subtree_check').
  3.   NOTE: this default has changed since nfs-utils version 1.0.x
  4. exporting *:/ssd/data
复制代码

2.3 Master节点挂载测试

查看共享目次
  1. showmount -e 192.168.3.20
复制代码
输出如下:
  1. Export list for 192.168.3.20:
  2. /ssd/data *
复制代码
创建共享目次 /mnt/share
  1. mkdir /mnt/share
复制代码
挂载nfs目次
  1. mount -t nfs 192.168.3.200:/ssd/data /mnt/share
复制代码
创建测试文件
  1. echo “test” > /mnt/share/test.txt
复制代码

卸载nfs共享目次
  1. umount /mnt/share
复制代码
2.4 创建 PV和PVC

接下来我们基于 NFS存储创建PV和PVC,验证不同的策略 :
第一个PV和PVC:
  1. apiVersion: v1
  2. kind: PersistentVolume
  3. metadata:
  4.   name: pv-rwo
  5. spec:
  6.   capacity:
  7.     storage: 1Gi
  8.   accessModes:
  9.     - ReadWriteOnce
  10.   persistentVolumeReclaimPolicy: Retain
  11.   nfs:
  12.     path: /ssd/data
  13.     server: 192.168.3.20
  14. ---
  15. apiVersion: v1
  16. kind: PersistentVolumeClaim
  17. metadata:
  18.   name: pvc-rwo
  19. spec:
  20.   accessModes:
  21.     - ReadWriteOnce
  22.   resources:
  23.     requests:
  24.       storage: 1Gi
复制代码

第二个PV和PVC:
  1. apiVersion: v1
  2. kind: PersistentVolume
  3. metadata:
  4.   name: pv-rwx
  5. spec:
  6.   capacity:
  7.     storage: 1Gi
  8.   accessModes:
  9.     - ReadWriteMany
  10.   persistentVolumeReclaimPolicy: Retain
  11.   nfs:
  12.     path: /ssd/data
  13.     server: 192.168.3.20
  14. ---
  15. apiVersion: v1
  16. kind: PersistentVolumeClaim
  17. metadata:
  18.   name: pvc-rwx
  19. spec:
  20.   accessModes:
  21.     - ReadWriteMany
  22.   resources:
  23.     requests:
  24.       storage: 1Gi
复制代码

第三个PV和PVC:
  1. apiVersion: v1
  2. kind: PersistentVolume
  3. metadata:
  4.   name: pv-rox
  5. spec:
  6.   capacity:
  7.     storage: 1Gi
  8.   accessModes:
  9.     - ReadOnlyMany
  10.   persistentVolumeReclaimPolicy: Retain
  11.   nfs:
  12.     path: /ssd/data
  13.     server: 192.168.3.20
  14. ---
  15. apiVersion: v1
  16. kind: PersistentVolumeClaim
  17. metadata:
  18.   name: pvc-rox
  19. spec:
  20.   accessModes:
  21.     - ReadOnlyMany
  22.   resources:
  23.     requests:
  24.       storage: 1Gi
复制代码
  ❔ 参数说明:
  
访问模式缩写描述典范使用场景ReadWriteOnceRWO该存储卷可以被一个节点上的Pod以读写方式挂载。当 Pod 在同一节点上运行时,ReadWriteOnce 访问模式仍旧可以允许多个 Pod 访问该卷。适用于单实例应用,如数据库(MySQL、PostgreSQL)。ReadOnlyManyROX该存储卷可以被多个节点上的多个Pod以只读方式挂载。适用于需要共享静态内容的场景,如设置文件或日志查看。ReadWriteManyRWX该存储卷可以被多个节点上的多个Pod以读写方式挂载。适用于需要多节点并发读写的场景,如共享文件存储。ReadWriteOncePodRWOP该存储卷只能被一个Pod以读写方式挂载,即使在同一节点上也不能被其他Pod挂载。 1.22+ 版本后支持。用于增强Pod间的独占资源访问,防止多个Pod竞争使用。
接纳策略描述适用场景Retain保留数据,即使PVC被删除,PV中的数据仍会被保留,需要手动清理或重新绑定PVC。适用于需要数据持久保留的场景,如数据库或备份存储。Delete删除PV和存储资源。当PVC被删除时,Kubernetes会主动删除PV及其对应的存储资源。适用于临时数据或不需要保留的数据,如测试环境。Recycle (已废弃)清空数据并将PV重置为Available状态,以便被新的PVC绑定。此功能在Kubernetes 1.11之后已废弃。已不推荐使用,Kubernetes 1.11版本之前的旧集群大概仍支持。
创建三个PV和PVC
  1. kubectl apply -f pv-rwo.yaml
  2. kubectl apply -f pv-rwm.yaml
  3. kubectl apply -f pv-rom.yaml
复制代码
2.5 观察PV和PVC的状态

查看PV状态
  1. kubectl get pv
复制代码
输出如下:
  1. NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM                                           STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
  2. pv-rox                                     1Gi        ROX            Retain           Available                                                                  <unset>                          11s
  3. pv-rwo                                     1Gi        RWO            Delete           Available                                                                  <unset>                          106s
  4. pv-rwx                                     1Gi        RWX            Retain           Available                                                                  <unset>                          42s
复制代码
查看PVC状态
  1. kubectl get pv
  2. c
复制代码
输出如下:
  1. NAME               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
  2. pvc-rox            Bound    pvc-e32cf856-b00b-4012-a25a-74c82ba8f092   1Gi        ROX            nfs-class      <unset>                 2m9s
  3. pvc-rwo            Bound    pvc-c864776b-5811-452f-9e98-f50466922be2   1Gi        RWO            nfs-class      <unset>                 3m44s
  4. pvc-rwx            Bound    pvc-7bebb579-5f8a-4994-a7a8-02bde0d651c2   1Gi        RWX            nfs-class      <unset>                 2m41s
复制代码
  ❔ 说明:PV和PVC的生命周期
  
生命周期阶段状态描述AvailablePV已创建且未绑定到任何PVC,表示可供PVC使用。BoundPV已绑定到一个PVC,表示正在被使用。ReleasedPVC被删除后,PV进入Released状态,表示PV已经释放,但资源尚未被重新使用,数据仍大概存在。FailedPV无法绑定到PVC,或接纳过程中出现错误。通常需要管理员手动干预修复。Reclaim根据persistentVolumeReclaimPolicy设置,PV的接纳策略可以是Retain、Recycle或Delete。
生命周期阶段状态描述PendingPVC已创建,但尚未找到合适的PV进行绑定。BoundPVC已成功绑定到一个PV,存储资源可以被Pod使用。LostPV被删除或失效,PVC进入Lost状态,表示无法继续访问存储资源。通常需要管理员处理。 2.6 Pod挂载不同PVC测试

2.6.1 创建Pod并挂载ReadWriteOnce的PV

将PVC pvc-rwo挂载到nginx1:
  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4.   name: nginx1
  5. spec:
  6.   containers:
  7.     - name: nginx1
  8.       image: harbor.zx/hcie/nginx:1.27.1
  9.       imagePullPolicy: IfNotPresent
  10.       volumeMounts:
  11.         - mountPath: "/usr/share/nginx/html"
  12.           name: my-config
  13.   volumes:
  14.     - name: my-configvim
  15.       persistentVolumeClaim:
  16.         claimName: pvc-rwo
复制代码
创建pod
  1. kubectl apply -f nginx1.yaml
复制代码
尝试再创建一个Pod nginx2,使用同一个PVC,观察PVC挂载环境。
  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4.   name: nginx2
  5. spec:
  6.   containers:
  7.     - name: nginx2
  8.       image: harbor.zx/hcie/nginx:1.27.1
  9.       imagePullPolicy: IfNotPresent
  10.       volumeMounts:
  11.         - mountPath: "/usr/share/nginx/html"
  12.           name: my-config
  13.   volumes:
  14.     - name: my-config
  15.       persistentVolumeClaim:
  16.         claimName: pvc-rwo
复制代码
查看pod的状态
  1. kubectl get pod
  2. -owide
复制代码
输出如下:
  1. NAME                                               READY   STATUS    RESTARTS        AGE   IP               NODE          NOMINATED NODE   READINESS GATES
  2. nginx1                                             1/1     Running   0               66m   172.16.135.207   k8s-master3   <none>           <none>
  3. nginx2                                             1/1     Running   0               61m   172.16.126.55    k8s-worker2   <none>           <none>
复制代码
  ❓ 思考: 测试发现,无论nginx1与nginx2运行在同一工作节点或不同工作节点上都能正常读写。
  

2.6.2 创建Pod并挂载ReadWriteOncePod的PV

现在将nginx1、nginx2删除:
  1. kubectl delete -f nginx1.yaml
  2. kubectl delete -f nginx2.yaml
  3. kubectl delete -f pv-rwo.yaml
复制代码
将第一个PV和PVC的策略改为ReadWriteOncePod
  1. apiVersion: v1
  2. kind: PersistentVolume
  3. metadata:
  4.   name: pv-rwo
  5. spec:
  6.   capacity:
  7.     storage: 1Gi
  8.   accessModes:
  9.     - ReadWriteOncePod
  10.   persistentVolumeReclaimPolicy: Retain
  11.   nfs:
  12.     path: /ssd/data
  13.     server: 192.168.3.20
  14. ---
  15. apiVersion: v1
  16. kind: PersistentVolumeClaim
  17. metadata:
  18.   name: pvc-rwo
  19. spec:
  20.   accessModes:
  21.     - ReadWriteOncePod
  22.   resources:
  23.     requests:
  24.       storage: 1Gi
复制代码
创建pv和pvc
  1. kubectl apply -f pv-rwo.yaml
复制代码
创建nginx1和nginx2
  1. kubectl apply -f nginx1.yaml
  2. kubectl apply -f nginx2.yaml
复制代码
查看pod
  1. kubectl get pod
复制代码
输出如下:
  1. NAME     READY   STATUS    RESTARTS   AGE
  2. nginx1   1/1     Running   0          67s
  3. nginx2   0/1     Pending   0          66s
复制代码

nginx2详细信息如下:
  1. Events:
  2.   Type     Reason            Age   From               Message
  3.   ----     ------            ----  ----               -------
  4.   Warning  FailedScheduling  12s   default-scheduler  0/5 nodes are available: 1 node has pod using PersistentVolumeClaim with the same name and ReadWriteOncePod access mode, 4 node(s) didn't match Pod's node affinity/selector. preemption: 0/5 nodes are available: 1 No preemption victims found for incoming pod, 4 Preemption is not helpful for scheduling.
复制代码


2.6.3 创建Pod并挂载ReadWriteMany的PV

现在将nginx1、nginx2删除:
  1. kubectl delete -f nginx1.yaml
  2. kubectl delete -f nginx2.yaml
复制代码
修改成pv-rwx的pvc
  1.   volumes:
  2.     - name: my-config
  3.       persistentVolumeClaim:
  4.         claimName: pvc-rwx
复制代码
重新创建nginx1、nginx2
  1. kubectl apply -f nginx1.yaml
  2. kubectl apply -f nginx2.yaml
复制代码
nginx1测试读写
  1. [root@k8s-master1 ~]#  kubectl exec -it nginx1 -- bash
  2. root@nginx1:/# echo "nginx1" >>/usr/share/nginx/html/nginx1
  3. root@nginx1:/# cat /usr/share/nginx/html/nginx1
  4. nginx1
复制代码
nginx2测试读写
  1. [root@k8s-master1 ~]#  kubectl exec -it nginx2 -- bash
  2. root@nginx2:/# echo "nginx2" >>/usr/share/nginx/html/nginx2
  3. root@nginx2:/# cat /usr/share/nginx/html/nginx2
  4. nginx2
复制代码


2.6.4 创建Pod并挂载ReadOnlyMany的PV

现在将nginx1、nginx2删除:
  1. kubectl delete -f nginx1.yaml
  2. kubectl delete -f nginx2.yaml
复制代码
Pod修改成pv-rox的pvc
  1.   containers:
  2.     - name: nginx1
  3.       image: harbor.zx/hcie/nginx:1.27.1
  4.       imagePullPolicy: IfNotPresent
  5.       volumeMounts:
  6.         - mountPath: "/usr/share/nginx/html"
  7.           name: my-config
  8.           readOnly: true
  9.   volumes:
  10.     - name: my-config
  11.       persistentVolumeClaim:
  12.         claimName: pvc-rox
复制代码
重新创建nginx1、nginx2
  1. kubectl apply -f nginx1.yaml
  2. kubectl apply -f nginx2.yaml
复制代码
查看nginx1详细信息,执行以下下令:
  1. kubectl describe pod nginx1
复制代码
输出如下:
  1. Containers:
  2.   nginx1:
  3.     Container ID:   containerd://28659839ee65b3be6579dd5d519ebcd89fefbf05e9908feb21b504728c19527a
  4.     Image:          harbor.zx/hcie/nginx:1.27.1
  5.     Image ID:       harbor.zx/hcie/nginx@sha256:127262f8c4c716652d0e7863bba3b8c45bc9214a57d13786c854272102f7c945
  6.     Port:           <none>
  7.     Host Port:      <none>
  8.     State:          Running
  9.       Started:      Thu, 17 Oct 2024 10:16:53 +0800
  10.     Ready:          True
  11.     Restart Count:  0
  12.     Environment:    <none>
  13.     Mounts:
  14.        /usr/share/nginx/html from my-config (ro)
  15.       /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-vcmlw (ro)
复制代码


nginx1测试写
  1. [root@k8s-master1 ~]#  kubectl exec -it nginx1 -- bash
  2. root@nginx1:/# echo "nginx1" >>/usr/share/nginx/html/nginx1
复制代码
输出如下:
  1. bash: /usr/share/nginx/html/nginx1: Read-only file system
复制代码


nginx2测试读
  1. [root@k8s-master1 ~]#  kubectl exec -it nginx2 -- bash
  2. root@nginx2:/# cat /usr/share/nginx/html/nginx2
  3. nginx2
  4. root@nginx2:/# cat /usr/share/nginx/html/nginx1
  5. nginx1
复制代码


2.7 PV的接纳策略测试

   ❔ 说明: 因为Recycle策略已经被丢弃了, 所以只验证Retain和Delete两种策略类型。
  登录nfs-server,创建卷目次
  1. mkdir /ssd/data/{retain,delete}
复制代码
编写Retain卷文件
  1. apiVersion: v1
  2. kind: PersistentVolume
  3. metadata:
  4.   name: pv-retain
  5. spec:
  6.   capacity:
  7.     storage: 1Gi
  8.   accessModes:
  9.     - ReadWriteOnce
  10.   persistentVolumeReclaimPolicy: Retain
  11.   nfs:
  12.     path: /ssd/data/retain
  13.     server: 192.168.3.20
  14. ---
  15. apiVersion: v1
  16. kind: PersistentVolumeClaim
  17. metadata:
  18.   name: pvc-retain
  19. spec:
  20.   accessModes:
  21.     - ReadWriteOnce
  22.   resources:
  23.     requests:
  24.       storage: 1Gi
  25.   storageClassName: ""
复制代码
编写Delete卷文件
  1. apiVersion: v1
  2. kind: PersistentVolume
  3. metadata:
  4.   name: pv-delete
  5. spec:
  6.   capacity:
  7.     storage: 1Gi
  8.   accessModes:
  9.     - ReadWriteOnce
  10.   persistentVolumeReclaimPolicy: Delete
  11.   nfs:
  12.     path: /ssd/data/delete
  13.     server: 192.168.3.20
  14. ---
  15. apiVersion: v1
  16. kind: PersistentVolumeClaim
  17. metadata:
  18.   name: pvc-delete
  19. spec:
  20.   accessModes:
  21.     - ReadWriteOnce
  22.   resources:
  23.     requests:
  24.       storage: 1Gi
  25.   storageClassName: ""
复制代码
创建存储卷
  1. kubectl apply -f pv-retain.yaml
  2. kubectl apply -f pv-delete.yaml
复制代码
创建pod测试
  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4.   name: pod-retain
  5. spec:
  6.   containers:
  7.   - name: busybox
  8.     image: harbor.zx/library/busybox:1.29-2
  9.     command: ["sh", "-c", "echo 'Hello from Retain' > /mnt/data/test.txt; sleep 3600"]
  10.     volumeMounts:
  11.     - mountPath: /mnt/data
  12.       name: volume
  13.   volumes:
  14.   - name: volume
  15.     persistentVolumeClaim:
  16.       claimName: pvc-retain
  17. ---
  18. apiVersion: v1
  19. kind: Pod
  20. metadata:
  21.   name: pod-delete
  22. spec:
  23.   containers:
  24.   - name: busybox
  25.     image: harbor.zx/library/busybox:1.29-2
  26.     command: ["sh", "-c", "echo 'Hello from Delete' > /mnt/data/test.txt; sleep 3600"]
  27.     volumeMounts:
  28.     - mountPath: /mnt/data
  29.       name: volume
  30.   volumes:
  31.   - name: volume
  32.     persistentVolumeClaim:
  33.       claimName: pvc-delete
复制代码
执行以下下令创建两个Pod:
  1. kubectl apply -f pv-test.yaml
复制代码
删除pod、pvc-retain和pvc-delete,观察PV的状态变化。
  1. kubectl delete pod pod-retain
  2. kubectl delete pod pod-delete
  3. kubectl delete pvc pvc-retain
  4. kubectl delete pvc pvc-delete
复制代码
观察pv的接纳状态,执行以下下令:
  1. kubectl get pv
  2. -w
复制代码
输出如下:
  1. NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                                             STORAGECLASS   VOLUMEATTRIBUTESCLASS   REASON   AGE
  2.                          3m33s
  3. pv-retain                                  1Gi        RWO            Retain           Bound      default/pvc-reta  in                                             <unset>                          3m34s
  4. ...
  5. pv-retain                                  1Gi        RWO            Retain           Released   default/pvc-reta  in                                             <unset>  
  6. ---
  7. pv-delete                                  1Gi        RWO            Delete           Bound      default/pvc-dele  te                                             <unset>                         4m42s
  8. ...
  9. pv-delete                                  1Gi        RWO            Delete           Released   default/pvc-dele  te                                             <unset>                          4m41s
  10. pv-delete                                  1Gi        RWO            Delete           Failed     default/pvc-dele  te                                             <unset>                          4m41s
复制代码


查看nfs底层数据
  1. root@ub22:/ssd/data# ls -l delete/
  2. total 4
  3. -rw-r--r-- 1 nobody nogroup 18 10月 17 11:27 test.txt
  4. root@ub22:/ssd/data# ls -l retain/
  5. total 4
  6. -rw-r--r-- 1 nobody nogroup 18 10月 17 11:27 test.txt
复制代码


2.7.1 手动删除并接纳卷

查看pv-delete详细信息
  1. Events:
  2.   Type     Reason              Age   From                         Message
  3.   ----     ------              ----  ----                         -------
  4.   Warning  VolumeFailedDelete  6m2s  persistentvolume-controller  error getting deleter volume plugin for volume "pv-delete": no deletable volume plugin matched
复制代码


登录NFS服务器,手动删除PV对应的目次。例如:
  1. # 登录到NFS服务器,手动删除目录
  2. rm -rf /ssd/data/delete
复制代码
然后删除PV资源:
  1. kubectl delete pv pv-delete
复制代码
或者,使用Finalizer强制删除PV

3 总结

Kubernetes 通过PV和PVC的方式提供了后端存储同一管理和机动存储的办理方案,在实际生产环境中,您可以根据业务需求选择合适的存储类型,并订定美满的数据备份方案,是确保体系稳定运行的关键。

4 参考文献



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4