Volumes
hostPath
- 将节点上的文件或目录挂载到 Pod 上,即使 Pod 被删除后重启,也可以重新加载到该目录,该目录下的文件不会丢失
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@k8s-master1 test]# vim po-hostpath-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: po-hostpath-test
spec:
volumes:
- name: hostpath-volume
hostPath:
path: /data # Pod 所在节点的目录
type: DirectoryOrCreate # 检查类型,在挂载前对挂载目录做什么检查操作,有多种选项,默认为空字符串,不做任何检查
# 空字符串:默认类型,不做任何检查
# DirectoryOrCreate:如果给定的 path 不存在,就创建一个权限 755 的空目录
# Directory:这个目录必须存在
# FileOrCreate:如果给定的文件不存在,则创建一个权限为 644 空文件
# File:这个文件必须存在
# Socket:UNIX 套接字,必须存在
# CharDevice:字符设备,必须存在
# BlockDevice:块设备,必须存在
containers:
- image: nginx:1.7.9
name: po-hostpath-test-c
volumeMounts:
- name: hostpath-volume # 挂载的 volume
mountPath: /c-data # 挂载到容器中的目录
1
2
3
4
5
6
7
8
9
10
11
12
13
[root@k8s-master1 test]# kubectl create -f po-hostpath-test.yaml
pod/po-hostpath-test created
[root@k8s-master1 test]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
po-hostpath-test 1/1 Running 0 15s 10.244.8.220 k8s-node2.zhch.lan <none> <none>
[root@k8s-node2 ~]# cd /data
[root@k8s-node2 data]# echo 'hostpath......' > a.txt
[root@k8s-master1 test]# kubectl exec -it po-hostpath-test -- bash
root@po-hostpath-test:/# cat /c-data/a.txt
hostpath......
emptyDir
- EmptyDir 主要用于一个 Pod 中不同的 Container 共享数据使用,由于只是在 Pod 内部使用,因此与其他 volume 比较大的区别是,如果 Pod 被删除了,那么 emptyDir 也会被删除,不具备持久化存储能力。
- 存储介质可以是任意类型,如 SSD、磁盘或网络存储。可以将 emptyDir.medium 设置为 Memory 让 k8s 使用 tmpfs(内存支持文件系统),速度比较快,但是重启 tmpfs 节点时,数据会被清除,且设置的大小会计入到 Container 的内存限制中。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[root@k8s-master1 test]# vim po-emptydir-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: po-emptydir-test
spec:
volumes:
- name: cache-volume
emptyDir: {}
containers:
- image: nginx:1.7.9
name: po-emptydir-test-c1
volumeMounts:
- name: cache-volume
mountPath: /log-cache # 挂载到容器中的目录
- image: tomcat:10.1.17-jre21
name: po-emptydir-test-c2
volumeMounts:
- name: cache-volume
mountPath: /data-cache
1
2
3
4
5
6
7
8
9
10
[root@k8s-master1 test]# kubectl create -f po-emptydir-test.yaml
pod/po-emptydir-test created
[root@k8s-master1 test]# kubectl exec -it po-emptydir-test -c po-emptydir-test-c1 -- bash
root@po-emptydir-test:/# echo 'empty dir test ......' > /log-cache/cache.txt
root@po-emptydir-test:/# exit
exit
[root@k8s-master1 test]# kubectl exec -it po-emptydir-test -c po-emptydir-test-c2 -- bash
root@po-emptydir-test:/usr/local/tomcat# cat /data-cache/cache.txt
empty dir test ......
configMap
secret
- configMap 和 secret 类型的挂载卷详见上一章
配置管理
的内容
NFS 挂载
- nfs 卷能将 NFS (网络文件系统) 挂载到你的 Pod 中。
- 生产环境,推荐给 NFS 共享目录单独挂载一块硬盘或单独的磁盘分区。
- 生产环境,推荐搭建 NFS 双机热备高可用环境,避免 NFS 单点故障
安装 NFS 服务
- 正常情况,应该准备另外的 NFS 服务器
- 这里为了简单,使用 k8s-master1 充当 NFS 服务器
1
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
[root@k8s-master1 ~]# yum install -y nfs-utils
[root@k8s-master1 ~]# systemctl enable --now nfs-server
# 查看 nfs 版本
[root@k8s-master1 ~]# cat /proc/fs/nfsd/versions
+3 +4 +4.1 +4.2
# 创建共享目录
[root@k8s-master1 ~]# mkdir -p /root/nfs_data
[root@k8s-master1 ~]# cd /root/nfs_data
[root@k8s-master1 nfs_data]# mkdir rw
[root@k8s-master1 nfs_data]# mkdir ro
# 设置共享目录
[root@k8s-master1 nfs_data]# vim /etc/exports
/root/nfs_data/rw 192.168.99.0/24(rw,sync,no_subtree_check,no_root_squash)
/root/nfs_data/ro 192.168.99.0/24(ro,sync,no_subtree_check,no_root_squash)
# 重启 NFS
[root@k8s-master1 nfs_data]# systemctl restart nfs-server
[root@k8s-master1 nfs_data]# exportfs -v
/root/nfs_data/rw
192.168.99.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,rw,secure,no_root_squash,no_all_squash)
/root/nfs_data/ro
192.168.99.0/24(sync,wdelay,hide,no_subtree_check,sec=sys,ro,secure,no_root_squash,no_all_squash)
# 测试(在另一台服务器上挂载 NFS 共享目录)
[root@Test ~]# yum install -y nfs-utils
[root@Test ~]# systemctl start nfs-server
[root@Test ~]# mkdir -p /mnt/nfs_test/rw
[root@Test ~]# mount -t nfs k8s-master1.zhch.lan:/root/nfs_data/rw /mnt/nfs_test/rw
[root@Test ~]# cd /mnt/nfs_test/rw
[root@Test rw]# echo 'nfs test...' > test.txt
[root@k8s-master1 nfs_data]# cat /root/nfs_data/rw/test.txt
nfs test...
[root@Test ~]# ls /mnt/nfs_test/rw
test.txt
[root@Test ~]# umount /mnt/nfs_test/rw
[root@Test ~]# ls /mnt/nfs_test/rw
[root@Test ~]#
使用 NFS 挂载卷
1
2
3
# 在所有 Kubernetes Node 节点安装 NFS (仅安装,无需启动)
[root@k8s-node1 ~]# yum install -y nfs-utils
[root@k8s-node2 ~]# yum install -y nfs-utils
1
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
[root@k8s-master1 test]# vim po-nfs-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: po-nfs-test
spec:
volumes:
- name: nfs-volume
nfs:
server: k8s-master1.zhch.lan # 网络存储服务 NFS 的地址
path: /root/nfs_data/rw # 网络存储目录,可选择 NFS 共享目录或其子目录,目录必须存在
readOnly: false # 是否只读
containers:
- image: nginx:1.7.9
name: po-nfs-test-c
volumeMounts:
- name: nfs-volume
mountPath: /my-nfs-data
[root@k8s-master1 test]# kubectl create -f po-nfs-test.yaml
pod/po-nfs-test created
[root@k8s-master1 test]# kubectl exec -it po-nfs-test -- bash
root@po-nfs-test:/#
root@po-nfs-test:/# ls /my-nfs-data
test.txt
root@po-nfs-test:/# cat /my-nfs-data/test.txt
nfs test...
PV 与 PVC
- 持久卷(PersistentVolume,PV)是集群中由管理员配置的一段网络存储。
- 持久卷申领(PersistentVolumeClaim,PVC)表达的是用户对存储的请求。
- StorageClass:充当 PV 的模板,自动为 PVC 创建 PV
- StorageClass 不受 namespace 作用域限制
- PV 生命周期:
- Available(可用): 表示可用状态,还未被任何 PVC 绑定
- Bound(已绑定): 表示 PV 已经被 PVC 绑定
- Released(已释放): 表示 PVC 被删除,但是资源还未被集群重新声明
- Failed(失败): 表示该 PV 的自动回收失败
- PV 回收策略:
- Retain (保留):保留数据,需要管理员手工清理数据
- Recycle(回收):清除 PV 中的数据,效果相当于执行 rm -rf /thevolume/*
- Delete (删除):与 PV 相连的后端存储完成 volume 的删除操作,这常见于云服务商的存储服务
- PV 访问模式:
- ReadWriteOnce(RWO):读写权限,但是只能被单个节点挂载
- ReadWriteMany(RWX):读写权限,可以被多个节点挂载
- ReadOnlyMany(ROX): 只读权限,可以被多个节点挂载
- ReadWriteOncePod:( Kubernetes v1.27 [beta] )卷可以被单个 Pod 以读写方式挂载。
需要注意的是,底层不同的存储类型支持的访问模式可能不同。
由于访问控制是 node 层,实际使用中,很容易遇到一个特殊的情况,即 2 个 Pods 对 RWO 的 PV 的读写,有时可行,有时不行。这是因为,若恰好 2 个 Pods 调度到同一个节点上,则这 2 个 Pods 就可以对数据同时访问。
- PersistentVolume.spec.storageClassName
- https://kubernetes.io/zh-cn/docs/concepts/storage/persistent-volumes/#class
- storageClassName 是该持久卷所属的 StorageClass 的名称。 空串表示该卷不属于任何 StorageClass。
- 每个 PV 可以属于某个类(Class),通过将其 storageClassName 属性设置为某个 StorageClass 的名称来指定。
- 特定 class 的 PV 卷只能绑定到请求该 class 存储卷的 PVC 申领。
- 未设置 storageClassName 的 PV 卷没有 class 设定,只能绑定到那些没有指定特定 storageClass 的 PVC 申领。
- PersistentVolume.spec.mountOptions
- https://kubernetes.io/zh-cn/docs/concepts/storage/persistent-volumes/#mount-options
- 当 PV 不支持指定的选项时会直接失败。比如 NFS 支持
hard
和nfsvers=4.1
等选项。
- PersistentVolume.spec.volumeMode : Filesystem 、Block
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
[root@k8s-master1 test]# vim pv0001.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv0001
spec:
capacity: # 容量
storage: 5Gi
volumeMode: Filesystem
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: any-scn # 可以是不存在的 StorageClass ,PV 的 storageClassName 的作用是与 pvc 的 storageClassName 进行匹配
mountOptions: # 挂载选项
- hard
- nfsvers=4.2
nfs:
server: k8s-master1.zhch.lan
path: /root/nfs_data/rw/pv0001
[root@k8s-master1 test]# kubectl create -f pv0001.yaml
persistentvolume/pv0001 created
[root@k8s-master1 test]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv0001 5Gi RWX Retain Available any-scn 10s
- PersistentVolumeClaim.spec.selector
- 目前, 若 PVC 设置了非空
selector
,则无法让集群为其动态制备 PV 卷。 - 目前 selector.matchExpressions 支持的操作符有 In、NotIn、Exists 和 DoesNotExist
- 目前, 若 PVC 设置了非空
1
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
[root@k8s-master1 test]# vim pvc0001.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: pvc0001
spec:
accessModes:
- ReadWriteMany # 此申领所要求的 PV 需支持的 accessModes
volumeMode: Filesystem # 此申领所要求的 PV 的 volumeMode
resources:
requests:
storage: 3Gi # 此申领所要求的 PV 的最小容量
storageClassName: any-scn # 此申领所要求的 PV 的 StorageClass 名称,如果不存在满足要求的 PV, 则由该 StorageClass 动态创建 PV
# 如果 storageClassName: "" ,则表示此 PVC 没有 StorageClass ,永远不会去动态创建 PV
# 如果没有设置 storageClassName,则此 PVC 的 StorageClass 是集群中的 DefaultStorageClass (注意集群中不一定存在 DefaultStorageClass)
#selector:
# matchLabels:
# release: "stable" # 要求 pv 具有名为`release`值为`stable`的标签
# matchExpressions:
# - {key: environment, operator: In, values: [dev]} # 要求 pv 具有标签`environment`且其值满足表达式: In [dev]
[root@k8s-master1 test]# kubectl create -f pvc0001.yaml
persistentvolumeclaim/pvc0001 created
[root@k8s-master1 test]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
pvc0001 Bound pv0001 5Gi RWX any-scn 5s
[root@k8s-master1 test]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pv0001 5Gi RWX Retain Bound default/pvc0001 any-scn 71s
- PVC 关联 Pod
1
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
[root@k8s-master1 test]# vim po-pvc-test.yaml
apiVersion: v1
kind: Pod
metadata:
name: po-pvc-test
spec:
volumes:
- name: pvc0001-volume
persistentVolumeClaim:
claimName: pvc0001
containers:
- image: nginx:1.7.9
name: po-pvc-test-c
volumeMounts:
- name: pvc0001-volume
mountPath: /usr/share/nginx/html
[root@k8s-master1 test]# kubectl create -f po-pvc-test.yaml
pod/po-pvc-test created
[root@k8s-master1 test]# kubectl get po -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
po-pvc-test 1/1 Running 0 29s 10.244.8.231 k8s-node2.zhch.lan <none> <none>
[root@k8s-master1 test]# curl 10.244.8.231
<html>
<head><title>403 Forbidden</title></head>
<body bgcolor="white">
<center><h1>403 Forbidden</h1></center>
<hr><center>nginx/1.7.9</center>
</body>
</html>
[root@k8s-master1 test]# echo 'pvc test...' > /root/nfs_data/rw/pv0001/index.html
[root@k8s-master1 test]# curl 10.244.8.231
pvc test...
创建 NFS StorageClass ,并设置为 Default Stroage Class
1
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
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
[root@k8s-master1 kubernetes]# vim nfs-storage.yaml
---
apiVersion: v1
kind: Namespace
metadata:
name: nfs-storage
---
apiVersion: v1
kind: ServiceAccount # 创建 ServiceAccount ,主要用来管理 NFS provisioner 在k8s集群中运行的权限
metadata:
name: nfs-client-provisioner
namespace: nfs-storage
---
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"]
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: run-nfs-client-provisioner
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: nfs-storage
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
namespace: nfs-storage
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
subjects:
- kind: ServiceAccount
name: nfs-client-provisioner
namespace: nfs-storage
roleRef:
kind: Role
name: leader-locking-nfs-client-provisioner
apiGroup: rbac.authorization.k8s.io
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-storage
annotations:
storageclass.kubernetes.io/is-default-class: "true"
provisioner: k8s-sigs.io/nfs-subdir-external-provisioner # 存储分配器的名字,自定义
parameters:
archiveOnDelete: "false" # 删除 pv 的时候, pv 的内容是偶要备份
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-client-provisioner
labels:
app: nfs-client-provisioner
namespace: nfs-storage
spec:
replicas: 1
selector:
matchLabels:
app: nfs-client-provisioner
strategy:
type: Recreate
template:
metadata:
labels:
app: nfs-client-provisioner
spec:
serviceAccountName: nfs-client-provisioner # ServiceAccount
containers:
- name: nfs-client-provisioner
# image: registry.k8s.io/sig-storage/nfs-subdir-external-provisioner:v4.0.2 # nfs存储分配器镜像
image: lank8s.cn/sig-storage/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 # 与 StorageClass 中自定义的存储分配器的名字保持一致
- name: NFS_SERVER
value: k8s-master1.zhch.lan # 与 template.spec.volumes 的 nfs.server 保持一致
- name: NFS_PATH
value: /root/nfs_data/rw/default # 与 template.spec.volumes 的 nfs.path 保持一致
volumes:
- name: nfs-client-root
nfs:
server: k8s-master1.zhch.lan
path: /root/nfs_data/rw/default
1
2
3
4
5
6
7
8
9
10
11
12
13
[root@k8s-master1 kubernetes]# kubectl apply -f nfs-storage.yaml
namespace/nfs-storage created
serviceaccount/nfs-client-provisioner created
clusterrole.rbac.authorization.k8s.io/nfs-client-provisioner-runner created
clusterrolebinding.rbac.authorization.k8s.io/run-nfs-client-provisioner created
role.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-client-provisioner created
storageclass.storage.k8s.io/managed-nfs-storage created
deployment.apps/nfs-client-provisioner created
[root@k8s-master1 kubernetes]# kubectl get sc
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
managed-nfs-storage (default) k8s-sigs.io/nfs-subdir-external-provisioner Delete Immediate false 28s
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 测试
[root@k8s-master1 test]# vim test-claim.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-claim
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
[root@k8s-master1 test]# kubectl create -f test-claim.yaml
persistentvolumeclaim/test-claim created
[root@k8s-master1 test]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-claim Bound pvc-a96c1c0c-a65a-4e47-997a-6ca99fc65790 1Mi RWX managed-nfs-storage 4s
[root@k8s-master1 test]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-a96c1c0c-a65a-4e47-997a-6ca99fc65790 1Mi RWX Delete Bound default/test-claim managed-nfs-storage 6s
排错过程记录
- 测试时发现 PVC 一直处理 Pending 状态
- describe pvc : waiting for a volume to be created, either by external provisioner “k8s-sigs.io/nfs-subdir-external-provisioner” or manually created by system administrator
- 检查 pod
- kubectl get po -n nfs-storage
- kubectl logs
nfs-client-provisioner-77f6478f79-f2rh8
-n nfs-storage- error retrieving resource lock nfs-storage/k8s-sigs.io-nfs-subdir-external-provisioner: endpoints “k8s-sigs.io-nfs-subdir-external-provisioner” is forbidden: User “system:serviceaccount:nfs-storage:nfs-client-provisioner” cannot get resource “endpoints” in API group “” in the namespace “nfs-storage”
- 至此,找到问题原因
附录
- github
- helm