如何在OpenShift集群上备份、克隆和迁移PVC

如何在OpenShift集群上备份、克隆和迁移PVC

之前因为图床不能用了,所以你看前面的文章都没有图片了,这一度让我对写博心灰意冷,大半年碰都不想碰,血的教训告诉大家慎重“薅羊毛”,有个好哥们一直催我更更更,说实话真的挺想拍死他的,哈哈哈哈,开玩笑的。进入正题吧,这次要说的是如何在 OpenShift 集群中如何备份、克隆和迁移 PVC(持久化存储卷声明)。

======开始正题======

最近在实施完全备份 OpenShift 集群的解决方案。本次要分享的就是在 OpenShift 中备份,恢复,迁移和克隆用来保存用户的 PVC(持久化存储卷声明) 所面临的挑战。

目前,这些功能还没有直接在 Kubernetes 中实现,所有 Kubernetes 发行版都没有一个完备可行的生产方案。有一些第三方产品和项目虽然可以满足部分需求,例如Velero,Avamar等,但都不能完全符合我们的要求,几点原因如下:

  • 价格/许可:一些方案需要购买license,但我们希望使用开源软件来实现
  • 安全:我们要避免使用 NodePort 或特权 pod
  • 开发变化:一些第三方的方案架构需要在自定义模板中添加 sidecar、自定义资源等才能运行
  • 其他解决方案需要安装自定义组件(如:自定义的控制平面或 CLI 工具)

出于这些原因,我决定自己出一个解决方案,而且也很容易实现:

  • 备份目标 PVC 到另一个 PVC
  • 在不同的存储类型之间迁移,比如从 NFS/NAS 迁移到 iSCSI/SAN
  • 多克隆几份 PVC

本文主要描述 PVC 备份系统。注意,此解决方案仅仅针对解决备份和迁移用户数据卷的问题,而不是针对 Kubernetes 控制平面数据和配置,例如etcd。

传统模式下,备份和恢复操作涉及两个不同的层面。首先是应用层,比如:数据库都有自己的工具集和创建应用一致性备份的程序。另一个则是底层存储。本文重点介绍如何备份,迁移和恢复存储层实体:Kubernetes PVC(持久化存储卷声明)和 PV(持久化存储卷)。

注意事项

像数据库的后端存储,他们的文件会被持续写入数据。备份这种文件比备份更改频率较低的文件(比如文档、图片、音频和视频)更需要注意。

备份处在读写状态的文件和数据库的最佳实践不在本文讨论范围,可以在 OpenShift 文档 中找到示例。但我们倾向于使用数据库自带的工具进行备份和恢复(例如,mysqldump,pg_dump等)。

您可以将数据库的备份工具与本文中介绍的解决方案结合使用,以获得数据库快照的完整备份,作为备份持久化存储卷方案的一部分。例如:

  • 备份数据库(mysqldump)。
  • 把所有 dump 文件存储到 PV 中。
  • 用我们自定义的解决方案备份 PV,可以把这个备份的 PV 称为存档 PV。
  • 把存档 PV 挂载到新的数据库,通过指定的 dump 文件恢复数据库。

架构

本方案的架构相对简单,核心组件是一个基于 自定义容器镜像 的 pod,称为 BackupEr 。这个 pod 负责运行 备份脚本

如上图所示,在 OpenShift 集群中创建的 MyProject Project 中, BackupEr pod 有访问 MyPod 的 PVC 的权限,BackupEr pod 也有自己的 PVC。当 BackupEr pod 启动时,他运行 backup.sh 脚本从 MyPod 的 PVC 中复制数据到自己的 PVC 中。这就是在 OpenShift 集群中备份、迁移或克隆 PV 的工具!

如果你想了解更多有关持久化存储的知识,可以查看 OpenShift 文档中 持久化存储章节。

警告

针对 NAS 的 PV:

如果你已经按照 安全建议 来搭建一台 NFS 服务器为 OpenShift 集群提供持久化存储,以 ownerID 65534 为例。即使 NFS 的 root_squash 将 root (UID 0) 映射到 nfsnobody (UID 65534), NPS exports 也可以用任意 ownerID,不一定需要 ownerID 65534。

