第一篇:

[Kubernetes]PV,PVC,StorageClass实战----|

第二篇:

[Kubernetes]PV,PVC,StorageClass实战----||

官网

Storage Classes | Kubernetes

1 对于PV或者StorageClass只能对应一种后端存储
2 对于手动的情况,一般我们会创建很多的PV,等有PVC需要使用的时候就可以直接使用了
3 对于自动的情况,那么就由StorageClass来自动管理创建
4 如果Pod想要使用共享存储,一般会创建PVC,PVC中描述了想要什么类型的后端存 储、空间等,K8s从而会匹配对应的PV,如果没有匹配成功,Pod就会处于Pending状态。Pod中使用只需要像使用volumes一样,指定名字就可以使用了
5 一个Pod可以使用多个PVC,一个PVC也可以给多个Pod使用
6 一个PVC只能绑定一个PV,一个PV只能对应一种后端存储

一:介绍

什么是StorageClass

Kubernetes提供了一套可以自动创建PV的机制,即:Dynamic Provisioning.而这个机制的核心在于:StorageClass这个API对象. StorageClass对象会定义下面两部分内容:

1),PV的属性.比如,存储类型,Volume的大小等.

2),创建这种PV需要用到的存储插件

有了这两个信息之后,Kubernetes就能够根据用户提交的PVC,找到一个对应的StorageClass,之后Kubernetes就会调用该StorageClass声明的存储插件,进而创建出需要的PV. 但是其实使用起来是一件很简单的事情,你只需要根据自己的需求,编写YAML文件即可,然后使用kubectl create命令执行即可

为什么需要StorageClass

在一个大规模的Kubernetes集群里,可能有成千上万个PVC,这就意味着运维人员必须实现创建出这个多个PV,此外,随着项目的需要,会有新的PVC不断被提交,那么运维人员就需要不断的添加新的,满足要求的PV,否则新的Pod就会因为PVC绑定不到PV而导致创建失败.而且通过 PVC 请求到一定的存储空间也很有可能不足以满足应用对于存储设备的各种需求

而且不同的应用程序对于存储性能的要求可能也不尽相同,比如读写速度、并发性能等,为了解决这一问题,Kubernetes 又为我们引入了一个新的资源对象:StorageClass,通过 StorageClass 的定义,管理员可以将存储资源定义为某种类型的资源,比如快速存储、慢速存储等,用户根据 StorageClass 的描述就可以非常直观的知道各种存储资源的具体特性了,这样就可以根据应用的特性去申请合适的存储资源了。

StorageClass运行原理及部署流程

要使用 StorageClass,我们就得安装对应的自动配置程序,比如我们这里存储后端使用的是 nfs,那么我们就需要使用到一个 nfs-client 的自动配置程序,我们也叫它 Provisioner,这个程序使用我们已经配置好的 nfs 服务器,来自动创建持久卷,也就是自动帮我们创建 PV。

1.自动创建的 PV 以${namespace}-${pvcName}-${pvName}这样的命名格式创建在 NFS 服务器上的共享数据目录中

2.而当这个 PV 被回收后会以archieved-${namespace}-${pvcName}-${pvName}这样的命名格式存在 NFS 服务器上

原理及部署流程说明

详细的运作流程可以参考下图

1. 前面的例子中,我们提前创建了 PV,然后通过 PVC 申请 PV 并在 Pod 中使用,这种方式叫做静态供给(Static Provision)。

与之对应的是动态供给(Dynamical Provision),即如果没有满足 PVC 条件的 PV,会动态创建 PV。相比静态供给,动态供给有明显的优势:不需要提前创建 PV,减少了管理员的工作量,效率高。

动态供给是通过 StorageClass 实现的,StorageClass 定义了如何创建 PV。

StorageClass作为对存储资源的抽象定义,对用户设置的PVC申请屏蔽后端存储的细节,一方面减少了用户对存储资源细节的关注,另一方面减少了管理员手工管理PV的工作,由系统自动完成PV的创建和绑定,实现了动态的资源供应。

StorageClass的定义主要包括名称、后端存储的提供者(privisioner)和后端存储的相关参数配置parameters以及存储卷回收策略reclaimPolicy。StorageClass 对象的名称很重要,它是用户请求特定类的方式。StorageClass一旦被创建,就无法修改,如需修改,只能删除重建。

管理员只能为不请求绑定任何特定类的 PVC 指定默认 StorageClass:有关详细信息,请参阅

PersistentVolumeClaim 部分

2 StorageClass的参考yaml文件 分析:

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: standard
provisioner: kubernetes.io/aws-ebs
parameters:type: gp2
reclaimPolicy: Retain
allowVolumeExpansion: true
mountOptions:- debug
volumeBindingMode: Immediate

2.1 参数详解:

1)provisioner:供应商,每个 StorageClass 都有一个配置器,用于确定用于配置 PV 的卷插件。必须指定此字段。

Volume Plugin[卷插件] Internal Provisioner[内部供应商] Config Example[配置示例]
AWSElasticBlockStore AWS EBS
AzureFile Azure File
AzureDisk Azure Disk
CephFS - -
Cinder OpenStack Cinder
FC - -
FlexVolume - -
Flocker -
GCEPersistentDisk GCE PD
Glusterfs Glusterfs
iSCSI - -
Quobyte Quobyte
NFS - NFS
RBD Ceph RBD
VsphereVolume vSphere
PortworxVolume Portworx Volume
ScaleIO ScaleIO
StorageOS StorageOS
Local - Local

您不限于指定此处列出的“内部”供应商(其名称以“kubernetes.io”为前缀并与 Kubernetes 一起提供)。 您还可以运行和指定外部供应商,它们是遵循Kubernetes 定义的规范的独立程序。外部供应商的作者可以完全决定他们的代码所在的位置、供应商的交付方式、需要如何运行、它使用的卷插件(包括 Flex)等。存储库 kubernetes-sigs/sig-storage-lib- external-provisioner 包含一个库,用于编写实现大部分规范的外部供应商。一些外部供应商列在存储库 kubernetes-sigs/sig-storage-lib-external-provisioner下。

例如,NFS 不提供内部配置器,但可以使用外部配置器。在某些情况下,第 3 方存储供应商会提供自己的外部配置器。

2)reclaimPolicy:回收策略。由 StorageClass 动态创建的 PersistentVolume 将具有在reclaimPolicy类的字段中指定的回收策略,可以是DeleteRetain。如果reclaimPolicy在创建 StorageClass 对象时指定 no,则默认为Delete.

手动创建并通过 StorageClass 管理的 PersistentVolume 将具有在创建时分配的任何回收策略

3)allowVolumeExpansion:允许卷扩展。

特征状态: Kubernetes v1.11 [beta]

PersistentVolume 可以配置为可扩展。此功能设置为 时true,允许用户通过编辑相应的 PVC 对象来调整卷的大小。

当底层 StorageClass 将该字段allowVolumeExpansion设置为 true 时,以下类型的卷支持卷扩展。

Volume type Required Kubernetes version
gcePersistentDisk 1.11
awsElasticBlockStore 1.11
Cinder 1.11
glusterfs 1.11
rbd 1.11
Azure File 1.11
Azure Disk 1.11
Portworx 1.11
FlexVolume 1.13
CSI 1.14 (alpha), 1.16 (beta)

注意:您只能使用卷扩展功能来增加卷,不能缩小它。

4)mountOptions:挂在参数。由 StorageClass 动态创建的 PersistentVolume 将在mountOptions类的字段中指定挂载选项。

如果卷插件不支持挂载选项但指定了挂载选项,则配置将失败。挂载选项未在类或 PV 上验证。如果挂载选项无效,则 PV 挂载失败

5)volumeBindingMode:卷绑定模式。

volumeBindingMode字段控制何时应该发生卷绑定和动态配置。未设置时,默认使用“立即”模式。

Immediate模式指示一旦创建 PersistentVolumeClaim,就会发生卷绑定和动态供应。对于拓扑受限且无法从集群中的所有节点全局访问的存储后端,将在不知道 Pod 调度要求的情况下绑定或配置 PersistentVolume。这可能会导致不可调度的 Pod。

集群管理员可以通过指定模式来解决此问题,该WaitForFirstConsumer模式将延迟 PersistentVolume 的绑定和配置,直到创建使用 PersistentVolumeClaim 的 Pod。PersistentVolume 将根据 Pod 的调度约束指定的拓扑进行选择或配置。这些包括但不限于资源需求、 节点选择器、 pod 亲和性和反亲和性以及污点和容忍度。

以下插件支持WaitForFirstConsumer动态配置:

  • AWSElasticBlockStore
  • GCEPersistentDisk
  • AzureDisk

以下插件支持WaitForFirstConsumer预先创建的 PersistentVolume 绑定:

  • AWSElasticBlockStore
  • GCEPersistentDisk
  • AzureDisk
  • Local
特征状态: Kubernetes v1.17 [stable]

动态配置和预先创建的 PV 也支持CSI 卷,但您需要查看特定 CSI 驱动程序的文档以查看其支持的拓扑键和示例。

笔记:

如果您选择使用WaitForFirstConsumer,请不要nodeName在 Pod 规范中使用来指定节点亲和性。如果nodeName在这种情况下使用,调度程序将被绕过,PVC 将保持在pending状态。

相反,在这种情况下,您可以使用节点选择器[node selector]作为主机名,如下所示。

nodeSelector:
    kubernetes.io/hostname: kube-01

apiVersion: v1
kind: Pod
metadata:name: task-pv-pod
spec:nodeSelector:kubernetes.io/hostname: kube-01volumes:- name: task-pv-storagepersistentVolumeClaim:claimName: task-pv-claimcontainers:- name: task-pv-containerimage: nginxports:- containerPort: 80name: "http-server"volumeMounts:- mountPath: "/usr/share/nginx/html"name: task-pv-storage

allowedTopologies:允许的拓扑

当集群操作员指定WaitForFirstConsumer卷绑定模式时,在大多数情况下不再需要将配置限制为特定拓扑。但是,如果仍然需要,allowedTopologies可以指定。

此示例演示如何将已配置卷的拓扑限制到特定区域,并且应该用作受支持插件的zonezones参数的替代。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: standard
provisioner: kubernetes.io/gce-pd
parameters:type: pd-standard
volumeBindingMode: WaitForFirstConsumer
allowedTopologies:
- matchLabelExpressions:- key: failure-domain.beta.kubernetes.io/zonevalues:- us-central1-a- us-central1-b

6)parameters:参数。

存储类具有描述属于该存储类的卷的参数。可以接受不同的参数,具体取决于provisioner. 例如,io1参数的值type和 参数 iopsPerGB是特定于 EBS 的。当省略参数时,使用一些默认值。

一个 StorageClass 最多可以定义 512 个参数。参数对象的总长度(包括其键和值)不能超过 256 KiB。

二:操作案例相关

1. 案例 此处用NFS做案例。

搭建StorageClass+NFS,大致有以下几个步骤:

1).创建一个可用的NFS Serve
2).创建Service Account.这是用来管控NFS provisioner在k8s集群中运行的权限
3).创建StorageClass.负责建立PVC并调用NFS provisioner进行预定的工作,并让PV与PVC建立管理
4).创建NFS provisioner.有两个功能,一个是在NFS共享目录下创建挂载点(volume),另一个则是建了PV并将PV与NFS的挂载点建立关联

1)nfs服务在[Kubernetes]PV,PVC,StorageClass实战----||中已经操作过

注意:/data/vlumes 172.16.10.5/24(rw,sync,no_subtree_check,no_root_squash)

no_root_squash # 以root身份来执行文件的操作,这样数据目录中的文件就可以被挂载对象使用权限更改的命令,笔者因权限问题,k8s一直报错 already present on machine ;通过pod日志查看才知道在容器中执行chown命令而出错。 # 建议将数据目录的权限调到最大来避免因权限引发对k8s内部的错误。 # 下面的图片中容器通过chown命令更改了属组,而引发了一系列的报错。起先就是在nfs上面没有权限的原因。

文件github

2) rbac.yaml 集群角色,普通角色,sa用户

[root@localhost nfs]# cat rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:name: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default   #根据实际环境设定namespace,下面类同
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: nfs-client-provisioner-runner
rules:- apiGroups: [""]resources: ["nodes"]verbs: ["get", "list", "watch"]- apiGroups: [""]resources: ["persistentvolumes"]verbs: ["get", "list", "watch", "create", "delete"]- apiGroups: [""]resources: ["persistentvolumeclaims"]verbs: ["get", "list", "watch", "update"]- apiGroups: ["storage.k8s.io"]resources: ["storageclasses"]verbs: ["get", "list", "watch"]- apiGroups: [""]resources: ["events"]verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: run-nfs-client-provisioner
subjects:- kind: ServiceAccountname: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default  #根据实际环境设定namespace,下面类同
roleRef:kind: ClusterRolename: nfs-client-provisioner-runnerapiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: leader-locking-nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default  #根据实际环境设定namespace,下面类同
rules:- apiGroups: [""]resources: ["endpoints"]verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: leader-locking-nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default  #根据实际环境设定namespace,下面类同
subjects:- kind: ServiceAccountname: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default  #根据实际环境设定namespace,下面类同
roleRef:kind: Rolename: leader-locking-nfs-client-provisionerapiGroup: rbac.authorization.k8s.io

3) StorageClass

[root@localhost nfs]# cat nfs-StorageClass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: managed-nfs-storage
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'  #这里的名称要和provisioner配置文件中的环境变量PROVISIONER_NAME保持一致
parameters:archiveOnDelete: "false"

4)  provisioner 提供者,外部提供者

[root@localhost nfs]# cat nfs-provisioner-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: nfs-client-provisionerlabels:app: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default #与RBAC文件中的namespace保持一致
spec:replicas: 1strategy:type: Recreateselector:matchLabels:app: nfs-client-provisionertemplate:metadata:labels:app: nfs-client-provisionerspec:serviceAccountName: nfs-client-provisionercontainers:- name: nfs-client-provisioner#image: k8s.gcr.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2image: registry.cn-beijing.aliyuncs.com/mydlq/nfs-subdir-external-provisioner:v4.0.0volumeMounts:- name: nfs-client-rootmountPath: /persistentvolumesenv:- name: PROVISIONER_NAME# 与nfs-StorageClass.yaml中的provisioner要一致value: k8s-sigs.io/nfs-subdir-external-provisioner- name: NFS_SERVERvalue: 172.16.10.5   #<=====NFS服务器的地址- name: NFS_PATHvalue: /opt/vlumes  # <=====NFS服务器的路径volumes:- name: nfs-client-rootnfs:server: 172.16.10.5  # <=====NFS服务器的地址path: /opt/vlumes     # <=====NFS服务器的路径

查看:

[root@localhost ~]# kubectl get pv,pvc,pods,sa,sc -o wide
NAME                                          READY   STATUS    RESTARTS   AGE   IP            NODE           NOMINATED NODE   READINESS GATES
pod/nfs-client-provisioner-57bd9598b5-dmb26   1/1     Running   0          10m   10.42.1.107   172.16.10.21   <none>           <none>NAME                                    SECRETS   AGE
serviceaccount/default                  1         5d23h
serviceaccount/nfs-client-provisioner   1         10mNAME                                              PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
storageclass.storage.k8s.io/managed-nfs-storage   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           false                  8m46s
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: managed-nfs-storage
provisioner k8s-sigs.io/nfs-subdir-external-provisioner
parameters:server:172.16.10.5path: /opt/vlumesreadOnly: "false"
  • server: Server 是 NFS 服务器的主机名或 IP 地址。
  • path: NFS 服务器导出的路径。
  • readOnly: 一个标志,指示存储是否将被安装为只读(默认为 false)。
  • provisioner: 提供者,供应商.由于NFS没有内部供应商,所以使用外部供应商.

Kubernetes 不包含内部 NFS 供应商。您需要使用外部配置器为 NFS 创建 StorageClass。这里有些例子:

  • NFS Ganesha server and external provisioner
  • NFS subdir external provisioner

2. 启动pod,pvc等

创建之前,是空的

[root@localhost pv]# ls /opt/vlumes/

pvc:

[root@localhost nfs]# cat test-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:name: test-claim
spec:storageClassName: managed-nfs-storage  #与nfs-StorageClass.yaml metadata.name保持一致accessModes:- ReadWriteManyresources:requests:storage: 1Mi

查看已经有pvc且pv也自动创建了也绑定【Bound】了

[root@localhost pv]# kubectl get pv,pvc -o wide
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS          REASON   AGE   VOLUMEMODE
persistentvolume/pvc-b88214f7-f646-41e4-bfbb-7805edf2436c   1Mi        RWX            Delete           Bound    default/test-claim   managed-nfs-storage            79s   FilesystemNAME                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE   VOLUMEMODE
persistentvolumeclaim/test-claim   Bound    pvc-b88214f7-f646-41e4-bfbb-7805edf2436c   1Mi        RWX            managed-nfs-storage   79s   Filesystem

