Kubernetes in Action中文版.pdf 观后笔记 二

六. 挂载卷 Volume

通过学习前五章,我们已经可以部署一个安全稳定的临时应用程序了。为什么说是临时的应用程序呢?

众所周知,程序=代码+数据,现在代码已经可以运行并提供服务,同时可以产生或读取数据了,但每次重启服务或者 K8S 帮助我们调度 Pod 导致容器重启以后,之前运行的一些有价值无价值的数据也就不存在了,在本章,我们将介绍怎么处理这份有价值的数据。

6.1 Volume 介绍

Kubernetes 通过定义 Volume 来满足这个需求,Volume 被定义为 Pod 这类顶级资源的一部分,并和 Pod 共享生命周期。
也就是 Pod 启动时创建卷,Pod 删除时销毁卷,期间卷的内容不会消失,所以 Pod 因为各种原因重启容器都不会影响卷的内容,如果一个 Pod 内包含多个容器,多个容器共享此卷。

Volume 类型

  • emptyDir: 用于存储临时数据的空目录
  • hostPath: 用于将工作节点的目录挂载到 Pod 中
  • gitRepo: 通过检出 Git 仓库内容来初始化的挂载卷
  • nfs: 用于挂载 nfs 共享卷到 Pod 中
  • configMap、secret、downwardAPI: K8S 内置的用于持久化存储的特殊类型资源
  • persistentVolumeClaim: K8S 的持久存储类型
  • gcePersistentDisk: 谷歌高效磁盘存储卷
  • awsElasticBlockStore: 亚马逊弹性块型存储卷

6.2 Volume 用例

1、最简单的 emptyDir

虽然 emptyDir 只能作为临时数据存储,不过利用容器共享卷的这一特性,在 Pod 的多个容器中共享文件还是很有效的。

apiVersion: v1
kind: Pod
metadata:
  name: fortune
spec:
  containers:
  - image: luksa/fortune
    name: html-generator
    volumeMounts:
    - name: html                # 使用名为 html 的卷
      mountPath: /var/htdocs    # 卷挂载位置
  - image: nginx:alpine
    name: html-server
    volumeMounts:
    - name: html
      mountPath: /usr/share/nginx/html
      readOnly: true
    ports:
      containerPort: 80
      protocol: TCP
  volumes:
  - name: html                  # 声明一个名为 html 的卷,Pod 创建时创建,自动绑定 Pod 的生命周期
    emptyDir: {}                # 空配置,配置项下面说

empytDir 的存储介质由运行 Pod 的节点提供,并且默认实在磁盘上创建一个空目录。我们可以通过修改emptyDir的配置项,实现在工作节点的内存创建目录。

apiVersion: v1
kind: Pod
metadata:
  name: fortune
spec:
  ...
  volumes:
  - name: html
    emptyDir: 
      medium: Memory

2、进一大步的 gitRepo

gitRepo 是 emptyDir 的进化版,它通过克隆一个 Git 仓库的特定分支版本来初始化目录的内容(不会同步更新仓库内容)。

apiVersion: v1
kind: Pod
metadata:
  name: fortune-for-gitRepo
spec:
  ...
  volumes:
  - name: html
    gitRepo: 
      repository: https://github.com/clockq/***.git # 一个 git 仓库的路径
      reversion: master # 检出 git 的主分支
      directory: .      # git clone 后放在 volume 的根目录,如果不写,会在根目录下创建一个 *** 项目名的子文件夹,实际仓库内容会放到 *** 子文件夹内

6.3 可持久化的 Volume

上述例子主要实现的功能是 Pod 内的容器重启后,卷中数据可复用。以及 Pod 内的多个容器利用卷共享卷中数据。但因为上述 Volume 的生命周期和 Pod 同步,也导致了 Volume 无法做到真正的持久化。

什么是真正的持久化呢?
当 Pod 重启后数据依旧存在,不论 Pod 是再次调度到上次的工作节点还是其他工作节点都可以加载到之前生成的持久化数据。

注意:如果需要 Volume 可以跨工作节点访问,就需要存储介质可以在所有工作节点可以访问。

