Kvindo™ Cloud Docs
A Kafka Cluster is a managed Apache Kafka deployment running in KRaft mode — the modern consensus design where Kafka itself elects a leader from a dedicated set of controller nodes, with no ZooKeeper ensemble to operate. Each node runs on its own dedicated VM, and the cluster is described as one resource: spec.brokers and spec.controllers each hold one entry per node, listing only its name and subnet.
• Two roles, sized independently — brokers hold your data, controllers hold only cluster metadata. Sizing is one offer triplet per role (brokerVmOfferId/brokerVolumeOfferId/brokerVolumeSizeGiB and the controller* equivalents) rather than per node, so a role's nodes are interchangeable by design and a heterogeneous role simply cannot be described
• Controllers: exactly 1 or 3 — the KRaft quorum needs an odd voter count. 1 is a documented non-HA tier that can later be grown to 3; shrinking a controller set is rejected
• Brokers: 1..N, grow-only — a modify may add one broker at a time. Kafka has no shrink operation, so removing a broker is rejected outright instead of being silently ignored
• Node name is the real identity — the server resolves each entry against this cluster's existing members by name and reuses the same node (and its VM) when it matches. Renaming is treated as removing the old node and adding a new one, deliberately, so don't rename a node you want to keep
• A NAT floating IP is required to create a cluster — every node installs Java at bootstrap, and controllers are never given a floating IP of their own, so they need outbound internet through the VPC's NAT. This is a create-time precondition, not a runtime caveat
• No cluster-wide credential on this resource — the internal admin SCRAM credential is generated and stored server-side only; create a Kafka User to get a real, tenant-facing password
• Public access is TLS-only — private by default. Setting spec.createPublicIpv4 adds the TLS listener on port 9096 advertised on a floating IP; the plaintext listener stays bound to the private network, so a public cluster is never reachable without TLS
| UI Name | API / kc name | Terraform name | Optional | Format | Default | Is read only | Description |
|---|---|---|---|---|---|---|---|
| Brokers | spec.brokers |
spec.brokers |
No | list of objects | — | No | One entry per broker — 1..N, grow-only |
| Broker name | brokers[].name |
brokers[].name |
No | string | — | No | The real identity — reuse across modifies to keep an existing broker; renaming is treated as remove-and-add |
| Broker subnet | brokers[].vpcSubnetId |
brokers[].vpc_subnet_id |
No | ULID — references a VPC Subnet | — | No | Subnet for this broker's VM |
| Controllers | spec.controllers |
spec.controllers |
No | list of objects | — | No | Exactly 1 or 3 — the KRaft quorum needs an odd voter count |
| Controller name | controllers[].name |
controllers[].name |
No | string | — | No | Same identity rule as brokers: renaming replaces the node |
| Controller subnet | controllers[].vpcSubnetId |
controllers[].vpc_subnet_id |
No | ULID — references a VPC Subnet | — | No | Subnet for this controller's VM |
| Parameters set | spec.parametersSetId |
spec.parameters_set_id |
Yes | ULID — references a KafkaParametersSet | — | No | Broker-level properties applied to the whole cluster; swapping which set is referenced is allowed freely on a modify |
| Kafka version | spec.kafkaVersion |
spec.kafka_version |
No | string — Kafka version | 4.3.1 |
No | Must be a version the deployed image prefetched — the platform installs Kafka only from the tarball it ships. Immutable in v1: there is no rolling upgrade |
| Broker VM offer | spec.brokerVmOfferId |
spec.broker_vm_offer_id |
No | string (≤32) — compute offer | — | No | Applies to every broker. Resolved and written back server-side when omitted; see the Console catalog for real offer ids (e.g. g3-2c4-100) |
| Broker volume offer | spec.brokerVolumeOfferId |
spec.broker_volume_offer_id |
No | string (≤32) — disk offer | — | No | Applies to every broker (e.g. gp3-750) |
| Broker volume size | spec.brokerVolumeSizeGiB |
spec.broker_volume_size_gib |
No | integer — GiB | — | No | Applies to every broker |
| Controller VM offer | spec.controllerVmOfferId |
spec.controller_vm_offer_id |
No | string (≤32) — compute offer | g3-1c1-100 |
No | Applies to every controller — a controller only serves metadata, so it is sized far smaller than a broker |
| Controller volume offer | spec.controllerVolumeOfferId |
spec.controller_volume_offer_id |
No | string (≤32) — disk offer | gp3-750 |
No | Applies to every controller |
| Controller volume size | spec.controllerVolumeSizeGiB |
spec.controller_volume_size_gib |
No | integer — GiB | 10 |
No | Applies to every controller |
| Use FQDN | spec.useFqdn |
spec.use_fqdn |
Yes | boolean | false |
No | Advertise DNS names instead of raw IPs, so a node's address can change without every client re-seeding. Immutable after creation |
| Public IPv4 | spec.createPublicIpv4 |
spec.create_public_ipv4 |
Yes | boolean | false |
No | Adds the TLS listener (port 9096) on a floating IP. Immutable after creation |
| Cluster id | status.clusterId |
status.cluster_id |
— | string | — | Yes | The Kafka cluster id — stable for the cluster's lifetime |
| CA certificate | status.caCertificate |
status.ca_certificate |
— | string (PEM) | — | Yes | The public half of the cluster CA — a client needs it as its truststore to reach the TLS listeners. Credential-free |
| Bootstrap (plaintext) | status.bootstrapServersPlaintext |
status.bootstrap_servers_plaintext |
— | list of strings | — | Yes | host:9092 for every broker — private addresses only, always populated even on a public cluster |
| Bootstrap (TLS) | status.bootstrapServersTls |
status.bootstrap_servers_tls |
— | list of strings | — | Yes | host:9093 — SASL_SSL over the private network |
| Bootstrap (public) | status.bootstrapServersPublic |
status.bootstrap_servers_public |
— | list of strings | — | Yes | host:9096 — SASL_SSL over the floating IP; empty unless spec.createPublicIpv4 |
| DNS seed FQDN | status.dnsSeedFqdn |
status.dns_seed_fqdn |
— | string | — | Yes | Set only when spec.useFqdn is enabled |
| Cluster state | status.clusterState |
status.cluster_state |
— | string | — | Yes | One of ok, degraded, no-quorum, or null when unknown |
| Active controllers | status.activeControllerCount |
status.active_controller_count |
— | integer | — | Yes | How many controllers are currently voters in the metadata quorum |
| Quorum leader | status.quorumLeaderNodeId |
status.quorum_leader_node_id |
— | integer | — | Yes | KRaft node.id of the current metadata-quorum leader |
| Under-replicated | status.underReplicatedPartitions |
status.under_replicated_partitions |
— | integer | — | Yes | Partitions with fewer in-sync replicas than their replication factor |
| Offline partitions | status.offlinePartitions |
status.offline_partitions |
— | integer | — | Yes | Partitions with no leader at all — non-zero means an outage for those topics |
| Unexpected topics | status.unexpectedTopics |
status.unexpected_topics |
— | list of strings | — | Yes | Topics that exist on the cluster but were never declared through the API. Reported, never deleted — a Streams/Debezium app whose internal topic was refused must surface, not vanish |
| Anti-affinity OK | status.antiAffinityOk |
status.anti_affinity_ok |
— | boolean | — | Yes | False when two nodes that should be separate share a VM (degraded placement) |
| Anti-affinity message | status.antiAffinityMessage |
status.anti_affinity_message |
— | string | — | Yes | Human-readable detail behind antiAffinityOk |
| Brokers | status.brokers |
status.brokers |
— | list of objects | — | Yes | Observed state per broker |
| Broker node id | brokers[].nodeId |
brokers[].node_id |
— | integer | — | Yes | The KRaft node.id the platform assigned — stable for the VM's lifetime, never reissued |
| Broker observed state | brokers[].observedState |
brokers[].observed_state |
— | string | — | Yes | One of active, fenced, unregistered, unknown |
| Broker leaders | brokers[].leaderPartitionCount |
brokers[].leader_partition_count |
— | integer | — | Yes | How many partition leaders this broker currently hosts |
| Controllers | status.controllers |
status.controllers |
— | list of objects | — | Yes | Observed state per controller |
| Quorum leader? | controllers[].isQuorumLeader |
controllers[].is_quorum_leader |
— | boolean | — | Yes | True for the controller currently leading the metadata quorum |
| Controller lag | controllers[].lagOffsets |
controllers[].lag_offsets |
— | integer | — | Yes | How far this controller trails the leader |
A single-broker, single-controller cluster — the smallest tier that is real, and a legitimate dev/cost starting point before growing to 3 controllers.
Terraform
Python SDK
kc CLI
Resource docs
Examples