kubernetes实践(六)pod的存储

By prince No comments

容器中的文件在磁盘上是临时存放的,这给容器中运行的特殊应用程序带来一些问题。 首先,当容器崩溃时,kubelet 将重新启动容器,容器中的文件将会丢失——因为容器会以干净的状态重建。 其次,当在一个 Pod 中同时运行多个容器时,常常需要在这些容器之间共享文件。 Kubernetes 抽象出 Volume 对象来解决这两个问题。

本质上,Kubernetes Volume 是一个目录,这一点与 Docker Volume 类似。当 Volume 被 mount 到 Pod,Pod 中的所有容器都可以访问这个 Volume。Kubernetes Volume 也支持多种 backend 类型,包括 emptyDir、hostPath、GCE Persistent Disk、AWS Elastic Block Store、NFS、Ceph 等。

Volume 提供了对各种 backend 的抽象,容器在使用 Volume 读写数据的时候不需要关心数据到底是存放在本地节点的文件系统中呢还是云硬盘上。对它来说,所有类型的 Volume 都只是一个目录。

 

2.emptyDir

emptyDir 是最基础的 Volume 类型。正如其名字所示,一个 emptyDir Volume 是 Host 上的一个空目录。

emptyDir Volume 对于容器来说是持久的,对于 Pod 则不是。当 Pod 从节点删除时,Volume 的内容也会被删除。但如果只是容器被销毁而 Pod 还在,则 Volume 不受影响。

也就是说:emptyDir Volume 的生命周期与 Pod 一致。

Pod 中的所有容器都可以共享 Volume,它们可以指定各自的 mount 路径。下面通过例子来实践 emptyDir,配置文件如下:

apiVersion: v1
kind: Pod
metadata:
  name: emptydir-pod
spec:
  containers:
    - name: emptydir-container
      image: nginx
      volumeMounts:
       - name: emptydir-storage
         mountPath: /nginx-volume
  volumes:
  - name: emptydir-storage
    emptyDir: {}

emptyDir 是 Host 上创建的临时目录,其优点是能够方便地为 Pod 中的容器提供共享存储,不需要额外的配置。但它不具备持久性,如果 Pod 不存在了,emptyDir 也就没有了。根据这个特性,emptyDir 特别适合 Pod 中的容器需要临时共享存储空间的场景。

3.hostPath

hostPath Volume 的作用是将 Docker Host (宿主机)文件系统中已经存在的目录 mount 给 Pod 的容器。大部分应用都不会使用 hostPath Volume,因为这实际上增加了 Pod 与节点的耦合,限制了 Pod 的使用。不过那些需要访问 Kubernetes 或 Docker 内部数据(配置文件和二进制库)的应用则需要使用 hostPath。

apiVersion: v1
kind: Pod
metadata:
  name: hostpath-pod
spec:
  containers:
    - name: hostpath-container
      image: nginx
      volumeMounts:
       - name: ssl-certs
         mountPath: /usr/share/nginx/conf/cert
         readOnly: true
  volumes:
  - name: ssl-certs
    hostPath:
     path: /etc/ssl/certs

这里定义了一个 hostPath volume ssl-certs,对应 Host 目录 /etc/ssl/certs

如果 Pod 被销毁了,hostPath 对应的目录也还会被保留,从这点看,hostPath 的持久性比 emptyDir 强。不过一旦 Host 崩溃,hostPath 也就没法访问了。

4.外部存储

Volume 的类型

Kubernetes 支持下列类型的卷:

如果 Kubernetes 部署在诸如 AWS、GCE、Azure 等公有云上,可以直接使用云硬盘作为 Volume,下面是 NFS 的例子:

apiVersion: v1
kind: Pod
metadata:
  name: nfspath-pod
spec:
  containers:
    - name: nfspath-container
      image: nginx
      volumeMounts:
       - name: nfs-storage
         mountPath: /usr/share/nginx/html
  volumes:
  - name: nfs-storage
    nfs:
     server: 192.168.50.14
     path: "/data/nginx"

nfs 文件系统的 /data/nginx 目录被 mount 到容器路径 /usr/share/nginx/html。

相对于 emptyDir 和 hostPath,这些 Volume 类型的最大特点就是不依赖 Kubernetes。Volume 的底层基础设施由独立的存储系统管理,与 Kubernetes 集群是分离的。数据被持久化后,即使整个 Kubernetes 崩溃也不会受损。

当然,运维这样的存储系统通常不是项简单的工作,特别是对可靠性、高可用和扩展性有较高要求时。

Volume 提供了非常好的数据持久化方案,不过在可管理性上还有不足。

5.PersistentVolume

拿前面 NFS 的例子来说,要使用 Volume,Pod 必须事先知道NFS的ip和目录

Pod 通常是由应用的开发人员维护,而 Volume 则通常是由存储系统的管理员维护。开发人员要获得上面的信息必须询问管理员。

这样就带来一个管理上的问题:应用开发人员和系统管理员的职责耦合在一起了。如果系统规模较小或者对于开发环境这样的情况还可以接受。但当集群规模变大,特别是对于生成环境,考虑到效率和安全性,这就成了必须要解决的问题。

为了解决这个问题Kubernetes 引入了 PersistentVolume和PersistentVolumeClaim

PersistentVolume (PV) 是外部存储系统中的一块存储空间,由管理员创建和维护。与 Volume 一样,PV 具有持久性,生命周期独立于 Pod。

PersistentVolumeClaim (PVC) 是对 PV 的申请 (Claim)。PVC 通常由普通用户创建和维护。需要为 Pod 分配存储资源时,用户可以创建一个 PVC,指明存储资源的容量大小和访问模式(比如只读)等信息,Kubernetes 会查找并提供满足条件的 PV。

有了 PersistentVolumeClaim,用户只需要告诉 Kubernetes 需要什么样的存储资源,而不必关心真正的空间从哪里分配,如何访问等底层细节信息。这些 Storage Provider 的底层信息交给管理员来处理,只有管理员才应该关心创建 PersistentVolume 的细节信息

1.搭建nfs

(1)在master节点上安装nfs的server

# 安装nfs
yum install -y nfs-utils
# 创建nfs目录
mkdir -p /nfs/data/
mkdir -p /nfs/data/nginx
mkdir -p /nfs/data/test
# 授予权限
chmod -R 777 /nfs/data
# 编辑export文件
vi /etc/exports
/nfs/data *(rw,no_root_squash,sync)
# 使得配置生效
exportfs -r
# 查看生效
exportfs
# 启动rpcbind、nfs服务
systemctl restart rpcbind && systemctl enable rpcbind
systemctl restart nfs && systemctl enable nfs
# 查看rpc服务的注册情况
rpcinfo -p localhost
# showmount测试
showmount -e master-ip

(2)所有node节点上安装客户端

yum -y install nfs-utils
systemctl start nfs && systemctl enable nfs
2.PV Static Provision

(1).创建PersistentVolume (PV)

下面创建一个 PV nginx-pv,配置文件 nginx-pv.yaml 如下:

apiVersion: v1
kind: PersistentVolume
metadata:
  name: nginx-pv
spec:
  capacity:
    storage: 2Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Recycle
  storageClassName: nginx 
  nfs:
    path: /nfs/data/nginx
    server: 192.168.50.14

① capacity 指定 PV 的容量为 2G。

② accessModes 指定访问模式为 ReadWriteMany,支持的访问模式有:
ReadWriteOnce – PV 能以 read-write 模式 mount 到单个节点。
ReadOnlyMany – PV 能以 read-only 模式 mount 到多个节点。
ReadWriteMany – PV 能以 read-write 模式 mount 到多个节点。

③ persistentVolumeReclaimPolicy 指定当 PV 的回收策略为 Recycle,支持的策略有:
Retain – 需要管理员手工回收。
Recycle – 清除 PV 中的数据,效果相当于执行 rm -rf /nfs/data/nginx/*
Delete – 删除 Storage Provider 上的对应存储资源,例如 AWS EBS、GCE PD、Azure Disk、OpenStack Cinder Volume 等。

④ storageClassName 指定 PV 的 class 为 nginx。相当于为 PV 设置了一个分类,PVC 可以指定 class 申请相应 class 的 PV。

⑤ 指定 PV 在 NFS 服务器上对应的目录。

创建 nginx-pv:

kubectl apply -f nginx-pv.yaml

查询pv

kubectl get pv

STATUS 为 Available,表示 nginx-pv 就绪,可以被 PVC 申请。

(2).创建PersistentVolumeClaim (PVC)

接下来创建 PVC nginx-pvc,配置文件 nginx-pvc.yml 如下:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nginx-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 2Gi
  storageClassName: nginx

创建 nginx-pvc

kubectl apply -f nginx-pvc.yaml

查询pvc

kubectl get pv

查询pvc

kubectl get pvc

从上面的输出可以看到 nginx-pvc 已经 Bound 到 nginx-pv,申请成功。

(3).创建pod使用pvc

接下来创建 pod,配置文件 nginx.yml 如下:

apiVersion: apps/v1beta1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - image: nginx
        name: nginx
        ports:
        - containerPort: 80
        volumeMounts:
        - name: nginx-persistent-storage
          mountPath: /usr/share/nginx/html
      volumes:
      - name: nginx-persistent-storage
        persistentVolumeClaim:
          claimName: nginx-pvc

创建pod

kubectl apply -f nginx.yaml

验证

#进入pod
kubectl exec -it nginx bash

#指定目录写入文件
cd /usr/share/nginx/html
echo "<html>index page</html>" > index.html
exit

#是否存在
cat /nfs/data/nginx/index.html
<html>index page</html>

(4).pv回收

当 PV 不再需要时,可通过删除 PVC 回收。

kubectl get pv

未删除pvc之前  pv的状态是Bound

kubectl delete pvc nginx-pvc

删除pvc之后pv的状态变为Available,,此时解除绑定后则可以被新的 PVC 申请。

/nfs/data/nginx文件中的文件被删除了

ll /nfs/data/nginx

因为 PV 的回收策略设置为 Recycle,所以数据会被清除,但这可能不是我们想要的结果。如果我们希望保留数据,可以将策略设置为 Retain

kubectl get pv

(5).PV的状态和回收策略

  • PV的状态

Available:表示当前的pv没有被绑定
Bound:表示已经被pvc挂载
Released:pvc没有在使用pv, 需要管理员手工释放pv
Failed:资源回收失败
  • PV回收策略

Retain:表示删除PVC的时候,PV不会一起删除,而是变成Released状态等待管理员手动清理
Recycle:在Kubernetes新版本就不用了,采用动态PV供给来替代
Delete:表示删除PVC的时候,PV也会一起删除,同时也删除PV所指向的实际存储空间

注意:目前只有NFS和HostPath支持Recycle策略。NFS 不支持 Delete ,AWS EBS、GCE PD、Azure Disk和Cinder支持Delete策略
3.PV Dynamical Provision

前面的例子中,我们提前创建了 PV,然后通过 PVC 申请 PV 并在 Pod 中使用,这种方式叫做静态分配(Static Provision)。

与之对应的是动态分配(Dynamical Provision),即如果没有满足 PVC 条件的 PV,会动态创建 PV。相比静态分配,动态分配有明显的优势:不需要提前创建 PV,减少了管理员的工作量,效率高。

动态分配是通过 StorageClass 实现的,StorageClass声明存储插件,用于自动创建PV。

说白了就是创建PV的模板,其中有两个重要部分:PV属性和创建此PV所需要的插件。

这样PVC就可以按“Class”来匹配PV。

可以为PV指定storageClassName属性,标识PV归属于哪一个Class。

下面是一个例子

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: standard
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
reclaimPolicy: Retain
allowVolumeExpansion: true
mountOptions:
  - debug
volumeBindingMode: Immediate

01 对于PV或者StorageClass只能对应一种后端存储
02 对于手动的情况,一般我们会创建很多的PV,等有PVC需要使用的时候就可以直接使用了
03 对于自动的情况,那么就由StorageClass来自动管理创建
04 如果Pod想要使用共享存储,一般会在创建PVC,PVC中描述了想要什么类型的后端存储、空间等,K8s从而会匹配对应的PV,如果没有匹配成功,Pod就会处于Pending状态。Pod中使用只需要像使用volumes一样,指定名字就可以使用了
05 一个Pod可以使用多个PVC,一个PVC也可以给多个Pod使用
06 一个PVC只能绑定一个PV,一个PV只能对应一种后端存储

有了StorageClass之后的PVC可以变成这样

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: mypvc
spec:
accessModes:
    - ReadWriteMany
resources:
 requests:
    storage: 1Mi
  storageClassName: standard

StorageClass之所以能够动态供给PV,是因为Provisioner,也就是Dynamic Provisioning

但是NFS这种类型,K8s中默认是没有Provisioner插件的,需要自己创建

(1)创建Provisioner插件所需角色

rbac.yaml文件

kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: nfs-provisioner-runner
rules:
  - 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: ["services", "endpoints"]
    verbs: ["get"]
  - apiGroups: ["extensions"]
    resources: ["podsecuritypolicies"]
    resourceNames: ["nfs-provisioner"]
    verbs: ["use"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: run-nfs-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-provisioner
     # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: ClusterRole
  name: nfs-provisioner-runner
  apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-provisioner
rules:
  - apiGroups: [""]
    resources: ["endpoints"]
    verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: leader-locking-nfs-provisioner
subjects:
  - kind: ServiceAccount
    name: nfs-provisioner
    # replace with namespace where provisioner is deployed
    namespace: default
roleRef:
  kind: Role
  name: leader-locking-nfs-provisioner
  apiGroup: rbac.authorization.k8s.io

创建命令

kubectl apply -f rbac.yaml

(2)创建Provisioner插件所需资源

deployment.yaml文件

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nfs-provisioner
---
kind: Deployment
apiVersion: extensions/v1beta1
metadata:
  name: nfs-provisioner
spec:
  replicas: 1
  strategy:
    type: Recreate
  template:
    metadata:
      labels:
        app: nfs-provisioner
    spec:
      serviceAccount: nfs-provisioner
      containers:
        - name: nfs-provisioner
          image: registry.cn-hangzhou.aliyuncs.com/open-ali/nfs-client-provisioner
          volumeMounts:
            - name: nfs-client-root
              mountPath: /persistentvolumes
          env:
            - name: PROVISIONER_NAME
              value: example.com/nfs
            - name: NFS_SERVER
              value: 192.168.50.14
            - name: NFS_PATH
              value: /nfs/data/test
      volumes:
        - name: nfs-client-root
          nfs:
            server: 192.168.50.14
            path: /nfs/data/test

创建资源

kubectl apply -f deployment.yaml

(3)创建StorageClass

class.yaml文件

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: example-nfs
provisioner: example.com/nfs

创建命令

kubectl apply -f class.yaml

(4)创建pvc资源

pvc.yaml文件

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: my-pvc
spec:
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Mi
  # 这个名字要和上面创建的storageclass名称一致
  storageClassName: example-nfs

创建命令

kubectl apply -f pvc.yaml

(4)创建pod使用pvc资源

busybox-pod.yaml文件

kind: Pod
apiVersion: v1
metadata:
  name: busybox
spec:
  containers:
  - name: busybox-container
    image: busybox
    volumeMounts:
      - name: my-pvc
        mountPath: "/usr/test"
  restartPolicy: "Never"
  volumes:
    - name: my-pvc
      persistentVolumeClaim:
        claimName: my-pvc

创建命令

kubectl apply -f busybox-pod.yaml

验证

#进入pod
kubectl exec -it busybox bash

#指定目录写入文件
cd /usr/test
echo "hello world" > hello.txt
exit
#验证是否创建
cat /nfs/data/test/hello.txt
hello world

发表评论

 

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据