1、最简单的例子 hostPath

只是将持久化数据放到工作节点的存储介质中,如果 Pod 重新调度后出现在别的工作节点,那么之前持久化的数据就没有用到,所以谨慎使用。

一般用来某些系统级别的应用(比如由 DaemonSet 管理的 Pod)读取工作节点的文件系统时使用。即便可以但也不建议用来做多个 Pod 之间的文件同步。

apiVersion: v1
kind: Pod
metadata:
  name: fortune-for-hostPath
spec:
  ...
  volumes:
  - name: html
    hostPath: 
      path: /data       # 工作节点的目录位置
      type: Directory   # 挂载的文件类型

2、使用 GCE 持久化卷

GCE(Google Compute Engine)是 Google 提供的云计算平台,对于使用方式日新月异,但国内不方便,所以有兴趣的看官网吧。

https://kubernetes.io/zh/docs/concepts/storage/volumes/#gcepersistentdisk

工作原理大致如下:
image

其他云平台大同小异。常见的云平台和接口如下:

  • GCE(Google) => gcePersistentDisk
  • AWS EC2(Amazon) => awsElasticBlockStore
  • Microsoft Azure(Microsoft) => azureFile、azureDisk
  • NFS(Linux) => nfs

需要了解更多的支持和配置信息,可以使用 $ kubectl explain 自行查询

6.4 从 Pod 解耦底层存储

掌握上面的内容可以绑定大多数的持久化存储了,但一个 Pod 的发布者(或者说服务的开发者)其实并不需要知道所使用的的存储介质,以及存储介质的具体配置,这些应该交给集群管理员来处理。

利用 Kubernetes Volume 屏蔽实际的存储技术不就是 K8S 所推崇的吗!否则就导致一个 Pod 与某种云平台产生了强依赖关系。

理想情况是,当开发人员需要一定一定数量的持久化存储是,向 K8S 请求,就好像请求 CPU,Memory等资源一样。而集群管理员的工作只需将存储介质配置好,并加入到集群的资源池中。

1、介绍持久卷与持久卷生命

为了使应用能够正常请求存储资源,同时避免处理基础设施细节,所以引入了持久卷和持久卷声明。

  1. 集群管理员只需要创建和管理某种存储介质。
  2. 然后创建 PV(PersistentVolume,持久卷)来抽象存储介质,此时可以设定存储大小和访问模式,PV 代理的存储能力会自动加入到 K8S 的资源池中。
  3. 当应用发布者需要使用持久卷时,只需创建一个 PVC(PersistentVolumeClaim,持久卷声明),指定所需的最小存储容量要求和访问模式。
  4. K8S 会自动找到可匹配的 PV,并绑定到此 PVC。
  5. 持久卷声明即可作为一个普通卷使用,并挂载到 Pod 上。
  6. 已经挂载的 PV 不能挂载在多个 PVC 上,只能等待之前的 PVC删除后释放,PV 才可以挂载到其他 PVC 上。

image

2、创建持久卷

apiVersion: v1
kind: PersistentVolume
metadata:
  name: demo-pv
spec:
  capacity:
    storage: 1Gi            # 定义存储卷大小
  accessModes:              # 定义存储访问模式
  - ReadWriteOnce           # 可以被一个用户绑定为读写模式
  - ReadOnlyMany            # 也可以被多个节点(而非Pod)绑定为只读模式
  persistentVolumeReclaimPolicy: Retain     # 定义回收策略
  # Retain => 当 PV 被删除后 PV 的内容会被保留。相应策略还有 Delete 和 Recycle,回收策略可以动态改变
  local:                    # 持久卷类型,使用本地存储
    path: /tmp/hostpath_pv/demo-pv   # 本地目录位置

3、通过持久卷声明绑定持久卷

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: demo-pvc
  namespace: demo-ns        # pvc 归属于某个 ns
spec:
  accessModes:
  - ReadWriteOnce           # 可以被一个用户绑定为读写模式
  storageClassName: ""      # 动态类配置
  resources:
    requests:
      storage: 1Gi          # 申请1Gi的存储空间

