diff --git a/api/v1alpha1/clickhousecluster_types.go b/api/v1alpha1/clickhousecluster_types.go index 3c642efc..935c56b8 100644 --- a/api/v1alpha1/clickhousecluster_types.go +++ b/api/v1alpha1/clickhousecluster_types.go @@ -183,6 +183,10 @@ type ClickHouseSettings struct { // +optional TLS ClusterTLSSpec `json:"tls,omitempty"` + // Network settings for the ClickHouse server, e.g. the addresses it listens on. + // +optional + Network NetworkSettings `json:"network,omitempty"` + // Enables synchronization of ClickHouse databases to the newly created replicas and cleanup of stale replicas // after scale down. // Supports only Replicated and integration databases. diff --git a/api/v1alpha1/common.go b/api/v1alpha1/common.go index 1119a62a..51675859 100644 --- a/api/v1alpha1/common.go +++ b/api/v1alpha1/common.go @@ -336,6 +336,17 @@ type ClusterTLSSpec struct { CABundle *CABundleSelector `json:"caBundle,omitempty"` } +// NetworkSettings defines ClickHouse server network listening configuration. +type NetworkSettings struct { + // ListenHost is the list of addresses the ClickHouse server listens on. + // Use it to conform to networking restrictions, e.g. ["0.0.0.0"] for IPv4-only, + // ["::"] for IPv6-only, or ["::", "0.0.0.0"] for dual-stack clusters. + // Defaults to ["::", "0.0.0.0"] when empty. + // +optional + // +listType=atomic + ListenHost []string `json:"listenHost,omitempty"` +} + // CABundleSelector selects a key holding a CA bundle from a Secret in the cluster's namespace. type CABundleSelector struct { // The name of the secret in the cluster's namespace to select from. diff --git a/api/v1alpha1/zz_generated.deepcopy.go b/api/v1alpha1/zz_generated.deepcopy.go index c95430ab..3783bdab 100644 --- a/api/v1alpha1/zz_generated.deepcopy.go +++ b/api/v1alpha1/zz_generated.deepcopy.go @@ -224,6 +224,7 @@ func (in *ClickHouseSettings) DeepCopyInto(out *ClickHouseSettings) { } in.Logger.DeepCopyInto(&out.Logger) in.TLS.DeepCopyInto(&out.TLS) + in.Network.DeepCopyInto(&out.Network) in.ExtraConfig.DeepCopyInto(&out.ExtraConfig) in.ExtraUsersConfig.DeepCopyInto(&out.ExtraUsersConfig) } @@ -594,6 +595,26 @@ func (in *NamedTemplateMeta) DeepCopy() *NamedTemplateMeta { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *NetworkSettings) DeepCopyInto(out *NetworkSettings) { + *out = *in + if in.ListenHost != nil { + in, out := &in.ListenHost, &out.ListenHost + *out = make([]string, len(*in)) + copy(*out, *in) + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NetworkSettings. +func (in *NetworkSettings) DeepCopy() *NetworkSettings { + if in == nil { + return nil + } + out := new(NetworkSettings) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *PersistentVolumeClaimTemplate) DeepCopyInto(out *PersistentVolumeClaimTemplate) { *out = *in diff --git a/config/crd/bases/clickhouse.com_clickhouseclusters.yaml b/config/crd/bases/clickhouse.com_clickhouseclusters.yaml index c2965e74..961a65de 100644 --- a/config/crd/bases/clickhouse.com_clickhouseclusters.yaml +++ b/config/crd/bases/clickhouse.com_clickhouseclusters.yaml @@ -6454,6 +6454,21 @@ spec: description: Maximum log file size. type: string type: object + network: + description: Network settings for the ClickHouse server, e.g. + the addresses it listens on. + properties: + listenHost: + description: |- + ListenHost is the list of addresses the ClickHouse server listens on. + Use it to conform to networking restrictions, e.g. ["0.0.0.0"] for IPv4-only, + ["::"] for IPv6-only, or ["::", "0.0.0.0"] for dual-stack clusters. + Defaults to ["::", "0.0.0.0"] when empty. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object tls: description: TLS settings, allows to configure secure endpoints and certificate verification for ClickHouse server. diff --git a/dist/chart/templates/crd/clickhouseclusters.clickhouse.com.yaml b/dist/chart/templates/crd/clickhouseclusters.clickhouse.com.yaml index f2faea51..efd93611 100644 --- a/dist/chart/templates/crd/clickhouseclusters.clickhouse.com.yaml +++ b/dist/chart/templates/crd/clickhouseclusters.clickhouse.com.yaml @@ -6457,6 +6457,21 @@ spec: description: Maximum log file size. type: string type: object + network: + description: Network settings for the ClickHouse server, e.g. + the addresses it listens on. + properties: + listenHost: + description: |- + ListenHost is the list of addresses the ClickHouse server listens on. + Use it to conform to networking restrictions, e.g. ["0.0.0.0"] for IPv4-only, + ["::"] for IPv6-only, or ["::", "0.0.0.0"] for dual-stack clusters. + Defaults to ["::", "0.0.0.0"] when empty. + items: + type: string + type: array + x-kubernetes-list-type: atomic + type: object tls: description: TLS settings, allows to configure secure endpoints and certificate verification for ClickHouse server. diff --git a/docs/reference/api-reference.mdx b/docs/reference/api-reference.mdx index 43a6c31d..187d9513 100644 --- a/docs/reference/api-reference.mdx +++ b/docs/reference/api-reference.mdx @@ -122,6 +122,7 @@ ClickHouseSettings defines ClickHouse server settings options. | `defaultUserPassword` | [DefaultPasswordSelector](#defaultpasswordselector) | Specifies source and type of the password for `default` ClickHouse user. | false | | | `logger` | [LoggerConfig](#loggerconfig) | Configuration of ClickHouse server logging. | false | | | `tls` | [ClusterTLSSpec](#clustertlsspec) | TLS settings, allows to configure secure endpoints and certificate verification for ClickHouse server. | false | | +| `network` | [NetworkSettings](#networksettings) | Network settings for the ClickHouse server, e.g. the addresses it listens on. | false | | | `enableDatabaseSync` | boolean | Enables synchronization of ClickHouse databases to the newly created replicas and cleanup of stale replicas
after scale down.
Supports only Replicated and integration databases. | false | true | | `extraConfig` | [RawExtension](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#rawextension-runtime-pkg) | Additional ClickHouse configuration that will be merged with the default one. | false | | | `extraUsersConfig` | [RawExtension](https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.28/#rawextension-runtime-pkg) | Additional ClickHouse users configuration that will be merged with the default one. | false | | @@ -353,6 +354,17 @@ NamedTemplateMeta defines supported metadata settings for template objects that Appears in: - [PersistentVolumeClaimTemplate](#persistentvolumeclaimtemplate) +## NetworkSettings {#networksettings} + +NetworkSettings defines ClickHouse server network listening configuration. + +| Field | Type | Description | Required | Default | +|-------|------|-------------|----------|---------| +| `listenHost` | string array | ListenHost is the list of addresses the ClickHouse server listens on.
Use it to conform to networking restrictions, e.g. ["0.0.0.0"] for IPv4-only,
["::"] for IPv6-only, or ["::", "0.0.0.0"] for dual-stack clusters.
Defaults to ["::", "0.0.0.0"] when empty. | false | | + +Appears in: +- [ClickHouseSettings](#clickhousesettings) + ## PDBPolicy {#pdbpolicy} PDBPolicy controls whether PodDisruptionBudgets are created. diff --git a/internal/controller/clickhouse/config.go b/internal/controller/clickhouse/config.go index ee96212b..bc0bd088 100644 --- a/internal/controller/clickhouse/config.go +++ b/internal/controller/clickhouse/config.go @@ -353,7 +353,12 @@ func clusterConfigGenerator(tmpl *template.Template, r *clickhouseReconciler, id return builder.String(), nil } +// defaultListenHost is used when spec.settings.network.listenHost is not set, +// preserving the historical dual-stack listening behavior. +var defaultListenHost = []string{"::", "0.0.0.0"} + type networkConfigParams struct { + ListenHost []string InterserverHTTPPort uint16 InterserverHTTPUser string InterserverHTTPPasswordEnvVar string @@ -381,7 +386,13 @@ func networkConfigGenerator(tmpl *template.Template, r *clickhouseReconciler, _ controllerutil.SortKey(protocols, func(p namedProtocol) string { return p.Name }) + listenHost := r.Cluster.Spec.Settings.Network.ListenHost + if len(listenHost) == 0 { + listenHost = defaultListenHost + } + params := networkConfigParams{ + ListenHost: listenHost, InterserverHTTPPort: PortInterserver, InterserverHTTPUser: InterserverUserName, InterserverHTTPPasswordEnvVar: EnvInterserverPassword, diff --git a/internal/controller/clickhouse/config_test.go b/internal/controller/clickhouse/config_test.go index 9a5e878f..86239a03 100644 --- a/internal/controller/clickhouse/config_test.go +++ b/internal/controller/clickhouse/config_test.go @@ -57,3 +57,61 @@ var _ = Describe("ConfigGenerator", func() { }) } }) + +var _ = Describe("networkConfigGenerator listen_host", func() { + networkGenerator := func() configGenerator { + for _, g := range generators { + if g.Filename() == "00-network.yaml" { + return g + } + } + + return nil + } + + newReconciler := func(listenHost []string) *clickhouseReconciler { + return &clickhouseReconciler{ + Cluster: &v1.ClickHouseCluster{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-cluster", + Namespace: "test-namespace", + }, + Spec: v1.ClickHouseClusterSpec{ + Replicas: new(int32(1)), + Shards: new(int32(1)), + DataVolumeClaimSpec: &corev1.PersistentVolumeClaimSpec{}, + Settings: v1.ClickHouseSettings{ + Network: v1.NetworkSettings{ListenHost: listenHost}, + }, + }, + Status: v1.ClickHouseClusterStatus{Version: "25.12.1.1"}, + }, + keeper: v1.KeeperCluster{Spec: v1.KeeperClusterSpec{Replicas: new(int32(1))}}, + } + } + + generateListenHost := func(listenHost []string) []any { + gen := networkGenerator() + Expect(gen).ToNot(BeNil()) + + data, err := gen.Generate(newReconciler(listenHost), v1.ClickHouseReplicaID{}) + Expect(err).ToNot(HaveOccurred()) + + obj := map[string]any{} + Expect(yaml.Unmarshal([]byte(data), &obj)).To(Succeed()) + Expect(obj).To(HaveKey("listen_host")) + + hosts, ok := obj["listen_host"].([]any) + Expect(ok).To(BeTrue()) + + return hosts + } + + It("defaults to dual-stack when listenHost is empty", func() { + Expect(generateListenHost(nil)).To(Equal([]any{"::", "0.0.0.0"})) + }) + + It("honors a custom IPv4-only listenHost override", func() { + Expect(generateListenHost([]string{"0.0.0.0"})).To(Equal([]any{"0.0.0.0"})) + }) +}) diff --git a/internal/controller/clickhouse/templates/network.yaml.tmpl b/internal/controller/clickhouse/templates/network.yaml.tmpl index f8b87f4d..deb9585f 100644 --- a/internal/controller/clickhouse/templates/network.yaml.tmpl +++ b/internal/controller/clickhouse/templates/network.yaml.tmpl @@ -1,6 +1,7 @@ listen_host: - - "::" - - "0.0.0.0" +{{- range .ListenHost }} + - "{{ . }}" +{{- end }} listen_try: 1 {{- /* can't use composable protocol for interserver, because server disables replication, if interserver_http_port is not set */}}