当前位置: 首页 > news >正文

CKA认证 | Day7 K8s存储

第七章 Kubernetes存储

1、数据卷与数据持久卷

为什么需要数据卷?

容器中的文件在磁盘上是临时存放的,这给容器中运行比较重要的应用程序带来一些问题。

  1. 问题1:当容器升级或者崩溃时,kubelet会重建容器,容器内文件会丢失;

  2. 问题2:一个Pod中运行多个容器并需要共享文件;

Kubernetes 卷(Volume) 这一抽象概念能够解决这两个问题。

1. 数据持久化
  • 容器的无状态性:Kubernetes 中的容器是无状态的,这意味着它们在重启或重新调度时,容器内的数据会丢失。
  • 持久化数据:通过使用数据卷,可以将数据持久化到外部存储系统中,确保数据在容器生命周期外仍然存在。
2. 数据共享
  • 多容器共享数据:在某些情况下,同一个 Pod 中的多个容器需要共享数据。数据卷允许这些容器共享同一个存储卷。
  • 跨容器协作:例如,一个容器生成日志文件,另一个容器需要读取这些日志文件。
3. 数据隔离
  • 独立存储:不同的 Pod 可以使用不同的数据卷,确保数据隔离,避免数据冲突。
  • 安全性:通过将敏感数据存储在独立的数据卷中,可以提高安全性。
4. 简化配置
  • 统一管理:使用数据卷可以简化应用程序的配置,因为存储配置可以在 Kubernetes 中集中管理。
  • 灵活性:可以根据需要选择不同的存储后端(如本地存储、云存储、网络文件系统等),而不需要修改应用程序代码。
5. 生命周期管理
  • 数据生命周期:数据卷可以独立于 Pod 的生命周期管理,确保数据在 Pod 重启或删除后仍然可用。
  • 自动挂载:Kubernetes 可以自动挂载和卸载数据卷,简化操作。

常用的数据卷:

官网:Volumes | Kubernetes

  • 节点本地存储(hostPath,emptyDir)
  • 网络存储(NFS,Ceph,GlusterFS)
  • 公有云存储(AWS EBS)
  • K8S资源存储(configmap,secret)

1.1 临时数据卷:emptyDir

emptyDir卷是一个临时存储卷,与Pod生命周期绑定一起,如果 Pod删除了卷也会被删除

应用场景:Pod中容器之间数据共享

选项:

  • sizeLimit:500Mi       //限制共享空间大小(比较少用)


示例:Pod内容器之间共享数据
apiVersion: v1
kind: Pod
metadata: name: emptydir-test
spec:containers:- name: write-container    //程序讲数据写入到文件image: centoscommand: ["bash","-c","for i in {1..100};do echo $i >> 
/data/hello;sleep 1;done"]volumeMounts:- name: data-volume    //通过volume卷名称引用mountPath: /data- name: read-container     //程序从文件中读取数据image: centoscommand: ["bash","-c","tail -f /data/hello"]volumeMounts:- name: data-volumemountPath: /datavolumes:      //定义卷的来源- name: data-volumeemptyDir: {}    //{}中为空值

验证1:验证容器之间是否能够共享数据

[root@k8s-master-1-71 ~]# kubectl apply -f emptydir-test.yaml
[root@k8s-master-1-71 ~]# kubectl get pods
NAME            READY   STATUS    RESTARTS      AGE
emptydir-test   2/2     Running   1 (21s ago)   3m14s
# 进入write-container查看
[root@k8s-master-1-71 ~]# kubectl exec -it emptydir-test -- bash[root@emptydir-test /]# tail -f /data/hello
...
99
100
command terminated with exit code 137
[root@emptydir-test /]# touch /data/aaa ; ls /data/     //往容器中写入临时文件
aaa  hello## 注:脚本循环结束后退出容器,按照默认的策略Always进行容器重启。
# 进入read-container查看
[root@k8s-master-1-71 ~]# kubectl exec -it emptydir-test -c read-container -- bash
[root@emptydir-test /]# ls /data/
aaa  hello

验证2:实际存储位置(基于节点的存储)

补充:Kubelet的工作目录为/var/lib/kubelet/,负载维护Pod数据的

[root@k8s-master-1-71 ~]# kubectl get pods -o wide
NAME            READY   STATUS    RESTARTS      AGE    IP              NODE             NOMINATED NODE   READINESS GATES
emptydir-test   2/2     Running   2 (45s ago)   5m3s   10.244.114.48   k8s-node2-1-73   <none>           <none># 根据查看到的Pod所在节点,找到相应的容器ID
[root@k8s-node2-1-73 ~]# docker ps | grep emptydir-test(例如:7c26307b-b290-4bac-9a3b-6f18ffc26776)
6deb6dc27ab2   centos                                              "bash -c 'for i in {…"   44 seconds ago   Up 43 seconds             k8s_write-container_emptydir-test_default_7c26307b-b290-4bac-9a3b-6f18ffc26776_3
7bb46657087b   centos                                              "bash -c 'tail -f /d…"   7 minutes ago    Up 7 minutes              k8s_read-container_emptydir-test_default_7c26307b-b290-4bac-9a3b-6f18ffc26776_0
fd4c6c671807   registry.aliyuncs.com/google_containers/pause:3.7   "/pause"                  7 minutes ago    Up 7 minutes              k8s_POD_emptydir-test_default_7c26307b-b290-4bac-9a3b-6f18ffc26776_0# 找到相关Pod关联的empty-dir目录,即可看到挂载的empty-dir目录内容
[root@k8s-node2-1-73 kubernetes.io~empty-dir]# pwd
/var/lib/kubelet/pods/7c26307b-b290-4bac-9a3b-6f18ffc26776/volumes/kubernetes.io~empty-dir[root@k8s-node2-1-73 kubernetes.io~empty-dir]# ls data-volume/
aaa  bbb  hello

Pod是节点级别的,Pod中的容器都是捆绑在一个节点上,所以 Pod删除了,卷也会被删除

[root@k8s-master-1-71 ~]# kubectl delete -f emptydir-test.yaml
[root@k8s-node2-1-73 kubernetes.io~empty-dir]# ls data-volume/
ls: 无法访问data-volume/: 没有那个文件或目录

1.2 节点数据卷:hostPath

hostPath卷挂载Node的文件系统(即Pod所在节点)上文件或者目 录到Pod中的容器。

应用场景:Pod中容器需要访问宿主机的文件

选项:

  • path:    //将宿主机的目录或文件映射到容器中去

  • type:    //指定类型(目录或文件)


示例:将宿主机/tmp目录挂载到容器/data目录
apiVersion: v1
kind: Pod
metadata:name: hostpath-test
spec:containers:- name: busyboximage: busyboxargs:- /bin/sh- -c- sleep 36000volumeMounts:- name: data-volumemountPath: /datavolumes:- name: data-volumehostPath:path: /tmp        //Pod所在节点的/tmp目录type: Directory

验证:

[root@k8s-master-1-71 ~]# kubectl apply -f hostpath-test.yaml[root@k8s-master-1-71 ~]# kubectl get pods -o wide
NAME            READY   STATUS    RESTARTS   AGE   IP              NODE             NOMINATED NODE   READINESS GATES
hostpath-test   1/1     Running   0          86s   10.244.114.49   k8s-node2-1-73   <none>           <none># 查看Pod所在节点的/tmp目录
[root@k8s-node2-1-73 ~]# ls /tmp/
systemd-private-85c79618aae44d7cbb01bccc046ecb10-chronyd.service-UMOhQf
systemd-private-fdf2b8d40ff841319feb738a463a8f6f-chronyd.service-GJT2Mn# 进入容器查看/data目录是否有相关映射文件
[root@k8s-master-1-71 ~]# kubectl exec -it hostpath-test -- sh
/ # ls /data/
systemd-private-85c79618aae44d7cbb01bccc046ecb10-chronyd.service-UMOhQf
systemd-private-fdf2b8d40ff841319feb738a463a8f6f-chronyd.service-GJT2Mn

1.3 网络数据卷:NFS

NFS:是一个主流的文件共享服务器(注:每个Node上都要安装nfs-utils包)

# 服务端部署
[root@k8s-node1-1-72 ~]# yum install -y nfs-utils
[root@k8s-node1-1-72 ~]# vi /etc/exports     //NFS共享配置目录
/ifs/kubernetes *(rw,no_root_squash)
# 解释:
共享目录 访问来源限制(权限)
[root@k8s-node1-1-72 ~]# mkdir -p /ifs/kubernetes[root@k8s-node1-1-72 ~]# systemctl enable nfs --now# 客户端测试
[root@k8s-node2-1-73 ~]# yum install -y nfs-utils
[root@k8s-node2-1-73 ~]# mount -t nfs 192.168.1.72:/ifs/kubernetes /mnt
[root@k8s-node2-1-73 ~]# df -Th | grep /ifs/kubernetes
192.168.1.72:/ifs/kubernetes nfs4       37G  4.3G   33G   12% /mnt

NFS卷:提供对NFS挂载支持,可以自动将NFS共享路径 挂载到Pod中

选项:

  • server: //指定NFS地址

  • path: /指定NFS共享目录


示例:将Nginx网站程序根目录持久化到 NFS存储,为多个Pod提供网站程序文件
apiVersion: apps/v1
kind: Deployment
metadata:labels:app: nfs-podname: nfs-pod
spec:selector:matchLabels:app: nginxreplicas: 3template:metadata:labels:app: nginxspec:containers:- name: webimage: nginxvolumeMounts:- name: data-volumemountPath: /usr/share/nginx/html    //挂载的目录volumes:- name: data-volume      //volume卷名称nfs:server: 192.168.1.72     //指定NFS地址path: /ifs/kubernetes    //NFS共享目录

验证1:容器之间的数据是否共享

[root@k8s-node2-1-73 ~]# kubectl apply -f nfs-pod.yaml
[root@k8s-master-1-71 ~]# kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGEnfs-pod-9b57f886-d8rzx   1/1     Running   0          10m
nfs-pod-9b57f886-tk99z   1/1     Running   0          55s
nfs-pod-9b57f886-wpr9q   1/1     Running   0          10m# 进入容器1,创建文件测试
[root@k8s-node2-1-73 ~]# kubectl exec -it nfs-pod-9b57f886-d8rzx -- bash
root@nfs-pod-9b57f886-d8rzx:/# df -Th |grep /ifs/kubernetes
192.168.1.72:/ifs/kubernetes nfs4      37G  4.3G   33G  12% /usr/share/nginx/html
root@nfs-pod-9b57f886-d8rzx:~# touch /usr/share/nginx/html/aaaaa
root@nfs-pod-9b57f886-d8rzx:~# ls /usr/share/nginx/html/
aaaaa# 进入容器2,查看文件
[root@k8s-node2-1-73 ~]# kubectl exec -it nfs-pod-9b57f886-tk99z -- bash
root@nfs-pod-9b57f886-tk99z:/# ls /usr/share/nginx/html/
aaaaa

验证2:增加/删除Pod是否能继续使用共享存储数据

# 删除Pod,查看是否能继续使用共享存储数据
[root@k8s-master-1-71 ~]# kubectl delete pod nfs-pod-9b57f886-tk99z[root@k8s-master-1-71 ~]# kubectl get pods
NAME                     READY   STATUS    RESTARTS   AGE
nfs-pod-9b57f886-d8rzx   1/1     Running   0          10m
nfs-pod-9b57f886-fg7n7   1/1     Running   0          55s    //新增Pod
nfs-pod-9b57f886-wpr9q   1/1     Running   0          10m[root@k8s-node2-1-73 ~]# kubectl exec -it nfs-pod-9b57f886-fg7n7 -- bash
root@nfs-pod-9b57f886-fg7n7:/# ls /usr/share/nginx/html/
aaaaa# 增加Pod,查看是否能继续使用共享存储数据
[root@k8s-master-1-71 ~]# kubectl scale deployment nfs-pod --replicas=5
[root@k8s-node2-1-73 ~]# kubectl exec -it nfs-pod-9b57f886-sj7m6 -- bash
root@nfs-pod-9b57f886-sj7m6:/# df -Th | grep /ifs/kubernetes
192.168.1.72:/ifs/kubernetes nfs4      37G  4.3G   33G  12% /usr/share/nginx/html
root@nfs-pod-9b57f886-sj7m6:/# ls /usr/share/nginx/html/
aaaaa

1.4 持久数据卷概述

持久卷(Persistent Volumes, PV) 是用于管理存储的重要概念。

安全性:如果要设置安全方面的认证,都需要提前将安全配置写入YAML,因此将会暴露在YAML文件中,导致安全性减低;

专业性:在应用的部署上,使用者对K8S、存储的不了解,对于建设者来说,倡导职责上的分离。

1.4.1 PV、PVC

持久卷(Persistent Volumes, PV)

  • 定义:持久卷是集群中的一块存储,由管理员配置和管理。它们独立于 Pod 的生命周期,可以被多个 Pod 使用。
  • 生命周期:持久卷的生命周期独立于 Pod,即使 Pod 被删除,数据仍然保留。

对存储资源创建和使用的抽象,使得存储作为集群中的资源管理(定义后端存储)

持久卷声明(Persistent Volume Claims, PVC)

  • 定义:持久卷声明是用户对持久卷的请求。用户不需要了解底层存储的细节,只需要声明所需的存储大小和访问模式。
  • 生命周期:持久卷声明的生命周期与 Pod 的生命周期无关,可以独立存在。
  • 绑定:持久卷声明会被绑定到一个满足其请求的持久卷上。一旦绑定,PVC 和 PV 之间是一对一的关系。

让用户不需要关心具体的Volume实现细节(访问模式、存储容量大小)

Pod申请PVC作为卷来使用,Kubernetes通过PVC查找绑定的PV,并Mount给Pod。

支持持久卷的存储插件:Persistent Volumes | Kubernetes

1.4.2 PV与PVC使用流程

官网:配置 Pod 以使用 PersistentVolume 作为存储 | Kubernetes

PVC配置示例:

---   //容器应用
apiVersion: apps/v1
kind: Deployment
metadata: labels:app: my-podname: my-pod
spec:selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- name: webimage: nginxvolumeMounts:- name: data-volumemountPath: /usr/share/nginx/html    //挂载的目录volumes:- name: data-volumepersistentVolumeClaim:claimName: my-pvc---   //PVC 卷需求模板
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: my-pvc      //与claimName对应进行关联
spec:accessModes:- ReadWriteManyresources:requests:storage: 5Gi
  • 查看PVC命令:kubectl get pvc
[root@k8s-master-1-71 ~]# kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
my-pod-6fbc98b678-42m29   0/1     Pending   0          10s
[root@k8s-master-1-71 ~]# kubectl get pvc
NAME     STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
my-pvc   Pending                                                     18s

注意:如果没有可用的PV,PVC无法进行资源分配会处于在Pending状态

PV配置示例:

apiVersion: v1
kind: PersistentVolume
metadata:name: my-pv      //随便定义
spec:capacity:storage: 5Gi     //后端存储定义资源accessModes:- ReadWriteManynfs:path: /ifs/kubernetesserver: 192.168.1.72
  • 查看PV命令:kubectl get pv
[root@k8s-master-1-71 ~]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM            STORAGECLASS   REASON   AGE
my-pv   5Gi        RWX            Retain           Bound    default/my-pvc                           11s
[root@k8s-master-1-71 ~]# kubectl get pvc
NAME     STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
my-pvc   Bound    my-pv    5Gi        RWX                           2m30s
[root@k8s-master-1-71 ~]# kubectl get pods
NAME                      READY   STATUS    RESTARTS   AGE
my-pod-6fbc98b678-42m29   1/1     Running   0          3m5s

测试:

[root@k8s-master-1-71 ~]# kubectl exec -it my-pod-6fbc98b678-42m29 -- bash
root@my-pod-6fbc98b678-42m29:/# df -Th | grep /ifs/kubernetes
192.168.1.72:/ifs/kubernetes nfs4      37G  4.3G   33G  12% /usr/share/nginx/html
root@my-pod-6fbc98b678-42m29:/# ls /usr/share/nginx/html/
aaaaa

思考:多PV配置

挂载PV的时候,不能将挂载点挂到同一目录,为保证应用的唯一性,需要在挂载点的节点上创建各自的目录,避免冲突

多PV配置示例:

apiVersion: v1
kind: PersistentVolume
metadata:name: pv001      //随便定义
spec:capacity:storage: 5Gi     //后端存储定义资源accessModes:- ReadWriteManynfs:path: /ifs/kubernetes/pv001server: 192.168.1.72
---
apiVersion: v1
kind: PersistentVolume
metadata:name: pv002      //随便定义
spec:capacity:storage: 15Gi     //后端存储定义资源accessModes:- ReadWriteManynfs:path: /ifs/kubernetes/pv002server: 192.168.1.72

总结:

1、PV与PVC怎么匹配?

主要根据PVC的存储容量访问模式进行匹配

2、存储容量怎么匹配?

容量只会向上匹配,如已有未使用PV有10G、20G,申请5G,向上取最近的PV容量10G

3、PV与PVC的关系?

一对一,存在绑定关系

4、容量请求是否有实际的限制?

目前容量请求主要用作于PVC与PV进行匹配的,只是抽象的存在,而具体的限制取决于后端存储,即请求容量不能超过共享存储的实际容量

1.4.3 PV 生命周期

1)AccessModes(访问模式):

AccessModes 是用来对 PV 进行访问模式的设置,用于描述用户应用对存储资源的访问权限,访问权限包括下面几种方式:

  • ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载

  • ReadOnlyMany(ROX):只读权限,可以被多个节点挂载

  • ReadWriteMany(RWX):读写权限,可以被多个节点挂载

备注:块存储(单节点)、文件系统、对象存储(多节点)

2)RECLAIM POLICY(回收策略):

目前 PV 支持的策略有三种:

  • Retain(保留): 保留数据,需要管理员手工清理数据(默认策略)

  • Recycle(回收):清除 PV 中的数据,效果相当于执行 rm -rf /ifs/kuberneres/*(一般结合StorageClass使用,NFS暂时无法看出效果)

  • Delete(删除):与 PV 相连的后端存储同时删除(一般结合StorageClass使用,NFS暂时无法看出效果)

persistentVolumeReclaimPolicy: 回收策略

3)STATUS(状态):

一个 PV 的生命周期中,可能会处于4中不同的阶段:

  • Available(可用):表示可用状态,还未被任何 PVC 绑定

  • Bound(已绑定):表示 PV 已经被 PVC 绑定

  • Released(已释放):PVC 被删除,但是资源还未被集群重新声明

  • Failed(失败): 表示该 PV 的自动回收失败


示例:观察回收状态和默认的Retain回收策略
[root@k8s-master-1-71 ~]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM            STORAGECLASS   REASON   AGE
my-pv   5Gi        RWX            Retain           Bound    default/my-pvc                           7h7m## 解释:目前回收策略为 Retain ,状态为 Bound(表示 PV 已经被 PVC 绑定)
[root@k8s-master-1-71 ~]# kubectl delete -f my-pvc.yaml
[root@k8s-master-1-71 ~]# kubectl get pv
NAME    CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM            STORAGECLASS   REASON   AGE
my-pv   5Gi        RWX            Retain           Released   default/my-pvc                           7h18m## 解释:连着删除Pod和PCV后,回收策略为 Retain ,状态为 Released(PVC 被删除,但是资源还未被集群重新声明)
[root@k8s-node1-1-72 ~]# ls /ifs/kubernetes/    //PV中的资源依旧保留,需要管理员手工清理数据
aaaaa

注意:删除PVC后,原来Bound的PV就无法继续使用,即使重新apply pvc.yaml,也是Pending状态。

思考:现在PV使用方式称为静态供给,需要K8s运维工程师提前创 建一堆PV,供开发者使用

1.5 PV 动态供给(StorageClass)

PV静态供给明显的缺点是维护成本太高了,需要提前创建PV且不灵活! 因此,K8s开始支持PV动态供给,使用StorageClass对象实现。StorageClass可以根据客户的PVC需求,通过PVC需求自动创建后端存储PV,且自动去绑定,无需像NFS还要创建目录隔离应用。

优点:

  • PV无需额外的提前独立创建;

  • PVC直接获取,也不用等待合适的PV;

支持动态供给的存储插件:Storage Classes | Kubernetes

相关GitHub部署:https://github.com/kubernetes-sigs/sig-storage-lib-external-provisioner

了解:Volume Plugin是支持存储的类型,Internal Provisioner内部是否支持

例如NFS内部是不支持的(不能直接PVC动态供给),且K8s默认不支持NFS动态供给,需要单独部署社区开发的插件

项目地址:https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner

部署StorageClass插件需要的3个文件:

cd deploy
kubectl apply -f rbac.yaml           # 授权访问apiserver
kubectl apply -f deployment.yaml     # 部署插件,需修改里面NFS服务器地址与共享目录
kubectl apply -f class.yaml          # 创建存储类(标识使用哪个存储)kubectl get sc                       # 查看存储类

补充:一个集群中可以有多个存储类,而一个存储类一般对应一个存储

流程图:


  • rbac.yaml 示例:

[root@k8s-master-1-71 nfs-external-provisioner]# cat rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:name: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: nfs-client-provisioner-runner
rules:- 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
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
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
subjects:- kind: ServiceAccountname: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default
roleRef:kind: Rolename: leader-locking-nfs-client-provisionerapiGroup: rbac.authorization.k8s.io
  • class.yaml 配置示例:
[root@k8s-master-1-71 nfs-external-provisioner]# cat class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: managed-nfs-storage     //StroagaClass存储类,在PVC中需要指定的标识(类似ingressclass选择Nginx)
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # or choose another name, must match deployment's env PROVISIONER_NAME'  //与Deployment的变量PROVISIONER_NAME保持一致 
parameters:archiveOnDelete: "false"
  • deployment.yaml 配置示例:
[root@k8s-master-1-71 nfs-external-provisioner]# cat deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:name: nfs-client-provisionerlabels:app: nfs-client-provisioner# replace with namespace where provisioner is deployednamespace: default
spec:replicas: 1strategy:type: Recreateselector:matchLabels:app: nfs-client-provisionertemplate:metadata:labels:app: nfs-client-provisionerspec:serviceAccountName: nfs-client-provisionercontainers:- name: nfs-client-provisioner    //部署了NFS供给程序的容器image: lizhenliang/nfs-subdir-external-provisioner:v4.0.1    //镜像地址volumeMounts:- name: nfs-client-rootmountPath: /persistentvolumesenv:- name: PROVISIONER_NAME     //class.yaml中的PROVISIONER_NAMEvalue: k8s-sigs.io/nfs-subdir-external-provisioner- name: NFS_SERVERvalue: 192.168.1.72     //需要指定后端NFS存储- name: NFS_PATHvalue: /ifs/kubernetesvolumes:- name: nfs-client-rootnfs:server: 192.168.1.72     //需要指定后端NFS存储path: /ifs/kubernetes
  • PVC指定存储类配置示例:
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: my-pvc
spec:storageClassName: "managed-nfs-storage"    //指定StorageClass存储类(class.yaml)accessModes:- ReadWriteMany  resources:requests:storage: 5Gi

测试1:基于NFS提供PVC动态供给

步骤1:部署NFS-StorageClass 存储类插件

[root@k8s-master-1-71 nfs-external-provisioner]# kubectl apply -f .
storageclass.storage.k8s.io/managed-nfs-storage created
deployment.apps/nfs-client-provisioner created
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created# 创建的Pod为deployment.yaml中的NFS供给程序容器
[root@k8s-master-1-71 nfs-external-provisioner]# kubectl get pods
NAME                                      READY   STATUS    RESTARTS   AGEnfs-client-provisioner-5848c9cddc-zkts2   1/1     Running   0          2m27s# 创建的storageclass为class.yaml中指定的存储类
[root@k8s-master-1-71 nfs-external-provisioner]# kubectl get storageclass
NAME                  PROVISIONER                                   RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
managed-nfs-storage   k8s-sigs.io/nfs-subdir-external-provisioner   Delete          Immediate           false                  2m54s

步骤2:在PVC中指定存储类名称

[root@k8s-master-1-71 ~]# kubectl apply -f my-pvc.yaml
apiVersion: apps/v1
kind: Deployment
metadata:labels:app: my-podname: my-pod
spec:selector:matchLabels:app: my-podtemplate:metadata:labels:app: my-podspec:containers:- name: my-podimage: nginxvolumeMounts:- name: data-volumemountPath: /usr/share/nginx/htmlvolumes:- name: data-volumepersistentVolumeClaim:claimName: my-pvc
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: my-pvcspec:storageClassName: "managed-nfs-storage"accessModes:- ReadWriteManyresources:requests:storage: 5Gi[root@k8s-master-1-71 ~]# kubectl get pods
NAME                                      READY   STATUS    RESTARTS   AGE
my-pod-6fbc98b678-t2gvg                   1/1     Running   0          4m11s
nfs-client-provisioner-5848c9cddc-zkts2   1/1     Running   0          24m[root@k8s-master-1-71 ~]# kubectl get pvc     //查看PVC
NAME     STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
my-pvc   Bound    pvc-51d00de4-52ea-4f1a-988a-bc3e5b7e961e   5Gi        RWX            managed-nfs-storage   3s[root@k8s-master-1-71 ~]# kubectl get pv      //查看PV
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM            STORAGECLASS          REASON   AGE
pvc-51d00de4-52ea-4f1a-988a-bc3e5b7e961e   5Gi        RWX            Delete           Bound      default/my-pvc   managed-nfs-storage            6s# 进入容器查看挂载
[root@k8s-master-1-71 ~]# kubectl exec -it my-pod-6fbc98b678-t2gvg -- bash
root@my-pod-6fbc98b678-t2gvg:/# df -Th | grep pvc-51d00de4-52ea-4f1a-988a-bc3e5b7e961e
192.168.1.72:/ifs/kubernetes/default-my-pvc-pvc-51d00de4-52ea-4f1a-988a-bc3e5b7e961e nfs4      37G  4.4G   33G  12% /usr/share/nginx/html

# 查看NFS服务器的挂载目录,可看到随机生成的PV目录

结论:通过基于NFS提供PVC动态供给,无需再定义单独的PV,也无需为了指定到某个NFS目录手动创建子目录,动态供给会自动帮忙实现PV及随机生成NFS子目录

注意:由于NFS提供PVC动态供给的PV的默认回收策略是 Delete ,所以在删除PVC的同时,也会将PV一起删除;在NFS目录上的文件也一并删除。


测试2:

[root@k8s-master-1-71 ~]# kubectl apply -f my-pvc2.yaml
[root@k8s-node1-1-72 ~]# ls /ifs/kubernetes/
aaaaa  default-my-pvc2-pvc-b7769112-aea6-4533-9e5c-5d14cd67fc65  default-my-pvc-pvc-51d00de4-52ea-4f1a-988a-bc3e5b7e961e[root@k8s-master-1-71 ~]# kubectl delete -f my-pvc2.yaml
[root@k8s-node1-1-72 ~]# ls /ifs/kubernetes/
aaaaa  default-my-pvc-pvc-51d00de4-52ea-4f1a-988a-bc3e5b7e961e

由于考虑数据的重要性,希望删除PVC时保留数据备份

[root@k8s-master-1-71 nfs-external-provisioner]# vi class.yaml
...archiveOnDelete: "True"     //将archiveOnDelete修改为 True,即可在删除PVC时保留备份# 需要删除class.yaml并重新apply应用
[root@k8s-master-1-71 nfs-external-provisioner]# kubectl delete -f class.yaml[root@k8s-master-1-71 nfs-external-provisioner]# kubectl apply -f class.yaml
# 测试删除PVC
[root@k8s-master-1-71 ~]# kubectl delete -f my-pvc.yaml

# 查看NFS服务器的挂载目录,原来的PV目录已重新命名(备份)

0

2、有状态应用部署初探

无状态与有状态:

Deployment控制器设计原则:管理的所有Pod一模一样提供同一个服务(replicas),使用共享存储,之间没有连接关系,也不考虑在哪台Node运 行,可随意扩容和缩容。这种应用称为“无状态”,例如Web服务

在实际的场景中,这并不能满足所有应用,尤其是分布式应用,会部署多个实例,这些实例之间往往有 依赖关系,部署的角色也不一样,例如主从关系、主备关系,这种应用称为“有状态”,例如MySQL主从、Etcd集群

无状态特点:

  1. 每个Pod都一样,且提供同一种服务

  2. Pod之间没有连接关系

  3. 使用共享存储

有状态特点:

  1. 每个Pod不对等,角色属性也不同

  2. Pod之间有连接关系(类似数据库主从)

  3. 每个Pod的数据都是有差异化的,需要独立的存储进行持久化,否则会产生冲突

2.1 StatefulSet 控制器介绍

StatefulSet控制器用于部署有状态应用,满足一些有状态应用的需求:

  • Pod有序的部署、扩容、删除和停止

  • Pod分配一个稳定的且唯一的网络标识

  • Pod分配一个独享的存储

2.2 StatefulSet 部署应用实践

1)稳定的网络ID(域名)

使用 Headless Service(相比普通Service只是将spec.clusterIP定义为None)来维护Pod网络身份。 并且添加 serviceName: “headless-svc” 字段指定 StatefulSet控制器 要使用这个Headless Service。让StatefulSet控制器为其创建每个Pod固定的域名解析地址。

DNS解析名称:<statefulsetName-index>.<service-name> .<namespace-name>.svc.cluster.local

[root@k8s-master-1-71 ~]# kubectl create deployment stateful-pod --image=nginx
[root@k8s-master-1-71 ~]# kubectl expose deployment stateful-pod --port=80 --target-port=80 --dry-run=client -o yaml > headless-svc.yaml
[root@k8s-master-1-71 ~]# kubectl apply -f headless-svc.yaml
apiVersion: v1
kind: Service
metadata:labels:app: headless-svcname: headless-svc
spec:clusterIP: None     //指定SVC的clusterIP为Noneports:- port: 80protocol: TCPtargetPort: 80selector:app: stateful-pod[root@k8s-master-1-71 ~]# kubectl get svc
NAME           TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)   AGE
headless-svc   ClusterIP   None             <none>        80/TCP    8s

补充:Cluster-IP为None,标识该Service为headlress无头服务,这种Service将没有负载均衡转发的功能;

2)稳定的存储

StatefulSet的存储卷 使用VolumeClaimTemplate创建,称为卷申请模板(针对StatefulSet专门设置的类型),当StatefulSet使用VolumeClaimTemplate创建 一个PersistentVolume时,同样也会为每个Pod分配并创建一个编号的PVC(一个PV对应一个PVC)

参考文档:StatefulSets | Kubernetes

参考:https://github.com/lizhenliang/k8s-statefulset


示例:StatefulSet部署(包括无头服务、StatefulSet+卷申请模)

[root@k8s-master-1-71 ~]# kubectl apply -f statefulset-test.yaml
# headless Service 服务配置示例
apiVersion: v1
kind: Service
metadata:name: nginx-headless     //headlessService服务名称,需要和StatefulSet的ServiceName保持一致labels:app: nginx
spec:ports:- port: 80name: webclusterIP: None     //设置cluster-IP为Noneselector:app: nginx     //指定StatefulSet的Pod# StatefulSet 配置示例
---
apiVersion: apps/v1
kind: StatefulSet
metadata:name: web
spec:selector:matchLabels:app: nginx # has to match .spec.template.metadata.labelsserviceName: "nginx-headless"  # 指定 headless Service 服务名称replicas: 3          # by default is 1minReadySeconds: 10  # by default is 0template:metadata:labels:app: nginx # has to match .spec.selector.matchLabelsspec:terminationGracePeriodSeconds: 10containers:- name: nginximage: nginxports:- containerPort: 80name: webvolumeMounts:- name: www-datamountPath: /usr/share/nginx/htmlvolumeClaimTemplates:     //卷申请模板(为StatefulSet专门设置的类型)- metadata:name: www-dataspec:accessModes: [ "ReadWriteOnce" ]storageClassName: "managed-nfs-storage"    # 指定 storageClass(kubectl get sc)resources:requests:storage: 1Gi

查看信息:

[root@k8s-master-1-71 ~]# kubectl get pods
NAME                                      READY   STATUS    RESTARTS      AGE
web-0                                     1/1     Running   0             4m32s
web-1                                     1/1     Running   0             3m31s
web-2                                     1/1     Running   0             2m51s# 查看 headless Service
[root@k8s-master-1-71 ~]# kubectl get svc
NAME             TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx-headless   ClusterIP   None         <none>        80/TCP    9m52s# 查看 headless Service 对应后端的Pod
[root@k8s-master-1-71 ~]# kubectl get ep
NAME                                          ENDPOINTS                                            AGE
nginx-headless                                10.244.114.10:80,10.244.117.50:80,10.244.117.51:80   13m# 查看 PVC
[root@k8s-master-1-71 ~]# kubectl get pvc
NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS          AGE
www-data-web-0   Bound    pvc-c8f06f51-5048-4a43-b822-9f6bcd1bd20c   1Gi        RWO            managed-nfs-storage   10m
www-data-web-1   Bound    pvc-ae9c2a66-0bcb-489e-98d0-0350d480fb68   1Gi        RWO            managed-nfs-storage   9m37s
www-data-web-2   Bound    pvc-50d69a6e-d3ef-42e2-bd13-e6c954d78677   1Gi        RWO            managed-nfs-storage   8m57s# 查看 PV
[root@k8s-master-1-71 ~]# kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                    STORAG                                                 ECLASS          REASON   AGE
pvc-50d69a6e-d3ef-42e2-bd13-e6c954d78677   1Gi        RWO            Delete           Bound      default/www-data-web-2   manage                                                 d-nfs-storage            9m1s
pvc-ae9c2a66-0bcb-489e-98d0-0350d480fb68   1Gi        RWO            Delete           Bound      default/www-data-web-1   manage                                                 d-nfs-storage            9m41s
pvc-c8f06f51-5048-4a43-b822-9f6bcd1bd20c   1Gi        RWO            Delete           Bound      default/www-data-web-0   manage                                                 d-nfs-storage            10m

测试网络:

  • ① 一个普通的headless-service对应Deplyment的域名解析,以及headless-service对应statefulset的域名解析
  • ② 测试 statefulset的域名 网络连通性
# 创建普通的headless-service对应Deplyment的域名解析
[root@k8s-master-1-71 ~]# kubectl create deployment web --image=nginx[root@k8s-master-1-71 ~]# kubectl expose deployment web --port=80 --target-port=80 --dry-run=client -o yaml > test-svc.yaml
[root@k8s-master-1-71 ~]# kubectl apply -f test-svc.yaml(   //修改 clusterIP: None
[root@k8s-master-1-71 ~]# kubectl get svc
NAME             TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
nginx-headless   ClusterIP   None         <none>        80/TCP    30m
web              ClusterIP   None         <none>        80/TCP    6m45s
[root@k8s-master-1-71 ~]# kubectl get ep
NAME                                          ENDPOINTS                                            AGE
nginx-headless                                10.244.114.10:80,10.244.117.50:80,10.244.117.51:80   31m
web                                           10.244.117.52:80                                     7m34s# 创建测试bs镜像pod
[root@k8s-master-1-71 ~]# kubectl run bs --image=busybox:1.28.4 -- sleep 24h
[root@k8s-master-1-71 ~]# kubectl exec -it bs -- sh
/ # nslookup web
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name:      web
Address 1: 10.244.117.52 10-244-117-52.web.default.svc.cluster.local
## DNS解析名称:<PodIP>.<service-name> .<namespace-name>.svc.cluster.local/ # nslookup nginx-headless
Server:    10.96.0.10
Address 1: 10.96.0.10 kube-dns.kube-system.svc.cluster.local
Name:      nginx-headless
Address 1: 10.244.114.10 web-1.nginx-headless.default.svc.cluster.local
Address 2: 10.244.117.50 web-0.nginx-headless.default.svc.cluster.local
Address 3: 10.244.117.51 web-2.nginx-headless.default.svc.cluster.local
## DNS解析名称:<statefulsetName-index>.<service-name> .<namespace-name>.svc.cluster.local[root@k8s-master-1-71 ~]# kubectl exec -it bs -- sh
/ # ping web-1.nginx-headless.default.svc.cluster.local
PING web-1.nginx-headless.default.svc.cluster.local (10.244.114.10): 56 data bytes
64 bytes from 10.244.114.10: seq=0 ttl=62 time=0.539 ms
64 bytes from 10.244.114.10: seq=1 ttl=62 time=0.474 ms

测试存储:

[root@k8s-node1-1-72 ~]# ls /ifs/kubernetes/     //自动生成独立的PV,
default-www-data-web-1-pvc-ae9c2a66-0bcb-489e-98d0-0350d480fb68
default-www-data-web-2-pvc-50d69a6e-d3ef-42e2-bd13-e6c954d78677
default-www-data-web-0-pvc-c8f06f51-5048-4a43-b822-9f6bcd1bd20c[root@k8s-master-1-71 ~]# kubectl exec -it web-0 -- bash
root@web-0:/# cd /usr/share/nginx/html/ ; echo 111 > index.html[root@k8s-node1-1-72 ~]# cat /ifs/kubernetes/default-www-data-web-0-pvc-c8f06f51-5048-4a43-b822-9f6bcd1bd20c/index.html
111
[root@k8s-node1-1-72 ~]# ls /ifs/kubernetes/default-www-data-web-2-pvc-50d69a6e-d3ef-42e2-bd13-e6c954d78677///目录为空
## 因此Statefulset控制器创建的每个Pod的存储为独立的

思考:在有状态环境部署下,分布式应用组件的角色不同,每个Pod也不相同,而在上述 StatefulSet部署示例中,假设运行一个etcd数据集群,有3个副本且肯定只有一个指定的镜像(镜像相同),如何区分这3个Pod的角色

解答:通过配置文件区分(每个节点的名称、IP、存储目录区分),需要保证启动的3个副本容器,每一个都能按照自己的角色(配置文件)进行启动。而配置文件则需要根据statefulset控制器部署的Pod容器编号进行区分,判断当前启动的是第几个容器,就使用哪个配置文件进行启动,从而实现Pod的角色。例如:etcd-0.conf、etcd-1.conf、etcd-2.conf

参考同类型的案例:运行 ZooKeeper,一个分布式协调系统 | Kubernetes


— StatefulSet 与 Deployment区别:有身份的!

身份三要素(唯一性):

  • 域名

  • 主机名

  • 存储(PVC)

3、应用程序数据存储

  • ConfigMap:存储配置文件
  • Secret:存储敏感数据

3.1 ConfigMap 存储应用配置

创建ConfigMap后,数据实际会持久化存储在K8s中Etcd,然后通过创建Pod时引用该数据。

应用场景:应用程序配置(类似配置管理中心如apollo、nacos)

Pod使用configmap数据有两种方式:

  1. 变量注入到容器里

  2. 数据卷挂载

两种数据类型:

  • 键值
  • 多行数据

相关命令:

创建ConfigMapkubectl create configmap --from-file=path/to/bar

kubectl create configmap my-configmap --from-literal=abc=123 --from-literal=cde=456

查看ConfigMap:kubectl get configmap

官方:ConfigMap | Kubernetes


ConfigMap 配置示例:

1)部署ConfigMap的YAML

[root@k8s-master-1-71 ~]# kubectl apply -f configmap-demo.yaml
apiVersion: v1
kind: ConfigMap
metadata:name: configmap-demo
data:abc: "123"     //key/value键值方式(字符串需要“”)cde: "456"redis.properties: |     //多行数据方式port: 6379host: 192.168.31.10# 查看创建的ConfigMap
[root@k8s-master-1-71 ~]# kubectl get configmap
NAME               DATA   AGE
configmap-demo     3      7s
## 备注:DATA显示为3,表示存储了3个数据,包括abc、cde、redis.properties

2)部署Pod的YAML

[root@k8s-master-1-71 ~]# kubectl apply -f configmap-pod.yaml
apiVersion: v1
kind: Pod
metadata:name: configmap-demo-pod
spec:containers:- name: nginximage: nginx# 定义环境变量env:- name: ABCD # 容器中的变量,请注意这里和 ConfigMap 中的键名是不一样的valueFrom:configMapKeyRef:name: configmap-demo      # 这个值来自 ConfigMapkey: abc     # 需要取值的键- name: CDEFvalueFrom:configMapKeyRef:name: configmap-demokey: cdevolumeMounts:- name: configmountPath: "/config"   # 挂载到哪个目录readOnly: true         # 挂载文件为只读volumes:# 在 Pod 级别设置卷,然后将其挂载到 Pod 内的容器中- name: configconfigMap:name: configmap-demo    # 提供想要挂载的 ConfigMap 的名字# 来自 ConfigMap 的一组键,将被创建为文件items:- key: "redis.properties"   # configmap-demo的Keypath: "redis.conf"     # 挂载后的名字

测试:进入pod中验证是否注入变量和挂载

[root@k8s-master-1-71 ~]# kubectl exec -it configmap-test-pod-5dff5f64c6-ncmnd -- bash
root@configmap-test-pod-5dff5f64c6-ncmnd:/# echo $ABCD
123
root@configmap-test-pod-5dff5f64c6-ncmnd:/# echo $CDEF
456
root@configmap-test-pod-5dff5f64c6-ncmnd:/# cat /config/redis.conf
port: 6379
host: 192.168.31.10

3.2 Secret 存储敏感信息

与ConfigMap类似,区别在于Secret主要存储敏感数据,所有的数据要经过base64编码。

应用场景:凭据

kubectl create secret 支持三种数据类型:

  • docker-registry:存储镜像仓库认证信息(镜像仓库需要账户密码认证)
  • generic:存储用户名、密码:
    • 例如:kubectl create secret generic ssh-key-secret --from-file=ssh-privatekey=/path/to/.ssh/id_rsa --from-file=ssh-publickey=/path/to/.ssh/id_rsa.pub
    • 例如:kubectl create secret generic my-secret --from-literal=username=produser --from-literal=password=123456
  • tls:存储证书:kubectl create secret tls --cert=path/to/tls.cert --key=path/to/tls.key
    • 例如 :05 K8s网络 使用ingress - tls 部署https

相关命令:

  • 查看Secret:kubectl get secret

相关base64命令:

  • # echo -n 'admin' | base64          //加密(YWRtaW4=)
  • # echo YWRtaW4= | base64 -d  //解密(admin)

补充: 在Secret 配置文件中未作显式设定时,默认的 Secret 类型是 Opaque


命令示例:

[root@k8s-master ~]# kubectl create secret generic my-secret --from-literal=username=admin --from-literal=password=123456

YAML示例:

1)将用户名密码进行编码

[root@k8s-master-1-71 ~]# echo -n 'admin' | base64   //用户名加密
YWRtaW4=
[root@k8s-master-1-71 ~]# echo -n '123456' | base64  //密码加密
MTIzNDU2

2)部署Secret的YAML

[root@k8s-master-1-71 ~]# vi secret-demo.yaml
apiVersion: v1
kind: Secret
metadata:name: my-secret
type: Opaque    //默认的 Secret 类型是 Opaque
data:username: YWRtaW4=password: MTIzNDU2# 查看创建的ConfigMap
[root@k8s-master-1-71 ~]# kubectl get secret
NAME              TYPE                DATA   AGE
secret-demo       Opaque              2      5s

TYPE类型参考:Secrets | Kubernetes

3)部署Pod的YAML

apiVersion: v1
kind: Pod
metadata:name: secret-demo-pod
spec:containers:- name: nginximage: nginx env:- name: USERNAME    # 容器中的变量valueFrom:secretKeyRef:name: secret-demokey: username- name: PASSWORD    # 容器中的变量valueFrom:secretKeyRef:name: secret-demokey: passwordvolumeMounts:- name: configmountPath: "/config"readOnly: truevolumes:- name: configsecret:secretName: secret-demoitems:- key: username path: username.txt- key: passwordpath: password.txt

备注:Pod使用Secret数据与ConfigMap方式一样

测试:进入pod中验证是否注入变量和挂载

[root@k8s-master-1-71 ~]# kubectl exec -it secret-test-pod-6554b98c8-8hrbb -- bash
root@secret-test-pod-6554b98c8-8hrbb:/# echo $USERNAME
admin
root@secret-test-pod-6554b98c8-8hrbb:/# echo $PASSWORD
123456
root@secret-test-pod-6554b98c8-8hrbb:/# ls /config/
password.txt  username.txt

课后作业

1、创建一个secret,并创建2个pod,pod1挂载该secret,路径为/secret,pod2使用环境变量引用该 secret,该变量的环境变量名为ABC

  • secret名称:my-secret
  • pod1名称:pod-volume-secret
  • pod2名称:pod-env-secret

2、 创建一个pv,再创建一个pod使用该pv

  • 容量:5Gi
  • 访问模式:ReadWriteOnce

3、创建一个pod并挂载数据卷,不可以用持久卷

  • 卷来源:emptyDir、hostPath任意
  • 挂载路径:/data

4、将pv按照名称、容量排序,并保存到/opt/pv文件

小结

本篇为 【Kubernetes CKA认证 Day7】的学习笔记,希望这篇笔记可以让您初步了解到 数据卷与数据持久卷、有状态应用部署、应用程序数据存储案例 ;课后还有扩展实践,不妨跟着我的笔记步伐亲自实践一下吧!


Tip:毕竟两个人的智慧大于一个人的智慧,如果你不理解本章节的内容或需要相关笔记、视频,可私信小安,请不要害羞和回避,可以向他人请教,花点时间直到你真正的理解。

相关文章:

CKA认证 | Day7 K8s存储

第七章 Kubernetes存储 1、数据卷与数据持久卷 为什么需要数据卷&#xff1f; 容器中的文件在磁盘上是临时存放的&#xff0c;这给容器中运行比较重要的应用程序带来一些问题。 问题1&#xff1a;当容器升级或者崩溃时&#xff0c;kubelet会重建容器&#xff0c;容器内文件会…...

ArcGIS Pro地形图四至角图经纬度标注与格网标注

今天来看看ArcGIS Pro 如何在地形图上设置四至角点的经纬度。方里网标注。如下图的地形图左下角经纬度标注。 如下图方里网的标注 如下为本期要介绍的例图&#xff0c;如下&#xff1a; 图片可点击放大 接下来我们来介绍一下 推荐学习&#xff1a;GIS入门模型构建器Arcpy批量…...

策略模式以及优化

使用场景 在一个条件语句中又包含了多个条件语句 具体策略类会过多 把抽象策略和具体策略放在一个枚举类里。 方法 exe() 相当于抽象策略&#xff0c;而A和B就相当于实现了抽象策略的具体策略 这样就只需要一个枚举类就可以解决具体策略类过多的问题 public enum Strategy {A{O…...

linux自动化一键批量检查主机端口

1、准备 我们可以使用下面命令关闭一个端口 sudo iptables -A INPUT -p tcp --dport 端口号 -j DROP我关闭的是22端口&#xff0c;各位可以关其它的或者打开其它端口测试&#xff0c;谨慎关闭22端口&#xff01;不然就会像我下面一样握手超时&#x1f62d;&#x1f62d;&…...

Vue3入门(9)

1. 【 replace属性】 作用&#xff1a;控制路由跳转时操作浏览器历史记录的模式。 浏览器的历史记录有两种写入方式&#xff1a;分别为push和replace&#xff1a; - push是追加历史记录&#xff08;默认值&#xff09;。 - replace是替换当前记录。 . 开启replace模式&#xff…...

《人工智能如何加速药物研发进程:从新药发现到临床试验的突破》

在当今医药领域&#xff0c;药物研发的复杂性和高成本使得新药的推出面临诸多挑战。而人工智能&#xff08;AI&#xff09;正以其强大的能力为药物研发带来新的契机&#xff0c;助力加速新药发现和临床试验过程。 新药发现阶段 靶点识别与筛选 药物研发的第一步是确定药物作…...

“鼎和财险一体化数据安全管控实践”入选信通院金融领域优秀案例

近日&#xff0c;由中国信通院举办的深度观察报告会系列论坛在京召开。在数字生态治理分论坛上&#xff0c;2024年度首期“磐安”优秀案例——六大行业应用优秀案例遴选结果发布&#xff0c;由北京原点数安科技有限公司与鼎和财产保险股份有限公司联合申报的“鼎和财险一体化数…...

探索多模态大语言模型(MLLMs)的推理能力

探索多模态大语言模型&#xff08;MLLMs&#xff09;的推理能力 Multimodal Large Language Models (MLLMs) flyfish 原文&#xff1a;Exploring the Reasoning Abilities of Multimodal Large Language Models (MLLMs): A Comprehensive Survey on Emerging Trends in Mult…...

72 mysql 的客户端和服务器交互 returnGeneratedKeys

前言 这里主要是针对如下 情况的讨论 比如我们 tz_zone 表有一个自增长的主键 id, 然后 客户端这边可以通过 PreparedStatement 相关的 api 来获取 服务端这边生成的自增长的 id 我们这里 来看一下 这个流程, 整体的 客户端 和 服务器的交互上面来看 问题来自于 主题&…...

【连续学习之SSL算法】2018年论文Selfless sequential learning

1 介绍 年份&#xff1a;2018 期刊&#xff1a; arXiv preprint Aljundi R, Rohrbach M, Tuytelaars T. Selfless sequential learning[J]. arXiv preprint arXiv:1806.05421, 2018. 本文提出了一种名为SLNID&#xff08;Sparse coding through Local Neural Inhibition and…...

【蓝桥杯——物联网设计与开发】拓展模块5 - 光敏/热释电模块

目录 一、光敏/热释电模块 &#xff08;1&#xff09;资源介绍 &#x1f505;原理图 &#x1f505;AS312 &#x1f319;简介 &#x1f319;特性 &#x1f505;LDR &#xff08;2&#xff09;STM32CubeMX 软件配置 &#xff08;3&#xff09;代码编写 &#xff08;4&#x…...

数字IC后端设计实现十大精华主题分享

今天小编给大家分享下吾爱IC社区星球上周十大后端精华主题。 Q1:星主&#xff0c;请教个问题&#xff0c;长tree的时候发现这个scan的tree 的skew差不多400p&#xff0c;我高亮了整个tree的schematic&#xff0c;我在想是不是我在这一系列mux前边打断&#xff0c;设置ignore p…...

高质量配音如何影响游戏的受欢迎度

在游戏行业中&#xff0c;创造沉浸式、引人入胜且令人难忘的体验往往决定了游戏的成功或失败。在影响游戏流行度的众多因素中&#xff0c;配音脱颖而出&#xff0c;成为将叙事与玩家互动连接起来的重要元素。高质量的配音将游戏中的对白转化为游戏的活跃部分&#xff0c;让玩家…...

QWidget应用封装为qt插件,供其他qt应用调用

在之前的文章中,有介绍通过QProcess的方式启动QWidget应用,然后将其窗口嵌入到其他的qt应用中,作为子窗口使用.这篇文章主要介绍qt插件的方式将QWidget应用的窗口封装为插件,然后作为其他Qt应用中的子窗口使用. 插件优点: 与主程序为同一个进程,免去了进程间繁琐的通信方式,…...

UE(虚幻)学习(四) 第一个C++类来控制小球移动来理解蓝图和脚本如何工作

UE5视频看了不少&#xff0c;但基本都是蓝图如何搞&#xff0c;或者改一下属性&#xff0c;理解UE系统现有组件使用的。一直对C脚本和蓝图之间的关系不是很理解&#xff0c;看到一个视频讲的很好&#xff0c;我也做笔记记录一下。 我的环境是UE5.3.2. 创建UE空项目 我们创建…...

使用FreeNAS软件部署ISCSI的SAN架构存储(IP-SAN)练习题

一&#xff0c;实验用到工具分别为&#xff1a; VMware虚拟机&#xff0c;安装教程&#xff1a;VMware Workstation Pro 17 安装图文教程 FreeNAS系统&#xff0c;安装教程&#xff1a;FreeNAS-11.2-U4.1安装教程2024&#xff08;图文教程&#xff09; 二&#xff0c;新建虚…...

Sql Sqserver 相关知识总结

Sql Sqserver 相关知识总结 文章目录 Sql Sqserver 相关知识总结前言优化语句查询&#xff08;select&#xff09;条件过滤&#xff08;Where&#xff09;分组处理&#xff08;GROUP BY&#xff09;模糊查询&#xff08;like&#xff09;包含&#xff08;in&#xff09;合集&am…...

面试题整理17----K8s中request和limit资源限制是如何实现的

面试题整理17----K8s中request和limit资源限制是如何实现的 1. 资源请求&#xff08;Resource Requests&#xff09;2. 资源限制&#xff08;Resource Limits&#xff09;3. 总结 在Kubernetes&#xff08;K8s&#xff09;中&#xff0c;Pod的资源限制&#xff08;Resource Lim…...

Spring Boot @Conditional注解

在Spring Boot中&#xff0c;Conditional 注解用于条件性地注册bean。这意味着它可以根据某些条件来决定是否应该创建一个特定的bean。这个注解可以放在配置类或方法上&#xff0c;并且它会根据提供的一组条件来判断是否应该实例化对应的组件。 要使用 Conditional注解时&#…...

jpeg文件学习

相关最全的一篇文章链接&#xff1a;https://www.cnblogs.com/wtysos11/p/14089482.html YUV基础知识 Y表示亮度分量&#xff1a;如果只显示Y的话&#xff0c;图像看起来会是一张黑白照。 U&#xff08;Cb&#xff09;表示色度分量&#xff1a;是照片蓝色部分去掉亮度&#x…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

day52 ResNet18 CBAM

在深度学习的旅程中&#xff0c;我们不断探索如何提升模型的性能。今天&#xff0c;我将分享我在 ResNet18 模型中插入 CBAM&#xff08;Convolutional Block Attention Module&#xff09;模块&#xff0c;并采用分阶段微调策略的实践过程。通过这个过程&#xff0c;我不仅提升…...

自然语言处理——循环神经网络

自然语言处理——循环神经网络 循环神经网络应用到基于机器学习的自然语言处理任务序列到类别同步的序列到序列模式异步的序列到序列模式 参数学习和长程依赖问题基于门控的循环神经网络门控循环单元&#xff08;GRU&#xff09;长短期记忆神经网络&#xff08;LSTM&#xff09…...

Web后端基础(基础知识)

BS架构&#xff1a;Browser/Server&#xff0c;浏览器/服务器架构模式。客户端只需要浏览器&#xff0c;应用程序的逻辑和数据都存储在服务端。 优点&#xff1a;维护方便缺点&#xff1a;体验一般 CS架构&#xff1a;Client/Server&#xff0c;客户端/服务器架构模式。需要单独…...

Qt Quick Controls模块功能及架构

Qt Quick Controls是Qt Quick的一个附加模块&#xff0c;提供了一套用于构建完整用户界面的UI控件。在Qt 6.0中&#xff0c;这个模块经历了重大重构和改进。 一、主要功能和特点 1. 架构重构 完全重写了底层架构&#xff0c;与Qt Quick更紧密集成 移除了对Qt Widgets的依赖&…...

数据挖掘是什么?数据挖掘技术有哪些?

目录 一、数据挖掘是什么 二、常见的数据挖掘技术 1. 关联规则挖掘 2. 分类算法 3. 聚类分析 4. 回归分析 三、数据挖掘的应用领域 1. 商业领域 2. 医疗领域 3. 金融领域 4. 其他领域 四、数据挖掘面临的挑战和未来趋势 1. 面临的挑战 2. 未来趋势 五、总结 数据…...

CSS(2)

文章目录 Emmet语法快速生成HTML结构语法 Snipaste快速生成CSS样式语法快速格式化代码 快捷键&#xff08;VScode&#xff09;CSS 的复合选择器什么是复合选择器交集选择器后代选择器(重要)子选择器(重要&#xff09;并集选择器(重要&#xff09;**链接伪类选择器**focus伪类选…...

React 样式方案与状态方案初探

React 本身只提供了基础 UI 层开发范式&#xff0c;其他特性的支持需要借助相关社区方案实现。本文将介绍 React 应用体系中样式方案与状态方案的主流选择&#xff0c;帮助开发者根据项目需求做出合适的选择。 1. React 样式方案 1.1. 内联样式 (Inline Styles) 通过 style …...

【Redis】Redis 的持久化策略

目录 一、RDB 定期备份 1.2 触发方式 1.2.1 手动触发 1.2.2.1 自动触发 RDB 持久化机制的场景 1.2.2.2 检查是否触发 1.2.2.3 线上运维配置 1.3 检索工具 1.4 RDB 备份实现原理 1.5 禁用 RDB 快照 1.6 RDB 优缺点分析 二、AOF 实时备份 2.1 配置文件解析 2.2 开启…...

Redis:常用数据结构 单线程模型

&#x1f308; 个人主页&#xff1a;Zfox_ &#x1f525; 系列专栏&#xff1a;Redis &#x1f525; 常用数据结构 &#x1f433; Redis 当中常用的数据结构如下所示&#xff1a; Redis 在底层实现上述数据结构的过程中&#xff0c;会在源码的角度上对于上述的内容进行特定的…...