EKSのリソースをVeleroを使ってS3にバックアップする

[Velero](https://velero.io/)はKubernetes Clusterのリソースをバックアップするのに使われるデファクト的なツールである。
このドキュメントはEKS上のリソースをS3にバックアップした際の手順を残すためのメモとなる。
なお、[こちら](https://aws.amazon.com/jp/blogs/containers/backup-and-restore-your-amazon-eks-cluster-resources-using-velero/)のAWSのブログを参考にした。


## 前提条件
ここでの手順では以下が揃っていることが前提となる。Veleroを使う上での必須要件ではない点に注意。

- eksctl v0.58以上
- AWS CLI v2
- Helm v3
- kubectl

## バックアップの準備
### 環境変数の定義
この作業で使う環境変数をまとめて定義しておく。
```bash
# S3のバケット名
export BUCKET=imurata-velero-test
# Region
export REGION=us-east-1
# アカウントID
export ACCOUNT=$(aws sts get-caller-identity --query Account --output text)
# EKSのクラスタ名
export PRIMARY_CLUSTER=imurata-eksctl
# EKSのコンテキスト名
export PRIMARY_CONTEXT=imurata-eksctl
```

### S3の設定
S3のバケットを作成する。既にあるものを使い回す場合は作成しなくてもOK。
```
aws s3 mb s3://$BUCKET --region $REGION
```
次にVeleroが使うポリシーを作成する。
```json
cat > velero_policy.json <<EOF
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "ec2:DescribeVolumes",
                "ec2:DescribeSnapshots",
                "ec2:CreateTags",
                "ec2:CreateVolume",
                "ec2:CreateSnapshot",
                "ec2:DeleteSnapshot"
            ],
            "Resource": "*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject",
                "s3:DeleteObject",
                "s3:PutObject",
                "s3:AbortMultipartUpload",
                "s3:ListMultipartUploadParts"
            ],
            "Resource": [
                "arn:aws:s3:::${BUCKET}/*"
            ]
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:ListBucket"
            ],
            "Resource": [
                "arn:aws:s3:::${BUCKET}"
            ]
        }
    ]
}
EOF

aws iam create-policy --policy-name VeleroAccessPolicy --policy-document file://velero_policy.json --no-cli-pager
```
Veleroが使うサービスアカウントを作成する。Namespaceを指定しているため、Veleroもこの先同じNamespaceにインストールする必要がある。
```
eksctl create iamserviceaccount \
--cluster=$PRIMARY_CLUSTER \
--name=velero-server \
--namespace=velero \
--role-name=eks-velero-backup \
--role-only \
--attach-policy-arn=arn:aws:iam::$ACCOUNT:policy/VeleroAccessPolicy \
--approve
```

### Veleroのインストール
ここではHelmでインストールする。
Chartリポジトリを追加する。
```
helm repo add vmware-tanzu https://vmware-tanzu.github.io/helm-charts
```
今回インストールするVeleroに対応するPluginのバージョンを調べる。
今回インストールするVeleroのバージョンはHelmコマンドで取得する。
```
$ helm search repo velero
NAME               	CHART VERSION	APP VERSION	DESCRIPTION
stable/velero      	2.7.4        	1.2.0      	A Helm chart for velero
vmware-tanzu/velero	3.1.2        	1.10.0     	A Helm chart for velero
```
1.10.0に対応するPluginのバージョンは[velero-plugin-for-aws](https://github.com/vmware-tanzu/velero-plugin-for-aws#compatibility)から取得する。1.6.xが対応しているようなので、以下で1.6.0を指定した設定ファイルを作成する。
```yaml
cat > values.yaml <<EOF
configuration:
  backupStorageLocation:
    bucket: $BUCKET
  provider: aws
  volumeSnapshotLocation:
    config:
      region: $REGION
credentials:
  useSecret: false
initContainers:
- name: velero-plugin-for-aws
  image: velero/velero-plugin-for-aws:v1.6.0
  volumeMounts:
  - mountPath: /target
    name: plugins
serviceAccount:
  server:
    annotations:
      eks.amazonaws.com/role-arn: "arn:aws:iam::${ACCOUNT}:role/eks-velero-backup"
EOF
```
EKSクラスタのコンテキストを作成し切り替える（既にkubeconfigに設定がある人は不要）。
```
aws eks --region $REGION update-kubeconfig --name $PRIMARY_CLUSTER --alias $PRIMARY_CONTEXT
kubectl config use-context $PRIMARY_CONTEXT
```
Veleroをクラスタにインストールする。
```
helm install velero vmware-tanzu/velero \
    --create-namespace \
    --namespace velero \
    -f values.yaml
```
Podがあがっていることを確認する。
```
$ kubectl get pod -n velero
NAME                      READY   STATUS    RESTARTS   AGE
velero-6566499778-x674s   1/1     Running   0          4m46s
```
VeleroのCLIを[こちらの手順](https://velero.io/docs/v1.6/basic-install/#install-the-cli)に従ってインストールする。
ここではMacのインストール手順のみ紹介しておく。
```
brew install velero
```

### バックアップ対象の作成
バックアップを取得する際のサンプルを構築する。オフィシャルブログだとghostを立てていたが、ここでは面倒なのでnginxで済ます。

```
kubectl create ns nginx-ns
kubectl run --image nginx nginx-pod -n nginx-ns
```

## バックアップの取得
veleroコマンドでバックアップを取得する。
```
 velero backup create nginx-backup --include-namespaces nginx-ns --wait
```
正常に取れたことを確認するために、BackupのStatusが`Completed`になっていることを確認する。
```
$ velero backup get nginx-backup
NAME           STATUS      ERRORS   WARNINGS   CREATED                         EXPIRES   STORAGE LOCATION   SELECTOR
nginx-backup   Completed   0        0          2023-02-01 18:41:43 +0900 JST   29d       default            <none>
```
バケットの中身も見てみる。ここでは[s3-tree](https://pypi.org/project/s3-tree/)を使う。
```
$ s3-tree $BUCKET
imurata-velero-test
└── backups
    └── nginx-backup
        ├── nginx-backup-csi-volumesnapshotclasses.json.gz
        ├── nginx-backup-csi-volumesnapshotcontents.json.gz
        ├── nginx-backup-csi-volumesnapshots.json.gz
        ├── nginx-backup-logs.gz
        ├── nginx-backup-podvolumebackups.json.gz
        ├── nginx-backup-resource-list.json.gz
        ├── nginx-backup-volumesnapshots.json.gz
        ├── nginx-backup.tar.gz
        └── velero-backup.json

2 directories, 9 files
```
正常にバックアップが取得できていることに加えて、VolumeSnapshotを利用してバックアップするのが分かる（PVはバックアップしてないけど）。恐らくこれによりディスクへのwrite中にバックアップを取得してもデータの一貫性が保証されるはずだ。多分。

## バックアップのリストア
リストアを確認するために、先ほど作成した確認用のPodとNamespaceを削除する。
```
kubectl delete ns nginx-ns
```
リストアを実行する。
```
velero restore create nginx-backup --from-backup nginx-backup --wait
```
リストアが成功したことも一応確認しておく。
```
$  velero restore get nginx-backup
NAME           BACKUP         STATUS      STARTED                         COMPLETED                       ERRORS   WARNINGS   CREATED                         SELECTOR
nginx-backup   nginx-backup   Completed   2023-02-01 18:49:57 +0900 JST   2023-02-01 18:49:58 +0900 JST   0        0          2023-02-01 18:49:56 +0900 JST   <none>
```

リストア完了後、nginxのPodが起動していることを確認する。
```
$ kubectl get pod -n nginx-ns
NAME        READY   STATUS    RESTARTS   AGE
nginx-pod   1/1     Running   0          20s
```

無事リストア出来ていることが確認できた。