一、存储卷概述
应用程序在处理请求时,可根据其对当前请求的处理是否受影响与此前的请求,将应用划分为有状态和无状态应用两种。微服务体系中,各种应用均被拆分了众多微服务或更小的应用模块,因此往往会存在为数不少的有状态应用,于是数据持久化几乎是必然只需。
Kubernetes提供的存储卷(volume)隶属于Pod资源,共享于Pod内的所有容器,可用于在容器的文件系统之外存储应用程序的相关数据,甚至还可以独立于Pod的生命周期之外实现数据持久化。
Pod本身具有生命周期,故其内部运行的容器及其相关数据自身均无法持久存在。Docker支持配置容器使用存储卷将数据持久存储于容器自身文件系统之外的存储空间中,它们可以是节点文件系统或网络文件系统之上的存储系统,不过,其存储卷是与Pod资源绑定而非容器。简单来说,存储卷是定义在Pod资源之上的、可被其内部的所有容器挂载的共享目录,它关联至某外部的存储设备之上的存储空间,从而独立于容器自身的文件系统,而数据是否具有持久化能力则取决于存储卷自身是否支持持久机制。
Kubernetes支持的存储卷类型
二、临时存储卷
Kubernetes支持存储卷类型,emptyDir存储卷的生命周期与其所属的Pod对象相同,它无法脱离Pod对象的生命周期提供数据存储功能,因此emptyDir通常仅用于数据缓存或临时存储。不过,基于emptyDir构建的gitRepo存储卷可以在Pod对象的生命周期起始时从相应的Git仓库中复制相应的数据文件到底层的emptrDir中,从而使得它具有一定意义上的持久性;但是,自Kubernetes 1.12版本起,girRepo存储卷已被废弃,所以不再进行说明。
emptyDir存储卷是Pod对象生命周期中的一个临时存储目录,类似于Docker上的"docker挂载卷",在Pod对象启动时即被创建,而在Pod对象被移除时会被一并删除。不具持久能力的emptyDir存储卷只能用于某些特殊场景。例如,同一Pod内的多个容器间文件共享,或者作为容器数据的临时存储目录用于数据缓存系统等等。
emptyDir存储卷则定义于.spec.volumes.emptyDir嵌套字段中,可用字段主要包含两个,具体如下:
medium:此目录所在的存储介质的类型,可取值为"default"或"Memory",默认为default,表示使用节点的默认存储介质;"Memory"表示使用基于RAM的临时文件系统tmpfs,空间受限于内存,但是性能非常好,通过用于为容器中的应用提供缓存空间。
sizeLimit:当前存储卷的空间限额,默认值为nil,表示不限制;不过在medium字段值为"Memory"时建议务必使用此限额。
1)编写emptyDir的yaml文件
]# cat emptydir.yaml
apiVersion: v1
kind: Pod
metadata:
name: vol-emptydir-pod
spec:
volumes:
- name: html
emptyDir: {}
containers:
- name: nginx
image: nginx:1.12-alpine
imagePullPolicy: IfNotPresent
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
- name: pagegen
image: alpine
imagePullPolicy: IfNotPresent
volumeMounts:
- name: html
mountPath: /html
command: ["/bin/sh", "-c"]
args:
- while true; do
echo $(homename) $(date) >> /html/index.html;
sleep 10;
done
]# kubectl apply -f emptydir.yaml
pod/vol-emptydir-pod created
2)查看Pod详细信息
]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
vol-emptydir-pod 2/2 Running 0 4s 10.244.1.95 node1 <none> <none>
]# kubectl describe pods vol-emptydir-pod
Name: vol-emptydir-pod
Namespace: default
Priority: 0
Node: node1/172.16.2.101
Start Time: Thu, 27 Aug 2020 19:18:58 +0800
Labels: <none>
Annotations: Status: Running
IP: 10.244.1.95
IPs:
IP: 10.244.1.95
Containers:
nginx:
Container ID: docker://3f912041344a89bc846b0b0b669e7a8f5a23b121d07600a79f8eb3deaea937ec
Image: nginx:1.12-alpine
Image ID: docker-pullable://nginx@sha256:db5acc22920799fe387a903437eb89387607e5b3f63cf0f4472ac182d7bad644
Port: <none>
Host Port: <none>
State: Running
Started: Thu, 27 Aug 2020 19:18:59 +0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/usr/share/nginx/html from html (rw)
/var/run/secrets/kubernetes.io/serviceaccount from default-token-47pch (ro)
pagegen:
Container ID: docker://937feb81e06f08dfd8b6cc4270529c3a7bd103d6357d51c198704656c5c21203
Image: alpine
Image ID: docker-pullable://alpine@sha256:185518070891758909c9f839cf4ca393ee977ac378609f700f60a771a2dfe321
Port: <none>
Host Port: <none>
Command:
/bin/sh
-c
Args:
while true; do echo $(homename) $(date) >> /html/index.html; sleep 10; done
State: Running
Started: Thu, 27 Aug 2020 19:18:59 +0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/html from html (rw)
/var/run/secrets/kubernetes.io/serviceaccount from default-token-47pch (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
html:
Type: EmptyDir (a temporary directory that shares a pod's lifetime)
Medium:
SizeLimit: <unset>
default-token-47pch:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-47pch
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled <unknown> default-scheduler Successfully assigned default/vol-emptydir-pod to node1
Normal Pulled <invalid> kubelet, node1 Container image "nginx:1.12-alpine" already present on machine
Normal Created <invalid> kubelet, node1 Created container nginx
Normal Started <invalid> kubelet, node1 Started container nginx
Normal Pulled <invalid> kubelet, node1 Container image "alpine" already present on machine
Normal Created <invalid> kubelet, node1 Created container pagegen
Normal Started <invalid> kubelet, node1 Started container pagegen
3)访问Pod
]# curl 10.244.1.95
Thu Aug 27 11:18:59 UTC 2020
Thu Aug 27 11:19:09 UTC 2020
三、节点存储卷hostPath
hostPath类型的存储卷是指将工作节点上某文件系统的目录或文件挂载于Pod中的一种存储卷,它可以独立于Pod资源的生命周期,因而具有持久性。但它是工作节点本地的存储空间,仅适用于特定情况下的存储卷要求,例如,将工作节点上的文件系统关联为Pod的存储卷,从而使得容器访问节点文件系统上的数据。这一点在运行有管理任务的系统级Pod资源需要访问节点上的文件时尤为有用。
配置hostPath存储卷的嵌套字段共有两个:一个是用于指定工作节点上的目录路径的必选字段path;另一个是指定存储卷类型的type,它支持使用的存储卷类型包含如下几种:
DirectoryOrCreate:指定的路径不存在时自动将其创建为权限是0755的空目录,属主属组均为kubelet。
Directory:必须存在的目录路径。
FileOrCreate:指定的路径不存在时自动将其创建为权限是0644的空文件,属主和属组同是kubelet。
File:必须是存在的文件路径。
Socket:必须存在的Socket文件路径。
CharDevice:必须存在的字符设备文件路径。
BlockDevice:必须存在的块设备文件路径。
1)编写hostPath存储卷yaml文件
]# cat hostPath.yaml
apiVersion: v1
kind: Pod
metadata:
name: vol-hostpath-pod
spec:
containers:
- name: filebeat
image: ikubernetes/filebeat:5.6.7-alpine
imagePullPolicy: IfNotPresent
env:
- name: REDIS_HOST
value: redis.ilinux.io:6379
- name: LOG_LEVEL
value: info
volumeMounts:
- name: varlog
mountPath: /var/log
- name: socket
mountPath: /var/run/docker.sock
- name: varlibdockercontainers
mountPath: /var/lib/docker/containers
readOnly: true
terminationGracePeriodSeconds: 30
volumes:
- name: varlog
hostPath:
path: /var/log
- name: varlibdockercontainers
hostPath:
path: /var/lib/docker/containers
- name: socket
hostPath:
path: /var/run/docker.sock
]# kubectl apply -f hostPath.yaml
pod/vol-hostpath-pod created
2)查看Pod详细信息
]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
vol-hostpath-pod 1/1 Running 0 10s 10.244.1.96 node1 <none> <none>
]# kubectl describe pods vol-hostpath-pod
Name: vol-hostpath-pod
Namespace: default
Priority: 0
Node: node1/172.16.2.101
Start Time: Thu, 27 Aug 2020 20:03:24 +0800
Labels: <none>
Annotations: Status: Running
IP: 10.244.1.96
IPs:
IP: 10.244.1.96
Containers:
filebeat:
Container ID: docker://59adc40615571579a6135abaac5dcc39ef9ee711d70427f5432fbb7329172c24
Image: ikubernetes/filebeat:5.6.7-alpine
Image ID: docker-pullable://ikubernetes/filebeat@sha256:3957f67b612aa8628f643f8ede02b71bfbabf34892ef136f1e5ee18bbc0775aa
Port: <none>
Host Port: <none>
State: Running
Started: Thu, 27 Aug 2020 20:03:25 +0800
Ready: True
Restart Count: 0
Environment:
REDIS_HOST: redis.ilinux.io:6379
LOG_LEVEL: info
Mounts:
/var/lib/docker/containers from varlibdockercontainers (ro)
/var/log from varlog (rw)
/var/run/docker.sock from socket (rw)
/var/run/secrets/kubernetes.io/serviceaccount from default-token-47pch (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
varlog:
Type: HostPath (bare host directory volume)
Path: /var/log
HostPathType:
varlibdockercontainers:
Type: HostPath (bare host directory volume)
Path: /var/lib/docker/containers
HostPathType:
socket:
Type: HostPath (bare host directory volume)
Path: /var/run/docker.sock
HostPathType:
default-token-47pch:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-47pch
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled <unknown> default-scheduler Successfully assigned default/vol-hostpath-pod to node1
Normal Pulled <invalid> kubelet, node1 Container image "ikubernetes/filebeat:5.6.7-alpine" already present on machine
Normal Created <invalid> kubelet, node1 Created container filebeat
Normal Started <invalid> kubelet, node1 Started container filebeat
.
使用hostPath存储卷时需要注意,不同的节点上的文件获取并不完全相同,于是,那些要求事先必须存在的目录或文件的满足状态也可能会有所不同;另外,基于资源可用状态的调度器调度Pod时,hostPath资源的可用性不会被考虑在内;再者,在节点中创建的文件或目录默认仅有root可写,若是期望容器内的进程拥有写权限,则要么将它运行为特权容器,要么修改节点上目录路径的权限。
那些并非执行系统管理级任务的且不受控于DaemonSet控制器的无状态应用在Pod资源被重新调度至其他节点运行时,此前创建的文件或目录大多不会存在。因此,hostPath存储卷虽然能持久保存数据,但是对于被调度器按需调度的应用来说并不适用,这时则需要用到的是独立于集群节点的持久性存储卷,即网络存储卷。
四、NFS网络存储卷
NFS即网络文件系统(Network File System),它是一种分布式文件协议;Kubernetes的NFS文件系统用于将某事先存在的NFS服务器上导出(export)的存储空间挂载到Pod中以供容器使用。与emptyDir不同的是,NFS存储卷在Pod对象终止后仅是被卸载而非删除。另外,NFS是文件系统级别的共享服务,它支持同时存在的多路挂载请求。定义NFS存储卷时,常用到以下字段:
server
path
readOnly
1)编写NFS存储卷的yaml文件
]# cat nfs.yaml
apiVersion: v1
kind: Pod
metadata:
name: vol-nfs-pod
labels:
app: redis
spec:
containers:
- name: redis
image: redis:4-alpine
imagePullPolicy: IfNotPresent
ports:
- containerPort: 6379
name: redisport
volumeMounts:
- mountPath: /data
name: redisdata
volumes:
- name: redisdata
nfs:
server: 172.16.2.250
path: /data/redis
readOnly: false
]# kubectl apply -f nfs.yaml
pod/vol-nfs-pod created
2)查看Pod详细信息
]# kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
vol-nfs-pod 1/1 Running 0 3s 10.244.1.101 node1 <none> <none>
]# kubectl describe pods vol-nfs-pod
Name: vol-nfs-pod
Namespace: default
Priority: 0
Node: node1/172.16.2.101
Start Time: Fri, 28 Aug 2020 09:40:12 +0800
Labels: app=redis
Annotations: Status: Running
IP: 10.244.1.101
IPs:
IP: 10.244.1.101
Containers:
redis:
Container ID: docker://aee82f9c44a7141548e4f5524304ccc5ab92961a1f72925747da59952bc16bb8
Image: redis:4-alpine
Image ID: docker-pullable://redis@sha256:aaf7c123077a5e45ab2328b5ef7e201b5720616efac498d55e65a7afbb96ae20
Port: 6379/TCP
Host Port: 0/TCP
State: Running
Started: Fri, 28 Aug 2020 09:40:14 +0800
Ready: True
Restart Count: 0
Environment: <none>
Mounts:
/data from redisdata (rw)
/var/run/secrets/kubernetes.io/serviceaccount from default-token-47pch (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
redisdata:
Type: NFS (an NFS mount that lasts the lifetime of a pod)
Server: 172.16.2.250
Path: /data/redis
ReadOnly: false
default-token-47pch:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-47pch
Optional: false
QoS Class: BestEffort
Node-Selectors: <none>
Tolerations: node.kubernetes.io/not-ready:NoExecute for 300s
node.kubernetes.io/unreachable:NoExecute for 300s
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Scheduled <unknown> default-scheduler Successfully assigned default/vol-nfs-pod to node1
Normal Pulled <invalid> kubelet, node1 Container image "redis:4-alpine" already present on machine
Normal Created <invalid> kubelet, node1 Created container redis
Normal Started <invalid> kubelet, node1 Started container redis
3)进入容器对Pod做数据持久化
]# kubectl exec -it vol-nfs-pod redis-cli
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
127.0.0.1:6379> set myname "Kubernetes"
OK
127.0.0.1:6379> BGSAVE
Background saving started
127.0.0.1:6379> exit
4)删除容器再重新创建查看数据是否存在
]# kubectl delete -f nfs.yaml
pod "vol-nfs-pod" deleted
]# kubectl apply -f nfs.yaml
pod/vol-nfs-pod created
# kubectl exec -it vol-nfs-pod redis-cli
kubectl exec [POD] [COMMAND] is DEPRECATED and will be removed in a future version. Use kubectl kubectl exec [POD] -- [COMMAND] instead.
127.0.0.1:6379> KEYS *
1) "myname"
127.0.0.1:6379> GET myname
"Kubernetes"
127.0.0.1:6379> exit
.
从上面的命令结果中可以看出,此前创建的键myname及其数据在Pod资源重建之后依然存在,这表明在删除Pod资源时,其关联的外部存储卷并不会被一同删除。如果需要清除此类的数据,需要用户通过存储系统的管理接口手动进行。
5)报错及解决方法
如果启动容器时容器状态一直为"CrashLoopBackOff"状态,查看日志为如下报错:
]# kubectl logs vol-nfs-pod
chown: .: Operation not permitted
请在NFS服务器上的exports文件中添加如下挂载选项即可解决
/data/redis *(rw,sync,no_root_squash)