Skip to content

fix(webhook): set EXTERNAL-IP when a service is changed to LoadBalancer; add --load-balancer-ip [KS-75] - #178

Merged
stevensbkang merged 3 commits into
developfrom
feat/ks-75/add-update-to-webhook
Aug 4, 2026
Merged

fix(webhook): set EXTERNAL-IP when a service is changed to LoadBalancer; add --load-balancer-ip [KS-75]#178
stevensbkang merged 3 commits into
developfrom
feat/ks-75/add-update-to-webhook

Conversation

@stevensbkang

Copy link
Copy Markdown
Member

The mutating webhook was registered for CREATE only, and nothing else watches Services, so admission was the sole trigger for populating status.loadBalancer.ingress. Flipping an existing Service from ClusterIP to LoadBalancer via kubectl apply or helm upgrade is an UPDATE, so it never received an EXTERNAL-IP and the only workaround was to delete and recreate the Service.

Split the webhook rule in two rather than adding UPDATE globally. Pods, PVCs and jobs stay CREATE-only because pod spec.nodeName and job spec.template are immutable after creation, so returning those patches on an update would make the apiserver reject the request. Only services match CREATE and UPDATE. services/status is not matched, so the status patch this triggers does not re-enter the webhook

…er; add --load-balancer-ip [KS-75]

The mutating webhook was registered for CREATE only, and nothing else
watches Services, so admission was the sole trigger for populating
status.loadBalancer.ingress. Flipping an existing Service from ClusterIP
to LoadBalancer via kubectl apply or helm upgrade is an UPDATE, so it
never received an EXTERNAL-IP and the only workaround was to delete and
recreate the Service.

Split the webhook rule in two rather than adding UPDATE globally. Pods,
PVCs and jobs stay CREATE-only because pod spec.nodeName and job
spec.template are immutable after creation, so returning those patches on
an update would make the apiserver reject the request. Only services match
CREATE and UPDATE. services/status is not matched, so the status patch
this triggers does not re-enter the webhook
@linear

linear Bot commented Aug 3, 2026

Copy link
Copy Markdown

KS-75

@github-actions

github-actions Bot commented Aug 3, 2026

Copy link
Copy Markdown

Note

PR image published: portainerci/kubesolo:pr-178

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Not ready to approve

The webhook Service struct uses sync.WaitGroup but the code calls wg.Go(...), which will not compile without adjusting the waitgroup implementation.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

This review doesn't count toward merge requirements. Sign up for the private preview to control whether Copilot approvals count.

Pull request overview

This PR fixes a gap in the mutating webhook’s LoadBalancer handling so that Services flipped from ClusterIP to LoadBalancer on UPDATE (e.g., kubectl apply / helm upgrade) still get status.loadBalancer.ingress populated, and adds a --load-balancer-ip override for the published EXTERNAL-IP.

Changes:

  • Scope the webhook rules so only services match CREATE+UPDATE, while pods/PVCs/jobs remain CREATE-only.
  • Add --load-balancer-ip (and runtime resolution) to control the EXTERNAL-IP independently of the detected node IP.
  • Add unit + e2e coverage for the UPDATE path, plus dry-run safety for out-of-band status patching.
File summaries
File Description
types/types.go Adds Embedded.LoadBalancerIP to carry the chosen EXTERNAL-IP through runtime wiring.
internal/config/flags/flags.go Introduces --load-balancer-ip and updates LoadBalancer flag description to include UPDATE behavior.
internal/runtime/network/ip.go Adds ResolveLoadBalancerIP() to validate/choose the published EXTERNAL-IP.
internal/runtime/network/ip_test.go Unit tests for ResolveLoadBalancerIP() behavior and fallbacks.
cmd/kubesolo/main.go Computes loadBalancerIP and wires it into embedded runtime configuration.
pkg/kubernetes/apiserver/service.go Passes Embedded.LoadBalancerIP into the webhook constructor.
pkg/kubernetes/webhook/service.go Renames stored IP from nodeIP to loadBalancerIP and embeds it into the status patch payload.
pkg/kubernetes/webhook/webhooks.go Skips out-of-band status mutation on dry-run requests; uses loadBalancerIP for logging.
pkg/kubernetes/webhook/loadbalancer.go Retries when a stale read returns the pre-update Service type; patches status when committed.
pkg/kubernetes/webhook/loadbalancer_test.go New unit tests validating retry behavior and status patching for CREATE vs UPDATE paths.
pkg/kubernetes/webhook/config.go Splits webhook rules so services include UPDATE while others remain CREATE-only.
test/e2e/manifests/06-lb-update/lb-update.yaml New e2e tier manifest covering ClusterIP→LoadBalancer flip, plus guard Job.
test/e2e/manifests/06-lb-update/flip-to-lb.yaml Update overlay manifest applied via kubectl apply to exercise UPDATE admission.
test/e2e/manifests.sh Adds tier6 e2e flow, shared polling helper, and webhook-rule assertions.
Review details
  • Files reviewed: 14/14 changed files
  • Comments generated: 3
  • Review effort level: Lite

We're testing this review assessment. Please use 👍 or 👎 to tell us if it's correct.

Comment thread pkg/kubernetes/webhook/service.go
Comment thread pkg/kubernetes/webhook/config.go Outdated
Comment thread test/e2e/manifests.sh Outdated
…assertion

Addresses Copilot review on #178. The Service path patches status out of
band, so None was inaccurate; the dry-run guard makes NoneOnDryRun the
correct declaration. The e2e assertion used a wildcard that would still
pass if PVCs or jobs were dropped from the CREATE-only rule
@stevensbkang
stevensbkang merged commit a5e88c1 into develop Aug 4, 2026
20 checks passed
@stevensbkang
stevensbkang deleted the feat/ks-75/add-update-to-webhook branch August 4, 2026 01:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants