Welcome to another installment of the "Kubernetes in a Nutshell" blog series ๐ In this part we will dive into Kubernetes Services. You will learn about:
- Kubernetes Services types for internal and external communication
- Techniques for service discovery within the cluster
- How to access external services etc.
As always, the code (and YAML!) is available on GitHub
Happy to get your feedback via Twitter or just drop a comment ๐๐ป
Kubernetes Pods are ephemeral i.e. they do not retain their properties across restarts or re-schedules. This applies to container storage (volume), identity (Pod name), and even IP addresses. This poses challenges in terms of application access. Higher level abstractions like Deployments control several Pods and treat them as stateless entities - how do clients access these groups of Pods? Does the client need to be aware of co-ordinates of every Pod underneath a Deployment? Also, you cannot count on a Pod getting the same IP after a restart - how will a client application continue to access the Pod?
Enter Kubernetes Services!
Kubernetes Service
A Service is a higher level component that provides access to a bunch of Pods. It decouples the client application from the specifics of a Deployment (or a set of Pods in general) to enable predictable and stable access.
Kubernetes defines the following types of Services:
-
ClusterIPโโโfor access only within the Kubernetes cluster -
NodePortโโโaccess using IP and port of the Kubernetes Node itself -
LoadBalancerโโโan external load balancer (generally cloud provider specific) is used e.g. an Azure Load Balancer in AKS -
ExternalNameโโโmaps aServiceto an external DNS name
One can classify access patterns into two broad categories:
- External access
- Internal access
External Access
You can use either NodePort or LoadBalancer service if you want external clients to access your apps inside the Kubernetes cluster.
NodePort
NodePort is exactly what it sounds like - makes it possible to access the app within the cluster using the IP of the Node (on which the Pod has been scheduled) and a random port assigned by Kubernetes e.g. for a HTTP endpoint, you would use http://<node_ip>:<port>
Here is an example:
apiVersion: v1
kind: Service
metadata:
name: kin-nodeport-service
spec:
type: NodePort
ports:
- port: 80
targetPort: 8080
selector:
app: kin-service-app
Although NodePort is conceptually quite simple, here are a few points you should note
- the random port allocation is restricted to rangeโโโ
30000โ32767 - the port is the same for every node in the cluster
- it is possible to specify a static port number, but the
Servicecreation might fail for reasons like port allocation, invalid port, etc.
LoadBalancer
When running in a cloud provider, a LoadBalancer service type triggers the provisioning of an external load balancer which distributes traffic amongst the backing Pods.
To see this in action, let's deploy an application on Azure Kubernetes Service and expose it using a LoadBalancer service.
Using a multi-node (at least two) cloud based Kubernetes cluster makes it easy to demonstrate this concept. Feel free to use any other cloud provider (such a
GKEto try out this scenario)
If you want to try this out using Azure, here are a few pre-requisites you should complete before going through the tutorials in this post:
- Get a free Microsoft Azure account!
- Install Azure CLI tool
-
Install
kubectlto access your Kubernetes cluster - Setup a two-node Kubernetes cluster on Azure using the CLI
Once you've finished setting up the cluster, make sure you configure kubectl to connect to it using the az aks get-credentials command - this downloads credentials and configures the Kubernetes CLI to use them.
az aks get-credentials --name <AKS-cluster-name> --resource-group <AKS-resource-group>
You should be all set now. Let's start by creating the Service along as well as the sample application.
To keep things simple, the YAML file is being referenced directly from the GitHub repo, but you can also download the file to your local machine and use it in the same way.
kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/loadbalancer/service.yaml
kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/app/app.yaml
the sample application is really simple Go program
To confirm that the Service has been created
kubectl get svc/kin-lb-service
You should get back a response similar to below
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kin-lb-service LoadBalancer 10.0.149.217 <pending> 80:31789/TCP 1m
The pending status for EXTERNAL-IP is temporary - it's because AKS is provisioning an Azure Load Balancer behind the scenes.
After some time, you should see EXTERNAL-IP populated with the public IP of the Load Balancer
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kin-lb-service LoadBalancer 10.0.149.217 242.42.420.42 80:31789/TCP 2m
Check that the application is deployed as well - you should see two Pods in Running state
kubectl get pod -l=app=kin-service-app
NAME READY STATUS RESTARTS AGE
kin-service-app-775f989dd-gr4jk 1/1 Running 0 5m
kin-service-app-775f989dd-rmq6r 1/1 Running 0 5m
You can access the application using the load balancer IP as such:
curl http://242.42.420.42/
Note that the IP will be different in your case. Also, the load balancer port is
80as perspec.ports.portattribute in theServicemanifest.
You should see a response similar to:
Hello from Pod IP 10.244.0.151 on Node aks-agentpool-37379363-0
This output shows:
- the IP of the
Pod, and, - the name of the
Nodeon which the Pod is present
If you try accessing the application again (curl http://242.42.420.42/), you will most likely be load balanced to another instance Pod which may be on a different Node and you might see a response such as:
Hello from Pod IP 10.244.1.139 on Node aks-agentpool-37379363-1
You can scale your application (in and out) and continue to access it using the Load Balancer IP.
If you want to get the Azure Load Balancer details using the CLI, please use the following commands:
export AZURE_RESOURCE_GROUP=[enter AKS resource group]
export AKS_CLUSTER_NAME=[enter AKS cluster name]
Get the AKS cluster infra resource group name using the az aks show command
INFRA_RG=$(az aks show --resource-group $AZURE_RESOURCE_GROUP --name $AKS_CLUSTER_NAME --query nodeResourceGroup -o tsv)
Use it to list the load balancers with the az network lb list command (you will get back a JSON response with the load balancer information)
az network lb list -g $INFRA_RG
Internal access with ClusterIP
ClusterIP service type and can be used communication within the cluster - just specify ClusterIP in the spec.type and you should be good to go!
ClusterIPis the default service type
Even for intra-cluster communication with ClusterIP Service type, there has to be a way for application A to call application B (via the Service). There are two ways of service discovery for apps within the cluster:
- Environment variables
DNS
Environment variables
Each Pod is populated with a set of environment variables specific to a Service. Let's see this in action! Create a ClusterIP service as follows, and confirm that it has been created
kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/clusterip/service.yaml
kubectl get svc/kin-cip-service
Remember the application we had deployed to explore LoadBalancer Service type? Well, delete and re-create it again (I'll explain why this was done, in a moment)
kubectl delete -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/app/app.yaml
kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/app/app.yaml
Once the app is in Running status (check kubectl get pod -l=app=kin-service-app), exec (execute a command directly inside the Pod) into the Pod to check its environment variables
kubectl exec <enter-pod-name> -- env | grep KIN_CIP
You will see results similar to below:
KIN_CIP_SERVICE_PORT_9090_TCP_ADDR=10.0.44.29
KIN_CIP_SERVICE_SERVICE_PORT=9090
KIN_CIP_SERVICE_PORT_9090_TCP_PROTO=tcp
KIN_CIP_SERVICE_PORT_9090_TCP=tcp://10.0.44.29:9090
KIN_CIP_SERVICE_PORT=tcp://10.0.44.29:9090
KIN_CIP_SERVICE_SERVICE_HOST=10.0.44.29
KIN_CIP_SERVICE_PORT_9090_TCP_PORT=9090
Notice the format of the environment variables names? They include the name of the ClusterIP Service itself (i.e. kin-cip-service) with - replaced by _ and the rest being upper-cased. This is a pre-defined format and can be used to communicate with another application given you know the name of the Service which backs it.
There is a caveat: a
Podis seeded with the environment variables only if theServicewas created before it! That's the reason why we had to recreate the application to see the effect.
Let's access this application from another Pod using the environment variables. Just run another Pod with curl
kubectl run --rm --generator=run-pod/v1 curl --image=radial/busyboxplus:curl -i --tty
you should see a command prompt soon
Confirm that this Pod has the environment variables as well (use env | grep KIN_CIP) and then simply curl the application endpoint using the environment variables
curl http://$KIN_CIP_SERVICE_SERVICE_HOST:$KIN_CIP_SERVICE_SERVICE_PORT
You should see the same response as in the case of LoadBalancer example, i.e.
Hello from Pod IP 10.244.0.153 on Node aks-agentpool-37379363-0
Try it a few more times to confirm that the load is getting balanced among the individual Pods! So we derived the environment variables based on the Service name i.e. kin-cip-service was converted to KIN_CIP_SERVICE and the rest of the parts were added - _SERVICE_HOST and _SERVICE_PORT for the host and port respectively.
DNS
Kubernetes has a built-in DNS server (e.g. CoreDNS) which maintains DNS records for each Service. Just like environment variables, DNS technique provides a consistent naming scheme based on which you can access applications given you know their Service name (and other info like namespace if needed).
The good thing is that this technique does not depend on the order of
SerivceandPodcreation as was the case with environment variables
You can try it right away:
Run the curl Pod again
kubectl run --rm --generator=run-pod/v1 curl --image=radial/busyboxplus:curl -i --tty
To access the application:
curl http://kin-cip-service.default.svc.cluster.local:9090
Everything should work the same way! The FQDN format is <service-name>.<namespace>.<cluster-domain-suffix>. In our case, it maps to:
-
service-name-kin-cip-service -
namespace-default -
cluster-domain-suffix-svc.cluster.local
For applications in the same
namespaceyou can actually skip most of this and just use the service name!
Mapping external services
There are cases where you are required to refer to external services from applications inside your Kubernetes cluster. You can do this in a couple of ways
- In a static manner using
Endpointsresource - Using the
ExternalNameservice type
Static Endpoints
It's a good time to reveal that Kubernetes creates an Endpoints resource for every Service you create (if you use a selector which is mostly the case except for few scenarios). It's the Endpoints object which actually captures the IPs of the backing Pods. You can look at existing ones using kubectl get endpoints.
In case you want to access an external service, you need to:
- create a
Servicewithout anyselector- Kubernetes will NOT create anEndpointsobject - manually create the
Endpointsresource corresponding to yourService(with the same name) and IP/port of the service you want to access.
This gives you the same benefits i.e. you can keep the Service constant and update the actual entity behind the scenes if needed. As an example, we will abstract access to a public HTTP endpoint http://demo.nats.io:8222/ using this technique.
Start by creating the Service
kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/external/static/service.yaml
Here is what the Service looks like. Notice that we are mapping port 8080 to the actual (target) port we want to access 8222
kind: Service
apiVersion: v1
metadata:
name: demo-nats-public-service
spec:
type: ClusterIP
ports:
- port: 8080
targetPort: 8222
Let's look at the Endpoints resource.
kind: Endpoints
apiVersion: v1
metadata:
name: demo-nats-public-service
subsets:
- addresses:
- ip: 107.170.221.32
ports:
- port: 8222
Notice the following:
- name of the resource (
demo-nats-public-service) is the same as theService - we've used the
subsetsattribute to specify theipand theport(we found theipbackingdemo.nats.ioby simply usingping demo.nats.io)
Create it using:
kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/external/static/endpoints.yaml
That's it! Let's see if this works. Simply run a curl Pod
kubectl run --rm --generator=run-pod/v1 curl --image=radial/busyboxplus:curl -i --tty
Now, all we need to use is the name of our Service i.e. demo-nats-public-service (along with the port 8080) and it will do the trick.
curl http://demo-nats-public-service:8080
You should see the following response (this is the same as if you had browsed to http://demo.nats.io:8222)
<html lang="en">
<head>
<link rel="shortcut icon" href="http://nats.io/img/favicon.ico">
<style type="text/css">
body { font-family: "Century Gothic", CenturyGothic, AppleGothic, sans-serif; font-size: 22; }
a { margin-left: 32px; }
</style>
</head>
<body>
<img src="http://nats.io/img/logo.png" alt="NATS">
<br/>
<a href=/varz>varz</a><br/>
<a href=/connz>connz</a><br/>
<a href=/routez>routez</a><br/>
<a href=/gatewayz>gatewayz</a><br/>
<a href=/leafz>leafz</a><br/>
<a href=/subsz>subsz</a><br/>
<br/>
<a href=https://docs.nats.io/nats-server/configuration/monitoring.html>help</a>
</body>
</html>
ExternalName
ExternalName is another Service type which can be used to map a Service to a DNS name. Note that this is a DNS name and not an IP/port combination as was the case with the above strategy (using manually created Endpoints).
In the Serivce manifest:
- don't include a
selector - use
externalNameattribute to specify DNS name e.g.test.example.com - using IP addresses is not allowed
Here is an example:
apiVersion: v1
kind: Service
metadata:
name: demo-nats-public-service2
spec:
type: ExternalName
externalName: demo.nats.io
We are creating a Service with the name demo-nats-public-service2 which maps to DNS name demo.nats.io using the spec.type which is ExternalName.
It works the same way i.e. you need to use the Service name to access the external entity. The only difference (compared to the manual Endpoints approach) is that you'll need to know the port as well. To try this:
Create the Service
kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/external/external-name/service.yaml
Simply run a curl Pod
kubectl run --rm --generator=run-pod/v1 curl --image=radial/busyboxplus:curl -i --tty
Now, all we need to use is the name of our Service i.e. demo-nats-public-service2 (along with port 8222)
curl http://demo-nats-public-service2:8222
You should see the same response as the previous scenario
Headless Service
You can get load balancing among a group of Pods using LoadBalancer, NodePort and ClusterIP Service types. A Headless Service allows access to individual Pods - there is no proxying involved in this case. This is useful in many scenarios e.g.
- consider a peer-to-peer system where individual instances (Pods) will need to access each other.
- a master-follower style service where follower instances need to be aware of the master Pod
In order to create a Headless Service, you need to explicitly specify None as a value for .spec.clusterIP. Here is an example:
apiVersion: v1
kind: Service
metadata:
name: kin-hl-service
spec:
clusterIP: None
ports:
- port: 9090
targetPort: 8080
selector:
app: kin-service-app
The way you use a Headless Service is different compared to other types. A DNS lookup against the Service (e.g. <service-name>.<namespace>.svc.cliuster.local) returns multiple IPs corresponding to different Pods (as compared to a single virtual IP in case of other Service types). Let's see this in action
Create the Service
kubectl apply -f https://raw.githubusercontent.com/abhirockzz/kubernetes-in-a-nutshell/master/services/headless/service.yaml
Run the curl Pod
kubectl run --rm --generator=run-pod/v1 curl --image=radial/busyboxplus:curl -i --tty
Check the backing Pod IPs
nslookup kin-hl-service.default.svc.cluster.local
You will get a response similar to the below
[ root@curl:/ ]$ nslookup kin-hl-service
Server: 10.0.0.10
Address 1: 10.0.0.10 kube-dns.kube-system.svc.cluster.local
Name: kin-hl-service
Address 1: 10.244.0.153 10-244-0-153.kin-hl-service.default.svc.cluster.local
Address 2: 10.244.1.141 10-244-1-141.kin-hl-service.default.svc.cluster.local
10-244-0-153.kin-hl-service.default.svc.cluster.local and 10.244.1.141 10-244-1-141.kin-hl-service.default.svc.cluster.local correspond to co-ordinates of the individual Pods - this is not possible with a traditional Service type. You can now use this to access specific Pod e.g.
curl http://10-244-0-153.kin-hl-service.default.svc.cluster.local:8080
//response
Hello from Pod IP 10.244.0.153 on Node aks-agentpool-37379363-0
curl http://10-244-1-141.kin-hl-service.default.svc.cluster.local:8080
//response
Hello from Pod IP 10.244.1.141 on Node aks-agentpool-37379363-1
Ingress
We did cover the basics of Kubernetes Service, but I do want to highlight Ingress which deserves a separate post altogether. Ingress is not a Service type (such as ClusterIP etc.) - think of as an abstraction on top of a Service. Just like Services front end a bunch of Pods, an Ingress can be configured to work with several backing Services and forward the requests as per rules which you can define.
The intelligence provided by an Ingress is actually implemented in the form of an Ingress Controller. For example, Minikube comes with an NGINX based Ingress Controller. The controller is responsible for providing access to the appropriate backing Service after evaluation of the Ingress rules.
That's it for this part of the "Kubernetes in a Nutshell" series. Stay tuned for more!
Friendly reminder if you are interested in learning Kubernetes and Containers using Azure! Simply create a free account and get going! A good starting point is to use the quickstarts, tutorials and code samples in the documentation to familiarize yourself with the service. I also highly recommend checking out the 50 days Kubernetes Learning Path. Advanced users might want to refer to Kubernetes best practices or watch some of the videos for demos, top features and technical sessions.
I really hope you enjoyed and learned something from this article ๐ Please like and follow if you did!


Top comments (0)