Kubernetes Volumes
# 1. Volumes 卷
Container 中的文件在磁盘上是临时存放的,这给 Container 中运行的较重要的应用程序带来一些问题。 问题之一是当容器崩溃时文件丢失。
kubelet 会重新启动容器,但容器会以干净的状态重启。 第二个问题会在同一 Pod 中运行多个容器并共享文件时出现。
Kubernetes Volume 这一抽象概念能够解决这两个问题。
Kubernetes Volume 为 Pod 中的容器提供了一种通过文件系统访问和共享数据的方式。存在不同类别的卷,你可以将其用于各种用途,例如:
- 基于 ConfigMap 或 Secret 填充配置文件
- 为 Pod 提供一些临时的涂销空间
- 在同一个 Pod 中的两个不同容器之间共享文件系统
- 在两个不同的 Pod 之间共享文件系统(即使这些 Pod 运行在不同的节点上)
- 持久化存储数据,这样即使 Pod 重启或被替换,存储的数据仍然可用
- 基于容器所在 Pod 的详细信息,将配置信息传递给运行在容器中的应用 (例如告诉边车容器:Pod 运行在哪个命名空间)
- 以只读权限访问另一个容器镜像中的数据
数据共享可以发生在容器内不同本地进程之间,或在不同容器之间,或在多个 Pod 之间。
# 2. 卷的类型
Kubernetes 支持很多类型的卷。 Pod 可以同时使用任意数目的卷类型。 临时卷类型的生命周期与 Pod 相同,但持久卷可以比 Pod 的存活期长。 当 Pod 不再存在时,Kubernetes 也会销毁临时卷;不过 Kubernetes 不会销毁持久卷。 对于给定 Pod 中任何类型的卷,在容器重启期间数据都不会丢失。
卷的核心是一个目录,其中可能存有数据,Pod 中的容器可以访问该目录中的数据。 所采用的不同卷的类型将决定该目录如何形成的、使用何种介质保存数据以及目录中存放的内容。 常用的卷类型有 configMap、emptyDir、local、nfs、secret 等。
ConfigMap:可以将配置文件以键值对的形式保存到 ConfigMap 中,并且可以在 Pod 中以文件或环境变量的形式使用。 ConfigMap 可以用来存储不敏感的配置信息,如应用程序的配置文件。EmptyDir:是一个空目录,可以在 Pod 中用来存储临时数据,当 Pod 被删除时,该目录也会被删除。Local:将本地文件系统的目录或文件映射到 Pod 中的一个 Volume 中,可以用来在 Pod 中共享文件或数据。NFS:将网络上的一个或多个 NFS 共享目录挂载到 Pod 中的 Volume 中,可以用来在多个 Pod 之间共享数据。Secret:将敏感信息以密文的形式保存到 Secret 中,并且可以在 Pod 中以文件或环境变量的形式使用。 Secret 可以用来存储敏感信息,如用户名密码、证书等。
# 3. 使用方式
使用卷时, 在 .spec.volumes 字段中设置为 Pod 提供的卷,并在 .spec.containers[*].volumeMounts 字段中声明卷在容器中的挂载位置。
容器中的进程看到的文件系统视图是由它们的容器镜像的初始内容以及挂载在容器中的卷(如果定义了的话)所组成的。
其中根文件系统同容器镜像的内容相吻合。 任何在该文件系统下的写入操作,如果被允许的话,都会影响接下来容器中进程访问文件系统时所看到的内容。
apiVersion: v1
kind: Pod
metadata:
name: configmap-pod
spec:
containers:
- name: test
image: busybox:1.28
volumeMounts:
..........
volumes:
............
2
3
4
5
6
7
8
9
10
11
12
# 4. 常见类型
# 4.1 emptyDir
apiVersion: v1
kind: Pod
metadata:
name: emptydir-example
spec:
containers:
- name: writer
image: busybox
command: [ "/bin/sh", "-c", "echo 'Hello World!' > /data/hello.txt ; sleep 3600" ]
volumeMounts:
- name: shared-data
mountPath: /data
- name: reader
image: busybox
command: [ "/bin/sh", "-c", "cat /data/hello.txt ; sleep 3600" ]
volumeMounts:
- name: shared-data
mountPath: /data
volumes:
- name: shared-data
emptyDir: { }
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
总结: emptyDir 是 Host 上创建的临时目录,其优点是能够方便地为 Pod 中的容器提供共享存储,不需要额外的配置。 它不具备持久性,如果Pod 不存在了,emptyDir 也就没有了。根据这个特性, emptyDir 特别适合 Pod 中的容器需要临时共享存储空间的场景,比如前面的生产者消费者用例。
# 4.2 hostPath
apiVersion: v1
kind: Pod
metadata:
name: busybox-hostpath
spec:
containers:
- name: busybox
image: busybox
command: [ "/bin/sh", "-c", "echo 'hello' > /data/data.txt && sleep 3600" ]
volumeMounts:
- name: data
mountPath: /data
volumes:
- name: data
hostPath:
path: /data/hostpath
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
总结: 如果 Pod 被销毀了,hostPath 对应的目录还是会被保留,从这一点来看,hostPath 的持久性比emptyDir 强。 不过一旦Host 崩溃,hostPath 也就无法访问了。但是这种方式也带来另外一个问题增加了 pod 与节点的耦合。
# 4.3 nfs
nfs: network filesystem : 网络文件存储系统
apiVersion: v1
kind: Pod
metadata:
name: nfs-test
spec:
containers:
- name: busybox
image: busybox
command: [ "/bin/sh", "-c", "while true; do sleep 3600; done" ]
volumeMounts:
- name: nfs-volume
mountPath: /mnt/nfs
volumes:
- name: nfs-volume
nfs:
server: <NFS_SERVER_IP>
path: /path/to/nfs/share
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
总结: 相对于 emptyDir 和 hostPath,这种 volume 类型的最大特点就是不依赖 Kuberees Volume 的底层基础设施由独立的存储系统管理, 与 Kubernetes 集群是分离的。数据被持久化后,即使整个 Kubernetes 崩溃也不会受损。 当然,运维这样的存储系统通常不是一项简单的工作,特别是对可靠性、可用性和扩展性 有较高要求的时候。
# 5. PV & PVC
# 5.1 问题
Volume 提供了非常好的数据持久化方案,不过在可管理性上还有不足。前面 nfs 例子来说,要使用 Volume, Pod 必须事先知道以下信息:
- 当前的 Volume 类型并明确 Volume 已经创建好。
- 必须知道 Volume 的具体地址信息。
但是 Pod 通常是由应用的开发人员维护,而 Volume 则通常是由存储系统的管理员维护。开发人员要获得上面的信息, 要么询问管理员,要么自己就是管理员。这样就带来一个管理上的问题:应用开发人员和系统管理员的职责耦合在一起了。 如果系统规模较小或者对于开发环境,这样的情况还可以接受,当集群规模变大,特别是对于生产环境, 考虑到效率和安全性, 这就成了必须要解决的问题。
# 5.2 PV & PVC
Kubernetes 给出的解决方案是 Persistent Volume & Persistent Volume Claim。
PersistentVolume(PV)是外部存储系统中的一块存储空间,由管理员创建和维护。与 Volume 一样,PV 具有持久性,生命周期独立于 Pod。PersistentVolumeClaim (PVC)是对 PV 的申请 (Claim)。PVC 通常由普通用户创建和维护。 需要为 Pod 分配存储资源时,用户可以创建一个PVC,指明存储资源的容量大小和访问模式 (比如只读)等信息, Kubernetes 会查找并提供满足条件的 PV。有了 PersistentVolumeClaim,用户只需要告诉 Kubernetes 需要什么样的存储资源, 而不必关心真正的空间从哪里分配、如何访问等底层细节信息。这些 Storage Provider 的底层信息交给管理员来处理, 只有管理员才应该关心创建 PersistentVolume 的细节信息。
简单来说,就是职责明确划分,PV 创建和管理资源,PVC 只需要使用即可。
# 5.3 基本使用
# 创建 PV
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 1Gi # 指定容量大小
accessModes: # 访问模式
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: nfs
nfs:
path: /{nfs-server目录名称}
server: { nfs-server IP 地址 }
2
3
4
5
6
7
8
9
10
11
12
13
14
accessModes: 支持的访问模式有3种:
ReadWriteOnce:表示 PV 能以 readwrite 模式 mount 到单个节点- 这个 PV 只能被某个节点以读写方式挂载,意味着这个 PV 只能被一个 Pod 挂载到某个节点上, 并且这个 Pod 可以对这个 PV 进行读写操作。如果尝试在其他节点上挂载这个 PV,就会失败。
ReadOnlyMany:表示 PV 能以 read-only 模式 mount 到多个节点- 这个 PV 能被多个节点以只读方式挂载,意味着这个 PV 可以被多个 Pod 挂载到多个节点上。
ReadWriteMany:表示 PV 能以 read-write 模式 mount 到多个节点。- 这个 PV 能被多个节点以读写方式挂载,意味着这个 PV 可以被多个 Pod 挂载到多个节点上。
persistentVolumeReclaimPolicy: 指定当 PV 的回收策略支持的策略有3种:
- Retain:在 PVC 被删除后,保留 PV 和其数据,手动清理 PV 中的数据。
- Delete:在 PVC 被删除后,自动删除 PV 和其数据。
- Recycle:在 PVC 被删除后,通过删除 PV 中的数据来准备 PV 以供重新使用。
值得注意的是,persistentVolumeReclaimPolicy 只适用于一些类型的 PV,如 NFS、HostPath、iSCSI 等。 对于一些云平台提供的存储,如 AWS EBS 和 Azure Disk,由于底层提供商会自动处理 PV 的回收问题,因此该属性不适用。
- storageClassName: 指定 PV 的class 为 nfs。相当于为 PV 设置了一个分类,PVC可以指定 class 申请相应 class 的 PV。
# 创建 PVC
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: nfs # 通过名字进行选择
#selector: # 通过标签形式
# matchLabels:
# pv-name: nfs-pv
2
3
4
5
6
7
8
9
10
11
12
13
14
# 使用 PVC
apiVersion: v1
kind: Pod
metadata:
name: busybox-nfs
spec:
containers:
- name: busybox
image: busybox
command: [ "/bin/sh" ]
args: [ "-c", "while true; do echo 'Hello NFS!' >> /data/index.html; sleep 1; done" ]
volumeMounts:
- name: nfs-volume
mountPath: /data
volumes:
- name: nfs-volume
persistentVolumeClaim:
claimName: nfs-pvc # 指定 PVC
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 5.4 动态供给StorageClass
在前面的例子中,我们提前创建了PV,然后通过 PVC 申请 PV 并在 Pod 中使用,这种方式叫作静态供给 ( Static Provision)
与之对应的是动态供给 (Dynamical Provision),即如果没有满足PVC 条件的PV,会动态创建 PV。
相比静态供给,动态供给有明显的优势:不需要提前创建 PV,减少了管理员 的工作量,效率高。动态供给是通过 StorageClass 实现的,
StorageClass 定义了如何创建 PV,但需要注意的是每个 StorageClass 都有一个制备器(Provisioner),
用来决定使用哪个卷插件制备 PV。 该字段必须指定
(https://kubernetes.io/zh-cn/docs/concepts/storage/storage-classes/ (opens new window))
才能实现动态创建。
下面我们以 NFS 为例:
定义 NFS Provider
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: kube-system
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels:
app: nfs-client-provisioner
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner
containers:
- name: nfs-client-provisioner
image: chronolaw/nfs-subdir-external-provisioner:v4.0.2
volumeMounts:
- name: nfs-client-root
mountPath: /persistentvolumes
env:
- name: PROVISIONER_NAME
value: k8s-sigs.io/nfs-subdir-external-provisioner
- name: NFS_SERVER
value: 10.15.0.25
- name: NFS_PATH
value: /root/nfs/data
volumes:
- name: nfs-client-root
nfs:
server: 10.15.0.25
path: /root/nfs/data
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: kube-system
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-client-provisioner-runner
rules:
- apiGroups: [ "" ]
resources: [ "nodes" ]
verbs: [ "get", "list", "watch" ]
- apiGroups: [ "" ]
resources: [ "persistentvolumes" ]
verbs: [ "get", "list", "watch", "create", "delete" ]
- apiGroups: [ "" ]
resources: [ "persistentvolumeclaims" ]
verbs: [ "get", "list", "watch", "update" ]
- apiGroups: [ "storage.k8s.io" ]
resources: [ "storageclasses" ]
verbs: [ "get", "list", "watch" ]
- apiGroups: [ "" ]
resources: [ "events" ]
verbs: [ "create", "update", "patch" ]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: kube-system
roleRef:
kind: ClusterRole
name: nfs-client-provisioner-runner
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: kube-system
rules:
- apiGroups: [ "" ]
resources: [ "endpoints" ]
verbs: [ "get", "list", "watch", "create", "update", "patch" ]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: kube-system
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
# replace with namespace where provisioner is deployed
namespace: kube-system
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
定义 StorageClass
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: mysql-nfs-sc
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner
parameters:
onDelete: "remain"
2
3
4
5
6
7
使用 StorageClass 动态创建
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
labels:
app: mysql
spec:
serviceName: mysql # headless 无头服务 保证网络标识符唯一 必须存在
replicas: 1
template:
metadata:
name: mysql
labels:
app: mysql
spec:
containers:
- name: mysql
image: mysql/mysql-server:8.0
imagePullPolicy: IfNotPresent
env:
- name: MYSQL_ROOT_PASSWORD
value: root
volumeMounts:
- mountPath: /var/lib/mysql # 自己容器写入数据目录
name: data # 保存到指定一个变量中 变量名字就是 data
ports:
- containerPort: 3306
restartPolicy: Always
volumeClaimTemplates: # 声明动态创建数据卷模板
- metadata:
name: data # 数据卷变量名称
spec:
accessModes: # 访问模式
- ReadWriteMany
storageClassName: mysql-nfs-sc # 使用哪个 storage class 模板存储数据
resources:
requests:
storage: 2G
selector:
matchLabels:
app: mysql
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41