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
2 changes: 2 additions & 0 deletions CHANGES.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ Changelog
Unreleased
----------

* Added ``clusterip`` exposure for clusters reachable only inside Kubernetes.

* Fixed changing ``spec.cluster.exposure`` failing while the cluster is suspended.

* Bumped sql_exporter to ``0.24.10`` for CVE fixes.
Expand Down
18 changes: 8 additions & 10 deletions crate/operator/create.py
Original file line number Diff line number Diff line change
Expand Up @@ -1361,18 +1361,18 @@ def get_data_service(
dns_record: Optional[str],
source_ranges: Optional[List[str]] = None,
additional_annotations: Optional[Dict] = None,
use_traefik: bool = False,
use_load_balancer: bool = True,
has_dedicated_masters: bool = False,
) -> V1Service:
res_annotations = {}

if not use_traefik:
if use_load_balancer:
res_annotations.update(_lb_annotations_to_add(dns_record))

if additional_annotations:
res_annotations.update(additional_annotations)

service_type = "ClusterIP" if use_traefik else "LoadBalancer"
service_type = "LoadBalancer" if use_load_balancer else "ClusterIP"

selector = {LABEL_COMPONENT: "cratedb", LABEL_NAME: name}
if has_dedicated_masters:
Expand All @@ -1393,7 +1393,7 @@ def get_data_service(
type=service_type,
)

if not use_traefik:
if use_load_balancer:
spec_kwargs["external_traffic_policy"] = "Local"
if source_ranges:
spec_kwargs["load_balancer_source_ranges"] = source_ranges
Expand Down Expand Up @@ -1488,7 +1488,7 @@ async def create_services(
logger: logging.Logger,
source_ranges: Optional[List[str]] = None,
additional_annotations: Optional[Dict] = None,
use_traefik: bool = False,
use_load_balancer: bool = True,
has_dedicated_masters: bool = False,
) -> None:
async with GlobalApiClient() as api_client:
Expand All @@ -1504,7 +1504,7 @@ async def create_services(
dns_record,
source_ranges,
additional_annotations=additional_annotations,
use_traefik=use_traefik,
use_load_balancer=use_load_balancer,
has_dedicated_masters=has_dedicated_masters,
)
await call_kubeapi(
Expand Down Expand Up @@ -1559,7 +1559,6 @@ async def recreate_services(
dns_record = spec.get("cluster", {}).get("externalDNS", None)

exposure = spec.get("cluster", {}).get("exposure", "loadbalancer")
use_traefik = exposure == "traefik"

await create_services(
owner_references,
Expand All @@ -1573,7 +1572,7 @@ async def recreate_services(
logger,
source_ranges,
additional_annotations,
use_traefik=use_traefik,
use_load_balancer=exposure == "loadbalancer",
has_dedicated_masters="master" in spec["nodes"],
)

Expand Down Expand Up @@ -1809,7 +1808,6 @@ async def handle( # type: ignore
has_dedicated_masters: bool = False,
**kwargs: Any,
):
use_traefik = exposure == "traefik"
await create_services(
owner_references,
namespace,
Expand All @@ -1822,7 +1820,7 @@ async def handle( # type: ignore
logger,
source_ranges,
additional_annotations,
use_traefik=use_traefik,
use_load_balancer=exposure == "loadbalancer",
has_dedicated_masters=has_dedicated_masters,
)

Expand Down
11 changes: 7 additions & 4 deletions crate/operator/exposure.py
Original file line number Diff line number Diff line change
Expand Up @@ -484,10 +484,10 @@ async def patch_service_exposure(
LoadBalancer and ClusterIP.
"""
service_name = f"crate-{name}"
use_traefik = new_exposure == "traefik"
use_load_balancer = new_exposure == "loadbalancer"

new_annotations: dict[str, Optional[str]] = {}
if use_traefik:
if not use_load_balancer:
# 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())
Expand All @@ -500,7 +500,7 @@ async def patch_service_exposure(
# When switching away from LoadBalancer, explicitly null out LB-specific
# fields - Kubernetes requires them to be absent for ClusterIP.
spec_patch: dict[str, Any] = {}
if use_traefik:
if not use_load_balancer:
spec_patch = {
"type": "ClusterIP",
"externalTrafficPolicy": None,
Expand Down Expand Up @@ -609,7 +609,7 @@ async def handle(

class ChangeExposureSubHandler(StateBasedSubHandler):
"""
Handles changes to spec.cluster.exposure between 'loadbalancer' and 'traefik'.
Handles changes to spec.cluster.exposure.
Deletes old resources and creates new ones accordingly.
"""

Expand Down Expand Up @@ -661,6 +661,9 @@ async def handle(
if old_exposure == "traefik":
await delete_traefik_resources(namespace, name)

if new_exposure == "clusterip" and source_ranges:
logger.info("allowedCIDRs has no effect with exposure=clusterip")

if new_exposure == "traefik":
await create_traefik_resources(
owner_references,
Expand Down
2 changes: 2 additions & 0 deletions crate/operator/handlers/handle_update_allowed_cidrs.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,8 @@ async def update_service_allowed_cidrs(

if exposure == "traefik":
await update_traefik_ip_restriction(namespace, name, new_cidrs, logger)
elif exposure == "clusterip" and new_cidrs:
logger.info("allowedCIDRs has no effect with exposure=clusterip")

# Grand-central IP allowlist follows its own exposure, which
# may differ from the CrateDB service exposure.
Expand Down
7 changes: 5 additions & 2 deletions crate/operator/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -1189,6 +1189,7 @@ async def suspend_or_start_cluster(
cratedb.get("spec", {}).get("cluster", {}).get("exposure", "loadbalancer")
)
use_traefik = exposure == "traefik"
use_load_balancer = exposure == "loadbalancer"

# Dedicated master nodes are suspended/resumed in lockstep with the data
# nodes, but with strict ordering: on resume the masters must come up
Expand Down Expand Up @@ -1217,7 +1218,7 @@ async def suspend_or_start_cluster(
await recreate_services(
namespace, name, cratedb["spec"], cratedb["metadata"], logger
)
if not use_traefik and not await is_lb_service_ready(
if use_load_balancer and not await is_lb_service_ready(
core, namespace, name
):
raise TemporaryError(delay=config.BOOTSTRAP_RETRY_DELAY)
Expand Down Expand Up @@ -1354,9 +1355,11 @@ async def suspend_or_start_cluster(
await delete_traefik_resources(namespace, name)
# Also delete the ClusterIP service
await delete_clusterip_service(core, namespace, name)
else:
elif use_load_balancer:
# Delete the LoadBalancer service
await delete_lb_service(core, namespace, name)
# Keep a clusterip service. It costs nothing, and its cluster IP
# must not change on resume.

if has_dedicated_masters and is_suspend:
await scale_master_statefulset(apps, namespace, name, 0, logger)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -583,7 +583,7 @@ spec:
exposure:
description: Service exposure type
type: string
enum: ["loadbalancer", "traefik"]
enum: ["loadbalancer", "traefik", "clusterip"]
required:
- imageRegistry
- name
Expand Down
4 changes: 3 additions & 1 deletion docs/source/crd-reference.rst
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ Core identity and cluster-wide configuration. **Required.**
* - ``exposure``
- string
- no
- Service exposure type. One of ``loadbalancer`` or ``traefik``.
- Service exposure type. One of ``loadbalancer`` (default), ``traefik``
or ``clusterip``. ``clusterip`` gives in-cluster access only, ignores
``allowedCIDRs`` and keeps the service while the cluster is suspended.
* - ``settings``
- object
- no
Expand Down
17 changes: 17 additions & 0 deletions tests/test_create.py
Original file line number Diff line number Diff line change
Expand Up @@ -1332,6 +1332,23 @@ def test_get_data_service(self, provider, dns, faker):
== dns
)

def test_get_data_service_without_load_balancer(self, faker):
with mock.patch("crate.operator.create.config.CLOUD_PROVIDER", "aws"):
service = get_data_service(
None,
faker.domain_word(),
{},
faker.port_number(),
faker.port_number(),
"mycluster.example.com",
["10.0.0.0/8"],
use_load_balancer=False,
)
assert service.spec.type == "ClusterIP"
assert service.spec.external_traffic_policy is None
assert service.spec.load_balancer_source_ranges is None
assert service.metadata.annotations == {}

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
Expand Down
Loading
Loading