持久卷
持久卷
PersistentVolume 子系统为用户和管理员提供了一个 API,总结了 Pods 如何提供和消耗存储的细节。为了最好地控制这个系统,引入了两个 API 功能。PersistentVolumee PersistentVolumeClaim。
-
PersistentVolume(PV)是集群中的一种资源,就像节点一样。但在这种情况下,它是一种存储资源。PV 是集群中的一块存储资源,它已经被管理员配置好了。PV 有一个独立于任何与之相关联的 pod 的生命周期。这个 API 允许存储的类型。NFS、ISCSI 或来自特定云提供商的存储。
-
PersistentVolumeClaim(PVC)类似于 Pod。Pods 消耗来自节点的资源,PVC 消耗来自 PV 的资源。但什么是 PVC 呢?它不过是用户创建的一个存储请求。
Local Persistent Volumes
在 Kubernetes 1.14 版本中,Local Persistent Volumes(以下简称 LPV)已变为正式版本(GA),LPV 的概念在 1.7 中被首次提出(alpha),并在 1.10 版本中升级到 beat 版本。现在用户终于可以在生产环境中使用 LPV 的功能和 API 了。
Local Persistent Volumes 代表了直接绑定在计算节点上的一块本地磁盘。Kubernetes 提供了一套卷插件(volume plugin)标准,使得 K8s 集群的工作负载可以使用多种块存储和文件存储。大部分磁盘插件都使用了远程存储,这是为了让持久化的数据与计算节点彼此独立,但远程存储通常无法提供本地存储那么强的读写性能。有了 LPV 插件,Kubernetes 负载现在可以用同样的 volume api,在容器中使用本地磁盘。
- 与 hostPath 的区别:hostPath 是一种 Volume,可以让 pod 挂载宿主机上的一个文件或目录(如果挂载路径不存在,则创建为目录或文件并挂载)。最大的不同在于调度器是否能理解磁盘和 node 的对应关系,一个使用 hostPath 的 pod,当他被重新调度时,很有可能被调度到与原先不同的 node 上,这就导致 pod 内数据丢失了。而使用 LPV 的 pod,总会被调度到同一个 node 上(否则就调度失败)。
NFS
我们将创建一个 NFS 类型的 PersistentVolume,为此我们将安装必要的软件包,在 GNU/Linux 上创建一个 NFS 服务器。
# Ubuntu
$ sudo apt-get install -y nfs-kernel-server
$ sudo apt-get install -y nfs-common
# CentOS
$ yum install -y nfs-utils
现在我们要在 elliot-01 节点上建立一个目录,并给予必要的权限来测试我们所说的一切。
$ sudo mkdir /opt/dados
$ sudo chmod 1777 /opt/dados/
$ sudo vim /etc/exports
# 添加如下行
/opt/dados *(rw,sync,no_root_squash,subtree_check)
# 将 NFS 配置应用于节点 elliot-01。
$ sudo exportfs -a
# 重启服务
$ sudo systemctl restart nfs-kernel-server
$ systemctl restart nfs
依然是在节点 elliot-01 中,我们将在这个目录下创建一个文件用于我们的测试。
$ sudo touch /opt/dados/FUNCIONA
还是在节点 elliot-01 中,我们将创建我们 PersistentVolume 的 manifest yaml。记得把服务器字段的 IP 地址改成节点 elliot-01 的 IP 地址。
sudo vim primeiro-pv.yaml
apiVersion : v1
kind : PersistentVolume
metadata :
name : prime-pv
spec :
capacity :
storage : 1Gi
accessModes :
- ReadWriteMany
persistentVolumeReclaimPolicy : Retain
nfs :
path : /opt/data
server : 10.138.0.2
readOnly : false
然后执行创建命令;
$ kubectl create -f primeiro-pv.yaml
persistentvolume/primeiro-pv created
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY ... AGE
primeiro-pv 1Gi RWX Retain ... 22s
$ kubectl describe pv primeiro-pv
Name: primeiro-pv
Labels: <none>
Annotations: <none>
Finalizers: [kubernetes.io/pv-protection]
StorageClass:
Status: Available
Claim:
Reclaim Policy: Retain
Access Modes: RWX
Capacity: 1Gi
Node Affinity: <none>
Message:
Source:
Type: NFS (an NFS mount that lasts the lifetime of a pod)
Server: 10.138.0.2
Path: /opt/dados
ReadOnly: false
Events: <none>
现在我们需要创建我们的 PersitentVolumeClaim,这样 Pod 就可以从我们的 PersistentVolume 中请求读写。
$ vim primeiro-pvc.yaml
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : prime-pvc
spec :
accessModes :
- ReadWriteMany
resources :
requests :
storage : 800Mi
然后执行创建命令:
$ kubectl create -f primeiro-pvc.yaml
persistentvolumeclaim/primeiro-pvc created
$ kubectl get pv
NAME CAPACITY ACCESS MOD ... CLAIM ... AGE
primeiro-pv 1Gi RWX ... default/primeiro-pvc ... 8m
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES ... AGE
primeiro-pvc Bound primeiro-pv 1Gi RWX ... 3m
现在我们有了一个 PersistentVolume 和一个 PersistentVolumeClaim,让我们创建一个消耗这个卷的部署。
$ vim nfs-pv.yaml
apiVersion : apps / v1
kind : Deployment
metadata :
labels :
run : nginx
name : nginx
namespace : default
spec :
progressDeadlineSeconds : 600
replicas : 1
revisionHistoryLimit : 10
selector :
matchLabels :
run : nginx
strategy :
rollingUpdate :
maxSurge : 1
maxUnavailable: 1
type : RollingUpdate
template :
metadata :
labels :
run : nginx
spec :
containers :
- image : nginx
imagePullPolicy : Always
name : nginx
volumeMounts :
- name : nfs-pv
mountPath : / giropops
resources : {}
terminationMessagePath : / dev / termination-log
terminationMessagePolicy : File
volumes :
- name : nfs-pv
persistentVolumeClaim :
claimName : prime-pvc
dnsPolicy : ClusterFirst
restartPolicy : Always
schedulerName : default-scheduler
securityContext : {}
terminationGracePeriodSeconds : 30
执行创建操作:
$ kubectl create -f nfs-pv.yaml
deployment.apps/nginx created
$ kubectl describe deployment nginx
Name: nginx
Namespace: default
CreationTimestamp: Wed, 7 Jul 2018 22:01:49 +0000
Labels: run=nginx
Annotations: deployment.kubernetes.io/revision=1
Selector: run=nginx
Replicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable
StrategyType: RollingUpdate
MinReadySeconds: 0
RollingUpdateStrategy: 1 max unavailable, 1 max surge
Pod Template:
Labels: run=nginx
Containers:
nginx:
Image: nginx
Port: <none>
Host Port: <none>
Environment: <none>
Mounts:
/giropops from nfs-pv (rw)
Volumes:
nfs-pv:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: primeiro-pvc
ReadOnly: false
Conditions:
Type Status Reason
---- ------ ------
Available True MinimumReplicasAvailable
Progressing True NewReplicaSetAvailable
OldReplicaSets: <none>
NewReplicaSet: nginx-b4bd77674 (1/1 replicas created)
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal ScalingReplicaSet 7s deployment-controller Scaled up replica set nginx-b4bd77674 to 1
正如我们可以看到详细介绍我们的 pod,它是使用值为 primeiro-pvc 的 ClaimName 创建的 NFS 卷。我们将详细检查我们的 pod 是否一切正常。
$ kubectl get pods -o wide
NAME READY STATUS RESTARTS AGE ... NODE
nginx-b4b... 1/1 Running 0 28s elliot-02
$ kubectl describe pod nginx-b4bd77674-gwc9k
Name: nginx-b4bd77674-gwc9k
Namespace: default
Priority: 0
PriorityClassName: <none>
Node: elliot-02/10.138.0.3
...
Mounts:
/giropops from nfs-pv (rw)
/var/run/secrets/kubernetes.io/serviceaccount from default-token-np77m (ro)
Conditions:
Type Status
Initialized True
Ready True
ContainersReady True
PodScheduled True
Volumes:
nfs-pv:
Type: PersistentVolumeClaim (a reference to a PersistentVolumeClaim in the same namespace)
ClaimName: primeiro-pvc
ReadOnly: false
default-token-np77m:
Type: Secret (a volume populated by a Secret)
SecretName: default-token-np77m
Optional: false
...
好吧,对吧?pod 实际上是使用卷 nfs 来挂载/giropops 这个卷。现在我们要用我们的例子列出容器中路径内的文件,在这个例子中,它位于 elliot-02 节点。
$ kubectl exec -ti nginx-b4bd77674-gwc9k -- ls /giropops/
FUNCIONA
我们可以看到一开始的文件都列在目录中。现在我们要用容器本身的命令 kubectl exec 和参数–touch 来创建另一个文件。
$ kubectl exec -ti nginx-b4bd77674-gwc9k -- touch /giropops/STRIGUS
$ kubectl exec -ti nginx-b4bd77674-gwc9k -- ls -la /giropops/
total 4
drwxr-xr-x. 2 root root 4096 Jul 7 23:13 .
drwxr-xr-x. 1 root root 44 Jul 7 22:53 ..
-rw-r--r--. 1 root root 0 Jul 7 22:07 FUNCIONA
-rw-r--r--. 1 root root 0 Jul 7 23:13 STRIGUS
在容器里面列表,我们可以看到文件被创建了,但是在我们的 NFS 服务器里面呢?我们将 NSF 服务器目录列在 elliot-01。
$ ls -la /opt/dados/
-rw-r--r-- 1 root root 0 Jul 7 22:07 FUNCIONA
-rw-r--r-- 1 root root 0 Jul 7 23:13 STRIGUS
看到了吗?我们的 NFS 服务器工作正常,现在让我们删除部署,看看卷的情况。
$ kubectl get deployment
NAME DESIRED CURRENT UP-TO-DATE AVAILABLE AGE
nginx 1 1 1 1 28m
$ kubectl delete deployment nginx
deployment.extensions "nginx" deleted
$ ls -la /opt/dados/
-rw-r--r-- 1 root root 0 Jul 7 22:07 FUNCIONA
-rw-r--r-- 1 root root 0 Jul 7 23:13 STRIGUS
正如预期的那样,文件还在,并没有在排除 Deployment 的情况下被删除,这是保留持久性文件供 Pods 消费的几种方法之一。