image

如上图一样,K8S 根据 PVC 申请的资源,去所有 PV 中找到能满足所有要求的 PV,然后两者绑定。此时列举 PVC 打印信息如下:

$ kubectl get pvc
NAME        STATUS  VOLUME  CAPACITY    ACCESSMODES AGE
demo-pvc    Bound   demo-pv 1Gi         RWO         13s

4、Pod 通过持久卷和持久卷声明来使用存储介质

apiVersion: v1
kind: Pod
metadata:
  name: fortune-for-pvc
spec:
  ...
  volumes:
  - name: html
    persistentVolumeClaim: 
      claimName: demo-pv    # 通过名字引用之前创建的 pvc

5、回收持久卷

持久化的回收,so easy,删就完了

$ kubectl delete pvc demo-pvc
$ kubectl delete pv demo-pv

PV、PVC、Pod 三者的生命周期如下:
image

6、总结

两种持久卷的使用方式对比如下图。
好处显而易见,通过中间加了一层抽象,使开发者的持久化工作更加简单,且对存储介质解耦。虽然需要集群管理员做更多的配置工作,但这是值得的。

image

6.5 持久卷的动态配置

通过上面章节,我们已经可以很好很方便的使用存储资源,但每次使用都需要集群管理员配置好 PV 来支持实际的存储。

不过还好,K8S 提供 持久卷配置 来自动创建 PV。集群管理员只需定义一个或多个 SC(StorageClass)资源,用户在创建 PVC 时就可以指定 SC,K8S 就会使用 SC 的置备程序(provisioner)自动创建 PV。

Kubernetes 包括最流行的云服务提供商的置备程序(provisioner),所以管理员不需手动创建。但如果 K8S 部署在本地就需要配置一个定制的置备程序。

1、定义 SC(StorageClass)

下面是 minikube 环境下使用 hostpath PV 的一个 SC,对于其他云平台的 SC,内容会更简单,但大同小异。

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: fast
  annotations:
    storageclass.kubernetes.io/is-default-class: "false" # 是否设置为默认 SC
    creationTimestamp: "2019-12-18"
    resourceVersion: "v0.1"
provisioner: kubernetes.io/minikube-hostpath            # 调用此 SC 时使用的置备程序,对于不同的云平台选择不同内容
reclaimPolicy: Retain                                   # Supported policies: Delete, Retain
parameters:                                             # 传递给 provisioner 的参数
  type: pd-ssd

2、PVC 使用 SC 动态创建 PV

当创建下面 PVC 时就会引入上面定义的 SC,因此调用里面配置的 provisioner 来自动创建一个 PV。

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: demo-pvc
  namespace: demo-ns
spec:
  accessModes:
  - ReadWriteOnce
  storageClassName: fast        # 选择之前创建的 SC
  resources:
    requests:
      storage: 1Gi

如果引入的 SC 不存在,则 PV 配置失败(ProvisioningFailed)

3、不指定存储类的例子

上面使用 SC 的例子,在某些场景下,又一次的提高了应用 Pod 和 PVC 的移植性,因为不需要集群管理员一个一个的创建 PV 了。

在上面例子中,我们显式的指定了 storageClassName: fast 才实现了我们要的效果,如果我们把这句话删除,则 PVC 会使用默认的 SC (使用 $ kubectl get sc 列出所有 SC 就会看到默认的)。

如果想要 PVC 不通过 provisioner 创建,而是绑定到之手动配置的 PV 时,就要想最开始的例子一样,storageClassName: "" 将动态类显式的配置为空字符串。

最后,附上动态持久卷的完整图例
image

关于 local PV,推荐两篇不错的文章,看的我清醒不少

  1. nfs动态卷实现 https://blog.csdn.net/weixin_41004350/article/details/90168631
  2. 动态配置本地存储 https://www.infoq.cn/article/kuxs6LbGMR3f7ph*bGpx

猜你喜欢

转载自www.cnblogs.com/clockq/p/12297728.html