第18关 K8s数据安全无忧——持久化存储详解

------> 课程视频同步分享在今日头条和B站

大家好,我是博哥爱运维,本期课程将深入解析Kubernetes的持久化存储机制,包括PV、PVC、StorageClass等的工作原理、使用场景、最佳实践等,帮您构建稳定可靠的状态存储,确保应用和数据 100% 安全。

Volume

我们这里先来聊聊K8s的存储模型Volume,来实践下如何将各种持久化的存储映射到Pod中的容器。

在我们上面的实战中,大家如果细心的话,会发现把nginx服务pod内的默认页面改了,但当重启pod后,这个页面又恢复成nginx容器初始的状态了,所以这里要和大家说的是,在没有配置持久化存储前,任何新增的数据在pod发生重启时都是无法保留的,而在K8s上,Pod的生命周期可能是很短,它们会被频繁地销毁和创建,自然在容器销毁时,里面运行时新增的数据,如修改的配置及日志文件等也会被清除。

那么怎么解决这一现象呢,我们可以用K8s volume来持久化保存容器的数据,Volume的生命周期独立于容器,Pod中的容器可能被销毁重建,但Volume会被保留。

本质上,K8s volume是一个目录,这点和Docker volume差不多,当Volume被mount到Pod上,这个Pod中的所有容器都可以访问这个volume,在生产场景中,我们常用的类型有这几种:

  • emptyDir
  • hostPath
  • PersistentVolume(PV) & PersistentVolumeClaim(PVC)
  • StorageClass
emptyDir

我们先开始讲讲emptyDir,它是最基础的Volume类型,pod内的容器发生重启不会造成emptyDir里面数据的丢失,但是当pod被重启后,emptyDir数据会丢失,也就是说emptyDir与pod的生命周期是一致的,那么大家可能有个疑问,这个之前讲的没有配置它也没什么区别呀,实际上在某些时候,它的作用还是挺大的,在生产中它的最实际实用是提供Pod内多容器的volume数据共享,下面我会用一个实际的生产者,消费者的例子来演示下emptyDir的作用,相信大家动动手就会理解得更快了

# cat web.yamlapiVersion: apps/v1
kind: Deployment
metadata:labels:app: webname: webnamespace: default
spec:replicas: 1selector:matchLabels:app: webtemplate:metadata:labels:app: webspec:containers:- image: nginx:1.21.6name: nginxresources:limits:cpu: "50m"memory: 20Mirequests:cpu: "50m"memory: 20MivolumeMounts:         # 准备将pod的目录进行卷挂载- name: html-files  # 自定个名称,容器内可以类似这样挂载多个卷mountPath: "/usr/share/nginx/html"- name: busybox       # 在pod内再跑一个容器,每秒把当时时间写到nginx默认页面上image: registry.cn-shanghai.aliyuncs.com/acs/busybox:v1.29.2args:- /bin/sh- -c- >while :; doif [ -f /html/index.html ];thenecho "[$(date +%F\ %T)] hello" > /html/index.htmlsleep 1elsetouch /html/index.htmlfidonevolumeMounts:- name: html-files  # 注意这里的名称和上面nginx容器保持一样,这样才能相互进行访问mountPath: "/html"  # 将数据挂载到当前这个容器的这个目录下volumes:- name: html-files   # 最后定义这个卷的名称也保持和上面一样emptyDir:          # 这就是使用emptyDir卷类型了medium: Memory   # 这里将文件写入内存中保存,这样速度会很快,配置为medium: "" 就是代表默认的使用本地磁盘空间来进行存储sizeLimit: 10Mi  # 因为内存比较珍贵,注意限制使用大小

更新这个web的配置

# kubectl apply -f web.yaml 
deployment.apps/web configured# 可以看到READY下面容器数量变为2了
# kubectl get pod
NAME                    READY   STATUS    RESTARTS   AGE
......
web-5bf769fdfc-44p7h    2/2     Running   0          2m4s# 接着创建一个service来请求测试下
kubectl expose deployment web --port 80 --target-port 80# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
......
web          ClusterIP   10.68.229.231   <none>        80/TCP    4h36m# 可以看到每次访问都是被写入当前最新时间的页面内容
[root@node-1 ~]# curl 10.68.229.231
[2020-11-27 07:21:34] hello
[root@node-1 ~]# curl 10.68.229.231
[2020-11-27 07:21:35] hello
[root@node-1 ~]# curl 10.68.229.231
[2020-11-27 07:21:36] hello
[root@node-1 ~]# curl 10.68.229.231
[2020-11-27 07:21:38] hello

我们来探究下原理

# 下面这个是docker容器运行时的记录
# 看下这个web的pod的描述信息
# kubectl describe pod web-5bf769fdfc-44p7h 
......
Node:         10.0.1.203/10.0.1.203     # 找到这个pod运行在哪个node上
......
Containers:nginx:Container ID:   docker://c1482a15f756ff3bc089973ec942a4e60f7ec34674ab8435a47a94d4b93411a7   # 找到pod内nginx容器的ID
......busybox:Container ID:  docker://ecedf3b0ffa6b5101e84a21f8dbf6188179875b5db61980bc93b65195f558c6f   # 找到pod内busybox容器的ID# 我们登陆10.0.1.203 这台node,查看pod内这两个容器的volume挂载信息,我们发现两个容器都 mount 了同一个目录
[root@node-3 ~]# docker inspect c1482a15f756ff3bc089973ec942a4e60f7ec34674ab8435a47a94d4b93411a7|grep volume|grep html"/var/lib/container/kubelet/pods/cc4832f3-c73c-479f-9088-12b079ff4608/volumes/kubernetes.io~empty-dir/html-files:/usr/share/nginx/html","Source": "/var/lib/container/kubelet/pods/cc4832f3-c73c-479f-9088-12b079ff4608/volumes/kubernetes.io~empty-dir/html-files",[root@node-3 ~]# docker inspect ecedf3b0ffa6b5101e84a21f8dbf6188179875b5db61980bc93b65195f558c6f|grep volume|grep html"/var/lib/container/kubelet/pods/cc4832f3-c73c-479f-9088-12b079ff4608/volumes/kubernetes.io~empty-dir/html-files:/html","Source": "/var/lib/container/kubelet/pods/cc4832f3-c73c-479f-9088-12b079ff4608/volumes/kubernetes.io~empty-dir/html-files",    # Containerd运行时日志目录: 
# ll /var/log/containers/|grep web
hostPath

hostPath Volume 的作用是将容器运行的node上已经存在文件系统目录给mount到pod的容器。在生产中大部分应用是是不会直接使用hostPath的,因为我们并不关心Pod在哪台node上运行,而hostPath又恰好增加了pod与node的耦合,限制了pod的使用,这里我们只作一下了解,知道有这个东西存在即可,一般只是一些安装服务会用到,比如下面我截取了网络插件calico的部分volume配置:

    volumeMounts:- mountPath: /host/drivername: flexvol-driver-host
......volumes:
......- hostPath:path: /usr/libexec/kubernetes/kubelet-plugins/volume/exec/nodeagent~udstype: DirectoryOrCreatename: flexvol-driver-host
PersistentVolume(PV) & PersistentVolumeClaim(PVC)

现在讲Volume里面在生产中用的最多的PersistentVolume(持久卷,简称PV)和 PersistentVolumeClaim(持久卷消费,简称PVC),通常在企业中,Volume是由存储系统的管理员来维护,他们来提供pv,pv具有持久性,生命周期独立于Pod;Pod则是由应用的开发人员来维护,如果要进行一卷挂载,那么就写一个pvc来消费pv就可以了,K8s会查找并提供满足条件的pv。

有了pvc,我们在K8s进行卷挂载就只需要考虑要多少容量了,而不用关心真正的空间是用什么存储系统做的等一些底层细节信息,pv这些只有存储管理员才应用去关心它。

K8s支持多种类型的pv,我们这里就以生产中常用的NFS来作演示(在云上的话就用NAS),生产中如果对存储要求不是太高的话,建议就用NFS,这样出问题也比较容易解决,如果有性能需求,可以看看rook的ceph,以及Rancher的Longhorn,这些我都在生产中用过,如果有需求的同学可以在评论区留言,我会单独做课程来讲解。

开始部署NFS-SERVER

