Velero备份实战 - 基于Ceph的CSI快照

背景知识

卷快照(Volume Snapshot)

继CSI之后,卷快照在Kubernetes v1.12发布了alpha版,引入了以下几个关键的API对象:

  • VolumeSnapshot

    与PVC类似,当用户想创建或删除一个卷快照时,可以选择创建或删除一个VolumeSnapshot。

  • VolumeSnapshotContent

    同PV类似,VolumeSnapshotContent代表集群中被分配的一个快照资源。当一个VolumeSnapshot被创建时,CSI驱动会创建一个VolumeSnapshotContent对象。VolumeSnapshotContent同VolumeSnapshot互相引用,是1对1的关系。

    VolumeSnapshotContent建立了同底层存储的快照的映射关系,VolumeSnapshotContent中的SnapshotHandle是一个指向存储快照的UID。

  • VolumeSnapshotClass

    VolumeSnapshotClass同StorageClass类似,指定了卷快照的一些参数,如驱动的信息,DeletionPolicy,访问快照的Secret等。

值得注意的是,上面3个API对象并不是Kubernetes的核心API对象,而是CRD。一般来说,CSI驱动会自动安装所需的CRD。

CSI Snapshotter

CSI Snapshotter是Kubernetes CSI实现的一部分,主要包括以下几个部分:

  • Kubernetes卷快照的CRD

  • 卷快照控制器(Volume snapshot controller)

  • CSI Snapshotter sidecar

    一般来说,这个sidecar同CSI驱动是一起部署的。

  • 快照验证webhook

其中,卷快照控制器和CSI Snapshotter sidecar是核心部分。卷快照控制器监听VolumeSnapshot和VolumeSnapshotContent的创建、更新、删除事件。而CSI Snapshotter sidecar只监听VolumeSnapshotContent的创建、更新、删除。

例如,在动态预配(dynamic provision)情况下,当一个VolumeSnapshot被创建后,卷快照控制器监听到这个事件,在条件满足的情况下会创建一个VolumeSnapshotContent。之后,CSI Snapshotter sidecar监听到VolumeSnapshotContent创建的事件,在条件满足的情况下会调用CSI驱动的API去创建底层存储的快照。

Ceph RBD

RBD是Ceph的块设备接口实现,是Ceph最常用的存储类型。RBD块设备类似磁盘可以被挂载,一个实例化的RBD设备被称为RBD镜像(image)。 RBD块设备具有快照、多副本、克隆和一致性等特性,数据以条带化的方式存储在Ceph集群的多个OSD中。

  • RBD快照(snapshot)

    RBD快照是是某一RBD镜像在特定时间点的一个只读副本。RBD的快照实现机制是COW(copy on write)写时复制。

  • RBD克隆(clone)

    RBD克隆是对某个快照的状态进行复制变成另一个镜像,从而可以实现写操作。

在Ceph CSI驱动的设计文档中,可以看到对一个PVC做一个快照大概分为如下几步:

  1. 创建一个临时的快照

  2. 从上面的快照克隆一个镜像,参数为--rbd-default-clone-format 2 --image-feature layering,deep-flatten

  3. 删除临时创建的快照

由此可见,当一个CSI的卷快照被创建时,Ceph实际创建的是一个可读可写的克隆。

小结

上面简单介绍了一下卷快照的基本组成以及Ceph的RBD快照概要,下面这张图简单描述了这几个组件之间的关系:

图片1

​ 图1 卷快照概念

环境准备

  • K8s 1.19安装

    这里略过,请参考使用kubeadm安装kubernetes_v1.19.x

  • Rook-Ceph 1.4安装

    1. 获取rook的代码仓库,并切换到release-1.4:

        git clone https://github.com/rook/rook.git
        git checkout release-1.4
    2. 进入ceph的example目录,编辑cluster.yaml

        cd rook/cluster/examples/kubernetes/ceph
        vi cluster.yaml

      resources:部分,按照机器的配置来指定Ceph的CPU和内存资源,以下是一个参考:

          resources:
            mgr:
              limits:
                cpu: "500m"
                memory: "1024Mi"
              requests:
                cpu: "500m"
                memory: "1024Mi"
            mon:
              limits:
                cpu: "500m"
                memory: "1024Mi"
            mds:
              limits:
                cpu: "500m"
                memory: "1024Mi"
            crashcollector:
              limits:
                cpu: "500m"
                memory: "1024Mi"
            osd:
              limits:
                cpu: "1"
                memory: "2048Mi"
              requests:
                cpu: "1"
                memory: "2048Mi"

      storage:部分,根据集群节点以及挂载的块设备来填写这部分信息,以下是一个单节点集群,单个块设备的例子:

          storage: # cluster level storage configuration and selection
            useAllNodes: false
            useAllDevices: false
            nodes:
            - name: "dev-host1"
              devices: # specific devices to use for storage can be specified for each node
              - name: "sdb"
                config:
                  osdsPerDevice: "2"
                  storType: bluestore
    3. Ceph安装,依次执行下面的命令:

        kubectl create -f common.yaml
        kubectl create -f operator.yaml
        kubectl create -f cluster.yaml

      这里安装过程大概会有15分钟左右,取决于集群节点的数量以及提供给Ceph的块设备的数量。如果需要rook的toolbox,可以等ceph安装完成后执行:

        kubectl create -f toolbox.yaml

      如果出现问题,请参考文章尾部的Ceph teardown部分,尝试卸载,调整参数之后,重新安装。

    4. 创建ceph storageclass, snapshot class

        cd csi/rbd

      注意:如果是单节点的集群,要将storageclass里的failureDomain去掉,并且把replicated: size改成2

      执行:

         kubectl create -f storageclass.yaml

      注意:snapshotclass的deletionPolicy必须是Retain,并且加上label:

        labels:
          velero.io/csi-volumesnapshot-class: "true"

      执行:

        kubectl create -f snapshotclass.yaml
  • Volumesnapshot CRD

    1. 获取external-snapshotter的代码仓库:

        git clone https://github.com/kubernetes-csi/external-snapshotter.git
    2. 进入external-snapshotter目录,切换到release-2.0分支

        git checkout release-2.0
    3. 执行以下命令来创建CRD:

        kubectl create -f config/crd
    4. 执行以下命令来创建snapshot controller:

        kubectl create -f deploy/kubernetes/snapshot-controller/
  • minio安装

    minio是一个开源的对象存储,并且与AWS的S3兼容,它的特点是容易安装上手。minio有多种安装模式,具体可以参考minio quickstart guide。本文用一个最简便的安装方式,就在velero的下载包中,有一个example/minio/的目录,进去执行:

      kubectl create -f 00-minio-deployment.yaml

    这个yaml文件里,有几个地方需要注意:

          spec:
            volumes:
            - name: storage
              emptyDir: {}
      ...
          env:
          - name: MINIO_ACCESS_KEY
            value: "minio"
          - name: MINIO_SECRET_KEY
            value: "minio123"
      ---
      apiVersion: v1
      kind: Service
      spec:
        type: ClusterIP
        ports:
          - port: 9000
            targetPort: 9000
            protocol: TCP

    这里的env实际上指定了minio的key/secret,用这个可以访问minio的GUI。

    下面Service的port可以根据自己的实际需要改成其他的端口号,比如31605。service是ClusterIP,可以根据需要改成NodePort。

    另外,这里的volume是emptydir形式,作为学习问题不大,如果需要用其他形式的存储,可以参考例如minio NFS backend

    最后,登陆minio的GUI并创建一个bucket,比如test1,为velero的安装做准备。

  • Velero 1.4.3安装

    1. 下载velero v1.4.3的安装包,解压。

    2. 准备好credential file:

        [root~ ]# cat credentials-velero
        [default]
        aws_access_key_id=minio
        aws_secret_access_key=minio123
    3. 在velero的下载目录执行:

        velero install --provider aws --bucket test1 --secret-file credentials-velero --use-volume-snapshots=true --use-restic --backup-location-config region=minio,s3ForcePathStyle="true",s3Url=http://ip:port,publicUrl=http://ip:port --plugins=velero/velero-plugin-for-aws:v1.1.0,velero/velero-plugin-for-csi:v0.1.1 --namespace=velero --snapshot-location-config region=default --features=EnableCSI

      上面命令的ip就是minio安装所在的机器ip,port是minio开放的port。

      执行完后,可以用kubectl logs -n velero velero-xxxx-xxx命令来查看velero的log来观察velero是否安装成功:

        time="2021-04-22T07:10:21Z" level=info msg="setting log-level to INFO" logSource="pkg/cmd/server/server.go:177"
        time="2021-04-22T07:10:21Z" level=info msg="Starting Velero server v1.4.3 (4fc1c700282d6ece5b3ffc03cf296c435ccc903e)" logSource="pkg/cmd/server/server.go:179"
        time="2021-04-22T07:10:21Z" level=info msg="1 feature flags enabled [EnableCSI]" logSource="pkg/cmd/server/server.go:181"
  • Wordpress + MySQL

    最后用Ceph的storageclass来为MySQL和Wordpress创建PVC,具体可以参考rook wordpress examplerook mysql example。安装完Wordpress后,可以发布一篇文章,作为后面恢复的验证。

    <img src="https://gitee.com/jibutech/tech-docs/raw/master/images/wp文章1.png" style="zoom:50%;" />

    同时可以进入MySQL的Pod查看wordpress的database,表wp_posts

      |  4 |           1 | 2021-04-20 13:47:35 | 2021-04-20 05:47:35 |    | bakcup1    |     | publish     | open           | open        |       | bakcup1       |         |        | 2021-04-20 13:47:35 | 2021-04-20 05:47:35 |      |           0 | https://foo.bar.com:30165/?p=4       |          0 | post      |