在查看格式

自动创建的 PV 以${namespace}-${pvcName}-${pvName}这样的命名格式创建在 NFS 服务器上的共享数据目录中

namespace:default

pvcName:test-claim-pvc

pvName:36a91a70-22c2-448d-b73b-c233a0baa51a

[root@localhost nfs]# ls /opt/vlumes/default-test-claim-pvc-36a91a70-22c2-448d-b73b-c233a0baa51a/

PV 名称是随机字符串,所以每次只要不删除 PVC,那么 Kubernetes 中的与存储绑定将不会丢失,要是删除 PVC 也就意味着删除了绑定的文件夹,下次就算重新创建相同名称的 PVC,生成的文件夹名称也不会一致,因为 PV 名是随机生成的字符串,而文件夹命名又跟 PV 有关, 所以删除 PVC 需谨慎。

pod:

[root@localhost nfs]# cat test-pod.yaml
kind: Pod
apiVersion: v1
metadata:name: test-pod
spec:containers:- name: test-podimage: docker.io/library/busybox:1.28.4command:- "/bin/sh"args:- "-c"- "touch /mnt/SUCCESS && exit 0 || exit 1"volumeMounts:- name: nfs-pvcmountPath: "/mnt"restartPolicy: "Never"volumes:- name: nfs-pvcpersistentVolumeClaim:claimName: test-claim

查看:

[root@localhost pv]# k3s  kubectl describe  pod/test-podWarning  FailedMount  27s (x3 over 29s)  kubelet            MountVolume.SetUp failed for volume "kube-api-access-bkpvs" : object "default"/"kube-root-ca.crt" not registered

获取sa,通过sa获取secrets

[root@localhost roles]# kubectl get sa
NAME                     SECRETS   AGE
default                  1         6d3h
nfs-client-provisioner   1         5m8s
[root@localhost roles]# kubectl get sa nfs-client-provisioner -o json
{"apiVersion": "v1","kind": "ServiceAccount","metadata": {"creationTimestamp": "2022-01-25T06:58:02Z","name": "nfs-client-provisioner","namespace": "default","resourceVersion": "505427","uid": "20d00b93-772f-49d3-a9b4-f3656f76f535"},"secrets": [{"name": "nfs-client-provisioner-token-d2lq7"}]
}

获取secrets的ca证书

[root@localhost roles]# kubectl get secrets nfs-client-provisioner-token-d2lq7
NAME                                 TYPE                                  DATA   AGE
nfs-client-provisioner-token-d2lq7   kubernetes.io/service-account-token   3      5m31s
[root@localhost roles]# kubectl get secrets nfs-client-provisioner-token-d2lq7 -o json
{"apiVersion": "v1","data": {"ca.crt": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkekNDQVIyZ0F3SUJBZ0lCQURBS0JnZ3Foa2pPUFFRREFqQWpNU0V3SHdZRFZRUUREQmhyTTNNdGMyVnkKZG1WeUxXTmhRREUyTkRJMU5qTXpOemt3SGhjTk1qSXdNVEU1TURNek5qRTVXaGNOTXpJd01URTNNRE16TmpFNQpXakFqTVNFd0h3WURWUVFEREJock0zTXRjMlZ5ZG1WeUxXTmhRREUyTkRJMU5qTXpOemt3V1RBVEJnY3Foa2pPClBRSUJCZ2dxaGtqT1BRTUJCd05DQUFSVnVXc3ZmY0Rxckh2RFV2U0ZoMEdocUFrMkFWaGJLaTRBdkoyVTRhZXQKNWsxeG5zQTBSbHN3Z3JPcTl3Q0podW0wRnVrQ2l5RUVNQVhsaU5ROVJkUGhvMEl3UURBT0JnTlZIUThCQWY4RQpCQU1DQXFRd0R3WURWUjBUQVFIL0JBVXdBd0VCL3pBZEJnTlZIUTRFRmdRVXFkcEM4TXlkK0l4MDlueGRIODZJCldTT3FkN013Q2dZSUtvWkl6ajBFQXdJRFNBQXdSUUlnR29pdWxCT1FwWXpRQnkxdUtxRmFFdXgrckk3MEltQncKVW92bWc4cnBJZ0FDSVFEMHBuMCsvV01NUnc5M1NGMDh6QVZUL3NUcC9vOVBxOWNWZDlhSlA2aXoyQT09Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K","namespace": "ZGVmYXVsdA==","token": "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWkNJNkltSmZablpSVUU1MVVsQnBWWGhVTnpCUVFURkRhMjVxWlhWUWRVTnNaa05GYlMxU09GWmxNRkl4UmtFaWZRLmV5SnBjM01pT2lKcmRXSmxjbTVsZEdWekwzTmxjblpwWTJWaFkyTnZkVzUwSWl3aWEzVmlaWEp1WlhSbGN5NXBieTl6WlhKMmFXTmxZV05qYjNWdWRDOXVZVzFsYzNCaFkyVWlPaUprWldaaGRXeDBJaXdpYTNWaVpYSnVaWFJsY3k1cGJ5OXpaWEoyYVdObFlXTmpiM1Z1ZEM5elpXTnlaWFF1Ym1GdFpTSTZJbTVtY3kxamJHbGxiblF0Y0hKdmRtbHphVzl1WlhJdGRHOXJaVzR0WkRKc2NUY2lMQ0pyZFdKbGNtNWxkR1Z6TG1sdkwzTmxjblpwWTJWaFkyTnZkVzUwTDNObGNuWnBZMlV0WVdOamIzVnVkQzV1WVcxbElqb2libVp6TFdOc2FXVnVkQzF3Y205MmFYTnBiMjVsY2lJc0ltdDFZbVZ5Ym1WMFpYTXVhVzh2YzJWeWRtbGpaV0ZqWTI5MWJuUXZjMlZ5ZG1salpTMWhZMk52ZFc1MExuVnBaQ0k2SWpJd1pEQXdZamt6TFRjM01tWXRORGxrTXkxaE9XSTBMV1l6TmpVMlpqYzJaalV6TlNJc0luTjFZaUk2SW5ONWMzUmxiVHB6WlhKMmFXTmxZV05qYjNWdWREcGtaV1poZFd4ME9tNW1jeTFqYkdsbGJuUXRjSEp2ZG1semFXOXVaWElpZlEubmFjR1ZESW1OMFpWcmM1OTgtdjVBUFlYNUVqM2JfMXNGd0VzYjRidEQ0MTlpWi1mcEhLZ3hRNE9pcE5yTXptQVVVRHZJUEtSbnc4YmJEUWdvQWNWU1AyakxEc0RLUl9BX2NibTM5V292RC1DYnZfU2VlbFBPNUs1SnRSbm5WaXN1UWlwTDFZTENxSEZnb3VQRHdMb0t6OEVPYU5DMEpiTG5vOGwwYlJvUWpoazUtOUpfYWxnR1gxeEhOcFlJbFc2SUh5aUttNWU0c1dJNnVnTUo5N0tNSnJlMEVhX0VjVEIzV0NoYl9LdlFoeG5ocGdHeGRSa2ExTU1iRlpqVS01ekxES3B3MjFMWUFaeDhJdFJHNEtsekNNNHdHbGVWU3dHUERrMU9OS0VyZUJRUHVMY3U3SXpNbnhyczdQZmFwWmZvUkhUazJPYTlqcjc4THlXdlJ3QzlB"},"kind": "Secret","metadata": {"annotations": {"kubernetes.io/service-account.name": "nfs-client-provisioner","kubernetes.io/service-account.uid": "20d00b93-772f-49d3-a9b4-f3656f76f535"},"creationTimestamp": "2022-01-25T06:58:02Z","name": "nfs-client-provisioner-token-d2lq7","namespace": "default","resourceVersion": "505425","uid": "667fe7ea-6025-407a-934f-4c8983c5ca00"},"type": "kubernetes.io/service-account-token"
}

查看

[root@localhost nfs]# kubectl get pv,pvc,pods,sc -o wide
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS          REASON   AGE   VOLUMEMODE
persistentvolume/pvc-5b33f6b3-a52b-450e-ba7f-422a24a93922   1Mi        RWX            Delete           Bound    default/test-claim   managed-nfs-storage            28m   FilesystemNAME                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE   VOLUMEMODE
persistentvolumeclaim/test-claim   Bound    pvc-5b33f6b3-a52b-450e-ba7f-422a24a93922   1Mi        RWX            managed-nfs-storage   28m   FilesystemNAME                                          READY   STATUS      RESTARTS   AGE   IP            NODE           NOMINATED NODE   READINESS GATES
pod/nfs-client-provisioner-848b88ddd4-w9rv9   1/1     Running     0          28m   10.42.1.114   172.16.10.21   <none>           <none>
pod/test-pod                                  0/1     Completed   0          28m   10.42.1.115   172.16.10.21   <none>           <none>NAME                                              PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
storageclass.storage.k8s.io/longhorn              driver.longhorn.io                            Delete          Immediate           true                   5d21h
storageclass.storage.k8s.io/managed-nfs-storage   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           true                   28m
[root@localhost nfs]# cat /opt/vlumes/default-test-claim-pvc-5b33f6b3-a52b-450e-ba7f-422a24a93922/SUCCESS

已经在ls /opt/vlumes/default-test-claim-pvc-5b33f6b3-a52b-450e-ba7f-422a24a93922/SUCCESS 存在了

自己的pvc和pod

pvc: 此时就不用先创建pv了,也不用家标签选择pv了

[root@localhost nfs]# cat pvc1.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: pvc1#namespace: wubo
spec:accessModes:  #访问模式#- ReadWriteOnce#- ReadWriteOncePod- ReadWriteMany#- ReadOnlyManyresources: #申请资源,8Gi存储空间requests:storage: 1GistorageClassName: managed-nfs-storage#selector:#  matchLabels:#    name: "wubo-pv1"#matchExpressions:#  - {key: environment, operator: In, values: [dev]}

pod:沿用上一篇文章的

[root@localhost nfs]# cat nginx.yaml
apiVersion: v1
kind: Service
metadata:labels: {name: nginx}name: nginx#namespace: wubo
spec:ports:- {name: t9080, nodePort: 30002, port: 80, protocol: TCP, targetPort: 80}selector: {name: nginx}type: NodePort
---
apiVersion: apps/v1
kind: Deployment
metadata:name: nginx#namespace: wubolabels: {name: nginx}
spec:replicas: 1selector:matchLabels: {name: nginx}template:metadata:name: nginxlabels: {name: nginx}spec:containers:- name: nginx#image: harbor.jettech.com/jettechtools/nginx:1.21.4#image: 172.16.10.5:5000/library/nginx:1.21.4image: docker.io/library/nginx:1.21.4volumeMounts:- name: volvmountPath: /datavolumes:- name: volvpersistentVolumeClaim:claimName: pvc1

说明应用层pod和pv之间解耦了,不需要关心pv,只需要找pvc就可以了

查看:

[root@localhost nfs]# kubectl get pv,pvc,pods,sc -o wide
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS          REASON   AGE     VOLUMEMODE
persistentvolume/pvc-5b33f6b3-a52b-450e-ba7f-422a24a93922   1Mi        RWX            Delete           Bound    default/test-claim   managed-nfs-storage            34m     Filesystem
persistentvolume/pvc-ae7207c9-034f-4af2-8d03-568540bdadda   1Gi        RWX            Delete           Bound    default/pvc1         managed-nfs-storage            3m44s   FilesystemNAME                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE     VOLUMEMODE
persistentvolumeclaim/test-claim   Bound    pvc-5b33f6b3-a52b-450e-ba7f-422a24a93922   1Mi        RWX            managed-nfs-storage   34m     Filesystem
persistentvolumeclaim/pvc1         Bound    pvc-ae7207c9-034f-4af2-8d03-568540bdadda   1Gi        RWX            managed-nfs-storage   3m44s   FilesystemNAME                                          READY   STATUS    RESTARTS   AGE     IP            NODE           NOMINATED NODE   READINESS GATES
pod/nfs-client-provisioner-848b88ddd4-w9rv9   1/1     Running   0          35m     10.42.1.114   172.16.10.21   <none>           <none>
pod/nginx-5cc4bd9557-zkszk                    1/1     Running   0          2m22s   10.42.2.97    172.16.10.15   <none>           <none>NAME                                              PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
storageclass.storage.k8s.io/longhorn              driver.longhorn.io                            Delete          Immediate           true                   5d21h
storageclass.storage.k8s.io/managed-nfs-storage   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           true                   35m

测试文件:

进入容器
[root@localhost nfs]# kubectl exec -it   pod/nginx-5cc4bd9557-zkszk sh输入数据
# cd /data
# ls
# mkdir wubo
# cd wubo
# echo aaa > a.txt
# exit查看一下挂载情况,其实和之前的静态类似,只不过这里是自动生成被挂载目录
# mount
172.16.10.5:/opt/vlumes/default-pvc1-pvc-ae7207c9-034f-4af2-8d03-568540bdadda on /data type nfs4 (rw,relatime,vers=4.1,rsize=1048576,wsize=1048576,namlen=255,hard,proto=tcp,port=0,timeo=600,retrans=2,sec=sys,clientaddr=172.16.10.15,local_lock=none,addr=172.16.10.5)

物理机查看信息:发现数据存在

[root@localhost nfs]# cat /opt/vlumes/default-pvc1-pvc-ae7207c9-034f-4af2-8d03-568540bdadda/wubo/a.txt
aaa

删除pod和pvc在此看:

[root@localhost nfs]# kubectl delete -f nginx.yaml
[root@localhost nfs]# kubectl delete -f pvc1.yaml 

在此查看:发现pvc删除后pv也跟着删除了,那么物理机上面的信息也会删除

[root@localhost nfs]# kubectl get pv,pvc,pods,sc -o wide
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                STORAGECLASS          REASON   AGE   VOLUMEMODE
persistentvolume/pvc-5b33f6b3-a52b-450e-ba7f-422a24a93922   1Mi        RWX            Delete           Bound    default/test-claim   managed-nfs-storage            39m   FilesystemNAME                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE   VOLUMEMODE
persistentvolumeclaim/test-claim   Bound    pvc-5b33f6b3-a52b-450e-ba7f-422a24a93922   1Mi        RWX            managed-nfs-storage   39m   FilesystemNAME                                          READY   STATUS    RESTARTS   AGE   IP            NODE           NOMINATED NODE   READINESS GATES
pod/nfs-client-provisioner-848b88ddd4-w9rv9   1/1     Running   0          40m   10.42.1.114   172.16.10.21   <none>           <none>NAME                                              PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
storageclass.storage.k8s.io/longhorn              driver.longhorn.io                            Delete          Immediate           true                   5d21h
storageclass.storage.k8s.io/managed-nfs-storage   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           true                   39m

物理机信息: 删除了

[root@localhost nfs]# cat /opt/vlumes/default-pvc1-pvc-ae7207c9-034f-4af2-8d03-568540bdadda/wubo/a.txt
cat: /opt/vlumes/default-pvc1-pvc-ae7207c9-034f-4af2-8d03-568540bdadda/wubo/a.txt: No such file or directory

其实没有删除换了个目录而已

[root@localhost nfs]# cat /opt/vlumes/archived-pvc-ae7207c9-034f-4af2-8d03-568540bdadda/wubo/a.txt
aaa

而当这个 PV 被回收后会以archieved-${namespace}-${pvcName}-${pvName}这样的命名格式存在 NFS 服务器上

因为reclaimPolicy: Delete选择的这个,可以改成Retain 自行测试。

[root@localhost nfs]# cat nfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: managed-nfs-storageannotations:storageclass.kubernetes.io/is-default-class: "false"  ## 是否设置为默认的storageclass
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:archiveOnDelete: "true"                                 ## 设置为"false"时删除PVC不会保留数据,"true"则保留数据
mountOptions:- hard                                                  ## 指定为硬挂载方式- nfsvers=4                                             ## 指定NFS版本,这个需要根据NFS Server版本号设置
reclaimPolicy: Delete                                    #Delete Retain
volumeBindingMode: Immediate                            #Immediate 立即模式 WaitForFirstConsumer 延迟模式
allowVolumeExpansion: true   #增加该字段表示允许动态扩

如果报错:

报错:

"MountVolume.SetUp failed for volume "kube-api-access-fcz9j" : object "default"/"kube-root-ca.crt" not registered"

[root@localhost tls]# pwd
/var/lib/rancher/k3s/server/tls
[root@localhost tls]# kubectl get configmap kube-root-ca.crt -o json
{"apiVersion": "v1","data": {"ca.crt": "-----BEGIN CERTIFICATE-----\nMIIBdzCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy\ndmVyLWNhQDE2NDI1NjMzNzkwHhcNMjIwMTE5MDMzNjE5WhcNMzIwMTE3MDMzNjE5\nWjAjMSEwHwYDVQQDDBhrM3Mtc2VydmVyLWNhQDE2NDI1NjMzNzkwWTATBgcqhkjO\nPQIBBggqhkjOPQMBBwNCAARVuWsvfcDqrHvDUvSFh0GhqAk2AVhbKi4AvJ2U4aet\n5k1xnsA0RlswgrOq9wCJhum0FukCiyEEMAXliNQ9RdPho0IwQDAOBgNVHQ8BAf8E\nBAMCAqQwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUqdpC8Myd+Ix09nxdH86I\nWSOqd7MwCgYIKoZIzj0EAwIDSAAwRQIgGoiulBOQpYzQBy1uKqFaEux+rI70ImBw\nUovmg8rpIgACIQD0pn0+/WMMRw93SF08zAVT/sTp/o9Pq9cVd9aJP6iz2A==\n-----END CERTIFICATE-----\n"},"kind": "ConfigMap","metadata": {"annotations": {"kubernetes.io/description": "Contains a CA bundle that can be used to verify the kube-apiserver when using internal endpoints such as the internal service IP or kubernetes.default.svc. No other usage is guaranteed across distributions of Kubernetes clusters."},"creationTimestamp": "2022-01-19T03:36:37Z","name": "kube-root-ca.crt","namespace": "default","resourceVersion": "425","uid": "8f0f88c9-d23a-482e-8c50-115ad7c892ca"}
}
[root@localhost tls]# grep -rin MIIBdzCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy
server-ca.crt:2:MIIBdzCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy
serving-kube-apiserver.crt:15:MIIBdzCCAR2gAwIBAgIBADAKBggqhkjOPQQDAjAjMSEwHwYDVQQDDBhrM3Mtc2Vy

我用的是k3s 证书路径/var/lib/rancher/k3s/server/tls

kube-root-ca.crt 与 server-ca.crt相同,因此当它说 kube-root-ca.crt 未注册时,我不确定证书链中可能还缺少什么

解决:

我了解 rootCAConfigMap 在默认服务帐户的每个命名空间中发布 kube-root-ca.crt。从 kubernetes 版本 1.22 开始,RootCAConfigMap 默认设置为 true,因此在创建 pod 时,将使用默认帐户的此证书。请查找有关基于绑定服务帐户令牌的预计数量的更多信息 - Managing Service Accounts | Kubernetes

运行k3s version v1.22.5+k3s,证书存在于 configmap 中

[root@localhost roles]# kubectl get configmap --all-namespaces | grep kube-root-ca.crt
default           kube-root-ca.crt                     1      6d3h
kube-system       kube-root-ca.crt                     1      6d3h
kube-public       kube-root-ca.crt                     1      6d3h
kube-node-lease   kube-root-ca.crt                     1      6d3h
longhorn-system   kube-root-ca.crt                     1      3d21h
wubo              kube-root-ca.crt                     1      24h
wuqi              kube-root-ca.crt                     1      24h

要停止使用默认服务帐户或已用于创建 pod 的特定服务帐户创建自动卷,只需在 serviceaccount 配置下将“automountServiceAccountToken”设置为“false”

1)在 1.6+ 版本中,您可以通过设置服务帐户来选择不为服务帐户自动挂载 API 凭据。在sa中停止使用:automountServiceAccountToken

apiVersion: v1
kind: ServiceAccount
metadata:name: build-robot
automountServiceAccountToken: false

2)在 1.6+ 版本中,您还可以选择不为特定 pod 自动挂载 API 凭证。pod中禁止使用:

apiVersion: v1
kind: Pod
metadata:name: my-pod
spec:serviceAccountName: build-robotautomountServiceAccountToken: false...

如果两者都指定了automountServiceAccountToken值,则 pod 规范优先于服务帐户

3.pvc创建后的扩容

kubernetes 1.11版本中开始支持pvc创建后的扩容

3.1 先查看storageclass是否配置了动态扩容,主要看storageclass是否存在allowVolumeExpansion字段

[root@localhost nfs]# kubectl get sc managed-nfs-storage -o yaml
allowVolumeExpansion: true
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:annotations:storageclass.kubernetes.io/is-default-class: "false"creationTimestamp: "2022-01-25T07:44:39Z"name: managed-nfs-storageresourceVersion: "510351"uid: c8b55920-610e-4a0c-a09a-4152c1611373
mountOptions:
- hard
- nfsvers=4
parameters:archiveOnDelete: "true"
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
reclaimPolicy: Delete
volumeBindingMode: Immediate

3.2  allowVolumeExpansion: false 情况:

[root@localhost nfs]# cat nfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: managed-nfs-storageannotations:storageclass.kubernetes.io/is-default-class: "false"  ## 是否设置为默认的storageclass
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:archiveOnDelete: "true"                                 ## 设置为"false"时删除PVC不会保留数据,"true"则保留数据
mountOptions:- hard                                                  ## 指定为硬挂载方式- nfsvers=4                                             ## 指定NFS版本,这个需要根据NFS Server版本号设置
reclaimPolicy: Delete                                    #Delete Retain
volumeBindingMode: Immediate                            #Immediate 立即模式 WaitForFirstConsumer 延迟模式
allowVolumeExpansion: false   #增加该字段表示允许动态扩容 #true false

可以看到有allowVolumeExpansion。如果没有,说明是不支持动态扩容的,可以扩一下测试看看

[root@localhost nfs]# kubectl edit pvc pvc1# Please edit the object below. Lines beginning with a '#' will be ignored,
# and an empty file will abort the edit. If an error occurs while saving this file will be
# reopened with the relevant failures.
#
apiVersion: v1
kind: PersistentVolumeClaim
metadata:annotations:pv.kubernetes.io/bind-completed: "yes"pv.kubernetes.io/bound-by-controller: "yes"volume.beta.kubernetes.io/storage-provisioner: k8s-sigs.io/nfs-subdir-external-provisionercreationTimestamp: "2022-01-25T07:57:14Z"finalizers:- kubernetes.io/pvc-protectionname: pvc1namespace: defaultresourceVersion: "511734"uid: 8d58b163-4669-40d9-8661-152c1d4045d2
spec:accessModes:- ReadWriteManyresources:requests:storage: 1GistorageClassName: managed-nfs-storagevolumeMode: FilesystemvolumeName: pvc-8d58b163-4669-40d9-8661-152c1d4045d2
status:accessModes:- ReadWriteManycapacity:storage: 1Giphase: Bound
~

修改1Gi为2Gi,保存发现报错:error: persistentvolumeclaims "pvc1" could not be patched: persistentvolumeclaims "pvc1" is forbidden: only dynamically provisioned pvc can be resized and the storageclass that provisions the pvc must support resize
You can run `kubectl replace -f /tmp/kubectl-edit-3666233252.yaml` to try this update again.

说明不支持动态扩容

3.3 给storageclass添加allowVolumeExpansion字段

[root@localhost nfs]#
[root@localhost nfs]# cat nfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: managed-nfs-storageannotations:storageclass.kubernetes.io/is-default-class: "false"  ## 是否设置为默认的storageclass
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:archiveOnDelete: "true"                                 ## 设置为"false"时删除PVC不会保留数据,"true"则保留数据
mountOptions:- hard                                                  ## 指定为硬挂载方式- nfsvers=4                                             ## 指定NFS版本,这个需要根据NFS Server版本号设置
reclaimPolicy: Delete                                    #Delete Retain
volumeBindingMode: Immediate                            #Immediate 立即模式 WaitForFirstConsumer 延迟模式
allowVolumeExpansion: true   #增加该字段表示允许动态扩容 #true false

此时就可以,不需要重新部署pvc和pod应用,只修要修改storageclass即可

[root@localhost nfs]# kubectl edit pvc pvc1
persistentvolumeclaim/pvc1 edited

4.StateFulDet+volumeClaimTemplates自动创建PV

statefulset:

特性:

(1)稳定而且唯一的网络标识:pod的hostname模式为(statefulset名称)-(序号)

(2)稳定而且持久的存储:通过volumeclaimtemplate为每个pod创建一个PV。删除、减少副本,不会删除相关的卷

(3)有序、平滑地部署和扩展:比如redis主从集群应该先启动主节点,在启动从节点

(4)有序、平滑地删除和终止:比如我们要缩减redis规模,我们要先关闭从节点

(5)有序地滚动更新。一般要先更新从节点。

组成:

三个组件:headless service、volumeclaimtemplate、statefulset

(1)headless service:

1)在deployment中,与之对应的服务是service,而在statefulset中与之对应的headless service。

headless service,即无头服务,与service的不同就是它没有cluster ip【不是pod的ip是cluster ip没有实体支撑】,解析他的名称时将返回改headless service对应的全部pod的endpoint列表【就是pod的ip 有实体支撑】。

