k8s 学习笔记之 k8s 存储管理

news2024/11/18 9:45:04

文章目录

  • 概述
    • 卷的常用类型
    • 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,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 "$@"

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

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

相关文章

云计算虚拟化-kvm创建虚拟机

作者介绍&#xff1a;简历上没有一个精通的运维工程师。希望大家多多关注作者&#xff0c;下面的思维导图也是预计更新的内容和当前进度(不定时更新)。 虚拟化&#xff0c;简单来说就是把一台服务器/PC电脑&#xff0c;虚拟成多台独立的虚拟机&#xff0c;每台虚拟机之间相互隔…

计算机编程中的设计模式及其在简化复杂系统设计中的应用

&#x1f493; 博客主页&#xff1a;瑕疵的CSDN主页 &#x1f4dd; Gitee主页&#xff1a;瑕疵的gitee主页 ⏩ 文章专栏&#xff1a;《热点资讯》 计算机编程中的设计模式及其在简化复杂系统设计中的应用 计算机编程中的设计模式及其在简化复杂系统设计中的应用 计算机编程中的…

编程考古-计算机发展(中)

晶体管计算机时代 尽管真空管技术标志着计算机步入了现代化的门槛&#xff0c;但其固有的局限性——庞大的体积、高昂的能耗、频繁的故障以及不菲的成本——极大地阻碍了其普及与实际应用。 晶体管的早期 Point-contact transistor 点接触晶体管 1947年&#xff0c;贝尔实验…

vue2+3 —— Day5/6

自定义指令 自定义指令 需求&#xff1a;当页面加载时&#xff0c;让元素获取焦点&#xff08;一进页面&#xff0c;输入框就获取焦点&#xff09; 常规操作&#xff1a;操作dom “dom元素.focus()” 获取dom元素还要用ref 和 $refs <input ref"inp" type&quo…

报错 No available slot found for the embedding model

报错内容 Server error: 503 - [address0.0.0.0:12781, pid304366] No available slot found for the embedding model. We recommend to launch the embedding model first, and then launch the LLM models. 目前GPU占用情况如下 解决办法: 关闭大模型, 先把 embedding mode…

Maven 构建项目

Maven 是一个项目管理和构建工具&#xff0c;主要用于 Java 项目。它简化了项目的构建、依赖管理、报告生成、发布等一系列工作。 构建自动化&#xff1a;Maven 提供了一套标准化的构建生命周期&#xff0c;包括编译、测试、打包、部署等步骤&#xff0c;通过简单的命令就可以执…

【C++笔记】C++三大特性之多态

【C笔记】C三大特性之多态 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】C三大特性之多态前言一.多态1.1 多态的概念1.2 虚函数1.3 虚函数的重写/覆盖1.4 多态的定义及实现 二.虚函数重写的⼀些其他问题2.1 协变(…

【项目实战】基于 LLaMA-Factory 通过 LoRA 微调 Qwen2

【项目实战】基于 LLaMAFactory 通过 LoRA 微调 Qwen2 一、项目介绍二、环境准备1、环境准备2、安装LLaMa-Factory3、准备模型数据集3.1 模型准备3.2 数据集准备 三、微调1、启动webui2、选择参数3、训练 四、测试五、总结 一、项目介绍 LLaMA-Factory是一个由北京航空航天大学…

内容占位符:Kinetic Loader HTML+CSS 使用CSS制作三角形原理

内容占位符 前言 随着我们对HTML和CSS3的学习逐渐深入&#xff0c;相信大家都已经掌握了网页制作的基础知识&#xff0c;包括如何使用HTML标记构建网页结构&#xff0c;以及如何运用CSS样式美化页面。为了进一步巩固和熟练这些技能&#xff0c;今天我们一起来完成一个有趣且实…

SpringSecurity 鉴权认证入门讲解

​ Spring Security 是 Spring 家族中的一个安全管理框架。相比与另外一个安全框架Shiro&#xff0c;它提供了更丰富的功能&#xff0c;社区资源也比Shiro丰富。 ​ 一般来说中大型的项目都是使用SpringSecurity 来做安全框架。小项目有Shiro的比较多&#xff0c;因为相比与Sp…

【插件】多断言 插件pytest-assume

背景 assert 断言一旦失败&#xff0c;后续的断言不能被执行 有个插件&#xff0c;pytest-assume的插件&#xff0c;可以提供多断言的方式 安装 pip3 install pytest-assume用法 pytest.assume(表达式,f’提示message’) pytest.assume(表达式,f‘提示message’) pytest.ass…

虾皮:LLM注意力机制的下沉现象分析

&#x1f4d6;标题&#xff1a;When Attention Sink Emerges in Language Models: An Empirical View &#x1f310;来源&#xff1a;arXiv, 2410.10781 &#x1f31f;摘要 &#x1f538;语言模型&#xff08;LM&#xff09;将大量注意力分配给第一个标记&#xff0c;即使它在…

MyBatis的select标签的resultType属性

在MyBatis框架中&#xff0c;映射文件中select标签的resultType属性&#xff0c;用于指定从数据库查询返回结果集需要映射的Java类型&#xff0c;即Mapper接口中方法返回值类型(或集合中的泛型类型)&#xff0c;可以是基本数据类型、基本数据类型的包装类型、自定义的PO类型、集…

ubuntu20.04如何升级python3.8到python3.10

主要参考了这两个链接&#xff1a; 如何在Ubuntu 20.04安装Python 3.10 | myfreaxhttps://www.myfreax.com/how-to-install-python-3-10-on-ubuntu-20-04/#:~:text%E5%9C%A8%E8%B0%83%E8%AF%95%E5%92%8C%E5%85%B6%E4%BB%96%E5%B7%A5%E5%85%B7%E4%B8%AD%E4%BD%BF%E7%94%A8%E7%B…

AWTK-WIDGET-WEB-VIEW 发布

awtk-widget-web-view 是通过 webview 提供的接口&#xff0c;实现的 AWTK 自定义控件&#xff0c;使得 AWTK 可以方便的显示 web 页面。 项目网址&#xff1a; https://gitee.com/zlgopen/awtk-widget-web-view webview 提供了一个跨平台的 webview 接口&#xff0c;是一个非…

丹摩征文活动|FLUX.1+ComfyUI部署与使用

丹摩征文活动&#xff5c;FLUX.1ComfyUI部署与使用 1.引言 在人工智能飞速发展的今天&#xff0c;丹摩智算平台&#xff08;DAMODEL&#xff09;以其卓越的AI算力服务脱颖而出&#xff0c;为开发者提供了一个简化AI开发流程的强大工具。通过租赁GPU资源&#xff0c;丹摩智算平…

性能高于Transformer模型1.7-2倍,彩云科技发布基于DCFormer架构通用大模型云锦天章

2017年&#xff0c;谷歌发布《Attention Is All You Need》论文&#xff0c;首次提出Transformer架构&#xff0c;掀开了人工智能自然语言处理&#xff08;NLP&#xff09;领域发展的全新篇章。Transformer架构作为神经网络学习中最重要的架构&#xff0c;成为后来席卷全球的一…

【异常解决】Linux shell报错:-bash: [: ==: 期待一元表达式 解决方法

博主介绍&#xff1a;✌全网粉丝21W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…

Linux解决普通用户无法使用sudo指令的问题

问题描述&#xff1a; Linux解决普通用户无法使用sudo指令的问题 sudo 指令是允许 普通用户 临时 以 超级用户 root 的权限运行。 普通用户如果没有配置而直接使用 sudo 指令&#xff1a;系统会提示没有权限&#xff08;如下图&#xff09; 使用sudo时系统提示&#xff08;当前…

9.1 使用haarcascade_frontalface_default.xml分类器对静态图像进行人脸检测。

1&#xff09;程序代码&#xff1a; # 1. 使用haarcascade_frontalface_default.xml分类器对静态图像进行人脸检测。 import cv2 import numpy as np # 构造级联分类器对象face_cascade cv2.CascadeClassifier(./data/haarcascades/haarcascade_frontalface_default.xml# ./…