Skip to content

Files

Latest commit

0279954 · Dec 27, 2021

History

History
577 lines (435 loc) · 22.2 KB

File metadata and controls

577 lines (435 loc) · 22.2 KB

七、Kubernetes 上的存储

在本章中,我们将学习如何在 Kubernetes 上提供应用存储。我们将回顾 Kubernetes 上的两个存储资源,卷和持久卷。卷非常适合瞬态数据需求,但是持久卷对于在 Kubernetes 上运行任何严重的有状态工作负载都是必要的。通过本章中学习的技能,您将能够以几种不同的方式和环境为运行在 Kubernetes 上的应用配置存储。

在本章中,我们将涵盖以下主题:

  • 了解卷和持久卷之间的区别
  • 使用卷
  • 创建永久卷
  • 持续批量索赔

技术要求

为了运行本章中详细介绍的命令,您将需要一台支持kubectl命令行工具的计算机以及一个工作正常的 Kubernetes 集群。参见 第 1 章与 Kubernetes通讯,了解几种与 Kubernetes 快速启动运行的方法,以及如何安装kubectl工具的说明。

本章使用的代码可以在本书的 GitHub 资源库中找到https://GitHub . com/PacktPublishing/Cloud-Native-with-Kubernetes/tree/master/chapter 7

了解卷和持久卷的区别

一个完全无状态的容器化应用可能只需要容器文件本身的磁盘空间。当运行这种类型的应用时,在 Kubernetes 上不需要额外的配置。

然而,这在现实世界中并不总是正确的。出于多种可能的原因,正在被移动到容器的旧应用可能需要磁盘空间卷。为了保存供容器使用的文件,您需要 Kubernetes 卷资源。

在 Kubernetes 中可以创建两种主要的存储资源:

  • 持久卷

两者的区别在于名称:虽然卷与特定 Pod 的生命周期相关联,但持久卷在被删除之前保持活动状态,并且可以在不同 Pod 之间共享。卷可以方便地跨 Pod 中的容器共享数据,而持久卷可以用于许多可能的高级目的。

让我们先看看如何实现卷。

Kubernetes 支持许多不同的卷子类型。大多数可用于卷或持久卷,但有些是特定于任一资源的。我们将从最简单的开始,回顾一些类型。

重要说明

您可以在 https://kubernetes . io/docs/concepts/storage/volumes/# volumes-type-of-volumes 上看到当前卷类型的完整列表。

以下是卷子类型的简短列表:

  • awsElasticBlockStore
  • cephfs
  • ConfigMap
  • emptyDir
  • hostPath
  • local
  • nfs
  • persistentVolumeClaim
  • rbd
  • Secret

如您所见,配置图和机密实际上都实现为类型的卷。此外,该列表还包括云提供商卷类型,如awsElasticBlockStore

与独立于任何一个 Pod 创建的持久卷不同,创建卷通常是在 Pod 的上下文中完成的。

要创建一个简单的卷,您可以使用以下 Pod YAML:

带 vol.yaml 的 pod

apiVersion: v1
kind: Pod
metadata:
  name: pod-with-vol
spec:
  containers:
  - name: busybox
    image: busybox
    volumeMounts:
    - name: my-storage-volume
      mountPath: /data
  volumes:
  - name: my-storage-volume
    emptyDir: {}

这个 YAML 将会创造一个 PODS 和一个类型emptyDir的体积。使用调配类型为emptyDir的卷,无论 Pod 分配到的节点上已经存在什么存储。如前所述,卷与 Pod 的生命周期相关联,而不是其容器。

这意味着在具有多个容器的 Pod 中,所有容器都将能够访问卷数据。让我们以 Pod 的 YAML 文件为例:

多容器 pod . YAML

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  containers:
  - name: busybox
    image: busybox
    volumeMounts:
    - name: config-volume
      mountPath: /shared-config
  - name: busybox2
    image: busybox
    volumeMounts:
    - name: config-volume
      mountPath: /myconfig
  volumes:
  - name: config-volume
    emptyDir: {}

在本例中,Pod 中的两个容器都可以访问卷数据,尽管路径不同。容器甚至可以通过共享卷中的文件进行通信。

规格的重要部分是volume spec本身(在volumes下的列表项)和体积的mount(在volumeMounts下的列表项)。

每个装载项包含一个名称,该名称对应于volumes部分中的卷名称,以及一个mountPath,该名称将指示卷装载到容器上的哪个文件路径。例如,在前面的 YAML,可以从位于/shared-configbusybox容器内和位于/myconfigbusybox2容器内访问体积config-volume

