Skip to content

Use ClusterIP, NodePort, LoadBalancer service types and endpoints

Pods are ephemeral. Therefore, placing these behind a service which provides a stable, static entrypoint is a fundamental use of the kubernetes service object. They can be one of the following types:

  • ClusterIP - Internal only
  • NodePort - External, requires access the nodes directly
  • LoadBalancer - External, requires cloud provider, or software implementation to provide one

We can think of these services like loadbalancers, with automatic endpoint management based on label selectors.

Cluster IP

This is the default service type which exposes a set of Pods via a cluster-internal IP. It cannot be reached from outside of the Kubernetes Cluster

flowchart TD
    ExtClient(External Client - Unreachable)

    subgraph Cluster [Kubernetes Cluster]
        PodClient(Pod inside Cluster)
        Service(Service: ClusterIP 10.96.0.1)
        Target1(Pod: Target App)
        Target2(Pod: Target App)
    end

    ExtClient -.-> |Cannot connect| Service
    PodClient --> |Requests| Service

    Service --> Target1
    Service --> Target2

    classDef blocked fill:#1E2D3D,stroke:#F44336,stroke-width:2px,color:#fff

    class PodClient client
    class ExtClient blocked
    class Service svc
    class Target1,Target2 pod

The yaml for which looks like:

apiVersion: v1
kind: Service
metadata:
  name: backend-clusterip
  namespace: myapp
spec:
  type: ClusterIP
  selector:
    app: backend
  ports:
    - port: 80
      targetPort: 8080

We define the service type in spec.type

We definite which Pods we want to target in spec.selector, where we specify the key:value pair of labels. For this specific example, it would look like:

flowchart LR
    subgraph Cluster [Kubernetes Cluster]
        Client(Internal Cluster Client)

        subgraph Namespace [Namespace: myapp]
            direction TD
            Service(Service: backend-clusterip Port 80)
            Pod1(Pod: app=backend Port 8080)
            Pod2(Pod: app=backend Port 8080)
        end
    end

    Client --> |Accesses Port 80| Service
    Service --> |Routes to TargetPort 8080| Pod1
    Service --> |Routes to TargetPort 8080| Pod2

    class Client client
    class Service svc
    class Pod1,Pod2 pod

NodePort

A nodePort service works by exposing a high-value port directly on worker nodes. We can then direct traffic to one of these nodes on the specified port to access our application, even from outside of the cluster.

flowchart TD
    ExtClient(External Client)

    subgraph Cluster [Kubernetes Cluster]
        PortA(Node A - NodePort 30000)
        PortB(Node B - NodePort 30000)

        subgraph Namespace [Namespace: myapp]
            direction LR
            Service(Service: backend-nodeport Port 80)
            Pod1(Pod: app=backend Port 8080)
            Pod2(Pod: app=backend Port 8080)
        end
    end

    ExtClient --> |Accesses NodeIP:30000| PortA
    ExtClient --> |Accesses NodeIP:30000| PortB

    PortA --> |Forwards to Port 80| Service
    PortB --> |Forwards to Port 80| Service

    Service --> |Routes to TargetPort 8080| Pod1
    Service --> |Routes to TargetPort 8080| Pod2

    class ExtClient client
    class PortA,PortB node
    class Service svc
    class Pod1,Pod2 pod

The corresponding yaml looks like:

apiVersion: v1
kind: Service
metadata:
  name: backend-nodeport
  namespace: myapp
spec:
  type: NodePort
  selector:
    app: backend
  ports:
    - port: 80
      targetPort: 8080
      nodePort: 30000

Loadbalancer

A loadBalancer service type exposes the service on a dedicated VIP, usually provided by a cloud provider or software-based solution (metallb, Cilium, etc).

Note we can consider the loadBalancer service type a wrapper around NodePort.

flowchart TD
    ExtClient(External Client)
    ExtLB(External Load Balancer - Public IP)

    subgraph Cluster [Kubernetes Cluster]
        NodeA(Worker Node A - Auto NodePort)
        NodeB(Worker Node B - Auto NodePort)

        subgraph Namespace [Namespace: myapp]
            direction LR
            Service(Service: backend-loadbalancer Port 80)
            Pod1(Pod: app=backend Port 8080)
            Pod2(Pod: app=backend Port 8080)
        end
    end

    ExtClient --> |Accesses Public IP| ExtLB

    ExtLB --> |Routes to Nodes| NodeA
    ExtLB --> |Routes to Nodes| NodeB

    NodeA --> |Forwards to Port 80| Service
    NodeB --> |Forwards to Port 80| Service

    Service --> |Routes to TargetPort 8080| Pod1
    Service --> |Routes to TargetPort 8080| Pod2

    class ExtClient client
    class ExtLB lb
    class NodeA,NodeB node
    class Service svc
    class Pod1,Pod2 pod

The yaml for which looks like:

apiVersion: v1
kind: Service
metadata:
  name: backend-loadbalancer
  namespace: myapp
spec:
  type: LoadBalancer
  selector:
    app: backend
  ports:
    - port: 80
      targetPort: 8080

Endpoints

Endpoints are essentially the destination points for a service traffic, regardless of type. They also provide a useful troubleshooting tool to determine if there are Pods active for a given service. For example:

david@fedora:~/cka$ kubectl get endpoints argocd-redis -n argocd
# Note the Endpoints IP
NAME           ENDPOINTS       AGE
argocd-redis   10.0.1.4:6379   40d

# Note the IP address of the Pod
david@fedora:~/cka$ kubectl get po -n argocd -o wide | grep -i redis
argocd-redis-7f9487d4fd-knhpn                       1/1     Running   2 (8d ago)      24d   10.0.1.4     srv-rk1-01   <none>           <none>

We know the argocd-redis service has a single active endpoint.

Exam Tip

Just because a service object has an IP address doesn't mean it will forward traffic to a working Pod.

Exam Tip

If you're not getting traffic back from a service object, check its endpoints.

Exam Tip

If a service object has endpoints, but is not returning any traffic, check which port it's forwarding traffic to on the pod, and which ports the pod is configured to listen on.