VictoriaMetrics / VictoriaMetrics/operator

VMCluster: serviceSpec.useAsDefault cannot change vmselect Service from headless to ClusterIP

Open
#2,487 4 comments 0 reactions 1 assignee View on GitHub

@AndrewChubatiuk is already working on this.

Since Aug 21, 2026.

waiting for release
Dominant language
Go
Stars
589
Forks
229
Avg merge
1d 9h
Merged PRs (30d)
77

Description

What happens

For components whose default Service is headless (vmselect, vmstorage, vmalertmanager), serviceSpec.useAsDefault: true silently keeps clusterIP: None no matter what is supplied in spec. There is no way to turn the main Service into a normal ClusterIP Service.

Why

build.Service re-inherits the default's clusterIP after replacing the spec:

https://github.com/VictoriaMetrics/operator/blob/v0.66.1/internal/controller/operator/factory/build/service.go#L89-L91

if serviceOverrides.Spec.ClusterIP == "" && serviceOverrides.Spec.Type == svc.Spec.Type {
    serviceOverrides.Spec.ClusterIP = svc.Spec.ClusterIP
}

At that point svc.Spec.ClusterIP is already "None", set by the component callback:

https://github.com/VictoriaMetrics/operator/blob/v0.66.1/internal/controller/operator/factory/vmcluster/vmcluster.go#L188-L189

svc.Spec.ClusterIP = "None"
svc.Spec.PublishNotReadyAddresses = true

Omitting type in the override is not an escape either, because it is defaulted from the base Service three lines earlier, which then makes the guard's second condition true:

https://github.com/VictoriaMetrics/operator/blob/v0.66.1/internal/controller/operator/factory/build/service.go#L86-L88

Reproduction

helm install victoria-metrics-operator vm/victoria-metrics-operator --version 0.67.2 

Then kubectl apply this file:

apiVersion: operator.victoriametrics.com/v1beta1
kind: VMCluster
metadata:
  name: example
  namespace: infra
spec:
  retentionPeriod: "1"
  vmselect:
    replicaCount: 1
    serviceSpec:
      useAsDefault: true
      spec:
        type: ClusterIP
        ports:
          - name: http
            port: 8481
            targetPort: 8481
  vmstorage:
    replicaCount: 1

Result:

kubectl get svc vmselect-example -o jsonpath='{.spec.clusterIP}'

Expected: an allocated cluster IP.

Actual: None.

Changing useAsDefault to false (and giving the override a distinct metadata.name) produces a second Service that does get a VIP — demonstrating the inconsistency.

Suggested fix

Only inherit the base clusterIP when the user's override is silent about the service shape. Capture whether the user set type before it is defaulted, and skip the inherit if they did:

userSetType := serviceOverrides.Spec.Type != ""
if serviceOverrides.Spec.Type == "" {
    serviceOverrides.Spec.Type = svc.Spec.Type
}
if !userSetType && serviceOverrides.Spec.ClusterIP == "" && serviceOverrides.Spec.Type == svc.Spec.Type {
    serviceOverrides.Spec.ClusterIP = svc.Spec.ClusterIP
}

That preserves today's behaviour for users who only override ports or labels, while letting an explicit type: ClusterIP mean "allocate a VIP".

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.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.