K8S 的 Volume

K8S 有很多 Volume 类型

  • awsElasticBlockStore
  • azureDisk
  • azureFile
  • cephfs
  • cinder
  • configMap
  • csi
  • downwardAPI
  • emptyDir
  • fc (fibre channel)
  • flexVolume
  • flocker
  • gcePersistentDisk
  • gitRepo (deprecated)
  • glusterfs
  • hostPath
  • iscsi
  • local
  • nfs
  • persistentVolumeClaim
  • projected
  • portworxVolume
  • quobyte
  • rbd
  • scaleIO
  • secret
  • storageos
  • vsphereVolume

详细信息可以查看官网
https://kubernetes.io/docs/concepts/storage/volumes/#types-of-volumes

这里只介绍几种:emptyDir,local,hostPath,nfs,secret,configMap

emptyDir

初始化为空目录,只在 Pod 运行时存在,当 Pod 停止或死掉后,目录下的所有数据都会被删除(如果只是 Container 死掉数据不会被删除),可以被同一个 Pod 下的不同 Container 使用

emptyDir 主要用于某些应用程序无需永久保存的临时目录,在多个容器之间共享数据等场景

下面是一个简单的例子,将 emptyDir 的 volume 给 mount 到 /cache 目录

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - name: cache-volume
    emptyDir: {}

缺省情况下,emptyDir 是在 Pod 运行的主机的文件系统上创建临时目录

此外也可以通过将 emptyDir.medium 设定为 "Memory" 从而使用内存作为文件系统,这种情况速度会比较快,但空间会比较小,而且如果主机重启数据会丢失

local

使 Volume 和 Pod 被分配到同一个 Node,实现数据的本地化,适合一些对读写磁盘数据要求比较高的,比如数据库

这种做法的风险是 Volume 和 Pod 都被绑定到固定的一个 Node (暂时不支持动态配置),一但这个 Node 出问题,会影响 Volume 和 Pod,可用性会降低

先创建一个 StorageClass

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: local-storage
provisioner: kubernetes.io/no-provisioner
volumeBindingMode: WaitForFirstConsumer

这里 WaitForFirstConsumer 表示不要立即绑定 Volume,而是等到 Pod 调度的时候再绑定

创建 PV

apiVersion: v1
kind: PersistentVolume
metadata:
  name: example-pv
spec:
  capacity:
    storage: 100Gi
  volumeMode: Filesystem
  accessModes:
  - ReadWriteOnce
  persistentVolumeReclaimPolicy: Delete
  storageClassName: local-storage
  local:
    path: /mnt/disks/ssd1
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - key: kubernetes.io/hostname
          operator: In
          values:
          - example-node

这个 PV 使用 example-node 节点的 /mnt/disks/ssd1 目录,并且指定 StorageClass 为前面创建的 local-storage

创建 PVC

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: example-pvc
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi
  storageClassName: local-storage

通过指定 storageClass 为前面创建的 local-storage,这样会等到 Pod 调度的时候再绑定 PVC 和 PV,确保能找到合适的 PV 使得 Volume 和 Pod 在同一个 Node 上

创建 Pod

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /cache
      name: cache-volume
  volumes:
  - cache-volume
    persistentVolumeClaim:
      claimName: example-pvc

等这个 Pod 创建的时候 example-pvc 再去绑定合适的 pv 使得 Pod 和 Volume 跑在同一个 Node

hostPath

把 Volume 挂到 host 主机的路径,这和 local 很像,但有区别

host 模式下 Pod 的运行节点是随机的,假设 Pod 第一次起来的时候是在 Node-A,这时会在 Node-A 创建目录挂到 Pod,如果后来 Pod crash 然后重启,可能会被分配到 Node-B,这时会在 Node-B 创建目录挂到 Pod,但是之前保存在 Node-A 的数据就丢失了

local 模式下 Pod 和创建的目录一直是在同一台主机上

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    hostPath:
      # directory location on host
      path: /data
      # this field is optional
      type: Directory

适用场景的例子,比如如果需要读取主机的系统路径 /sys,读取主机的 docker 信息 /var/lib/docker,这些都是固定路径,每台机都有,而且只关心本机的信息

nfs

apiVersion: v1
kind: Pod
metadata:
  name: test-pd
spec:
  containers:
  - image: k8s.gcr.io/test-webserver
    name: test-container
    volumeMounts:
    - mountPath: /test-pd
      name: test-volume
  volumes:
  - name: test-volume
    nfs:
      path: /data
      server: 192.168.1.10

或者

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: nfs
spec:
  capacity:
    storage: 1Mi
  accessModes:
    - ReadWriteMany
  nfs:
    server: nfs-server.default.svc.cluster.local
    path: "/data/volumes"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nfs
