k8s 学习笔记之 k8s 存储管理
文章目录
- 概述
- 卷
- 卷的常用类型
- emptyDir
- 边车容器
- HostPath
- nfs
- PV/PVC
- 静态供给 PV 和 PVC
- 创建静态 PV
- 创建 pvc
- 创建 pod 应用 pvc
- 动态供给 PV 和 PVC
- 创建 StorageClass
- 创建 pvc
- 创建 pod 使用 pvc
- PV 的生命周期
- 内置存储对象
- ConfigMap
- Secret
- 配置文件自动重新加载方案
- **1. 应用内动态检测文件变更**
- **2. 通过信号触发重新加载**
- **3. 使用 Reloader 或类似工具**
- **4. 手动重启 Pod**
- 拉取镜像的脚本
概述
在虚拟机的环境中,应用程序的数据通常存储在本地磁盘上,即使重启虚拟机也不会数据丢失。但是在 pod 中。pod 的特点就是 “临时性” ,随着 pod 的重建,容器中的数据也会消失,这将导致一些应用程序读不到之前的数据。因此 pod 引入了持久化这个概念,也就是 “卷”
卷
卷是 pod 中存储数据和共享数据的一个抽象概念。它提供了一种将存储设备挂载进容器的机制。
卷的常用类型
卷的分类 | 卷类型 | 说明 |
---|---|---|
临时存储 | emptyDir | 用于 pod 中,容器之间的共享 |
本地存储 | hostPath | 将节点文件系统上的文件或者目录挂载到 pod 中 |
对象存储 | ConfigMap,Secret | k8s 内置的存储对象,用于存储应用程序配置和敏感数据 |
自建存储系统 | NFS,Ceph,ISCSI | 将自建的存储系统挂载到 pod 中 |
存储对象 | persistentVolumesClaim(PVC) | 与 PV 持久卷配合使用 |
emptyDir
empytDir 用于在 pod 中实现容器之间的数据共享,与 pod 的生命周期一致,当 pod 被删除时,对应的目录也会销毁
[root@k8s-master ~]# cat emptyDir.yaml
apiVersion: v1
kind: Pod
metadata:name: pod-emptydir
spec:containers:- image: docker.io/library/centos:latestimagePullPolicy: IfNotPresentname: appcommand: ["/bin/sh","-c","for i in {1..10000};do echo $i >> /opt/file.txt;sleep 1;done"]volumeMounts:- name: datamountPath: /opt- image: docker.io/library/centos:latestimagePullPolicy: IfNotPresentname: sidecar # 边车容器command: ["/bin/sh","-c","tail -f /opt/file.txt"]volumeMounts:- name: datamountPath: /optvolumes:- name: dataemptyDir: {}
[root@k8s-master ~]# kubectl apply -f emptyDir.yaml
pod/pod-emptydir created
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
pod-emptydir 2/2 Running 0 3s
在上面的例子中,我们定义了两个容器和一个 emptyDir 卷,该卷被挂载到两个容器中的同一个目录下了,因此该目录中的文件可以被彼此访问
[root@k8s-master ~]# kubectl exec -it pod-emptydir --container app -- /bin/bash
Defaulted container "app" out of: app, sidecar
[root@pod-emptydir /]# cd opt/
[root@pod-emptydir opt]# ls
file.txt
[root@k8s-master ~]# kubectl exec -it pod-emptydir --container sidecar -- /bin/bash
[root@pod-emptydir /]# cd opt/
[root@pod-emptydir opt]# ls
file.txt
边车容器
边车容器(Sidecar Container)是与主应用容器在同一个Pod中运行的辅助容器。它们通过提供额外的服务或功能(如日志记录、监控、安全性或数据同步)来增强或扩展主应用容器的功能,而无需直接修改主应用代码。边车容器与主容器共享网络和存储命名空间,使得它们能够紧密交互并共享资源。
除了边车容器,Kubernetes还支持其他类型的容器,包括:
-
标准容器(Application Containers):这是最常见的容器类型,用于运行主要的应用逻辑。
-
Init 容器:这些容器在应用容器启动之前运行,用于执行一些初始化任务,比如设置配置文件或者等待外部服务就绪。Init 容器在Pod中的所有应用容器启动前完成执行并退出。
-
Ephemeral 容器:这是一种临时性的容器,它们缺少对资源或执行的保证,并且永远不会自动重启。Ephemeral 容器主要用于调试目的,允许用户加入一个临时容器到正在运行的Pod中,用于调试。
HostPath
hostpath 卷用来将宿主机的目录挂载进容器,这使得容器可以访问宿主机的数据,由于挂载的是宿主机的目录,因此在容器被销毁后,数据并不会丢失。
但是在 k8s 卷的分类中,我们还有一个专门做持久卷的,名叫 PV/PVC 。那这两种挂载方式到底差距在哪呢:
HostPath:
- 直接挂载宿主机本地路径到容器,适合单节点和开发环境。
- 不支持跨节点存储,数据与特定节点绑定。
- 无自动扩展功能,存储容量和管理完全依赖宿主机。
- 没有 Kubernetes 对存储的生命周期管理,容器删除后数据可能丢失。
PV/PVC:
- Kubernetes 管理的持久化存储,可以通过 PVC 请求动态存储资源。
- 支持跨节点、云存储等多种后端,具备高可用性和扩展性。
- 具备自动扩展、容量管理和存储生命周期管理功能。
- 支持不同存储策略(如保留、删除等),适用于生产环境。
那我们在 yaml 文件中如何挂载它呢?
[root@k8s-master ~]# cat hostPath.yaml
apiVersion: v1
kind: Pod
metadata:name: hostpath-pod
spec:containers:- name: hostpath-containerimage: docker.io/library/nginx:latestimagePullPolicy: IfNotPresentvolumeMounts:- name: hostpath-volumemountPath: /usr/share/nginx/htmlvolumes:- name: hostpath-volumehostPath:path: /data # 这会挂载宿主机的 /data 目录到容器的 /usr/share/nginx/html 目录type: DirectoryOrCreate
[root@k8s-master ~]# kubectl get pod
NAME READY STATUS RESTARTS AGE
hostpath-pod 1/1 Running 0 4s
在该 yaml 文件中,我们将 /usr/share/nginx/html 挂载到容器里面的 data 目录下了
hostPath 所支持的卷的类型 (这些参数在 yaml 文件中有对应)
取值 | 作用 |
---|---|
”“ | 该字段为空或者未指定,默认是 DirectoryOrCreate |
DirectoryOrCreate | 如果指定的目录不存在,则会自动创建空目录并为其赋值 0755 |
Directory | 指定的目录必须存在 |
FileOrCreate | 和上面那个差不多,在空文件被创建出来之后默认赋权 0644 |
File | 指定文件必须存在 |
Socket | 指定套接字文件必须存在 |
CharDevice | 指定的字符设备必须存在 |
BlockDevice | 指定的块设备必须存在 |
k8s 在 pod 启动时会检查路径是否与期望类型所匹配,如果不匹配或者类型检查异常,pod 会呈现 ContainerCreateing 状态
hostpath 卷不支持存储容器限制,并且可使用的存储容量受主机文件系统限制
nfs
将 nfs 服务器挂载到 pod 中,实现 pod 之间的数据共享。我们在之前还提到过一个 emptyDir 和 nfs 挂载的实现的功能是一致的,那他们的区别在哪呢?
NFS 和 emptyDir 的主要区别是:
- 持久性:
- NFS:数据持久化,Pod 删除后数据保留。
- emptyDir:数据临时存储,Pod 删除时数据丢失。
- 共享范围:
- NFS:可以跨多个节点和 Pod 共享数据。
- emptyDir:仅限单个 Pod 内的容器共享数据。
- 适用场景:
- NFS:需要跨 Pod 和节点共享数据的场景。
- emptyDir:适合 Pod 内部容器间共享临时数据。
pv对接nfs共享,使用静态创建的方式创建pvc
[root@k8s-master ~]# cat nginx.json
{"apiVersion": "apps/v1","kind": "Deployment","metadata": {"name": "nginx"},"spec": {"selector": {"matchLabels": {"app": "nginx"}},"template": {"metadata": {"labels": {"app": "nginx"}},"spec": {"containers": [{"image": "docker.io/library/nginx:latest","imagePullPolicy": "IfNotPresent","name": "nginx","volumeMounts": [{"name": "data","mountPath": "/data"}]}],"volumes": [{"name": "data","nfs": {"server": "192.168.142.139","path": "/data/nfs"}}]}}}
}
mkdir -p /data/nfs
chmod 777 /data/nfs/
vim /etc/exports
/data/nfs *(no_root_squash,rw,no_all_squash)
systemctl restart nfs-server.service
showmount -e 192.168.142.139[root@k8s-slave1 ~]# df -h | grep 192.168.142.139:/data/nfs
df: /var/lib/kubelet/pods/6210f716-6da2-4a14-b320-33b169b684bc/volumes/kubernetes.io~nfs/mypv1: Stale file handle
192.168.142.139:/data/nfs 17G 6.3G 11G 38% /var/lib/kubelet/pods/fc2ccf93-693b-44b8-93f7-6d60f2f1b412/volumes/kubernetes.io~nfs/data
PV/PVC
在之前,我们已经简单的介绍了一下 PV/PVC 与 hostPath 之间的对比,也是知道了 PV/PVC 存在的意义,那么什么是 PV ? 什么是 PVC ?
持久卷(PV)就是 Kubernetes 用来管理集群中存储的工具,它让你不用关心底层存储的具体实现,只要把它用作存储就行。简单来说,就是让你的数据在 Pod 重启或销毁后还能保留下来。
持久卷声明(PVC)是用户申请存储的方式,简单来说,就是你向 Kubernetes 请求一个存储空间。PVC 就像是一个 “存储请求单”,你告诉 Kubernetes 需要多大的存储空间,Kubernetes 会找一个合适的持久卷(PV)来满足这个请求。
你可以把 PV 和 PVC 想象成 房子 和 租房合同 的关系:
-
PV(持久卷) 就是一个已经建好的 房子,它有一定的空间和资源,可能是物理硬盘、NFS 共享或云存储等。
-
PVC(持久卷声明) 就是你去 租房,向 Kubernetes 提出需要多大面积的房子,Kubernetes 会找一个合适的 房子(PV) 给你。
所以,PVC 是你用来申请 PV 的工具,PVC 就像是租房合同,PV 是你租到的房子。
在 PV/PVC 的世界里还有两个非常重要的概念:PV 静态供给,PV 动态供给
-
静态供给:管理员手动创建好 PV(持久卷),然后用户通过 PVC(持久卷声明) 来请求匹配的 PV。这种方式下,管理员负责管理存储资源。
-
动态供给:当用户创建 PVC 时,Kubernetes 会根据 StorageClass 自动创建一个符合要求的 PV。这种方式下,管理员不需要事先创建 PV,Kubernetes 会自动提供存储。
下面是两个示例:
静态供给 PV 和 PVC
创建静态 PV
[root@k8s-master ~]# cat pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:name: static-pv
spec:capacity:storage: 10GiaccessModes:- ReadWriteOnce # 只能被一个节点挂载persistentVolumeReclaimPolicy: Retain # PV 删除后数据保留storageClassName: standardhostPath:path: /data/static-pv # 宿主机路径
persistentVolumeReclaimPolicy 参数有三个值可以选
-
Retain:保留数据。PV 被释放后,存储资源不会被回收,管理员需要手动处理该 PV。数据保留在原位置,可以进行手动清理或重新绑定到新的 PVC。
-
Recycle(已弃用,不再推荐使用):回收数据。在这种模式下,PV 被释放后,Kubernetes 会尝试清除存储中的数据(通常是执行
rm -rf /some-directory/*
操作),然后将 PV 设置为可以重新绑定的状态。这种方式已被 Kubernetes 弃用。 -
Delete:删除数据。PV 被释放后,Kubernetes 会自动删除与 PV 相关联的存储资源(如删除一个云存储卷)。数据将被永久删除。
创建 pvc
[root@k8s-master ~]# cat pvc.yaml
# pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: static-pvc
spec:accessModes:- ReadWriteOnceresources:requests:storage: 10Gi # 请求10GB的存储storageClassName: standard
创建 pod 应用 pvc
pull-image.sh 是我自己编写的脚本,专门用来拉取镜像的,如果小伙伴们也是 container 无法拉取镜像,不妨可以试一试我这个脚本,脚本放到最后
[root@k8s-master ~]# ./pull-image.sh nginx:latest 192.168.142.139 192.168.142.140 192.168.142.141
镜像 nginx:latest 已存在于本地
Docker save nginx:latest
Docker save nginx:latest is successful
Sending nginx-latest.tar to 192.168.142.139
root@192.168.142.139's password:
nginx-latest.tar 100% 187MB 947.0MB/s 00:00
Successfully sent nginx-latest.tar to 192.168.142.139
root@192.168.142.139's password:
镜像 nginx:latest 已存在于 192.168.142.139
Sending nginx-latest.tar to 192.168.142.140
root@192.168.142.140's password:
nginx-latest.tar 100% 187MB 355.8MB/s 00:00
Successfully sent nginx-latest.tar to 192.168.142.140
root@192.168.142.140's password:
导入镜像到 192.168.142.140
root@192.168.142.140's password:
unpacking docker.io/library/nginx:latest (sha256:466e72df2f0b10ecb0dc90dc99d523a1c6432b764ebcbcdab3f0a7ef5cd4e061)...done
ctr import nginx-latest.tar is successful on 192.168.142.140
Sending nginx-latest.tar to 192.168.142.141
root@192.168.142.141's password:
nginx-latest.tar 100% 187MB 325.1MB/s 00:00 1
Successfully sent nginx-latest.tar to 192.168.142.141
root@192.168.142.141's password:
导入镜像到 192.168.142.141
root@192.168.142.141's password:
unpacking docker.io/library/nginx:latest (sha256:466e72df2f0b10ecb0dc90dc99d523a1c6432b764ebcbcdab3f0a7ef5cd4e061)...done
ctr import nginx-latest.tar is successful on 192.168.142.141
[root@k8s-master ~]# cat pod1.yaml
# pod.yaml
apiVersion: v1
kind: Pod
metadata:name: static-pod
spec:containers:- name: nginximage: docker.io/library/nginx:latestimagePullPolicy: IfNotPresentvolumeMounts:- mountPath: /usr/share/nginx/htmlname: static-storagevolumes:- name: static-storagepersistentVolumeClaim:claimName: static-pvc
[root@k8s-master ~]# kubectl apply -f pod1.yaml
pod/static-pod created
[root@k8s-master ~]# kubectl get pod
static-pod 1/1 Bound 0 5s
以上就是静态供给了,但是相较于静态供给,还是动态供给香多了
动态供给 PV 和 PVC
在动态供给中,管理员配置了存储类(StorageClass),Kubernetes 会根据 PVC 的需求动态创建一个 PV。
创建 StorageClass
# storage-class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:name: dynamic-storage
provisioner: kubernetes.io/aws-ebs # 使用 AWS EBS 或者可以是其他存储提供商
parameters:type: gp2
创建 pvc
[root@k8s-master ~]# cat dynamic-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:name: dynamic-pvc
spec:accessModes:- ReadWriteOnceresources:requests:storage: 1GistorageClassName: dynamic-storage # 确保 PVC 引用正确的 StorageClass
创建 pod 使用 pvc
[root@k8s-master ~]# cat pod-dynamic.yaml
# pod-dynamic.yaml
apiVersion: v1
kind: Pod
metadata:name: dynamic-pod
spec:containers:- name: nginximage: docker.io/library/nginx:latestimagePullPolicy: IfNotPresentvolumeMounts:- mountPath: /usr/share/nginx/htmlname: dynamic-storagevolumes:- name: dynamic-storagepersistentVolumeClaim:claimName: dynamic-pvc
最后的结果和上面静态供给是差不多的
演示到这里,pv / pvc 大概就是这么用的
PV 的生命周期
最后,我们再来聊一聊 pv 的生命周期:pv 的生命周期包含多个阶段:
- Provisioning PV 供给: 可以通过静态供给和动态供给创建 PV
- Binding 绑定: 在 PV 创建后,并且处于 available 状态时,PVC 与 PV 绑定,此时 PV 就会转换成 Bound
- Using 使用: 当 PVC 与 PV 成功绑定之后,Pod 获取到 PV 的存储资源,从而将容器中的数据存储到外部的存储系统中
- Releasing 释放: 当 PVC 被删除时 , PV 也会随之被删除,具体行为由回收策略决定。
- Reclaiming 回收: 释放后,根据回收策略执行相应的操作,具体的操作就是上面讲到的 Retain ,Recycle ,Delete
另外,动态供给创建的 pv ,回收策略默认使用的是 Delete,但是这个可以在 StorageClass 的 yaml 文件中通过 reclaimPolicy 参数进行修改
内置存储对象
ConfigMap
它是一种专门用来存储各种配置文件的 pod,它以键值对的形式存储保存数据。
例如 nginx 的配置文件等等
下面是一个简单的示例:
[root@k8s-master ~]# cat nginx-proxy-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:name: nginx-config
data:a.conf: |server {listen 80;server_name a.example.com;location / {proxy_pass http://192.168.142.139:8080}}
当我们看到 data 的时候是不是似曾相识,这个就是 nginx 反向代理的配置文件
[root@k8s-master ~]# kubectl apply -f nginx-proxy-configmap.yaml
configmap/nginx-config created
[root@k8s-master ~]# kubectl get cm
NAME DATA AGE
kube-root-ca.crt 1 11d
nginx-config 1 7s
那 ConfigMap 如何配合 PV 使用呢?
# nginx-proxy-pod.yaml
apiVersion: v1
kind: Pod
metadata:name: nginx-proxy
spec:containers:- name: webimage: docker.io/library/nginx:latestimagePullPolicy: IfNotPresentvolumeMounts:- name: configmountPath: /etc/nginx/conf.d # 将 nginx 的配置文件挂载进容器volumes:- name: configconfigMap:name: nginx-config
[root@k8s-master ~]# kubectl get pod
nginx-proxy 1/1 Running 0 46s[root@k8s-master ~]# kubectl exec nginx-proxy -- ls /etc/nginx/conf.d a.conf
/etc/nginx/conf.d:
a.conf
每次挂载新的配置文件都有一个等待的过程,在这个过程中,存在一种自动更新的机制。kubelet 组件会定期检查 pod 挂载的 ConfigMap 对象中的数据是否发生变更,如果发生变更,就会将最新的配置文件再次加载到容器中去,以确保 pod 始终是最新的配置。
如果只想挂载指定的 键 或者 文件名,我们还可以这样:
volumes:
- name: configconfigMap:name: nginx-configitems:- key: "a.conf"path: "a.example.com.conf"
这样,只有 ConfigMap 中键为 a.conf 的数据会被挂载到容器中。
还有一件事,默认情况下 /etc/nginx/conf.d 目录下是会有一个 defalut.conf 文件的,但是当我们将 a.conf 挂载进去之后,它就不见了,这其实是因为我们的挂载操作是覆盖操作。然而,我们要如何避免这种事情呢?
volumeMounts:
- name: configmountPath: /etc/nginx/conf.d/a/example.com.confsubPath: a.example.com.conf
volumes:
- name: configconfigMap:name: nginx-configitems:- key: "a.conf"path: "a.example.com.conf"
但是要注意的是,当我们使用了 subPath 之后,kubelet 不会自动更新新的数据到 pod 中
Secret
它比较敏感,因为它主要负责存储一些特别重要的信息,比如找密码,密钥证书等。
secret 支持三种类型
-
Opaque(默认类型):
- 描述:最常用的
Secret
类型。用于存储任意的键值对数据,这些数据会被以 base64 编码的形式存储。 - 示例用途:存储用户名和密码、API 密钥等。
- 默认类型:如果在
Secret
的定义中没有指定类型,Kubernetes 会默认使用Opaque
类型。
- 描述:最常用的
apiVersion: v1
kind: Secret
metadata:name: mysecret
type: Opaque
data:username: bXl1c2VybmFtZQ== # base64 编码的用户名password: bXlwYXNzd29yZA== # base64 编码的密码
-
kubernetes.io//service-account-token:
- 描述:此类型的
Secret
存储的是服务账户的令牌,它会自动由 Kubernetes 控制平面创建和管理。 - 示例用途:为 pod 提供与 Kubernetes API 通信所需的身份验证令牌。
- 创建方式:当你创建服务账户时,Kubernetes 会自动创建与该账户关联的
Secret
。
- 描述:此类型的
apiVersion: v1
kind: Secret
metadata:name: default-token-abcdeannotations:kubernetes.io/service-account.name: default
type: kubernetes.io/service-account-token
data:token: <base64 encoded token>ca.crt: <base64 encoded ca.crt>
-
kubernetes.io//dockercfg 和 kubernetes.io//dockerconfigjson:
- 描述:这两种类型的
Secret
用于存储 Docker 配置文件,通常用于存储 Docker 仓库的认证信息。 - 示例用途:在
Secret
中存储 Docker 的认证信息,以便 Kubernetes 能够从私有 Docker 仓库拉取镜像。 - 区别:
kubernetes.io/dockercfg
存储.dockercfg
配置文件,而kubernetes.io/dockerconfigjson
存储的是 Docker 配置 JSON 文件(更现代的格式)。
示例
kubernetes.io/dockerconfigjson
: - 描述:这两种类型的
apiVersion: v1
kind: Secret
metadata:name: my-docker-secret
type: kubernetes.io/dockerconfigjson
data:.dockerconfigjson: <base64 encoded docker config json>
当然在 k8s 的一些老版本中是这第三个:docker-registry ,generic 以及 tls
- Kubernetes 1.2 是 Secret 类型支持的重要版本:
Opaque
(默认类型)、kubernetes.io/service-account-token
和kubernetes.io/dockercfg
同时被引入。 - Kubernetes 1.9 开始推荐使用
kubernetes.io/dockerconfigjson
,这是更现代的私有镜像认证方式。 - 当然这两种方式只是 不同表达方式,它们并没有严格意义上的更新替换,只是在文档和实践中有些变化。
配置文件自动重新加载方案
当 ConfigMap 和 Secret 以卷的方式被挂载到容器中时,如果它们发生改变,那么最新的数据也会被更新到 pod 中。为了使得最新的配置生效,应用程序还需要有自动检测和处理变更的能力,具体的实现方式有以下几种:
1. 应用内动态检测文件变更
- 原理:应用程序自行监控挂载文件的变更,并动态加载最新内容。
- 实现方式:
- 使用文件监听工具(如 inotify)。
- 定期轮询挂载路径,检测文件更新。
- 适用场景:轻量级服务,业务逻辑明确,适合直接修改应用代码。
2. 通过信号触发重新加载
- 原理:监听指定信号(如
SIGHUP
),触发配置重新加载。 - 实现方式:
- 在应用程序内设置信号处理器。
- 配合文件挂载变更,使用工具发送信号:
kill -SIGHUP <PID>
- 适用场景:对应用进行小幅改动即可实现,适合中等规模服务。
3. 使用 Reloader 或类似工具
- 原理:借助外部工具监控 ConfigMap 或 Secret 变更,触发 Pod 滚动更新。
- 实现方式:
- 部署 Reloader(或类似工具)。
- 在 Pod 上配置注解:
metadata:annotations:reloader.stakater.com/match: "true"
- 适用场景:需要在更新时完全重启服务,适合大型分布式服务。
4. 手动重启 Pod
- 原理:手动触发 Pod 滚动更新,以应用最新配置。
- 实现方式:
- 修改 Deployment 或 StatefulSet 的 annotation:
kubectl patch deployment <name> -p '{"spec":{"template":{"metadata":{"annotations":{"date":"<current-timestamp>"}}}}}'
- 适用场景:非频繁更新情况下的简易方案。
拉取镜像的脚本
仅支持检测 tar 包,具体的使用方法,开袋即食,跑完脚本就直接写 yaml 文件就可以了,前提是我们要下 yaml 文件的 image 下再加一条 imagePullPolicy: IfNotPresent / Never 。然后还有一点要注意的就是如果本地有镜像,并且已经 docker save 了,那么镜像务必使 tar 结尾,不然脚本会重新为你 docker save ,然后就是具体的使用方法,在上面,已经有示范了
#!/bin/bash# 检查 Docker 是否安装
check_docker_installed() {if ! docker -v > /dev/null 2>&1; thenecho "Docker is not installed"exit 1fi
}# 检查是否提供了足够的参数
check_args() {if [ "$#" -lt 2 ]; thenecho "Usage: $0 <image-name> <host1> [<host2> ...]"exit 1fi
}# 检查本地是否存在指定的镜像
check_local_image() {local image_name=$1if docker images --format '{{.Repository}}:{{.Tag}}' | grep -q "$image_name"; thenecho "镜像 $image_name 已存在于本地"return 0elseecho "正在拉取 Docker 镜像 $image_name..."if docker pull "$image_name" > /dev/null 2> pull.log; thenecho "Docker 镜像 $image_name 拉取成功"return 0elseecho "Docker 镜像 $image_name 拉取失败。详情查看 pull.log。"exit 1fifi
}# 检查 tar 文件是否存在
check_tar_file() {local image_name=$1local tar_file=$2if [ -f "$tar_file" ]; thenecho "镜像文件 $tar_file 已经存在"return 0elseecho "Docker save $image_name"if docker save -o "$tar_file" "$image_name" 2> save.log; thenecho "Docker save $image_name is successful"return 0elseecho "Docker save $image_name failed. Check save.log for details."exit 1fifi
}# 发送和导入镜像到其他主机
send_and_import_image() {local image_name=$1local tar_file=$2shift 2 # 移动参数,使得 $@ 包含剩余的主机地址for host in "$@"; doecho "Sending $tar_file to $host"scp "$tar_file" "root@$host:/root/" && echo "Successfully sent $tar_file to $host" || echo "Failed to send $tar_file to $host"# 检查目标主机上是否存在镜像if ssh root@$host "docker images --format '{{.Repository}}:{{.Tag}}' | grep -q '$image_name'"; thenecho "镜像 $image_name 已存在于 $host"elseecho "导入镜像到 $host"if ssh root@$host "ctr -n k8s.io images import /root/$tar_file"; thenecho "ctr import $tar_file is successful on $host"elseecho "ctr import $tar_file failed on $host"fifidone
}# 主函数
main() {check_docker_installedcheck_args "$@"local image_name=$1local iso="${image_name//:/-}"local tar_file="$iso.tar"check_local_image "$image_name"check_tar_file "$image_name" "$tar_file"send_and_import_image "$image_name" "$tar_file" "${@:2}"
}# 调用主函数
main "$@"
相关文章:

k8s 学习笔记之 k8s 存储管理
文章目录 概述卷卷的常用类型emptyDir边车容器 HostPathnfsPV/PVC静态供给 PV 和 PVC创建静态 PV创建 pvc创建 pod 应用 pvc 动态供给 PV 和 PVC创建 StorageClass创建 pvc创建 pod 使用 pvc PV 的生命周期 内置存储对象ConfigMapSecret 配置文件自动重新加载方案**1. 应用内动…...

ios swift开发--ios远程推送通知配置
远程推送通知(Push Notifications)在 iOS 平台上是免费提供的,但需要一些准备工作。以下是开通和使用远程推送通知的基本步骤: 开通远程推送通知 注册 Apple Developer Program: 访问 Apple Developer 并注册一个开发…...

【JavaEE进阶】CSS
本节⽬标 掌握 CSS 基本语法规范和CSS选择器的各种⽤法, 熟练使⽤CSS的常⽤属性. 一.CSS介绍 1.什么是CSS? CSS(Cascading Style Sheet),层叠样式表, ⽤于控制⻚⾯的样式. CSS 能够对⽹⻚中元素位置的排版进⾏像素级精确控制, 实现美化⻚⾯的效果. 能够做到⻚⾯…...

基于Java Springboot宠物领养救助平台
一、作品包含 源码数据库设计文档万字PPT全套环境和工具资源部署教程 二、项目技术 前端技术:Html、Css、Js、Vue、Element-ui 数据库:MySQL 后端技术:Java、Spring Boot、MyBatis 三、运行环境 开发工具:IDEA/eclipse 数据…...

C/C++ 中有哪些类型转换方式? 分别有什么区别?
在C编写C/C代码的时候,我们经常会遇到发生类型转换的场景,比如 赋值运算符的两个操作数不同、实参和形参类型不同、函数返回值类型和接收返回值的类型不同,都会发生类型转换;所以,在C语言中提供了两种类型转换 —— 隐…...

小程序租赁系统开发为企业提供高效便捷的租赁服务解决方案
内容概要 在这个数字化飞速发展的时代,小程序租赁系统应运而生,成为企业管理租赁业务的一种新选择。随着移动互联网的普及,越来越多的企业开始关注如何利用小程序来提高租赁服务的效率和便捷性。小程序不仅可以为用户提供一个快速、易用的平…...

Scala的Array
数组:物理空间上连续的(一个挨一个) 优势:根据下标,能快速找到元素 列表:物理空间上不连续(不是一个元素挨着一个元素) 优势:插入元素,删除比较快 object…...

等保测评怎么做?具体流程是什么?
等保测评是对信息系统进行等保(等级保护)安全评测的过程。等保是指对信息系统进行等级化保护管理,目的是提高信息系统的安全性,防止信息泄露、篡改、破坏等安全问题。哈尔滨等保测评按照《中华人民共和国网络安全法》及《信息安全…...

基于YOLOv8深度学习的汽车车身车损检测系统研究与实现(PyQt5界面+数据集+训练代码)
本文研究并实现了一种基于YOLOV8深度学习模型的汽车车身车损检测系统,旨在解决传统车损检测中效率低、精度不高的问题。该系统利用YOLOV8的目标检测能力,在单张图像上实现了车身损坏区域的精确识别和分类,尤其是在车身凹痕、车身裂纹和车身划…...

力扣 LeetCode 144. 二叉树的前序遍历(Day6:二叉树)
解题思路: 方法一:递归(中左右) class Solution {List<Integer> res new ArrayList<>();public List<Integer> preorderTraversal(TreeNode root) {recur(root);return res;}public void recur(TreeNode roo…...

Adobe Illustrator(Ai)修图软件入门操作参考,收集查过的各个细节用法
到现在,对于Ai的使用也是一半一半,基本上都是用到啥就查啥。因为用得也不是很频繁,脑子也记不住很多操作,所以有时候靠肌肉记忆,很多时候,得再百度一遍…… 所以 我在这再备份一下,做个搬运工 …...

Apache Paimon、Apache Hudi、Apache Iceberg对比分析
Apache Paimon、Apache Hudi、Apache Iceberg 都是面向大数据湖的表格式存储管理框架。它们各自的架构、数据管理方式以及适用场景有所不同。下面是对三者的详细对比分析: 1. 基本简介 Apache Paimon: Paimon 是一个新兴的数据湖存储引擎,旨在支持流批一体的数据处理和管理…...

[ 网络安全介绍 5 ] 为什么要学习网络安全?
🍬 博主介绍 👨🎓 博主介绍:大家好,我是 _PowerShell ,很高兴认识大家~ ✨主攻领域:【渗透领域】【数据通信】 【通讯安全】 【web安全】【面试分析】 🎉点赞➕评论➕收藏 养成习…...

生产环境centos8 Red Hat8部署ansible and 一键部署mysql两主两从ansible脚本预告
一、各节点服务器创建lvm逻辑卷组 1.初始化磁盘为物理卷(PV) 命令:sudo pvcreate /dev/vdb 2.创建卷组(VG) 命令:sudo vgcreate db_vg /dev/vdb 3.创建逻辑卷(LV) 命令:s…...

华为云stack网络服务流量走向
1.同VPC同子网同主机内ECS间互访流量走向 一句话通过主机内部br-int通信 2.同VPC同子网跨主机ECS间互访流量走向 3.同VPC不同子网同主机ECS间互访流量走向 查看ECS配置文件底层KVM技术 查看日志 查看ECS的ID号(管理员身份查询所有租户信息) 查看ECS的其…...

嵌入式硬件杂谈(二)-芯片输入接入0.1uf电容的本质(退耦电容)
引言:对于嵌入式硬件这个庞大的知识体系而言,太多离散的知识点很容易疏漏,因此对于这些容易忘记甚至不明白的知识点做成一个梳理,供大家参考以及学习,本文主要针对芯片输入接入0.1uf电容的本质的知识点的进行学习。 目…...

计算机网络HTTP——针对实习面试
目录 计算机网络HTTP什么是HTTP?HTTP和HTTPS有什么区别?分别说明HTTP/1.0、HTTP/2.0、HTTP/3.0请说明访问网页的全过程请说明HTTP常见的状态码Cookie和Session有什么区别?HTTP请求方式有哪些?请解释GET和POST的区别?HT…...

JAVA中对象实体与对象引用有何不同?举例说明
在 Java 中,对象实体(Object instance)和对象引用(Object reference)是两个不同的概念,虽然它们通常被一起讨论,但它们的作用和表现方式是不同的。下面我们来详细说明这两者的区别。 1. 对象实体…...

C++设计思想-001-设计模式-单例模式
1.单例模式优点 保证一个类仅有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享; 实现: 1.1 单例模式的类只提供私有的构造函数 1.2类定义中含有一个该类的静态私有对象 1.3该类提供了一个静态的公有的函数用于创建或获取它本身的静态私有对象 2.单…...

远程连接服务器
1、远程连接服务器简介 ssh secure shell 非对称加密:一对公钥私钥 对称加密:加密和解密使用的是同一把密钥;(同一秘钥既可以进行加密也可以进行解密 )优势:使用一个秘钥它的加密效率高一些(快一些) …...

【分布式技术】ES扩展知识-Elasticsearch分词器的知识与选择
ES知识扩展 分词器有哪些?1. 标准分词器(Standard Analyzer):示例示例文本分析配置参数与自定义应用场景 2. Simple Analyzer:示例示例文本分析应用场景与限制结论 3. Whitespace Analyzer:示例示例文本分析…...

【网络安全 | 漏洞挖掘】通过密码重置污染实现账户接管
未经许可,不得转载。 文章目录 密码重置污染攻击漏洞挖掘的过程目标选择与初步测试绕过 Cloudflare 的尝试发现两个域名利用 Origin 头部污染实现账户接管攻击流程总结在今天的文章中,我们将深入探讨一种 账户接管 漏洞,并详细分析如何绕过 Cloudflare 的保护机制,利用密码…...

【Nginx从入门到精通】01 、教程简介
讲师:张一鸣老师 课程简介 重量级课程 由浅入深,内容非常广泛 几十个线上的实战案例(图谱),几乎涵盖当前所有互联网主流应用场景 性能:由压测得出结果 调优:从操作系统开始,使你对高并发系统架构的技…...

MySQL面试之底层架构与库表设计
华子目录 mysql的底层架构客户端连接服务端连接的本质,连接用完会立马丢弃吗解析器和优化器的作用sql执行前会发生什么客户端的连接池和服务端的连接池数据库的三范式 mysql的底层架构 客户端连接服务端 连接的本质,连接用完会立马丢弃吗 解析器和优化器…...

C2 追踪器:监控指挥与控制的重要性
12 款暗网监控工具 20 款免费网络安全工具 移动取证软件:为什么 Belkasoft X 应该是您的首选工具 网络安全已成为不断演变的威胁形势中的关键领域。 网络攻击者经常使用命令和控制 (C2) 基础设施来执行和管理攻击。 这些基础设施使恶意软件和攻击者能够与受害设…...

二、神经网络基础与搭建
神经网络基础 前言一、神经网络1.1 基本概念1.2 工作原理 二、激活函数2.1 sigmoid激活函数2.1.1 公式2.1.2 注意事项 2.2 tanh激活函数2.2.1 公式2.2.2 注意事项 2.3 ReLU激活函数2.3.1 公式2.3.2 注意事项 2.4 SoftMax激活函数2.4.1 公式2.4.2 Softmax的性质2.4.3 Softmax的应…...

java导出pdf
引入包 <properties><itext.version>8.0.5</itext.version></properties><dependencies><dependency><groupId>com.itextpdf</groupId><artifactId>itext-core</artifactId><version>${itext.version}</…...

muduo之线程同步CountDownLatch
简介 CountDownLatch称为门阀,用于等待另外线程执行完成 结构 #mermaid-svg-6Azuu15vhIS2hCP1 {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-6Azuu15vhIS2hCP1 .error-icon{fill:#552222;}#mermaid-s…...

【Python系列】Python中打印详细堆栈信息的技巧
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学…...

SpringBoot中监听器、过滤器、拦截器和AOP详解
SpringBoot中监听器、过滤器、拦截器和AOP详解 在构建 Spring Boot 应用程序时,监听器(Listener)、过滤器(Filter)、拦截器(Interceptor)和面向切面编程(AOP)是四种常用…...