Qingular

Endpoints and Service Discovery

·CKAk8sPractice

CKA Domain 3 — Kubernetes Endpoints/EndpointSlice resource management and service discovery mechanisms

← Back to CKA Practice Index

Overview

Endpoints is a Kubernetes resource that tracks the IP addresses of Service backend Pods. When a Service has a Selector, K8s automatically creates and updates Endpoints. EndpointSlice is the next-generation replacement (K8s v1.21+), solving the performance issues of a single Endpoints resource in large-scale clusters.


1. Endpoints Resource

Automatic Management

When a Service defines a Selector, K8s automatically creates and maintains Endpoints:

apiVersion: v1
kind: Service
metadata:
  name: my-svc
spec:
  selector:
    app: nginx
  ports:
    - port: 80
      targetPort: 80
---
# Endpoints automatically created by K8s
apiVersion: v1
kind: Endpoints
metadata:
  name: my-svc          # Name must match the Service name
subsets:
  - addresses:
      - ip: 10.244.1.5
        nodeName: node-1
        targetRef:
          kind: Pod
          name: nginx-pod-1
          namespace: default
      - ip: 10.244.2.7
        nodeName: node-2
        targetRef:
          kind: Pod
          name: nginx-pod-2
          namespace: default
    ports:
      - port: 80
        protocol: TCP
# View Endpoints
kubectl get endpoints
kubectl get endpoints my-svc
kubectl describe endpoints my-svc

# Endpoints name has a one-to-one mapping with Service name
# If a Service has no Endpoints, it means no Pods matched
kubectl get svc,ep

Manually Creating Endpoints (Pointing to External Services)

When you need a Service to point to an external service (such as an external database), you can create a Service without a Selector and manually bind Endpoints.

# 1. Create a Service without a Selector
apiVersion: v1
kind: Service
metadata:
  name: external-mysql
spec:
  ports:
    - port: 3306
      targetPort: 3306
---
# 2. Manually create Endpoints with the same name
apiVersion: v1
kind: Endpoints
metadata:
  name: external-mysql    # Name must match the Service name
subsets:
  - addresses:
      - ip: 192.168.1.100    # External database IP
      - ip: 192.168.1.101
    ports:
      - port: 3306
# Verify
kubectl get svc external-mysql
kubectl get endpoints external-mysql

# Test from a Pod
kubectl run test-db --image=busybox:1.28 --rm -it --restart=Never -- telnet external-mysql 3306

2. EndpointSlice (K8s v1.21+)

EndpointSlice is the replacement for Endpoints, solving the performance bottleneck of a single Endpoints object in large clusters.

Core Features

  • Automatic sharding: Each EndpointSlice contains at most 100 endpoints by default
  • Address types: Supports IPv4, IPv6, FQDN
  • More efficient updates: Only changed shards are updated rather than the entire Endpoints
# View EndpointSlices
kubectl get endpointslices
kubectl get endpointslices -o yaml

# View association with Service
kubectl describe endpointslices <slice-name>

EndpointSlice YAML Example

apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
  name: my-svc-abc123
  labels:
    kubernetes.io/service-name: my-svc    # Required label identifying the owning Service
addressType: IPv4
endpoints:
  - addresses:
      - 10.244.1.5
    conditions:
      ready: true
      serving: true
      terminating: false
    nodeName: node-1
    targetRef:
      kind: Pod
      name: nginx-pod-1
      namespace: default
  - addresses:
      - 10.244.2.7
    conditions:
      ready: true
      serving: true
      terminating: false
    nodeName: node-2
  - addresses:
      - 10.244.3.9
    conditions:
      ready: false        # Pod not ready
      serving: false
      terminating: false
ports:
  - name: http
    protocol: TCP
    port: 80

Endpoints vs EndpointSlice

FeatureEndpointsEndpointSlice
API versionv1discovery.k8s.io/v1
GA stageGA from the startK8s v1.21+ GA
Single object capacityUnlimited (single large object)Max 100 endpoints per slice
Update efficiencyFull updateIncremental update
Multiple address typesIPv4 onlyIPv4, IPv6, FQDN
Large-scale clustersPerformance degradationRecommended

3. Service Types Without a Selector

In addition to manually binding Endpoints, there are the following Selector-less Service variants:

1. ExternalName Service

