Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ Changelog
Unreleased
----------

* Fixed AWS load balancers being orphaned when switching
``spec.cluster.exposure`` from ``loadbalancer`` to ``traefik`` by keeping the
``aws-load-balancer-type`` annotation so Kubernetes deletes the NLB.

* Replaced grand-central Ingress with HTTPRoute and Traefik Middlewares when
``spec.cluster.exposure`` is set to ``traefik``.

Expand Down
10 changes: 8 additions & 2 deletions crate/operator/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -1253,13 +1253,19 @@ def _lb_annotations_to_add(dns_record: Optional[str]) -> dict:

def _lb_annotations_to_remove() -> dict:
"""
Return all LB annotations set to None to remove them via merge patch.
Return the LB tuning annotations set to None to remove them via merge patch.

The ``aws-load-balancer-type`` annotation is deliberately NOT removed: the
in-tree service controller reads it in ``EnsureLoadBalancerDeleted`` to tell
an NLB from a Classic ELB. Dropping it while switching the service to
ClusterIP makes the controller fall back to the Classic ELB path, fail to
find the NLB, and orphan it on AWS (crate/cloud#2976, kubernetes#95042). The
annotation is inert on a ClusterIP service, so leaving it is safe.
"""
return {
"service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled": None, # noqa
"service.beta.kubernetes.io/aws-load-balancer-connection-draining-timeout": None, # noqa
"service.beta.kubernetes.io/aws-load-balancer-connection-idle-timeout": None,
"service.beta.kubernetes.io/aws-load-balancer-type": None,
"service.beta.kubernetes.io/aws-load-balancer-cross-zone-load-balancing-enabled": None, # noqa
"service.beta.kubernetes.io/azure-load-balancer-disable-tcp-reset": None,
"service.beta.kubernetes.io/azure-load-balancer-tcp-idle-timeout": None,
Expand Down
4 changes: 2 additions & 2 deletions crate/operator/exposure.py
Original file line number Diff line number Diff line change
Expand Up @@ -487,8 +487,8 @@ async def patch_service_exposure(

new_annotations: dict[str, Optional[str]] = {}
if use_traefik:
# When switching to traefik, explicitly null out all LB-specific annotations
# so they don't linger on the ClusterIP service.
# Drop the LB tuning annotations, but keep aws-load-balancer-type so the
# service controller can delete the NLB instead of orphaning it.
new_annotations.update(_lb_annotations_to_remove())
else:
new_annotations.update(_lb_annotations_to_add(dns_record))
Expand Down
13 changes: 13 additions & 0 deletions tests/test_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
CloudProvider,
)
from crate.operator.create import (
_lb_annotations_to_remove,
build_cratedb_labels,
create_services,
create_sql_exporter_config,
Expand Down Expand Up @@ -1193,6 +1194,18 @@ def test_get_data_service(self, provider, dns, faker):
== dns
)

def test_lb_type_annotation_retained_when_switching_to_clusterip(self):
# Switching to traefik (LoadBalancer -> ClusterIP) must keep
# aws-load-balancer-type so the service controller can identify and
# delete the NLB; dropping it orphans the LB on AWS (crate/cloud#2976).
removed = _lb_annotations_to_remove()
assert "service.beta.kubernetes.io/aws-load-balancer-type" not in removed
assert all(value is None for value in removed.values())
assert (
"service.beta.kubernetes.io/aws-load-balancer-connection-draining-enabled"
in removed
)

def test_data_service_selector_excludes_masters_with_dedicated_masters(self, faker):
name = faker.domain_word()
service = get_data_service(
Expand Down
Loading