卷规格本身采用一个名称,在本例中为my-storage,以及特定于卷类型的附加键/值,在本例中为emptyDir,并且只采用空括号。

现在,让我们来看一下装载到 Pod 上的云供应卷的示例。例如,要安装 AWS 弹性块存储 ( EBS )卷,可以使用以下 YAML:

pod-with-ebs.yaml

apiVersion: v1
kind: Pod
metadata:
  name: my-app
spec:
  containers:
  - image: busybox
    name: busybox
    volumeMounts:
    - mountPath: /data
      name: my-ebs-volume
  volumes:
  - name: my-ebs-volume
    awsElasticBlockStore:
      volumeID: [INSERT VOLUME ID HERE]

只要您的群集设置正确,可以通过 AWS 进行身份验证,YAML 就会将您现有的 EBS 卷连接到 Pod。如您所见,我们使用awsElasticBlockStore键专门配置要使用的确切卷标识。在这种情况下,您的 AWS 帐户和地区中必须已经存在 EBS 卷。使用 AWS 弹性 Kubernetes 服务 ( EKS )这要容易得多,因为它允许我们从 Kubernetes 内部自动调配 EBS 卷。

Kubernetes 还在 Kubernetes AWS 云提供商中包含了自动调配卷的功能,但这些功能是用于持久卷的。我们将在持久卷部分了解如何获取这些自动调配的卷。

持久卷

持久卷相对于常规 Kubernetes 卷有一些关键的优势。如前所述,它们(持久卷)的生命周期与集群的生命周期相关,而不是单个 Pod 的生命周期。这意味着只要集群在运行,持久卷就可以在 Pods 之间共享和重用。由于这个原因,该模式更好地匹配外部存储,如 EBS(AWS 上的块存储服务),因为存储本身比单个 Pod 更持久。

使用持久卷实际上需要两种资源:PersistentVolume本身和一个PersistentVolumeClaim,用于将一个PersistentVolume安装到一个 Pod 上。

让我们从PersistentVolume本身开始——看看创建PersistentVolume的基本 YAML:

pv.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: my-pv
spec:
  storageClassName: manual
  capacity:
    storage: 5Gi
  accessModes:
    - ReadWriteOnce
  hostPath:
    path: "/mnt/mydata"

现在让我们把这个分开。从规格中的第一行开始–storageClassName

第一个配置storageClassName代表我们想要使用的存储类型。对于hostPath卷类型,我们只需指定manual,但是对于 AWS EBS,例如,您可以创建并使用名为gp2Encrypted的存储类来匹配启用了 EBS 加密的 AWS 中的gp2存储类型。因此,存储类是可用于特定卷类型的配置组合,可在卷规范中参考。

继续我们的 AWS StorageClass示例,让我们为gp2Encrypted提供一个新的StorageClass:

gp 2-存储类. yaml

kind: StorageClass
apiVersion: storage.k8s.io/v1
metadata:
  name: gp2Encrypted
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
provisioner: kubernetes.io/aws-ebs
parameters:
  type: gp2
  encrypted: "true"
  fsType: ext4

现在,我们可以使用gp2Encrypted存储类创建我们的PersistentVolume。但是,使用动态调配的 EBS(或其他云)卷创建PersistentVolumes有一个快捷方式。当使用动态调配的卷时,我们首先创建PersistentVolumeClaim,然后自动生成PersistentVolume

持续卷索赔

我们现在知道,您可以在 Kubernetes 中轻松创建持久卷,但是,这不允许您将存储绑定到 Pod。您需要创建一个PersistentVolumeClaim,它声明一个PersistentVolume,并允许您将该声明绑定到一个 Pod 或多个 Pod。

在我们上一节新的StorageClass的基础上,让我们声明将自动创建一个新的PersistentVolume,因为没有其他具有我们期望的StorageClass的持久卷:

pvc .亚 ml

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: my-pv-claim
spec:
  storageClassName: gp2Encrypted
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

在此文件上运行kubectl apply -f将会创建一个新的、自动生成的持久卷 ( PV )。如果您的 AWS 云提供商设置正确,这将导致创建一个新的 EBS 卷,并启用 GP2 类型和加密。

在我们将 EBS 支持的持久卷附加到 Pod 之前,让我们确认 EBS 卷在 AWS 中创建正确。

为此,我们可以导航到我们的 AWS 控制台,并确保我们位于 EKS 集群运行所在的同一地区。然后转到服务 > EC2 点击左侧菜单弹性块商店下的。在本节中,我们将看到一个与我们的聚氯乙烯状态具有相同大小的自动生成体积的行项目( 1 GiB )。它应该具有 GP2 类,并且应该启用加密。让我们看看在 AWS 控制台中会是什么样子:

Figure 7.1 – AWS console with autocreated EBS volume

图 7.1–自动创建 EBS 卷的 AWS 控制台

如您所见,我们在 AWS 中正确创建了动态生成的 EBS 卷,启用了加密并分配了 gp2 卷类型。现在我们已经创建了我们的卷,我们已经确认它是在 AWS 中创建的,我们可以将其附加到我们的 Pod 上。

将持久卷声明(PVCs)附加到 Pods

现在我们有了一个PersistentVolume和一个PersistentVolumeClaim,我们可以将它们连接到一个 Pod 上进行消费。这个过程非常类似于附加一个配置映射或机密——这是有意义的,因为配置映射和机密本质上是卷的类型!

查看 YAML,它允许我们将加密的 EBS 卷附加到 Pod 上,并将其命名为pod-with-attachment.yaml:

带附件的 pod . YAML

apiVersion: v1
kind: Pod
metadata:
  name: my-pod
spec:
  volumes:
    - name: my-pv
      persistentVolumeClaim:
        claimName: my-pv-claim
  containers:
    - name: my-container
      image: busybox
      volumeMounts:
        - mountPath: "/usr/data"
          name: my-pv

运行kubectl apply -f pod-with-attachment.yaml将会产生一个 Pod ,通过我们对/usr/data的声明安装我们的PersistentVolume

为了确认卷已成功创建,让我们将exec放入我们的 Pod,并在卷已装载的位置创建一个文件:

> kubectl exec -it shell-demo -- /bin/bash
> cd /usr/data
> touch myfile.txt

现在,让我们使用以下命令删除 Pod :

> kubectl delete pod my-pod

并使用以下命令重新创建:

> kubectl apply -f my-pod.yaml

如果我们做得对,我们应该能够在运行kubectl exec再次进入 Pod 时看到我们的文件:

> kubectl exec -it my-pod -- /bin/bash
> ls /usr/data
> myfile.txt

成功!

我们现在知道如何为 Kubernetes 创建云存储提供的持久卷。但是,您可能正在内部或使用 minikube 在笔记本电脑上运行 Kubernetes。让我们来看一些可以替代使用的备用持久卷子类型。

没有云存储的持久卷

我们之前的示例假设您正在云环境中运行 Kubernetes,并且可以利用云平台(AWS EBS 等)提供的存储服务。然而,这并不总是可能的。您可能正在数据中心环境或专用硬件上运行 Kubernetes。

在这种情况下,有许多潜在的解决方案可以为 Kubernetes 提供存储。一个简单的方法是将卷类型更改为hostPath,这将在节点的现有存储设备中创建持久卷。例如,当在 minikube 上运行时,这很好,但是没有像 AWS EBS 那样提供强大的抽象。对于一个具有类似于 EBS 等云存储工具的内部部署功能的工具,让我们看看如何将 Ceph 与 Rook 结合使用。要获得完整的文档,请查看 https://rook.io/docs/rook/v1.3/ceph-quickstart.html 的鲁克医生(他们也会教你 Ceph)。

Rook 是一个流行的开源 Kubernetes 存储抽象层。它可以通过各种提供商(如 EdgeFS 和 NFS)提供持久卷。在这种情况下,我们将使用 Ceph,一个提供对象、块和文件存储的开源存储项目。为了简单起见,我们将使用块模式。

在 Kubernetes 上安装 Rook 实际上非常简单。我们将带您从安装 Rook 到设置 Ceph 集群,最后在我们的集群上配置持久卷。

安装车