备份

  1. 执行备份命令
[root~ ]# velero backup create wp-backup --include-namespaces wordpress
Backup request "wp-backup" submitted successfully.
Run `velero backup describe wp-backup` or `velero backup logs wp-backup` for more details.
  1. 查看备份状态
[root~ ]# kubectl get backups.velero.io -n v elero
NAME        AGE
wp-backup   32s
[root~ ]# kubectl get backups.velero.io -n velero wp-backup -o yaml
...
...
spec:
  hooks: {}
  includedNamespaces:

  - wordpress
    storageLocation: default
      ttl: 720h0m0s
      volumeSnapshotLocations:
  - default
    status:
      expiration: "2021-05-22T12:15:11Z"
      formatVersion: 1.1.0
      phase: InProgress
      progress:
    totalItems: 19
      startTimestamp: "2021-04-22T12:15:11Z"
      version: 1
  1. 查看volumesnapshot状态
[root~ ]# kubectl get volumesnapshot -n wordpress
NAME                          AGE
velero-mysql-pv-claim-kkxwx   3m2s
velero-wp-pv-claim-587dp      3m47s
[root~ ]# k get volumesnapshotcontent
NAME                                               AGE

snapcontent-549679d6-a5cf-41fb-aaa4-fc11fe09e9ab   2m52s
snapcontent-984839a0-55a8-41de-8bd6-5d0bfaba6e2c   3m37s

这里如果去查看snapcontent-549679d6-a5cf-41fb-aaa4-fc11fe09e9ab的信息,可以看到:

Spec:
  Deletion Policy:  Retain
  Driver:           rook-ceph.rbd.csi.ceph.com
  Source:
    Volume Handle:             0001-0009-rook-ceph-0000000000000002-05b2f2d6-a19b-11eb-baf1-7a576c7e3674
  Volume Snapshot Class Name:  csi-rbdplugin-snapclass
  Volume Snapshot Ref:
    API Version:       snapshot.storage.k8s.io/v1beta1
    Kind:              VolumeSnapshot
    Name:              velero-mysql-pv-claim-kkxwx
    Namespace:         wordpress
    Resource Version:  43496419
    UID:               549679d6-a5cf-41fb-aaa4-fc11fe09e9ab
Status:
  Creation Time:    1619093815974957075
  Ready To Use:     true
  Restore Size:     10737418240
  Snapshot Handle:  0001-0009-rook-ceph-0000000000000002-a0a1af05-a364-11eb-baf1-7a576c7e3674
Events:             <none>

其中,Deletion Policy被我们之前设置成了Retain,这样的话,即使刚创建的Volumesnapshot的CR被删掉,VolumesnapShotContent也不会被删掉,VolumeSnapshotContent指向的Ceph的快照也不会被删掉。

  1. 查看RBD快照

查看并进入Ceph的tools Pod:

rook-ceph-tools-5949d6759-2kwzm                    1/1     Running     2          33d

[root]# kubectl exec -it -n rook-ceph rook-ceph-tools-5949d6759-2kwzm -- bash
[root@rook-ceph-tools-5949d6759-2kwzm /]# 

用上面VolumesnapShotContentSnapshot Handle的后半部分a0a1af05-a364-11eb-baf1-7a576c7e3674来找到rbd的快照:

[root@rook-ceph-tools-5949d6759-2kwzm /]# rbd ls replicapool | grep a0a1af05-a364-11eb-baf1-7a576c7e3674
csi-snap-a0a1af05-a364-11eb-baf1-7a576c7e3674

上面replicapool其实是安装Ceph时的默认存储池的名字,然后可以用rbd info命令去看这个快照的属性:

[root@rook-ceph-tools-5949d6759-2kwzm /]# rbd info replicapool/csi-snap-a0a1af05-a364-11eb-baf1-7a576c7e3674
rbd image 'csi-snap-a0a1af05-a364-11eb-baf1-7a576c7e3674':
        size 10 GiB in 2560 objects
        order 22 (4 MiB objects)
        snapshot_count: 1
        id: 455cd100fcb2d
        block_name_prefix: rbd_data.455cd100fcb2d
        format: 2
        features: layering, deep-flatten, operations
        op_features: clone-child
        flags:
        create_timestamp: Thu Apr 22 12:16:55 2021
        access_timestamp: Fri Apr 23 06:36:18 2021
        modify_timestamp: Thu Apr 22 12:16:55 2021
        parent: replicapool/csi-vol-05b2f2d6-a19b-11eb-baf1-7a576c7e3674@7ad163d8-0c3c-49ef-a58e-43b95423beee
        overlap: 10 GiB

可以看到这个rbd image的属性其实跟前面讲Ceph CSI驱动创建快照时候的参数是一致的,format: 2layering, deep-flatten应该表示了这个image是一个rbd克隆。

  1. 备份完成
status:
  completionTimestamp: "2021-04-22T12:16:58Z"
  expiration: "2021-05-22T12:15:11Z"
  formatVersion: 1.1.0
  phase: Completed
  progress:
    itemsBackedUp: 26
    totalItems: 26
  startTimestamp: "2021-04-22T12:15:11Z"
  version: 1

这里还是对前面backups.velero.io的查看,这里状态已经变成了Completed,表示成功完成。

