diff --git a/modules/clickhouse/k8s/1.0/README.md b/modules/clickhouse/k8s/1.0/README.md new file mode 100644 index 00000000..0daf19f2 --- /dev/null +++ b/modules/clickhouse/k8s/1.0/README.md @@ -0,0 +1,91 @@ +# clickhouse / k8s / 1.0 + +Deploys a ClickHouse cluster as a `ClickHouseInstallation` (CHI) custom +resource, applied through a small local Helm chart under `chart/` (no plan-time +CRD schema check). Inputs `clickhouse_operator` (CRDs) and `clickhouse_keeper` +(coordination) are declared dependencies, so Facets deploys them first. + +- **CRD apiVersion / kind**: `clickhouse.altinity.com/v1` / + `ClickHouseInstallation`. +- **Keeper wiring**: `configuration.zookeeper.nodes[0]` is populated from + `var.inputs.clickhouse_keeper.attributes.service_host` and `.port`. +- **Service DNS (`service_host` output)**: + `clickhouse-..svc.cluster.local`. Altinity's CHI + service is `clickhouse-`; the CHI name here is ``. + Verify against your operator version and adjust `outputs.tf` if it differs. +- **Ports**: HTTP `8123`, native TCP `9000`. + +## Users (`users_json`) + +`users_json` is a JSON array. Each element: + +```json +{ + "name": "app", + "password_secret": "clickhouse-app-password", + "password_sha256_hex": "", + "networks": ["::/0"], + "profile": "default" +} +``` + +- **No plaintext passwords are hardcoded.** `password_secret` is the *name* of a + Facets secret intended to hold the credential; wire it operationally (e.g. via + a dedicated `--secret` spec field / external-secret) — the module does not + resolve secret values from inside a JSON string. +- `password_sha256_hex` (optional) renders directly into the CHI as + `/password_sha256_hex`. Omit both fields for a passwordless user. +- `networks` → `/networks/ip`; `profile` → `/profile`. + +`settings_json` is a JSON object merged into `configuration.settings`. + +## Dedicated node-pool placement (`node_pool` input) + +Optional input `node_pool` (`@facets/kubernetes_nodepool`, `optional: true`). +Wire it to pin ClickHouse onto a dedicated, tainted node pool; leave it +unwired and the module schedules anywhere (no `nodeSelector`/`tolerations` +rendered). Placement is read defensively in `main.tf`: + +- `node_selector` — a **merge** of the output's custom `node_selector` and the + always-present GKE pool label: + `merge(try(var.inputs.node_pool.attributes.node_selector, {}), local.node_pool_name != "" ? { "cloud.google.com/gke-nodepool" = local.node_pool_name } : {})` + → `spec.templates.podTemplates[].spec.nodeSelector` (guarded by `{{- if }}`). +- `taints` ← `try(var.inputs.node_pool.attributes.taints, [])`, each + `{key,value,effect}` mapped to a toleration `{key,value,effect,operator:"Equal"}` + → `spec.templates.podTemplates[].spec.tolerations` (guarded by `{{- if }}`). + +**Why the GKE pool label (eu-prod pilot finding):** the node_pool output's +custom `node_selector` arrived **empty** at apply, so pods landed on regular +nodes. Every node in a GKE pool reliably carries +`cloud.google.com/gke-nodepool=`, and the output always exposes +`node_pool_name` (e.g. `clickhouse-node-pool`) — so the module pins on that +label. Any custom labels from the output still merge in alongside it. When +`node_pool` is unwired, `node_pool_name` is `""`, the merge yields `{}`, and no +`nodeSelector` renders. + +The `kubernetes_node_pool/gcp/1.0` output emits `taints[].effect` already in +Kubernetes form (`NoSchedule`/`PreferNoSchedule`/`NoExecute`), so the effect is +passed through verbatim — no `NO_SCHEDULE`→`NoSchedule` normalization is needed. +`variables.tf` types `node_selector` as `map(string)` (not the schema-default +empty `object({})`, which silently drops label keys during Terraform coercion). +If a future `raptor module` mutation regenerates `variables.tf`, re-widen +`node_selector` back to `map(string)`. + +### Replica spread (operator-native podDistribution) + +Replica spreading is delegated to the Altinity operator via `podDistribution` +(`ClickHouseAntiAffinity` + `ReplicaAntiAffinity`) — **not** a raw +`podAntiAffinity` in the podTemplate. + +`podDistribution` is a **direct child of the podTemplate entry** — +`spec.templates.podTemplates[].podDistribution`, a sibling of that entry's +`spec:` — matching Altinity's `chi-examples/02-templates-01` reference. The +podTemplate is applied because the CHI references it via +`spec.defaults.templates.podTemplate: default` (+ `dataVolumeClaimTemplate`). + +- **eu-prod pilot findings:** (1) a raw `podAntiAffinity` injected into the CHI + podTemplate `spec` made the operator loop the StatefulSet `Update→Recreate` and + stall with no STS/pod created. (2) Placing `podDistribution` under the cluster + entry (`configuration.clusters[].templates`) also broke the STS reconcile — it + belongs on the podTemplate. Moving it there lets the operator manage the spread + as part of its own reconcile, no churn. diff --git a/modules/clickhouse/k8s/1.0/chart/Chart.yaml b/modules/clickhouse/k8s/1.0/chart/Chart.yaml new file mode 100644 index 00000000..af9b72e7 --- /dev/null +++ b/modules/clickhouse/k8s/1.0/chart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: clickhouse-chi +description: Renders an Altinity ClickHouseInstallation (CHI) custom resource +type: application +version: 1.0.0 +appVersion: "1.0" diff --git a/modules/clickhouse/k8s/1.0/chart/templates/chi.yaml b/modules/clickhouse/k8s/1.0/chart/templates/chi.yaml new file mode 100644 index 00000000..63feb873 --- /dev/null +++ b/modules/clickhouse/k8s/1.0/chart/templates/chi.yaml @@ -0,0 +1,95 @@ +apiVersion: "clickhouse.altinity.com/v1" +kind: "ClickHouseInstallation" +metadata: + name: {{ .Values.clusterName | quote }} + namespace: {{ .Values.namespace | quote }} +spec: + # Cluster DNS domain override. The operator hardcodes cluster.local when + # constructing replica host FQDNs; on clusters with a custom DNS domain (some + # GKE clusters use the env name, not cluster.local) that yields unresolvable + # hosts and the CHI deadlocks on replica health-checks. namespaceDomainPattern + # makes the operator build ..svc. instead. + namespaceDomainPattern: "%s.svc.{{ .Values.clusterDomain }}" + configuration: + {{- if .Values.zookeeper.host }} + zookeeper: + nodes: + - host: {{ .Values.zookeeper.host | quote }} + port: {{ int .Values.zookeeper.port }} + {{- end }} + clusters: + - name: {{ .Values.clusterName | quote }} + layout: + shardsCount: {{ int .Values.shards }} + replicasCount: {{ int .Values.replicas }} + {{- if .Values.users }} + users: + {{- range .Values.users }} + {{ .name }}/profile: {{ .profile | default "default" | quote }} + {{- if .password_sha256_hex }} + {{ .name }}/password_sha256_hex: {{ .password_sha256_hex | quote }} + {{- end }} + {{- if .networks }} + {{ .name }}/networks/ip: + {{- range .networks }} + - {{ . | quote }} + {{- end }} + {{- end }} + {{- end }} + {{- end }} + {{- if .Values.settings }} + settings: + {{- range $k, $v := .Values.settings }} + {{ $k }}: {{ $v | quote }} + {{- end }} + {{- end }} + defaults: + templates: + podTemplate: default + dataVolumeClaimTemplate: default + templates: + podTemplates: + - name: default + # Operator-native replica spreading. podDistribution is a direct child of + # the podTemplate entry (sibling of spec:), NOT under the cluster's + # templates — putting it on the cluster broke the STS reconcile. A raw + # podAntiAffinity in the podTemplate spec also caused STS churn; this lets + # Altinity manage the spread (across hosts, then across replicas) natively. + podDistribution: + - type: ClickHouseAntiAffinity + - type: ReplicaAntiAffinity + spec: + {{- if .Values.nodeSelector }} + nodeSelector: + {{- range $k, $v := .Values.nodeSelector }} + {{ $k }}: {{ $v | quote }} + {{- end }} + {{- end }} + {{- if .Values.tolerations }} + tolerations: + {{- range .Values.tolerations }} + - key: {{ .key | quote }} + value: {{ .value | quote }} + effect: {{ .effect | quote }} + operator: {{ .operator | default "Equal" | quote }} + {{- end }} + {{- end }} + containers: + - name: clickhouse + image: {{ .Values.image | quote }} + resources: + requests: + cpu: {{ .Values.resources.cpu | quote }} + memory: {{ .Values.resources.memory | quote }} + limits: + cpu: {{ .Values.resources.cpu | quote }} + memory: {{ .Values.resources.memory | quote }} + volumeClaimTemplates: + - name: default + spec: + storageClassName: {{ .Values.storageClass | quote }} + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.storageSize | quote }} diff --git a/modules/clickhouse/k8s/1.0/chart/values.yaml b/modules/clickhouse/k8s/1.0/chart/values.yaml new file mode 100644 index 00000000..7b1c59a6 --- /dev/null +++ b/modules/clickhouse/k8s/1.0/chart/values.yaml @@ -0,0 +1,26 @@ +# Defaults are overridden by the Terraform helm_release values block. +clusterName: default +namespace: clickhouse +image: clickhouse/clickhouse-server:24.8 +shards: 1 +replicas: 3 +storageClass: premium-rwo +storageSize: 100Gi +# Kubernetes cluster DNS domain (overridden by the Terraform values block). +clusterDomain: cluster.local +resources: + cpu: "1" + memory: "2Gi" +# zookeeper (ClickHouse Keeper) coordination endpoint +zookeeper: + host: "" + port: 2181 +# users: list of {name, password_sha256_hex?, networks: [..], profile} +users: [] +# settings: map of clickhouse server settings +settings: {} +# Dedicated node-pool placement (populated from the optional node_pool input). +# nodeSelector: map of labels; tolerations: list of {key,value,effect,operator}. +# Both empty by default so an unwired node_pool renders no placement. +nodeSelector: {} +tolerations: [] diff --git a/modules/clickhouse/k8s/1.0/facets.yaml b/modules/clickhouse/k8s/1.0/facets.yaml new file mode 100644 index 00000000..ce584ffe --- /dev/null +++ b/modules/clickhouse/k8s/1.0/facets.yaml @@ -0,0 +1,153 @@ +# ╔═══════════════════════════════════════════════════════════╗ +# ║ MANAGED BY: raptor module — DO NOT EDIT DIRECTLY ║ +# ║ ║ +# ║ Modify via: ║ +# ║ raptor module set-spec --add ║ +# ║ raptor module add-input / remove-input ║ +# ║ raptor module add-output / remove-output ║ +# ║ raptor module show | raptor module validate ║ +# ╚═══════════════════════════════════════════════════════════╝ +intent: clickhouse +flavor: k8s +version: "1.0" +description: ClickHouse cluster (ClickHouseInstallation) on k8s via Altinity operator +clouds: + - kubernetes + - gcp +inputs: + clickhouse_keeper: + type: '@facets/clickhouse-keeper' + clickhouse_operator: + type: '@facets/clickhouse-operator' + kubernetes_details: + type: '@facets/kubernetes-details' + providers: + - helm + - kubernetes + node_pool: + type: '@facets/kubernetes_nodepool' +outputs: + default: + type: '@facets/clickhouse' +spec: + properties: + clickhouse_version: + default: "24.8" + description: ClickHouse server image tag (an LTS tag is recommended, e.g. 24.8) + maxLength: 30 + minLength: 1 + title: ClickHouse Version + type: string + cluster_domain: + default: cluster.local + description: Kubernetes cluster DNS domain. Override per-env when the cluster is not the default cluster.local (some GKE clusters use a custom domain e.g. the env name). + maxLength: 253 + minLength: 1 + title: Cluster DNS Domain + type: string + cluster_name: + default: default + description: Logical ClickHouse cluster name (used in the CHI layout) + maxLength: 40 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + title: Cluster Name + type: string + namespace: + default: clickhouse + description: Kubernetes namespace to deploy the ClickHouse cluster into + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + title: Namespace + type: string + replicas: + default: 3 + description: Number of replicas per shard + maximum: 20 + minimum: 1 + title: Replicas + type: number + resources: + properties: + cpu: + default: "1" + description: CPU request and limit per ClickHouse pod (e.g. 1) + maxLength: 16 + minLength: 1 + title: CPU Request/Limit + type: string + memory: + default: 2Gi + description: Memory request and limit per ClickHouse pod (e.g. 2Gi) + maxLength: 16 + minLength: 1 + title: Memory Request/Limit + type: string + title: Resources + type: object + settings_json: + default: '{}' + description: JSON object of ClickHouse server settings merged into configuration.settings (e.g. {"max_concurrent_queries":"200"}) + title: Settings (JSON) + type: string + x-ui-editor: true + x-ui-editor-language: json + shards: + default: 1 + description: Number of shards in the cluster + maximum: 100 + minimum: 1 + title: Shards + type: number + storage_class: + default: premium-rwo + description: StorageClass for ClickHouse persistent volumes + maxLength: 63 + minLength: 1 + title: Storage Class + type: string + storage_size: + default: 100Gi + description: Persistent volume size per ClickHouse pod (e.g. 100Gi) + maxLength: 16 + minLength: 2 + title: Storage Size + type: string + users_json: + default: '[]' + description: 'JSON array of ClickHouse users. Each item: {name, password_secret?, networks (list of CIDRs/host regex), profile}. password_secret is the NAME of a Facets secret holding the SHA256 password; leave empty for a passwordless user. Example: [{"name":"app","password_secret":"clickhouse-app-password","networks":["::/0"],"profile":"default"}]' + title: Users (JSON) + type: string + x-ui-editor: true + x-ui-editor-language: json + type: object + x-ui-order: + - cluster_name + - namespace + - clickhouse_version + - shards + - replicas + - storage_size + - storage_class + - resources + - users_json + - settings_json + - cluster_domain +sample: + flavor: k8s + kind: clickhouse + spec: + clickhouse_version: "24.8" + cluster_domain: cluster.local + cluster_name: default + namespace: clickhouse + replicas: 3 + resources: + cpu: "1" + settings_json: '{}' + shards: 1 + storage_class: premium-rwo + storage_size: 100Gi + users_json: '[]' + version: "1.0" diff --git a/modules/clickhouse/k8s/1.0/main.tf b/modules/clickhouse/k8s/1.0/main.tf new file mode 100644 index 00000000..fca6853d --- /dev/null +++ b/modules/clickhouse/k8s/1.0/main.tf @@ -0,0 +1,101 @@ +# ╔═══════════════════════════════════════════════════════════╗ +# ║ Module: clickhouse/k8s/1.0 ║ +# ║ Renders a ClickHouseInstallation (CHI) via a local helm ║ +# ║ chart so no plan-time CRD schema check is needed. ║ +# ║ Wires the ClickHouse Keeper input as the zookeeper node. ║ +# ╚═══════════════════════════════════════════════════════════╝ + +locals { + spec = var.instance.spec + cluster_name = lookup(local.spec, "cluster_name", "default") + namespace = lookup(local.spec, "namespace", "clickhouse") + clickhouse_version = lookup(local.spec, "clickhouse_version", "24.8") + shards = lookup(local.spec, "shards", 1) + replicas = lookup(local.spec, "replicas", 3) + storage_size = lookup(local.spec, "storage_size", "100Gi") + storage_class = lookup(local.spec, "storage_class", "premium-rwo") + cluster_domain = lookup(local.spec, "cluster_domain", "cluster.local") + resources = lookup(local.spec, "resources", {}) + cpu = lookup(local.resources, "cpu", "1") + memory = lookup(local.resources, "memory", "2Gi") + + # *_json string fields → decoded structures (jsondecode pattern) + users = jsondecode(lookup(local.spec, "users_json", "[]")) + settings = jsondecode(lookup(local.spec, "settings_json", "{}")) + + # ClickHouse Keeper coordination endpoint from the clickhouse_keeper input. + keeper_attrs = coalesce(var.inputs.clickhouse_keeper.attributes, {}) + keeper_host = lookup(local.keeper_attrs, "service_host", "") + keeper_port = lookup(local.keeper_attrs, "port", 2181) + + # Dedicated node-pool placement (optional input — read defensively). + # node_selector: map of labels to pin the pods onto the pool's nodes. + # taints: list of {key,value,effect} that we translate into tolerations so + # the pods are allowed onto the tainted pool. When node_pool is unwired both + # collapse to empty and no placement is rendered. + # The node_pool output's custom node_selector can arrive empty, but every + # node in a GKE pool reliably carries cloud.google.com/gke-nodepool=, + # and the output always exposes node_pool_name — so we pin on that label. + node_pool_name = try(var.inputs.node_pool.attributes.node_pool_name, "") + node_selector = merge( + try(var.inputs.node_pool.attributes.node_selector, {}), + local.node_pool_name != "" ? { "cloud.google.com/gke-nodepool" = local.node_pool_name } : {} + ) + # GKE reports taint effect as NO_SCHEDULE/PREFER_NO_SCHEDULE/NO_EXECUTE; + # k8s tolerations require the camelCase form. Normalise (passthrough if already correct). + effect_map = { + NO_SCHEDULE = "NoSchedule" + PREFER_NO_SCHEDULE = "PreferNoSchedule" + NO_EXECUTE = "NoExecute" + NoSchedule = "NoSchedule" + PreferNoSchedule = "PreferNoSchedule" + NoExecute = "NoExecute" + } + node_taints = try(var.inputs.node_pool.attributes.taints, []) + tolerations = [ + for t in local.node_taints : { + key = t.key + value = t.value + effect = lookup(local.effect_map, t.effect, t.effect) + operator = "Equal" + } + ] +} + +resource "helm_release" "clickhouse" { + name = local.cluster_name + chart = "${path.module}/chart" + namespace = local.namespace + create_namespace = true + wait = true + cleanup_on_fail = true + + values = [ + yamlencode({ + clusterName = local.cluster_name + namespace = local.namespace + image = "clickhouse/clickhouse-server:${local.clickhouse_version}" + shards = local.shards + replicas = local.replicas + storageClass = local.storage_class + storageSize = local.storage_size + clusterDomain = local.cluster_domain + resources = { + cpu = local.cpu + memory = local.memory + } + zookeeper = { + host = local.keeper_host + port = local.keeper_port + } + users = local.users + settings = local.settings + # Dedicated node-pool placement (empty when node_pool input is unwired). + nodeSelector = local.node_selector + tolerations = local.tolerations + }) + ] +} + +# The operator (CRDs) and the keeper must exist before this CHI is applied. +# Both are declared inputs, so Facets deploys them first at the blueprint level. diff --git a/modules/clickhouse/k8s/1.0/outputs.tf b/modules/clickhouse/k8s/1.0/outputs.tf new file mode 100644 index 00000000..cb506ca9 --- /dev/null +++ b/modules/clickhouse/k8s/1.0/outputs.tf @@ -0,0 +1,23 @@ +# ╔═══════════════════════════════════════════════════════════╗ +# ║ Output contract: @facets/clickhouse ║ +# ║ Keys managed by CLI — fill in the values only ║ +# ║ Do not add or remove keys. Do not rename. ║ +# ║ ║ +# ║ View schema: raptor get output-type @facets/clickhouse ║ +# ╚═══════════════════════════════════════════════════════════╝ + +locals { + output_attributes = { + cluster_name = local.cluster_name + http_port = 8123 + namespace = local.namespace + service_host = "clickhouse-${local.cluster_name}.${local.namespace}.svc.cluster.local" + tcp_port = 9000 + } + output_interfaces = { + } +} + +# --- END MANAGED SECTION --- Add your custom outputs below --- + +# Add your custom Terraform outputs below this line. diff --git a/modules/clickhouse/k8s/1.0/variables.tf b/modules/clickhouse/k8s/1.0/variables.tf new file mode 100644 index 00000000..ea263472 --- /dev/null +++ b/modules/clickhouse/k8s/1.0/variables.tf @@ -0,0 +1,88 @@ +# ╔═══════════════════════════════════════════════════════════╗ +# ║ AUTO-GENERATED from facets.yaml — DO NOT EDIT ║ +# ║ ║ +# ║ Any changes will be overwritten on next mutation command. ║ +# ╚═══════════════════════════════════════════════════════════╝ + +variable "instance" { + type = object({ + spec = object({ + clickhouse_version = optional(string) + cluster_domain = optional(string) + cluster_name = optional(string) + namespace = optional(string) + replicas = optional(number) + resources = optional(object({ + cpu = optional(string) + memory = optional(string) + })) + settings_json = optional(string) + shards = optional(number) + storage_class = optional(string) + storage_size = optional(string) + users_json = optional(string) + }) + }) +} + +variable "instance_name" { + type = string + description = "Resource name in the blueprint (architectural name, e.g. main-db, api)" +} + +variable "environment" { + type = object({ + name = string # Environment name (e.g., dev, staging, prod) + unique_name = string # Project + environment (globally unique, e.g., myapp-prod) + }) + description = "Environment context. Use unique_name + instance_name for globally unique resource names." +} + +variable "inputs" { + type = object({ + clickhouse_keeper = object({ + attributes = optional(object({ + namespace = optional(string) + port = optional(number) + replicas = optional(number) + service_host = optional(string) + })) + interfaces = optional(object({})) + }) + clickhouse_operator = object({ + attributes = optional(object({ + namespace = optional(string) + release_name = optional(string) + })) + interfaces = optional(object({})) + }) + kubernetes_details = object({ + attributes = optional(object({ + cloud_provider = optional(string) + cluster_endpoint = optional(string) + cluster_id = optional(string) + cluster_location = optional(string) + cluster_name = optional(string) + })) + interfaces = optional(object({ + kubernetes = optional(object({ + cluster_ca_certificate = optional(string) + host = optional(string) + })) + })) + }) + node_pool = object({ + attributes = optional(object({ + node_class_name = optional(string) + node_pool_name = optional(string) + node_selector = optional(object({})) + taints = optional(list(object({ + effect = optional(string) + key = optional(string) + value = optional(string) + }))) + })) + interfaces = optional(object({})) + }) + }) +} diff --git a/modules/clickhouse_keeper/k8s/1.0/README.md b/modules/clickhouse_keeper/k8s/1.0/README.md new file mode 100644 index 00000000..403fb52d --- /dev/null +++ b/modules/clickhouse_keeper/k8s/1.0/README.md @@ -0,0 +1,58 @@ +# clickhouse_keeper / k8s / 1.0 + +Deploys a ClickHouse Keeper ensemble (replication coordination) as a +`ClickHouseKeeperInstallation` (CHK) custom resource, applied through a small +local Helm chart under `chart/` so Terraform never needs the CRD registered at +plan time. The Altinity operator (an input dependency, `clickhouse_operator`) +installs the CRD and must be deployed first — Facets orders it via the input. + +- **CRD apiVersion / kind**: `clickhouse-keeper.altinity.com/v1` / + `ClickHouseKeeperInstallation`. +- **Service DNS (`service_host` output)**: + `keeper-..svc.cluster.local`. The Altinity operator + creates the client Service for a CHK named `` as + `keeper-`. Verify against your operator version; if it differs, + update the `service_host` expression in `outputs.tf`. +- **Port**: `2181` (Keeper client port). + +## Dedicated node-pool placement (`node_pool` input) + +Optional input `node_pool` (`@facets/kubernetes_nodepool`, `optional: true`). +Wire it to pin the Keeper ensemble onto a dedicated, tainted node pool; leave it +unwired and the module schedules anywhere (no `nodeSelector`/`tolerations` +rendered). Placement is read defensively in `main.tf`: + +- `node_selector` — a **merge** of the output's custom `node_selector` and the + always-present GKE pool label: + `merge(try(var.inputs.node_pool.attributes.node_selector, {}), local.node_pool_name != "" ? { "cloud.google.com/gke-nodepool" = local.node_pool_name } : {})` + → `spec.templates.podTemplates[].spec.nodeSelector` (guarded by `{{- if }}`). +- `taints` ← `try(var.inputs.node_pool.attributes.taints, [])`, each + `{key,value,effect}` mapped to a toleration `{key,value,effect,operator:"Equal"}` + → `spec.templates.podTemplates[].spec.tolerations` (guarded by `{{- if }}`). + +**Why the GKE pool label (eu-prod pilot finding):** the node_pool output's +custom `node_selector` arrived **empty** at apply. Every node in a GKE pool +reliably carries `cloud.google.com/gke-nodepool=`, and the output +always exposes `node_pool_name` — so the module pins on that label. When +`node_pool` is unwired, `node_pool_name` is `""`, the merge yields `{}`, and no +`nodeSelector` renders. + +The `kubernetes_node_pool/gcp/1.0` output emits `taints[].effect` already in +Kubernetes form (`NoSchedule`/`PreferNoSchedule`/`NoExecute`), so the effect is +passed through verbatim — no `NO_SCHEDULE`→`NoSchedule` normalization is needed. +`variables.tf` types `node_selector` as `map(string)` (not the schema-default +empty `object({})`, which silently drops label keys during Terraform coercion). +If a future `raptor module` mutation regenerates `variables.tf`, re-widen +`node_selector` back to `map(string)`. + +### podTemplate application + ensemble spread + +The CHK references its podTemplate via `spec.defaults.templates.podTemplate: +default` (+ `dataVolumeClaimTemplate: default`). **This reference is required** — +an unreferenced podTemplate named `default` is ignored by the operator, which is +why the Keeper's `nodeSelector` did not reach the pods in the eu-prod pilot until +the `defaults` block was added. + +The Keeper relies on `nodeSelector` pinning only — no pod anti-affinity and no +`podDistribution` is injected (the CHK layout differs from the CHI; the raw +`podAntiAffinity` earlier versions rendered was removed after the pilot). diff --git a/modules/clickhouse_keeper/k8s/1.0/chart/Chart.yaml b/modules/clickhouse_keeper/k8s/1.0/chart/Chart.yaml new file mode 100644 index 00000000..84f22dcf --- /dev/null +++ b/modules/clickhouse_keeper/k8s/1.0/chart/Chart.yaml @@ -0,0 +1,6 @@ +apiVersion: v2 +name: clickhouse-keeper-chk +description: Renders an Altinity ClickHouseKeeperInstallation (CHK) custom resource +type: application +version: 1.0.0 +appVersion: "1.0" diff --git a/modules/clickhouse_keeper/k8s/1.0/chart/templates/keeper.yaml b/modules/clickhouse_keeper/k8s/1.0/chart/templates/keeper.yaml new file mode 100644 index 00000000..079d220f --- /dev/null +++ b/modules/clickhouse_keeper/k8s/1.0/chart/templates/keeper.yaml @@ -0,0 +1,58 @@ +apiVersion: "clickhouse-keeper.altinity.com/v1" +kind: "ClickHouseKeeperInstallation" +metadata: + name: {{ .Values.keeperName | quote }} + namespace: {{ .Values.namespace | quote }} +spec: + # Cluster DNS domain override — keep host-FQDN construction correct on + # clusters whose DNS domain is not cluster.local (kept in sync with the CHI). + namespaceDomainPattern: "%s.svc.{{ .Values.clusterDomain }}" + defaults: + templates: + podTemplate: default + dataVolumeClaimTemplate: default + configuration: + clusters: + - name: "keeper" + layout: + replicasCount: {{ int .Values.replicas }} + settings: + logger/level: "information" + templates: + podTemplates: + - name: default + spec: + {{- if .Values.nodeSelector }} + nodeSelector: + {{- range $k, $v := .Values.nodeSelector }} + {{ $k }}: {{ $v | quote }} + {{- end }} + {{- end }} + {{- if .Values.tolerations }} + tolerations: + {{- range .Values.tolerations }} + - key: {{ .key | quote }} + value: {{ .value | quote }} + effect: {{ .effect | quote }} + operator: {{ .operator | default "Equal" | quote }} + {{- end }} + {{- end }} + containers: + - name: clickhouse-keeper + image: {{ .Values.image | quote }} + resources: + requests: + cpu: {{ .Values.resources.cpu | quote }} + memory: {{ .Values.resources.memory | quote }} + limits: + cpu: {{ .Values.resources.cpu | quote }} + memory: {{ .Values.resources.memory | quote }} + volumeClaimTemplates: + - name: default + spec: + storageClassName: {{ .Values.storageClass | quote }} + accessModes: + - ReadWriteOnce + resources: + requests: + storage: {{ .Values.storageSize | quote }} diff --git a/modules/clickhouse_keeper/k8s/1.0/chart/values.yaml b/modules/clickhouse_keeper/k8s/1.0/chart/values.yaml new file mode 100644 index 00000000..846c917d --- /dev/null +++ b/modules/clickhouse_keeper/k8s/1.0/chart/values.yaml @@ -0,0 +1,17 @@ +# Defaults are overridden by the Terraform helm_release values block. +keeperName: clickhouse-keeper +namespace: clickhouse +replicas: 3 +image: clickhouse/clickhouse-keeper:24.8-alpine +storageClass: premium-rwo +storageSize: 20Gi +# Kubernetes cluster DNS domain (overridden by the Terraform helm_release values). +clusterDomain: cluster.local +resources: + cpu: "500m" + memory: "512Mi" +# Dedicated node-pool placement (populated from the optional node_pool input). +# nodeSelector: map of labels; tolerations: list of {key,value,effect,operator}. +# Both empty by default so an unwired node_pool renders no placement. +nodeSelector: {} +tolerations: [] diff --git a/modules/clickhouse_keeper/k8s/1.0/facets.yaml b/modules/clickhouse_keeper/k8s/1.0/facets.yaml new file mode 100644 index 00000000..c57c279f --- /dev/null +++ b/modules/clickhouse_keeper/k8s/1.0/facets.yaml @@ -0,0 +1,115 @@ +# ╔═══════════════════════════════════════════════════════════╗ +# ║ MANAGED BY: raptor module — DO NOT EDIT DIRECTLY ║ +# ║ ║ +# ║ Modify via: ║ +# ║ raptor module set-spec --add ║ +# ║ raptor module add-input / remove-input ║ +# ║ raptor module add-output / remove-output ║ +# ║ raptor module show | raptor module validate ║ +# ╚═══════════════════════════════════════════════════════════╝ +intent: clickhouse_keeper +flavor: k8s +version: "1.0" +description: ClickHouse Keeper ensemble (replication coordination) via Altinity operator CHK CRD +clouds: + - kubernetes + - gcp +inputs: + clickhouse_operator: + type: '@facets/clickhouse-operator' + kubernetes_details: + type: '@facets/kubernetes-details' + providers: + - helm + - kubernetes + node_pool: + type: '@facets/kubernetes_nodepool' +outputs: + default: + type: '@facets/clickhouse-keeper' +spec: + properties: + cluster_domain: + default: cluster.local + description: Kubernetes cluster DNS domain. Override per-env when the cluster is not the default cluster.local (some GKE clusters use a custom domain e.g. the env name). + maxLength: 253 + minLength: 1 + title: Cluster DNS Domain + type: string + keeper_name: + default: clickhouse-keeper + description: Name of the ClickHouseKeeperInstallation resource + maxLength: 40 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + title: Keeper Name + type: string + namespace: + default: clickhouse + description: Kubernetes namespace to deploy the Keeper ensemble into + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + title: Namespace + type: string + replicas: + default: 3 + description: Number of Keeper replicas in the ensemble (odd number recommended) + maximum: 9 + minimum: 1 + title: Replicas + type: number + resources: + properties: + cpu: + default: 500m + description: CPU request and limit per Keeper pod (e.g. 500m) + maxLength: 16 + minLength: 1 + title: CPU Request/Limit + type: string + memory: + default: 512Mi + description: Memory request and limit per Keeper pod (e.g. 512Mi) + maxLength: 16 + minLength: 1 + title: Memory Request/Limit + type: string + title: Resources + type: object + storage_class: + default: premium-rwo + description: StorageClass for the Keeper persistent volumes + maxLength: 63 + minLength: 1 + title: Storage Class + type: string + storage_size: + default: 20Gi + description: Persistent volume size per Keeper replica (e.g. 20Gi) + maxLength: 16 + minLength: 2 + title: Storage Size + type: string + type: object + x-ui-order: + - keeper_name + - namespace + - replicas + - storage_size + - storage_class + - resources + - cluster_domain +sample: + flavor: k8s + kind: clickhouse_keeper + spec: + cluster_domain: cluster.local + keeper_name: clickhouse-keeper + namespace: clickhouse + replicas: 3 + resources: + cpu: 500m + storage_class: premium-rwo + storage_size: 20Gi + version: "1.0" diff --git a/modules/clickhouse_keeper/k8s/1.0/main.tf b/modules/clickhouse_keeper/k8s/1.0/main.tf new file mode 100644 index 00000000..7d557c06 --- /dev/null +++ b/modules/clickhouse_keeper/k8s/1.0/main.tf @@ -0,0 +1,83 @@ +# ╔═══════════════════════════════════════════════════════════╗ +# ║ Module: clickhouse_keeper/k8s/1.0 ║ +# ║ Renders a ClickHouseKeeperInstallation (CHK) via a local ║ +# ║ helm chart so no plan-time CRD schema check is needed. ║ +# ║ Ordering: depends on clickhouse_operator input (CRDs). ║ +# ╚═══════════════════════════════════════════════════════════╝ + +locals { + spec = var.instance.spec + keeper_name = lookup(local.spec, "keeper_name", "clickhouse-keeper") + namespace = lookup(local.spec, "namespace", "clickhouse") + replicas = lookup(local.spec, "replicas", 3) + storage_size = lookup(local.spec, "storage_size", "20Gi") + storage_class = lookup(local.spec, "storage_class", "premium-rwo") + cluster_domain = lookup(local.spec, "cluster_domain", "cluster.local") + resources = lookup(local.spec, "resources", {}) + cpu = lookup(local.resources, "cpu", "500m") + memory = lookup(local.resources, "memory", "512Mi") + + # Dedicated node-pool placement (optional input — read defensively). + # node_selector: map of labels to pin the pods onto the pool's nodes. + # taints: list of {key,value,effect} translated into tolerations so the pods + # are allowed onto the tainted pool. Both collapse to empty when unwired. + # The node_pool output's custom node_selector can arrive empty, but every + # node in a GKE pool reliably carries cloud.google.com/gke-nodepool=, + # and the output always exposes node_pool_name — so we pin on that label. + node_pool_name = try(var.inputs.node_pool.attributes.node_pool_name, "") + node_selector = merge( + try(var.inputs.node_pool.attributes.node_selector, {}), + local.node_pool_name != "" ? { "cloud.google.com/gke-nodepool" = local.node_pool_name } : {} + ) + # GKE reports taint effect as NO_SCHEDULE/PREFER_NO_SCHEDULE/NO_EXECUTE; + # k8s tolerations require the camelCase form. Normalise (passthrough if already correct). + effect_map = { + NO_SCHEDULE = "NoSchedule" + PREFER_NO_SCHEDULE = "PreferNoSchedule" + NO_EXECUTE = "NoExecute" + NoSchedule = "NoSchedule" + PreferNoSchedule = "PreferNoSchedule" + NoExecute = "NoExecute" + } + node_taints = try(var.inputs.node_pool.attributes.taints, []) + tolerations = [ + for t in local.node_taints : { + key = t.key + value = t.value + effect = lookup(local.effect_map, t.effect, t.effect) + operator = "Equal" + } + ] +} + +resource "helm_release" "keeper" { + name = local.keeper_name + chart = "${path.module}/chart" + namespace = local.namespace + create_namespace = true + wait = true + cleanup_on_fail = true + + values = [ + yamlencode({ + keeperName = local.keeper_name + namespace = local.namespace + replicas = local.replicas + image = "clickhouse/clickhouse-keeper:24.8-alpine" + storageClass = local.storage_class + storageSize = local.storage_size + clusterDomain = local.cluster_domain + resources = { + cpu = local.cpu + memory = local.memory + } + # Dedicated node-pool placement (empty when node_pool input is unwired). + nodeSelector = local.node_selector + tolerations = local.tolerations + }) + ] +} + +# The operator (and its CRDs) must exist before this CHK CR is applied. +# Facets deploys the clickhouse_operator input resource first (it is a +# declared input dependency), so ordering is handled at the blueprint level. diff --git a/modules/clickhouse_keeper/k8s/1.0/outputs.tf b/modules/clickhouse_keeper/k8s/1.0/outputs.tf new file mode 100644 index 00000000..9f015366 --- /dev/null +++ b/modules/clickhouse_keeper/k8s/1.0/outputs.tf @@ -0,0 +1,22 @@ +# ╔═══════════════════════════════════════════════════════════╗ +# ║ Output contract: @facets/clickhouse-keeper ║ +# ║ Keys managed by CLI — fill in the values only ║ +# ║ Do not add or remove keys. Do not rename. ║ +# ║ ║ +# ║ View schema: raptor get output-type @facets/clickhouse-ke ║ +# ╚═══════════════════════════════════════════════════════════╝ + +locals { + output_attributes = { + namespace = local.namespace + port = 2181 + replicas = local.replicas + service_host = "keeper-${local.keeper_name}.${local.namespace}.svc.${local.cluster_domain}" + } + output_interfaces = { + } +} + +# --- END MANAGED SECTION --- Add your custom outputs below --- + +# Add your custom Terraform outputs below this line. diff --git a/modules/clickhouse_keeper/k8s/1.0/variables.tf b/modules/clickhouse_keeper/k8s/1.0/variables.tf new file mode 100644 index 00000000..c24cdf58 --- /dev/null +++ b/modules/clickhouse_keeper/k8s/1.0/variables.tf @@ -0,0 +1,75 @@ +# ╔═══════════════════════════════════════════════════════════╗ +# ║ AUTO-GENERATED from facets.yaml — DO NOT EDIT ║ +# ║ ║ +# ║ Any changes will be overwritten on next mutation command. ║ +# ╚═══════════════════════════════════════════════════════════╝ + +variable "instance" { + type = object({ + spec = object({ + cluster_domain = optional(string) + keeper_name = optional(string) + namespace = optional(string) + replicas = optional(number) + resources = optional(object({ + cpu = optional(string) + memory = optional(string) + })) + storage_class = optional(string) + storage_size = optional(string) + }) + }) +} + +variable "instance_name" { + type = string + description = "Resource name in the blueprint (architectural name, e.g. main-db, api)" +} + +variable "environment" { + type = object({ + name = string # Environment name (e.g., dev, staging, prod) + unique_name = string # Project + environment (globally unique, e.g., myapp-prod) + }) + description = "Environment context. Use unique_name + instance_name for globally unique resource names." +} + +variable "inputs" { + type = object({ + clickhouse_operator = object({ + attributes = optional(object({ + namespace = optional(string) + release_name = optional(string) + })) + interfaces = optional(object({})) + }) + kubernetes_details = object({ + attributes = optional(object({ + cloud_provider = optional(string) + cluster_endpoint = optional(string) + cluster_id = optional(string) + cluster_location = optional(string) + cluster_name = optional(string) + })) + interfaces = optional(object({ + kubernetes = optional(object({ + cluster_ca_certificate = optional(string) + host = optional(string) + })) + })) + }) + node_pool = object({ + attributes = optional(object({ + node_class_name = optional(string) + node_pool_name = optional(string) + node_selector = optional(object({})) + taints = optional(list(object({ + effect = optional(string) + key = optional(string) + value = optional(string) + }))) + })) + interfaces = optional(object({})) + }) + }) +} diff --git a/modules/clickhouse_operator/k8s/1.0/facets.yaml b/modules/clickhouse_operator/k8s/1.0/facets.yaml new file mode 100644 index 00000000..ba9545be --- /dev/null +++ b/modules/clickhouse_operator/k8s/1.0/facets.yaml @@ -0,0 +1,77 @@ +# ╔═══════════════════════════════════════════════════════════╗ +# ║ MANAGED BY: raptor module — DO NOT EDIT DIRECTLY ║ +# ║ ║ +# ║ Modify via: ║ +# ║ raptor module set-spec --add ║ +# ║ raptor module add-input / remove-input ║ +# ║ raptor module add-output / remove-output ║ +# ║ raptor module show | raptor module validate ║ +# ╚═══════════════════════════════════════════════════════════╝ +intent: clickhouse_operator +flavor: k8s +version: "1.0" +description: Altinity ClickHouse Operator — installs the ClickHouseInstallation/ClickHouseKeeperInstallation CRDs and the operator +clouds: + - kubernetes + - gcp +inputs: + kubernetes_details: + type: '@facets/kubernetes-details' + providers: + - helm + - kubernetes + node_pool: + type: '@facets/kubernetes_nodepool' + optional: true +outputs: + default: + type: '@facets/clickhouse-operator' +spec: + properties: + namespace: + default: clickhouse + description: Kubernetes namespace to install the operator into + maxLength: 63 + minLength: 1 + pattern: ^[a-z0-9]([-a-z0-9]*[a-z0-9])?$ + title: Namespace + type: string + operator_version: + default: 0.24.0 + description: Version of the altinity-clickhouse-operator Helm chart + maxLength: 20 + minLength: 1 + title: Operator Version + type: string + resources: + properties: + cpu: + default: 100m + description: CPU request and limit for the operator pod (e.g. 100m) + maxLength: 16 + minLength: 1 + title: CPU Request/Limit + type: string + memory: + default: 256Mi + description: Memory request and limit for the operator pod (e.g. 256Mi) + maxLength: 16 + minLength: 1 + title: Memory Request/Limit + type: string + title: Resources + type: object + type: object + x-ui-order: + - operator_version + - namespace + - resources +sample: + flavor: k8s + kind: clickhouse_operator + spec: + namespace: clickhouse + operator_version: 0.24.0 + resources: + cpu: 100m + version: "1.0" diff --git a/modules/clickhouse_operator/k8s/1.0/main.tf b/modules/clickhouse_operator/k8s/1.0/main.tf new file mode 100644 index 00000000..f374224e --- /dev/null +++ b/modules/clickhouse_operator/k8s/1.0/main.tf @@ -0,0 +1,85 @@ +# ╔═══════════════════════════════════════════════════════════╗ +# ║ Module: clickhouse_operator/k8s/1.0 ║ +# ║ Installs the Altinity ClickHouse operator + CRDs via Helm ║ +# ╚═══════════════════════════════════════════════════════════╝ + +locals { + spec = var.instance.spec + namespace = lookup(local.spec, "namespace", "clickhouse") + resources = lookup(local.spec, "resources", {}) + cpu = lookup(local.resources, "cpu", "100m") + memory = lookup(local.resources, "memory", "256Mi") + + # Dedicated node-pool placement (optional input — read defensively). + # The node_pool output's custom node_selector can arrive empty, but every node + # in a GKE pool reliably carries cloud.google.com/gke-nodepool=, and the + # output always exposes node_pool_name — so we pin on that label. + node_pool_name = try(var.inputs.node_pool.attributes.node_pool_name, "") + node_selector = merge( + try(var.inputs.node_pool.attributes.node_selector, {}), + local.node_pool_name != "" ? { "cloud.google.com/gke-nodepool" = local.node_pool_name } : {} + ) + # GKE reports taint effect as NO_SCHEDULE/PREFER_NO_SCHEDULE/NO_EXECUTE; + # k8s tolerations require the camelCase form. Normalise (passthrough if already correct). + effect_map = { + NO_SCHEDULE = "NoSchedule" + PREFER_NO_SCHEDULE = "PreferNoSchedule" + NO_EXECUTE = "NoExecute" + NoSchedule = "NoSchedule" + PreferNoSchedule = "PreferNoSchedule" + NoExecute = "NoExecute" + } + node_taints = try(var.inputs.node_pool.attributes.taints, []) + tolerations = [ + for t in local.node_taints : { + key = t.key + value = t.value + effect = lookup(local.effect_map, t.effect, t.effect) + operator = "Equal" + } + ] + + # Only emit placement keys when a node_pool is wired, so the chart defaults + # (unpinned) are preserved otherwise. Both the operator Deployment (top-level + # nodeSelector/tolerations) and the CRD-install Job (crdHook.*) are pinned — + # the crdHook is a pre-install hook and would otherwise fail to schedule on + # the tainted/at-capacity default nodes and hang the release. + placement = merge( + length(local.node_selector) > 0 ? { nodeSelector = local.node_selector } : {}, + length(local.tolerations) > 0 ? { tolerations = local.tolerations } : {}, + ) + crd_hook = length(local.placement) > 0 ? { crdHook = local.placement } : {} +} + +resource "helm_release" "operator" { + name = "clickhouse-operator" + repository = "https://docs.altinity.com/clickhouse-operator/" + chart = "altinity-clickhouse-operator" + version = lookup(local.spec, "operator_version", "0.24.0") + namespace = local.namespace + create_namespace = true + wait = true + cleanup_on_fail = true + + values = [ + yamlencode(merge({ + operator = { + resources = { + requests = { + cpu = local.cpu + memory = local.memory + } + limits = { + cpu = local.cpu + memory = local.memory + } + } + } + }, + # Top-level nodeSelector/tolerations pin the operator Deployment pod. + local.placement, + # crdHook.nodeSelector/tolerations pin the CRD-install Job. + local.crd_hook, + )) + ] +} diff --git a/modules/clickhouse_operator/k8s/1.0/outputs.tf b/modules/clickhouse_operator/k8s/1.0/outputs.tf new file mode 100644 index 00000000..117aeba4 --- /dev/null +++ b/modules/clickhouse_operator/k8s/1.0/outputs.tf @@ -0,0 +1,20 @@ +# ╔═══════════════════════════════════════════════════════════╗ +# ║ Output contract: @facets/clickhouse-operator ║ +# ║ Keys managed by CLI — fill in the values only ║ +# ║ Do not add or remove keys. Do not rename. ║ +# ║ ║ +# ║ View schema: raptor get output-type @facets/clickhouse-op ║ +# ╚═══════════════════════════════════════════════════════════╝ + +locals { + output_attributes = { + namespace = helm_release.operator.namespace + release_name = helm_release.operator.name + } + output_interfaces = { + } +} + +# --- END MANAGED SECTION --- Add your custom outputs below --- + +# Add your custom Terraform outputs below this line. diff --git a/modules/clickhouse_operator/k8s/1.0/variables.tf b/modules/clickhouse_operator/k8s/1.0/variables.tf new file mode 100644 index 00000000..58728b7e --- /dev/null +++ b/modules/clickhouse_operator/k8s/1.0/variables.tf @@ -0,0 +1,64 @@ +# ╔═══════════════════════════════════════════════════════════╗ +# ║ AUTO-GENERATED from facets.yaml — DO NOT EDIT ║ +# ║ ║ +# ║ Any changes will be overwritten on next mutation command. ║ +# ╚═══════════════════════════════════════════════════════════╝ + +variable "instance" { + type = object({ + spec = object({ + namespace = optional(string) + operator_version = optional(string) + resources = optional(object({ + cpu = optional(string) + memory = optional(string) + })) + }) + }) +} + +variable "instance_name" { + type = string + description = "Resource name in the blueprint (architectural name, e.g. main-db, api)" +} + +variable "environment" { + type = object({ + name = string # Environment name (e.g., dev, staging, prod) + unique_name = string # Project + environment (globally unique, e.g., myapp-prod) + }) + description = "Environment context. Use unique_name + instance_name for globally unique resource names." +} + +variable "inputs" { + type = object({ + kubernetes_details = object({ + attributes = optional(object({ + cloud_provider = optional(string) + cluster_endpoint = optional(string) + cluster_id = optional(string) + cluster_location = optional(string) + cluster_name = optional(string) + })) + interfaces = optional(object({ + kubernetes = optional(object({ + cluster_ca_certificate = optional(string) + host = optional(string) + })) + })) + }) + node_pool = object({ + attributes = optional(object({ + node_class_name = optional(string) + node_pool_name = optional(string) + node_selector = optional(map(string)) + taints = optional(list(object({ + effect = optional(string) + key = optional(string) + value = optional(string) + }))) + })) + interfaces = optional(object({})) + }) + }) +} diff --git a/outputs/clickhouse-keeper/outputs.yaml b/outputs/clickhouse-keeper/outputs.yaml new file mode 100644 index 00000000..93e021b5 --- /dev/null +++ b/outputs/clickhouse-keeper/outputs.yaml @@ -0,0 +1,23 @@ +name: '@facets/clickhouse-keeper' +properties: + type: object + properties: + attributes: + type: object + properties: + namespace: + description: Kubernetes namespace where the ClickHouse Keeper ensemble runs + type: string + service_host: + description: In-cluster DNS name of the ClickHouse Keeper service + type: string + port: + description: Client port the Keeper ensemble listens on (2181) + type: number + replicas: + description: Number of Keeper replicas in the ensemble + type: number + interfaces: + type: object + properties: {} +providers: [] diff --git a/outputs/clickhouse-operator/outputs.yaml b/outputs/clickhouse-operator/outputs.yaml new file mode 100644 index 00000000..28e929eb --- /dev/null +++ b/outputs/clickhouse-operator/outputs.yaml @@ -0,0 +1,17 @@ +name: '@facets/clickhouse-operator' +properties: + type: object + properties: + attributes: + type: object + properties: + namespace: + description: Kubernetes namespace where the ClickHouse operator is installed + type: string + release_name: + description: Helm release name of the ClickHouse operator + type: string + interfaces: + type: object + properties: {} +providers: [] diff --git a/outputs/clickhouse/outputs.yaml b/outputs/clickhouse/outputs.yaml new file mode 100644 index 00000000..e435a77a --- /dev/null +++ b/outputs/clickhouse/outputs.yaml @@ -0,0 +1,26 @@ +name: '@facets/clickhouse' +properties: + type: object + properties: + attributes: + type: object + properties: + cluster_name: + description: Logical ClickHouse cluster name + type: string + namespace: + description: Kubernetes namespace where the ClickHouse cluster runs + type: string + service_host: + description: In-cluster DNS name of the ClickHouse cluster service + type: string + http_port: + description: ClickHouse HTTP interface port (8123) + type: number + tcp_port: + description: ClickHouse native TCP protocol port (9000) + type: number + interfaces: + type: object + properties: {} +providers: []