⛴️

Kubernetesクラスタの移行にVeleroを使った話

2020/12/21に公開

この記事はKubernetes Advent Calendar 2020 その2の20日目の記事です。

背景

半年ほど前の話ですが、私の所属している企業ではEC2インスタンスの上にkopsを使って建てたKubernetesクラスタを利用していました。しかし、EKSなどのマネージドなKubernetesクラスタと比較するとクラスタの管理やバージョンアップが容易ではなく、クラスタ自身も2年以上稼働し続けているものだったため、EKSへの移行を計画していました。このへんの話はよくある話だと思います。

幸運にもKubernetesクラスタ自体を新しく建てること自体はそこまで難しくありませんが、クラスタ上のアプリケーション等を古いクラスタから新しいクラスタに載せ替える作業にはかなり手間がかかります。
yamlで宣言的に書かれているんだから、HelmなりKustomizeなりでぱっと移行できそうな気もしますが、新しくOperatorなどを入れるのと違って、数年間運用されているアプリーケーションが動いているという状態は。Kubernetesクラスタ自体がステートを持っているのに等しい状態なので、Helm等でinstallしても何かしらの依存関係でPodが立ち上がらなかったりします。

DevのクラスタはHelmで移行してみましたが、前述の問題もあってかなり手間が発生したり、Dev環境が正常に動かない時間が発生していました。Prodのクラスタの移行は深夜に行いましたが、長時間クラスタが動かない状態は避けなければならなかったため、クラスタのリソースの移行にVeleroというツールを用いました。

What`s Velero

https://velero.io/
VeleroはVMwareからOSSとして公開されているKubernetesクラスタのバックアップ・リストア・マイグレーション等を行うためのツールです。ここで取られるバックアップに含まれるのはDeploymentやConfigMapなどのリソースで、kube-api-serverの起動に利用された引数などは含まれません。リソースの状態をまるっと保存しているとイメージするとわかりやすいかもしれません。

Veleroを選択した理由

Veleroを選択した主な理由は以下です。

この中でもapiVersionの更新に対応しているというのが非常にありがたかったです。
旧クラスタのDeploymentはほとんどのものがextensions/v1beta1を使っていましたが、あたらしいクラスタではそのApiVersionは使えないため、Helm等でApplyするとエラーになってしまいます。
Veleroではそのあたりのバージョンの差分をリストアする段階で吸収してくれて適切なApiVersion(Deploymentならapps/v1)に変更してくれました。

Veleroの主な使い方

次に簡単にVeleroの使い方を紹介します。

CLIのインストール

$ brew install velero

or

release page

$ tar -xvf <RELEASE-TARBALL-NAME>.tar.gz

check velero version

$ velero version

Veleroのクラスタへのインストール

Veleroにはpluginの機構があり、EKSやGKEなどマネージドKubernetesに対して、簡単にS3などのバックアップを設定することができます。
ここではバケット名やリージョン、

$ CLUSTER_NAME=sample-cluster
$ VELERO_BUCKET=eks-stdm-dev
$ AWS_REGION=ap-northeast-1
$ kubectl config use-context sample-cluster
$ velero install \
    --provider aws \
    --plugins velero/velero-plugin-for-aws:v1.0.1 \
    --bucket $VELERO_BUCKET \
    --backup-location-config region=$AWS_REGION \
    --snapshot-location-config region=$AWS_REGION \
    --secret-file ~/.aws/credentials
---------------------------------

$ kubectl get all -n velero                                                                                                                      
NAME                          READY   STATUS            RESTARTS   AGE
pod/velero-649db948f4-qcj5q   0/1     PodInitializing   0          12s

NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/velero   0/1     1            0           13s

NAME                                DESIRED   CURRENT   READY   AGE
replicaset.apps/velero-649db948f4   1         1         0       13s

バックアップ&リストをやってみる

実際にバックアップを取った後、リストアをしてリソースがもとに戻ることを確認して見ます。

用意

https://www.eksworkshop.com/intermediate/280_backup-and-restore/deploy-app/
https://docs.nginx.com/nginx-ingress-controller/installation/installation-with-helm/
でテストアプリと、type LBを作成. nginxをhelmでインストールしてみる

$  helm repo add nginx-stable https://helm.nginx.com/stable
$  helm repo add nginx-stable https://helm.nginx.com/stable
$  helm repo update
$  helm install -n staging my-release nginx-stable/nginx-ingress
$  helm list -n staging
NAME      	NAMESPACE	REVISION	UPDATED                             	STATUS  	CHART              	APP VERSION
my-release	staging  	1       	2020-09-29 19:00:15.409846 +0900 JST	deployed	nginx-ingress-0.6.1	1.8.1


$ > kubectl get all -n staging
NAME                                            READY   STATUS    RESTARTS   AGE
pod/my-release-nginx-ingress-54fc64c4c5-zctr8   1/1     Running   0          51s
pod/wordpress-578744754c-kzlv5                  1/1     Running   0          79m
pod/wordpress-mysql-5b697dbbfc-9chw6            1/1     Running   0          79m

NAME                               TYPE           CLUSTER-IP       EXTERNAL-IP                                                                    PORT(S)                      AGE
service/my-release-nginx-ingress   LoadBalancer   10.100.191.241   a9a607b886c61463c9ab34a2b1cf294f-1084391947.ap-northeast-1.elb.amazonaws.com   80:32222/TCP,443:30894/TCP   51s
service/wordpress                  LoadBalancer   10.100.163.187   ae3784ca27723485c83b86836f87090e-809501219.ap-northeast-1.elb.amazonaws.com    80:32688/TCP                 79m
service/wordpress-mysql            ClusterIP      None             <none>                                                                         3306/TCP                     79m

NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-release-nginx-ingress   1/1     1            1           51s
deployment.apps/wordpress                  1/1     1            1           79m
deployment.apps/wordpress-mysql            1/1     1            1           79m

NAME                                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/my-release-nginx-ingress-54fc64c4c5   1         1         1       51s
replicaset.apps/wordpress-578744754c                  1         1         1       79m
replicaset.apps/wordpress-mysql-5b697dbbfc            1         1         1       79m

ここまででデプロイ終わり

バックアップの作成

VeleroのバックアップはCLIから行います。オンデマンドで好きなタイミングでバックアップを取る方法と、時間を指定して定期的にバックアップをとる方法があります。今回は単発でバックアップをとります。

$ velero backup create staging-backup --include-namespaces staging
$ velero backup get
staging-backup                              Completed   0        0          2020-xx-xx 19:02:00 +0900 JST   29d       default            <none>

リソースの削除

ここで先ほどDeployしたリソースをnamespaceごと削除します。

$ kubectl delete namespace staging
$ kubectl get all -n staging

No resources found in staging namespace.

リストア

バックアップと同様にリストアもCLIから行います。--from-backupオプションからリストアする対象のバックアップを指定します

$ velero restore create --from-backup staging-backup
$ kubectl get all -n staging

NAME                                            READY   STATUS              RESTARTS   AGE
pod/my-release-nginx-ingress-54fc64c4c5-zctr8   1/1     Running             0          8s
...

NAME                               TYPE           CLUSTER-IP      EXTERNAL-IP                                                                   PORT(S)                      AGE
service/my-release-nginx-ingress   LoadBalancer   10.100.245.48   ae075cd90574b4c0f90fc8811b49956c-957111671.ap-northeast-1.elb.amazonaws.com   80:30882/TCP,443:30129/TCP   8s
...

NAME                                       READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/my-release-nginx-ingress   1/1     1            1           8s
...

NAME                                                  DESIRED   CURRENT   READY   AGE
replicaset.apps/my-release-nginx-ingress-54fc64c4c5   1         1         1       8s
...

$ helm3 list -n staging
NAME      	NAMESPACE	REVISION	UPDATED                             	STATUS  	CHART              	APP VERSION
my-release	staging  	1       	2020-09-29 19:00:15.409846 +0900 JST	deployed	nginx-ingress-0.6.1	1.8.1

Veleroを利用することでこのようにクラスタのリソースのバックアップ・リストアが簡単に行えます。
ここでは紹介だけにしますが、ほかにもDeploymentだけのリストア・バックアップができたり、リソース名でフィルタリングをかけられたりといった機能があります。詳細はドキュメントを参照してください。
また先ほどちらっと書きましたが、plugin形式でバックアップ先やバックアップ・リストア時の挙動を拡張することも可能です
https://github.com/vmware-tanzu/velero-plugin-example

クラスタ間のマイグレーション

クラスタ間でのバックアップ・リストアも基本的な流れは同じです。
前の手順と同じようにバックアップを行う旧クラスタでVeleroをインストールしておきます。

クラスタ間のリストア時の認証はあるSecretの値を共有することで認証します。
Secretは旧クラスタ側のvelero namespaceにあるcloud-credentialsというSecretの値を使うので、ファイルに保存しておきます。

  • secretの取得
$  kubectl get secrets/cloud-credentials -n velero --template={{.data.cloud}} | base64 -D >> secret.txt

そしてそのSecretを使って、新クラスタ側のインストールを行います。

$ velero install \
--secret-file=secret.txt \
--provider aws \
--backup-location-config region=AWS_REGION \
--snapshot-location-config region=AWS_REGION \
--plugins=velero/velero-plugin-for-aws:v1.0.1 \
--bucket VELERO_BUCKET

両方のクラスタでインストールが完了したら、旧クラスタの方でバックアップを作成します。ここではstaging namespaceのみのバックアップを作成しています。

$ velero backup create cluster-migration-staging --include-namespaces staging

あとは普通のリストアと同様にバックアップする対象を選択することでクラスタ間のマイグレーションが行えます・

  • バックアップのリストア
$ velero restore create --from-backup cluster-migration-staging

$ kubectl get all -n staging
NAME                                   READY   STATUS              RESTARTS   AGE
pod/wordpress-578744754c-gj7h5         0/1     ContainerCreating   0          31s
...

Veleroを使った移行で困った点

最後にVeleroを使ってマイグレーションした際に困った点をいくつか紹介して終わります・

Jobの扱い

私が使ったVeleroのバージョン(v1.5.1)ではJobをリストアした際に、JobはCompleteならJobリソース自体存在しない状態になるのですが、PodがCompleteではない場合、Jobが作り出したPodもPodリソースとしてリストアされてしまいます。
その状態で、PodリソースとJobリソースをリストアするとリストア先のクラスタでそのJobに紐づくPodが2つ(旧クラスタのJobが作ったPod + 新クラスタのJobが作ったPod)作られてしまいました。

Helmのアノテーション問題

このクラスタ移行時に同時にHelm2系からHelm3系のアップデートをしていました。Helm2系とHelm3系ではHelmで管理されているリソースについているアノテーションが違ったために、Helmが正しくリソースを認識できない問題が発生していました。
Helmが認識するようにアノテーションをOverwriteするスクリプトを用意することで正しくHelmが認識するように対処しました。
https://github.com/helm/helm-2to3/issues/147

Secret情報の扱い

通常だと、クラスタのバックアップを取った時にSecretの中身も見える状態なので注意が必要です。
バックアップ先のS3などのACLで権限を絞るか、リストアの対象からSecretを外す必要があります。

最後に

Veleroかなりおすすめです

Discussion