Altinity / Altinity/clickhouse-operator

Dangerous behavior when reconciling LoadBalancer type Services

Open
#1,650 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Go
Stars
2.6k
Forks
574
Avg merge
8d 6h
Merged PRs (30d)
6

Description

Description

There is a potentially dangerous behavior in the current implementation of reconcileService function. When a Service update fails (which could happen due to temporary webhook connection issues or other transient errors), the code attempts to delete and recreate the Service:

if err != nil {
	if apiErrors.IsNotFound(err) {
		// The Service is either not found or not updated. Try to recreate it
		w.a.V(1).M(cr).F().Info("Service: %s not found. err: %v", util.NamespaceNameString(service), err)
	} else {
		// The Service is either not found or not updated. Try to recreate it
		w.a.WithEvent(cr, a.EventActionUpdate, a.EventReasonUpdateFailed).
			WithAction(cr).
			WithError(cr).
			M(cr).F().
			Error("Update Service: %s failed with error: %v", util.NamespaceNameString(service), err)
	}

	_ = w.c.deleteServiceIfExists(ctx, service.GetNamespace(), service.GetName())
	err = w.createService(ctx, cr, service)
}

This approach is particularly dangerous for Services of type LoadBalancer. When such a Service is deleted and recreated:

  1. The previously allocated external IP address is released
  2. A new LoadBalancer is provisioned with a different IP address
  3. All client connections to the previous IP address are broken
  4. DNS records need to be updated, causing potential service disruption
  5. Applications relying on stable endpoints will fail
Impact

This can cause significant production outages in environments where:

  • Applications depend on stable LoadBalancer IP addresses
  • External DNS records point to these LoadBalancer IPs
  • Client applications have cached the IP addresses
Proposed Solution

The code should handle LoadBalancer type Services differently from other Service types. Instead of deleting and recreating them on update failure, it should:

  1. Check the Service type before attempting recreation
  2. For LoadBalancer Services, log the error but preserve the existing Service
  3. Only perform delete-and-recreate for other Service types (ClusterIP, NodePort)

Example implementation:

if err != nil {
    // The Service is either not found or not updated.
    // For LoadBalancer type services, we should be careful not to delete and recreate
    // as this would cause the LoadBalancer IP to change
    if service.Spec.Type == core.ServiceTypeLoadBalancer {
        w.a.WithEvent(chi, eventActionReconcile, eventReasonReconcileFailed).
            WithStatusAction(chi).
            WithStatusError(chi).
            M(chi).F().
            Error("FAILED to update LoadBalancer Service: %s CHI: %s. NOT deleting to preserve external IP", service.Name, chi.Name)
        return err
    }

    // For other service types, we can try to recreate it
    ...
}

This change would prevent unexpected IP address changes for LoadBalancer Services while still allowing recreation of other Service types.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at the reconcileService function and inspect its deleteServiceIfExists and createService calls, focusing on the update-error path for Services whose type is LoadBalancer. Confirm the existing Service type before recreation. Done means failed LoadBalancer updates preserve the Service and its external IP, while the existing recreation behavior remains for other Service types.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, kubernetes
Domain
infrastructure
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.