2)statefulset在headless service的基础上又为statefuleset控制的每个pod副本创建了一个DNS域名,这个域名的格式为:

$(podname).(headless server name)

FQDN(命令:hostname -f):$(podname).(headless server name).namespace.svc.cluster.local

3)为什么需要headless service无头服务?

在用deployment时,每一个pod名称是没有顺序地,是随机字符串,因此pod名称是无序的,但是在statefulset中要求必须是有序,没一个pod不能被随意取代,pod重建后pod名称还是一样的。而pod ip是变化的,所以是以pod名称来识别。pod名称是pod唯一性的标识符,必须持久稳定有效。这时候要用到无头服务,它可以给每个Pod一个唯一的名称。

(2)volumeclaimtemplates:存储卷申请模板,创建PVC,指定pvc名称大小,将自动创建pvc,且pvc必须由存储类供应。

1)为什么需要volumeclaimtemplate?

对于有状态的副本集都会用到持久卷,对于分布式系统来说,他的最大特点是数据是不一样的,所以各个节点不能使用同一存储卷,每个节点有自己的专用存储,但是如果在deployment中的Pod template里定义的存储卷,是所有副本集共用一个存储卷,数据是相同的,因为基于模板来的,而statefulset中每个pod都要自己的专用存储卷,所以statefulset的存储卷就不能再用pod模板来创建了,于是statefulset使用volumeclaimtemplate,称为卷申请模板,它会为每个pod生成不同的pvc,并绑定PV,从而实现各pod有专用存储。这就是为什么要用volumeclaimtemplate的原因。

(3)statefulset:定义具体应用,名为nginx,有三个pod副本,并为每个pod定义了一个域名部署statefulset。

配置文件的选项:

Kubectl explain sts.spec:查看主要字段解释

replicas:定义副本数
selector:标签选择器
servicename:必须关联到一个无头服务
template:定义pod模板(其中定义关联哪个存储卷)
volumeClaimTemplates:生成PVC

4.1 创建无头服务及statefulset

nginx-statefulset.yaml

[root@localhost nfs]# cat nginx-statefulset.yaml
apiVersion: v1
kind: Service
metadata:labels: {name: nginx-headless}name: nginx-headless#namespace: wubo
spec:ports:- {name: t9081, port: 81, protocol: TCP, targetPort: 80}selector: {name: nginx-headless}#type: NodePortclusterIP: None ##注意此处的值,None表示无头服务
---
apiVersion: apps/v1
kind: StatefulSet
metadata:name: nginx-headless#namespace: wubolabels: {name: nginx-headless}
spec:serviceName: "nginx-headless"replicas: 3 #三个副本selector:matchLabels: {name: nginx-headless}template:metadata:name: nginx-headlesslabels: {name: nginx-headless}spec:containers:- name: nginx-headless#image: harbor.jettech.com/jettechtools/nginx:1.21.4#image: 172.16.10.5:5000/library/nginx:1.21.4image: docker.io/library/nginx:1.21.4volumeMounts:- name: volvmountPath: /datavolumeClaimTemplates:- metadata:name: volvannotations:volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"   #managed-nfs-storage为我们创建的storage-class名称spec:accessModes: [ "ReadWriteOnce" ]resources:requests:storage: 1Gi

此时不需要在创建PVC了。pod分布在3个node上,在nfs-server的/opt/vlumes 有三个目录

[root@localhost nfs]# kubectl get pv,pvc,pods,sc,statefulset -o wide
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                           STORAGECLASS          REASON   AGE   VOLUMEMODE
persistentvolume/pvc-8601bae7-3182-43fa-b92b-da733f429427   1Gi        RWO            Delete           Bound    default/volv-nginx-headless-0   managed-nfs-storage            23s   Filesystem
persistentvolume/pvc-2800e24f-9d11-4be3-9676-4c629bcdf3ba   1Gi        RWO            Delete           Bound    default/volv-nginx-headless-1   managed-nfs-storage            21s   Filesystem
persistentvolume/pvc-8a974bbb-d6d6-484c-81b5-46cca9643288   1Gi        RWO            Delete           Bound    default/volv-nginx-headless-2   managed-nfs-storage            18s   FilesystemNAME                                          STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE   VOLUMEMODE
persistentvolumeclaim/volv-nginx-headless-0   Bound    pvc-8601bae7-3182-43fa-b92b-da733f429427   1Gi        RWO            managed-nfs-storage   24s   Filesystem
persistentvolumeclaim/volv-nginx-headless-1   Bound    pvc-2800e24f-9d11-4be3-9676-4c629bcdf3ba   1Gi        RWO            managed-nfs-storage   21s   Filesystem
persistentvolumeclaim/volv-nginx-headless-2   Bound    pvc-8a974bbb-d6d6-484c-81b5-46cca9643288   1Gi        RWO            managed-nfs-storage   18s   FilesystemNAME                                          READY   STATUS    RESTARTS   AGE     IP            NODE           NOMINATED NODE   READINESS GATES
pod/nfs-client-provisioner-848b88ddd4-rnq65   1/1     Running   0          3m32s   10.42.1.120   172.16.10.21   <none>           <none>
pod/nginx-headless-0                          1/1     Running   0          24s     10.42.2.98    172.16.10.15   <none>           <none>
pod/nginx-headless-1                          1/1     Running   0          21s     10.42.1.121   172.16.10.21   <none>           <none>
pod/nginx-headless-2                          1/1     Running   0          18s     10.42.0.194   172.16.10.5    <none>           <none>NAME                                              PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
storageclass.storage.k8s.io/longhorn              driver.longhorn.io                            Delete          Immediate           true                   5d22h
storageclass.storage.k8s.io/managed-nfs-storage   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           true                   3m20sNAME                              READY   AGE   CONTAINERS       IMAGES
statefulset.apps/nginx-headless   3/3     24s   nginx-headless   docker.io/library/nginx:1.21.4

物理机信息:

[root@localhost nfs]# ls /opt/vlumes/ -al
total 52220
drwxr-xr-x 6 root root      263 Jan 25 16:17 .
drwxr-xr-x 6 root root       65 Jan 25 10:33 ..
drwxrwxrwx 2 root root        6 Jan 25 16:17 default-volv-nginx-headless-0-pvc-8601bae7-3182-43fa-b92b-da733f429427
drwxrwxrwx 2 root root        6 Jan 25 16:17 default-volv-nginx-headless-1-pvc-2800e24f-9d11-4be3-9676-4c629bcdf3ba
drwxrwxrwx 2 root root        6 Jan 25 16:17 default-volv-nginx-headless-2-pvc-8a974bbb-d6d6-484c-81b5-46cca9643288

NFS Server上或进入各自的容器中操作也可以 :

[root@localhost nfs]# echo 172.16.10.15 > /opt/vlumes/default-volv-nginx-headless-0-pvc-8601bae7-3182-43fa-b92b-da733f429427/index.html
[root@localhost nfs]# echo 172.16.10.21 > /opt/vlumes/default-volv-nginx-headless-1-pvc-2800e24f-9d11-4be3-9676-4c629bcdf3ba/index.html
[root@localhost nfs]# echo 172.16.10.5 > /opt/vlumes/default-volv-nginx-headless-2-pvc-8a974bbb-d6d6-484c-81b5-46cca9643288/index.html

集群任意节点上:

#进入集群中任意pod中,解析nginx-headless 服务/ # nslookup nginx-headless
root@localhost nfs]# kubectl exec -it   pod/nginx-headless-0 --  /bin/sh# nslookup nginx-headless.default.svc.jettech.comName:      nginx-headless
Address 1: 10.10.2.3 10-10-2-4.nginx-headless.default.svc.jettech.com  #可以看到有两个地址
Address 2: 10.10.2.4 10-10-2-4.nginx-headless.default.svc.jettech.com  Address 3: 10.10.2.5 10-10-2-5.nginx-headless.default.svc.jettech.com #curl 10.10.2.3
172.16.10.5
#curl 10.10.2.4
172.16.10.15
#curl 10.10.2.5
172.16.10.21
# cd /data
# ls
index.html
# cat index.html
172.16.10.15
# exit
[root@localhost nfs]# kubectl exec -it   pod/nginx-headless-1 --  /bin/sh
# cd /data
# cat index.html
172.16.10.21
# exxit
/bin/sh: 3: exxit: not found
# exit
command terminated with exit code 127
[root@localhost nfs]# kubectl exec -it   pod/nginx-headless-3 --  /bin/sh
Error from server (NotFound): pods "nginx-headless-3" not found
[root@localhost nfs]# kubectl exec -it   pod/nginx-headless-2 --  /bin/sh
# cd /data
# cat index.html
172.16.10.5
# exit
[root@localhost nfs]# 

#对于statefulset我们可以通过添加/删除pod副本的数量,观察PV/PVC的状态及变化.

5.关于StorageClass回收策略对数据的影响

5.1第一种配置

  archiveOnDelete: "false"  reclaimPolicy: Delete   #默认没有配置,默认值为Delete

测试结果:

1.pod删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
2.sc删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
3.删除PVC后,PV被删除且NFS Server对应数据被删除

5.2 第二种配置

   archiveOnDelete: "false"  reclaimPolicy: Retain  

测试结果:

1.pod删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
2.sc删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
3.删除PVC后,PV不会别删除,且状态由Bound变为Released,NFS Server对应数据被保留
4.重建sc后,新建PVC会绑定新的pv,旧数据可以通过拷贝到新的PV中
5.3和4步骤可以不这样操作也可以使用,请参考:PV可在用2.4.2 删除 PVC章节

5.3 第三种配置

   archiveOnDelete: "ture"  reclaimPolicy: Retain

结果

1.pod删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
2.sc删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
3.删除PVC后,PV不会别删除,且状态由Bound变为Released,NFS Server对应数据被保留
4.重建sc后,新建PVC会绑定新的pv,旧数据可以通过拷贝到新的PV中

5.4 第四种配置

  archiveOnDelete: "ture"  reclaimPolicy: Delete

结果

1.pod删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
2.sc删除重建后数据依然存在,旧pod名称及数据依然保留给新pod使用
3.删除PVC后,PV不会别删除,且状态由Bound变为Released,NFS Server对应数据被保留
4.重建sc后,新建PVC会绑定新的pv,旧数据可以通过拷贝到新的PV中

总结:除以第一种配置外,其他三种配置在PV/PVC被删除后数据依然保留

6.设置为默认存储

使用storageclass的方式有两种一种是在创建pvc资源配置清单中指定使用。另外一种方法就是把它设置为默认的存储。

6.1 PVC中使用:

[root@localhost nfs]# cat test-pvc.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:name: test-claimannotations:## 这里指定了是用storageclass,如果storageclass是默认的存储方式则这里可以不用写。 看下面:storageclass:设置默认存储volume.beta.kubernetes.io/storage-class: "managed-nfs-storage"
spec:storageClassName: managed-nfs-storageaccessModes:- ReadWriteManyresources:requests:storage: 1Mi

6.2 设置默认存储,其他pvc可以直接使用:

6.2.1)设置默认

[root@localhost nfs]# cat nfs-storageclass.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: managed-nfs-storageannotations:storageclass.kubernetes.io/is-default-class: "false"  ## 是否设置为默认的storageclass
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'
parameters:archiveOnDelete: "true"                                 ## 设置为"false"时删除PVC不会保留数据,"true"则保留数据
mountOptions:- hard                                                  ## 指定为硬挂载方式- nfsvers=4                                             ## 指定NFS版本,这个需要根据NFS Server版本号设置
[root@localhost nfs]#

6.2.2)设置默认另一种方式设置默认存储

查看当前sc

[root@localhost nfs]# kubectl get sc
NAME                  PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
longhorn              driver.longhorn.io                            Delete          Immediate           true                   5d22h
managed-nfs-storage   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           true                   33m

#设置managed-nfs-storage为默认后端存储,managed-nfs-storage 为 storageclass 配置的名字

[root@localhost nfs]#  kubectl patch storageclass managed-nfs-storage -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
storageclass.storage.k8s.io/managed-nfs-storage patched 

# 再次查看,注意时候有default标识

[root@localhost nfs]# kubectl get sc
NAME                            PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
longhorn                        driver.longhorn.io                            Delete          Immediate           true                   5d22h
managed-nfs-storage (default)   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           true                   35m

6.3 如何使用默认的StorageClass

如果集群有一个默认的StorageClass能够满足我们的需求,那么剩下所有需要做的就是创建PersistentVolumeClaim(PVC),剩下的都有默认的动态配置搞定,包括无需去指定storageClassName:

[root@localhost nfs]# cat pvc2.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: pvc2#namespace: wubo
spec:accessModes:  #访问模式#- ReadWriteOnce#- ReadWriteOncePod- ReadWriteMany#- ReadOnlyManyresources: #申请资源,8Gi存储空间requests:storage: 1Gi#storageClassName: managed-nfs-storage#selector:#  matchLabels:#    name: "wubo-pv1"#matchExpressions:#  - {key: environment, operator: In, values: [dev]}

观察发现pvc和pv都多了一个

[root@localhost nfs]# kubectl get pv,pvc,pods,sc,statefulset -o wide
NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                           STORAGECLASS          REASON   AGE   VOLUMEMODE
persistentvolume/pvc-8601bae7-3182-43fa-b92b-da733f429427   1Gi        RWO            Delete           Bound    default/volv-nginx-headless-0   managed-nfs-storage            36m   Filesystem
persistentvolume/pvc-2800e24f-9d11-4be3-9676-4c629bcdf3ba   1Gi        RWO            Delete           Bound    default/volv-nginx-headless-1   managed-nfs-storage            36m   Filesystem
persistentvolume/pvc-8a974bbb-d6d6-484c-81b5-46cca9643288   1Gi        RWO            Delete           Bound    default/volv-nginx-headless-2   managed-nfs-storage            36m   Filesystem
persistentvolume/pvc-a1948238-ce02-4004-8963-3b3778f35454   1Gi        RWX            Delete           Bound    default/pvc2                    managed-nfs-storage            2s    FilesystemNAME                                          STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE   VOLUMEMODE
persistentvolumeclaim/volv-nginx-headless-0   Bound    pvc-8601bae7-3182-43fa-b92b-da733f429427   1Gi        RWO            managed-nfs-storage   36m   Filesystem
persistentvolumeclaim/volv-nginx-headless-1   Bound    pvc-2800e24f-9d11-4be3-9676-4c629bcdf3ba   1Gi        RWO            managed-nfs-storage   36m   Filesystem
persistentvolumeclaim/volv-nginx-headless-2   Bound    pvc-8a974bbb-d6d6-484c-81b5-46cca9643288   1Gi        RWO            managed-nfs-storage   36m   Filesystem
persistentvolumeclaim/pvc2                    Bound    pvc-a1948238-ce02-4004-8963-3b3778f35454   1Gi        RWX            managed-nfs-storage   2s    FilesystemNAME                                          READY   STATUS    RESTARTS   AGE   IP            NODE           NOMINATED NODE   READINESS GATES
pod/nfs-client-provisioner-848b88ddd4-rnq65   1/1     Running   0          39m   10.42.1.120   172.16.10.21   <none>           <none>NAME                                                        PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
storageclass.storage.k8s.io/longhorn                        driver.longhorn.io                            Delete          Immediate           true                   5d22h
storageclass.storage.k8s.io/managed-nfs-storage (default)   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           true                   39m

6.4 关闭默认的StorageClass

不能删除默认的StorageClass,因为它是作为集群的k3s安装的,如果它被删除,会被重新安装。

