Sauvegarde et restauration ClickHouse
Fonctionnement de la sauvegarde ClickHouse
Un sidecar de sauvegarde s'exécute aux côtés de ClickHouse, surveillant en continu les changements et réalisant automatiquement des sauvegardes complètes et incrémentales planifiées : il n'y a pas de tâche cron distincte à configurer.
Les sauvegardes sont :
- Cohérentes au niveau applicatif : réalisées via le mécanisme natif
FREEZEde ClickHouse (hardlinks sans copie), et non un snapshot brut de volume, de sorte qu'une sauvegarde ne capture jamais une table en cours d'écriture. - Écrites dans votre object storage : à la fois les métadonnées/manifests et les blobs de données sous-jacents sont copiés dans votre bucket configuré, sous le préfixe de chemin que vous configurez ci-dessous.
- Dédupliquées et conscientes des chaînes : les sauvegardes incrémentales ne stockent que ce qui a changé ; les politiques de rétention suppriment des chaînes de sauvegarde entières plutôt que de laisser des incréments orphelins.
FREEZE fonctionne en créant des liens physiques (hard links) vers les données de table en direct, et non des copies. Les permissions, la propriété et les attributs d'un lien physique sont une propriété des données sous-jacentes elles-mêmes : ils sont partagés entre tous les liens physiques qui pointent vers elles. Modifier les permissions d'un fichier sous le chemin de sauvegarde les modifie aussi sur les données en direct que ClickHouse est en train de lire et d'écrire activement, ce qui peut entraîner une corruption des données. Ne touchez jamais manuellement aux permissions à cet endroit ; laissez ClickHouse et le sidecar de sauvegarde gérer ce chemin de manière exclusive.
Configuration du sidecar de sauvegarde
Un sidecar de sauvegarde est configuré par défaut sur le pod ClickHouse, contrôlé par clickhouse.backup.enabled (true par défaut). Il exécute clickhouse-backup en mode server --watch, qui auto-planifie les sauvegardes complètes et incrémentales dans le processus ; il n'y a pas de tâche cron distincte, ni rien d'autre à déployer. Ne définissez clickhouse.backup.enabled: false que si vous avez l'intention de gérer entièrement le sidecar vous-même via clickhouse.sidecars à la place.
Destination de l'object storage
La destination de sauvegarde n'a aucune valeur par défaut : vous devez la configurer avant que les sauvegardes puissent s'exécuter. Elle est déclarée sous clickhouse.backup.objectStorage, avec la même structure et le même vocabulaire que le bucket de données propre à ClickHouse sous clickhouse.objectStorage (voir Backends d'object storage) : un provider plus un bloc de fournisseur portant le bucket/conteneur, un prefix facultatif, et soit credentialless: true (workload identity, sans clé statique) soit une référence existingSecret. Les identifiants ne vivent jamais que dans un secret Kubernetes que vous créez séparément, jamais en ligne dans votre fichier values (le même modèle décrit dans Prérequis) ; le chart refuse les clés d'identifiants dans extraVars.
Le chart dérive les deux chemins de stockage à partir de l'unique prefix : les manifests de sauvegarde arrivent sous <prefix>/metadata et les copies des blobs de données du disque objet sous <prefix>/objects.
Seule la structure est partagée avec le bucket de données, jamais les valeurs : utilisez un bucket véritablement distinct (voir Exigences du bucket). Le chart l'impose et fait échouer le déploiement si la sauvegarde cible le bucket ou le conteneur contenant les données qu'elle protège. Choisissez votre fournisseur ci-dessous.
- AWS S3
- Azure Blob Storage
- GCS
- IBM Cloud Object Storage
- MinIO
clickhouse:
backup:
objectStorage:
provider: s3
s3:
bucket: '<bucket-name>'
region: '<region>'
prefix: 'backups'
credentialless: true
Bucket cible : s3.bucket. Aucune entrée d'identifiant nécessaire avec credentialless: true : avec IRSA (voir Backends d'object storage), le token du ServiceAccount est automatiquement monté dans chaque conteneur du pod, y compris ce sidecar. Avec des clés statiques à la place, définissez credentialless: false et faites pointer existingSecret vers un secret portant les clés access-key-id et secret-access-key (la même forme que le secret du bucket de données, avec ses propres identifiants distincts).
Avec Microsoft Entra Workload ID configuré pour le disque de stockage (voir Backends d'object storage), le sidecar en hérite gratuitement : le webhook mutant d'AKS injecte les variables d'environnement du token fédéré et le volume de token projeté dans chaque conteneur du pod par défaut, pas seulement dans celui de ClickHouse : aucune configuration distincte nécessaire. Ajoutez simplement :
clickhouse:
backup:
objectStorage:
provider: azblob
azblob:
storageAccountName: '<storage-account-name>'
containerName: '<container-name>'
prefix: 'backups'
credentialless: true
Bucket cible : azblob.storageAccountName + azblob.containerName (le compte est passé par son nom ici, contrairement au storageAccountUrl du bucket de données : le sidecar et le disque ClickHouse le prennent sous des formes différentes). Aucune entrée d'identifiant nécessaire avec credentialless: true. Assurez-vous que le rôle RBAC accordé à l'identité managée (voir Backends d'object storage) couvre aussi le conteneur de sauvegarde, s'il est différent de celui de ClickHouse. Avec une clé de compte statique à la place, définissez credentialless: false et faites pointer existingSecret vers un secret portant une clé account-key (un secret ayant la forme de celui du bucket de données, avec sa clé supplémentaire account-name, fonctionne tel quel : seule account-key est lue).
clickhouse:
backup:
objectStorage:
provider: gcs
gcs:
bucket: '<bucket-name>'
prefix: 'backups'
existingSecret: 'clickhouse-backup-gcs-credentials'
Le client GCS du sidecar de sauvegarde n'accepte pas les clés HMAC (contrairement au disque propre à ClickHouse, voir Backends d'object storage) : il a besoin de sa propre clé JSON native de compte de service, passée sous forme de secret Kubernetes plutôt que définie en ligne dans values, le même modèle décrit dans Prérequis : créez-la directement, ou synchronisez-la depuis votre propre coffre de secrets via un SecretStore/ExternalSecret de l'External Secrets Operator ou un Vault Secrets Operator.
Quelle que soit la manière dont vous le renseignez, le secret (n'importe quel nom, référencé par existingSecret) doit contenir une clé credentials-json avec le contenu du fichier de clé JSON. Si vous le créez directement :
kubectl create secret generic clickhouse-backup-gcs-credentials \
--namespace <namespace> \
--from-file=credentials-json=<path-to-service-account-key.json>
Bucket cible : gcs.bucket. Nécessite l'identifiant ci-dessus.
Le client GCS de clickhouse-backup utilise l'API native de GCS et sa chaîne d'Application Default Credentials, de sorte que gcs.credentialless: true peut s'authentifier via GKE Workload Identity Federation, sans clé JSON.
Ce chemin provient de la documentation propre à clickhouse-backup (son client GCS prend en charge les identifiants par défaut) et de la documentation Workload Identity Federation de GCP, mais n'a pas été testé de bout en bout sur un véritable cluster GKE. Validez-le vous-même avant de vous y fier en production.
Cela nécessite une configuration de Workload Identity Federation spécifique au sidecar de sauvegarde (distincte du disque propre à ClickHouse, qui a toujours besoin de la paire de clés HMAC statiques quoi qu'il arrive) :
- Workload Identity Federation activée sur votre cluster GKE.
- Un ServiceAccount Kubernetes dédié, annoté avec l'e-mail d'un compte de service GCP.
- Une liaison de politique IAM permettant à ce ServiceAccount d'usurper l'identité du compte de service GCP.
- Un rôle de stockage accordé au compte de service GCP, limité au bucket.
Activer Workload Identity Federation (si ce n'est pas déjà activé sur le cluster) :
gcloud container clusters update <cluster-name> \
--zone <zone> \
--workload-pool=<project-id>.svc.id.goog
Créer le compte de service GCP :
gcloud iam service-accounts create clickhouse-backup --project=<project-id>
Configuration du chart : annotez le ServiceAccount ClickHouse pour qu'il puisse usurper l'identité du compte de service GCP :
clickhouse:
serviceAccount:
annotations:
iam.gke.io/gcp-service-account: clickhouse-backup@<project-id>.iam.gserviceaccount.com
Liaison de politique IAM : autorisez le ServiceAccount Kubernetes à usurper l'identité du compte de service GCP (remplacez <namespace>) :
gcloud iam service-accounts add-iam-policy-binding \
clickhouse-backup@<project-id>.iam.gserviceaccount.com \
--role roles/iam.workloadIdentityUser \
--member "serviceAccount:<project-id>.svc.id.goog[<namespace>/clickhouse]"
Rôle de stockage : accordez au compte de service GCP l'accès au bucket :
gcloud storage buckets add-iam-policy-binding gs://<bucket-name> \
--member=serviceAccount:clickhouse-backup@<project-id>.iam.gserviceaccount.com \
--role=roles/storage.objectAdmin
Une fois cela en place :
clickhouse:
backup:
objectStorage:
provider: gcs
gcs:
bucket: '<bucket-name>'
prefix: 'backups'
credentialless: true
Bucket cible : gcs.bucket. Aucune clé JSON à créer ou stocker. Pour usurper explicitement l'identité d'un compte de service GCP spécifique plutôt que de vous appuyer sur la chaîne d'Application Default Credentials, ajoutez GCS_SA_EMAIL: '<gcp-sa-email>' sous clickhouse.backup.config.extraVars.
clickhouse-backup atteint IBM Cloud Object Storage via son type de stockage distant s3, sur le endpoint direct COS avec adressage de type chemin (path-style) et une paire de clés HMAC statiques :
clickhouse:
backup:
objectStorage:
provider: s3
s3:
endpoint: 'https://s3.direct.<region>.cloud-object-storage.appdomain.cloud'
bucket: '<backup-bucket-name>'
region: '<region>-standard' # the bucket's storage class code, for example eu-de-standard
prefix: 'backups'
forcePathStyle: true # required whenever a custom endpoint is set
existingSecret: 'clickhouse-backup-cos-credentials'
Les identifiants sont passés sous forme de secret Kubernetes, jamais en ligne dans votre fichier values (le même modèle décrit dans Prérequis). Quelle que soit la manière dont vous le renseignez, le secret (n'importe quel nom, référencé par existingSecret) doit contenir les clés access-key-id et secret-access-key :
kubectl create secret generic clickhouse-backup-cos-credentials \
--namespace <namespace> \
--from-literal=access-key-id=<cos-hmac-access-key-id> \
--from-literal=secret-access-key=<cos-hmac-secret-access-key>
Bucket cible : s3.bucket, un bucket dédié aux sauvegardes, créé au préalable avec un identifiant de service HMAC limité à Writer comme le bucket de données. Nécessite l'identifiant ci-dessus.
clickhouse-backup communique aussi avec MinIO via son type de stockage distant s3 (comme AWS S3), simplement pointé vers votre endpoint MinIO avec un adressage de type chemin (path-style) et une paire access-key/secret-key statiques, puisque MinIO n'a pas d'identité équivalente à IAM avec laquelle se fédérer :
clickhouse:
backup:
objectStorage:
provider: s3
s3:
endpoint: 'http://<minio-host>:<minio-port>'
bucket: '<bucket-name>'
region: 'us-east-1' # required by the S3 client even though MinIO ignores it
prefix: 'backups'
forcePathStyle: true # required whenever a custom endpoint is set
existingSecret: 'clickhouse-backup-minio-credentials'
config:
extraVars:
S3_DISABLE_SSL: 'true' # omit and use an https:// endpoint above instead if MinIO terminates TLS
Les identifiants sont passés sous forme de secret Kubernetes, jamais en ligne dans votre fichier values (le même modèle décrit dans Prérequis). Quelle que soit la manière dont vous le renseignez, le secret (n'importe quel nom, référencé par existingSecret) doit contenir les clés access-key-id et secret-access-key :
kubectl create secret generic clickhouse-backup-minio-credentials \
--namespace <namespace> \
--from-literal=access-key-id=<minio-access-key> \
--from-literal=secret-access-key=<minio-secret-key>
Bucket cible : s3.bucket. Nécessite l'identifiant ci-dessus.
Politiques de cycle de vie du bucket
Les règles de cycle de vie et les protections de bucket pour les deux buckets sont documentées ensemble dans Politiques de cycle de vie du bucket : voir la sous-section Backup bucket pour celui-ci (versioning, Object Lock, réplication, mises en garde sur le tier archive) et la sous-section ClickHouse data bucket pour le bucket de données propre à ClickHouse. Les deux ont besoin de règles différentes, par endroits opposées.
Paramètres ajustables
D'autres paramètres du sidecar disposent d'un champ typé sous clickhouse.backup.config. Ne surchargez que ceux dont vous avez besoin :
clickhouse:
backup:
config:
backupsToKeepRemote: 30 # only overriding retention here
| Paramètre | Objectif | Valeur par défaut |
|---|---|---|
watchSchedules | Planning des sauvegardes complètes/incrémentales piloté par cron, voir Comprendre le planning de sauvegarde ci-dessous. | name=ch-bkp,full=0 2 * * 0,increment=0 2 * * *,full_type=rebase,delete_previous_cycle=false |
backupsToKeepRemote | Nombre de sauvegardes individuelles à conserver dans l'object storage : chaque sauvegarde complète et chaque incrément compte séparément, voir Comprendre le planning de sauvegarde ci-dessous. | 7 |
rebaseBeforeRemoveOldRemote | Sans cela, backupsToKeepRemote n'est pas un plafond strict : avec les chaînes incrémentales, clickhouse-backup ne supprimera pas une chaîne dont un incrément plus récent dépend encore, de sorte que le nombre conservé peut dépasser la limite. Cela rebase le plus ancien incrément encore dans la fenêtre de rétention sur une sauvegarde complète autonome, le rendant indépendant de la chaîne plus ancienne qui le précède, laquelle peut ensuite être supprimée, maintenant une rétention stricte. | true |
uploadConcurrency | Tables téléversées en parallèle pendant une sauvegarde, et (même valeur) fichiers par table téléversés en parallèle. | 2 |
downloadConcurrency | Identique à uploadConcurrency, pour la restauration. | 2 |
s3Concurrency | Chunks parallèles pour le transfert multipart S3 d'un même fichier, un niveau plus fin que uploadConcurrency/downloadConcurrency. | 2 |
rebaseConcurrency | Tables traitées en parallèle pendant une opération rebase (voir full_type=rebase ci-dessous). | 2 |
logLevel | Verbosité des logs du sidecar. | info |
allowEmptyBackups | Sans cela, une nouvelle installation sans encore aucune table est traitée comme une erreur fatale, provoquant un crash-loop du sidecar. | true |
Les paramètres de concurrence utilisent par défaut une valeur fixe et prudente plutôt que l'auto-détection propre à clickhouse-backup, qui se dimensionne sur le nombre total de CPU du nœud plutôt que sur la limite de CPU réelle de ce conteneur : augmentez-les si vous avez donné plus de CPU au sidecar et souhaitez des sauvegardes et restaurations plus rapides.
Tout autre paramètre non sensible de clickhouse-backup va sous clickhouse.backup.config.extraVars, sous son nom natif en SCREAMING_CASE ; voir la référence de configuration propre à clickhouse-backup pour la liste exhaustive. Le chart rejette une clé extraVars qui entre en conflit avec un champ typé ci-dessus (par exemple extraVars.BACKUPS_TO_KEEP_REMOTE) ou avec un champ appartenant à clickhouse.backup.objectStorage (par exemple extraVars.S3_BUCKET) : définissez plutôt le champ dédié. Il rejette aussi purement et simplement les clés d'identifiants (S3_ACCESS_KEY, GCS_CREDENTIALS_JSON, AZBLOB_ACCOUNT_KEY, ...) : extraVars est rendu dans un ConfigMap, pas un secret, de sorte que les identifiants ne passent jamais que par existingSecret (ou votre propre secret via clickhouse.backup.sidecar.extraEnvVarsSecret).
Comprendre le planning de sauvegarde
WATCH_SCHEDULES pilote la cadence de sauvegarde avec des expressions cron. Un planning a la forme :
name=<name>,full=<cron>[,increment=<cron>][,full_type=create|rebase][,delete_previous_cycle=true|false]
| Clé | Requise | Signification |
|---|---|---|
name | Oui | Préfixe pour les noms de sauvegarde de cette chaîne. Vous permet d'exécuter plusieurs plannings indépendants côte à côte, séparés par ;. |
full | Oui | Expression cron pour la sauvegarde complète. |
increment | Non | Expression cron pour la sauvegarde incrémentale. Omettez pour des sauvegardes complètes uniquement. |
full_type | Non (create) | create re-téléverse toutes les données pour chaque sauvegarde complète. rebase effectue à la place une copie côté serveur de la chaîne précédente (pas de re-téléversement, bien plus rapide), mais seulement une fois qu'une sauvegarde précédente existe déjà ; le tout premier cycle retombe toujours sur create. |
delete_previous_cycle | Non (false) | true supprime toutes les sauvegardes plus anciennes de cette chaîne dès qu'une nouvelle sauvegarde complète réussit, ne conservant que le cycle courant. false laisse les cycles plus anciens en place, élagués uniquement par backupsToKeepRemote ci-dessus. |
Les expressions cron acceptent la syntaxe standard à 5 champs (minute, heure, jour, mois, jour de la semaine), un champ optionnel de secondes en tête, et les descripteurs de type @every/@daily.
La valeur par défaut du chart :
clickhouse:
backup:
config:
watchSchedules: name=ch-bkp,full=0 2 * * 0,increment=0 2 * * *,full_type=rebase,delete_previous_cycle=false
Cela exécute une sauvegarde complète chaque dimanche à 2h du matin (via rebase une fois qu'une précédente existe, donc sans re-téléversement) et une sauvegarde incrémentale chaque jour à 2h du matin. Le dimanche, les expressions cron full et increment se déclenchent au même tick : la sauvegarde complète a toujours la priorité, de sorte qu'aucun incrément distinct ne s'exécute ce jour-là.
Fenêtre de rétention : avec delete_previous_cycle: false, backupsToKeepRemote conserve les N sauvegardes les plus récentes, comptant chaque sauvegarde complète et chaque incrément comme sa propre entrée. À 7 entrées par semaine (1 complète + 6 incréments), backupsToKeepRemote: 7 conserve environ 1 semaine d'historique.
Fraîcheur des sauvegardes
La fraîcheur des sauvegardes est exposée dans le produit à l'aide des tables système propres à ClickHouse. Considérez une sauvegarde comme non saine si aucune sauvegarde complète valide n'existe dans votre objectif de point de reprise, ou si aucune nouvelle sauvegarde n'a été réalisée récemment.
La boucle de surveillance du sidecar réessaie une sauvegarde échouée au tick planifié suivant au lieu de s'arrêter : un échec persistant (mauvais identifiants, un bucket inaccessible, un disque plein) enregistre une erreur à chaque tentative mais ne fait jamais planter le conteneur. Le statut du pod et le compteur de redémarrages restent au vert tout du long. Voir Surveillance et alerting ClickHouse pour mettre en place un signal externe qui, lui, détecte cela.
Restauration
Restaurer les données ClickHouse est une opération manuelle et délibérée : elle n'est pas déclenchée automatiquement. clickhouse-backup restaure sur place, sur la même instance ClickHouse aux côtés de laquelle le sidecar s'exécute : il a besoin d'un accès direct au système de fichiers pour attacher les data parts de la sauvegarde, la même exigence qui le maintient déployé en tant que sidecar en premier lieu (voir Fonctionnement de la sauvegarde ClickHouse ci-dessus). Il n'existe pas de workflow de restauration dans une instance vide distincte sans monter tout un second déploiement ClickHouse.
Procédure de haut niveau :
- Faites d'abord un snapshot de l'état actuel :
clickhouse-backup create_remotesur la ou les tables affectées, même si ces données sont déjà dégradées. Cela vous donne un repli distinct de votre chaîne de sauvegarde habituelle si la restauration elle-même tourne mal. - Identifiez la sauvegarde à partir de laquelle restaurer : listez les sauvegardes distantes, vérifiez qu'elle n'est pas signalée comme corrompue, et que sa chaîne de sauvegarde est intacte.
- Vérifiez la compatibilité des versions avant de vous engager dans une restauration complète :
clickhouse-backup download --schema <backup_name>ne récupère que le schéma et les métadonnées (léger, sans les données de table), afin que vous puissiez inspecterclickhouse_versiondans lemetadata.jsonrésultant par rapport à la version de ClickHouse actuellement en cours d'exécution. - Arrêtez les écritures vers la ou les tables affectées : mettez en pause ou réduisez l'échelle des producteurs qui y écrivent.
- Restaurez d'abord sous un nom différent (
--restore-database-mapping/--restore-table-mapping), jamais directement dans la table en direct. - Vérifiez la copie restaurée (nombre de lignes, contrôle ponctuel de valeurs connues) avant de toucher à la production.
- Basculez : une fois que la copie mappée est validée, restaurez à nouveau directement sur le nom de la table en direct avec
--rm(restore/restore_remote --rm <backup_name>) :clickhouse-backupsupprime le schéma existant et le rattache depuis la sauvegarde.
Pour simplement tester qu'une sauvegarde fonctionne (et non répondre à un incident réel), restaurez plutôt sous un nom de table ou de base de données jetable, afin qu'un exercice ne touche jamais aux données de production.
Si vous réessayez une restauration en utilisant le même nom de sauvegarde après une tentative échouée ou interrompue, l'outil de restauration peut reprendre à partir d'un état local obsolète et ne restaurer que le schéma (DDL), en sautant silencieusement les données réelles, sans erreur. Si une restauration semble vide ou incomplète, effacez l'état local de l'outil avant de réessayer, ou utilisez une cible distincte à chaque fois.
Portée de la reprise après sinistre
Certaines fonctionnalités qui utilisent ClickHouse joignent aussi ces données à des informations stockées dans PostgreSQL (par exemple, un inventaire des machines qui appartiennent à votre organisation). Restaurer ClickHouse seul, dans un environnement dont PostgreSQL diffère, produit des résultats incohérents dans le produit : les données ClickHouse et le registre PostgreSQL sont en désaccord l'un avec l'autre.
Une reprise après sinistre complète pour ces fonctionnalités nécessite de restaurer à la fois ClickHouse et PostgreSQL à un point cohérent dans le temps. Planifiez votre stratégie de sauvegarde PostgreSQL (voir Sauvegarde et restauration) et votre stratégie de sauvegarde ClickHouse ensemble, et testez une restauration combinée dans le cadre de vos exercices de reprise après sinistre.
Objectifs de reprise
Nous ne publions pas actuellement d'objectif de point de reprise (RPO) ni d'objectif de temps de reprise (RTO) fixes pour ClickHouse : ceux-ci dépendent de votre cadence de sauvegarde et de votre volume de données. Nous recommandons d'exécuter un exercice de restauration sur votre propre volume de données pour mesurer votre RTO réel, et de régler votre cadence de sauvegarde (fréquence des sauvegardes complètes et incrémentales) pour correspondre à la fenêtre de perte de données que vous êtes prêt à accepter.