自学内容网 自学内容网

k8s 学习笔记之 k8s 存储管理

概述

在虚拟机的环境中,应用程序的数据通常存储在本地磁盘上,即使重启虚拟机也不会数据丢失。但是在 pod 中。pod 的特点就是 “临时性” ,随着 pod 的重建,容器中的数据也会消失,这将导致一些应用程序读不到之前的数据。因此 pod 引入了持久化这个概念,也就是 “卷”

卷是 pod 中存储数据和共享数据的一个抽象概念。它提供了一种将存储设备挂载进容器的机制。

卷的常用类型

卷的分类卷类型说明
临时存储emptyDir用于 pod 中,容器之间的共享
本地存储hostPath将节点文件系统上的文件或者目录挂载到 pod 中
对象存储ConfigMap,Secretk8s 内置的存储对象,用于存储应用程序配置和敏感数据
自建存储系统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:latest
    imagePullPolicy: IfNotPresent
    name: app
    command: ["/bin/sh","-c","for i in {1..10000};do echo $i >> /opt/file.txt;sleep 1;done"]
    volumeMounts:
    - name: data
      mountPath: /opt
  - image: docker.io/library/centos:latest
    imagePullPolicy: IfNotPresent
    name: sidecar # 边车容器
    command: ["/bin/sh","-c","tail -f /opt/file.txt"]
    volumeMounts:
    - name: data
      mountPath: /opt
  volumes:
  - name: data
    emptyDir: {}
[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还支持其他类型的容器,包括:

  1. 标准容器(Application Containers):这是最常见的容器类型,用于运行主要的应用逻辑。

  2. Init 容器:这些容器在应用容器启动之前运行,用于执行一些初始化任务,比如设置配置文件或者等待外部服务就绪。Init 容器在Pod中的所有应用容器启动前完成执行并退出。

  3. 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-container
    image: docker.io/library/nginx:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - name: hostpath-volume
      mountPath: /usr/share/nginx/html
  volumes:
  - name: hostpath-volume
    hostPath:
      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 挂载的实现的功能是一致的,那他们的区别在哪呢?

NFSemptyDir 的主要区别是:

  • 持久性
    • 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)来满足这个请求。

你可以把 PVPVC 想象成 房子租房合同 的关系:

  • 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: 10Gi
  accessModes:
     - ReadWriteOnce  # 只能被一个节点挂载
  persistentVolumeReclaimPolicy: Retain  # PV 删除后数据保留
  storageClassName: standard
  hostPath:
    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:
    - ReadWriteOnce
  resources:
    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: nginx
    image: docker.io/library/nginx:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: static-storage
  volumes:
  - name: static-storage
    persistentVolumeClaim:
      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:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi
  storageClassName: 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: nginx
    image: docker.io/library/nginx:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
    - mountPath: /usr/share/nginx/html
      name: dynamic-storage
  volumes:
  - name: dynamic-storage
    persistentVolumeClaim:
      claimName: dynamic-pvc

最后的结果和上面静态供给是差不多的

演示到这里,pv / pvc 大概就是这么用的

PV 的生命周期

最后,我们再来聊一聊 pv 的生命周期:pv 的生命周期包含多个阶段:

  1. Provisioning PV 供给: 可以通过静态供给和动态供给创建 PV
  2. Binding 绑定: 在 PV 创建后,并且处于 available 状态时,PVC 与 PV 绑定,此时 PV 就会转换成 Bound
  3. Using 使用: 当 PVC 与 PV 成功绑定之后,Pod 获取到 PV 的存储资源,从而将容器中的数据存储到外部的存储系统中
  4. Releasing 释放: 当 PVC 被删除时 , PV 也会随之被删除,具体行为由回收策略决定。
  5. 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: web
    image: docker.io/library/nginx:latest
    imagePullPolicy: IfNotPresent
    volumeMounts:
      - name: config
        mountPath: /etc/nginx/conf.d # 将 nginx 的配置文件挂载进容器
  volumes:
    - name: config
      configMap:
        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: config
  configMap:
    name: nginx-config
    items:
    - key: "a.conf"
      path: "a.example.com.conf"

这样,只有 ConfigMap 中键为 a.conf 的数据会被挂载到容器中。

还有一件事,默认情况下 /etc/nginx/conf.d 目录下是会有一个 defalut.conf 文件的,但是当我们将 a.conf 挂载进去之后,它就不见了,这其实是因为我们的挂载操作是覆盖操作。然而,我们要如何避免这种事情呢?

volumeMounts:
- name: config
  mountPath: /etc/nginx/conf.d/a/example.com.conf
  subPath: a.example.com.conf
volumes:
- name: config
  configMap:
    name: nginx-config
    items:
    - 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-abcde
  annotations:
    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-tokenkubernetes.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; then
        echo "Docker is not installed"
        exit 1
    fi
}

# 检查是否提供了足够的参数
check_args() {
    if [ "$#" -lt 2 ]; then
        echo "Usage: $0 <image-name> <host1> [<host2> ...]"
        exit 1
    fi
}

# 检查本地是否存在指定的镜像
check_local_image() {
    local image_name=$1
    if docker images --format '{{.Repository}}:{{.Tag}}' | grep -q "$image_name"; then
        echo "镜像 $image_name 已存在于本地"
        return 0
    else
        echo "正在拉取 Docker 镜像 $image_name..."
        if docker pull "$image_name" > /dev/null 2> pull.log; then
            echo "Docker 镜像 $image_name 拉取成功"
            return 0
        else
            echo "Docker 镜像 $image_name 拉取失败。详情查看 pull.log。"
            exit 1
        fi
    fi
}

# 检查 tar 文件是否存在
check_tar_file() {
    local image_name=$1
    local tar_file=$2
    if [ -f "$tar_file" ]; then
        echo "镜像文件 $tar_file 已经存在"
        return 0
    else
        echo "Docker save $image_name"
        if docker save -o "$tar_file" "$image_name" 2> save.log; then
            echo "Docker save $image_name is successful"
            return 0
        else
            echo "Docker save $image_name failed. Check save.log for details."
            exit 1
        fi
    fi
}

# 发送和导入镜像到其他主机
send_and_import_image() {
    local image_name=$1
    local tar_file=$2
    shift 2 # 移动参数,使得 $@ 包含剩余的主机地址
    for host in "$@"; do
        echo "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'"; then
            echo "镜像 $image_name 已存在于 $host"
        else
            echo "导入镜像到 $host"
            if ssh root@$host "ctr -n k8s.io images import /root/$tar_file"; then
                echo "ctr import $tar_file is successful on $host"
            else
                echo "ctr import $tar_file failed on $host"
            fi
        fi
    done
}

# 主函数
main() {
    check_docker_installed
    check_args "$@"

    local image_name=$1
    local 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 "$@"

原文地址:https://blog.csdn.net/qq_62866151/article/details/143837618

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!