当前位置: 代码网 > 服务器>服务器>云虚拟主机 > 一篇文章读懂K8S的PV和PVC以及实践攻略

一篇文章读懂K8S的PV和PVC以及实践攻略

2024年11月27日 云虚拟主机 我要评论
1 概念1.1 什么是存储卷?在容器化环境中,因为容器的生命周期是临时的,所以伴随产生的数据默认也是临时的。当容器重启或崩溃后,其内部数据将丢失。因此,kubernetes 引入了存储卷(volume

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的特性:

  • 生命周期独立于 pod,不会因为 pod 删除而消失。
  • pv 是集群级的资源,管理员负责创建。
  • 支持多种存储类型,如本地磁盘、nfs、cephfs等。
  • 支持不同的访问模式,如 readwriteonce(一次只能一个 pod 写入)和 readwritemany(多个 pod 可同时写入)。

1.5 pvc (persistent volume claim)

pvc 是用户对持久存储的请求声明,它规定了存储容量和访问权限等需求,并以一种抽象方法通知kubernetes 集群,再由kubernetes 自动匹配合适的 pv,并进行绑定。这样对用户屏蔽了后端存储,实现存储统一管理。

以下是pvc的特性:

  • pvc 解耦了应用与存储资源,使开发人员不需要直接处理具体的存储细节。
  • 如果没有符合条件的 pv,pvc 会处于“待绑定”状态,直到管理员创建满足条件的 pv。
  • pvc类似于 pod, pod 消耗节点资源(cpu和内存),pvc 消耗 pv 资源,通常与pv是1对1的关系。

1.6 动态卷(dynamic volume provisioning)

pv由管理员提前创建提供给用户使用,称为静态卷

当用户在提交 pvc 时,k8s 根据 storageclass 自动创建 pv,而不需要管理员提前准备好存储卷,称为动态卷

当然,如果要实现动态卷, 必须设置存储类,否则kubernetes无法创建pv,如果pvc设置storageclassname字段为“”,也不会自动创建动态卷。

1.7 pv、pvc 与后端存储的关系

以“租房场景”来说明storageclass、pv、pvc 与后端存储之间的关系。

  • 后端存储: 物业公司(如 nfs、aws ebs 等)是所有房源的真正提供者。它们负责实际存储资源的供应,例如:云存储、nfs、本地磁盘。
  • storageclass: 类似租房平台上的房型模板。它定义了不同租房规则,比如:标准单间(普通低成本存储)、豪华公寓(高性能大容量存储)、短租房(临时存储)。用户在提交租房申请(pvc)时,可以指定采用哪种房型模板(storageclass)。根据这个模板,平台会自动生成实际的房源(pv)。
  • pv: 类似已经发布到平台上的“具体房子”。每个 pv 是由后端存储提供的资源,例如:某间20平方的单人房(块存储),或某间共享公寓(文件存储)。
  • pvc :租客提交的租房申请。它描述了租客希望租到的房屋类型和条件,例如:面积(容量)、入住规则(只允许一人入住,或带宠物共住)、房型要求(公寓、小区、城中村)。kubernetes 会根据 pvc 提交的条件,自动匹配合适的 pv。如果没有符合条件的 pv,系统会根据 storageclass 动态生成一个 pv,并绑定给 pvc。

2 实战:pv 和 pvc 的部署攻略

❔ 说明:以nfs作为后端存储,验证pv和pvc相关策略。

2.1 实验准备

  • nfs server系统: ubuntu22.04
  • kubernetes环境: v1.29.7

2.2 部署nfs server

安装nfs-server

apt install -y nfs-kernel-server nfs-common

配置nfs, 把数据目录挂着到ssd磁盘

sudo mkdir -p /ssd/data
sudo chown nobody:nogroup /ssd/data
sudo chmod 777 /ssd/data

编辑/etc/exports文件

/ssd/data  *(rw,sync)

❔ 参数说明:

  • /ssd/data:指定服务器上的共享目录路径。
  • *:表示允许所有的客户端访问该共享。如果是业务环境要求访问限制,可以替换为特定的ip地址或子网,例如 kubernetes集群的网段。
  • rw :read-write,表示客户端具有读写该共享目录的权限。如果设置为 ro(read-only),则客户端只能读取数据,无法写入。
  • sync(同步写入):表示所有对该共享目录的写入操作都将同步地写入到磁盘上。客户端发起的写操作必须等到数据写入磁盘后才会被确认。如果配置为 async,则写入操作会在数据实际写入磁盘之前就返回,这样会提高性能,但在系统崩溃时可能导致数据丢失。

重启nfs服务

systemctl enable nfs-server
systemctl restart nfs-server

查看nfs配置

exportfs -rv

输出如下:

exportfs: /etc/exports [1]: neither 'subtree_check' or 'no_subtree_check' specified for export "*:/ssd/data".
  assuming default behaviour ('no_subtree_check').
  note: this default has changed since nfs-utils version 1.0.x

exporting *:/ssd/data
  • 证明配置成功

2.3 master节点挂载测试

查看共享目录

showmount -e 192.168.3.20

输出如下:

export list for 192.168.3.20:
/ssd/data *

创建共享目录 /mnt/share

mkdir /mnt/share

挂载nfs目录

mount -t nfs 192.168.3.200:/ssd/data /mnt/share

创建测试文件

echo “test” > /mnt/share/test.txt
  • 创建成功,证明写入成功

卸载nfs共享目录

umount /mnt/share

2.4 创建 pv和pvc

接下来我们基于 nfs存储创建pv和pvc,验证不同的策略 :

第一个pv和pvc:

apiversion: v1
kind: persistentvolume
metadata:
  name: pv-rwo
spec:
  capacity:
    storage: 1gi
  accessmodes:
    - readwriteonce
  persistentvolumereclaimpolicy: retain
  nfs:
    path: /ssd/data
    server: 192.168.3.20
---
apiversion: v1
kind: persistentvolumeclaim
metadata:
  name: pvc-rwo
spec:
  accessmodes:
    - readwriteonce
  resources:
    requests:
      storage: 1gi

第二个pv和pvc:

apiversion: v1
kind: persistentvolume
metadata:
  name: pv-rwx
spec:
  capacity:
    storage: 1gi
  accessmodes:
    - readwritemany
  persistentvolumereclaimpolicy: retain
  nfs:
    path: /ssd/data
    server: 192.168.3.20
---
apiversion: v1
kind: persistentvolumeclaim
metadata:
  name: pvc-rwx
spec:
  accessmodes:
    - readwritemany
  resources:
    requests:
      storage: 1gi

第三个pv和pvc:

apiversion: v1
kind: persistentvolume
metadata:
  name: pv-rox
spec:
  capacity:
    storage: 1gi
  accessmodes:
    - readonlymany
  persistentvolumereclaimpolicy: retain
  nfs:
    path: /ssd/data
    server: 192.168.3.20
---
apiversion: v1
kind: persistentvolumeclaim
metadata:
  name: pvc-rox
spec:
  accessmodes:
    - readonlymany
  resources:
    requests:
      storage: 1gi

❔ 参数说明:

  • pv和pvc的volumemode保持一致:filesystem 模式
    • 当 volumemode 设置为 filesystem (默认值)时,存储卷会被挂载到pod的一个目录中。如果存储卷是基于块设备且设备为空,kubernetes 会在第一次挂载前自动在设备上创建一个文件系统。
    • 这种模式适合大多数应用场景,用户可以像使用普通文件系统一样访问存储卷。
    block 模式
    • 当 volumemode 设置为 block 时,存储卷会作为一个原始块设备提供给pod,而不经过文件系统层。这意味着该卷在pod中被暴露为一个块设备。
    • 这种模式适合需要直接访问块存储的应用,如数据库或需要最高i/o性能的应用,但应用程序必须能够处理原始块设备的数据管理。
  • pv和pvc的accessmodes保持一致:
访问模式缩写描述典型使用场景
readwriteoncerwo该存储卷可以被一个节点上的pod以读写方式挂载。当 pod 在同一节点上运行时,readwriteonce 访问模式仍然可以允许多个 pod 访问该卷。适用于单实例应用,如数据库(mysql、postgresql)。
readonlymanyrox该存储卷可以被多个节点上的多个pod以只读方式挂载。适用于需要共享静态内容的场景,如配置文件或日志查看。
readwritemanyrwx该存储卷可以被多个节点上的多个pod以读写方式挂载。适用于需要多节点并发读写的场景,如共享文件存储。
readwriteoncepodrwop该存储卷只能被一个pod以读写方式挂载,即使在同一节点上也不能被其他pod挂载。 1.22+ 版本后支持。用于增强pod间的独占资源访问,防止多个pod竞争使用。
  • pv的reclaimpolicy
回收策略描述适用场景
retain保留数据,即使pvc被删除,pv中的数据仍会被保留,需要手动清理或重新绑定pvc。适用于需要数据持久保留的场景,如数据库或备份存储。
delete删除pv和存储资源。当pvc被删除时,kubernetes会自动删除pv及其对应的存储资源。适用于临时数据或不需要保留的数据,如测试环境。
recycle (已废弃)清空数据并将pv重置为available状态,以便被新的pvc绑定。此功能在kubernetes 1.11之后已废弃。已不推荐使用,kubernetes 1.11版本之前的旧集群可能仍支持。

创建三个pv和pvc

kubectl apply -f pv-rwo.yaml
kubectl apply -f pv-rwm.yaml
kubectl apply -f pv-rom.yaml

2.5 观察pv和pvc的状态

查看pv状态

kubectl get pv

输出如下:

name                                       capacity   access modes   reclaim policy   status      claim                                           storageclass   volumeattributesclass   reason   age
pv-rox                                     1gi        rox            retain           available                                                                  <unset>                          11s
pv-rwo                                     1gi        rwo            delete           available                                                                  <unset>                          106s
pv-rwx                                     1gi        rwx            retain           available                                                                  <unset>                          42s

查看pvc状态

kubectl get pvc

输出如下:

name               status   volume                                     capacity   access modes   storageclass   volumeattributesclass   age
pvc-rox            bound    pvc-e32cf856-b00b-4012-a25a-74c82ba8f092   1gi        rox            nfs-class      <unset>                 2m9s
pvc-rwo            bound    pvc-c864776b-5811-452f-9e98-f50466922be2   1gi        rwo            nfs-class      <unset>                 3m44s
pvc-rwx            bound    pvc-7bebb579-5f8a-4994-a7a8-02bde0d651c2   1gi        rwx            nfs-class      <unset>                 2m41s

❔ 说明:pv和pvc的生命周期

  • pv的 生命周期状态表:
生命周期阶段状态描述
availablepv已创建且未绑定到任何pvc,表示可供pvc使用。
boundpv已绑定到一个pvc,表示正在被使用。
releasedpvc被删除后,pv进入released状态,表示pv已经释放,但资源尚未被重新使用,数据仍可能存在。
failedpv无法绑定到pvc,或回收过程中出现错误。通常需要管理员手动干预修复。
reclaim根据persistentvolumereclaimpolicy配置,pv的回收策略可以是retain、recycle或delete。
  • pvc的生命周期:
生命周期阶段状态描述
pendingpvc已创建,但尚未找到合适的pv进行绑定。
boundpvc已成功绑定到一个pv,存储资源可以被pod使用。
lostpv被删除或失效,pvc进入lost状态,表示无法继续访问存储资源。通常需要管理员处理。

2.6 pod挂载不同pvc测试

2.6.1 创建pod并挂载readwriteonce的pv

将pvc pvc-rwo挂载到nginx1:

apiversion: v1
kind: pod
metadata:
  name: nginx1
spec:
  containers:
    - name: nginx1
      image: harbor.zx/hcie/nginx:1.27.1
      imagepullpolicy: ifnotpresent
      volumemounts:
        - mountpath: "/usr/share/nginx/html"
          name: my-config
  volumes:
    - name: my-configvim 
      persistentvolumeclaim:
        claimname: pvc-rwo

创建pod

kubectl apply -f nginx1.yaml

尝试再创建一个pod nginx2,使用同一个pvc,观察pvc挂载情况。

apiversion: v1
kind: pod
metadata:
  name: nginx2
spec:
  containers:
    - name: nginx2
      image: harbor.zx/hcie/nginx:1.27.1
      imagepullpolicy: ifnotpresent
      volumemounts:
        - mountpath: "/usr/share/nginx/html"
          name: my-config
  volumes:
    - name: my-config
      persistentvolumeclaim:
        claimname: pvc-rwo

查看pod的状态

kubectl get pod -owide

输出如下:

name                                               ready   status    restarts        age   ip               node          nominated node   readiness gates
nginx1                                             1/1     running   0               66m   172.16.135.207   k8s-master3   <none>           <none>
nginx2                                             1/1     running   0               61m   172.16.126.55    k8s-worker2   <none>           <none>

❓ 思考: 测试发现,无论nginx1与nginx2运行在同一工作节点或不同工作节点上都能正常读写。

  • 这里推测,虽然kubernetes的readwriteonce模式限制了跨节点的读写访问,但某些存储系统(如nfs)本身是支持多节点并发访问的。因此即使kubernetes中的访问模式配置为readwriteonce,nfs协议也不强制执行这种访问限制。

2.6.2 创建pod并挂载readwriteoncepod的pv

现在将nginx1、nginx2删除:

kubectl delete -f nginx1.yaml
kubectl delete -f nginx2.yaml
kubectl delete -f pv-rwo.yaml

将第一个pv和pvc的策略改为readwriteoncepod

apiversion: v1
kind: persistentvolume
metadata:
  name: pv-rwo
spec:
  capacity:
    storage: 1gi
  accessmodes:
    - readwriteoncepod
  persistentvolumereclaimpolicy: retain
  nfs:
    path: /ssd/data
    server: 192.168.3.20
---
apiversion: v1
kind: persistentvolumeclaim
metadata:
  name: pvc-rwo
spec:
  accessmodes:
    - readwriteoncepod
  resources:
    requests:
      storage: 1gi

创建pv和pvc

kubectl apply -f pv-rwo.yaml

创建nginx1和nginx2

kubectl apply -f nginx1.yaml
kubectl apply -f nginx2.yaml

查看pod

kubectl get pod

输出如下:

name     ready   status    restarts   age
nginx1   1/1     running   0          67s
nginx2   0/1     pending   0          66s
  • 这时nginx2状态是“pending”出现无法挂载的情况。

nginx2详细信息如下:

events:
  type     reason            age   from               message
  ----     ------            ----  ----               -------
  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.

  • 可以看到nginx2无法挂载是因为 readwriteoncepod 只允许一个pod挂载。
  • 为了做好存储访问控制,可以考虑使用其他存储插件或者存储系统。
  • 另外,可以使用readwriteoncepod(rwop)访问模式来替代readwriteonce,该模式在kubernetes 1.22及更高版本中可用,能够限制pv只能被一个pod挂载,即使在同一节点上也不允许其他pod使用。

2.6.3 创建pod并挂载readwritemany的pv

现在将nginx1、nginx2删除:

kubectl delete -f nginx1.yaml
kubectl delete -f nginx2.yaml

修改成pv-rwx的pvc

  volumes:
    - name: my-config
      persistentvolumeclaim:
        claimname: pvc-rwx

重新创建nginx1、nginx2

kubectl apply -f nginx1.yaml
kubectl apply -f nginx2.yaml

nginx1测试读写

[root@k8s-master1 ~]#  kubectl exec -it nginx1 -- bash
root@nginx1:/# echo "nginx1" >>/usr/share/nginx/html/nginx1
root@nginx1:/# cat /usr/share/nginx/html/nginx1
nginx1

nginx2测试读写

[root@k8s-master1 ~]#  kubectl exec -it nginx2 -- bash
root@nginx2:/# echo "nginx2" >>/usr/share/nginx/html/nginx2
root@nginx2:/# cat /usr/share/nginx/html/nginx2
nginx2
  • 证明一个pv可以共享挂载给多个pod。

2.6.4 创建pod并挂载readonlymany的pv

现在将nginx1、nginx2删除:

kubectl delete -f nginx1.yaml
kubectl delete -f nginx2.yaml

pod修改成pv-rox的pvc

  containers:
    - name: nginx1
      image: harbor.zx/hcie/nginx:1.27.1
      imagepullpolicy: ifnotpresent
      volumemounts:
        - mountpath: "/usr/share/nginx/html"
          name: my-config
          readonly: true
  volumes:
    - name: my-config
      persistentvolumeclaim:
        claimname: pvc-rox

重新创建nginx1、nginx2

kubectl apply -f nginx1.yaml
kubectl apply -f nginx2.yaml

查看nginx1详细信息,执行以下命令:

kubectl describe pod nginx1

输出如下:

containers:
  nginx1:
    container id:   containerd://28659839ee65b3be6579dd5d519ebcd89fefbf05e9908feb21b504728c19527a
    image:          harbor.zx/hcie/nginx:1.27.1
    image id:       harbor.zx/hcie/nginx@sha256:127262f8c4c716652d0e7863bba3b8c45bc9214a57d13786c854272102f7c945
    port:           <none>
    host port:      <none>
    state:          running
      started:      thu, 17 oct 2024 10:16:53 +0800
    ready:          true
    restart count:  0
    environment:    <none>
    mounts:
       /usr/share/nginx/html from my-config (ro) 
      /var/run/secrets/kubernetes.io/serviceaccount from kube-api-access-vcmlw (ro)
  • 存储卷是以只读访问方式挂载。

nginx1测试写

[root@k8s-master1 ~]#  kubectl exec -it nginx1 -- bash
root@nginx1:/# echo "nginx1" >>/usr/share/nginx/html/nginx1

输出如下:

bash: /usr/share/nginx/html/nginx1: read-only file system
  • 证明该存储卷是只读。

nginx2测试读

[root@k8s-master1 ~]#  kubectl exec -it nginx2 -- bash
root@nginx2:/# cat /usr/share/nginx/html/nginx2
nginx2
root@nginx2:/# cat /usr/share/nginx/html/nginx1
nginx1
  • 证明一个pv可以“只读”方式共享给多个pod

2.7 pv的回收策略测试

❔ 说明: 因为recycle策略已经被丢弃了, 所以只验证retaindelete两种策略类型。

登录nfs-server,创建卷目录

mkdir /ssd/data/{retain,delete}

编写retain卷文件

apiversion: v1
kind: persistentvolume
metadata:
  name: pv-retain
spec:
  capacity:
    storage: 1gi
  accessmodes:
    - readwriteonce
  persistentvolumereclaimpolicy: retain
  nfs:
    path: /ssd/data/retain
    server: 192.168.3.20
---
apiversion: v1
kind: persistentvolumeclaim
metadata:
  name: pvc-retain
spec:
  accessmodes:
    - readwriteonce
  resources:
    requests:
      storage: 1gi
  storageclassname: ""

编写delete卷文件

apiversion: v1
kind: persistentvolume
metadata:
  name: pv-delete
spec:
  capacity:
    storage: 1gi
  accessmodes:
    - readwriteonce
  persistentvolumereclaimpolicy: delete
  nfs:
    path: /ssd/data/delete
    server: 192.168.3.20
---
apiversion: v1
kind: persistentvolumeclaim
metadata:
  name: pvc-delete
spec:
  accessmodes:
    - readwriteonce
  resources:
    requests:
      storage: 1gi
  storageclassname: ""

创建存储卷

kubectl apply -f pv-retain.yaml
kubectl apply -f pv-delete.yaml

创建pod测试

apiversion: v1
kind: pod
metadata:
  name: pod-retain
spec:
  containers:
  - name: busybox
    image: harbor.zx/library/busybox:1.29-2
    command: ["sh", "-c", "echo 'hello from retain' > /mnt/data/test.txt; sleep 3600"]
    volumemounts:
    - mountpath: /mnt/data
      name: volume
  volumes:
  - name: volume
    persistentvolumeclaim:
      claimname: pvc-retain
---
apiversion: v1
kind: pod
metadata:
  name: pod-delete
spec:
  containers:
  - name: busybox
    image: harbor.zx/library/busybox:1.29-2
    command: ["sh", "-c", "echo 'hello from delete' > /mnt/data/test.txt; sleep 3600"]
    volumemounts:
    - mountpath: /mnt/data
      name: volume
  volumes:
  - name: volume
    persistentvolumeclaim:
      claimname: pvc-delete

执行以下命令创建两个pod:

kubectl apply -f pv-test.yaml

删除podpvc-retainpvc-delete,观察pv的状态变化。

kubectl delete pod pod-retain
kubectl delete pod pod-delete
kubectl delete pvc pvc-retain
kubectl delete pvc pvc-delete

观察pv的回收状态,执行以下命令:

kubectl get pv -w

输出如下:

name                                       capacity   access modes   reclaim policy   status     claim                                             storageclass   volumeattributesclass   reason   age
                         3m33s
pv-retain                                  1gi        rwo            retain           bound      default/pvc-reta  in                                             <unset>                          3m34s
...
pv-retain                                  1gi        rwo            retain           released   default/pvc-reta  in                                             <unset>  
---
pv-delete                                  1gi        rwo            delete           bound      default/pvc-dele  te                                             <unset>                         4m42s
...
pv-delete                                  1gi        rwo            delete           released   default/pvc-dele  te                                             <unset>                          4m41s
pv-delete                                  1gi        rwo            delete           failed     default/pvc-dele  te                                             <unset>                          4m41s
  • pv状态从bound到released, 但是delete策略回收nfs的存储卷失败。

查看nfs底层数据

root@ub22:/ssd/data# ls -l delete/
total 4
-rw-r--r-- 1 nobody nogroup 18 10月 17 11:27 test.txt
root@ub22:/ssd/data# ls -l retain/
total 4
-rw-r--r-- 1 nobody nogroup 18 10月 17 11:27 test.txt
  • 底层的数据没有被删除。

2.7.1 手动删除并回收卷

查看pv-delete详细信息

events:
  type     reason              age   from                         message
  ----     ------              ----  ----                         -------
  warning  volumefaileddelete  6m2s  persistentvolume-controller  error getting deleter volume plugin for volume "pv-delete": no deletable volume plugin matched
  • 因为我并没有使用nfs的provider进行管理导致,所以不支持nfs存储卷删除操作。

登录nfs服务器,手动删除pv对应的目录。例如:

# 登录到nfs服务器,手动删除目录
rm -rf /ssd/data/delete

然后删除pv资源:

kubectl delete pv pv-delete

或者,使用finalizer强制删除pv

  • 如果pv已经标记为failed,可以尝试移除pv的finalizer以强制删除:
    kubectl patch pv pv-delete -p '{"metadata":{"finalizers":null}}'
    

3 总结

kubernetes 通过pv和pvc的方式提供了后端存储统一管理和灵活存储的解决方案,在实际生产环境中,您可以根据业务需求选择合适的存储类型,并制定完善的数据备份方案,是确保系统稳定运行的关键。

4 参考文献

到此这篇关于k8s的pv和pvc以及实践攻略的文章就介绍到这了,更多相关k8s的pv和pvc详解内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2025  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com