这意味着即使你有配置了 NFS 挂载点的 OpenShift 节点的 root 访问权限,你也可能不会有存储在该挂载点上的文件的读写权限。备份脚本对这种情景有一些特别的优势:

  1. 容器镜像不需要以 root 用户启动,但是在执行脚本前需要一些小操作:
    1
    2
    chmod u+s /usr/bin/sed && \
    chmod +x $IMAGE_SCRIPTS_HOME/backup.sh

/usr/bin/sed 可执行文件上设置粘滞位,给 sed 程序的所有者以 suid 权限,让普通用户可以像 root 用户一样操作。

  1. 然后 backup.sh 脚本使用这个有 suid 权限的 sed 从源 PVC 复制文件到目标 PVC。
    • 从你想要克隆的 PVC 中的文件获取 uid 和 gid
    • 使用 sed 来为 BackupEr pod 中的 /etc/passwd/etc/group 添加上一步中获得的 UID/GID
    • su 下运行 rsync 为 pod 的用户数据库文件添加 UID/GID

注意:以下为完整的 Dockerfilescript 的 url:

下文中我们将讨论这个 UID 的安全含义。

针对 SAN 的 PV:

唯一的限制就是 BackupEr pod/PVC 和你想备份的 pod/PVC 要部署在 OpenShift 集群中的同一个节点上。设置 BackupEr pod 中的 spec.nodeName 为所需的 OpenShift 节点。点击查看完整示例 backup-block。指定主机的配置部分如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
apiVersion: v1
kind: Pod
metadata:

[...]

spec:
containers:

[...]

dnsPolicy: ClusterFirst
nodeName: ${NODE}

[...]

先决条件

本文目标:

  • OpenShift集群必须为 3.9 及以上版本,才能提供必须要用到的特性:ValidatingWebhookConfiguration。
  • 必须构建 BackupEr 容器镜像并 push 到容器镜像仓库,或者使用自定义模板,或拉取完整镜像 docker pull quay.io/josgonza/pvc-backup:latest

安装

在 OpenShift 中部署 BackupEr (以下步骤需要 cluster-admin 或类似的权限):

  1. 在执行第一个备份前,需要调整安全上下文(SCC)

    1
    oc create -f https://raw.githubusercontent.com/josgonza-rh/pvc-backup/master/openshift/scc_restricted-runasnonroot-uid.yaml
  2. 在 OpenShift 中添加 BackupEr 模板

    1
    2
    oc create -f https://raw.githubusercontent.com/josgonza-rh/pvc-backup/master/openshift/temp_backup-block.yaml -n openshift
    oc create -f https://raw.githubusercontent.com/josgonza-rh/pvc-backup/master/openshift/temp_backup-shared.yaml -n openshift
  3. 将步骤1中调整后的安全上下文(SCC)添加到模板 BackupEr 创建的 ServiceAccount

    1
    oc adm policy add-scc-to-user restricted-runasnonroot-uid -z pvc-backup-deployer -n myproject

备份

部署 BackupEr pod 以执行 PVC 备份

1
2
3
4
5
6
7
8
9
10
11
12
## Using templates installed in step 2:

#### ex How to backup a SAN/iSCSI PVC
oc new-app --template=backup-block \
-p PVC_NAME=pvc-to-backup \
-p PVC_BCK=pvc-for-backuper \
-p NODE=node1.mydomain.com

#### ex How to backup a NAS/NFS PVC
oc new-app --template=backup-shared \
-p PVC_NAME=pvc-to-backup \
-p PVC_BCK=pvc-for-backuper

恢复

步骤3绑定安全上下文到创建备份的 ServiceAccount,可以卸载当前的 PVC (备份源,即 myapp pod 的 PVC)并挂载带有备份数据的目标 PVC( 即BackupEr pod 的 PVC),以恢复数据

1
2
oc set volumes dc/myapp --add --overwrite --name=mydata \
--type=persistentVolumeClaim --claim-name=pvc-for-backuper

该步骤不会删除备份源 PVC,使用 oc rollback dc/myapp 可以还原挂载。

安全问题

如果你仔细阅读了本文,就会发现此方案只适合用于受控的集群环境,因为他有一些安全警告:

  • myapp pod 需要额外的能力,如:SETUID, SETGIDDAC_OVERRIDE(给 Service Account 添加新的 SCC)。
  • 普通集群用户不仅可以使用 pvc-backup-deployer ServiceAccount 部署 BackupEr pod,也可以部署自己的应用。

