The operator manages Kubernetes NetworkPolicy resources at two levels, both
off by default:
- Cluster policies — per-cluster policies covering the internal traffic of
ClickHouseClusterandKeeperClusterresources, enabled throughspec.networkPolicyon each custom resource. - Operator pod policies — chart-shipped policies restricting ingress to the controller manager pod itself for the metrics and webhook endpoints.
Cluster NetworkPolicies
Enable the managed policy per cluster:
apiVersion: clickhouse.com/v1alpha1
kind: ClickHouseCluster
spec:
networkPolicy:
policy: Enabled
---
apiVersion: clickhouse.com/v1alpha1
kind: KeeperCluster
spec:
networkPolicy:
policy: EnabledThe managed policies cover cluster-internal traffic only. Selecting the pods switches them to default deny for ingress, and the operator allows exactly what the clusters need to function:
| Cluster | Allowed source | Allowed ports |
|---|---|---|
| ClickHouse | The cluster’s own pods | 9009 (interserver), 9001 (management) |
| ClickHouse | Operator pods (label clickhouse.com/role: operator, any namespace) |
9001, 9002 (management) |
| Keeper | The cluster’s own pods | 9234 (Raft) |
| Keeper | Operator pods and every ClickHouseCluster referencing this keeper |
2181, 2281 (client), 9123 (HTTP control) |
A keeper admits ClickHouse clusters based on their keeperClusterRef — adding
or removing a reference updates the keeper’s policy automatically, including
references from other namespaces.
Allowing clients and monitoring
Client connections and metrics scraping are not covered: with the managed
policy enabled, nothing can reach the client ports (9000/8123, or the TLS
variants) or the metrics port until you allow it. NetworkPolicies are additive,
so grant access with your own policy next to the managed one:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-clients
namespace: <cluster-namespace>
spec:
podSelector:
matchLabels:
app: <name>-clickhouse
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
role: my-app
ports:
- protocol: TCP
port: 9000The same pattern applies to Prometheus scrapes (port 9363 on ClickHouse,
9090 on Keeper) — allow your monitoring namespace explicitly.
Setting networkPolicy.policy: Disabled (the default) removes the managed
policy; user-defined policies are never touched by the operator unless they
carry the cluster’s app label.
Cluster-wide opt-out
NetworkPolicy management can also be disabled cluster-wide via the operator’s
ENABLE_NETWORK_POLICY environment variable. With ENABLE_NETWORK_POLICY=false,
the operator skips the NetworkPolicy reconcile step for every
ClickHouseCluster and KeeperCluster regardless of their spec.networkPolicy.policy,
and does not watch NetworkPolicy resources at all. The operator’s
ServiceAccount therefore does not need RBAC permissions on
networkpolicies.networking.k8s.io, which is useful when running the operator
under a restricted ServiceAccount that intentionally omits those permissions.
# in the operator Deployment spec
env:
- name: ENABLE_NETWORK_POLICY
value: "false"With Helm, the same switch is exposed as a chart value:
# values.yaml
controller:
networkPolicyManagement:
enabled: falseOperator pod policies
The chart also ships optional policies that restrict which traffic can reach the controller manager pod — the operator process itself. They cover the two ports the operator exposes to other clients: the metrics endpoint and the admission webhook.
What the Helm chart creates
When enabled, the chart creates up to two ingress-only policies, both selecting the controller manager pod:
| Policy | Allowed source | Allowed port |
|---|---|---|
allow-metrics-traffic |
Namespaces labeled metrics: enabled |
metrics.port (default 8080/TCP) |
allow-webhook-traffic |
Namespaces labeled webhook: enabled |
webhook.port (default 9443/TCP) |
Both policies declare only policyTypes: [Ingress]. They do not restrict egress
from the operator, and they do not touch ClickHouse server or Keeper pods.
Default-deny behavior
Selecting a pod with an ingress NetworkPolicy switches that pod to default
deny for ingress: once either policy applies, any inbound traffic to the
controller manager pod that is not explicitly allowed is dropped. After enabling,
the only ingress that reaches the operator is:
- a metrics scrape from a namespace labeled
metrics: enabled, and - an admission webhook call from a namespace labeled
webhook: enabled.
Everything else to the pod is denied. This is the intended hardening, but it means an unlabeled scraper or webhook caller stops working the moment the policies take effect.
Enabling the policies
With Helm, set the gate in your values:
# values.yaml
networkPolicy:
enabled: truehelm upgrade --install clickhouse-operator \
oci://ghcr.io/clickhouse/clickhouse-operator-helm \
-n clickhouse-operator-system --create-namespace \
-f values.yamlallow-webhook-traffic additionally requires webhook.enabled: true (the
default), so disabling the webhook also removes its policy.
With the raw kubectl manifests, uncomment the [NETWORK POLICY] section as
described in the kubectl install guide.
The raw manifests ship the same two policies.
Labeling client namespaces
Because both policies match the source by namespaceSelector, every namespace
that needs to reach the operator must carry the matching label. A scrape or
webhook call from an unlabeled namespace is dropped.
# Allow a Prometheus namespace to scrape the metrics endpoint
kubectl label namespace <prometheus-namespace> metrics=enabled
# Allow webhook callers from a given namespace
kubectl label namespace <caller-namespace> webhook=enabledPair this with the metrics RBAC described in Monitoring → Securing the metrics endpoint: the NetworkPolicy controls reachability, while the ClusterRole binding controls authorization. Both must be in place for a secured scrape to succeed.
Verifying
NS=clickhouse-operator-system
# The policies exist
kubectl -n $NS get networkpolicy
# Inspect the selectors and allowed sources
kubectl -n $NS describe networkpolicyAfter enabling, confirm that:
- Prometheus still scrapes the metrics endpoint (its namespace is labeled
metrics: enabledand bound to the metrics-reader ClusterRole). - Creating or updating a
ClickHouseClusterstill passes admission (the webhook is reachable).
If a scrape returns no data or a CR apply hangs, an unlabeled source namespace or the API server reachability caveat above is the most likely cause.
Related guides
- Monitoring the operator — the metrics endpoint, its RBAC, and securing scrapes.
- Install with kubectl — where to uncomment the network policy section.
- Install with Helm — chart values relevant to the operator.