我们将使用由 Rook GitHub 存储库提供的典型的 Rook 安装默认设置。这可以根据使用情形进行高度定制,但允许我们为工作负载快速设置数据块存储。为此,请参考以下步骤:

  1. 首先,让我们克隆 Rook 存储库:

    > git clone --single-branch --branch master https://github.com/rook/rook.git
    > cd cluster/examples/kubernetes/ceph
    
  2. 我们的下一步是创建所有相关的 Kubernetes 资源,包括几个自定义资源定义 ( CRDs )。我们将在后面的章节中讨论这些,但是现在,考虑它们是专门针对 Rook 的新 Kubernetes 资源,在典型的 Pods、Services 等之外。要创建公共资源,请运行以下命令:

    > kubectl apply -f ./common.yaml
    
  3. 接下来,让我们开始我们的 Rook 操作符,它将处理为特定的 Rook 提供者提供所有必要的资源,在这种情况下,它将是 Ceph:

    > kubectl apply -f ./operator.yaml
    
  4. 在下一步之前,使用以下命令确保车操作员 Pod 实际运行:

    > kubectl -n rook-ceph get pod
    
  5. 一旦车荚处于Running状态,我们就可以建立我们的 Ceph 集群了!这个的 YAML 也在我们从 Git 克隆的文件夹中。使用以下命令创建它:

    > kubectl create -f cluster.yaml
    

这个过程可能需要几分钟。Ceph 集群由几种不同的 Pod 类型组成,包括操作员、对象存储设备 ( 操作系统)和管理人员。

为了确保我们的 Ceph 集群正常工作,Rock 提供了一个工具箱容器映像,允许您使用 Rock 和 Ceph 命令行工具。要启动工具箱,可以使用位于https://rook.io/docs/rook/v0.7/toolbox.html的 Rook 项目提供的工具箱 Pod 规范。

以下是工具箱 Pod 的规格示例:

rook 工具箱 pod.yaml

apiVersion: v1
kind: Pod
metadata:
  name: rook-tools
  namespace: rook
spec:
  dnsPolicy: ClusterFirstWithHostNet
  containers:
  - name: rook-tools
    image: rook/toolbox:v0.7.1
    imagePullPolicy: IfNotPresent

可以看到,这个 Pod 使用了 Rook 提供的特殊容器映像。该映像附带了所有工具,你需要调查鲁克和 Ceph 预装。

一旦工具箱 Pod 运行,就可以使用rookctlceph命令检查集群状态(查看 Rook 文档了解详情)。

车-ceph-块存储类

现在我们的集群正在工作,我们可以创建我们的存储类,供我们的 PVs 使用。我们将这个存储类称为rook-ceph-block。这是我们的 YAML 文件(ceph-rook-combined.yaml),它将包括我们的CephBlockPool(它将处理我们在 Ceph 的块存储–更多信息请参见https://rook.io/docs/rook/v0.9/ceph-pool-crd.html)以及存储类本身:

ceph-rook-combined.yaml

apiVersion: ceph.rook.io/v1
kind: CephBlockPool
metadata:
  name: replicapool
  namespace: rook-ceph
spec:
  failureDomain: host
  replicated:
    size: 3
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
   name: rook-ceph-block
provisioner: rook-ceph.rbd.csi.ceph.com
parameters:
    clusterID: rook-ceph
    pool: replicapool
    imageFormat: "2"
currently supports only `layering` feature.
    imageFeatures: layering
    csi.storage.k8s.io/provisioner-secret-name: rook-csi-rbd-provisioner
    csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
    csi.storage.k8s.io/node-stage-secret-name: rook-csi-rbd-node
    csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
csi-provisioner
    csi.storage.k8s.io/fstype: xfs
reclaimPolicy: Delete

如您所见,YAML 规范定义了我们的StorageClassCephBlockPool 资源。正如我们在本章前面提到的,StorageClass是我们告诉 Kubernetes 如何实现PersistentVolumeClaim的方式。另一方面,CephBlockPool资源告诉 Ceph 如何以及在哪里创建分布式存储资源——在这种情况下,复制多少存储。

现在我们可以给 PODS 一些储存空间了!让我们用新的存储类别创建一个新的聚氯乙烯:

rook-ceh PVC . YAML

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: rook-pvc
spec:
  storageClassName: rook-ceph-block
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 1Gi

我们的 PVC 属于存储类rook-ceph-block,所以它将使用我们刚刚创建的新存储类。现在,让我们把聚氯乙烯给我们的 YAML 文件中的 PODS:

rook-ceh pod . YAML

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

当 Pod 被创建时,Rook 应该旋转一个新的持久卷并将其连接到 Pod。让我们仔细观察 Pod,看看它是否正常工作:

> kubectl exec -it my-rook-test-pod -- /bin/bash
> cd /usr/rooktest
> touch myfile.txt
> ls

我们得到以下输出:

> myfile.txt

成功!

虽然我们刚刚在 Ceph 中使用了 Rook 和 Ceph 的数据块存储功能,但它也有一个文件系统模式,这有一些好处——让我们讨论一下为什么您可能想要使用它。

车 Ceph 文件系统

Rook 的 Ceph Block 提供程序的缺点是一次只能被一个 Pod 写入。为了用 Rook/Ceph 创建持久卷,我们需要使用支持 RWX 模式的文件系统提供程序。更多信息,请查看https://rook.io/docs/rook/v1.3/ceph-quickstart.html的鲁克/Ceph 医生。

直到创建 Ceph 集群,前面的所有步骤都适用。此时,我们需要创建我们的文件系统。让我们使用下面的 YAML 文件来创建它:

路克-ceh-fs . YAML

apiVersion: ceph.rook.io/v1
kind: CephFilesystem
metadata:
  name: ceph-fs
  namespace: rook-ceph
spec:
  metadataPool:
    replicated:
      size: 2
  dataPools:
    - replicated:
        size: 2
  preservePoolsOnDelete: true
  metadataServer:
    activeCount: 1
    activeStandby: true

在本例中,我们将元数据和数据复制到至少两个池中以提高可靠性,如metadataPooldataPool块中所配置的。我们还使用preservePoolsOnDelete键保留删除时的池。

接下来,让我们专门为 Rook/Ceph 文件系统存储创建我们的新存储类。下面的 YAML 就是这样做的:

路克-ceh-fs-storage class . YAML

apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: rook-cephfs
provisioner: rook-ceph.cephfs.csi.ceph.com
parameters:
  clusterID: rook-ceph
  fsName: ceph-fs
  pool: ceph-fs-data0
  csi.storage.k8s.io/provisioner-secret-name: rook-csi-cephfs-provisioner
  csi.storage.k8s.io/provisioner-secret-namespace: rook-ceph
  csi.storage.k8s.io/node-stage-secret-name: rook-csi-cephfs-node
  csi.storage.k8s.io/node-stage-secret-namespace: rook-ceph
reclaimPolicy: Delete

这个rook-cephfs存储类指定了我们之前创建的池,并描述了我们存储类的回收策略。最后,它使用了一些注释,这些注释在 Rook/Ceph 文档中有所解释。现在,我们可以通过聚氯乙烯将它连接到部署,而不仅仅是 Pod!看看我们的 PV:

路克-cehfs-PVC . YAML

kind: PersistentVolumeClaim
apiVersion: v1
metadata:
  name: rook-ceph-pvc
spec:
  storageClassName: rook-cephfs
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

这个持久的卷引用了我们在ReadWriteMany模式下的新rook-cephfs存储类——我们需要1 Gi这个数据。接下来,我们可以创建我们的Deployment:

rook-cephfs-deployment.yaml

apiVersion: v1
kind: Deployment
metadata:
  name: my-rook-fs-test
spec:
  replicas: 3
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25% 
  selector:
    matchLabels:
      app: myapp
  template:
      spec:
  	  volumes:
    	  - name: my-rook-ceph-pv
        persistentVolumeClaim:
          claimName: rook-ceph-pvc
  	  containers:
    	  - name: my-container
         image: busybox
         volumeMounts:
         - mountPath: "/usr/rooktest"
           name: my-rook-ceph-pv

Deployment引用了我们使用volumes下的persistentVolumeClaim块进行的ReadWriteMany持续体积索赔。部署后,我们所有的 Pods 现在都可以读写同一个持久卷。

在此之后,您应该对如何创建持久卷并将其附加到 Pods 有了很好的了解。

总结

在本章中,我们回顾了在 Kubernetes 上提供存储的两种方法—卷和持久卷。首先,我们讨论了这两种方法之间的区别:虽然卷与 Pod 的生命周期相关联,但持久卷会持续到它们或群集被删除。然后,我们研究了如何实现卷并将它们连接到我们的 Pods。最后,我们将对卷的学习扩展到持久卷,并发现了如何使用几种不同类型的持久卷。这些技能将帮助您在许多可能的环境中为您的应用分配持久和非持久存储—从内部部署到云。

在下一章中,我们将绕过应用问题,讨论如何在 Kubernetes 上控制 Pod 的放置。

问题

  1. 卷和持久卷有什么区别?
  2. 什么是StorageClass,它与体积有什么关系?
  3. 当创建 Kubernetes 资源(如持久卷)时,如何自动调配云资源?
  4. 在哪些使用案例中,您认为使用卷而不是持久卷会令人望而却步?

进一步阅读

请参考以下链接了解更多信息: