LoginSignup
4
1

Velero+MinioによるHarborの引越し作業

Last updated at Posted at 2022-08-30

はじめに

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を使わざるを得ない

ドキュメントの構成から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を準備しておきます。

credentials-velero
[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を使っている場合には不要です。

redisのvolumeを除外する
$ sudo kubectl -n harbor annotate pod/my-harbor-redis-0 backup.velero.io/backup-volumes-exclude=data

続いてバックアップ対象のvolumeを明示的に追加します。この作業はResticを使っている場合には必須です。

バックアップ対象のvolumeを明示的に追加する
$ 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でバックアップを取得します。

veleroでのバックアップの取得
$ sudo velero backup create harbor-backup-01 --include-namespaces harbor --wait

次のようなメッセージが出力されました。

velero実行時の画面出力
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を利用した場合の出力例を掲載します。

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 コマンドなどで正しく動作するように修正します。

svc/harborの修正
$ sudo kubectl -n harbor edit svc/harbor

Serviceオブジェクトは修正しましたが、システム自体は正常リストアされ動作しています。

遭遇した問題

移行先に既にnamespaceが存在しているとエラーになります。

namespace/harborが存在している場合のエラー
$ sudo velero restore create --from-backup harbor-backup-01 --wait
An error occurred: restores.velero.io "harbor-restore" already exists

namespace/harborを初期化して問題がなければ、これを削除して、再度リストア処理を実行します。

注意: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を変更してから次のようにエラーに遭遇してしまいました。

upgrade時のエラー
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 ... でやり直しができます。
あまり結果は気にせずに試します。

再度upgradeを実施
$ 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の例をみて、違和感はあったのですが、実行してみないと分からないです。

PVCの状況
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機能の有効化

addons.ymlファイルの差分
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ファイルを反映させます。

rook/ディレクトリで作業を実施
$ sudo kubectl apply -f cluster/examples/kubernetes/ceph/csi/rbd/snapshotclass.yaml
volumesnapshotclass.snapshot.storage.k8s.io/csi-rbdplugin-snapclass created

この状態で再びveleroでバックアップを取得します。

veleroの再実行時に"harbor-backup-20220829"を指定
$ 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などはこの手順で導入しているため、作業手順を揃えるために変更を実施しました。

Makefileの内容
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 
...

以上

4
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
1