Skip to content

Feature request: ClusterIP-only exposure without LoadBalancer or ingress resources #918

Open

Description

@itenev

Use case

Allow a CrateDB cluster to serve clients exclusively inside its Kubernetes
cluster, through an ordinary ClusterIP Service, without provisioning a cloud
load balancer or creating ingress resources.

This would avoid unnecessary infrastructure, cost and exposure for in-cluster
applications, and avoid requiring cloud load-balancer provisioning permissions
when external access is not wanted.

Current behavior

The operator's cluster exposure API currently offers loadbalancer and
traefik:

  • Omitting spec.cluster.exposure defaults to loadbalancer. The data Service
    has type: LoadBalancer; for AWS, the operator adds the NLB annotation.
  • traefik produces a ClusterIP data Service, but also creates Traefik
    IngressRouteTCP resources and requires spec.cluster.externalDNS.
  • Neither mode expresses "ClusterIP only, no external routing".

This behavior is present in operator 2.63.0 and remains in the current master
sources inspected on 2026-10-14. This is a feature request, not a report that
the cloud controller should provision load balancers without authorization.

Environment and observed impact

  • CrateDB Kubernetes Operator: 2.63.0 for the original test.
  • CrateDB: 6.2.11 for the original test.
  • Platform: Kubernetes on AWS EKS.
  • Kubernetes: 1.34 (reported for the original test; current test-cluster
    control-plane version independently confirmed through the read-only EKS
    DescribeCluster API on 2026-10-14).

In the original test, clients could use the generated Service's ClusterIP,
but the cloud controller attempted unnecessary LB provisioning. Missing AWS
permissions prevented provisioning and later blocked Service cleanup.
These were cloud-controller IAM failures, not operator Kubernetes RBAC failures.
They illustrate the motivation for internal-only exposure, rather than a
request to change AWS authorization behavior.

No raw logs or stack traces are included in this report. The feature gap can
also be established from the CRD and Service/exposure implementation linked
below; the reproduction instructions are not a claim of a new test run.

How to observe the current behavior

In an isolated test environment:

  1. Create an otherwise valid CrateDB resource, omitting
    spec.cluster.exposure and spec.cluster.externalDNS.

  2. Inspect its generated data Service:

    kubectl -n <namespace> get service crate-<cluster-name> \
      -o jsonpath='{.spec.type}{"\n"}'
  3. Observe LoadBalancer. On AWS, this requests cloud LB provisioning even
    if clients only use the Service's in-cluster address.

  4. Choosing traefik instead is not an internal-only alternative: it requires
    externalDNS and attempts to create Traefik routing resources.

Do not run this reproduction on a live environment merely to report the
feature: it can provision billable resources.

Requested behavior

Please consider an explicit opt-in internal-only mode, for example:

spec:
  cluster:
    exposure: clusterip

clusterip is a proposed name, not an existing supported value. An equivalent
maintainer-preferred API would be welcome.

In this mode:

  • Keep the data Service as ClusterIP, with its HTTP/PostgreSQL ports and
    selectors, and retain normal cluster discovery.
  • Do not request a LoadBalancer or generate Traefik/other ingress routes,
    ingress middleware, or external-DNS hostname annotations.
  • Do not require an external hostname, ingress controller or its CRDs.
  • Preserve existing defaults and behavior for loadbalancer and traefik.
  • Preserve the selected mode through restart/resume and Service recreation.

Optional follow-up: exposure transitions

Creation, restart/resume and Service recreation in internal-only mode are the
core requirement. General transitions between exposure modes can be delivered
separately if that keeps the initial contribution small.

For that follow-up:

  • Transitioning to internal-only removes operator-owned external routes and
    lets the cloud controller clean up any existing LB correctly.
  • Preserve AWS LB-identification annotations required for cleanup; see Fix orphaned AWS NLB on exposure loadbalancer->traefik switch #851.
  • Preserve the existing data Service/ClusterIP where feasible.
  • Returning to an existing exposure mode restores its current behavior.

Selecting internal-only cannot bypass permissions needed to clean up a
previously provisioned LB; newly created internal-only clusters should not
require those provisioning permissions.

Scope and implementation discussion

This request concerns spec.cluster.exposure for the database client Service,
not a new Grand Central routing mode.

Would you accept this capability upstream, and is an explicit third exposure
mode the preferred API? If the change is reasonably small, we would be
interested in discussing a contribution after agreement on the behavior.
Any contribution would follow the repository's developer guide, including its
CLA requirement, a feature branch based on current upstream master, applicable
tests and pre-commit checks. This issue does not promise an implementation or
assert that the CLA has already been completed.

Using a dummy ingress is not an equivalent supported contract: missing Traefik
CRDs can fail resource creation, while an absent ingress controller can leave
unused routing objects that may become active if a controller is installed
later.

References

Activity

  1. self-assigned this
    on Oct 20, 2026
  2. WalBeh commented on Oct 20, 2026

    @WalBeh
    Contributor

    Implemented in #920 as spec.cluster.exposure: clusterip:

    • The data Service crate-<name> is a plain ClusterIP: no LB annotations, no external-dns hostname, no Traefik resources, externalDNS not needed.
    • Switching between all three modes patches the Service in place, so it keeps its ClusterIP and the Fix orphaned AWS NLB on exposure loadbalancer->traefik switch #851 NLB cleanup still applies. Moving away from loadbalancer still needs the cloud controller's permissions to delete the existing LB.
    • Suspend keeps the Service with clusterip, so the ClusterIP survives suspend/resume.
    • allowedCIDRs has no effect with clusterip.

    Notes for clients:

    • Connect to crate-<name>.<namespace>. crate-discovery-<name> is headless and also resolves to dedicated master nodes.
    • kube-proxy balances per connection, so pooled connections stay on one pod until they reconnect. A maximum connection lifetime on the pool spreads them again after a rolling restart.
    • With external-dns --policy=upsert-only, switching to clusterip leaves the old DNS record behind.

    Could you try the branch on EKS? We can't reproduce the IAM side on minikube.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions