Kubernetes: what is Endpoints

By | 03/13/2021

Usually, we don’t see Endpoints objects when using Kubernetes Services, as they are working under the hood, similarly to ReplicaSets which are “hidden” behind Kubernetes Deployments.

Kubernetes Service

So, Service is a Kubernetes abstraction that uses labels to chose pods to route traffic to, see the Kubernetes: ClusterIP vs NodePort vs LoadBalancer, Services, and Ingress – an overview with examples and  Kubernetes: Service, load balancing, kube-proxy, and iptables:

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

As soon as a new pod appear in a cluster, with labels matching with Service’s selector,  the app=MyApp in the example above – Service will start sending traffic to it.

This is achieved by adding an IP address of this Pod to the Endpoints list of this Service.

Let’s create a simple example:

---
apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
  labels:
    app: nginx
spec:
  containers:
    - name: nginx-container
      image: nginx
      ports:
        - name: web
          containerPort: 80
          protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

Here, we are creating a Pod with NGINX, and a Service with the default type ClusterIP.

Apply the manifest:

[simterm]

$ kubectl apply -f svc-example.yaml 
pod/nginx-pod created
service/nginx-svc created

[/simterm]

Check the Service:

[simterm]

$ kubectl get service nginx-svc
NAME        TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)   AGE
nginx-svc   ClusterIP   172.20.69.253   <none>        80/TCP    26s

[/simterm]

Kubernetes Endpoints

Now, let’s take a closer look at it:

[simterm]

$ kubectl describe service nginx-svc
Name:              nginx-svc
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          app=nginx
Type:              ClusterIP
IP Families:       <none>
IP:                172.20.69.253
IPs:               <none>
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         10.21.56.143:80

[/simterm]

In the end, we can the Endpoints of this Service – the IP od the pod.

Check this Pod:

[simterm]

$ kubectl describe pod nginx-pod
Name:         nginx-pod
Namespace:    default
Priority:     0
Node:         ip-10-21-49-33.us-east-2.compute.internal/10.21.49.33
Start Time:   Sat, 13 Mar 2021 08:37:55 +0200
Labels:       app=nginx
Annotations:  kubernetes.io/psp: eks.privileged
Status:       Running
IP:           10.21.56.143
...

[/simterm]

Here is the IP mentioned above.

And now, let’s check the Ednpointds, which are dedicated API-objects and which can be observed in the same way as Services and Pods:

[simterm]

$ kubectl get endpoints nginx-svc
NAME        ENDPOINTS         AGE
nginx-svc   10.21.56.143:80   18m

[/simterm]

If we will add other pods with the same labels by describing them as additional objects in the manifest file or by creating a Deployment – those pods will be added as Endpoints for the Service:

---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deploy
  labels:
    app: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx-container
        image: nginx
        ports:
          - name: web
            containerPort: 80
            protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  selector:
    app: nginx
  ports:
    - protocol: TCP
      port: 80
      targetPort: 80

Create the Deployment:

[simterm]

$ kubectl apply -f svc-example.yaml 
deployment.apps/nginx-deploy created
service/nginx-svc unchanged

[/simterm]

And check the Endpoints:

[simterm]

$ kubectl get endpoints nginx-svc
NAME        ENDPOINTS                                        AGE
nginx-svc   10.21.37.55:80,10.21.54.174:80,10.21.56.143:80   21m

[/simterm]

Here we can see our 10.21.56.143:80 from the previous pod, and two new – from the pods specified in the replicas of the Deployment above.

Find those pods by using the --selector, similarly, as a Service looks for pods to add them to its Endpoints:

[simterm]

$ kubectl get pod --selector=app=nginx -o wide
NAME                            READY   STATUS    RESTARTS   AGE     IP          
nginx-deploy-7fcd954c94-gbm6d   1/1     Running   0          2m28s   10.21.54.174
nginx-deploy-7fcd954c94-mg8kr   1/1     Running   0          2m28s   10.21.37.55 
nginx-pod                       1/1     Running   0          23m     10.21.56.143

[/simterm]

Custom Endpoint

We also can create a custom endpoint that will be pointed to any desired resource.

For example, describe a new Service:

kind: Service
apiVersion: v1
metadata:
  name: external-svc
spec:
  ports:
    - name: web
      protocol: TCP
      port: 80
      targetPort: 80

Pay attention, that in this case, we didn’t add the selector field.

And describe the Endpoints object:

kind: Endpoints
apiVersion: v1
metadata:
  name: external-svc
subsets: 
  - addresses:
        - ip: 139.59.205.180
    ports:
      - port: 80
        name: web

Here:

  1. name: must be the same as the Service
  2. addresses: an address to send traffic to, in this example this an IP address of a server in the DigitalOcean cloud where the rtfm.co.ua is leaving, but you can set multiply address so the Service will do load-balancing between them as described in the Kubernetes: Service, load balancing, kube-proxy, and iptables
  3. ports.port and ports.name also must be the same as the corresponding Service

Create them:

[simterm]

$ kubectl apply -f external-endpoint.yaml 
service/external-svc created
endpoints/external-svc created

[/simterm]

Check the Service and its Endpoints:

[simterm]

$ kubectl describe svc external-svc
Name:              external-svc
Namespace:         default
Labels:            <none>
Annotations:       <none>
Selector:          <none>
Type:              ClusterIP
IP Families:       <none>
IP:                172.20.45.77
IPs:               <none>
Port:              web  80/TCP
TargetPort:        80/TCP
Endpoints:         139.59.205.180:80

[/simterm]

Run a Pod to check if this Service working:

[simterm]

$ kubectl run pod --rm -i --tty --image ubuntu -- bash

[/simterm]

Install the curl in this pod:

[simterm]

root@pod:/# apt update && apt -y install curl

[/simterm]

And check the Service by its name:

[simterm]

root@pod:/# curl -Ls external-svc | grep \<title\>
<title>RTFM: Linux, DevOps, and system administration</title>

[/simterm]

Or by using its FQDN:

[simterm]

root@pod:/# curl -Ls external-svc.default.svc.cluster.local | grep \<title\>
<title>RTFM: Linux, DevOps, and system administration</title>

[/simterm]

externalName

Another solution to access an external resource can be using a Service with the externalName type:

---
apiVersion: v1
kind: Service
metadata:
  name: rtfm-service
spec:
  ports:
    - port: 80
  type: ExternalName
  externalName: rtfm.co.ua

Apply, and check:

[simterm]

root@pod:/# curl -Ls rtfm-service | grep \<title\>
<title>RTFM: Linux, DevOps, and system administration</title>

[/simterm]

Done.