Repository: kubernetes
Issue: Incorrect endpoint slice metrics for dualstack services
Labels: kind/bug,sig/network,triage/accepted

### What happened?

When deploying a dualstack service, endpointslices for both IP families will be created. The endpoint slice metrics reported are only 1 of the 2 IP Families. `endpoint_slice_controller_desired_endpoint_slices` and `endpoint_slice_controller_num_endpoint_slices` reports only the endpoint slices of 1 IP family and `endpoint_slice_controller_endpoints_desired` reports only the endpoints of 1 IP Family.

For example, let's say 2 pods are selected by a dualstack service, 2 endpoint slices will be created (1 for IPv4 and i for IPv6), `endpoint_slice_controller_desired_endpoint_slices` and `endpoint_slice_controller_num_endpoint_slices` will be increased by 1 (instead of 2) and `endpoint_slice_controller_endpoints_desired` will be increased by 2 (instead of 4 (2 pods and 2 IPs per pod)).

### What did you expect to happen?

Using Dualstack service, the metrics reported by the endpointslice controller should reflect the correct amount the endpoint slices created with the correct amount of endpoints.

### How can we reproduce it (as minimally and precisely as possible)?

In the details below, I added some instructions to access the metrics for the controller-manager in KinD.
<details>

Dualstack KinD configuration:
```
---
kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
networking:
  ipFamily: dual
nodes:
- role: control-plane
  image: kindest/node:v1.30.0@sha256:047357ac0cfea04663786a612ba1eaba9702bef25227a794b52890dd8bcd692e
- role: worker
  image: kindest/node:v1.30.0@sha256:047357ac0cfea04663786a612ba1eaba9702bef25227a794b52890dd8bcd692e
```

Allow anonymous users to access metrics:
```
cat <<EOF | kubectl apply -f -
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
  name: anonymous-metrics-reader
rules:
- nonResourceURLs: ["/metrics"]
  verbs: ["get"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: anonymous-metrics-reader
subjects:
- kind: User
  name: system:anonymous
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: ClusterRole
  name: anonymous-metrics-reader
  apiGroup: rbac.authorization.k8s.io
EOF
```

Expose the controller-manager metrics:
```
kubectl port-forward kube-controller-manager-kind-control-plane -n kube-system 10257:10257
```

</details>

For reference, Get `endpoint_slice_controller_desired_endpoint_slices` from the kube-controller-manager metrics:
```
$ curl -ks https://localhost:10257/metrics | grep endpoint_slice_controller_desired_endpoint_slices
# HELP endpoint_slice_controller_desired_endpoint_slices [ALPHA] Number of EndpointSlices that would exist with perfect endpoint allocation
# TYPE endpoint_slice_controller_desired_endpoint_slices gauge
endpoint_slice_controller_desired_endpoint_slices 1
```

For reference, List the EndpointSlices:
```
$ kubectl get endpointslices --all-namespaces
NAMESPACE     NAME                 ADDRESSTYPE   PORTS        ENDPOINTS               AGE
default       kubernetes           IPv4          6443         172.18.0.2              16m
kube-system   kube-dns-82glk       IPv4          9153,53,53   10.244.0.3,10.244.0.4   16m
```

Deploy a dualstack service:
```
cat <<EOF | kubectl apply -f -
---
apiVersion: v1
kind: Service
metadata:
  name: demo-service
spec:
  ipFamilies:
    - IPv4
    - IPv6
  ipFamilyPolicy: "RequireDualStack"
  selector:
    app: demo-pod
  type: ClusterIP
  ports:
    - protocol: TCP
      port: 5000
      targetPort: 5000
EOF
```

Get `endpoint_slice_controller_desired_endpoint_slices` from the kube-controller-manager metrics:
```
$ curl -ks https://localhost:10257/metrics | grep endpoint_slice_controller_desired_endpoint_slices
# HELP endpoint_slice_controller_desired_endpoint_slices [ALPHA] Number of EndpointSlices that would exist with perfect endpoint allocation
# TYPE endpoint_slice_controller_desired_endpoint_slices gauge
endpoint_slice_controller_desired_endpoint_slices 2
```

List the EndpointSlices:
```
$ kubectl get endpointslices --all-namespaces
NAMESPACE     NAME                 ADDRESSTYPE   PORTS        ENDPOINTS               AGE
default       demo-service-7pplq   IPv4          <unset>      <unset>                 7m47s
default       demo-service-km7tq   IPv6          <unset>      <unset>                 7m47s
default       kubernetes           IPv4          6443         172.18.0.2              16m
kube-system   kube-dns-82glk       IPv4          9153,53,53   10.244.0.3,10.244.0.4   16m
```

I think `kubernetes` EndpointSlice is not counted as it is not managed by the endpointslice controller of the controller manager (It doesn't have the `endpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.io` label).

As observed here, 2 endpoint slices have been created (1 IPv4 and 1 IPv6), but the `endpoint_slice_controller_desired_endpoint_slices` has increased by 1.

### Anything else we need to know?

This has been spotted during the review of the dualstack service implementation: https://github.com/kubernetes/kubernetes/pull/91824/files#r511160829

When we create a Dualstack service, the [reconcileByAddressType](https://github.com/kubernetes/endpointslice/blob/v0.30.2/reconciler.go#L147) function is called twice (1 IPv4, 1 IPv6), PortCache is created twice for the same service and the metrics cache is updated twice with the same key (ServiceNM) here: [kubernetes/endpointslice/reconciler.go#L265](https://github.com/kubernetes/endpointslice/blob/v0.30.2/reconciler.go#L265). Since the same key is used, the second iteration in `reconcileByAddressType` will overwrite the first one, so metrics will not be correctly reported (metrics for 1 IP Family will be missing). The PortCache could be used in the main `Reconcile` function and the address type could be used as prefix when calling `Set` for the PortCache here: [kubernetes/endpointslice/reconciler.go#L227](https://github.com/kubernetes/endpointslice/blob/v0.30.2/reconciler.go#L227).

The EndpointSlice Mirroring reconciler uses the same concept with the metrics cache, but use the same PortCache for all IP families and uses the IP Family has prefix for the key ([pkg/controller/endpointslicemirroring/utils.go#L40](https://github.com/kubernetes/kubernetes/blob/v1.30.2/pkg/controller/endpointslicemirroring/utils.go#L40)), so the problem does not exist in this reconciler.

### Kubernetes version

<details>

```console
$ kubectl version
Client Version: v1.30.0
Kustomize Version: v5.0.4-0.20230601165947-6ce0bf390ce3
Server Version: v1.30.0
```

</details>

### Cloud provider

<details>

</details>


### OS version

<details>

```console
# On Linux:
$ cat /etc/os-release
# paste output here
$ uname -a
# paste output here

# On Windows:
C:\> wmic os get Caption, Version, BuildNumber, OSArchitecture
# paste output here
```

</details>


### Install tools

<details>

</details>


### Container runtime (CRI) and version (if applicable)

<details>

</details>


### Related plugins (CNI, CSI, ...) and versions (if applicable)

<details>

</details>