# 我们这里在10.0.1.201上安装(在生产中,大家要提供作好NFS-SERVER环境的规划)
# yum -y install nfs-utils
# ubuntu安装NFS服务端
# apt-get install nfs-kernel-server -y# 创建NFS挂载目录
# mkdir /nfs_dir
# chown nobody.nogroup /nfs_dir# 修改NFS-SERVER配置
# echo '/nfs_dir *(rw,sync,no_root_squash)' > /etc/exports# 重启服务
# systemctl restart rpcbind.service
# systemctl restart nfs-kernel-server.service 
# systemctl restart nfs-utils.service 
# systemctl restart nfs-server.service # 增加NFS-SERVER开机自启动
# systemctl enable nfs-server.service 
Created symlink from /etc/systemd/system/multi-user.target.wants/nfs-server.service to /usr/lib/systemd/system/nfs-server.service.# 验证NFS-SERVER是否能正常访问
## 注:如果查看不到目录,可以到另一机器上挂载试试  
## root@node-2:~# mount.nfs 10.0.1.201:/nfs_dir /mnt/
# showmount -e 10.0.1.201                 
Export list for 10.0.1.201:
/nfs_dir *

创建基于NFS的PV

首先在NFS-SERVER的挂载目录里面创建一个目录

# mkdir /nfs_dir/pv1

接着准备好pv的yaml配置,保存为pv1.yaml

# cat pv1.yaml 
apiVersion: v1
kind: PersistentVolume
metadata:name: pv1labels:type: test-claim    # 这里建议打上一个独有的标签,方便在多个pv的时候方便提供pvc选择挂载
spec:capacity:storage: 1Gi     # <----------  1accessModes:- ReadWriteOnce     # <----------  2persistentVolumeReclaimPolicy: Recycle     # <----------  3storageClassName: nfs     # <----------  4nfs:path: /nfs_dir/pv1     # <----------  5server: 10.0.1.201
  1. capacity 指定 PV 的容量为 1G。

  2. accessModes 指定访问模式为 ReadWriteOnce,支持的访问模式有:
    ReadWriteOnce – PV 能以 read-write 模式 mount 到单个节点。
    ReadOnlyMany – PV 能以 read-only 模式 mount 到多个节点。
    ReadWriteMany – PV 能以 read-write 模式 mount 到多个节点。

  3. persistentVolumeReclaimPolicy 指定当 PV 的回收策略为 Recycle,支持的策略有:
    Retain – 需要管理员手工回收。
    Recycle – 清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*。
    Delete – 删除 Storage Provider 上的对应存储资源,例如 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等。

  4. storageClassName 指定 PV 的 class 为 nfs。相当于为 PV 设置了一个分类,PVC 可以指定 class 申请相应 class 的 PV。

  5. 指定 PV 在 NFS 服务器上对应的目录,这里注意,我测试的时候,需要手动先创建好这个目录并授权好,不然后面挂载会提示目录不存在 mkdir /nfsdata/pv1 && chown -R nobody.nogroup /nfsdata 。

创建这个pv

# kubectl apply -f pv1.yaml 
persistentvolume/pv1 created# STATUS 为 Available,表示 pv1 就绪,可以被 PVC 申请
# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS      CLAIM   STORAGECLASS   REASON   AGE
pv1    1Gi        RWO            Recycle          Available           nfs                     4m45s

接着准备PVC的yaml,保存为pvc1.yaml

# cat pvc1.yaml 
kind: PersistentVolumeClaim
apiVersion: v1
metadata:name: pvc1
spec:accessModes:- ReadWriteOnceresources:requests:storage: 1GistorageClassName: nfsselector:matchLabels:type: test-claim

创建这个pvc

# kubectl apply -f pvc1.yaml          
persistentvolumeclaim/pvc1 created# 看下pvc的STATUS为Bound代表成功挂载到pv了
# kubectl get pvc           
NAME   STATUS   VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc1   Bound    pv1      1Gi        RWO            nfs            2s# 这个时候再看下pv,STATUS也是Bound了,同时CLAIM提示被default/pvc1消费
# kubectl get pv
NAME   CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM          STORAGECLASS   REASON   AGE
pv1    1Gi        RWO            Recycle          Bound    default/pvc1   nfs  

下面我们准备pod服务来挂载这个pvc,这里就以上面最开始演示用的nginx的deployment的yaml配置来作修改

# cat nginx.yaml 
apiVersion: v1
kind: Service
metadata:labels:app: nginxname: nginx
spec:ports:- port: 80protocol: TCPtargetPort: 80selector:app: nginx---
apiVersion: apps/v1
kind: Deployment
metadata:labels:app: nginxname: nginx
spec:replicas: 1selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- image: nginx:1.21.6name: nginxvolumeMounts:    # 我们这里将nginx容器默认的页面目录挂载- name: html-filesmountPath: "/usr/share/nginx/html"volumes:- name: html-filespersistentVolumeClaim:  # 卷类型使用pvc,同时下面名称处填先创建好的pvc1claimName: pvc1

