Skip to content

Commit 86f5267

Browse files
authored
feat(platform): implement backup strategy for clickhouse app (#2598)
<!-- Thank you for making a contribution! Here are some tips for you: - Use Conventional Commits for the PR title: `type(scope): description` - Types: feat, fix, docs, style, refactor, perf, test, build, ci, chore - Scopes are not an exhaustive list — pick the most specific scope for the change and extend the list when a genuinely new area appears. Examples: - System components: dashboard, platform, operator, cilium, kube-ovn, linstor, fluxcd, cluster-api - Managed apps: postgres, mariadb, redis, kafka, clickhouse, virtual-machine, kubernetes - Development and maintenance: api, hack, tests, ci, docs, maintenance - Breaking changes: append `!` after type/scope (`feat(api)!: ...`) or add a `BREAKING CHANGE:` footer - If it's a work in progress, consider creating this PR as a draft. - Don't hesistate to ask for opinion and review in the community chats, even if it's still a draft. - Add the label `backport` if it's a bugfix that needs to be backported to a previous version. --> ## What this PR does ### Screenshots <!-- REQUIRED for UI changes: attach screenshots or screen recordings demonstrating the visual impact of your changes. PRs with UI changes without screenshots will not be merged. --> ### Release note <!-- Write a release note: - Explain what has changed internally and for users. - Start with the same `type(scope):` prefix as in the PR title - Follow the guidelines at https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md. --> ```release-note ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Altinity-based ClickHouse backup/restore flow: cluster-scoped strategy, injected sidecar when enabled, optional referencing of existing S3 credential Secrets with per-key mapping, s3PathOverride, and API auth secret. * **Documentation** * Complete ClickHouse demo scripts, scenario guides, and updated backup configuration docs clarifying legacy vs Altinity behavior. * **Tests** * New unit and E2E coverage for Altinity flows, ownership, namespace invariants, and parameter round-trips. * **Chores** * RBAC extended for job lifecycle and added image build/publish step. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2 parents b810dbd + 250e1b3 commit 86f5267

43 files changed

Lines changed: 11561 additions & 63 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎api/apps/v1alpha1/clickhouse/types.go‎

Lines changed: 40 additions & 10 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎api/apps/v1alpha1/clickhouse/zz_generated.deepcopy.go‎

Lines changed: 16 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// Package v1alpha1 defines strategy.backups.cozystack.io API types.
3+
//
4+
// Group: strategy.backups.cozystack.io
5+
// Version: v1alpha1
6+
package v1alpha1
7+
8+
import (
9+
corev1 "k8s.io/api/core/v1"
10+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
11+
"k8s.io/apimachinery/pkg/runtime"
12+
)
13+
14+
func init() {
15+
SchemeBuilder.Register(func(s *runtime.Scheme) error {
16+
s.AddKnownTypes(GroupVersion,
17+
&Altinity{},
18+
&AltinityList{},
19+
)
20+
return nil
21+
})
22+
}
23+
24+
const (
25+
AltinityStrategyKind = "Altinity"
26+
)
27+
28+
// +kubebuilder:object:root=true
29+
// +kubebuilder:subresource:status
30+
// +kubebuilder:resource:scope=Cluster
31+
32+
// Altinity defines a backup strategy that delegates execution to Altinity's
33+
// clickhouse-backup tool, run as a one-shot batch/v1.Job per BackupJob /
34+
// RestoreJob. The strategy carries a templated PodTemplateSpec; the driver
35+
// renders it against the source application and the resolved BackupClass
36+
// parameters, wraps it in a Job, and surfaces completion as a Cozystack
37+
// Backup artifact.
38+
type Altinity struct {
39+
metav1.TypeMeta `json:",inline"`
40+
metav1.ObjectMeta `json:"metadata,omitempty"`
41+
42+
Spec AltinitySpec `json:"spec,omitempty"`
43+
Status AltinityStatus `json:"status,omitempty"`
44+
}
45+
46+
// +kubebuilder:object:root=true
47+
48+
// AltinityList contains a list of Altinity backup strategies.
49+
type AltinityList struct {
50+
metav1.TypeMeta `json:",inline"`
51+
metav1.ListMeta `json:"metadata,omitempty"`
52+
Items []Altinity `json:"items"`
53+
}
54+
55+
// AltinitySpec specifies the desired Altinity-driven backup strategy.
56+
type AltinitySpec struct {
57+
// JobTemplate is the PodTemplateSpec the driver wraps in a batch/v1.Job
58+
// for both backup and restore runs. Helm-style Go templates are supported
59+
// in every string field. The available context is:
60+
// .Application - the application object (apps.cozystack.io/ClickHouse)
61+
// .Release.Name, .Release.Namespace - shorthand for the application
62+
// name/namespace
63+
// .Mode - "backup" or "restore"
64+
// .Parameters - map[string]string from the matched BackupClassStrategy
65+
// .Backup - the source Backup object (only set on restore;
66+
// carries .Name, .Spec, .Status)
67+
JobTemplate corev1.PodTemplateSpec `json:"jobTemplate"`
68+
}
69+
70+
// AltinityStatus reports observed state for the strategy CR. Driver
71+
// controllers surface diagnostic conditions here (e.g. validation issues).
72+
type AltinityStatus struct {
73+
// Conditions holds the latest available observations.
74+
// +optional
75+
// +listType=map
76+
// +listMapKey=type
77+
Conditions []metav1.Condition `json:"conditions,omitempty"`
78+
}

‎api/backups/strategy/v1alpha1/zz_generated.deepcopy.go‎

Lines changed: 97 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
#!/bin/bash
2+
# Shared helpers for the ClickHouse backup/restore demo.
3+
# Source this file in other scripts: source "$(dirname "$0")/00-helpers.sh"
4+
5+
export RED='\033[0;31m'
6+
export GREEN='\033[0;32m'
7+
export YELLOW='\033[1;33m'
8+
export BLUE='\033[0;34m'
9+
export MAGENTA='\033[0;35m'
10+
export CYAN='\033[0;36m'
11+
export WHITE='\033[1;37m'
12+
export NC='\033[0m'
13+
export BOLD='\033[1m'
14+
15+
# Default settings (override via environment).
16+
export NAMESPACE="${NAMESPACE:-tenant-test}"
17+
export CLICKHOUSE_NAME="${CLICKHOUSE_NAME:-clickhouse-test}"
18+
export CLICKHOUSE_RESTORE_NAME="${CLICKHOUSE_RESTORE_NAME:-clickhouse-restore}"
19+
export BUCKET_NAME="${BUCKET_NAME:-clickhouse-backups}"
20+
export BACKUPCLASS_NAME="${BACKUPCLASS_NAME:-clickhouse-backup}"
21+
export STRATEGY_NAME="${STRATEGY_NAME:-altinity}"
22+
export BACKUPJOB_NAME="${BACKUPJOB_NAME:-clickhouse-backup-job}"
23+
export RESTOREJOB_INPLACE_NAME="${RESTOREJOB_INPLACE_NAME:-clickhouse-restore-inplace}"
24+
export RESTOREJOB_TOCOPY_NAME="${RESTOREJOB_TOCOPY_NAME:-clickhouse-restore-to-copy}"
25+
# The clickhouse-backup sidecar lives in the application Pod (rendered by the
26+
# chart when backup.enabled=true). Tenants don't manage a separate Secret;
27+
# the chart projects bucket coordinates into <release>-backup-s3 from the
28+
# spec.backup.* values populated by step 04 from the BucketInfo cache.
29+
30+
log_info() { echo -e "${BLUE}i${NC} $*" >&2; }
31+
log_success() { echo -e "${GREEN}OK${NC} $*" >&2; }
32+
log_warning() { echo -e "${YELLOW}!${NC} $*" >&2; }
33+
log_error() { echo -e "${RED}x${NC} $*" >&2; }
34+
log_step() { echo -e "\n${MAGENTA}${BOLD}> $*${NC}" >&2; }
35+
log_substep() { echo -e "${CYAN} -> $*${NC}" >&2; }
36+
log_command() { echo -e "${WHITE} $ $*${NC}" >&2; }
37+
38+
separator() {
39+
echo -e "\n${CYAN}------------------------------------------------------------${NC}\n" >&2
40+
}
41+
42+
print_header() {
43+
local title="$1"
44+
echo -e "\n${MAGENTA}${BOLD}== $title ==${NC}\n" >&2
45+
}
46+
47+
# Wait until a JSONPath value on a resource matches the desired string.
48+
wait_for_field() {
49+
local resource_type="$1"
50+
local resource_name="$2"
51+
local jsonpath="$3"
52+
local desired="$4"
53+
local namespace="${5:-}"
54+
local timeout="${6:-300}"
55+
56+
log_substep "Waiting for $resource_type/$resource_name $jsonpath to become '$desired'..."
57+
local elapsed=0
58+
local ns_flag=()
59+
[[ -n "$namespace" ]] && ns_flag=(-n "$namespace")
60+
61+
while true; do
62+
local current
63+
current=$(kubectl get "$resource_type" "$resource_name" "${ns_flag[@]}" -o jsonpath="$jsonpath" 2>/dev/null || true)
64+
if [[ "$current" == "$desired" ]]; then
65+
log_success "$resource_type/$resource_name reached '$desired'"
66+
return 0
67+
fi
68+
if [[ $elapsed -ge $timeout ]]; then
69+
log_error "Timeout waiting for $resource_type/$resource_name (current: '$current', expected: '$desired')"
70+
return 1
71+
fi
72+
sleep 5
73+
elapsed=$((elapsed + 5))
74+
done
75+
}
76+
77+
# Run a SQL statement against the ClickHouse cluster of the given app instance.
78+
# Args: <release-name> <sql>
79+
# Note: the Cozystack ClickHouse RD prefixes Helm release names with
80+
# "clickhouse-", so the resources rendered by the chart (StatefulSet, Secret,
81+
# etc.) carry that prefix even when the user-facing application name does not.
82+
clickhouse_query() {
83+
local release="$1"
84+
local sql="$2"
85+
kubectl -n "$NAMESPACE" exec -i \
86+
"statefulset/chi-clickhouse-${release}-clickhouse-0-0" -c clickhouse -- \
87+
clickhouse-client -u backup --password "$(_clickhouse_password "$release")" -q "$sql"
88+
}
89+
90+
# Read the auto-generated 'backup' user password from the chart-rendered Secret.
91+
_clickhouse_password() {
92+
local release="$1"
93+
kubectl -n "$NAMESPACE" get secret "clickhouse-${release}-credentials" \
94+
-o jsonpath='{.data.backup}' | base64 -d
95+
}

0 commit comments

Comments
 (0)