keeper: accept node* endpoints and read <identity> for digest auth, add clickhouse.keeper_identity - #1545
Conversation
parseKeeperNodes selected only elements named exactly "node", but ClickHouse
treats every child of <zookeeper> whose name starts with "node" as an endpoint
(src/Common/ZooKeeper/ZooKeeperArgs.cpp: key.starts_with("node")). The
ClickHouse Kubernetes operator renders its `zookeeper: nodes: [...]` YAML as
repeated <nodes> elements, so on those clusters keeper.Connect failed with
"/zookeeper/node not exists in .../preprocessed_configs/config.xml".
Both callers (RBAC backup when user_directories.replicated exists, named
collections backup when named_collections_storage is a keeper* type) run by
default and end in log.Fatal, so `server --watch` with watch_is_main_process
restarted on every scheduled run and never produced a backup.
Mirror the server rule, and name the element actually found in the per-node
error messages.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KqygQvwaxVCuS1EjCJUqxG
…dentity keeper.Connect sent digest credentials only from a <zookeeper><digest> element, which ClickHouse does not define. The server reads <zookeeper><identity> (ZooKeeperArgs.cpp: key == "identity" sets auth_scheme = "digest"), so on a cluster with Keeper ACLs the connection succeeded and every read failed with "zk: not authenticated". Server configs commonly hide that value from preprocessed_configs/config.xml (hide_in_preprocessed, from_env); the ClickHouse Kubernetes operator does so by default, and then neither element is there to read. Add clickhouse.keeper_identity (env CLICKHOUSE_KEEPER_IDENTITY) as an explicit source which takes precedence, then <identity>, then the legacy <digest>. The value is passed untrimmed, exactly as the server would send it, and never logged; only its source is. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KqygQvwaxVCuS1EjCJUqxG
default-config now prints the new option, which the cli suite compares against a stored snapshot. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KqygQvwaxVCuS1EjCJUqxG
|
CI note: the Testflows matrix on the first run failed in one place only, the The re-run for c2d8209 is waiting for workflow approval since this comes from a fork. Update: the first run has finished. All 16 Go integration |
|
Re-run for c2d8209 (https://github.com/Altinity/clickhouse-backup/actions/runs/34381311882): Testflows 11/11 green, so the snapshot fix is confirmed; Go integration 15/16 green. The one failure is |
…ix/keeper-node-prefix # Conflicts: # test/testflows/clickhouse_backup/tests/snapshots/cli.py.cli.snapshot
fix #1546
Problem
On a cluster managed by the ClickHouse Kubernetes operator (
ghcr.io/clickhouse/clickhouse-operator), clickhouse-backup 2.8.0 cannot back up RBAC or named collections. Both steps run by default (rbac_backup_always/named_collections_backup_always) and end inlog.Fatal, soserver --watchwithwatch_is_main_process: truerestarts on every scheduled run and never uploads a backup. We ran five days with zero backups before the container restart count gave it away.There are two mismatches between what
keeper.Connectreads frompreprocessed_configs/config.xmland what ClickHouse itself reads from the same file:parseKeeperNodesselects only children named exactlynode. ClickHouse accepts every<zookeeper>child whose name starts withnode(src/Common/ZooKeeper/ZooKeeperArgs.cpp:key.starts_with("node")). The operator writeszookeeper: nodes: [...]in YAML, which ClickHouse renders as repeated<nodes>elements:<zookeeper><digest>, an element ClickHouse does not define. The server reads<zookeeper><identity>(key == "identity"→auth_scheme = "digest"). With Keeper ACLs in place every read then fails:hide_in_preprocessed/from_env), so there is nothing to read there at all.Changes
parseKeeperNodesaccepts every element child of<zookeeper>whose name starts withnode, mirroring the server rule. Per-node error messages name the element actually found (/zookeeper/nodes[0]/host not exists ...).keeper.Connecttakes digest credentials from, in order: the newclickhouse.keeper_identity(envCLICKHOUSE_KEEPER_IDENTITY),<zookeeper><identity>, then the legacy<zookeeper><digest>(kept so existing configs keep working). The value is passed untrimmed, exactly as the server would send it, and is never logged; only its source is.ReadMe.mddocuments the option;ChangeLog.mdentry under v2.8.1 BUG FIXES.No behaviour change for configs that already worked:
<node>still parses,<digest>still authenticates, the new option defaults to empty.Tests
TestParseKeeperNodesAcceptsNodePrefixedElements(<nodes>,<node1>,<node>mixed withsession_timeout_ms/root/identitysiblings),TestParseKeeperNodesWithoutEndpoints,TestParseKeeperNodesReportsElementNameOnBadHost,TestKeeperIdentityPrecedence.go test -short ./pkg/...passes.Verified against a live cluster
ClickHouse 26.7.3.19, clickhouse-operator v0.0.7, 3-node Keeper with digest ACLs,
user_directories.replicatedandnamed_collections_storage: keeper_encrypted. This branch cross-compiled and run inside the sidecar next to the shipped 2.8.0:Possible follow-up, not in this PR
createConfigsNamedCollectionsAndRBACIfNecessaryuseslog.Fatalfor these auxiliary steps, which turns a config-parsing problem into a total loss of data backups underwatch. Returning the error instead would let the data backup proceed and surface the failure through the normal status and metrics paths. That could be a separate change if maintainers agree.🤖 Generated with Claude Code
https://claude.ai/code/session_01KqygQvwaxVCuS1EjCJUqxG