Anyone who doesn’t know about Kubernetes Service Object, directly from official page, Service is an abstraction which defines a logical set of Pods and a policy by which to access them. Technically speaking it’s a proxy component running inside a Kubernetes cluster, which route the traffics to specific pods based on label selector.
Examples
Let’s create a sample hello-world application using the following manifest and try all 3 services types.
hello-world.yaml
apiVersion: extensions/v1beta1 kind: Deployment metadata: name: hello-world labels: app: hello-world spec: replicas: 1 template: metadata: labels: app: hello-world spec: containers: - image: gcr.io/google-samples/hello-app:1.0 imagePullPolicy: IfNotPresent name: hello-world ports: - containerPort: 8080 protocol: TCP
Apply the manifest using kubectl apply -f hello-world.yaml
and validate as follows.
kubectl describe -f hello-world.yaml Output: Name: hello-world Namespace: default CreationTimestamp: Tue, 10 Mar 2020 12:52:03 -0600 Labels: app=hello-world Annotations: deployment.kubernetes.io/revision: 1 kubectl.kubernetes.io/last-applied-configuration: {"apiVersion":"extensions/v1beta1","kind":"Deployment","metadata":{"annotations":{},"labels":{"app":"hello-world"},"name":"hello-world","n... Selector: app=hello-world Replicas: 1 desired | 1 updated | 1 total | 1 available | 0 unavailable StrategyType: RollingUpdate MinReadySeconds: 0 RollingUpdateStrategy: 1 max unavailable, 1 max surge Pod Template: Labels: app=hello-world Containers: hello-world: Image: gcr.io/google-samples/hello-app:1.0 Port: 8080/TCP Host Port: 0/TCP Environment: <none> Mounts: <none> Volumes: <none> Conditions: Type Status Reason ---- ------ ------ Available True MinimumReplicasAvailable OldReplicaSets: <none> NewReplicaSet: hello-world-5b55d8d85b (1/1 replicas created) Events: Type Reason Age From Message ---- ------ ---- ---- ------- Normal ScalingReplicaSet 5m18s deployment-controller Scaled up replica set hello-world-5b55d8d85b to 1
“Hello World” application is up and running.
ClusterIP
A ClusterIP is a default service type which exposes spec.clusterIp:spec.ports[*].port
- Exposes the service on a cluster internal IP and accessible from
spec.clusterIp
port. - If a
spec.ports[*].targetPort
is set it will route from the port to the targetPort. - You can only access the service while inside the cluster, meaning only container to container communication is possible.
Let’s create a ClusterIP Service type that matches the above deployment.
clusterip-service.yaml
apiVersion: v1 kind: Service metadata: name: clusterip-helloworld spec: selector: app: hello-world ports: - protocol: TCP port: 80 targetPort: 8080
Apply the manifest using kubectl apply -f clusterip-service.yaml
and validate as follows.
kubectl get svc clusterip-helloworld -o json Output: { "apiVersion": "v1", "kind": "Service", "metadata": { "annotations": { "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"name\":\"clusterip-helloworld\",\"namespace\":\"default\"},\"spec\":{\"ports\":[{\"port\":80,\"protocol\":\"TCP\",\"targetPort\":8080}],\"selector\":{\"app\":\"hello-world\"}}}\n" }, "creationTimestamp": "2020-03-10T18:54:31Z", "name": "clusterip-helloworld", "namespace": "default", "resourceVersion": "1026527", "selfLink": "/api/v1/namespaces/default/services/clusterip-helloworld", "uid": "9f83d2e9-c3b1-4492-8ffe-12dd84b23dcc" }, "spec": { "clusterIP": "10.104.64.157", "ports": [ { "port": 80, "protocol": "TCP", "targetPort": 8080 } ], "selector": { "app": "hello-world" }, "sessionAffinity": "None", "type": "ClusterIP" }, "status": { "loadBalancer": {} } }
As per the output Endpoints: 10.1.0.188:8080
which means service object matches one pod with label app=hello-world
. We can test the service object by connecting to the same pods as follows,
kubectl get pods Output: NAME READY STATUS RESTARTS AGE hello-world-5b55d8d85b-h8r7s 1/1 Running 0 10m
exec into pod
kubectl exec -it hello-world-5b55d8d85b-h8r7s sh ## No curl command present inside hello-world so I am using wget, / # wget http://clusterip-helloworld Connecting to clusterip-helloworld (10.104.64.157:80) index.html 100% |*****************************************************************| 68 0:00:00 ETA / # cat index.html Hello, world! Version: 1.0.0 Hostname: hello-world-5b55d8d85b-h8r7s
This means we can connect to the application using ClusterIP service object.
NodePort
A NodePort exposes the following:
<NodeIP>:spec.ports[*].nodePort spec.clusterIp:spec.ports[*].port
- This means it exposes the service on each Node’s IP at a static port (the NodePort).
- You’ll be able to contact the NodePort service, from outside the cluster, by requesting
<NodeIP>:<NodePort>
. - When you access service like this, it will route the request to
spec.clusterIp:spec.ports[*].port
, which will in turn route it to yourspec.ports[*].targetPort
, if set. - This also means you can access the service the same way as ClusterIP, meaning you can access the application using ClusterIP inside the cluster, but you can’t access service
<ClusterIP>:spec.ports[*].nodePort
from outside.
Let’s create a NodePort Service type that matches the above deployment.
nodeport-service.yaml
apiVersion: v1 kind: Service metadata: name: nodeport-helloworld spec: selector: app: hello-world ports: - protocol: TCP port: 80 targetPort: 8080 type: NodePort
Apply the manifest file using kubectl apply -f nodeport-service.yaml
and validate as follows,
kubectl get svc nodeport-helloworld -o json { "apiVersion": "v1", "kind": "Service", "metadata": { "annotations": { "kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Service\",\"metadata\":{\"annotations\":{},\"name\":\"nodeport-helloworld\",\"namespace\":\"default\"},\"spec\":{\"ports\":[{\"port\":80,\"protocol\":\"TCP\",\"targetPort\":8080}],\"selector\":{\"app\":\"hello-world\"},\"type\":\"NodePort\"}}\n" }, "creationTimestamp": "2020-03-10T20:16:49Z", "name": "nodeport-helloworld", "namespace": "default", "resourceVersion": "1032459", "selfLink": "/api/v1/namespaces/default/services/nodeport-helloworld", "uid": "2473d7ab-afcf-4f59-9431-2bc45edbb86c" }, "spec": { "clusterIP": "10.100.54.48", "externalTrafficPolicy": "Cluster", "ports": [ { "nodePort": 31666, "port": 80, "protocol": "TCP", "targetPort": 8080 } ], "selector": { "app": "hello-world" }, "sessionAffinity": "None", "type": "NodePort" }, "status": { "loadBalancer": { "ingress": [ { "hostname": "localhost" } ] } } }
As per above output you should able to browse application outside kubernetes cluster as follows,
# Find NodePort $ kubectl get svc nodeport-helloworld -o custom-columns=nodePort:.spec.ports[].nodePort nodePort 31666 # Find any NodeIP kubectl get node -o wide Output: NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME docker-desktop Ready master 47d v1.15.5 192.168.65.3 <none> Docker Desktop 4.19.76-linuxkit docker://19.3.5 # Use NodeIp and NodePort $ curl 192.168.65.3:31666 Output: Hello, world! Version: 1.0.0 Hostname: hello-world-5b55d8d85b-h8r7s
And inside cluster (exec to any pod first, kubectl exec -it hello-world-5b55d8d85b-h8r7s sh
) as follows,
$ kubectl get svc nodeport-helloworld -o custom-columns=clusterIP:.spec.clusterIP,port:.spec.ports[].port Output: clusterIP port 10.100.54.48 80 / # wget 10.100.54.48 Connecting to 10.100.54.48 (10.100.54.48:80) index.html 100% |***********************************************************************| 68 0:00:00 ETA / # cat index.html Hello, world! Version: 1.0.0 Hostname: hello-world-5b55d8d85b-h8r7s
LoadBalancer
A LoadBalancer exposes the service externally using a cloud provider’s load balancer. It exposes the following:
spec.loadBalancerIp:spec.ports[*].port <NodeIP>:spec.ports[*].nodePort spec.clusterIp:spec.ports[*].port
- This means you can access service externally using your loadbalancer ip.
- This also means you’ll be able to contact the LoadBalancer service, from outside the cluster, by requesting
<NodeIP>:<NodePort>
. - This also means you can access the service same way as ClusterIP.
Let’s create a LoadBalancer
Service type that matches the above deployment.
loadbalancer-service.yaml
apiVersion: v1 kind: Service metadata: name: loadbalancer-helloworld spec: selector: app: hello-world ports: - protocol: TCP port: 80 targetPort: 8080 type: LoadBalancer
Apply the manifest file using kubectl apply -f loadbalancer-service.yaml
.
Now identify all endpoints for each type,
- LoadBalancer type
$ kubectl get svc loadbalancer-helloworld -o custom-columns=HOSTNAME:.status.loadBalancer.ingress[].hostname,PORT:.spec.ports[].port Output: HOSTNAME PORT localhost 80 $ curl localhost Output: Hello, world! Version: 1.0.0 Hostname: hello-world-5b55d8d85b-h8r7s
- NodePort Type
# Find NodePort $ kubectl get svc loadbalancer-helloworld -o custom-columns=nodePort:.spec.ports[].nodePort --no-headers Output: 31238 # Find any NodeIP $ kubectl get node -o wide Output: NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME docker-desktop Ready master 47d v1.15.5 192.168.65.3 <none> Docker Desktop 4.19.76-linuxkit docker://19.3.5 # curl 192.168.65.3:31238 Hello, world! Version: 1.0.0 Hostname: hello-world-5b55d8d85b-h8r7s
- Service Type
# Find Service IP kubectl get svc loadbalancer-helloworld -o custom-columns=clusterIP:.spec.clusterIP,port:.spec.ports[].port Output: clusterIP port 10.108.228.116 80 # Exec into container kubectl exec -it hello-world-5b55d8d85b-h8r7s sh # wget / # wget 10.108.228.116 Connecting to 10.108.228.116 (10.108.228.116:80) index.html 100% |*************************************************************************| 68 0:00:00 ETA / # cat index.html Hello, world! Version: 1.0.0 Hostname: hello-world-5b55d8d85b-h8r7s
Conclusion
Based on the above example,
- If you create
LoadBalancer
service type it automatically createsNodePort
andClusterIP
types service object also. - If you create
NodePort
service type it automatically createsClusterIP
types service object also.
This means
ClusterIP exposure
<NodePort exposure
<LoadBalancer exposure

- If you want to expose something externally, you have to create
LoadBalancer
orNodePort
type service objects, otherwise you can simply useClusterIP
service type. - You can have multiple service types for the same application, we created all 3 service types in the above example.
kubectl get svc -o wide | grep hello-world Output: clusterip-helloworld ClusterIP 10.104.64.157 <none> 80/TCP 156m app=hello-world loadbalancer-helloworld LoadBalancer 10.108.228.116 localhost 80:31238/TCP 54m app=hello-world nodeport-helloworld NodePort 10.100.54.48 <none> 80:31666/TCP 73m app=hello-world