JavaScriptを有効にしてください

Kubernetes上でイイ感じにGitリポジトリをバックアップするやつを作った

 ·   ·  ☕ 8 min read

概要

「gitbackup」というGitリポジトリをバックアップするためのKubernetes Operatorを作りました。

https://github.com/ebiiim/gitbackup に公開したので遊んでください。

はじめに

イイ感じにGitリポジトリをバックアップしたい

インターネッツの住人なので大企業のサービスを含め任意の「ホームページ」を砂上の楼閣と認識しており、明らかに信頼性の低い自宅のHDDに人生のほぼすべてを保管しています1。Gitリポジトリも人生の一部なので同じようにしたいと考えました。

平たく言うと「書き散らしたク○コードをGitHubにあげて永遠の物にした気でいるようだけどお前垢バンされたらどうすんの?」です。

どうやって実現するの?

最近は自宅のITインフラをKubernetesに載せるのがマイブームなので、同じようにやりたいです。具体的には、冒頭の図を↓こんな感じのマニフェストでやれたら嬉しいなと思いました。

1
2
3
4
5
6
7
8
apiVersion: gitbackup.ebiiim.com/v1beta1
kind: Repository
metadata:
  name: repo1
spec:
  src: https://github.com/ebiiim/repo1
  dst: https://git.ebiiim.com/me/repo1
  schedule: "0 6 * * *"

なぜ○○○を使わないの?

コードホスティングサービスのミラー機能は?(複数のサービスが同時に壊れることは無いでしょ?)

さきほどのポエムに書いた2

インターネッツの住人なので大企業のサービスを含め任意の「ホームページ」を砂上の楼閣と認識しており、

GitLabを構築してRepository Mirroringを使うのは?(多くのGitHub Alternativesが似たような機能を備えているよね?)

仕事ならそれでやると思う。

それKubernetesでやる必要なくね?

それはそのとおりだが、前述したように自宅ITインフラをKubernetesで管理したい。VM飽きたを用途ごとに作って管理するのはめんどうくさいし。

普通にCronJobでやれば?

自宅クラスタに自作Operator載せるの楽しそうじゃん🥳🥳🥳

さっそく使ってみよう

インストールする

READMEに書いたとおりですが、コマンド2行でインストールできます。

  1. Kubernetesクラスタを用意する

    お好みのものをご準備ください。

  2. cert-managerをインストールする(コマンド実行後30秒ほど待つ)

    すでにインストールしてある場合は本ステップを省略します。

    1
    
    kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.10.0/cert-manager.yaml
    
  3. gitbackupをインストールする

    1
    
    kubectl apply -f https://github.com/ebiiim/gitbackup/releases/download/v0.2.0/gitbackup.yaml
    

Repositoryリソースを作成する

Repositoryリソースは、1つのGitリポジトリからもう1つのGitリポジトリへのコピーを周期的に実施します。

1
2
3
4
5
6
7
8
apiVersion: gitbackup.ebiiim.com/v1beta1
kind: Repository
metadata:
  name: repo1
spec:
  src: https://github.com/ebiiim/repo1
  dst: https://git.ebiiim.com/me/repo1
  schedule: "0 6 * * *"

この例でいえば、0 6 * * *(毎日6時, UTC)に https://github.com/ebiiim/repo1https://git.ebiiim.com/me/repo1 にコピーします。

実際には、RepositoryリソースはCronJobを1つ作成し、そのCronJob(によって作成されたJobのPod)がgit clone --mirrorgit push --mirrorを実行することでバックアップが行われます。

1
2
3
4
5
6
7
$ kubectl get repos
NAME    AGE
repo1   5s

$ kubectl get cronjobs
NAME              SCHEDULE    SUSPEND   ACTIVE   LAST SCHEDULE   AGE
gitbackup-repo1   0 6 * * *   False     0        <none>          5s

あとは、バックアップしたいリポジトリの数だけRepositoryリソースを作ればOKです。

認証を行う

読者のみなさまはすでにお気づきかもしれませんが、ここまでの手順では認証について触れていません。

普通(たとえばGitHub)は認証が必要ですよね。gitbackupではgit credentialstoreモードを使います。つまり、次の状態を想定します。

  1. ~/.gitconfigに次の記載がある

    [credential]
        helper = store
    
  2. ~/.git-credentialsに各サイトの認証情報が平文で記載されている

    https://12345678:[email protected]
    https://user:[email protected]
    

このうち1.はデフォルトで自動生成されますが、2.はユーザが用意する必要があります。

Secretを手動で作成するか、すでに~/.git-credentialsがある場合は次のコマンドで作成します。※この例ではSecret名をgitbackup-secretとします。

1
kubectl create secret generic gitbackup-secret --from-file=$HOME/.git-credentials

続いて、このSecretを使うようにRepositoryリソースを変更します。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  apiVersion: gitbackup.ebiiim.com/v1beta1
  kind: Repository
  metadata:
    name: repo1
  spec:
    src: https://github.com/ebiiim/repo1
    dst: https://git.ebiiim.com/me/repo1
    schedule: "0 6 * * *"
+   gitCredentials:
+     name: gitbackup-secret

これで、指定した認証情報を使ってアクセスを行うようになりました。

Collectionリソースで複数のGitリポジトリをバックアップする

