{"id":18780783,"url":"https://github.com/cloudymax/swfs-lab","last_synced_at":"2026-02-03T13:07:06.660Z","repository":{"id":259195998,"uuid":"864442602","full_name":"cloudymax/swfs-lab","owner":"cloudymax","description":null,"archived":false,"fork":false,"pushed_at":"2025-08-29T19:48:37.000Z","size":81,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2026-01-14T14:42:38.593Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/cloudymax.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-09-28T08:23:38.000Z","updated_at":"2025-08-29T19:48:41.000Z","dependencies_parsed_at":"2025-01-28T08:23:55.847Z","dependency_job_id":"c42298ff-0805-43e5-8585-70607921bd4d","html_url":"https://github.com/cloudymax/swfs-lab","commit_stats":null,"previous_names":["cloudymax/swfs-lab"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/cloudymax/swfs-lab","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudymax%2Fswfs-lab","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudymax%2Fswfs-lab/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudymax%2Fswfs-lab/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudymax%2Fswfs-lab/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cloudymax","download_url":"https://codeload.github.com/cloudymax/swfs-lab/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cloudymax%2Fswfs-lab/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29046503,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-03T10:09:22.136Z","status":"ssl_error","status_checked_at":"2026-02-03T10:09:16.814Z","response_time":96,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-07T20:27:59.467Z","updated_at":"2026-02-03T13:07:06.637Z","avatar_url":"https://github.com/cloudymax.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Backup and Recovery with K8up\n\nThis guide will walk you through the creation, backup, and recovery processes for a local [SeaweedFS](https://github.com/seaweedfs/seaweedfs) deployment using [K8up](https://k8up.io/) and [Backblaze B2](https://www.backblaze.com/docs/cloud-storage).\n\nK8up is a Kubernetes-native wrapper for [Restic](https://restic.readthedocs.io/en/stable/) therefore users of Restic and other Restic-based tooling like [Velero](https://velero.io/) should also find the techniques described here useful. Users of other S3 hosted services such as [Wasabi S3](https://wasabi.com/), [Cloudflare R2](https://www.cloudflare.com/developer-platform/r2/) etc... should also be able to follow along.\n\n\u003e For the purposes of this demo, backups are set to run very frequently and plain-text passwords are also used for convenience - do NOT do that in production.\n\nPR ref: https://github.com/seaweedfs/seaweedfs/pull/5034\n\n## Outline\n\n1. [K3s Cluster creation](#k3s-cluster-creation)\n2. [SeaweedFS instance setup](#seaweedfs-instance-and-user-setup)\n3. [Configure scheduled backups of SeaweedFS to B2](#configure-scheduled-backups-of-seaweedfs-to-b2)\n4. [Restore SeaweedFS from B2 backups](#restore-seaweedfs-from-b2-backups)\n\n\n## Requirements\n\n- [Kubectl](https://kubernetes.io/docs/tasks/tools/)\n- [Helm](https://helm.sh/docs/intro/install/)\n- [Restic](https://restic.readthedocs.io/en/latest/020_installation.html)\n- S3 CLI tool of your choice, I'll be using [mc](https://github.com/minio/mc)\n\n\u003ch2 id=\"k3s-cluster-creation\"\u003eK3s Cluster creation\u003c/h2\u003e\n\n1. Download the k3s installer\n\n    ```bash\n    curl -sfL https://get.k3s.io \u003e k3s-install.sh\n    ```\n\n2. install k3s\n\n    ```bash\n    bash k3s-install.sh --disable=traefik\n    ```\n\n3. Wait for node to be ready\n\n    ```console\n    $ sudo k3s kubectl get node\n    NAME   STATUS   ROLES                  AGE   VERSION\n    vm0    Ready    control-plane,master   1m   v1.27.4+k3s1\n    ```\n\n4. Make an accessible version of the kubeconfig\n\n    ```bash\n    mkdir -p ~/.config/kube\n\n    sudo cp /etc/rancher/k3s/k3s.yaml ~/.config/kube/config\n\n    sudo chown $USER:$USER ~/.config/kube/config\n\n    export KUBECONFIG=~/.config/kube/config\n    ```\n\n5. Install k8up\n\n    ```bash\n    helm repo add k8up-io https://k8up-io.github.io/k8up\n    helm repo update\n\n    kubectl apply -f https://github.com/k8up-io/k8up/releases/download/k8up-4.4.3/k8up-crd.yaml\n    helm install k8up k8up-io/k8up\n    ```\n\n\n\u003ch2 id=\"seaweedfs-instance-and-user-setup\"\u003eSeaweedFS instance and user setup\u003c/h2\u003e\n\n1. install the MinIO client\n\n    Docs: https://min.io/docs/minio/linux/reference/minio-mc.html\n\n    ```bash\n    mkdir -p $HOME/minio-binaries\n\n    wget https://dl.min.io/client/mc/release/linux-amd64/mc -O $HOME/minio-binaries/mc\n\n    chmod +x $HOME/minio-binaries/mc\n\n    export PATH=$PATH:$HOME/minio-binaries/\n    ```\n\n2. Download SeaweedFS, unzip, then cd to the helm dir\n\n    ```bash\n    wget https://github.com/seaweedfs/seaweedfs/archive/refs/tags/3.77.zip\n    unzip 3.77.zip\n    cd seaweedfs-3.77/k8s/charts/seaweedfs\n    ```\n\n3. Create a minimal values file for the Seaweedfs deployment which adds annotations for K8up.\n\n    ```bash\n    /bin/cat \u003c\u003c EOF \u003e test-values.yaml\n    master:\n      enabled: true\n      data:\n        type: \"persistentVolumeClaim\"\n        size: \"10G\"\n        storageClass: \"local-path\"\n        annotations:\n          \"k8up.io/backup\": \"true\"\n      livenessProbe:\n        periodSeconds: 5\n      readinessProbe:\n        periodSeconds: 5\n\n    volume:\n      enabled: true\n      readMode: proxy\n      dataDirs:\n        - name: data\n          type: \"persistentVolumeClaim\"\n          size: \"10G\"\n          storageClass: \"local-path\"\n          annotations:\n            \"k8up.io/backup\": \"true\"\n          maxVolumes: 0\n      idx: {}\n      livenessProbe:\n        periodSeconds: 5\n      readinessProbe:\n        periodSeconds: 5\n\n    filer:\n      enabled: true\n      encryptVolumeData: true\n      enablePVC: true\n      storage: 10Gi\n      defaultReplicaPlacement: \"000\"\n      data:\n        type: \"persistentVolumeClaim\"\n        size: \"10G\"\n        storageClass: \"local-path\"\n        annotations:\n          \"k8up.io/backup\": \"true\"\n      s3:\n        enabled: true\n        enableAuth: true\n        port: 8333\n        httpsPort: 0\n        allowEmptyFolder: false\n        createBuckets:\n          - name: shared\n            anonymousRead: false\n      livenessProbe:\n        periodSeconds: 5\n      readinessProbe:\n        periodSeconds: 5\n\n    s3:\n      enabled: false\n    cosi:\n      enabled: false\n    EOF\n    ```\n\n4. Deploy via Helm (takes longer on slow drives)\n\n   ```bash\n   helm install seaweedfs . -f test-values.yaml --wait\n   ```\n\n5. Expose the filer service via a LoadBalancer (servicelb in k3s). This will let us view the admin UI as well as reach the S3 endpoint during the demo.\n\n    ```bash\n    /bin/cat \u003c\u003c EOF \u003e service.yaml\n    apiVersion: v1\n    kind: Service\n    metadata:\n      labels:\n        app.kubernetes.io/component: filer\n        app.kubernetes.io/instance: seaweedfs\n        app.kubernetes.io/name: seaweedfs\n      name: seaweedfs-filer-lb\n    spec:\n      ports:\n      - name: swfs-filer\n        port: 8888\n        protocol: TCP\n        targetPort: 8888\n      - name: swfs-filer-grpc\n        port: 18888\n        protocol: TCP\n        targetPort: 18888\n      - name: swfs-s3\n        port: 8333\n        protocol: TCP\n        targetPort: 8333\n      - name: metrics\n        port: 9327\n        protocol: TCP\n        targetPort: 9327\n      selector:\n        app.kubernetes.io/component: filer\n        app.kubernetes.io/name: seaweedfs\n      type: LoadBalancer\n    EOF\n    ```\n\n6. Export your LoadBalancer IP address as an env var\n\n   ```bash\n   export NODE_IP=\"\"\n   ```\n\n7. Create an alias for your server using your S3 CLI tool:\n\n    - You can find the `admin_access_key_id` and `admin_secret_access_key` values in the secret `seaweedfs-s3-secret`\n\n      ```bash\n      mc alias set seaweedfs http://$NODE_IP:8333 $admin_access_key_id $admin_secret_access_key\n      ```\n\n8. Create a bucket that will hold our demo data\n\n      ```bash\n      mc mb seaweedfs/backups\n      ```\n\n8. Add some data to the bucket\n\n      ```bash\n      mc cp ./some-file seaweedfs/backups/\n      ```\n\n9. Verify its there\n\n      ```bash\n      mc ls seaweedfs/backups\n      ```\n\n10. Open the Web UI at http://$NODE_IP:8888 in a browser to view or add more data.\n\n \u003ch2 id=\"configure-scheduled-backups-of-seaweedf3-to-b2\"\u003eConfigure scheduled backups of SeaweedFS to B2\u003c/h2\u003e\n\n 1. Create a secret containing your external S3 credentials\n\n    - You will need to get these from your provider (Backblaze, Wasabi etc..):\n\n      ```bash\n      export ACCESS_KEY_ID=$(echo -n \"\" | base64)\n\n      export ACCESS_SECRET_KEY=$(echo -n \"\" |base64)\n      ```\n\n      ```bash\n      /bin/cat \u003c\u003c EOF \u003e backblaze-secret.yaml\n      apiVersion: v1\n      kind: Secret\n      metadata:\n        name: backblaze-credentials\n      type: Opaque\n      data:\n        \"ACCESS_KEY_ID\": \"$ACCESS_KEY_ID\"\n        \"ACCESS_SECRET_KEY\": \"$ACCESS_SECRET_KEY\"\n      EOF\n\n      kubectl apply -f backblaze-secret.yaml\n      ```\n\n 2. Create a secret containing a random password for restic\n\n    - Generate a password.\n\n      ```bash\n      export RESTIC_PASS=\"\"\n      ```\n\n    - Create a secret manifest\n\n      ```bash\n      /bin/cat \u003c\u003c EOF \u003e restic.yaml\n      apiVersion: v1\n      kind: Secret\n      metadata:\n        name: restic-repo\n      type: Opaque\n      stringData:\n        \"password\": \"$RESTIC_PASS\"\n      EOF\n      ```\n\n    - Create the secret\n\n      ```bash\n      kubectl apply -f restic.yaml\n      ```\n\n3. Create a scheduled backup\n\n    - Export your S3 address:\n\n      ```bash\n      export BACKUP_S3_URL=\"\"\n      export BACKUP_S3_BUCKET=\"\"\n      ```\n\n    - Create a manifest for the backup\n\n      ```bash\n      /bin/cat \u003c\u003c EOF \u003e backup.yaml\n      apiVersion: k8up.io/v1\n      kind: Schedule\n      metadata:\n        name: schedule-backups\n      spec:\n        backend:\n          repoPasswordSecretRef:\n            name: restic-repo\n            key: password\n          s3:\n            endpoint: \"$BACKUP_S3_URL\"\n            bucket: \"$BACKUP_S3_BUCKET\"\n            accessKeyIDSecretRef:\n              name: backblaze-credentials\n              key: ACCESS_KEY_ID\n            secretAccessKeySecretRef:\n              name: backblaze-credentials\n              key: ACCESS_SECRET_KEY\n        backup:\n          schedule: '*/5 * * * *'\n          keepJobs: 4\n        check:\n          schedule: '0 1 * * 1'\n        prune:\n          schedule: '0 1 * * 0'\n          retention:\n            keepLast: 5\n            keepDaily: 14\n      EOF\n      ```\n\n    - Create the backup and let it run\n\n      ```bash\n      kubectl apply -f backup.yaml\n      ```\n\n\u003ch2 id=\"restore-seaweedfs-from-b2-backups\"\u003eRestore SeaweedFS from B2 backups\u003c/h2\u003e\n\n1. Uninstall SeaweedFS, delete the PVCs, secrets, and scheduled backup\n\n   ```bash\n   kubectl delete -f backup.yaml\n   helm uninstall seaweedfs\n   kubectl delete pvc data-default-seaweedfs-master-0\n   kubectl delete pvc data-filer-seaweedfs-filer-0\n   kubectl delete pvc data-seaweedfs-volume-0\n   ```\n\n2. Create PVCs to hold our restored data\n\n    - Create a manifest for the PVCs\n\n      ```bash\n      /bin/cat \u003c\u003c EOF \u003e pvc.yaml\n      ---\n      kind: PersistentVolumeClaim\n      apiVersion: v1\n      metadata:\n        name: swfs-volume-data\n        annotations:\n          \"k8up.io/backup\": \"true\"\n      spec:\n        accessModes:\n          - ReadWriteOnce\n        resources:\n          requests:\n            storage: 10Gi\n      ---\n      kind: PersistentVolumeClaim\n      apiVersion: v1\n      metadata:\n        name: swfs-master-data\n        annotations:\n          \"k8up.io/backup\": \"true\"\n      spec:\n        accessModes:\n          - ReadWriteOnce\n        resources:\n          requests:\n            storage: 10Gi\n      ---\n      kind: PersistentVolumeClaim\n      apiVersion: v1\n      metadata:\n        name: swfs-filer-data\n        annotations:\n          \"k8up.io/backup\": \"true\"\n      spec:\n        accessModes:\n          - ReadWriteOnce\n        resources:\n          requests:\n            storage: 10Gi\n      EOF\n      ```\n\n    - Create the PVCs\n\n      ```bash\n      kubectl apply -f pvc.yaml\n      ```\n\n3. Setup your restic credentials\n\n    ```bash\n    # the password used in your restic-repo secret\n    export RESTIC_PASSWORD=\"\"\n\n    # Your S3 credentials\n    export AWS_ACCESS_KEY_ID=\"\"\n    export AWS_SECRET_ACCESS_KEY=\"\"\n    export RESTIC_REPOSITORY=\"s3://$BACKUP_S3_URL/$BACKUP_S3_BUCKET\"\n    ```\n\n4. Find your desired snapshot to restore\n\n    ```console\n    $ restic snapshots\n    repository d91e9530 opened (version 2, compression level auto)\n    created new cache in /home/friend/.cache/restic\n    ID        Time                 Host        Tags        Paths\n    --------------------------------------------------------------------------------------------\n    4a25424a  2023-11-20 19:40:10  default                 /data/data-default-seaweedfs-master-0\n    649b25c7  2023-11-20 19:40:14  default                 /data/data-filer-seaweedfs-filer-0\n    99160498  2023-11-20 19:40:19  default                 /data/data-seaweedfs-volume-0\n    --------------------------------------------------------------------------------------------\n    3 snapshots\n    ```\n\n4. Use the K8up CLI or a declarative setup to restore data to the PVC. You will need to do this for each PVC that needs to be restored\n\n    - Example manifest for a S3-to-PVC restore job which uses the restic snapshots shown above.\n\n      ```bash\n      /bin/cat \u003c\u003c EOF \u003e s3-to-pvc.yaml\n      ---\n      apiVersion: k8up.io/v1\n      kind: Restore\n      metadata:\n        name: restore-volume-data\n      spec:\n        restoreMethod:\n          folder:\n            claimName: swfs-volume-data\n        snapshot: \"99160498\"\n        backend:\n          repoPasswordSecretRef:\n            name: restic-repo\n            key: password\n          s3:\n            endpoint: \"$BACKUP_S3_URL\"\n            bucket: \"$BACKUP_S3_BUCKET\"\n            accessKeyIDSecretRef:\n              name: backblaze-credentials\n              key: ACCESS_KEY_ID\n            secretAccessKeySecretRef:\n              name: backblaze-credentials\n              key: ACCESS_SECRET_KEY\n      ---\n      apiVersion: k8up.io/v1\n      kind: Restore\n      metadata:\n        name: restore-master-data\n      spec:\n        restoreMethod:\n          folder:\n            claimName: swfs-master-data\n        snapshot: \"4a25424a\"\n        backend:\n          repoPasswordSecretRef:\n            name: restic-repo\n            key: password\n          s3:\n            endpoint: \"$BACKUP_S3_URL\"\n            bucket: \"$BACKUP_S3_BUCKET\"\n            accessKeyIDSecretRef:\n              name: backblaze-credentials\n              key: ACCESS_KEY_ID\n            secretAccessKeySecretRef:\n              name: backblaze-credentials\n              key: ACCESS_SECRET_KEY\n      ---\n      apiVersion: k8up.io/v1\n      kind: Restore\n      metadata:\n        name: restore-filer-data\n      spec:\n        restoreMethod:\n          folder:\n            claimName: swfs-filer-data\n        snapshot: \"649b25c7\"\n        backend:\n          repoPasswordSecretRef:\n            name: restic-repo\n            key: password\n          s3:\n            endpoint: \"$BACKUP_S3_URL\"\n            bucket: \"$BACKUP_S3_BUCKET\"\n            accessKeyIDSecretRef:\n              name: backblaze-credentials\n              key: ACCESS_KEY_ID\n            secretAccessKeySecretRef:\n              name: backblaze-credentials\n              key: ACCESS_SECRET_KEY\n      EOF\n      ```\n\n    - Apply manifest\n      ```bash\n      kubectl apply -f s3-to-pvc.yaml\n      ```\n\n5. Re-deploy Seaweedfs from the existing PVCs\n\n   - Create a manifest that targets the PVCs we created\n\n      ```bash\n      /bin/cat \u003c\u003c EOF \u003e restore-values.yaml\n      master:\n        enabled: true\n        data:\n          type: \"existingClaim\"\n          claimName: \"swfs-master-data\"\n        livenessProbe:\n          periodSeconds: 5\n        readinessProbe:\n          periodSeconds: 5\n\n      volume:\n        enabled: true\n        readMode: proxy\n        dataDirs:\n          - name: data\n            type: \"existingClaim\"\n            claimName: \"swfs-volume-data\"\n            maxVolumes: 0\n        idx: {}\n        livenessProbe:\n          periodSeconds: 5\n        readinessProbe:\n          periodSeconds: 5\n\n      filer:\n        enabled: true\n        encryptVolumeData: true\n        enablePVC: true\n        storage: 10Gi\n        defaultReplicaPlacement: \"000\"\n        data:\n          type: \"existingClaim\"\n          claimName: \"swfs-filer-data\"\n        s3:\n          enabled: true\n          enableAuth: false\n          port: 8333\n          httpsPort: 0\n          allowEmptyFolder: false\n        livenessProbe:\n          periodSeconds: 5\n        readinessProbe:\n          periodSeconds: 5\n\n      s3:\n        enabled: false\n      cosi:\n        enabled: false\n      EOF\n      ```\n\n    - Deploy via Helm\n\n      ```bash\n      helm install seaweedfs . -f restore-values.yaml --wait\n      ```\n\n7. Update your alias for your server:\n\n    - get the `admin_access_key_id` and `admin_secret_access_key` from the secret `seaweedfs-s3-secret`\n\n      ```bash\n      mc alias set seaweedfs http://$NODE_IP:30000 $admin_access_key_id $admin_secret_access_key\n      ```\n\n    - View for your data:\n\n      ```bash\n      mc ls seaweedfs\n      ```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloudymax%2Fswfs-lab","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcloudymax%2Fswfs-lab","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcloudymax%2Fswfs-lab/lists"}