はじめに
K8sクラスターをいくつか管理していますが、通常はできるだけアップグレードを行い、システムの新規導入は避けるようにしています。今回は久し振りに新事業のためにクラスターを1つ、OSから新規導入することにしました。
とはいえ予算がないので、用途によって2つのクラスターに分けてホスティングしていたアプリケーションを一方に寄せてk8sクラスターを一つ空にすることにしました。
PVCを利用する自作アプリのデータのバックアップにはSynology NASのActiveBackup for Businessアプリを使って、定期的にSynology側から各ノードやバックアップ用のsshdポッドやsidecarにrsyncをかけています。しかし、このバックアップを利用したアプリケーションの引っ越しは神経を使う作業です。
今回はHarborというHelmで導入している複雑な構成のアプリケーションが相手になるため、単純なPVCのバックアップでは太刀打ちできません。課題は比較的大規模なアプリケーションのデータを含めたバックアップとリストア作業です。
Harborはメンテナンスのためにデータの更新を停止することができますが、データのExport機能を持っていません。Harborではissuesへのバックアップ機能のリクエストに対して、開発者がVeleroの利用を推奨しています。
他のアプリケーションにも応用できるようHelmで導入したHarborのバックアップをVeleroで行い、引っ越しに後にアップグレードを行った顛末のメモを残しておきます。
なおKubernetes v1.25.6からv1.27.5に更新する際に記事全体を見直し構成を変更しました。
環境
以下の環境での経験を元に記事をまとめていますが、Kubernetes: v1.25.6、Harbor: v2.7.1 でも同様にバックアップを取得しています。
- Kubernetes v1.22.5 (Kubespray v2.18.0)
- Rook/Ceph v1.7.11
- Harbor v2.3.2 (Helm chart v1.7.2)
- Velero v1.9.1
- Minio 5.0.33 working on another k8s (v1.22.5) cluster
$ sudo helm -n minio list
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
my-minio minio 1 2021-08-31 01:04:24.853217561 +0000 UTC deployed minio-5.0.33 master
参考資料
Harborの公式ドキュメントのVeleroを利用したバックアップの説明。
冒頭で、PVのSnapshotを利用する場合と、Resticを利用する場合で設定の違いがありますが、この選択は慎重に行ってください。自前のオンプレミスK8sクラスターを運用している場合にはResticを利用する方が良いはずです。
Veleroの公式ガイド。Minioの利用とResticによるPVCのバックアップについて書かれています。
Rook/Ceph v1.7でRBD,CephFSのSnapshotを有効にする際に参考にしたドキュメントです。
実際にはVeronoで有効に利用することはできませんでした。
Harbor公式ガイドのHelmのアップグレード手順。側の説明にもあるようにVeleroを使ってもRedisのバックアップは除かれているなど制限があります。
一般的なHarborのバックアップ・リストアについては、アップグレードガイドを参照してください。
Veleroを使った感想
最終的にHarborのバックアップを取得し、他のクラスターに引っ越すことができました。
ちゃんと動いた後の感想は、APIベースのバックアップツールは強力だなと思います。
バックエンドのObject StorageをMinioにするか、RADOSにするか、もう少し確認が必要ですが、これからはKubernetesクラスターには必ず導入しておこうと思いました。
ただ以下のような問題に遭遇しました。次のセクションからは、こういった問題に遭遇しないように手順をまとめていきます。
【ポイント1】事実上Resticを使わざるを得ない
v1.13.2を利用している現在ではResticに代ってKopiaを使用しています。
いずれにしてもVolumeのスナップショット機能を使うのではないと理解してください。
詳細はVelero - File System Backupに記載されています。
ドキュメントの構成からSnapshotを使う方が一般的なのかなと思いましたが、AWS,GKEなど特定のクラウドを利用している場合を除いて、オンプレミスでkubernetesクラスターを構成しているような場合にはResticだけがほぼ唯一の選択だと思います。
これはVeleroのissuesなどに書かれていることですが、PVのSnapshot機能の実態はRook/Cephなどの実装に強く依存します。CSIでインタフェースだけ定義されていて、実際の動作は実装によって異なります。Veloroで期待されていることはAWS EBSのように複数クラスターから同一Snapshotにアクセスできることです。
当然Rook/Cephで作成したSnapshotにはクラスター内からしかアクセスできませんから、引越し先のクラスターからはアクセスできずに失敗します。同一クラスター内であれば適切に構成してあればSnapshotの利用は可能なはずです。
このためのオンプレミスで構築しているKubernetesクラスター間を移動する場合には、Resticを使ってMinioなどのObject Storage上にPVCの内容を保存・利用する他に手段はありません。
Minioの9000番ポートにアクセスできれば、複数のk8sクラスターに導入したVeleroから同一のバックアップにアクセスすることが可能になります。ResticよりはSnapshotを利用する方がバックアップの信頼性は向上しますが、アプリケーションの構成がそれほどまでの厳密性を要求しないのであれば引っ越しや何かあった時の復元という用途では十分に利用できます。
【ポイント2】明示的にバックアップ対象をannotateする必要があった
各Podのannotateにバックアップ対象のvolume名を明示する必要がありました。最初はPVCの名前を書いてしまって失敗しましたが、指定する値は .spec.containers.volumes のname:値です。
Veleroの"Using opt-in pod volume backup"をみると、次のように書かれていて、Resticを使う場合には、含めるPVCを明示的に記述する必要があって、除外する場合には記述する必要がないことになっています。
Velero, by default, uses this approach to discover pod volumes that need to be backed up using Restic. Every pod containing a volume to be backed up using Restic must be annotated with the volume’s name using the backup.velero.io/backup-volumes annotation.
この下の手順ではHarborのドキュメントに従って、除外するRedisのPVCを指定していますが、実際には不要なはずです。backup.velero.io/backup-volumes-excludeはaccess token, secrets, configmapについて除外を指定する場合に利用します。
HarborのドキュメントにあるVeleroを使うシナリオは、AWSなどでのSnapshot機能の利用を想定しているのでしょう。
結果的にいろいろな試行錯誤を経て、無事にVeleroによるPVCを含めたアプリケーションのバックアップが取得できるようになりました。
作業手順のまとめ
Veleroのインストールと設定作業
Veleroのコマンドはバイナリが公式サイトからダウンロードできるので、kubectlコマンドを実行するホストに配置します。
また、velero installコマンドを実行することで、kubectlと同じ方法でkubeapiサーバーに接続し、必要な情報の取得、リソースの登録などを行います。
私の環境ではkubectlコマンドは、sudoコマンドと組み合せているので、veleroも同様にsudoから特権ユーザーで実行しています。
Minioを利用する場合、./credentials-velero には公式ガイドにあるようにS3互換bucketに接続するためのAPI_KEYとSECRET_KEYが書かれています。Minio側であらかじめBucketを準備しておきます。
[default]
aws_access_key_id = af9f64c84bd6d952
aws_secret_access_key = b1f2fec0f4002634f2627c82a1c705f6
ここに書くaws_access_key_id, aws_secret_access_keyは、指定したbucketに接続するために必要なaccess-keyとsecret-access-keyを指定します。
MinioのServiceがClusterIPを利用している場合には外部からアクセスできないため、s3Urlに指定するURLのホスト名は、"my-minio.minio.svc.cluster.local"のような形式になります。今回は別クラスターに引っ越すため、引越し先のMinioに領域を確保しているため、type: LoadBalancerを指定してLAN内でアクセス可能なIPアドレス(192.168.1.19)を準備しています。
Veleroのインストールについては次の記事を参照してください。
Harborのバックアップ
まずHarborの公式ガイドに従って、HarborのsettingsからReadOnlyモードに設定しておきます。
公式ガイドを尊重してredisのPVCをveleroの対象外するためのラベルを追加します。なおHarborはhelmでの登録時に名前をmy-harborに変更しているため、PVC名なども適宜修正が必要です。実際にはResticを使っている場合には不要です。
$ sudo kubectl -n harbor annotate pod/my-harbor-redis-0 backup.velero.io/backup-volumes-exclude=data
続いてバックアップ対象のvolumeを明示的に追加します。この作業はResticを使っている場合には必須です。
$ sudo kubectl -n harbor annotate pod/my-harbor-database-0 backup.velero.io/backup-volumes=database-data
$ sudo kubectl -n harbor annotate pod/my-harbor-chartmuseum-7c49675b5b-sqdrr backup.velero.io/backup-volumes=chartmuseum-data
$ sudo kubectl -n harbor annotate pod/my-harbor-registry-68fbb4cfb7-l6zmq backup.velero.io/backup-volumes=registry-data
$ sudo kubectl -n harbor annotate pod/my-harbor-trivy-0 backup.velero.io/backup-volumes=data
Podの名前はStatefulSet以外でないと不定になるので、都度確認してください。
続いて、veleroでバックアップを取得します。
$ sudo velero backup create harbor-backup-01 --include-namespaces harbor --wait
次のようなメッセージが出力されました。
Backup request "harbor-backup-01" submitted successfully.
Waiting for backup to complete. You may safely press ctrl-c to stop waiting - your backup will continue in the background.
...........
Backup completed with status: Completed. You may check for more information using the commands `velero backup describe harbor-backup-01` and `velero backup logs harbor-backup-01`.
後述するようにMinioのWeb-UIを利用した方が確実ですが、出力されているようにvelero backup describe
を利用した場合の出力例を掲載します。
$ sudo velero backup describe --details harbor-backup-20230817-1109
Name: harbor-backup-20230817-1109
Namespace: velero
Labels: velero.io/storage-location=default
Annotations: velero.io/source-cluster-k8s-gitversion=v1.25.6
velero.io/source-cluster-k8s-major-version=1
velero.io/source-cluster-k8s-minor-version=25
Phase: Completed
Errors: 0
Warnings: 0
Namespaces:
Included: harbor
Excluded: <none>
Resources:
Included: *
Excluded: <none>
Cluster-scoped: auto
Label selector: <none>
Storage Location: default
Velero-Native Snapshot PVs: auto
TTL: 720h0m0s
Hooks: <none>
Backup Format Version: 1.1.0
Started: 2023-08-17 02:09:49 +0000 UTC
Completed: 2023-08-17 02:20:10 +0000 UTC
Expiration: 2023-09-16 02:09:49 +0000 UTC
Total items to be backed up: 123
Items backed up: 123
Resource List:
... 省略 ...
Velero-Native Snapshots: <none included>
Restic Backups:
Completed:
harbor/my-harbor-chartmuseum-78f59dc78b-4j8hr: chartmuseum-data
harbor/my-harbor-database-0: database-data
harbor/my-harbor-registry-7c58898f96-9h8z8: registry-data
harbor/my-harbor-trivy-0: data
確認
MinioのWeb UIからvelero/とrestic/のディレクトリが存在するか確認しておきます。
$ firefox http://192.168.1.19:9000
細かく内部を確認する必要はありませんが、resticの動作に失敗していると、resticディレクトリが存在しないなどの不自然な点があるはずです。
準備したBucket全体のサイズを確認し、Harborの全データが含まれていそうか確認してください。
別クラスターでのリストア
別クラスターにもveleroを導入し、先ほどと同様に ./credentials-velero を作成し、velero install
を実行します。
特に問題がなければ、リストア処理を実行します。
$ sudo velero restore create --from-backup harbor-backup-01 --wait
Veleroを利用した場合、ClusterIPのような動的な値はうまく修正してくれますが、loadBalancerIPのような静的な値については手動での修正が必要です。
Serviceオブジェクトなどは loadBalancerIP の指定が正しくないので、kubectl edit コマンドなどで正しく動作するように修正します。
$ sudo kubectl -n harbor edit svc/harbor
Serviceオブジェクトは修正しましたが、システム自体は正常リストアされ動作しています。
遭遇した問題
移行先に既にnamespaceが存在しているとエラーになります。
$ sudo velero restore create --from-backup harbor-backup-01 --wait
An error occurred: restores.velero.io "harbor-restore" already exists
namespace/harborを初期化して問題がなければ、これを削除して、再度リストア処理を実行します。
$ sudo kubectl delete ns harbor
Harborのアップグレード (1.7.2 → 1.8.3)
veleroによるリストアが完了したらHarborのアップグレードを行います。万が一失敗してもnamespaceを削除して再度リストアすれば繰り返し作業が可能です。
今回はharbor-helm 1.7.2(Harbor v2.3.x)を利用していたので、1.8.xの最新版の1.8.3(Harbor v2.4.x)に更新します。
ただvalues.yamlを変更してから次のようにエラーに遭遇してしまいました。
Error: UPGRADE FAILED: failed to replace object: PersistentVolumeClaim "my-harbor-chartmuseum" is invalid: spec: Forbidden: spec is immutable after creation except resources.requests for bound claims
core.PersistentVolumeClaimSpec{
AccessModes: {"ReadWriteOnce"},
Selector: nil,
Resources: {Requests: {s"storage": {i: {...}, s: "50Gi", Format: "BinarySI"}}},
- VolumeName: "",
+ VolumeName: "pvc-d6801a4e-4347-4560-92b4-63e60c7c9e05",
StorageClassName: &"rook-ceph-block",
VolumeMode: &"Filesystem",
... // 2 identical fields
}
&& failed to replace object: PersistentVolumeClaim "my-harbor-registry" is invalid: spec: Forbidden: spec is immutable after creation except resources.requests for bound claims
core.PersistentVolumeClaimSpec{
AccessModes: {"ReadWriteOnce"},
Selector: nil,
Resources: {Requests: {s"storage": {i: {...}, s: "50Gi", Format: "BinarySI"}}},
- VolumeName: "",
+ VolumeName: "pvc-445559cc-6fe1-45e4-a180-10cd37bcea37",
StorageClassName: &"rook-ceph-block",
VolumeMode: &"Filesystem",
... // 2 identical fields
}
&& failed to replace object: StatefulSet.apps "my-harbor-database" is invalid: spec: Forbidden: updates to statefulset spec for fields other than 'replicas', 'template', 'updateStrategy' and 'minReadySeconds' are forbidden
どうもNAMEに"harbor"の名称が入っている場合に共通の問題らしく下記のissuesを見つけました。
今回は何か問題が発生しても、kubectl delete ns harbor & velero restore ...
でやり直しができます。
あまり結果は気にせずに試します。
$ sudo helm -n harbor upgrade my-harbor --force --set fullnameOverride=my-harbor-harbor .
Release "my-harbor" has been upgraded. Happy Helming!
NAME: my-harbor
LAST DEPLOYED: Tue Aug 30 03:11:44 2022
NAMESPACE: harbor
STATUS: deployed
REVISION: 3
TEST SUITE: None
NOTES:
Please wait for several minutes for Harbor deployment to complete.
Then you should be able to visit the Harbor portal at https://harbor.example.com
For more details, please visit https://github.com/goharbor/harbor
helm listで状況を確認します。
$ sudo helm -n harbor list
NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION
my-harbor harbor 3 2022-08-30 03:11:44.106979339 +0000 UTC deployed harbor-1.8.3 2.4.3
これで見た目はちゃんとしていますが、残念ながら失敗してしまいました。現象としては初期化された状態になっています。
原因はPVCが新規に作成されてしまっていた点で、issuesの例をみて、違和感はあったのですが、実行してみないと分からないです。
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
data-my-harbor-harbor-redis-0 Bound pvc-dce55191-cac5-45c4-ae1e-19e2812ca3ce 10Gi RWO rook-ceph-block 3m
data-my-harbor-harbor-trivy-0 Bound pvc-62943af7-5e48-4a82-996f-08af1f6c4575 50Gi RWO rook-ceph-block 3m
data-my-harbor-redis-0 Bound pvc-4c042930-3e70-4d77-acfc-5571e90d7236 10Gi RWO rook-ceph-block 103m
data-my-harbor-trivy-0 Bound pvc-aba885d1-05d8-4c45-9b2c-a69364550bee 50Gi RWO rook-ceph-block 103m
database-data-my-harbor-database-0 Bound pvc-96ab8e95-b66b-4f4b-97d4-6a8f7248db53 50Gi RWO rook-ceph-block 103m
database-data-my-harbor-harbor-database-0 Bound pvc-cf309c35-cd8c-491b-8c06-c31ef380818c 10Gi RWO rook-ceph-block 3m
my-harbor-chartmuseum Bound pvc-d6801a4e-4347-4560-92b4-63e60c7c9e05 50Gi RWO rook-ceph-block 103m
my-harbor-harbor-chartmuseum Bound pvc-c86accc3-c834-456a-b2cd-c9f7b052ac06 50Gi RWO rook-ceph-block 3m1s
my-harbor-harbor-jobservice Bound pvc-2ced258c-9127-4ee8-b8d0-b76c468afaf7 10Gi RWO rook-ceph-block 3m1s
my-harbor-harbor-registry Bound pvc-80a6e130-be3c-4250-be65-bcd9a185e0fc 50Gi RWO rook-ceph-block 3m1s
my-harbor-jobservice Bound pvc-21f30b37-5266-4028-b23a-2e41fe2b4105 10Gi RWO rook-ceph-block 15m
my-harbor-registry Bound pvc-445559cc-6fe1-45e4-a180-10cd37bcea37 50Gi RWO rook-ceph-block 103m
veleroでリストアしても良かったのですが、values.yamlにexistingClaimを明示的に指定して、かつfullnameOverrideも指定します。
この状態で再びhelm upgradeを実行します。
$ vi values.yaml
$ sudo helm -n harbor upgrade my-harbor --force --set fullnameOverride=my-harbor .
最終的には、同じバージョンのhelmを繰り返し適用していますが、問題なく動作し、"my-harbor-harbor"の名前がついてしまったPVCは手動で削除しました。
この状態で問題なくpull/pushができるようになりました。
【閑話休題】Rook/CephのSnapshot機能を有効にする
RookCephのバージョンはSnapshotをサポートしていることは分かっていましたが、必要な設定をしていなかったので、これを有効にします。
kubesprayでのSnapshot機能の有効化
diff --git a/inventory/mycluster/group_vars/k8s_cluster/addons.yml b/inventory/mycluster/group_vars/k8s_cluste
r/addons.yml
index 98ceef0c..830aaf35 100644
--- a/inventory/mycluster/group_vars/k8s_cluster/addons.yml
+++ b/inventory/mycluster/group_vars/k8s_cluster/addons.yml
@@ -56,7 +56,7 @@ local_volume_provisioner_enabled: false
# CSI Volume Snapshot Controller deployment, set this to true if your CSI is able to manage snapshots
# currently, setting cinder_csi_enabled=true would automatically enable the snapshot controller
# Longhorn is an extenal CSI that would also require setting this to true but it is not included in kubespray
-# csi_snapshot_controller_enabled: false
+csi_snapshot_controller_enabled: true
# CephFS provisioner deployment
cephfs_provisioner_enabled: false
これをansible-playbookから反映させたら、rook/cephで必要な設定を行います。
Rook/CephでのSnapshotClassの作成
次のようにYAMLファイルを反映させます。
$ sudo kubectl apply -f cluster/examples/kubernetes/ceph/csi/rbd/snapshotclass.yaml
volumesnapshotclass.snapshot.storage.k8s.io/csi-rbdplugin-snapclass created
この状態で再びveleroでバックアップを取得します。
$ sudo velero backup create harbor-backup-20220829 --include-namespaces harbor --snapshot-volumes --wait
同じ名前のbackupは作成できなかったので、別名に変更しますが、やはりサイズからPVCの中についてはバックアップされていないようです。
ログをみると既に取得されているsnapshotに反応しているようでしたが、snapshotを使わずにresticを使うであるなどのメッセージが出力されて、うまく動作していないようでした。
ここでsnapshotを取得できても、他のクラスターからは参照できず、使えなさそうなので、全面的にresticを利用するような方向で考えていきます。
Harbor Helm v1.11.1からv1.12.4へのアップグレードについて
元々この記事を執筆していた時点では、Harbor Helmを利用するために、Githubからcloneした goharbor/harbor-helm リポジトリを利用していました。
今回から次のようなMakefileを準備して、make update && make fetch
コマンドから最新のtar.gzアーカイブを取得・展開、values.yamlファイルの編集、make upgrade
の実行による更新処理という作業手順に変更しています。
Minioなどはこの手順で導入しているため、作業手順を揃えるために変更を実施しました。
NAME = harbor
REL_NAME = my-harbor
CHART_PATH = harbor/harbor
VALUES_YAML = values.yaml
.PHONY: init
init:
sudo kubectl create ns $(NAME)
.PHONY: add
add:
sudo helm repo add harbor https://helm.goharbor.io
.PHONY: update
update:
sudo helm repo update
.PHONY: fetch
fetch:
sudo helm fetch $(CHART_PATH)
.PHONY: install
install:
(cd $(NAME) ; sudo helm install $(REL_NAME) --namespace $(NAME) -f $(VALUES_YAML) . )
.PHONY: upgrade
upgrade:
(cd $(NAME) ; sudo helm upgrade $(REL_NAME) --namespace $(NAME) -f $(VALUES_YAML) . )
.PHONY: delete
delete:
sudo helm delete --namespace $(NAME) $(REL_NAME)
.PHONY: delete-ns
delete-ns:
sudo kubectl delete ns $(NAME)
なおhelmのリストコマンドは、sudo helm -n harbor list
のように実行します。
$ make update
$ make fetch
$ tar xvzf harbor-1.12.4.tgz
$ vi harbor/values.yaml ## existingClaimなどを中心に変更を実施する
$ make upgrade
Harborのアップグレードも基本的には自動的にReconcileによって収束するのですが、時々、次のようなおかしい状態のまま固まってしまう場合があります。
長時間放置すれば収束するのかもしれませんが、古いプロセスを停止して
NAME READY STATUS RESTARTS AGE
my-harbor-core-c5999b57c-b8dzs 1/1 Running 1 (9m38s ago) 11m
my-harbor-database-0 1/1 Running 0 8m58s
my-harbor-jobservice-84779489-xt45n 1/1 Running 5 (9m22s ago) 11m
my-harbor-nginx-684fd4d7c4-cw2zr 1/1 Running 0 11m
my-harbor-notary-server-7f7768d79b-lqxd2 1/1 Running 4 (3m20s ago) 11m
my-harbor-notary-signer-59b5bdcf77-lqjxk 1/1 Running 3 (8m55s ago) 11m
my-harbor-portal-ddbc555ff-l8rvz 1/1 Running 0 11m
my-harbor-redis-0 1/1 Running 0 10m
my-harbor-registry-7c58898f96-9h8z8 2/2 Running 4 (98d ago) 160d
my-harbor-registry-f7c976c5b-svpzw 0/2 ContainerCreating 0 11m
my-harbor-trivy-0 1/1 Running 0 10m
この状態ではPodを手動で削除しても不整合を解消することはできませんでした。
次のように全てのreplicaset定義を削除し、deploymentに作り直してもらっています。
$ sudo kubectl delete replicaset.apps/my-harbor-registry-7c58898f96
$ sudo kubectl delete replicaset.apps/my-harbor-registry-f7c976c5b
...
以上