本文发表于一年多前。旧文章可能包含过时内容。请检查页面中的信息自发布以来是否已变得不正确。

如何处理数据密集型 Kubernetes 环境中的数据重复问题

为什么要重复数据?

为每个团队创建应用程序副本及其状态副本很方便。例如,您可能需要一个单独的数据库副本来测试一些重要的模式更改,或者开发其他破坏性操作,如批量插入/删除/更新...

复制数据需要大量时间。这是因为您需要先将所有数据从源块存储提供商下载到计算节点,然后再将其发送回存储提供商。此过程涉及大量的网络流量和 CPU/RAM 使用。通过将某些昂贵的操作卸载到专用硬件来实现硬件加速,始终能大幅提升性能。它能将完成操作所需的时间缩短好几个数量级。

卷快照来帮忙

Kubernetes 在 1.12 版本中引入了 VolumeSnapshots 作为 alpha 版本,在 1.17 版本中升级为 beta 版本,并在 1.20 版本中发布了正式版。VolumeSnapshots 使用存储提供商的专用 API 来复制数据卷。

由于数据已经位于同一存储设备(设备阵列)中,对于具有本地快照的存储提供商(大多数本地存储提供商),复制数据通常是元数据操作。您只需将新磁盘指向一个不可变快照,并只保存增量(或让它执行全盘复制)。由于此操作在存储后端内部进行,因此速度快得多,通常不涉及通过网络发送流量。公共云存储提供商的底层工作方式略有不同。它们将快照保存到对象存储,然后在“复制”磁盘时,再从对象存储复制回块存储。从技术上讲,在云提供商端会花费大量的计算和网络资源,但从 Kubernetes 用户的角度来看,VolumeSnapshots 的工作方式相同,无论是本地快照存储提供商还是远程快照存储提供商,并且此操作不涉及计算和网络资源。

听起来我们有解决方案了,对吗?

实际上,VolumeSnap照是命名空间的,Kubernetes 会保护命名空间数据不被租户(命名空间)之间共享。此 Kubernetes 限制是一个有意识的设计决策,以便在不同命名空间中运行的 Pod 无法挂载其他应用程序的 PersistentVolumeClaim(PVC)。

一种解决方法是在一个命名空间中创建多个包含重复数据的卷。但是,您可能会很容易引用错误的副本。

因此,想法是通过命名空间来分离团队/项目,以避免这种情况,并通常限制对生产命名空间的访问。

解决方案?外部创建“黄金快照”

绕过此设计限制的另一种方法是外部创建快照(而不是通过 Kubernetes)。这也被称为手动预配置快照。接下来,我将把它导入为多租户的黄金快照,可用于多个命名空间。下面的图示将针对 AWS EBS (Elastic Block Storage) 和 GCE PD (Persistent Disk) 服务。

准备黄金快照的高级计划

  1. 识别您要在云提供商中克隆数据的磁盘(EBS/持久性磁盘)
  2. 制作磁盘快照(在云提供商控制台中)
  3. 获取磁盘快照 ID

为每个团队克隆数据的高级计划

  1. 创建命名空间“sandbox01”
  2. 将磁盘快照 (ID) 作为 VolumeSnapshotContent 导入到 Kubernetes
  3. 在命名空间“sandbox01”中创建映射到 VolumeSnapshotContent 的 VolumeSnapshot
  4. 从 VolumeSnapshot 创建 PersistentVolumeClaim
  5. 安装带 PVC 的 Deployment 或 StatefulSet

步骤 1:识别磁盘

首先,您需要识别您的黄金源。在我的例子中,它是“production”命名空间中 PersistentVolumeClaim “postgres-pv-claim”上的 PostgreSQL 数据库。

kubectl -n <namespace> get pvc <pvc-name> -o jsonpath='{.spec.volumeName}'

输出将类似于

pvc-3096b3ba-38b6-4fd1-a42f-ec99176ed0d90

步骤 2:准备您的黄金源

您只需执行一次此操作,或者在每次要刷新黄金数据时执行。

创建磁盘快照

转到 AWS EC2 或 GCP Compute Engine 控制台,搜索具有与上次输出匹配的标签的 EBS 卷(在 AWS 上)或持久性磁盘(在 GCP 上)。在本例中,我看到:pvc-3096b3ba-38b6-4fd1-a42f-ec99176ed0d9

点击“创建快照”并为其命名。您可以在控制台中手动执行此操作,在 AWS CloudShell / Google Cloud Shell 中,或在终端中执行。要在终端中创建快照,您必须安装并配置 AWS CLI 工具 (aws) 或 Google 的 CLI (gcloud)。

这是在 GCP 上创建快照的命令

gcloud compute disks snapshot <cloud-disk-id> --project=<gcp-project-id> --snapshot-names=<set-new-snapshot-name> --zone=<availability-zone> --storage-location=<region>
Screenshot of a terminal showing volume snapshot creation on GCP