使ってみて気づいたのですが、1つのプロジェクトで複数のGitリポジトリがあったり、そもそも多くのGitリポジトリがあったりする場合があります。そうなると project1-repo1 project1-repo2 のような名前で似た設定のRepositoryリソースをたくさん作ることになり、すごくめんどうくさいです。なので、そういうユースケースを想定したCollectionリソースを用意しました。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
apiVersion: gitbackup.ebiiim.com/v1beta1
kind: Collection
metadata:
  name: coll1
spec:
  schedule: "0 6 * * *"
  gitCredentials:
    name: coll1-secret
  repos:
    - name: repo1
      src: https://github.com/ebiiim/repo1
      dst: https://git.ebiiim.com/me/repo1
    - name: repo2
      src: https://github.com/ebiiim/repo2
      dst: https://git.ebiiim.com/me/repo2
    - name: repo3
      src: https://github.com/ebiiim/repo3
      dst: https://git.ebiiim.com/me/repo3

見てのとおりですが、このCollectionリソースは、複数のGitリポジトリからそれぞれに対応したGitリポジトリへのコピーを周期的に実施します。

実際には、CollectionリソースはRepositoryリソースを複数作成し、作成されたRepositoryリソースがバックアップを行います。ReplicaSetとPodのような関係ですね。

1
2
3
4
5
6
7
8
9
$ kubectl get colls
NAME    AGE
coll1   5s

$ kubectl get repos
NAME                AGE
coll1-repo1         5s
coll1-repo2         5s
coll1-repo3         5s

Collectionリソースは、各Repositoryリソースに同じ内容を設定していきますが、schedule は1分ずつずらします。この例でいえば、repo1 には 0 6 * * *repo2 には 1 6 * * *repo3 には 2 6 * * * を指定します。同時に大量のアクセスが発生することを回避するためです。

1
2
3
4
5
$ kubectl get cronjobs
NAME                        SCHEDULE    SUSPEND   ACTIVE   LAST SCHEDULE   AGE
gitbackup-coll1-repo1       0 6 * * *   False     0        <none>          5s
gitbackup-coll1-repo2       1 6 * * *   False     0        <none>          5s
gitbackup-coll1-repo3       2 6 * * *   False     0        <none>          5s

実装を見てみよう

ここまでで使い方と動作を説明しました。続いては実装です。といっても簡単なカスタムコントローラなので特筆すべきことはないのですが……。

gitbackupの実装にはKubebuilderを利用しました。KubebuilderはOperator Patternを簡単にやるためのコードをテンプレートするツールです。カスタムコントローラやCRDを一から書くのではなく、穴埋めで作れるようにしてくれます。詳しい説明は公式ドキュメントつくって学ぶKubebuilderがわかりやすいです。

Reconciler

RepositoryReconcilerCollectionReconcilerがそれぞれRepositoryリソースとCollectionリソースを管理します。

RepositoryReconcilerはRepositoryリソースの内容に従ってCronJobやConfigMapを作成するので、必要な権限を割り当てています3。Secretに関しては、LocalObjectReferenceを使って名前を渡すだけなので、権限は必要ありません。どこの馬の骨とも知れないカスタムコントローラにSecretを見せたくないですし。

Admission Webhook

Mutating admission webhooksでリソースの各パラメータの初期値を設定し、Validating admission webhooksで検証します。

たとえば、Repositoryリソースでは(使い方の説明では省略しましたが)spec.gitImagespec.gitConfig に適切な初期値を設定します。

テスト

EnvTestでリソースの作成を軽く確認しています。

Repositoryリソースを削除した際に、そのRepositoryリソースによって作成されたCronJobがガベージコレクションによって削除されます4が、これは実クラスタ上でテストする必要があります。EnvTestは実クラスタでのテストも可能なので、kindでクラスタを作ってその上でやればいいですが、いまのところテストコードとしては作成していません。

異なるバージョンのKubernetesでの動作確認はkindとシェルスクリプトで少しだけやっています。

議論

パスワードを平文で置いておくのは危なくないですか?

同感ですが、この問題はSecretを使う任意のアプリケーションに存在します。良いアイデアがありましたらご教示ください。

ご参考: GitOpsをやる場合はbitnami-labs/sealed-secretsが役に立つと思います

Collectionリソースのscheduleに 59 23 * * * を設定するとどうなりますか?

  • 1つ目のRepositoryは59 23 * * *に設定されます
  • 2つ目のRepositoryは0 23 * * *に設定されます
  • 3つ目のRepositoryは1 23 * * *に設定されます
  • Nつ目のRepositoryは(58+N)%60 23 * * *に設定されます

1時間の中でループします。cron expressionのインクリメントを(ちゃんと定義して)実装するのがめんどうくさいのと、実用的にはこれで十分な気がするため、このような実装にしました。良いアイデアがありましたらご教示ください。

cron expressionをインクリメントする代わりにランダム時間sleepするとかですかね……。そしたら @daily なども使えますし。

おわりに

イイ感じにGitリポジトリをバックアップすることができました。

カスタムコントローラの習作として作り始めたものですが、思いのほか普通に使えそうなので書きました。


  1. もちろんインターネット上にもバックアップしています ↩︎

  2. そんなことを考えていても仕方ないのはわかります ↩︎

  3. 厳密にはこのカスタムコントローラ用のサービスアカウントに割り当てられます ↩︎

  4. ownerReferenceの話。ご参考: Owners and Dependents ↩︎