这时候就需要用到 Admission Webhooks(基于 Webhook 的准入控制机制)。我们可以使用 Admission Webhook 来防止在用户项目中创建的特权服务账户被滥用。

Admission Webhooks 调用 webhook 服务器以便在创建 pod 时更新配置——例如注入标签,或者在准入过程中验证 pod 配置。在资源对象存储到etcd之前,身份认证和授权请求之后,拦截所有到 master API 的请求。

我们准备使用两种 Admission Webhooks 类型中的一种, Validating admission webhooks ,允许使用验证 webhooks 来强制使用自定义的准入策略

我们的Validating admission webhookdenysabck , 拦截对 API 的请求并且丢弃使用 pvc-backup-deployer ServiceAccount 运行的除 BackupEr 之外的其他容器的全部请求。你可以在代码中查看完整过程。

部署自定义的 webhook

  1. 部署 Node.js 代码:webhook-denysabck,有以下几种方式:

Webhook 与集群 API 之间通信必须使用可信任的 SSL 证书来保证安全。如果 webhook-denysabck 是部署在集群内部,我建议使用 Service 而不是通过 Route 来和集群 API 通信(Service 也必须使用可信任的证书)

  1. 现在,为了配置和启用我们自定义的 webhook,可以使用 denysabck-webhook-cfg 的 yaml 文件作为模板。只要将 webhooks.clientConfig.url 替换为一个有效的部署在集群外的 webhook-denysabck 的 url 或者 Route 来代替以 Service 与 API 通信的方法。使用 Service 的方法的示例。

启用 ValidatingAdmissionWebhook 准入控制器

  1. 编辑 /etc/origin/master/master-config.yaml 添加以下内容:
1
2
3
4
5
6
7
8
9
10
11
12
admissionConfig:
pluginConfig:

[...]

ValidatingAdmissionWebhook:
configuration:
apiVersion: v1
disable: false
kind: DefaultAdmissionConfig

[...]
  1. 重启 API Server和控制器
  2. 为需要 webhook 验证的 Project 添加标签
    1
    oc label namespace myproject “denysabck=enabled”

更多信息请查看 OpenShift 文档中自定义准入控制器部分

警示:OpenShift 3.9 中,Admission webhooks 功能还只是技术预览版

改进

一些改进的想法:

  • 使用基于角色的安全上下文访问代替将 ServiceAccount 直接分配给 SCC ,这样的好处是可以把安全上下文的权限范围指定在某些 Project 或者整个集群。具体请看下文。
  • 使用 CronJob 自动备份。一些自动化维护任务的例子
  • 改为 Operator

基于角色的安全上下文访问

之前没提过这个,但这确实是个很有趣的方法(该功能在3.11及以上GA),我决定专门为他写几行,尽量保持尽可能简单的安装步骤。

这个方法的优点是,他不使用“传统”安全上下文 (SCC) 分配( oc adm policy add-scc-to- ):

  • 不需要去改变 SCC 对象( groups 字段和 users字段 )
  • 避免在更新 SCC 时丢失这些配置

只需要关心管理角色绑定(rolebindings)

  1. 创建集群角色(ClusterRole)

    1
    oc create -f https://raw.githubusercontent.com/josgonza-rh/pvc-backup/master/openshift/clusterrole_backuper.yaml
  2. BackupEr 模板导入到 OpenShift 平台

    1
    2
    oc create -f https://raw.githubusercontent.com/josgonza-rh/pvc-backup/master/openshift/temp_backup-block_3.11.yaml -n openshift
    oc create -f https://raw.githubusercontent.com/josgonza-rh/pvc-backup/master/openshift/temp_backup-shared_3.11.yaml -n openshift

因为这个模板负责创建 ServiceAccount 并为其分配自定义 ClusterRole,你不再需要额外的命令来启动备份进程,这与我们之前的备份不同。

总结

看到这里你应该能感觉到在 Kubernetes/OpenShift 集群中备份、实施和迁移 PVC 有多困难了吧。 OpenShift 支持使用 webhooks 和基于角色的安全上下文访问部署安全的自定义应用,但本文的这个方案并不受 RedHat 的支持,只是抛转引玉,不建议用于生产环境。期待未来会有基于 Operator 的实现方案,可以自动安装、备份和恢复像数据库这样有状态的应用。