Skip to content

Commit b312cbe

Browse files
committed
rename worker to exporter, add importer, rename templates & clear README
1 parent 96101cf commit b312cbe

7 files changed

Lines changed: 102 additions & 82 deletions

File tree

README.md

Lines changed: 62 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,98 +1,86 @@
1-
# ClusterSnap - Cloud-Agnostic Kubernetes Backup Tool
1+
# ClusterSnap
2+
**Cloud-Agnostic Kubernetes Backup Tool**
23

3-
> ClusterSnap runs as a Kubernetes CronJob, zips PVC data from a shared volume,
4-
> uploads the archive to any cloud storage backend (AWS S3, MinIO, SeaweedFS, or
5-
> Azure Blob Storage), and pushes structured telemetry to Grafana Loki and
6-
> Prometheus - all from a single, provider-agnostic codebase.
4+
> Clustersnap is a lightweight, cloud-agnostic backup tool that safely secures Kubernetes volumes into remote storage like Azure Blob Storage or AWS S3.
5+
6+
7+
* **Export** Kubernetes data to remote storage.
8+
* **Import** backed-up data to Kubernetes.
9+
* **Backups Isolation:** Each Kubernetes namespace is backed up into a dedicated space.
10+
* **Ensure data Integrity:** Scale-down running workload, backup their volumes and restore the workload.
11+
* **Cloud-Native & Minimalist:** Light footprint, easy to install, and fully customizable from standard Kubernetes manifests without third-party CRD dependencies.
12+
13+
14+
## Requirements
15+
* Remote storage (Azure Blob Storage, S3 etc...), with credentials
16+
* Working Kubernetes cluster
717

8-
- 🗜️ **Single ZIP archive** per backup run using Python's built-in `zipfile`
9-
- ☁️ **Provider-agnostic** storage layer - swap S3 for Azure with one env var
10-
- 📋 **JSON-structured logs** parsed natively by Grafana Loki
11-
- 🛡️ **ZIP path-traversal protection** during restore
12-
- 🔒 **Dual Azure auth** - shared-key or service-principal
13-
- 📊 **Prometheus metrics** pushed to Pushgateway after every run
1418

1519
## Installation
16-
### Install from sources
17-
* create venv
18-
```
19-
python -m .venv
20-
source .venv/bin/activate
21-
```
22-
23-
* install dependencies
24-
```
25-
pip install --upgrade pip
26-
pip install -r requirements.txt
27-
```
28-
29-
* create `.env` file
30-
* AWS/S3
20+
* install CLI
21+
* create venv
3122
```
32-
tofill
23+
python -m .venv
24+
source .venv/bin/activate
3325
```
3426
35-
* Azure Blob Storage (shared-key)
27+
* install dependencies
3628
```
37-
tofill
29+
pip install --upgrade pip
30+
pip install -r requirements.txt
3831
```
3932
40-
* run
41-
```
42-
python -m src.main
43-
```
44-
45-
<!-- ## Install locally with Docker
46-
* build
47-
```
48-
docker build . --tag clustersnap:local
49-
```
50-
51-
* run
52-
```
53-
docker run clustersnap:local
54-
``` -->
55-
56-
## Install on Kubernetes (pod)
57-
* create secret to pull image
58-
```
59-
kubectl create secret docker-registry registry-auth-clustersnap \
60-
--docker-server=ghcr.io \
61-
--docker-username=GITHUB_ACCOUNT \
62-
--docker-password=GITHUB_PAT \
63-
--docker-email=GITHUB_ACCOUNT
64-
```
65-
* create secret for remote storage access
66-
* edit `src/templates/backup.secret.yaml` with your own config
67-
* apply
33+
* create required Kubernetes objects
34+
* `pullImage` secret
35+
```
36+
kubectl create secret docker-registry registry-auth-clustersnap \
37+
--docker-server=ghcr.io \
38+
--docker-username=GITHUB_ACCOUNT \
39+
--docker-password=GITHUB_PAT \
40+
--docker-email=GITHUB_ACCOUNT
41+
```
42+
43+
* `config` Secret
44+
* edit `src/templates/backup.secret.yaml` with your own config
45+
* apply
46+
```
47+
NAMESPACE=YOUR_NAMESPACE envsubst < templates/backup.secret.yaml | kubectl apply -f -
48+
```
49+
50+
* `backup-map` ConfigMap
51+
> Note: this ConfigMap is used to orchestrate the backup. \
52+
> You can manually edit it to customize the backup behaviors, the command below is just a quick way to create it.
6853
```
69-
NAMESPACE=tenant-test0 envsubst < templates/backup.secret.yaml | kubectl apply -f -
54+
python -m src.master -c KUBECTL_CONTEXT -rm
7055
```
71-
* create pod
72-
```
73-
NAMESPACE=tenant-test0 envsubst < tests/resources_test.yaml | kubectl apply -f -
74-
```
7556
7657
## Usage
77-
* local
58+
> Ensure required secrets & configmap are created
59+
### Kubernetes
60+
* create pod (& required resources such as RBAC etc...)
7861
```
79-
python -m src.main --c CONTEXT -n NAMESPACE
62+
NAMESPACE=YOUR_NAMESPACE envsubst < tests/resources_test.yaml | kubectl apply -f -
8063
```
64+
* wait for it to end...
8165
82-
* docker/kubernetes
83-
just run the image
66+
### Local CLI
67+
* backup cluster-wide
68+
```
69+
python -m src.master -c KUBECTL_CONTEXT
70+
```
71+
72+
* backup given namespace
73+
```
74+
python -m src.master -c KUBECTL_CONTEXT -n NAMESPACE
75+
```
8476
8577
8678
## Developpers
8779
### Architecture
88-
```
89-
clustersnap/
90-
├── src/
91-
│ ├── main.py # Entrypoint for CLI & Kubernetes main pod
92-
│ ├── worker.py # Entrypoint for Kubernetes job pods
93-
│ └── helpers/
94-
│ ├── kubernetes.py # Kubernetes class
95-
```
80+
* `master.py` => entrypoint of the master pod (orchestrate backup jobs, scaling replicas etc...)
81+
* `exporter.py` => entrypoint of the exporter pod (do the backup = dump persisted storage & sync it to remote storage)
82+
* `importer.py` => entrypoint of the importer pod (import backups from remote storage to Kubernetes)
83+
9684
9785
### Workflow of export
9886
```
File renamed without changes.

src/importer.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# todo

src/kubernetes/job.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ def create_backup_job(kube, pvc_name: str, mounted_path: str, node_name: str) ->
2525
print(f"starting backup job: {job_name}")
2626

2727
project_root = Path(__file__).resolve().parents[2]
28-
template_path = f"{project_root}/templates/backup.job.yaml"
28+
template_path = f"{project_root}/templates/exporter.job.yaml"
2929
try:
3030
with open(template_path, "r") as f:
3131
yaml_content = f.read()

src/utils/backup_map.py

Lines changed: 37 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,32 +16,62 @@ def get_backup_map(kube) -> dict:
1616

1717

1818
def backup_map_scanner(kube, target_namespaces: list) -> dict:
19-
"""Scan the cluster to get a YAML map of what will be backuped."""
19+
"""Scan the cluster to get a YAML map to orchestrate the backups."""
2020
map_data = {
2121
"cluster": kube.get_cluster_name(),
2222
"namespaces": []
2323
}
2424

2525
print(f"cluster: {kube.get_cluster_name()}")
2626
print(f"namespaces:")
27+
original_ns = kube.namespace
2728

2829
for ns in target_namespaces:
2930
kube.namespace = ns
30-
print(f"- name: {kube.get_namespace()}")
31+
current_ns = kube.get_namespace()
32+
print(f"- name: {current_ns}")
3133

3234
ns_data = {
33-
"name": kube.get_namespace(),
35+
"name": current_ns,
36+
"workloads": [], # <--- Nouvelle section pour conserver l'état des replicas
3437
"claims": []
3538
}
39+
40+
workload_list = set()
3641

37-
pvcs = kube.list_pvc()
38-
if not pvcs:
42+
pvc_list = kube.list_pvc()
43+
if not pvc_list:
3944
print(f" claims: []")
4045
map_data["namespaces"].append(ns_data)
4146
continue
4247

48+
# Collect workload replicas
49+
for pvc in pvc_list:
50+
mounts = kube.get_pvc(pvc['name'])
51+
if mounts:
52+
for m in mounts:
53+
resource_str = m['resource'] # ex: "StatefulSet/keycloak-postgresql"
54+
if resource_str not in workload_list:
55+
workload_list.add(resource_str)
56+
resource_type, resource_name = resource_str.split('/')
57+
58+
# Récupération live du nombre de replicas via ton wrapper kube
59+
replicas = kube.get_replicas(resource_type, resource_name)
60+
61+
ns_data["workloads"].append({
62+
"resource": resource_str,
63+
"replicas": replicas
64+
})
65+
66+
print(f" workloads:")
67+
for wl in ns_data["workloads"]:
68+
print(f" - resource: {wl['resource']}")
69+
print(f" replicas: {wl['replicas']}")
70+
71+
72+
# Collect PVC
4373
print(f" claims:")
44-
for pvc in pvcs:
74+
for pvc in pvc_list:
4575
print(f" - name: {pvc['name']}")
4676
print(f" status: {pvc['status']}")
4777
print(f" capacity: {pvc['capacity']}")
@@ -79,4 +109,5 @@ def backup_map_scanner(kube, target_namespaces: list) -> dict:
79109
ns_data["claims"].append(pvc_data)
80110
map_data["namespaces"].append(ns_data)
81111

112+
kube.namespace = original_ns
82113
return map_data
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ spec:
2222
- name: backup-worker
2323
image: ghcr.io/cosmo-tech/clustersnap:latest
2424
imagePullPolicy: Always
25-
command: ["python", "-m", "src.worker"]
25+
command: ["python", "-m", "src.exporter"]
2626
args: ["--pvc", "{{ PVC_NAME }}", "--path", "{{ MOUNTED_PATH }}"]
2727
resources:
2828
requests:

0 commit comments

Comments
 (0)