Maps a Service to an external domain name via DNS CNAME.

apiVersion: v1
kind: Service
metadata:
  name: external-api
spec:
  type: ExternalName
  externalName: api.example.com
  # No selector or ports needed
kubectl run test --image=busybox:1.28 --rm -it --restart=Never -- nslookup external-api
# Returns a CNAME record: api.example.com

2. Service Pointing to External IPs (No Selector + Manual Endpoints)

apiVersion: v1
kind: Service
metadata:
  name: external-ip-svc
spec:
  ports:
    - name: http
      port: 80
---
apiVersion: v1
kind: Endpoints
metadata:
  name: external-ip-svc
subsets:
  - addresses:
      - ip: 10.0.0.1
      - ip: 10.0.0.2
    ports:
      - port: 80

3. Verifying a Selector-less Service

# A Service without a Selector does not automatically create Endpoints
kubectl describe svc external-ip-svc
# Note the output has no Endpoints line (unless manually created)

4. Service Discovery

Kubernetes provides two service discovery mechanisms: Environment Variables and DNS.

1. Environment Variables

K8s injects Service environment variables when a Pod starts.

# View environment variables in a Pod
kubectl exec <pod-name> -- env

# Output contains (example):
# MY_SVC_SERVICE_HOST=10.96.0.20
# MY_SVC_SERVICE_PORT=80
# MY_SVC_PORT=tcp://10.96.0.20:80
# MY_SVC_PORT_80_TCP=tcp://10.96.0.20:80
# MY_SVC_PORT_80_TCP_PROTO=tcp
# MY_SVC_PORT_80_TCP_PORT=80
# MY_SVC_PORT_80_TCP_ADDR=10.96.0.20

Environment variable naming convention:

<SERVICE_NAME>_SERVICE_HOST
<SERVICE_NAME>_SERVICE_PORT
# Verify environment variables
kubectl run env-test --image=busybox:1.28 --rm -it --restart=Never -- sh
# Inside the container
env | grep -i kubernetes
# KUBERNETES_SERVICE_HOST=10.96.0.1
# KUBERNETES_SERVICE_PORT=443

Limitations:

  • Only injected at Pod creation time; Services created after the Pod starts will not appear
  • Depends on the creation order of Pod and Service (DNS is recommended)

Resolve Service domain names through CoreDNS.

# Access within the same namespace
kubectl run dns-test --image=busybox:1.28 --rm -it --restart=Never -- nslookup my-svc

# Access across namespaces
kubectl run dns-test --image=busybox:1.28 --rm -it --restart=Never -- nslookup my-svc.other-ns

# Full FQDN
kubectl run dns-test --image=busybox:1.28 --rm -it --restart=Never -- nslookup my-svc.default.svc.cluster.local

3. Environment Variables vs DNS Comparison

FeatureEnvironment VariablesDNS
Creation order dependencyYes (Service must exist before Pod)No
Dynamic updatesNo (requires Pod recreation)Yes
Cross-namespaceNot supportedSupported
RecommendedNot recommendedRecommended

# Debug: Service has no Endpoints
kubectl describe svc my-svc
# See <none> in the Endpoints field

# Check if Selector matches Pods
kubectl get pods -l app=my-app
kubectl get svc my-svc -o yaml | grep selector

# Check if Pods are ready
kubectl get pods -l app=my-app
# Ensure STATUS is Running and READY column is 1/1

# Manually test Endpoints connectivity
kubectl run test --image=busybox:1.28 --rm -it --restart=Never -- wget -qO- http://<endpoint-ip>:80

# View EndpointSlice details
kubectl get endpointslices -l kubernetes.io/service-name=my-svc -o yaml


🧪 Complete Hands-on Example: Creating Manual Endpoints Pointing to an External Service

Scenario Description

Create a Service without a Selector, manually configure Endpoints pointing to a simulated external database IP, then verify that Pods can access the "external database" via the Service name.

Prerequisites

  • Cluster is running normally
  • kubectl is configured for cluster access
  • Prepare a reachable external IP for testing (can use a cluster node IP or any reachable address)

Steps

Step 1: Create a Service without a Selector

# Create a Service named external-db without specifying a selector
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Service
metadata:
  name: external-db
spec:
  ports:
    - protocol: TCP
      port: 3306
      targetPort: 3306
EOF
# Expected output: service/external-db created

# Verify the Service has no Selector and no Endpoints
kubectl get svc external-db
# NAME           TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
# external-db    ClusterIP   10.96.200.10    <none>        3306/TCP   5s

kubectl describe svc external-db
# ... Note the Endpoints field is <none> and there is no Selector line in the output

Step 2: Manually create Endpoints with the same name pointing to an external IP

# Manually create Endpoints; the name must exactly match the Service
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Endpoints
metadata:
  name: external-db    # Name must match the Service
subsets:
  - addresses:
      - ip: 192.168.1.100    # Simulated external database IP
    ports:
      - port: 3306
EOF
# Expected output: endpoints/external-db created

# Verify Endpoints are bound
kubectl get endpoints external-db
# NAME           ENDPOINTS                    AGE
# external-db    192.168.1.100:3306           5s

kubectl describe svc external-db
# ... Endpoints field now shows 192.168.1.100:3306

Step 3: Create a test Pod to verify service discovery

# Create a test Pod
kubectl run test-app --image=busybox:1.28 --rm -it --restart=Never -- sh
# Note: Execute the following commands inside the container

# Inside the test Pod's shell:
# 1. Check DNS resolution
nslookup external-db
# Expected output:
# Server:    10.96.0.10
# Address:   10.96.0.10:53
# Name:      external-db.default.svc.cluster.local
# Address:   10.96.200.10      # Service's ClusterIP

# 2. Connect to the external service via Service name
# (If there is an actual service on 192.168.1.100:3306, use the following commands to test)
# nc -zv external-db 3306
# telnet external-db 3306

# 3. View environment variables
env | grep EXTERNAL
# Expected output:
# EXTERNAL_DB_SERVICE_HOST=10.96.200.10
# EXTERNAL_DB_SERVICE_PORT=3306
# EXTERNAL_DB_PORT=tcp://10.96.200.10:3306
# ...

# 4. View /etc/resolv.conf
cat /etc/resolv.conf
# Expected output:
# nameserver 10.96.0.10
# search default.svc.cluster.local svc.cluster.local cluster.local
# options ndots:5

# Press Ctrl+D to exit the container

Step 4: Compare with automatic Endpoints (create a Service with a Selector)

# Create automatic Endpoints for comparison
kubectl create deployment test-nginx --image=nginx:1.25 --replicas=2
kubectl expose deployment test-nginx --name=auto-svc --port=80 --target-port=80

# View the automatically created Endpoints
kubectl get endpoints auto-svc
# NAME        ENDPOINTS                           AGE
# auto-svc    10.244.1.5:80,10.244.2.7:80         10s

kubectl describe endpoints auto-svc
# Output includes targetRef, referencing specific Pod names

# Compare with manually created Endpoints
kubectl describe endpoints external-db
# Output has no targetRef because it points to an external address rather than a Pod

Step 5: Add multiple external addresses (high availability scenario)

# Update Endpoints to add multiple external addresses for simple load balancing
cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Endpoints
metadata:
  name: external-db
subsets:
  - addresses:
      - ip: 192.168.1.100    # Primary database
      - ip: 192.168.1.101    # Standby database
    ports:
      - port: 3306
EOF
# Expected output: endpoints/external-db configured

# Verify multiple addresses
kubectl get endpoints external-db
# NAME           ENDPOINTS                                    AGE
# external-db    192.168.1.100:3306,192.168.1.101:3306       1m

# Verify from a Pod that DNS still resolves to the Service ClusterIP
kubectl run test-app2 --image=busybox:1.28 --rm -it --restart=Never -- nslookup external-db
# Expected output: DNS still returns the Service ClusterIP; kube-proxy handles load balancing between the two backends

Verification

# View all Services and their Endpoints
kubectl get svc,ep

# Check the full status of the Selector-less Service
kubectl describe svc external-db
# Confirm the output includes:
# Type:              ClusterIP
# IP:                10.96.200.10
# Port:              <unset>  3306/TCP
# Endpoints:         192.168.1.100:3306,192.168.1.101:3306
# Session Affinity:  None
# Note: No Selector field is displayed

# Clean up resources
kubectl delete svc external-db auto-svc
kubectl delete endpoints external-db
kubectl delete deployment test-nginx

Exam Tips