Veleroでのバックアップをチューニングで爆速にしたい

この記事は[NTTコムウェア Advent Calendar 2024](https://qiita.com/advent-calendar/2024/nttcomware) 6日目の記事です。

NTTコムウェアの東です。
社内では技術支援・ナレッジ蓄積を行う後方部隊的な部署で特にKubernetesやコンテナ界隈を担当し、オープンソースを活用したクラウドネイティブ系技術の普及展開を目論んでいます。


皆さん、Kubernetes(k8s)でのバックアップについてどのようにお考えでしょうか？

クラウドネイティブ/k8s界隈では、なるべくSPOFを排除したいという理念のもと、結果整合性を受け入れることで可用性とスケーラビリティを確保したり、ノード/コンテナを使い捨て(家畜)として扱うことなど、可用性を高くする工夫が取り入れやすくなっています。
またほとんどのクラウドサービスで、データ領域(オブジェクトストレージ、ブロックストレージ、ファイルシステム)について、基盤側で冗長化が施されており、単なるHDDを単体で使用する際に比べ、耐久性が高くなっています。

とはいえ、故障が発生する確率を0にすることはできません。諸行無常。形あるものはいつか壊れるのです。故障はクリスマスや年末年始も関係なく襲ってきます。
そこで、k8sにおいてもバックアップが大切になってきます。

[Velero](https://velero.io/)はk8s上に存在するオブジェクトやPersistentVolumeClaim(PVC)/PersistentVolume(PV)に関してバックアップおよびリストアするためにオープンソースで開発されているツールです。

k8s上でVeleroを稼働させると、k8s上に存在するリソースやPVC/PVを自動で検出し、それらを外部のオブジェクトストレージにコピーまたはスナップショットを取得します。

リストアを別のk8sクラスタ[^1]に行うことができるので、バックアップのみならずディザスタリカバリやk8sクラスタ移行などに応用することもできます。

[^1]:別のリージョン、別のアカウント等、バックアップデータ(≒オブジェクトストレージの中身)にアクセスできる範囲であればリストア可能です。クラウドサービスをまたがってリストアしたケースもあるようです。


Veleroは比較的[ドキュメント](https://velero.io/docs/main/)が豊富で基本的な使用方法はすぐに理解できると思います。
本稿はタイトルでも示した通り、Veleroの応用的なナレッジを目指し、バックアップにかかる時間をなるべく短縮する方法について紹介したいと思います。そのため、基本的な使用方法やインストール手順などの紹介は(紙面の都合もあり)必要最小限に留めます。

Veleroによるバックアップ/リストアの時間について、支配的なのはなんといってもPVのデータの扱いです[^2]。

[^2]:Pod等を定義するマニフェストは単なるテキストでサイズがそれほど大きくないためPVに比べバックアップ/リストア時間にはほとんど影響しません。

というわけで本稿では、PVのデータにフォーカスし、これをいかに早くバックアップ/リストアするかのチューニングポイントを紹介していきます。
またチューニングを応用し、**実際に8倍ほど高速化** :rocket: する例をハンズオン形式でも紹介します。


以降、バックアップ/リストアの詳細な動作を理論面で紹介したのち、具体的なチューニングポイントおよび実例を挙げていきます。
このうち理論面については特にk8s/Veleroが稼働する基盤環境には依らないかたちで紹介しますが、具体的なチューニングポイントおよび実例についてはAWS上のEKSを題材として紹介します。
AWS(EKS)以外の環境においては若干あてはまらない場合もあるかと思いますが、理論面をご理解のうえ適宜読み替えていただくことで参考にはなるかと思います

## Veleroの機能と本稿の主眼

PVのバックアップ/リストアのチューニングは一言でいえば、「データコピーをいかに早く実行するか」に尽きます。

一方で、Veleroはバックアップ/リストア対象のPVの種類に応じ、以下4つの機能を有します。それぞれで、データのコピー先や経路が異なるため、チューニングポイントも様々です。

* [Velero Native スナップショット](https://velero.io/docs/main/how-velero-works/#backup-workflow)
   * 動作：ストレージのスナップショットをVeleroが内包するドライバで取得する
   * 対象：Amazon EBS、Azure Manages Disks、Google Persistent Disks 等のストレージを k8s in-tree ドライバで取り扱う場合が該当
* [CSIスナップショット](https://velero.io/docs/main/csi/)
   * 動作：ストレージのスナップショットをCSIドライバで取得する
   * 対象：Amazon EBS、Azure Manages Disks、Google Persistent Disks 等のストレージを CSI ドライバで取り扱う場合が該当
* [CSIスナップショットデータムーブ](https://velero.io/docs/main/csi-snapshot-data-movement/)
   * 動作：ストレージのスナップショットをCSIドライバで取得し、その中身をオブジェクトストレージへコピーする
   * 対象：Amazon EBS、Azure Manages Disks、Google Persistent Disks 等のストレージを CSI ドライバで取り扱う場合が該当
      * "対象"が上記"CSIスナップショット"と同じですが、こちらはデータをオブジェクトストレージへコピーするため、スナップショットが直接参照できない場所（地理的に離れている等。例えば他のクラウド事業者やオンプレなど。AWSの場合、他リージョンや他アカウントが該当。）でも、オブジェクトストレージが参照できればリストアができる、という違いがあります。（一般にオブジェクトストレージの方が参照できる範囲は広く、また、コピーも容易です。）
* [File System Backup(FSB)](https://velero.io/docs/main/file-system-backup/)
   * 動作：ストレージの中身を(直接)オブジェクトストレージへコピーする
   * 対象：Amazon EFS、AzureFile、NFS、k8s の emptyDir、local などスナップショットに対応しないほぼすべてのストレージが該当


本稿では上記のうち、最も仕組み・経路が複雑な「**CSIスナップショットデータムーブ**」に絞ってチューニングポイントを紹介します。
CSIスナップショットデータムーブでのチューニングポイントは、他の機能を用いる際にも応用できるものが多々あるので、各チューニングポイント紹介の際に適宜補足していきます。

CSIスナップショットに対応したボリュームの実例として、以降、AWSの[Amazon EBSをPVとして用い](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/ebs-csi.html)て説明します。


## CSIスナップショットデータムーブの仕組み

CSIスナップショットデータムーブを用いた際のバックアップ/リストアの流れを大まかに説明し、それを踏まえたチューニングポイントを列挙します。

### バックアップ

まず対象のPVに紐づくボリュームのスナップショットをCSIドライバを介して取得します。
次に取得したスナップショットから、新しいボリュームを作成し、それをVeleroが管理するアップロード作業用PodにPVとしてアタッチします。
アップロード作業用Podは、アタッチされたボリュームの中身を[kopia](https://kopia.io/)と呼ばれるファイルレベルのバックアップツール[^3]を用い、オブジェクトストレージにアップロードします。このアップロード処理はdatauploadリソースというカスタムリソースで管理されます。

[^3]:[Veleroはkopiaを内包](https://velero.io/docs/main/file-system-backup/#how-velero-integrates-with-kopia)しており、意識せずともVeleroが内部的にkopiaを利用しています。

### リストア

Veleroにより管理されるダウンロード作業用Podが作成され、それに新規PV(空)をアタッチされます。
その中でダウンロード作業用Podがオブジェクトストレージからデータをダウンロードし、新規PVにデータを書き戻します。このダウンロード処理はdatadownloadリソースというカスタムリソースで管理されます。

書き戻しが終わると、その新規PVを(別途Veleroによりバックアップされたマニフェストからリストアされた)アプリケーションPodにアタッチし直します。


バックアップ/リストアの動作の概要図を以下に示します。
なお、図ではS3バケットのデータを別リージョンにコピーしています（グレー矢印）が、これについてはVeleroは機能を有さないため、必要な場合は別途[クラウド側の機能](https://repost.aws/ja/knowledge-center/move-objects-s3-bucket)で実施してください。


![velero-all-overview.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/0b2d9dfa-fd1e-ecc2-e29c-ee6e9e822f46.png)


図中にバックアップ/リストアの動作（データの流れ）を踏まえた、チューニングポイントを記載しました。

* No.1 事前のスナップショット取得
* No.2 バックアップ/リストア対象のPVCのストレージタイプ高速化
* No.3 Workerノードのタイプ高速化
* No.4 S3へのVPC Endpoint
* No.5 kopiaキャッシュ領域高速化


以下、それぞれのチューニングポイントについて詳細を示します。

## チューニングポイント

### No.1 事前のスナップショット取得

これは、Veleroによるバックアップに伴い取得されるスナップショットを、先に手動で取得しておく、というテクニックです。
とはいえ、ここで手動で取得するスナップショットをこの後Veleroが直接使用するわけではありません。

しかしながら、Amazon EBSでは同じボリュームに対して取得した複数のスナップショットは、ユーザが意識せずとも[自動的に増分バックアップになる](https://docs.aws.amazon.com/ja_jp/ebs/latest/userguide/how_snapshots_work.html)ため、あらかじめ手動で取得しておくことで、その後Veleroでの取得時にはデータ量を増加分のみに抑えられるのです。

このチューニングはバックアップ時にのみ有効です。リストア時には実施する必要はありません（実施できません）。

:::note info
Azureの場合でも、[Velero導入時のオプションに「incremental: "true"」を付与する](https://medium.com/@danieljimgarcia/we-save-thousands-every-month-by-using-incremental-snapshots-with-our-velero-backups-c1a39dd64382)ことで、[増分スナップショット](https://learn.microsoft.com/ja-jp/azure/virtual-machines/disks-incremental-snapshots?tabs=azure-cli)を有効化できるようなので、本テクニックが効く可能性があると思います。（すみません。実機未確認です。）
:::

### No.2 バックアップ/リストア対象のPVCのストレージタイプ高速化

Veleroによりデータがオブジェクトストレージへアップロードされる際のスループットをなるべく向上させるため、バックアップ/リストア対象のPVCのストレージタイプをなるべく高速なものにしておきます。
Veleroはアップロードに際し(当然ですが)ストレージの中身を全て読み込むため、読み込み速度を上げておくことでアップロード作業を高速にできます。

[Amazon EBS](https://docs.aws.amazon.com/ja_jp/ebs/latest/userguide/requesting-ebs-volume-modifications.html)および[aws-ebs-csi-driver](https://github.com/kubernetes-sigs/aws-ebs-csi-driver/blob/master/docs/modify-volume.md)は、ボリュームタイプのオンラインでの変更に対応しているため、バックアップ取得時にのみ一時的にタイプを(高速なものに)変更することもできます。ただし、ボリュームタイプのオンライン変更は6時間以上の間隔を空ける必要がある点にご注意ください。

このチューニングはバックアップ/リストアの両者で有効です。また、FSB機能利用時も有効です。

### No.3 Workerノードのタイプ高速化

Workerノードのタイプを、EBS帯域およびNW帯域がなるべく大きいものにしておきます。
これは、Veleroによりデータがオブジェクトストレージへアップロードされる際のスループットをなるべく向上させるためです。
バックアップ対象のPVからのデータ読み取り速度がEBS帯域により制限される点と、(ネットワークを介した)S3へのアップロードがNW帯域に制限されるため、これらをなるべく大きくしておくのです。

帯域の大きいものを選ぶと、自然にCPU/メモリに関しても増強されるため、これも性能向上に寄与します。

このチューニングはバックアップ/リストアの両者で有効です。また、FSB機能利用時も有効です。

### No.4 S3へのVPC Endpoint

S3へのアクセスを高速化するため、[VPC Endpoint](https://docs.aws.amazon.com/ja_jp/vpc/latest/privatelink/vpc-endpoints-s3.html)を作成します。
これは、Veleroに限らず、AWSでS3を用いる際の[一般的な性能向上策の1つ](https://repost.aws/ja/knowledge-center/s3-transfer-data-bucket-instance)です。

特にS3の場合は無償のゲートウェイ型が選択できるので料金面を心配する必要もなく、使わない手はありません。

このチューニングはバックアップ/リストアの両者で有効です。また、FSB機能利用時も有効です。

### No.5 kopiaキャッシュ領域高速化

Workerノードの/(root)領域となっているEBSのストレージタイプをなるべく高速なものにしておきます。
これは、Veleroが管理するダウンロード作業用PodがS3からデータを書き戻す際に、ダウンロードしたデータを一時的にコンテナ内の領域に[キャッシュする](https://kopia.io/docs/advanced/caching/)ため、キャッシュの書き込みがボトルネックになるのを防ぐためです。

キャッシュを配置するコンテナ内の領域の実体はWorkerノードの/(root)領域である[^4]ため、ここのI/O性能を上げるのです。

[^4]:Velero v1.14まではダウンロード作業用Pod＝DeamonSetで稼働のnode-agentであったため、これにエフェメラルボリュームをアタッチすることでキャッシュ領域にPVを使用することができました。が、Velero v1.15でアーキテクチャが変更され、ダウンロード作業用Podが都度直接作成されるものになり、このPodにspec.nodeNameが付与されているがため[この制限](https://kubernetes.io/docs/concepts/storage/storage-classes/)に引っかかり、volumeBindingModeがWaitForFirstConsumerであるストレージクラスのPVをアタッチできなくなりました。

(No.2と同様に)Amazon EBSは、ボリュームタイプのオンラインでの変更に対応しているため、バックアップ取得時にのみ一時的にタイプを(高速なものに)変更することもできます。ただし、ボリュームタイプのオンライン変更は6時間以上の間隔を空ける必要がある点にご注意ください。

このチューニングはリストア時にのみ有効です。FSB機能利用時も有効です。

## 実践例

ここからは、実際にデモアプリを構築し、バックアップ/リストアを実行し、かかった時間を測定してみます。

具体的には以下図のようなデモアプリをデプロイし、これまでに示したチューニング前後でどのくらい違ってくるかを確認します。

なお、チューニング前後で選択しているボリュームやインスタンスのタイプ等はあくまで例ですので、深い意味はないです。

![velero-demo-app.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/b5b1b649-eff1-4613-250f-1d6fa37abbe4.png)

動作させる上で前提となる環境を紹介します。
バージョン等の詳細は筆者が使用したものですが、必ずこうでなければならないわけではありません。

* EKSクラスタが構築済みであること。
   * EKS v1.30
      * リージョン：ap-northeast-1

* EKSに以下のアドオンが導入済みであること。
   * [aws-ebs-csi-driver](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/workloads-add-ons-available-eks.html#add-ons-aws-ebs-csi-driver)
   * [snapshot-controller](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/workloads-add-ons-available-eks.html#addons-csi-snapshot-controller)

* EKSおよびAWSを操作するためのインスタンス（踏み台）を構築済みであること。
   * 以下ツールを導入・設定済み。
      * [aws cli](https://docs.aws.amazon.com/ja_jp/cli/latest/userguide/cli-chap-welcome.html) : 2.15.31
      * [eksctl](https://eksctl.io/) : 0.194.0
      * [kubectl](https://kubernetes.io/ja/docs/reference/kubectl/) : v1.30.1
      * [helm](https://helm.sh/ja/) : v3.16.3

* EKSにVeleroが構築済みであること。
   * Velero v1.15.0
      * [Helmチャート](https://github.com/vmware-tanzu/helm-charts/tree/main/charts/velero) v8.0.0 で構築


### チューニング無し

まず比較対象となるチューニング無しでの一連の流れを説明します。

なお、本稿では紙面の都合上、EKSやVeleroの基本的な構築・操作手順は割愛し、構築・操作に必要な設定ファイルのうちチューニングに関わる部分の記載のみに留めます。

#### Workerノード準備

筆者はEKS構築にeksctlを用いたため、EKS構成を定義するClusterConfig.yamlに以下2つのノードグループ定義を追記します。

ここでは"チューニング無し"ということで、あえてスペックの低いタイプ(m4.xlarge)でEKSにノードグループを作成(追加)します。
/(root)領域のボリュームタイプもgp3とはしていますが、IOPSやスループットはデフォルトのままです。

なお、チューニングには直接関連しませんが筆者は、複数のWorkerノードが確実に各AZに分散配置されるよう、タイプは同じですがあえてノードグループを分けAZを明示しています。

```yaml
・・・略・・・

managedNodeGroups:
・・・略・・・

- name: m4xlarge-1a
  instanceType: m4.xlarge
  desiredCapacity: 1
  minSize: 0
  maxSize: 10
  privateNetworking: true
  volumeEncrypted: true
  volumeType: gp3
  availabilityZones: [ "ap-northeast-1a" ]

- name: m4xlarge-1c
  instanceType: m4.xlarge
  desiredCapacity: 1
  minSize: 0
  maxSize: 10
  privateNetworking: true
  volumeEncrypted: true
  volumeType: gp3
  availabilityZones: [ "ap-northeast-1c" ]
```

上記を追記したClusterConfig.yamlでノードグループを追加します。

```bash
$ eksctl create nodegroup --config-file=ClusterConfig.yaml
```


#### ストレージクラス/ボリュームスナップショットクラス準備

ストレージクラスおよびボリュームスナップショットクラスを以下の通り定義します。
ストレージクラスはボリュームタイプをgp3としていますが、IOPSやスループットはデフォルトのままです。

```yaml
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  annotations:
    storageclass.kubernetes.io/is-default-class: "true"
  name: ebs-sc
provisioner: ebs.csi.aws.com
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
  encrypted: "true"
  type: gp3
```

```yaml
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshotClass
metadata:
  labels:
    velero.io/csi-volumesnapshot-class: "true"
  name: csi-aws-vsc
deletionPolicy: Delete
driver: ebs.csi.aws.com
```

#### Velero準備

筆者はVeleroを以下のvalues.yamlを用い[Helmチャート](https://github.com/vmware-tanzu/helm-charts/tree/main/charts/velero)で構築しました。

```yaml
configuration:
  uploaderType: kopia
  backupStorageLocation:
  - name: default
    bucket: ＜S3バケット名＞
    config:
      region: ＜リージョン名＞
    provider: aws
    accessMode: ReadWrite
  volumeSnapshotLocation:
  - name: default
    provider: aws
    config:
      region: ＜リージョン名＞
  features: EnableCSI
  extraEnvVars:
    TZ: "Asia/Tokyo"
  extraArgs:
  - --backup-repository-configmap=velero-backup-repository-configmap #★1
credentials:
  useSecret: false
deployNodeAgent: true
initContainers:
- name: velero-plugin-for-aws
  image: velero/velero-plugin-for-aws:v1.11.0
  volumeMounts:
  - mountPath: /target
    name: plugins
serviceAccount:
  server:
    create: false
    name: velero-server
nodeAgent:
  extraEnvVars:
    TZ: "Asia/Tokyo"
  extraArgs:
  - --node-agent-configmap=velero-node-agent-configmap #★2
  - --data-mover-prepare-timeout=120m
  - --resource-timeout=120m
configMaps:
  backup-repository-configmap: #★1'
    labels: {}
    data:
      kopia: |
        {
          "cacheLimitMB": 71680
        }
  node-agent-configmap: #★2'
    labels: {}
    data:
      node-agent-configmap.json: |+
        {
            "loadAffinity": [
                {
                    "nodeSelector": {
                        "matchLabels": {
                            "node.kubernetes.io/instance-type": "m4.xlarge"
                        }
                    }
                }
            ]
        }
```

AWSサービスへのアクセス権限付与には[こちら](https://aws.amazon.com/jp/blogs/news/backup-and-restore-your-amazon-eks-cluster-resources-using-velero/)を参考に[IAM Roles for Service Accounts（IRSA）](https://docs.aws.amazon.com/ja_jp/eks/latest/userguide/iam-roles-for-service-accounts.html)を使用するよう設定しています。(別途IAMポリシーおよびiamserviceaccount作成済み)

:::note warn
VeleroでのIRSA使用に関し、v1.13以上でバックアップ/リストア時間が1時間を超えるとトークンのタイムアウトが発生し失敗する、という[issue](https://github.com/vmware-tanzu/velero/issues/8173)が報告されています。2024/11/29時点でopenのため、根本的な解決には至っていないようです。
このissueは、IRSAを用いず[アクセスキーを用いる構成](https://github.com/vmware-tanzu/velero-plugin-for-aws?tab=readme-ov-file#option-1-set-permissions-with-an-iam-user)にすることで回避できると思われます。
:::


本稿主眼であるチューニングに関連するのは、以下★1,1',2,2'の部分です。

* ★1, 1' backup-repository-configmap関連
ダウンロード作業用Podがダウンロードしたデータを[キャッシュする領域の使用量の上限](https://velero.io/docs/v1.15/backup-repository-configuration/)を70GBに設定するConfigMapの作成およびConfigMap名の設定をしています。
"チューニング"とはちょっとズレるのでが、これを設定しないとVelero(kopia)はキャッシュ領域を際限無く使用するため、リストアするデータ量によってはWorkerノードの/(root)領域が枯渇してしまうため設定しています。
なお、70GBという値は、Workerノード作成時にボリュームサイズを指定しなかった場合のデフォルトである80GBから、OSやコンテナイメージによる使用分を引いた残りをすべて使用させる、という考え方で算出しました。
70GBでなければならないわけではないため、適宜Workerノードの残ディスク容量から逆算してください。

* ★2, 2' node-agent-configmap関連
バックアップ時にVeleroが管理する[アップロード作業用Podを作成するWorkerノードを指定する](https://velero.io/docs/v1.15/data-movement-backup-node-selection/)ための設定をConfigMapで施しています。
ここでは"チューニング無し"ということで、あえてスペックの低いタイプ(m4.xlarge)を使用するようにしています。

:::note info
上記★1,1',2,2'で生成されるConfigMap名には"velero-"という接頭語が付与されます。
:::

:::note info
上記★1,1',2,2'の設定はいずれも[Velero v1.15](https://github.com/vmware-tanzu/velero/releases/tag/v1.15.0)からの新機能です。
:::

以下コマンドでVeleroをデプロイします。

```bash
$ helm install velero vmware-tanzu/velero \
  --namespace velero \
  --version 8.0.0 \
  -f values.yaml
```

:::note warn
本稿では、Velero(が生成するPod)に対し、[CPU/メモリの上限(RequestsとLimits)](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits)を設定しませんでしたが、必要に応じ他のPod等への影響を加味した上限を設定することをお勧めします。

Velero構築にHelmチャートを使用する場合、コンポーネントごとにresources:設定が分かれている点に留意してください。（本稿でダウンロード/アップロード作業Podと呼んでいるものに関係するのは"node-agent"の方です。）
* velero server: https://github.com/vmware-tanzu/helm-charts/blob/velero-8.0.0/charts/velero/values.yaml#L76
* node-agent: https://github.com/vmware-tanzu/helm-charts/blob/velero-8.0.0/charts/velero/values.yaml#L543
:::

#### デモアプリ準備

以下StatefulSetをデモアプリとします。

```yaml
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: my-app
  namespace: test
spec:
  replicas: 2
  selector:
    matchLabels:
      app: my-app
  serviceName: my-app
  template:
    metadata:
      labels:
        app: my-app
    spec:
      affinity:
        nodeAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 1
            preference:
              matchExpressions:
              - key: "node.kubernetes.io/instance-type"
                operator: In
                values:
                - "m4.xlarge" #★1
      containers:
      - name: my-app
        image: busybox:latest
        command:
          - "/bin/sh"
        args:
          - "-c"
          - "sleep inf"
        volumeMounts:
          - name: pv1
            mountPath: "/mnt/pv1"
      topologySpreadConstraints:
      - maxSkew: 1
        topologyKey: topology.kubernetes.io/zone
        whenUnsatisfiable: ScheduleAnyway
        labelSelector:
          matchLabels:
            app: my-app
  volumeClaimTemplates:
  - apiVersion: v1
    kind: PersistentVolumeClaim
    metadata:
      name: pv1
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 25Gi
      storageClassName: ebs-sc #★2
```

Podが2つ起動し、永遠にsleepし続け、特に何もしないものです。
それぞれのPodに25GiBのPVをアタッチしています。

チューニングに関連するのは、★1～3の部分です。

* ★1
デモアプリが稼働するWorkerノードのタイプを指定しています。
ここでは"チューニング無し"ということで、あえてスペックの低いタイプ(m4.xlarge)を使用するようにしています。

* ★2, 3
PVが使用するストレージクラスを指定しています。
さきほど作成したebs-sc(特にチューニング無し)を使用します。


```bash
$ kubectl apply -f my-app.yaml

$ kubectl get -n test pod,pvc
NAME           READY   STATUS    RESTARTS   AGE
pod/my-app-0   1/1     Running   0          45s
pod/my-app-1   1/1     Running   0          29s

NAME                                 STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE    
persistentvolumeclaim/pv1-my-app-0   Bound    pvc-f4fca5d4-9de3-4037-8857-8f5905414502   100Gi      RWO            ebs-sc         <unset>                 45s    
persistentvolumeclaim/pv1-my-app-1   Bound    pvc-6605fd2d-df94-45ef-ae70-0150714ae57f   100Gi      RWO            ebs-sc         <unset>                 29s    
```

Pod起動後、アタッチしたPV内に、以下スクリプトで1GB×25個のダミーデータを生成しておきます。

```bash
#!/bin/bash

NS="test"
POD="my-app"
DISK=pv1
SEQ=25
COUNT=1000

for p in 0 1
do
        for i in $( seq 1 $SEQ )
        do
                echo "pod: ${POD}-${p} , gen data /mnt/${DISK}/file${i} ..."
                kubectl --context=${CONT} -n ${NS} exec -it ${POD}-${p} -- dd if=/dev/urandom of=/mnt/${DISK}/file${i} bs=1M count=${COUNT} oflag=direct
        done
done
```

```bash
$ kubectl -n test exec -it my-app-0 -- du -sh /mnt/pv1/
24.4G   /mnt/pv1/
$ kubectl -n test exec -it my-app-1 -- du -sh /mnt/pv1/
24.4G   /mnt/pv1/
```


#### バックアップ

以下コマンドでVeleroによるバックアップを開始します。
各引数の意味は以下の通りです。

* --snapshot-move-data
この引数により「[CSIスナップショットデータムーブ](https://velero.io/docs/v1.15/csi-snapshot-data-movement/)」を有効化しています。

* --include-namespaces test
バックアップ対象のネームスペースを(デモアプリをデプロイした)testと指定しています。

* --csi-snapshot-timeout 6000m --item-operation-timeout 6000m
バックアップ処理のタイムアウト時間をちょっと長め(100時間)に設定しています。
前者がCSIスナップショット取得のタイムアウトでデフォルト10分、後者がその他操作(データアップロード)のタイムアウトでデフォルト4時間です。

```bash
$ velero create backup myapp-tune-off \
  --snapshot-move-data \
  --include-namespaces test \
  --csi-snapshot-timeout 6000m \
  --item-operation-timeout 6000m
```

バックアップを開始すると、まずVeleroによりVolumeSnapshotリソースが作成され、スナップショットの作成が開始されます。ただし、VolumeSnapshotリソースはVelero動作が次のフェーズに移ると、veleroネームスペースに移動し、バックアップが完了すると削除されます。

```bash
$ kubectl -n test get volumesnapshot
NAME                        READYTOUSE   SOURCEPVC      SOURCESNAPSHOTCONTENT   RESTORESIZE   SNAPSHOTCLASS   SNAPSHOTCONTENT                                    CREATIONTIME   AGE
velero-pv1-my-app-0-qrs9l   false        pv1-my-app-0                           25Gi          csi-aws-vsc     snapcontent-bb410d8d-c7e8-4388-a68a-48e1607b0f80   85s            86s
velero-pv1-my-app-1-vgc4j   false        pv1-my-app-1                           25Gi          csi-aws-vsc     snapcontent-33edd93a-a7d6-4383-89c4-d820843b50bf   80s            81s
```

スナップショット完了までにはデータ量に応じた時間がかかります。
スナップショット取得はsnapshot-controllerの以下ログで確認できます。ここでは、34分ほどかかりました。

```bash
$ kubectl -n kube-system logs snapshot-controller-xxxxxxxxx-xxxxx
・・・
I1127 04:48:10.339757       1 snapshot_controller.go:645] createSnapshotContent: Creating content for snapshot test/velero-pv1-my-app-0-qrs9l through the plugin ...     
I1127 04:48:15.493025       1 snapshot_controller.go:645] createSnapshotContent: Creating content for snapshot test/velero-pv1-my-app-1-vgc4j through the plugin ...
・・・
I1127 05:22:25.728909       1 event.go:377] Event(v1.ObjectReference{Kind:"VolumeSnapshot", Namespace:"test", Name:"velero-pv1-my-app-0-qrs9l", UID:"bb410d8d-c7e8-4388-a68a-48e1607b0f80", APIVersion:"snapshot.storage.k8s.io/v1", ResourceVersion:"69729114", FieldPath:""}): type: 'Normal' reason: 'SnapshotReady' Snapshot test/velero-pv1-my-app-0-qrs9l is ready to use.
I1127 05:22:30.027185       1 event.go:377] Event(v1.ObjectReference{Kind:"VolumeSnapshot", Namespace:"test", Name:"velero-pv1-my-app-1-vgc4j", UID:"33edd93a-a7d6-4383-89c4-d820843b50bf", APIVersion:"snapshot.storage.k8s.io/v1", ResourceVersion:"69729164", FieldPath:""}): type: 'Normal' reason: 'SnapshotReady' Snapshot test/velero-pv1-my-app-1-vgc4j is ready to use.
・・・
```

スナップショット取得が完了すると、それを基に新しいボリュームが作成され、データのS3へのアップロードが開始されます。アップロード作業の進捗は、datauploadリソースで確認できます。

```bash
$ kubectl get -n velero dataupload
NAME                             STATUS      STARTED   BYTES DONE    TOTAL BYTES    STORAGE LOCATION   AGE     NODE
myapp-tune-off-c8dxm             Completed   78m       22336569344   26214400000    default            112m    ip-192-168-101-246.ap-northeast-1.compute.internal
myapp-tune-off-hcc8q             Completed   78m       21659385856   26214400000    default            112m    ip-192-168-58-61.ap-northeast-1.compute.internal
```


"チューニング無し"の環境ではバックアップは約62分(3706秒)で完了しました。

```bash
$ velero backup get myapp-tune-off
NAME                       STATUS            ERRORS   WARNINGS   CREATED                         EXPIRES   STORAGE LOCATION   SELECTOR
myapp-tune-off             Completed         0        0          2024-11-27 13:48:09 +0900 JST   28d       default            <none>

$ velero backup describe --details myapp-tune-off
Name:         myapp-tune-off
Namespace:    velero
・・・

Phase:  Completed
・・・

Started:    2024-11-27 13:48:09 +0900 JST
Completed:  2024-11-27 14:49:55 +0900 JST
・・・
```


#### リストア

続いて、リストア時間を確認します。

＃先ほどチューニングポイントを示した図では、S3データを他リージョンへコピーしてリストアする構成を描きましたが、ここでは簡単のため同一リージョン/k8sクラスタにリストアします。

* --from-backup myapp-tune-off
どのバックアップを起点としてリストアを行うかを指定します。
さきほど取得したバックアップ名"myapp-tune-off"を指定します。

* --item-operation-timeout 6000m
タイムアウト時間をちょっと長め(100時間)に設定しています。

* --namespace-mappings test:test-r
リストア時に元のネームスペース"test"を、"test-r"に書き変えたかたちでリストアします。
これにより、元のアプリをそのままにリストアできるため、本稿のような動作確認には便利です。

なお、以下コマンドでは"リストア名"について省略しています。その場合、リストア名はバックアップ名にリストア開始日時を付与したもの(バックアップ名-YYYYMMDDHHMISS)になります。

```bash
$ velero create restore \
  --from-backup myapp-tune-off \
  --item-operation-timeout 6000m \
  --namespace-mappings test:test-r
```

リストアが開始されると、まず各マニフェストのリストアが行われ、アプリのPod等がVeleroにより作成(リストア)されます。
ですが、PVを持ったPodの場合、Veleroによる書き戻しが完了するまではPodは起動せずPendingとなります。

```bash
$ kubectl -n test-r get pod,pvc
NAME           READY   STATUS    RESTARTS   AGE
pod/my-app-0   0/1     Pending   0          93s
pod/my-app-1   0/1     Pending   0          93s

NAME                                 STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/pv1-my-app-0   Pending                                      ebs-sc         <unset> 93s
persistentvolumeclaim/pv1-my-app-1   Pending                                      ebs-sc         <unset> 93s
```

Veleroによる書き戻しの進捗状況は、datadownloadリソースで確認できます。

```bash
$ kubectl -n velero get datadownload
NAME                                  STATUS       STARTED   BYTES DONE   TOTAL BYTES   STORAGE LOCATION   AGE   NODE
myapp-tune-off-20241127154405-rvs2d   InProgress   19s       875626496    26214400000   default            38s   ip-192-168-58-61.ap-northeast-1.compute.internal
myapp-tune-off-20241127154405-tmm5b   InProgress   15s       871596032    26214400000   default            38s   ip-192-168-101-246.ap-northeast-1.compute.internal
```

"チューニング無し"の環境ではリストアは約12分(719秒)で完了しました。

```bash
$ velero restore get myapp-tune-off-20241127154405
NAME                            BACKUP           STATUS      STARTED                         COMPLETED                       ERRORS   WARNINGS   CREATED
        SELECTOR
myapp-tune-off-20241127154405   myapp-tune-off   Completed   2024-11-27 15:44:06 +0900 JST   2024-11-27 15:56:05 +0900 JST   0        1          2024-11-27 15:44:06 +0900 JST   <none>

$ velero restore describe --details myapp-tune-off-20241127154405
Name:         myapp-tune-off-20241127154405
Namespace:    velero
・・・

Phase:                       Completed
・・・

Started:    2024-11-27 15:44:06 +0900 JST
Completed:  2024-11-27 15:56:05 +0900 JST
・・・
```


### チューニング有り

続いて、チューニング有りでの一連の流れを説明します。
前述の「チューニング無し」との差分のみにフォーカスし記載していきます。

#### VPC Endpoint作成

チューニングポイントNo.4に対応する手順として、[公式ドキュメント](https://docs.aws.amazon.com/ja_jp/vpc/latest/privatelink/vpc-endpoints-s3.html#create-gateway-endpoint-s3)等を参考に、EKSをデプロイしたVPCにS3用のエンドポイントを作成します。


![s3-vpce.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/b9e5c18f-c180-bf69-2dc1-65a392c9ba5d.png)

#### Workerノード準備

"チューニング無し"ではインスタンスタイプを"m4.xlarge"としましたが、これを"c6i.4xlarge"に変更します(★1)。

また、/(root)領域としてアタッチするEBSボリュームのスループットを312MB/sに変更しています(★2)。
(IOPSについてはデフォルトの3000のままとしています。)

```yaml
・・・

managedNodeGroups:
・・・

- name: c6i4xlarge-1a
  instanceType: c6i.4xlarge #★1
  desiredCapacity: 1
  minSize: 0
  maxSize: 10
  privateNetworking: true
  volumeEncrypted: true
  volumeType: gp3
  availabilityZones: [ "ap-northeast-1a" ]
  volumeThroughput: 312 #★2

- name: c6i4xlarge-1c
  instanceType: c6i.4xlarge #★1
  desiredCapacity: 1
  minSize: 0
  maxSize: 10
  privateNetworking: true
  volumeEncrypted: true
  volumeType: gp3
  availabilityZones: [ "ap-northeast-1c" ]
  volumeThroughput: 312 #★2
```

変更前後のdiffは以下の通りです。(折りたたんでいます)

<details><summary>diff</summary>

```diff
@@ -66,8 +66,8 @@
   privateNetworking: true
   volumeEncrypted: true

-- name: m4xlarge-1a
-  instanceType: m4.xlarge
+- name: c6i4xlarge-1a
+  instanceType: c6i.4xlarge
   desiredCapacity: 1
   minSize: 0
   maxSize: 10
@@ -75,9 +75,10 @@
   volumeEncrypted: true
   volumeType: gp3
   availabilityZones: [ "ap-northeast-1a" ]
+  volumeThroughput: 312

-- name: m4xlarge-1c
-  instanceType: m4.xlarge
+- name: c6i4xlarge-1c
+  instanceType: c6i.4xlarge
   desiredCapacity: 1
   minSize: 0
   maxSize: 10
@@ -85,3 +86,4 @@
   volumeEncrypted: true
   volumeType: gp3
   availabilityZones: [ "ap-northeast-1c" ]
+  volumeThroughput: 312
```
</details>

変更(追記)したClusterConfig.yamlでノードグループを追加します。

```bash
$ eksctl create nodegroup --config-file=ClusterConfig.yaml
```

#### ストレージクラス/ボリュームスナップショットクラス準備

"チューニング有り"用のストレージクラス"ebs-sc-tune"を、以下マニフェストで新たに作成します。
EBSボリュームのスループットを312MB/sに変更しています(★1)。

```yaml
---
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
  name: ebs-sc-tune
provisioner: ebs.csi.aws.com
reclaimPolicy: Delete
volumeBindingMode: WaitForFirstConsumer
allowVolumeExpansion: true
parameters:
  encrypted: "true"
  type: gp3
  throughput: "312" #★1
```

変更前後のdiffは以下の通りです。(折りたたんでいます)

<details><summary>diff</summary>

```diff
@@ -2,9 +2,7 @@
 apiVersion: storage.k8s.io/v1
 kind: StorageClass
 metadata:
-  annotations:
-    storageclass.kubernetes.io/is-default-class: "true"
-  name: ebs-sc
+  name: ebs-sc-tune
 provisioner: ebs.csi.aws.com
 reclaimPolicy: Delete
 volumeBindingMode: WaitForFirstConsumer
@@ -12,4 +10,6 @@
 parameters:
   encrypted: "true"
   type: gp3
+  throughput: "312"
```
</details>

ボリュームスナップショットクラスに変更はありません。

#### Velero準備

"チューニング無し"で使用したHelm用のvalues.yamlで定義したConfigMap"velero-node-agent-configmap"の内容を、(先ほど作成した)性能の高いインスタンスタイプを使用するよう変更します。

ConfigMapは直接書き換えられないので、一旦削除し、新たにデプロイします。
その後、変更を反映するため、(Veleroの各ノードでのアップロード/ダウンロード等の実処理を担う)node-agentを再起動します。


```bash
## Configmap"velero-node-agent-configmap"を一旦削除
$ kubectl -n velero delete configmap velero-node-agent-configmap

## 新たなConfigMap定義を作成
$ cat << EOF > velero-node-agent-configmap-tune.yaml 
apiVersion: v1
kind: ConfigMap
metadata:
  name: velero-node-agent-configmap
  namespace: velero
data:
  node-agent-configmap.json: |
    {
        "loadAffinity": [
            {
                "nodeSelector": {
                    "matchLabels": {
                        "node.kubernetes.io/instance-type": "c6i.4xlarge"
                    }
                }
            }
        ]
    }
EOF

## 新たなConfigMapをデプロイ
$ kubectl apply -f velero-node-agent-configmap-tune.yaml

## node-agentを再起動
$ kubectl -n velero rollout restart daemonset node-agent
```

#### デモアプリ準備

デモアプリについて、以下の3点を変更し、デプロイします。

* ネームスペースをtest-tuneに変更
* 配置先ノード(Node Affinity)を高スペックなもの(c6i.4xlarge)に変更
* PVのストレージクラスを"ebs-sc-tune"に変更


```diff
@@ -2,13 +2,13 @@
 apiVersion: v1
 kind: Namespace
 metadata:
-  name: test
+  name: test-tune
 ---
 apiVersion: apps/v1
 kind: StatefulSet
 metadata:
   name: my-app
-  namespace: test
+  namespace: test-tune
 spec:
   replicas: 2
   selector:
@@ -29,7 +29,7 @@
               - key: "node.kubernetes.io/instance-type"
                 operator: In
                 values:
-                - "m4.xlarge"
+                - "c6i.4xlarge"
       containers:
       - name: my-app
         image: busybox:latest
@@ -61,7 +61,7 @@
       resources:
         requests:
           storage: 25Gi
-      storageClassName: ebs-sc
+      storageClassName: ebs-sc-tune
```

先ほどと同様に、Pod起動後、アタッチしたPV内に1GB×25個のダミーデータを生成しておきます。


#### バックアップ

チューニングNo.1に対応する手順として、事前にPVのスナップショットを取得します。

スナップショットは(Veleroも使用する)[CSIのスナップショット機能](https://kubernetes.io/ja/docs/concepts/storage/volume-snapshots/)を用いると簡単です。

以下の各VolumeSnapshotリソースをデプロイすることで、スナップショットが取得されます。

```bash
## VolumeSnapshotリソース(yaml)定義
$ cat << EOF > volumesnapshots.yaml
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: pv1-my-app-0-snapshot
  namespace: test-tune
  labels:
    created-for: "preparation-for-backup-with-velero"
spec:
  volumeSnapshotClassName: csi-aws-vsc
  source:
    persistentVolumeClaimName: pv1-my-app-0
---
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: pv1-my-app-1-snapshot
  namespace: test-tune
  labels:
    created-for: "preparation-for-backup-with-velero"
spec:
  volumeSnapshotClassName: csi-aws-vsc
  source:
    persistentVolumeClaimName: pv1-my-app-1
EOF

## VolumeSnapshotリソースデプロイ
$ kubectl apply -f volumesnapshots.yaml
```


CSIを用いず、クラウドのAPIを直接利用してもかまいません。
例えばAmazon EBSの場合、[以下のようなコマンド](https://docs.aws.amazon.com/cli/latest/reference/ec2/create-snapshot.html)でもスナップショットが取得できます。

```bash
$ aws ec2 create-snapshot --volume-id ＜ボリュームID＞
```

ボリュームIDは、(k8sの)PVのIDではなく、(AWSが管理の)EBSボリュームのIDである点に留意してください。
PVの.spec.csi.volumeHandleの値で確認できます。(以下vol-09133bf242b6a4ff7がボリュームID)

```bash
$ kubectl get pv pvc-6605fd2d-df94-45ef-ae70-0150714ae57f -o json | jq -r '.spec.csi.volumeHandle'
vol-09133bf242b6a4ff7
```

VolumeSnapshotリソースのreadyToUse欄がtrueになること、または各クラウドの管理コンソール上で完了を確認してください。


```bash
$ kubectl -n test-tune get VolumeSnapshot
```

なお、スナップショットの完了までにはデータ量に応じた時間がかかります。
が、この作業はVeleroによるバックアップ作業とは完全に切り離されており、かつ、あくまで後の(Veleroによる)スナップショット取得時の差分を最小化する目的であるためアプリケーションに全く影響を与えずにバックグラウンドで実行できるというのがミソです。

ちなみに筆者環境では、"チューニング無し"では34分程度かかったスナップショット取得が、チューニング後は3分程度で完了しました。本稿では主眼にしていませんが、EBSのボリュームタイプ/性能がスナップショット取得時間にも影響するようです。

スナップショット取得時間はsnapshot-controllerの以下ログで確認しました。

```bash
$ kubectl -n kube-system logs snapshot-controller-xxxxxxxxx-xxxxx
・・・
I1127 07:25:01.098612       1 snapshot_controller.go:645] createSnapshotContent: Creating content for snapshot test-tune/pv1-my-app-0-snapshot through the plugin ...    
I1127 07:25:01.120896       1 snapshot_controller.go:645] createSnapshotContent: Creating content for snapshot test-tune/pv1-my-app-1-snapshot through the plugin ...
・・・
I1127 07:29:22.274013       1 event.go:377] Event(v1.ObjectReference{Kind:"VolumeSnapshot", Namespace:"test-tune", Name:"pv1-my-app-0-snapshot", UID:"5b312c95-c8b2-4fd1-81ab-4aaaa15d0dce", APIVersion:"snapshot.storage.k8s.io/v1", ResourceVersion:"69782586", FieldPath:""}): type: 'Normal' reason: 'SnapshotReady' Snapshot test-tune/pv1-my-app-0-snapshot is ready to use.
I1127 07:29:22.294625       1 event.go:377] Event(v1.ObjectReference{Kind:"VolumeSnapshot", Namespace:"test-tune", Name:"pv1-my-app-1-snapshot", UID:"30871aa1-3534-4885-bf6d-1e6406245b86", APIVersion:"snapshot.storage.k8s.io/v1", ResourceVersion:"69782584", FieldPath:""}): type: 'Normal' reason: 'SnapshotReady' Snapshot test-tune/pv1-my-app-1-snapshot is ready to use.
・・・
```

スナップショットが完了したら速やかにバックアップを実行します。
コマンドはバックアップ名以外、"チューニング無し"と同じです。

```bash
$ velero create backup myapp-tune-on \
  --snapshot-move-data \
  --include-namespaces test-tune \
  --csi-snapshot-timeout 6000m \
  --item-operation-timeout 6000m
```

バックアップを開始すると、まずVeleroによりVolumeSnapshotリソースが作成され、スナップショットの作成が開始されます。
が、スナップショットはすでに先ほど手で作成済みなので、(差分が多くない限り)すぐに完了します。
この場合、1分程度で完了しました。

```bash
$ kubectl -n kube-system logs snapshot-controller-xxxxxxxxx-xxxxx
・・・
I1127 07:32:12.266727       1 snapshot_controller.go:645] createSnapshotContent: Creating content for snapshot test-tune/velero-pv1-my-app-0-cwrx4 through the plugin ...
I1127 07:32:17.397906       1 snapshot_controller.go:645] createSnapshotContent: Creating content for snapshot test-tune/velero-pv1-my-app-1-44hzs through the plugin ...
・・・
I1127 07:33:20.231330       1 event.go:377] Event(v1.ObjectReference{Kind:"VolumeSnapshot", Namespace:"test-tune", Name:"velero-pv1-my-app-0-cwrx4", UID:"a2bd236f-daed-44e5-9805-e642c9bafb77", APIVersion:"snapshot.storage.k8s.io/v1", ResourceVersion:"69785318", FieldPath:""}): type: 'Normal' reason: 'SnapshotReady' Snapshot test-tune/velero-pv1-my-app-0-cwrx4 is ready to use.
I1127 07:33:24.804995       1 event.go:377] Event(v1.ObjectReference{Kind:"VolumeSnapshot", Namespace:"test-tune", Name:"velero-pv1-my-app-1-44hzs", UID:"8095dab2-4356-40d7-8015-ebcb8d88d783", APIVersion:"snapshot.storage.k8s.io/v1", ResourceVersion:"69785371", FieldPath:""}): type: 'Normal' reason: 'SnapshotReady' Snapshot test-tune/velero-pv1-my-app-1-44hzs is ready to use.
・・・
```

VolumeSnapshotは以下の通り、先ほど手で作成したものとVeleroにより作成したものの両者が表示されます。

```bash
$ kubect get -n test-tune volumesnapshot
NAME                        READYTOUSE   SOURCEPVC      SOURCESNAPSHOTCONTENT   RESTORESIZE   SNAPSHOTCLASS   SNAPSHOTCONTENT                                    CREATIONTIME   AGE
pv1-my-app-0-snapshot       true         pv1-my-app-0                           25Gi          csi-aws-vsc     snapcontent-5b312c95-c8b2-4fd1-81ab-4aaaa15d0dce   8m6s    
       8m7s
pv1-my-app-1-snapshot       true         pv1-my-app-1                           25Gi          csi-aws-vsc     snapcontent-30871aa1-3534-4885-bf6d-1e6406245b86   8m6s    
       8m7s
velero-pv1-my-app-0-cwrx4   false        pv1-my-app-0                           25Gi          csi-aws-vsc     snapcontent-a2bd236f-daed-44e5-9805-e642c9bafb77   55s     
       56s
velero-pv1-my-app-1-44hzs   false        pv1-my-app-1                           25Gi          csi-aws-vsc     snapcontent-8095dab2-4356-40d7-8015-ebcb8d88d783   51s     
       51s
```

スナップショット取得が完了すると、それを基に新しいボリュームが作成され、データのS3へのアップロードが開始されます。
アップロード作業の進捗は、datauploadリソースで確認できます。

```bash
$ kubectl get -n velero dataupload
NAME                             STATUS       STARTED   BYTES DONE    TOTAL BYTES    STORAGE LOCATION   AGE     NODE
myapp-tune-on-5djz2              InProgress   22s       1199964160    26214400000    default            98s     ip-192-168-2-155.ap-northeast-1.compute.internal
myapp-tune-on-rhns8              InProgress   26s       2461466624    26214400000    default            103s    ip-192-168-119-84.ap-northeast-1.compute.internal
```

"チューニング有り"の環境ではバックアップは約4分(214秒)で完了しました。

```bash
$ velero backup get myapp-tune-on
NAME            STATUS      ERRORS   WARNINGS   CREATED                         EXPIRES   STORAGE LOCATION   SELECTOR
myapp-tune-on   Completed   0        0          2024-11-27 16:32:11 +0900 JST   29d       default            <none>

$ velero backup describe --details myapp-tune-on
Name:         myapp-tune-on
・・・

Phase:  Completed
・・・

Started:    2024-11-27 16:32:11 +0900 JST
Completed:  2024-11-27 16:35:45 +0900 JST
・・・
```


#### リストア

続いて、リストア時間を確認します。
元とするバックアップ名およびネームスペースが異なる以外、コマンドは"チューニング無し"と同じです。

```bash
$ velero create restore \
  --from-backup myapp-tune-on \
  --item-operation-timeout 6000m \
  --namespace-mappings test-tune:test-tune-r
```

"チューニング無し"と同様に、まず各マニフェストのリストアが行われ、アプリのPod等がVeleroにより作成(リストア)されます。

```bash
$ kubectl -n test-tune-r get pod,pvc
NAME           READY   STATUS    RESTARTS   AGE
pod/my-app-0   0/1     Pending   0          34s
pod/my-app-1   0/1     Pending   0          33s

NAME                                 STATUS    VOLUME   CAPACITY   ACCESS MODES   STORAGECLASS   VOLUMEATTRIBUTESCLASS   AGE
persistentvolumeclaim/pv1-my-app-0   Pending                                      ebs-sc-tune    <unset>                 34s
persistentvolumeclaim/pv1-my-app-1   Pending                                      ebs-sc-tune    <unset>                 34s
```

Veleroによる書き戻しの進捗状況は、datadownloadリソースで確認できます。

```bash
$ kubectl -n velero get datadownload
NAME                                  STATUS       STARTED   BYTES DONE    TOTAL BYTES   STORAGE LOCATION   AGE   NODE
myapp-tune-on-20241127164031-4cr66    InProgress   23s       8561721344    26214400000   default            45s   ip-192-168-2-155.ap-northeast-1.compute.internal       
myapp-tune-on-20241127164031-wjtkc    InProgress   27s       8712224768    26214400000   default            45s   ip-192-168-119-84.ap-northeast-1.compute.internal
```

"チューニング有り"の環境ではリストアは約2分(123秒)で完了しました。


```bash
$ velero restore get myapp-tune-on-20241127164031
NAME                           BACKUP          STATUS      STARTED                         COMPLETED                       ERRORS   WARNINGS   CREATED
      SELECTOR
myapp-tune-on-20241127164031   myapp-tune-on   Completed   2024-11-27 16:40:32 +0900 JST   2024-11-27 16:42:35 +0900 JST   0        2          2024-11-27 16:40:32 +0900 
JST   <none>

$ velero restore describe --details myapp-tune-on-20241127164031
Name:         myapp-tune-on-20241127164031
・・・

Phase:                       Completed
・・・

Started:    2024-11-27 16:40:32 +0900 JST
Completed:  2024-11-27 16:42:35 +0900 JST
・・・
```


## 結果まとめ

チューニング有無とバックアップ/リストア時間をまとめると以下の通りとなりました。

| 操作 |チューニング前|チューニング後|
|:----|:----|:----|
|バックアップ |3706秒(61.8分)|430秒(7.2分)|
|リストア  |719秒(12.0分)|123秒(2.1分)|

バックアップについて、スナップショット取得にかかった時間とアップロードにかかった時間の内訳は以下の通りでした。

| 操作 |チューニング前|チューニング後|
|:----|:----|:----|
|スナップショット <br> (バックアップ開始～スナップショット取得完了)|2060秒(34.3分)|216秒(3.6分)|
|アップロード <br> (スナップショット取得完了～バックアップ完了)|1646秒(27.4分)|214秒(3.6分)|


バックアップで8.6倍、リストアで5.8倍高速化することができました。
ボリュームやインスタンスタイプを増強しただけの効果が出たかたちです。

※チューニング前後でディスク/NW/CPU/メモリの使用量がどうなったかは、本稿末尾に参考として記載します。詳細を知りたい方はそちらも参照してみてください。

## さいごに

というわけで、無事にチューニングによりVeleroの動作を爆速にすることができました。
ボリュームやインスタンスタイプを高速なものにすると料金は高くなりますが、その分時間を節約できるということで、タイムisマネーです。

今回はチューニングということで、いろいろな手順を紹介しましたが、Veleroでのバックアップ/リストア自体はとても簡単（ワンコマンド）であることも感じていただけたかと思います。


皆様も是非Veleroを活用し、より堅牢にk8sを運用し心安らかにクリスマス＆年末をお過ごしください。


----
※本稿に記載されている製品名、サービス名は、各団体の商標または登録商標です。


## 参考：バックアップ/リストア時のリソース消費

以下、参考情報として、ディスク/NW/CPU/メモリのそれぞれについて、実行中の推移を紹介しながら、チューニングの効果を確認していきます。
なお、Workerノードのグラフ(ディスク/ネットワーク)については、アップロード/ダウンロード処理が2つのノードで並列に実行されたため2ノード分収集しましたが、各ノードで傾向に大きな差は無いため、片方のみ掲載します。

### バックアップ

* WorkerノードのディスクI/O
   * チューニング無し
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/4c6d8141-f0fc-8389-ef9b-434b4eb5fb06.png)
   * チューニング有り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/eb7cb6c1-8a3b-e615-ce46-8482a1ba0350.png)
   * チューニング無しでは15MB/sあたりで不安定な挙動[^5]でしたが、チューニング有りでは最大250MB/sとなり、短時間で処理を完了できました
   * チューニング無しでバックアップ開始(13:48)から35分程度は0で推移していますが、これはAWS側でのスナップショット取得を待っているためです

[^5]:Amazon EBSではスナップショットから新規作成したボリュームは裏側で[S3からの書き戻し（事前処理）が完了するまでレイテンシーが増加する](https://docs.aws.amazon.com/ja_jp/ebs/latest/userguide/ebs-initialize.html)ためこのような挙動になったものと思われます。

* WorkerノードのネットワークI/O
   * チューニング無し
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/ae91bbff-0fe8-dd7b-cb78-635d5fbc4dbd.png)
   * チューニング有り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/d49405a3-a91d-9102-bd29-3bad3eaae3ed.png)
   * チューニング無しでは150Mbpsあたりで不安定な挙動でしたが、チューニング有りでは最大2Gbpsとなり、短時間で処理を完了できました
   * ディスクの内容をネットワーク経由でS3へアップロードする、というタスクの性質上、両者ともディスク/I/Oの性能と呼応(=スループットの傾向が一致)しているようです
   * プラスとマイナスはそれぞれ受信、送信を表しています。Workerノードにおいてはコンテナ内の仮想NICの送信が、ノードNICの受信にあたるため、両方同等の値でグラフが描かれています

* アップロード作業PodのCPU/メモリ
   * チューニング無し
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/858b6439-49f7-aac9-26f7-dbc366324cd7.png)
   * チューニング有り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/6ef4e080-8299-abf7-d5ae-1d4bc076df5c.png)
   * チューニング無しではCPUを0.3コア、メモリを256MiB程度使用しましたが、チューニング有りではCPUを1.5～2コア、メモリを800MiB程度使用しました
   * チューニングによりI/O周りのスループットが向上したことで、Veleroがより活発に活動できたようです

:::note warn
本稿では、Velero(が生成するPod)に対し、[CPU/メモリの上限(RequestsとLimits)](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/#requests-and-limits)を設定しませんでしたが、必要に応じ他のPod等への影響を加味した上限を設定することをお勧めします。

Velero構築にHelmチャートを使用する場合、コンポーネントごとにresources:設定が分かれている点に留意してください。（本稿でダウンロード/アップロード作業Podと呼んでいるものに関係するのは"node-agent"の方です。）
* velero server: https://github.com/vmware-tanzu/helm-charts/blob/velero-8.0.0/charts/velero/values.yaml#L76
* node-agent: https://github.com/vmware-tanzu/helm-charts/blob/velero-8.0.0/charts/velero/values.yaml#L543
:::


### リストア

* WorkerノードのディスクI/O
   * チューニング無し
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/138e1c2a-78db-47b7-fbdf-ba5bce46891c.png)
   * チューニング有り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/07ce573e-f8f6-4360-46ae-9936706a1461.png)
   * チューニング無しでは50MB/s程度でしたが、チューニング有りでは最大250MB/sとなり、短時間で処理を完了できました
   * 両者とも、グラフが2本描かれていますが、片方がkopiaのキャッシュ領域への書き込み、もう片方がリストア対象PVへの書き込みです

* WorkerノードのネットワークI/O
   * チューニング無し
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/5ad185b0-0c87-1620-08aa-9a723ea5426d.png)
   * チューニング有り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/eb842845-d567-a3af-049d-37152953eef2.png)
   * チューニング無しでは400Mbps程度でしたが、チューニング有りでは最大2Gbpsとなり、短時間で処理を完了できました

* ダウンロード作業PodのCPU/メモリ
   * チューニング無し
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/f1919d97-9aed-d83b-e541-361bed2f1029.png)
   * チューニング有り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/0638baf5-34f3-dacf-b88a-2a19fe8ea97d.png)
   * チューニング無しではCPUを0.6コア、メモリを535MiB程度使用しましたが、チューニング有りではCPUを1.5コア、メモリを90MiB程度使用しました
      * チューニング有りではリストア処理が早すぎてメトリクスデータが1点しか取れずグラフが点になってしまいました
   * チューニングによりI/O周りのスループットが向上したことで、Veleroがより活発に活動できたようです

* Workerノードのディスク使用量
   * チューニング無し
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/9d0e5fec-8956-6ba7-801d-39d4fba45ec3.png)
   * チューニング有り
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2345308/22f78e73-2086-5d31-4bb2-269aba04a2d1.png)
   * リストア時にはkopiaがキャッシュをWorkerノードの/(root)領域に配置するため、リストア中はディスク使用量が上昇しています
   * 両者とも30GB程度（≒リストア対象のPVサイズ）を使用しています