[root@localhost nfs]# ls /var/lib/rancher/k3s/server/manifests/local-storage.yaml
/var/lib/rancher/k3s/server/manifests/local-storage.yaml
[root@localhost nfs]# cat /var/lib/rancher/k3s/server/manifests/local-storage.yaml
apiVersion: v1
kind: ServiceAccount
metadata:name: local-path-provisioner-service-accountnamespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:name: local-path-provisioner-role
rules:
- apiGroups: [""]resources: ["nodes", "persistentvolumeclaims", "configmaps"]verbs: ["get", "list", "watch"]
- apiGroups: [""]resources: ["endpoints", "persistentvolumes", "pods"]verbs: ["*"]
- apiGroups: [""]resources: ["events"]verbs: ["create", "patch"]
- apiGroups: ["storage.k8s.io"]resources: ["storageclasses"]verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:name: local-path-provisioner-bind
roleRef:apiGroup: rbac.authorization.k8s.iokind: ClusterRolename: local-path-provisioner-role
subjects:
- kind: ServiceAccountname: local-path-provisioner-service-accountnamespace: kube-system
---
apiVersion: apps/v1
kind: Deployment
metadata:name: local-path-provisionernamespace: kube-system
spec:replicas: 1selector:matchLabels:app: local-path-provisionertemplate:metadata:labels:app: local-path-provisionerspec:priorityClassName: "system-node-critical"serviceAccountName: local-path-provisioner-service-accounttolerations:- key: "CriticalAddonsOnly"operator: "Exists"- key: "node-role.kubernetes.io/control-plane"operator: "Exists"effect: "NoSchedule"- key: "node-role.kubernetes.io/master"operator: "Exists"effect: "NoSchedule"containers:- name: local-path-provisionerimage: rancher/local-path-provisioner:v0.0.20imagePullPolicy: IfNotPresentcommand:- local-path-provisioner- start- --config- /etc/config/config.jsonvolumeMounts:- name: config-volumemountPath: /etc/config/env:- name: POD_NAMESPACEvalueFrom:fieldRef:fieldPath: metadata.namespacevolumes:- name: config-volumeconfigMap:name: local-path-config
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: local-pathannotations:storageclass.kubernetes.io/is-default-class: "true"
provisioner: rancher.io/local-path
volumeBindingMode: WaitForFirstConsumer
reclaimPolicy: Delete
---
kind: ConfigMap
apiVersion: v1
metadata:name: local-path-confignamespace: kube-system
data:config.json: |-{"nodePathMap":[{"node":"DEFAULT_PATH_FOR_NON_LISTED_NODES","paths":["/var/lib/rancher/k3s/storage"]}]}setup: |-#!/bin/shwhile getopts "m:s:p:" optdocase $opt inp)absolutePath=$OPTARG;;s)sizeInBytes=$OPTARG;;m)volMode=$OPTARG;;esacdonemkdir -m 0777 -p ${absolutePath}chmod 701 ${absolutePath}/..teardown: |-#!/bin/shwhile getopts "m:s:p:" optdocase $opt inp)absolutePath=$OPTARG;;s)sizeInBytes=$OPTARG;;m)volMode=$OPTARG;;esacdonerm -rf ${absolutePath}helperPod.yaml: |-apiVersion: v1kind: Podmetadata:name: helper-podspec:containers:- name: helper-podimage: rancher/mirrored-library-busybox:1.32.1

当然可以在安装k3s的时候选择不安装

[root@localhost nfs]# k3s server --help | grep disable--etcd-disable-snapshots                   (db) Disable automatic etcd snapshots--disable value                            (components) Do not deploy packaged components and delete any deployed components (valid items: coredns, servicelb, traefik, local-storage, metrics-server)--disable-scheduler                        (components) Disable Kubernetes default scheduler--disable-cloud-controller                 (components) Disable k3s default cloud controller manager--disable-kube-proxy                       (components) Disable running kube-proxy--disable-network-policy                   (components) Disable k3s default network policy controller--disable-helm-controller                  (components) Disable Helm controller[root@localhost nfs]# k3s server  --disable local-storage

或者可以停掉默认的StorageClass行为,通过删除annotation:storageclass.kubernetes.io/is-default-class,或者设置为false。

[root@localhost nfs]# kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"false"}}}'
[root@localhost nfs]# kubectl get storageclass local-path  -o yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:annotations:storageclass.kubernetes.io/is-default-class: "false"creationTimestamp: "2022-01-25T09:01:48Z"name: local-pathresourceVersion: "519166"uid: 8c710916-1b12-4844-9eb4-e9cce6de5244
provisioner: rancher.io/local-path
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
[root@localhost nfs]# kubectl patch storageclass local-path -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'
storageclass.storage.k8s.io/local-path patched
[root@localhost nfs]# kubectl get storageclass local-path  -o yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:annotations:storageclass.kubernetes.io/is-default-class: "true"creationTimestamp: "2022-01-25T09:01:48Z"name: local-pathresourceVersion: "519244"uid: 8c710916-1b12-4844-9eb4-e9cce6de5244
provisioner: rancher.io/local-path
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer

如果没有StorageClass对象标记默认的annotation,那么PersistentVolumeClaim对象(在不指定StorageClass情况下)将不自动触发动态配置。相反,它们将回到绑定可用的*PersistentVolume(PV)*的状态。

6.5 当删除PersistentVolumeClaim(PVC)会发生什么

如果一个卷是动态配置的卷,则默认的回收策略为“删除”。这意味着,在默认的情况下,当PVC被删除时,基础的PV和对应的存储也会被删除。如果需要保留存储在卷上的数据,则必须在PV被设置之后将回收策略从delete更改为retain。

[Kubernetes]PV,PVC,StorageClass实战----|||相关推荐

  1. Kubernetes PV/PVC/StroageClass 持久化存储简介

    2019独角兽企业重金招聘Python工程师标准>>> PersistentVolume(简称PV)和PersistentVolumeClaim(简称PVC) PersistentV ...

  2. kubernetes PV、PVC、StorageClass

    眼下,你我可能已经错过了互联网技术大爆炸的时代,也没有在数字货币早期的狂热里分到一杯羹.可就在此时此刻,在沉寂了多年的云计算与基础设施领域,一次以"容器"为名的历史变革,正呼之欲出 ...

  3. Kubernetes——PV与PVC

    目录 一.PV和PVC详解 二.PV和PVC生命周期 2.1 Provisioning 2.2 Binding 2.3 Using 2.4 Releasing 2.5 Reclaiming 2.6 R ...

  4. Kubernetes Volume及其类型(NFS、SAN) - PV - PVC - PV与PVC与Pod的关系

    目录 volume 卷 官方文档:卷 | Kubernetes 一.emptyDir(临时卷) 二.hostPath卷 type字段参数 hostPath 实验: 三.第3方提供的存储卷(百度云.阿里 ...

  5. Kubernetes PV和PVC 常见问题

    文章目录 Kubernetes PV和PVC 常见问题 PV和PVC的关系 删除Kubernetes的PV和PVC时状态一直为Terminating PVC创建后一直处在Pending状态 Kuber ...

  6. K8S的StorageClass实战(NFS)

    关于StorageClass 在K8S环境,当pod需要存储空间时,StorageClass比PV更灵活和方便,官方文档地址:https://kubernetes.io/docs/concepts/s ...

  7. 资源利用率监控与存储卷(pv/pvc)

    官网:https://kubernetes.io/zh/ 一.资源利用率的概述 k8s容器创建的时候是随机分配的,如果随机分配时,有一个节点分配得比较多时,则会出现卡顿等等的事情: 1.metrics ...

  8. k8s mysql pv_k8s+mysql+pv+pvc+NFS实现mysql容器编排

    k8s+mysql+pv+pvc+NFS实现mysql容器编排 首先需要介绍概念:pv和pvc PV 是集群提供的一种存储资源,是实际可用的磁盘.和挂 PV 的 Pod 有着独立的生命周期,Pod 销 ...

  9. k8s-----(| 三 |)存储Volume卷,PV / PVC,nfs持久化

    文章目录 一. 存储Volume 卷 1. emptyDir本地临时卷 2. hostPath 本地卷 二 . PV/PVC 1. 概念 2. PV 一些概念 (1)PV 的类型(插件) (2)访问模 ...

最新文章

  1. GridView、Repeater合并单元格
  2. 浅谈工作流引擎的几个关键因素
  3. java http请求
  4. MyBatis 缓存详解-二级缓存介绍
  5. Mac下IDEA安装Protobuf插件
  6. 20210119:力扣第42周双周赛(下)
  7. SqlHelper帮助类_上(SQLServer数据库含Connection详解)
  8. word更改字距调整,让文章更具美感!
  9. jquey知识点整理
  10. html画布里面画圆,html5 canvas 画布画圆
  11. 这一篇带你学点儿 Java8 中的流式数据处理
  12. 索尼计算机更换硬盘教程视频教程,自己动手,索尼PS4更换SSD硬盘全教程
  13. TreeSet引发的OSGI服务代理创建异常
  14. 大数据常见错误及解决方案
  15. 【笔记】Oracle删除项目编号多余的数据
  16. 电磁场与仿真软件(36)
  17. electron-vue通过配置文件设置baseUrl
  18. 【Hive】Hive 查询
  19. 协议 + socket import 和 form xx import *的区别 028
  20. 【全套完整版本】YOLOv3使用方法记录

热门文章

  1. 半监督学习 图像分类_自我监督学习的图像分类。
  2. MySQL--基础知识点--27--事务类型
  3. 怎么用java做全民飞机大战_Java飞机大战游戏设计与实现
  4. [QQ机器人]Nonebot2 入群退群检测插件
  5. 46道Redis面试题,含参考答案!
  6. python-QGIS环境配置
  7. 2020年最新民生香港卡教程(只需要存5W)
  8. 刘澜:北大影响力笔记
  9. 如何卸载干净JAVA?
  10. Cobbler 自动化部署系统