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