GCP 快照创建

GCP 通过 PVC 名称识别磁盘,因此是直接映射。在 AWS 中,您需要首先通过 CSIVolumeName AWS 标签(值为 PVC 名称)查找卷(volume-id),该标签将用于快照创建。

Screenshot of AWS web console, showing EBS volume identification

在 AWS 上识别磁盘 ID

标记完成卷(volume-id)vol-00c7ecd873c6fb3ec,然后可以在 AWS 控制台中创建 EBS 快照,或者使用aws cli

aws ec2 create-snapshot --volume-id '<volume-id>' --description '<set-new-snapshot-name>' --tag-specifications 'ResourceType=snapshot'

步骤 3:获取您的磁盘快照 ID

在 AWS 中,上述命令将输出类似以下内容:

"SnapshotId": "snap-09ed24a70bc19bbe4"

如果您使用的是 GCP 云,您可以通过查询给定快照的名称,从 gcloud 命令获取快照 ID

gcloud compute snapshots --project=<gcp-project-id> describe <new-snapshot-name> | grep id:

您应该会得到类似的输出

id: 6645363163809389170

步骤 4:为每个团队创建开发环境

现在我有了我的黄金快照,它是不可变数据。每个团队都将获得此数据的副本,团队成员可以根据需要修改它,前提是每个团队都将创建一个新的 EBS/持久性磁盘。

下面我将为每个命名空间定义一个清单。为了节省时间,您可以使用诸如 sedyq 等工具替换命名空间名称(例如将“sandbox01”更改为“sandbox42”),或者使用 Kubernetes 感知模板工具(如 Kustomize),或者在 CI/CD 管道中使用变量替换。

这是一个清单示例

---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotContent
metadata:
 name: postgresql-orders-db-sandbox01
 namespace: sandbox01
spec:
 deletionPolicy: Retain
 driver: pd.csi.storage.gke.io
 source:
   snapshotHandle: 'gcp/projects/staging-eu-castai-vt5hy2/global/snapshots/6645363163809389170'
 volumeSnapshotRef:
   kind: VolumeSnapshot
   name: postgresql-orders-db-snap
   namespace: sandbox01
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
 name: postgresql-orders-db-snap
 namespace: sandbox01
spec:
 source:
   volumeSnapshotContentName: postgresql-orders-db-sandbox01

在 Kubernetes 中,VolumeSnapshotContent (VSC) 对象不是命名空间的。但是,我需要为每个不同的命名空间使用单独的 VSC,因此每个 VSC 的 metadata.name 也必须不同。为了简化这一点,我将目标命名空间作为名称的一部分。

现在是时候用安装在您的 K8s 集群中的 CSI(容器存储接口)驱动替换驱动程序字段了。主要的云提供商都有支持 VolumeSnapshots 的块存储 CSI 驱动,但通常 CSI 驱动默认不安装,请咨询您的 Kubernetes 提供商。

上面的清单定义了一个在 GCP 上运行的 VSC。在 AWS 上,驱动程序和 SnashotHandle 值可能如下所示:

  driver: ebs.csi.aws.com
  source:
    snapshotHandle: "snap-07ff83d328c981c98"

此时,我需要使用**保留**策略,这样 CSI 驱动就不会尝试删除我手动创建的 EBS 磁盘快照。

对于 GCP,您必须手动构建此字符串——添加完整的项目 ID 和快照 ID。对于 AWS,它只是一个普通的快照 ID。

VSC 还需要指定哪个 VolumeSnapshot (VS) 将使用它,因此 VSC 和 VS 相互引用。

现在我可以从上面的 VS 创建 PersistentVolumeClaim。首先设置这个很重要

---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: postgres-pv-claim
 namespace: sandbox01
spec:
 dataSource:
   kind: VolumeSnapshot
   name: postgresql-orders-db-snap
   apiGroup: snapshot.storage.k8s.io
 accessModes:
   - ReadWriteOnce
 resources:
   requests:
     storage: 21Gi

如果默认 StorageClass 具有 WaitForFirstConsumer 策略,那么实际的云磁盘将仅在某个 Pod 绑定该 PVC 时才从 Golden Snapshot 创建。

现在我将该 PVC 分配给我的 Pod(在我的案例中是 PostgreSQL),就像我使用任何其他 PVC 一样。

kubectl -n <namespace> get volumesnapshotContent,volumesnapshot,pvc,pod

VS 和 VSC 都应为 READYTOUSE true,PVC 已绑定,并且 Pod(来自 Deployment 或 StatefulSet)正在运行。

为了继续使用我的黄金快照中的数据,我只需要为下一个命名空间重复此操作即可!无需在复制过程中浪费时间和计算资源。