不同场景的恢复

  • 删除PVC

    删除PVC之前,可以新写一篇文章,用来表示当前应用的最新版本的数据,然后再执行PVC的删除:

      [root~ ]# kubectl delete pvc -n wordpress mysql-pv-claim
      persistentvolumeclaim "mysql-pv-claim" deleted
      [root~ ]# kubectl delete pvc -n wordpress wp-pv-claim
      persistentvolumeclaim "wp-pv-claim" deleted
      [root~ ]#

    恢复:

      [root~ ]# velero create restore --from-backup wp-backup
      Restore request "wp-backup-20210422204956" submitted successfully.
      Run `velero restore describe wp-backup-20210422204956` or `velero restore logs wp-backup-20210422204956` for more details.

    这时候如果去看restores.velero.io的状态,会发现很快就恢复完成了:

      [root~ ]# kubectl describe restores.velero.io -n velero wp-backup-20210422204956
      
      ...
      
      Spec:
        Backup Name:  wp-backup
        Excluded Resources:
          nodes
          events
          events.events.k8s.io
          backups.velero.io
          restores.velero.io
          resticrepositories.velero.io
        Included Namespaces:
          *
      Status:
        Phase:     Completed
        Warnings:  6
      Events:      <none>

    查看PV,PVC:

      [root~ ]# kubectl get pv
      NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS     CLAIM                      STORAGECLASS      REASON   AGE
      pvc-179efdd1-4d91-4b4f-a2dd-2ecfccedfb28   10Gi       RWO            Delete           Bound      wordpress/wp-pv-claim      rook-ceph-block            76s
      pvc-a444c477-d2f9-481e-8db6-85b9d893ecad   10Gi       RWO            Delete           Released   wordpress/mysql-pv-claim   rook-ceph-block            2d7h
      pvc-ad1c99d3-1f7f-486f-9924-0c997ad503cb   10Gi       RWO            Delete           Bound      wordpress/mysql-pv-claim   rook-ceph-block            76s
      pvc-bc45621b-50a3-4056-8f1b-43038b6e0174   10Gi       RWO            Delete           Released   wordpress/wp-pv-claim      rook-ceph-block            2d7h
      [root~ ]# kubectl get pvc -n wordpress
      NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
      mysql-pv-claim   Bound    pvc-ad1c99d3-1f7f-486f-9924-0c997ad503cb   10Gi       RWO            rook-ceph-block   79s
      wp-pv-claim      Bound    pvc-179efdd1-4d91-4b4f-a2dd-2ecfccedfb28   10Gi       RWO            rook-ceph-block   79s

    这里可以看到前面删除的两个PV处于Released的状态,恢复回来的PV是另外的名字。

    恢复完成之后,需要注意一点,要把MySQL的Pod删掉来造成Pod重启一下(deployment会自动创建新的Pod)。这个重启十分重要,会让Pod把PV重新挂载一遍,否则的话,Pod里面的数据还是最新的,并不是想要恢复的那个版本。

    再去看Wordpress的文章,没有问题,这里读者可以自行验证,这里就不展示了。

  • 损坏PV的数据

    这里可以直接把Wordpress里面的文章删掉,或者修改当前文章,然后再尝试恢复。这里跟上面场景主要的区别在于数据是应用随着时间正常更新了(比如修改或新增文章),还是恶意被破坏了(比如删掉文章)。但是从步骤来说,是一样的,记得恢复完要把MySQL的Pod重启一下。

  • 删除namespace

      [root~ ]# kubectl delete ns wordpress
      namespace "wordpress" deleted
      [root~ ]#

    再执行恢复,也很快成功了,再去看MySQL的数据:

      |  4 |           1 | 2021-04-20 13:47:35 | 2021-04-20 05:47:35 |    | bakcup1    |     | publish     | open           | open        |       | bakcup1       |         |        | 2021-04-20 13:47:35 | 2021-04-20 05:47:35 |      |           0 | https://foo.bar.com:30165/?p=4       |          0 | post      |

    没有问题,数据恢复成功。

总结

以上介绍了怎么用velero通过Ceph的快照方式来备份恢复有状态的应用,可以看到,不论是删掉PV,删掉命名空间,还是损坏原始PV的数据,都可以通过velero把原始数据恢复回来。但是,这里需要注意的是,velero备份的只是VolumeSnapshot和VolumeSnapshotContent的CR,并没有把PV的数据进行导出,一旦Ceph集群出现问题,比如集群遇到错误起不来,那备份到对象存储的资源是没法恢复丢失的数据的。

参考

rook ceph quick start

ceph teardown

volume snapshots

external-snapshotter

rbd snapshot

Venky Shankar - Red Hat - Ceph RBD Deep Dive

design-rbd-snap-clone

    说点什么吧...