Kubernetes Services, Endpoints and EndpointSlices

Tested on Rocky Linux 10.2 (Red Quartz) workstation
Package kubectl 1.36.3
Applies to Any host with kubectl configured; Kubernetes 1.27+ cluster with Linux worker nodes
Cert prep CKA · CKAD · CKS
Lab environment Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm
Privilege Normal user (no sudo required on the workstation)
Scope ClusterIP, NodePort, and LoadBalancer Service types; headless ClusterIP Services; selectors; port fields; EndpointSlice inspection; readiness effects on backends; and end-to-end verification. Does not cover full Service troubleshooting runbooks, CoreDNS deep dives, Ingress or Gateway API rules, NetworkPolicy, kube-proxy internals, or manual legacy Endpoints creation.
Related guides Expose services with Ingress

A Deployment gives you Pods that come and go. A Service gives clients a stable discovery point in front of Pods that can change over time.

Before you expose a Service, read Kubernetes networking for how Pod networking, kube-proxy, and CNI plugins fit together.

This guide keeps one three-replica web Deployment in svc-lab and walks through:

  • ClusterIP Service first, with EndpointSlice inspection
  • Separate NodePort, LoadBalancer, and headless Service examples

Normal ClusterIP-based Services provide a virtual IP; headless Services expose backend addresses through DNS.


Why Kubernetes Needs Services

Pod IP addresses are not permanent. When a Pod is rescheduled, scaled, or replaced, its IP changes. Clients cannot hard-code Pod IPs and still survive normal cluster operations.

A Service solves that by providing:

  • A stable DNS name and, for non-headless ClusterIP-based Services, a stable virtual IP
  • Label-based Pod selection instead of per-Pod client configuration
  • One place to define the client-facing port and backend target port

Kubernetes records the selected backend addresses in EndpointSlices. Traffic forwarding depends on two separate layers:

  • Service proxy or dataplane (commonly kube-proxy, but a replacement may be used) watches Services and EndpointSlices and programs forwarding to eligible endpoints
  • CNI plugin provides Pod networking; some products also replace kube-proxy, but those remain separate roles

For a normal selector-based ClusterIP Service, the traffic path is:

text
Client
Service DNS name (optional resolution)
ClusterIP and Service port
Service proxy or dataplane rules
Ready Pod IP and target port

The EndpointSlice is not a network hop. It supplies the backend addresses and readiness conditions that the Service dataplane uses to program this path.

Headless Services return backend addresses through DNS without using a virtual Service IP.


Create the Backend Deployment

I use a small nginx-based Deployment with three replicas, one named container port, and a readiness probe that gates traffic. Each Pod writes its hostname into the default page so curl command responses identify which backend answered. The Deployment selector uses app: web; Services in this lab select on service: web so you can change Service membership without disturbing ReplicaSet reconciliation.

Create the namespace and Deployment manifest:

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: svc-lab
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: web
  namespace: svc-lab
spec:
  replicas: 3
  selector:
    matchLabels:
      app: web
  template:
    metadata:
      labels:
        app: web
        service: web
    spec:
      containers:
      - name: app
        image: nginx:1.27-alpine
        command: ["/bin/sh", "-c"]
        args:
        - |
          echo "server $(hostname)" > /usr/share/nginx/html/index.html
          touch /tmp/healthy
          exec nginx -g "daemon off;"
        ports:
        - name: http
          containerPort: 80
        readinessProbe:
          exec:
            command: ["/bin/sh", "-c", "test -f /tmp/healthy"]
          initialDelaySeconds: 2
          periodSeconds: 3

Apply the manifest and wait for the rollout to finish:

bash
kubectl apply -f web-deployment.yaml

Sample output:

output
namespace/svc-lab created
deployment.apps/web created
bash
kubectl rollout status deployment/web -n svc-lab --timeout=120s

Sample output:

output
deployment "web" successfully rolled out

kubectl rollout status watches the current Deployment revision until its rollout completes, which matches the explanation that all three replicas are ready for the following tests.

List the Pods with node and IP details:

bash
kubectl get pods -n svc-lab -l service=web -o wide

The following outputs are representative. Pod and EndpointSlice names, IP addresses, endpoint order, Service IPs, generated node ports, nodes, and ages will differ between clusters.

Sample output:

output
NAME                               READY   STATUS    RESTARTS   AGE    IP           NODE       NOMINATED NODE   READINESS GATES
web-<replicaset-hash>-<pod-suffix-1>   1/1     Running   0          <age>  <pod-ip-1>   worker01   <none>           <none>
web-<replicaset-hash>-<pod-suffix-2>   1/1     Running   0          <age>  <pod-ip-2>   worker01   <none>           <none>
web-<replicaset-hash>-<pod-suffix-3>   1/1     Running   0          <age>  <pod-ip-3>   worker01   <none>           <none>

Confirm the Pod-template labels the Service will select:

bash
kubectl get pods -n svc-lab -l service=web --show-labels

Sample output:

output
NAME                               READY   STATUS    RESTARTS   AGE   LABELS
web-<replicaset-hash>-<pod-suffix-1>   1/1     Running   0          <age>   app=web,pod-template-hash=<replicaset-hash>,service=web
web-<replicaset-hash>-<pod-suffix-2>   1/1     Running   0          <age>   app=web,pod-template-hash=<replicaset-hash>,service=web
web-<replicaset-hash>-<pod-suffix-3>   1/1     Running   0          <age>   app=web,pod-template-hash=<replicaset-hash>,service=web

All three Pods carry app=web for the Deployment selector and service=web for Service selection. The pod-template-hash label is added automatically by the Deployment controller.


Configure Service Ports and ClusterIP

Create the ClusterIP Service

ClusterIP is the default Service type. It allocates a virtual IP that is normally reachable only inside the cluster.

Add this Service to the same manifest or apply it separately:

yaml
apiVersion: v1
kind: Service
metadata:
  name: web
  namespace: svc-lab
spec:
  type: ClusterIP
  selector:
    service: web
  ports:
  - name: http
    port: 80
    targetPort: http
    protocol: TCP

Apply the Service:

bash
kubectl apply -f web-service.yaml

Inspect the allocated cluster IP and port:

bash
kubectl get service -n svc-lab web

Sample output:

output
NAME   TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)   AGE
web    ClusterIP   <cluster-ip>   <none>        80/TCP    <age>

Understand port, targetPort, and containerPort

These fields show up together in Service and Pod YAML but play different roles:

Field Meaning
containerPort Documents the port the container listens on; not required for numeric Service forwarding
targetPort Port on the selected backend Pod
port Port exposed by the Service
nodePort Port exposed on each eligible node for a NodePort Service

Keep these rules in mind when you read or write manifests:

  • targetPort defaults to the value of port when omitted.
  • targetPort may be numeric or reference a named Pod port (as in this lab, http maps to container port 80).
  • A numeric targetPort, such as 80, sends traffic to that Pod port whether or not containerPort: 80 is declared.
  • A named targetPort, such as http, must match a named container port on each selected Pod.
  • When a Service exposes multiple ports, each port needs a unique name.

In the web Service above, clients connect to Service port 80, and Kubernetes forwards to the Pod port named http, which is container port 80.

Test Service DNS and ClusterIP

Test from a temporary client Pod inside the cluster. The namespace-qualified name web.svc-lab works without assuming that the cluster DNS domain is cluster.local. A Pod in the same namespace could also use the short name web.

Wait until the EndpointSlice lists all three backends, then press Ctrl+C:

bash
kubectl get endpointslice -n svc-lab -l kubernetes.io/service-name=web --watch

All temporary client commands below attach to the Pod and remove it after the command exits. Depending on kubectl output handling, you may also see a final Pod deletion message after the displayed response.

Test a backend Pod IP and target port directly:

bash
POD_IP=$(kubectl get pod -n svc-lab -l service=web -o jsonpath='{.items[0].status.podIP}')

kubectl run podip-client \
  -n svc-lab \
  --rm \
  -i \
  --restart=Never \
  --image=curlimages/curl:8.5.0 \
  --command \
  -- curl \
  -fsS "http://${POD_IP}:80"

Sample output:

output
server web-<replicaset-hash>-<pod-suffix>

This request bypasses Service DNS, the ClusterIP, and Service proxying. A failure here means the Pod is not serving on the expected target port, so fix the backend before troubleshooting the Service.

Test Service DNS resolution:

bash
kubectl run curl-client -n svc-lab --rm -i --restart=Never --image=curlimages/curl:8.5.0 --command -- curl -fsS http://web.svc-lab

Sample output:

output
server web-<replicaset-hash>-<pod-suffix>

Test the ClusterIP and Service port directly:

bash
CLUSTER_IP=$(kubectl get service web -n svc-lab -o jsonpath='{.spec.clusterIP}')

kubectl run clusterip-client \
  -n svc-lab \
  --rm \
  -i \
  --restart=Never \
  --image=curlimages/curl:8.5.0 \
  --command \
  -- curl \
  -fsS "http://${CLUSTER_IP}:80"

Sample output:

output
server web-<replicaset-hash>-<pod-suffix>

The DNS request verifies Service DNS resolution and routing. The ClusterIP request bypasses DNS and verifies the virtual IP and Service port directly. Kubernetes documents backend Pod-IP testing, DNS-name testing, and direct Service-IP testing as separate checks. Repeated curls may hit different replicas because the Service proxy load-balances across ready endpoints.


Understand EndpointSlices

How Service Selectors Choose Pods

The Service selector is compared with Pod labels. Matching Pod addresses are recorded in one or more EndpointSlice objects. The Service proxy watches Services and EndpointSlices, then forwards traffic to endpoints marked ready.

Inspect the Service selector and ports:

bash
kubectl describe service -n svc-lab web

Or, for compact output:

bash
kubectl get service web -n svc-lab -o jsonpath='selector={.spec.selector}{"\n"}ports={.spec.ports}{"\n"}'

List EndpointSlices owned by that Service:

bash
kubectl get endpointslice -n svc-lab -l kubernetes.io/service-name=web

Sample output:

output
NAME                 ADDRESSTYPE   PORTS   ENDPOINTS                                      AGE
web-<endpointslice-suffix>   IPv4          80      <pod-ip-1>,<pod-ip-2>,<pod-ip-3>   <age>

Read addresses, target ports, and ready conditions:

bash
kubectl describe endpointslice -n svc-lab -l kubernetes.io/service-name=web

Sample output:

output
Name:         web-<endpointslice-suffix>
Namespace:    svc-lab
Labels:       endpointslice.kubernetes.io/managed-by=endpointslice-controller.k8s.io
              kubernetes.io/service-name=web
AddressType:  IPv4
Ports:
  Name  Port  Protocol
  ----  ----  --------
  http  80    TCP
Endpoints:
  - Addresses:  <pod-ip-1>
    Conditions:
      Ready:    true
    TargetRef:  Pod/web-<replicaset-hash>-<pod-suffix-1>
  - Addresses:  <pod-ip-2>
    Conditions:
      Ready:    true
    TargetRef:  Pod/web-<replicaset-hash>-<pod-suffix-2>
  - Addresses:  <pod-ip-3>
    Conditions:
      Ready:    true
    TargetRef:  Pod/web-<replicaset-hash>-<pod-suffix-3>

Demonstrate Service membership without changing the Deployment selector. Pick one Pod and remove only the service label the Service matches on:

bash
POD=$(kubectl get pod -n svc-lab -l service=web -o jsonpath='{.items[0].metadata.name}')

POD_IP=$(kubectl get pod -n svc-lab "$POD" -o jsonpath='{.status.podIP}')

kubectl label pod -n svc-lab "$POD" service=web-off --overwrite

Watch until $POD_IP disappears, then press Ctrl+C:

bash
kubectl get endpointslice -n svc-lab -l kubernetes.io/service-name=web --watch

Restore the label and watch until the address returns:

bash
kubectl label pod -n svc-lab "$POD" service=web --overwrite

kubectl get endpointslice -n svc-lab -l kubernetes.io/service-name=web --watch

EndpointSlices contain Pods matching the Service selector. ReplicaSet ownership and reconciliation use the Deployment selector on app=web, so changing service affects Service membership without triggering replacement Pods.

EndpointSlices and Readiness

Readiness decides whether a Pod should receive traffic. A failing readiness probe marks the Pod NotReady without restarting the container. Liveness and startup probe details live in the Kubernetes health probes guide.

With all three lab Pods healthy, every address in the EndpointSlice shows Ready: true. Remove the readiness marker file on one Pod to simulate a probe failure:

bash
POD=$(kubectl get pod -n svc-lab -l service=web -o jsonpath='{.items[0].metadata.name}')

kubectl exec -n svc-lab "$POD" -- rm -f /tmp/healthy

Wait for the actual readiness transition:

bash
kubectl wait -n svc-lab --for=condition=Ready=false "pod/$POD" --timeout=30s

Sample output:

output
pod/<generated-pod-name> condition met

The Pod stays Running, but it is not Ready. kubectl wait confirms the Pod condition changed; the EndpointSlice controller copies that state separately. EndpointSlices maintain their own ready, serving, and terminating conditions.

bash
kubectl get endpointslice \
  -n svc-lab \
  -l kubernetes.io/service-name=web \
  -o custom-columns='NAME:.metadata.name,PODS:.endpoints[*].targetRef.name,READY:.endpoints[*].conditions.ready' \
  --watch

Wait until the entry corresponding to $POD changes to false, then press Ctrl+C and run kubectl describe endpointslice:

bash
kubectl describe endpointslice -n svc-lab -l kubernetes.io/service-name=web

Sample output:

output
- Addresses:  <pod-ip>
    Conditions:
      Ready:    false
    TargetRef:  Pod/web-<replicaset-hash>-<pod-suffix>

Normal Service routing stops selecting that backend while Ready is false. Restore the marker file and wait again:

bash
kubectl exec -n svc-lab "$POD" -- touch /tmp/healthy

kubectl wait -n svc-lab --for=condition=Ready "pod/$POD" --timeout=30s

Watch until that entry returns to true, then press Ctrl+C:

bash
kubectl get endpointslice \
  -n svc-lab \
  -l kubernetes.io/service-name=web \
  -o custom-columns='NAME:.metadata.name,PODS:.endpoints[*].targetRef.name,READY:.endpoints[*].conditions.ready' \
  --watch

The EndpointSlice condition returns to Ready: true and the address is eligible for traffic again.

publishNotReadyAddresses: true on a Service is a special override used mainly for peer-discovery scenarios where DNS discovery and Service consumers must be able to reach Pods before those Pods become Ready. Leave it unset for normal HTTP Services.

EndpointSlices vs the Deprecated Endpoints API

EndpointSlices use the discovery.k8s.io/v1 API. A Service can have multiple EndpointSlices, which scale better and carry richer networking metadata than the older object.

On current clusters you may still see the core v1 Endpoints object. The Endpoints API was officially deprecated in Kubernetes 1.33.

bash
kubectl get endpoints -n svc-lab web

On Kubernetes 1.33 and later, kubectl also displays a deprecation warning. Older clusters within this article's supported range may show the Endpoints table without that warning.

Sample output:

output
Warning: v1 Endpoints is deprecated in v1.33+; use discovery.k8s.io/v1 EndpointSlice
NAME   ENDPOINTS                     AGE
web    <pod-ip-1>:80,<pod-ip-2>:80,<pod-ip-3>:80   <age>

Use EndpointSlice commands for inspection and troubleshooting. Do not teach or rely on manually created legacy Endpoints objects for selector-based Services; the controller maintains EndpointSlices automatically.


Compare Service Types

NodePort

NodePort builds on ClusterIP and allocates one port from the cluster's configured NodePort range, which defaults to 30000–32767. The same port is configured on every node, although the node addresses that accept it can be restricted by the Service proxy's nodePortAddresses configuration. Clients can generally reach the Service at <NodeIP>:<nodePort>, though the exact reachable addresses depend on your network layout, firewalls, and whether the selected node address is included in the Service proxy's NodePort address configuration.

Create a NodePort Service (you can delete or keep the existing ClusterIP web Service; this example uses a separate name):

yaml
apiVersion: v1
kind: Service
metadata:
  name: web-nodeport
  namespace: svc-lab
spec:
  type: NodePort
  selector:
    service: web
  ports:
  - name: http
    port: 80
    targetPort: http

Apply it and read the allocated node port:

bash
kubectl apply -f web-nodeport.yaml
bash
NODE_PORT=$(kubectl get service web-nodeport -n svc-lab -o jsonpath='{.spec.ports[0].nodePort}')

echo "$NODE_PORT"

Sample output:

output
<node-port>
bash
kubectl get service -n svc-lab web-nodeport

Sample output:

output
NAME           TYPE       CLUSTER-IP     EXTERNAL-IP   PORT(S)              AGE
web-nodeport   NodePort   <cluster-ip>   <none>        80:<node-port>/TCP   <age>

List node addresses you can target from outside the cluster:

bash
kubectl get nodes -o wide

Sample output:

output
NAME       STATUS   ROLES           AGE    VERSION   INTERNAL-IP      EXTERNAL-IP   OS-IMAGE
k8s-cp     Ready    control-plane   <age>  v1.36.3   <node-ip>        <none>        Rocky Linux 10.2 (Red Quartz)
worker01   Ready    <none>          <age>  v1.36.3   <node-ip>        <none>        Rocky Linux 10.2 (Red Quartz)

Test the NodePort data path from inside the cluster:

bash
NODE_IP=$(kubectl get nodes \
  -l '!node-role.kubernetes.io/control-plane' \
  -o jsonpath='{.items[0].status.addresses[?(@.type=="InternalIP")].address}')

kubectl run nodeport-client \
  -n svc-lab \
  --rm \
  -i \
  --restart=Never \
  --image=curlimages/curl:8.5.0 \
  --command \
  -- curl \
  -fsS "http://${NODE_IP}:${NODE_PORT}"

Sample output:

output
server web-<replicaset-hash>-<pod-suffix>

This in-cluster test confirms that the node IP and NodePort route to the Service. To verify external access, run the same curl command from a workstation or client network that can route to the selected node IP. External reachability still depends on node routing and firewall rules.

NodePort is useful in labs and for simple exposure, but it is not the preferred production pattern for public HTTP routing. For stable hostnames, TLS, and path rules, use Ingress or Gateway API after internal Service routing works.

LoadBalancer

A LoadBalancer Service asks the platform for an external load-balancer front end. Provisioning is asynchronous; the address appears under .status.loadBalancer when the controller succeeds.

yaml
apiVersion: v1
kind: Service
metadata:
  name: web-lb
  namespace: svc-lab
spec:
  type: LoadBalancer
  selector:
    service: web
  ports:
  - name: http
    port: 80
    targetPort: http

Apply the manifest:

bash
kubectl apply -f web-lb.yaml
bash
kubectl get service -n svc-lab web-lb

Sample output:

output
NAME     TYPE           CLUSTER-IP    EXTERNAL-IP   PORT(S)                AGE
web-lb   LoadBalancer   <cluster-ip>  <pending>     80:<node-port>/TCP     <age>

On a local kubeadm cluster without a cloud or metal load-balancer integration, EXTERNAL-IP commonly stays <pending>. That does not block in-cluster access through the ClusterIP. By default, Kubernetes allocates NodePorts for a LoadBalancer Service, which is why PORT(S) normally shows both the Service port and a generated node port. A load-balancer implementation that routes directly to Pods can use allocateLoadBalancerNodePorts: false. allocateLoadBalancerNodePorts defaults to true.

Avoid provider-specific annotations in portable tutorials; wire those only when you know your platform controller requires them.

Headless Service

A headless Service normally remains type: ClusterIP and sets clusterIP: None. Kubernetes does not define a separate headless Service type. DNS returns backend endpoint addresses directly instead of load-balancing through one Service IP.

yaml
apiVersion: v1
kind: Service
metadata:
  name: web-headless
  namespace: svc-lab
spec:
  clusterIP: None
  selector:
    service: web
  ports:
  - name: http
    port: 80
    targetPort: http

Apply the headless Service:

bash
kubectl apply -f web-headless.yaml

Confirm there is no cluster IP:

bash
kubectl get service -n svc-lab web-headless

Sample output:

output
NAME           TYPE        CLUSTER-IP   EXTERNAL-IP   PORT(S)   AGE
web-headless   ClusterIP   None         <none>        80/TCP    <age>

Inspect its EndpointSlice:

bash
kubectl get endpointslice -n svc-lab -l kubernetes.io/service-name=web-headless

Sample output:

output
NAME                           ADDRESSTYPE   PORTS   ENDPOINTS                                      AGE
web-headless-<endpointslice-suffix>   IPv4          80      <pod-ip-1>,<pod-ip-2>,<pod-ip-3>   <age>

Resolve the Service DNS name from a debug Pod:

bash
kubectl run dns-client -n svc-lab --rm -i --restart=Never --image=busybox:1.36.1 --command -- nslookup web-headless.svc-lab

Sample output:

output
Server:  <cluster-dns-ip>
Address: <cluster-dns-ip>:53

Name:	web-headless.svc-lab
Address: <pod-ip-1>
Name:	web-headless.svc-lab
Address: <pod-ip-2>
Name:	web-headless.svc-lab
Address: <pod-ip-3>

Headless Services are commonly paired with StatefulSets so each Pod keeps a stable network identity. See Kubernetes StatefulSets for ordered Pod names and per-replica DNS records.


Inspect a Service End to End

When Service traffic misbehaves, walk the path in order instead of jumping to external exposure:

  1. Check Service type, selector, and ports with kubectl describe service.
  2. Confirm matching Pod labels with kubectl get pods --show-labels.
  3. Confirm Pod readiness with kubectl get pods (READY column).
  4. Inspect EndpointSlices for addresses and Ready conditions.
  5. Test a Pod IP and targetPort directly from a debug Pod.
  6. Test the ClusterIP and Service port.
  7. Resolve the Service DNS name.
  8. Test NodePort or LoadBalancer only after internal routing works.

For a quick Service summary:

bash
kubectl describe service -n svc-lab web

Sample output:

output
Name:                     web
Namespace:                svc-lab
Selector:                 service=web
Type:                     ClusterIP
IP:                       <cluster-ip>
Port:                     http  80/TCP
TargetPort:               http/TCP
Endpoints:                <pod-ip-1>:80,<pod-ip-2>:80,<pod-ip-3>:80

If internal checks pass but clients still fail, continue with the dedicated Service troubleshooting guide. For name-resolution problems, see Kubernetes DNS troubleshooting. For local workstation access without changing the Service type, kubectl port-forward is often enough during development.


Service Type Comparison

Service form Main address Typical use
ClusterIP Internal virtual IP Communication inside the cluster
NodePort Node IP and allocated port Simple external or lab access
LoadBalancer External load-balancer address Provider-integrated external exposure
Headless ClusterIP Backend addresses through DNS Direct discovery and stateful peers

What's Next


References

Summary

You built a three-replica web Deployment and fronted it with Kubernetes Services in svc-lab. A ClusterIP Service gave in-cluster clients one DNS name and virtual IP while EndpointSlices listed the Pod addresses and target ports behind that front door.

The important split is between Service port and Pod targetPort, and between the Deployment selector (app) and the Service selector (service). Readiness probes control which backends are eligible; a Pod can stay Running while Service traffic skips it. NodePort and LoadBalancer extend the same selector model outward, and headless Services trade the virtual IP for direct DNS answers when peers need individual Pod addresses.

On a bare lab cluster, LoadBalancer may stay pending even when ClusterIP routing works, so verify internal paths first. When something still fails after that checklist, use the Service troubleshooting guide before changing Ingress rules or cloud load-balancer settings.


Frequently Asked Questions

1. What is the default Kubernetes Service type?

ClusterIP is the default when you omit type in the Service spec. Kubernetes allocates a virtual IP that is reachable inside the cluster through kube-proxy or an equivalent dataplane implementation.

2. What is the difference between port and targetPort on a Service?

port is what clients connect to on the Service address. targetPort is the port on the selected Pod. When targetPort is omitted, it defaults to the same value as port.

3. When should I use a headless Service?

Use clusterIP None when you want DNS to return backend Pod addresses directly instead of a single virtual Service IP. StatefulSets and peer-discovery workloads commonly use headless Services.

4. Do unready Pods receive Service traffic?

Normally, no. Pods that fail readiness remain in EndpointSlices with conditions.ready set to false and are excluded from ordinary Service routing. When publishNotReadyAddresses is true, Kubernetes marks the generated endpoints ready regardless of Pod readiness. This makes them available through DNS and can also make them eligible for Service traffic. During workload termination, a Service proxy may still use serving, terminating endpoints when all available endpoints are terminating.

5. Should I create Endpoints objects manually?

No for normal selector-based Services. The EndpointSlice controller creates EndpointSlices from Pods matching the Service selector. Unready Pods can remain listed with conditions.ready set to false and are normally excluded from Service traffic. Manually maintained legacy Endpoints objects are deprecated; use EndpointSlices for selectorless Services that need manually managed backends. The ready condition is normally based on endpoint serving and termination state and is forced true when publishNotReadyAddresses is enabled.

6. Why does my LoadBalancer Service show EXTERNAL-IP pending?

A LoadBalancer Service needs a cloud or on-prem load-balancer controller that can provision an external address. On a bare kubeadm lab without that integration, pending is expected and does not mean the ClusterIP path is broken.
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with more than 15 years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)