spec:
  accessModes:
    - ReadWriteMany
  storageClassName: ""
  resources:
    requests:
      storage: 1Mi
---
apiVersion: v1
kind: ReplicationController
metadata:
  name: nfs-busybox
spec:
  replicas: 2
  selector:
    name: nfs-busybox
  template:
    metadata:
      labels:
        name: nfs-busybox
    spec:
      containers:
      - image: busybox
        command:
          - sh
          - -c
          - 'while true; do date > /mnt/index.html; hostname >> /mnt/index.html; sleep $(($RANDOM % 5 + 5)); done'
        imagePullPolicy: IfNotPresent
        name: busybox
        volumeMounts:
          # name must match the volume name below
          - name: nfs
            mountPath: "/mnt"
      volumes:
      - name: nfs
        persistentVolumeClaim:
          claimName: nfs

最终相当于执行

mount -t nfs nfs-server.default.svc.cluster.local:/data/volumes /mnt

注意需要保证目标主机的 nfs 是配置好的,相应的路径是可以 mount 的

secret

---
apiVersion: v1
kind: Secret
metadata:
  name: mysecret
data:
  username: YWRtaW4=
  password: MWYyZDFlMmU2N2Rm
type: Opaque
---
apiVersion: v1
kind: Pod
metadata:
  name: mypod
spec:
  containers:
  - name: mypod
    image: redis
    volumeMounts:
    - name: foo
      mountPath: "/etc/foo"
      readOnly: true
  volumes:
  - name: foo
    secret:
      secretName: mysecret

secret 里面定义的 username 和 password 是 base64 编码的
可以看到启动的容器里面的 /etc/foo 目录下会有两个文件,名字分别是 username 和 password,里面的内容被自动按 base64 解码了

secret 安全性

  • 实际上 secret 通过 kubectl get secret mysecret -o yaml 同样可以看到 username 和 password 的值,当然这应该可以配置权限,使得只有相应的帐号可以查询使用
  • secret 是写在容器的内存文件系统而不是磁盘文件系统,不会落盘增加安全性
  • secret 的内容通过 base64 编码存在 etcd 中,挂到容器时会被自动解码为明文

可以看到实际上比起 ConfigMap 安全性似乎没多大提升,base64 实际上很容易解码,或许以后会改进

真正要求安全性的话可以考虑用 Vault

ConfigMap

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: test-cfg
data:
  redis_host: "192.168.1.100"
  redis_port: "6379"
  config.json: |-
    {
        "Postgres": {
            "Host": "192.168.1.200",
            "Port": "5432"
        },
        "Kafka": {
            "Host": "192.168.1.10:9092",
            "From": "smallest"
        }
    }
  config.properties: |
    username = admin
    password = admin
---
apiVersion: v1
kind: Pod
metadata:
  name: configmap-pod
spec:
  containers:
    - name: test
      image: busybox
      volumeMounts:
        - name: config-vol
          mountPath: /etc/config
  volumes:
    - name: config-vol
      configMap:
        name: test-cfg

启动后可以看到 config map 定义的所有配置项都变成 /etc/config 目录下的文件

也可以只挂载 config map 定义的部分配置项

apiVersion: v1
kind: Pod
metadata:
  name: configmap-pod
spec:
  containers:
    - name: test
      image: busybox
      volumeMounts:
        - name: config-vol
          mountPath: /etc/config
      env:
        - name: MY_REDIS_HOST
          valueFrom:
            configMapKeyRef:
              name: test-cfg
              key: redis_host
  volumes:
    - name: config-vol
      configMap:
        name: test-cfg
        items:
           - key: config.properties
             path: path/to/my-config.properties
           - key: config.json
             path: config.json

启动后可以看到 /etc/config 目录下只有 config.json 文件
而 config.properties 被重命名并放到 /etc/config/path/to/my-config.properties
另外 redis_host 被作为环境变量配置了进去

通过 subPath 选取 config map 的配置

apiVersion: v1
kind: Pod
metadata:
  name: configmap-pod
spec:
  containers:
    - name: test
      image: busybox
      volumeMounts:
        - name: config-vol
          mountPath: /etc/config/my-config.json
          subPath: config.json
  volumes:
    - name: config-vol
      configMap:
        name: test-cfg

这样会自动到 test-cfg 这个 config map 寻找 config.json 配置项,将其重命名并挂载到 /etc/config/my-config.json



上一篇:【收藏】关于元数据(Metadata)和元数据管理,这是我的见过最全的解读!


下一篇:Expanse shopify主题模板修改