更新配置

# kubectl apply -f nginx.yaml 
service/nginx unchanged
deployment.apps/nginx configured# 我们看到新pod已经在创建了
# kubectl get pod
NAME                     READY   STATUS              RESTARTS   AGE
nginx-569546db98-4nmmg   0/1     ContainerCreating   0          5s
nginx-f89759699-6vgr8    1/1     Running             1          23h
web-5bf769fdfc-44p7h     2/2     Running             0          113m# 我们这里直接用svc地址测试一下
# kubectl get svc
NAME         TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
kubernetes   ClusterIP   10.68.0.1       <none>        443/TCP   23h
nginx        ClusterIP   10.68.238.54    <none>        80/TCP    23h
web          ClusterIP   10.68.229.231   <none>        80/TCP    6h27m# 咦,这里为什么是显示403了呢,注意,卷挂载后会把当前已经存在这个目录的文件给覆盖掉,这个和传统机器上的磁盘目录挂载道理是一样的
[root@node-1 ~]# curl 10.68.238.54
<html>
<head><title>403 Forbidden</title></head>
<body>
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.19.5</center>
</body>
</html># 我们来自己创建一个index.html页面
# echo 'hello, world!' > /nfs_dir/pv1/index.html# 再请求下看看,已经正常了
# curl 10.68.238.54                             
hello, world!# 我们来手动删除这个nginx的pod,看下容器内的修改是否是持久的呢?
# kubectl delete pod nginx-569546db98-4nmmg 
pod "nginx-569546db98-4nmmg" deleted# 等待一会,等新的pod被创建好
# kubectl get pod
NAME                     READY   STATUS    RESTARTS   AGE
nginx-569546db98-99qpq   1/1     Running   0          45s# 再测试一下,可以看到,容器内的修改现在已经被持久化了
# curl 10.68.238.54        
hello, world!# 后面我们再想修改有两种方式,一个是exec进到pod内进行修改,还有一个是直接修改挂载在NFS目录下的文件
# echo 111 > /nfs_dir/pv1/index.html
# curl 10.68.238.54  
111

下面讲下如何回收PVC以及PV

# 这里删除时会一直卡着,我们按ctrl+c看看怎么回事
# kubectl delete pvc pvc1 
persistentvolumeclaim "pvc1" deleted
^C# 看下pvc发现STATUS是Terminating删除中的状态,我分析是因为服务pod还在占用这个pvc使用中
# kubectl get pvc
NAME   STATUS        VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc1   Terminating   pv1      1Gi        RWO            nfs            21m# 先删除这个pod
# kubectl delete pod nginx-569546db98-99qpq 
pod "nginx-569546db98-99qpq" deleted# 再看先删除的pvc已经没有了
# kubectl get pvc
No resources found in default namespace.# 根据先前创建pv时的数据回收策略为Recycle – 清除 PV 中的数据,这时果然先创建的index.html已经被删除了,在生产中要尤其注意这里的模式,注意及时备份数据,注意及时备份数据,注意及时备份数据
# ll /nfs_dir/pv1/
total 0# 虽然此时pv是可以再次被pvc来消费的,但根据生产的经验,建议在删除pvc时,也同时把它消费的pv一并删除,然后再重启创建都是可以的

我们先提到了K8s的设计是,pv交给存储管理员来管理,我们是管用pvc来消费就好,但这里我们实际还是得一起管理pv和pvc,在实际工作中,我们(存储管理员)可以提前配置好pv的动态供给StorageClass,来根据pvc的消费动态生成pv

StorageClass

我这是直接拿生产中用的实例来作演示,利用nfs-client-provisioner来生成一个基于nfs的StorageClass,部署配置yaml配置如下,保持为nfs-sc.yaml:

apiVersion: v1
kind: ServiceAccount
metadata:name: nfs-client-provisionernamespace: kube-system---
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: ["list", "watch", "create", "update", "patch"]- apiGroups: [""]resources: ["endpoints"]verbs: ["get", "list", "watch", "create", "update", "patch"]---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:name: run-nfs-client-provisioner
subjects:- kind: ServiceAccountname: nfs-client-provisionernamespace: kube-system 
roleRef:kind: ClusterRolename: nfs-client-provisioner-runnerapiGroup: rbac.authorization.k8s.io---
kind: Deployment
apiVersion: apps/v1
metadata:name: nfs-provisioner-01namespace: kube-system
spec:replicas: 1strategy:type: Recreateselector:matchLabels:app: nfs-provisioner-01template:metadata:labels:app: nfs-provisioner-01spec:serviceAccountName: nfs-client-provisionercontainers:- name: nfs-client-provisionerimage: bogeit/nfs-subdir-external-provisioner:v4.0.2imagePullPolicy: IfNotPresentvolumeMounts:- name: nfs-client-rootmountPath: /persistentvolumesenv:- name: PROVISIONER_NAMEvalue: nfs-provisioner-01  # 此处供应者名字供storageclass调用- name: NFS_SERVERvalue: 10.0.1.201   # 填入NFS的地址- name: NFS_PATHvalue: /nfs_dir   # 填入NFS挂载的目录volumes:- name: nfs-client-rootnfs:server: 10.0.1.201   # 填入NFS的地址path: /nfs_dir   # 填入NFS挂载的目录---
# use aliyun's nas need configure: https://help.aliyun.com/document_detail/130727.html?spm=a2c4g.11174283.6.715.1aad2ceeUrijYZ
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: nfs-boge
provisioner: nfs-provisioner-01
# Supported policies: Delete、 Retain , default is Delete
reclaimPolicy: Retain

开始创建这个StorageClass:

# kubectl apply -f nfs-sc.yaml 
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
deployment.apps/nfs-provisioner-01 createdorageclass.storage.k8s.io/nfs-boge created# 注意这个是在放kube-system的namespace下面,这里面放置一些偏系统类的服务
# kubectl -n kube-system get pod -w
NAME                                       READY   STATUS              RESTARTS   AGE
calico-kube-controllers-7fdc86d8ff-dpdm5   1/1     Running             1          24h
calico-node-8jcp5                          1/1     Running             1          24h
calico-node-m92rn                          1/1     Running             1          24h
calico-node-xg5n4                          1/1     Running             1          24h
calico-node-xrfqq                          1/1     Running             1          24h
coredns-d9b6857b5-5zwgf                    1/1     Running             1          24h
metrics-server-869ffc99cd-wfj44            1/1     Running             2          24h
nfs-provisioner-01-5db96d9cc9-qxlgk        0/1     ContainerCreating   0          9s
nfs-provisioner-01-5db96d9cc9-qxlgk        1/1     Running             0          21s# StorageClass已经创建好了
# kubectl get sc
NAME       PROVISIONER          RECLAIMPOLICY   VOLUMEBINDINGMODE   ALLOWVOLUMEEXPANSION   AGE
nfs-boge   nfs-provisioner-01   Retain          Immediate           false                  37s

我们来基于StorageClass创建一个pvc,看看动态生成的pv是什么效果:

# vim pvc-sc.yaml 
kind: PersistentVolumeClaim
apiVersion: v1
metadata:name: pvc-sc
spec:storageClassName: nfs-bogeaccessModes:- ReadWriteManyresources:requests:storage: 1Mi# kubectl  apply -f pvc-sc.yaml 
persistentvolumeclaim/pvc-sc created# kubectl  get pvc
NAME     STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
pvc-sc   Bound    pvc-63eee4c7-90fd-4c7e-abf9-d803c3204623   1Mi        RWX            nfs-boge       3s
pvc1     Bound    pv1                                        1Gi        RWO            nfs            24m# kubectl  get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM            STORAGECLASS   REASON   AGE
pv1                                        1Gi        RWO            Recycle          Bound    default/pvc1     nfs                     49m
pvc-63eee4c7-90fd-4c7e-abf9-d803c3204623   1Mi        RWX            Retain           Bound    default/pvc-sc   nfs-boge                7s

我们修改下nginx的yaml配置,将pvc的名称换成上面的pvc-sc:

# vim nginx.yaml 
---
apiVersion: apps/v1
kind: Deployment
metadata:labels:app: nginxname: nginx
spec:replicas: 1selector:matchLabels:app: nginxtemplate:metadata:labels:app: nginxspec:containers:- image: nginx:1.21.6name: nginxvolumeMounts:    # 我们这里将nginx容器默认的页面目录挂载- name: html-filesmountPath: "/usr/share/nginx/html"volumes:- name: html-filespersistentVolumeClaim:claimName: pvc-sc# kubectl apply -f nginx.yaml 
service/nginx unchanged
deployment.apps/nginx configured# 这里注意下,因为是动态生成的pv,所以它的目录基于是一串随机字符串生成的,这时我们直接进到pod内来创建访问页面
# kubectl exec -it nginx-57cdc6d9b4-n497g -- bash
root@nginx-57cdc6d9b4-n497g:/# echo 'storageClass used' > /usr/share/nginx/html/index.html
root@nginx-57cdc6d9b4-n497g:/# exit# curl 10.68.238.54                              
storageClass used# 我们看下NFS挂载的目录
# ll /nfs_dir/
total 0
drwxrwxrwx 2 root root 24 Nov 27 17:52 default-pvc-sc-pvc-63eee4c7-90fd-4c7e-abf9-d803c3204623
drwxr-xr-x 2 root root  6 Nov 27 17:25 pv1

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/199746.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

properties出现中文乱码解决方法(万能)

目录 1. 问题所示2. 原理分析3. 解决方法1. 问题所示 在使用Properties类的时候,中文出现乱码 如图所示: 正常思维来讲,估计是中文编码有问题,于是我将其改为UTF-8的编码方式 通过下方的改动: 可到了这一步,中文还是乱码(这一步改成功的网友可自动立场,没改成功的网…

PVE系列-LVM安装MacOS的各个版本及VNC加密隧道访问

PVE系列-LVM安装MacOS的各个版本 环境配置大概过程&#xff1a;详细步骤&#xff1a;1.建立安装环境和下载安装工具2. 重启后&#xff0c;执行osx-setup配置虚拟机3. 安装到硬盘&#xff0c;4.设定引导盘&#xff0c;以方便自动开机启动5.打开屏幕共享和系统VNC6.VNC加密的ssh隧…

synchronized底层原理(二)

书接上文 文章目录 1. 锁升级原理2. Synchronized锁优化1. 偏向锁批量重偏向&批量撤销2. 自旋优化3. 锁粗化4. 锁消除 1. 锁升级原理 前面介绍了对象的几种加锁状态&#xff0c;分别是无锁、偏向锁、轻量级锁和重量级锁。有下面几个关键点&#xff1a; 当开启JVM偏向延迟…

什么是美颜sdk?美颜sdk对比评测、技术评估

为了满足用户对于更美好画面的需求&#xff0c;各种美颜sdk应运而生。本文将深入探讨美颜sdk的概念&#xff0c;进行对比评测&#xff0c;并对其技术进行综合评估。 一、什么是美颜sdk&#xff1f; 美颜sdk使开发者们可以方便地在自己的应用中集成美颜功能&#xff0c;从而提…

前端食堂技术周刊第 107 期:技术博客节、Deno Cron、FEDAY、XState v5、Electron 2023 生态系统回顾

美味值&#xff1a;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f;&#x1f31f; 口味&#xff1a;烤椰拿铁 食堂技术周刊仓库地址&#xff1a;https://github.com/Geekhyt/weekly 大家好&#xff0c;我是童欧巴。欢迎来到前端食堂技术周刊&#xff0c;我们先来看下…

like concat()函数

mybatis中为了防止sql注入&#xff0c;使用like语句时并不是直接使用&#xff0c;而是使用concat函数 <if test"goodName ! null and goodName ! "> and good_name like concat(%, #{goodName}, %)</if> concat()函数 1、功能&#xff1a;将多个字符串…

【5】PyQt按钮

QPushButton 常见的按钮实现类包括:QPushButton、QRadioButton和QCheckBox QPushButton是最普通的按钮控件&#xff0c;可以响应一些用户的事件 from PyQt5.QtWidgets import QApplication, QWidget, QPushButton import sysdef func():print("按下按钮啦&#xff0c;火…

C语言每日一题(46)整数转罗马数字

力扣网12 整数转罗马数字 题目描述 罗马数字包含以下七种字符&#xff1a; I&#xff0c; V&#xff0c; X&#xff0c; L&#xff0c;C&#xff0c;D 和 M。 字符 数值 I 1 V 5 X 10 L 50 C 100 D …

AI:LangChain

LangChain是一个开源的框架&#xff0c;旨在使开发人员能够轻松构建使用大型语言模型&#xff08;LLMs&#xff09;的应用程序。它提供了一种方式&#xff0c;通过这种方法&#xff0c;开发者可以利用像OpenAI的GPT-3或GPT-3.5这样的模型&#xff0c;以及Hugging Face提供的其他…

UVC debug 工具

v4l2-ctl media-ctl v4l2-ctl和media-ctl是Linux系统中用于配置和控制摄像头的命令行工具。 v4l2-ctl&#xff08;Video for Linux Control&#xff09;是一个用于配置和控制摄像头的工具。它允许用户查看设备的当前状态、设置视频格式、调整图像属性&#xff08;如亮度、对比…

Isaac Sim教程06 OmniGraph图编程

Isaac Sim OmniGraph图编程 版权信息 Copyright 2023 Herman YeAuromix. All rights reserved.This course and all of its associated content, including but not limited to text, images, videos, and any other materials, are protected by copyright law. The autho…

Spring容器启动过程中的自定义操作插口汇总

目录标题 PostConstruct注解EventListener方式InitializingBean的afterPropertiesSet方法实现ApplicationRunner接口重写run方法实现AplicationContextAware接口重写setApplicationContext实现ServletContextListener接口contextInitialized方法实现ServletContextAware接口set…

7个简单技巧,让你从容应对压力面试!

01-什么是压力面试&#xff1f; 压力面试是指有意制造紧张&#xff0c;以了解求职者将如何面对工作压力的一种面试形式。 事实上&#xff0c;压力面试不是单独存在的一类面试&#xff0c;往往是穿插在面试过程中。 面试人通过提出不礼貌、冒犯的问题&#xff0c;或者用怀疑、…

【1day】蓝凌OA 系统custom.jsp 接口远程命令执行漏洞学习

注:该文章来自作者日常学习笔记,请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与作者无关。 目录 一、漏洞概述 二、影响版本 三、资产测绘 四、漏洞复现

梦回吹角连营(2)(快速幂快乘)

Description 给定f(n)(a1)*n^a(a2)*n^(a1)...b*n^(b-1) 求f(n)%10000000033 Input 输入一个正整数T(T<10),表示有T组数据&#xff0c;每组数据包括三个整数a,b,n (0<n<10^9,1<a < b-1<10^20) Output 输出 f(n)%10000000033 的结果 Sample Input 1 1 2…

【1day】蓝凌OA 系统datajson.js接口远程命令执行漏洞学习

注:该文章来自作者日常学习笔记,请勿利用文章内的相关技术从事非法测试,如因此产生的一切不良后果与作者无关。 目录 一、漏洞概述 二、影响版本 三、资产测绘 四、漏洞复现

要求CHATGPT高质量回答的艺术:提示工程技术的完整指南

要求CHATGPT高质量回答的艺术&#xff1a;提示工程技术的完整指南 第一章&#xff1a;什么是提示工程&#xff1f; 提示工程是创建提示或询问或给出指令的过程&#xff0c;这些指令指导像ChatGPT这样的语言模型的输出。 它允许用户控制模型的输出并生成适合他们特定需求的文…

十三、FreeRTOS之FreeRTOS时间管理

本节主要介绍以下内容&#xff1a; 1&#xff0c;延时函数介绍&#xff08;了解&#xff09; 2&#xff0c;延时函数解析&#xff08;熟悉&#xff09; 3&#xff0c;延时函数演示实验&#xff08;掌握&#xff09; 4&#xff0c;课堂总结&#xff08;掌握&#xff09; 一、…

最新测试开发招聘信息汇总,内含社招和实习生岗位~

1 科大讯飞 [武汉/合肥/西安] 科大讯飞&#xff08;教育事业部&#xff09; - 移动、服务端高级测试开发工程师 一、移动方向高级测试开发工程师 岗位职责&#xff1a; 1.负责教育 BG 中 APP/SDK/软硬一体等产品类型的专项测试工作&#xff0c;包括专项测试方案设计、自动化测…

通过时间交织技术扩展ADC采样速率的简要原理

前言 数据采集是将自然界中存在的模拟信号通过模数转换器&#xff08;ADC&#xff09;转换成数字信号&#xff0c;再对该数字信号进行相应的接收和处理。数据采集系统作为数据采集的手段&#xff0c;在移动通信、图向采集、无线电等领域有重要作用。随着电子信息技术的飞速发展…