1、Storage
1.1 Volume
容器中的文件在磁盘上是临时存放的,这给容器中运行的特殊应用程序带来一些问题。
首先,当容器崩溃时,kubelet 将重新启动容器,容器中的文件将会丢失——因为容器会以干净的状态重建。
其次,当在一个
Pod
中同时运行多个容器时,常常需要在这些容器之间共享文件。Kubernetes 抽象出
Volume
对象来解决这两个问题。
使用Volume可以防止数据丢失,容器崩溃或新开容器都可以使用之前的Volume来达到恢复数据的作用
1.2 Host类型Volume实战
在前面的章节中提到过,Pod中的容器是会共享网络与存储的,共享网络我们已经提过了,这里来验证Pod共享存储的问题。
定义一个Pod,其中包含两个Container,都使用Pod的Volume
-
准备YAML文件
volume-pod.yaml
apiVersion: v1 kind: Pod metadata: name: volume-pod spec: containers: - name: nginx-container # container 1 image: nginx ports: - containerPort: 80 volumeMounts: - name: volume-pod # 指定使用的Volume mountPath: /nginx-volume - name: busybox-container # container 2 image: busybox command: ['sh', '-c', 'echo The app is running! && sleep 3600'] volumeMounts: - name: volume-pod # 指定使用的Volume mountPath: /busybox-volume volumes: - name: volume-pod hostPath: # 使用本地路径作为存储空间 path: /tmp/volume-pod
-
创建资源
[root@master-kubeadm-k8s volume]# kubectl apply -f volume-pod.yaml pod/volume-pod created
-
查看资源
[root@master-kubeadm-k8s volume]# kubectl get pods NAME READY STATUS RESTARTS AGE volume-pod 2/2 Running 0 2m5s
-
测试Volume
# 来到Pod所在的worker节点 # 查看容器 [root@worker02-kubeadm-k8s ~]# docker ps | grep volume 61f2146c3e6e busybox "sh -c 'echo The app…" 32 seconds ago Up 32 seconds k8s_busybox-container_volume-pod_default_8f9de4e8-8aed-11ea-97c6-5254008afee6_0 ae4aad0d60fb nginx "nginx -g 'daemon of…" About a minute ago Up About a minute k8s_nginx-container_volume-pod_default_8f9de4e8-8aed-11ea-97c6-5254008afee6_0 ff467713a95f k8s.gcr.io/pause:3.1 "/pause" 2 minutes ago Up 2 minutes k8s_POD_volume-pod_default_8f9de4e8-8aed-11ea-97c6-5254008afee6_0 # 进入nginx容器 [root@worker02-kubeadm-k8s ~]# docker exec -it ae4aad0d60fb sh # 查看目录,现在是空的,所以看不到东西 # ls /nginx-volume # 创建一个文件,再到宿主机 /tmp/volume-pod 目录查看 # echo '1111' -> test.html # ls test.html # 发现也有了刚才创建的文件 [root@worker02-kubeadm-k8s ~]# ls /tmp/volume-pod test.html # 再去另一个 busy-box 容器中看是否有这个文件 [root@worker02-kubeadm-k8s volume-pod]# docker exec -it 61f2146c3e6e sh # 发现也有这个文件 / # ls /busybox-volume test.html
通过验证,Pod中的容器是共享同一个Volume的
1.3 Persistent Volume
由于使用本地的Volume会有一些限制,比如Pod必须要与Volume在同一台宿主机,宿主机挂了数据就会丢失。所以K8S还提供了真正的持久化存储的技术,将数据挂载到其他专门用于存储数据的服务器上,这样即使宿主机挂了,数据还在。
PersistentVolume是为用户和管理员提供了一个API抽象的细节如何提供如何使用存储。 K8S引入了两个新的API资源:PersistentVolume(PV)、PersistentVolumeClaim(PVC)。
一般PV是由专业的运维人员去配置,PVC是有开发配置
PersistentVolume:PV在K8S中也是一种资源,但生命周期独立于Pod。封装了底层存储卷实现的细节。它是用来定义或配置存储的资源,市面上有很多可以作为文件服务器的工具,比如NFS、RBD、Cinder等,PV就是帮我们打通了与这些服务器的对接。
PersistentVolumeClaim:PVC是给用户申请存储空间的。 当开发者需要通过PVC去申请存储空间,可以请求特定的空间大小和访问模式,比如他们可以申请是一次读/写或多次只读等。
简单来说,PV定义了一块存储空间,Pod想要申请存储空间,就要通过PVC去申请,PVC通过访问模式与申请的空间大小去匹配PV。
1.3.1 流程图
下面这张图完整说明了Pod、PVC、PV与文件服务器的关系。
一个Pod想要一块存储空间,需要绑定一个PVC,通过访问模式与申请的存储空间大小去匹配并绑定PV,而PV才是真正与文件服务打交道的资源。
1.3.2 案例实操
1.3.2.1 准备文件服务器
-
NFS
nfs(network file system)网络文件系统,是FreeBSD支持的文件系统中的一种,允许网络中的计算机之间通过TCP/IP网络共享资源
-
选择一台机器搭建NFS
这里选择Master节点
# 1、 选择master节点作为nfs的server,所以在master节点上 # 安装nfs yum install -y nfs-utils # 创建nfs目录 mkdir -p /nfs/data/ # 为应用服务创建资源目录 mkdir -p /nfs/data/nginx # 授予权限 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测试 【master节点的ip】 showmount -e 192.168.50.111 # 2、 所有node上安装客户端 yum install -y nfs-utils systemctl start nfs && systemctl enable nfs
1.3.2.2 编写PV
-
编写YAML文件
pv.yaml
apiVersion: v1 kind: PersistentVolume metadata: name: nginx-pv spec: capacity: storage: 2Gi # 存储空间大小 volumeMode: Filesystem # 存储模式 accessModes: # 访问模式 - ReadWriteMany # 表示允许多个Pod多次读写 # ReadWriteOnece 表示只允许一个Pod进行独占式读写操作 nfs: path: /nfs/data/nginx # 远端服务器的目录 server: 192.168.50.111 # 远端的服务器ip
-
创建PV
[root@master-kubeadm-k8s pv]# kubectl apply -f pv.yaml persistentvolume/my-pv created
-
查看PV
[root@master-kubeadm-k8s pv]# kubectl get pv NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE nginx-pv 2Gi RWX Retain Available 4s
1.3.2.3 编写PVC
-
编写PVC
pvc.yaml
根据 accessModes、storage去匹配合适的PV
apiVersion: v1 kind: PersistentVolumeClaim metadata: name: nginx-pvc spec: accessModes: - ReadWriteMany # 访问模式 resources: requests: storage: 2Gi # 申请的存储空间大小
-
创建PVC
persistentvolumeclaim/nginx-pvc created [root@master-kubeadm-k8s pv]# kubectl get pvc
-
查看PVC
可以看到它的状态是 Bound,Volume是 nginx-pv,表示已经绑定到PV
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE nginx-pvc Bound nginx-pv 2Gi RWX 5s
1.3.2.4 编写Pod绑定PVC
-
编写YAML文件
nginx-pod.yaml
apiVersion: apps/v1 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: # 使用PVC claimName: nginx-pvc # PVC名称
-
创建Pod
[root@master-kubeadm-k8s pv]# kubectl apply -f nginx-pod.yaml deployment.apps/nginx created
-
查看Pod
[root@master-kubeadm-k8s pv]# kubectl get pods NAME READY STATUS RESTARTS AGE nginx-77945f44db-sllmh 1/1 Running 0 51s
1.3.2.5 测试持久化存储
-
在映射目录创建文件
# 进入NFS机器的映射目录 [root@master-kubeadm-k8s pv]# cd /nfs/data/nginx/ # 编写内容到 pv.html 文件 [root@master-kubeadm-k8s nginx]# echo '
test pv
' -> pv.html -
访问Nginx
# 查看nginx的ip [root@master-kubeadm-k8s nginx]# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-77945f44db-sllmh 1/1 Running 0 4m15s 192.168.221.122 worker02-kubeadm-k8s
# 测试访问,可以发现已经拿到我们在映射目录创建的文件了 [root@master-kubeadm-k8s nginx]# curl 192.168.221.122/pv.html test pv
-
PV的存在虽然解决了数据存储安全的问题,但仅仅上面的案例,还是不够好,如果Pod有很多,难道要让运维人员一个一个去创建吗,虽然也是一种方案,但并不完美。
下面要聊的StorageClass,就是可以动态的管理PV,操作更灵活!
1.4 StorageClass
StorageClass 提供了描述存储 “类” 的方法。 不同的类型可能会映射到不同的服务质量等级或备份策略,或是由集群管理员制定的任意策略。
Kubernetes 本身并不清楚各种类代表的什么。这个类的概念在其他存储系统中有时被称为 “配置文件”。
每个 StorageClass 都包含
provisioner
、parameters
和reclaimPolicy
字段, 这些字段会在 StorageClass 需要动态分配PersistentVolume
时会使用到。StorageClass 对象的命名很重要,用户使用这个命名来请求生成一个特定的类。 当创建 StorageClass 对象时,管理员设置 StorageClass 对象的命名和其他参数,一旦创建了对象就不能再对其更新。
管理员可以为没有申请绑定到特定 StorageClass 的 PVC 指定一个默认的存储类(PV)
StorageClass其实可以理解为是一个 PV 的模板,PVC想要绑定PV,可以通过StorageClass模板去动态的创建PV。
其中有两个重要部分:PV属性和创建此PV所需要的插件。
这样PVC就可以按“Class”来匹配PV。
可以为PV指定storageClassName属性,标识PV归属于哪一个Class。
1.4.1 存储分配器
因为文件系统有很多,StorageClass不可能默认就实现了对所有文件系统的交互,存储分配器就是StorageClass与外部的文件系统打交道的,可以理解为是一个 插件 !
每个 StorageClass 都有一个分配器,用来决定使用哪个卷插件分配 PV。该字段必须指定。
-
K8S本身已经实现了一部分与外部文件系统打交道的插件,但还有一些还未支持
- 下方打勾的表示K8S已经支持,未打钩的表示不支持
-
我们之前使用的是NFS,但 K8S 并没有实现 NFS 的内部分配器,那我们可以使用 NFS外部分配器,也就是非官方的实现。
1.4.2 StorageClass案例
在创建StorageClass资源之前,要说明一下,不是所有人都可以创建StorageClass资源的,在K8S中一些特殊的资源必须要有apiServer的认证才能创建,这里想要创建StorageClass,就需要RBAC的认证,简单来说就是需要创建一个账户去与apiServer进行交互。
-
准备NFS服务器
- Master节点已经是NFS了,所以不需要准备了
-
准备认证资源
创建权限账户资源
-
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
-
创建资源
[root@master-kubeadm-k8s storageclass]# kubectl apply -f rbac.yaml clusterrole.rbac.authorization.k8s.io/nfs-provisioner-runner created clusterrolebinding.rbac.authorization.k8s.io/run-nfs-provisioner created role.rbac.authorization.k8s.io/leader-locking-nfs-provisioner created rolebinding.rbac.authorization.k8s.io/leader-locking-nfs-provisioner created
-
-
准备PV模板资源
创建pv模板
- class.yaml
kind: StorageClass apiVersion: storage.k8s.io/v1 metadata: name: example-nfs provisioner: example.com/nfs
-
创建资源
[root@master-kubeadm-k8s storageclass]# kubectl apply -f class.yaml storageclass.storage.k8s.io/example-nfs created
-
查看资源
[root@master-kubeadm-k8s storageclass]# kubectl get sc NAME PROVISIONER AGE example-nfs example.com/nfs 39s
-
准备申请存储资源
创建申请资源的pvc
- pvc.yaml
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: my-pvc spec: accessModes: - ReadWriteMany resources: requests: storage: 1Mi # 这个名字要和上面创建的storageclass名称一致 storageClassName: example-nfs
-
创建资源
[root@master-kubeadm-k8s storageclass]# kubectl apply -f pvc.yaml persistentvolumeclaim/my-pvc created
-
查看资源
可以看到创建好的pvc绑定了storageClass
[root@master-kubeadm-k8s storageclass]# kubectl get pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE my-pvc Pending example-nfs 23s nginx-pvc Bound nginx-pv 2Gi RWX 16h
-
准备NFS插件资源
创建nfs插件
- nfs-deployment.yaml
apiVersion: v1 kind: ServiceAccount # 创建认证账户,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 # storageClass的资源名称 - name: NFS_SERVER value: 192.168.50.111 # NFS服务器ip - name: NFS_PATH value: /nfs/data/sunny # 映射的目录,注意提前创建好 volumes: - name: nfs-client-root nfs: server: 192.168.50.111 path: /nfs/data/sunny
-
创建资源
[root@master-kubeadm-k8s storageclass]# kubectl apply -f nfs-deployment.yaml serviceaccount/nfs-provisioner created deployment.extensions/nfs-provisioner created
-
查看资源
# 查看nfs pod资源 [root@master-kubeadm-k8s storageclass]# kubectl get pods NAME READY STATUS RESTARTS AGE nfs-provisioner-68bd86ff89-6rpnc 1/1 Running 0 105s # 查看serviceaccount [root@master-kubeadm-k8s storageclass]# kubectl get sa NAME SECRETS AGE default 1 37d nfs-provisioner 1 3m7s
-
准备Pod资源
创建pod
- nginx-pod.yaml
kind: Pod apiVersion: v1 metadata: name: nginx spec: containers: - name: nginx image: nginx volumeMounts: - name: my-pvc mountPath: "/usr/sunny" restartPolicy: "Never" volumes: - name: my-pvc persistentVolumeClaim: claimName: my-pvc # 指定要使用的pvc
-
创建资源
[root@master-kubeadm-k8s storageclass]# kubectl apply -f nginx-pod.yaml pod/nginx created
-
查看资源
[root@master-kubeadm-k8s storageclass]# kubectl get pods NAME READY STATUS RESTARTS AGE nfs-provisioner-68bd86ff89-6rpnc 1/1 Running 0 3m29s nginx 1/1 Running 0 112s
-
功能验证
-
查看nfs服务器的映射目录
在PVC与storageClass动态创建的PV绑定之后,这里就会自动创建一个目录
-
-
新增文件,测试nginx-pod
# 创建文件 [root@master-kubeadm-k8s default-my-pvc-pvc-d9b32154-8b80-11ea-afe2-5254008afee6]# echo 'test storageClass' -> class.html [root@master-kubeadm-k8s default-my-pvc-pvc-d9b32154-8b80-11ea-afe2-5254008afee6]# ll total 4 -rw-r--r--. 1 root root 20 May 1 08:04 class.html # 测试nginx-pod中是否有这个文件 [root@worker02-kubeadm-k8s ~]# docker exec -it 051a77e8a908 bash # 可以看到,文件同步了! root@nginx:/# cd /usr/sunny/ root@nginx:/usr/sunny# ls class.html
1.5 总结
1.5.1 PV、PVC、SC的关系
- 对于PV或者StorageClass只能对应一种后端存储
- 对于手动的情况,一般我们会创建很多的PV,等有PVC需要使用的时候就可以直接使用了
- 对于自动的情况,那么就由StorageClass来自动管理创建
- 如果Pod想要使用共享存储,一般会创建PVC,PVC中描述了想要什么类型的后端存储、空间等,K8s从而会匹配对应的PV,如果没有匹配成功,Pod就会处于Pending状态。Pod中使用只需要像使用volumes一样,指定名字就可以使用了
- 一个Pod可以使用多个PVC,一个PVC也可以给多个Pod使用
- 一个PVC只能绑定一个PV,一个PV只能对应一种后端存储
1.5.2 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策略。AWS EBS、GCE PD、Azure Disk和Cinder支持Delete策略