We’ve seen that Kubernetes is a very dynamic system, with Pods coming and going at any time. Unfortunately, this also means that if you connected to a Pod via a particular IP address at one moment, that endpoint might be unreachable later. Let’s see how the Kubernetes Service object solves this problem.
Services
In Kubernetes, your application may be running in a Deployment with several replicas, as seen previously. However, each Pod has its own unique IP address, which changes when the Pod is rescheduled or deleted. This makes it tough to know which IP address your applications need to link to. This is where Services come in. They make it possible for a group of Pods to be accessible via a single stable IP address.
As seen above, a Service specifies a set of endpoints, which often are Pods but can also include other Services. When a client submits a request to the Service’s IP address, the request is automatically forwarded to a Pod in the Service's backend pool. As a bonus, clients within the Kubernetes cluster don't need to use the Service's IP address. They simply send requests using the Service name, which automatically resolves to the Service's IP address.
The way a Service knows which Pods to target is through label selectors, similar to how a ReplicaSet finds its Pods.
Kubernetes provides various types of Services: ClusterIP, NodePort, LoadBalancer, and ExternalName, each designed for a particular use case. But first, let’s look at a Service manifest.
The Service manifest
A Service is a Kubernetes object that you can create, view, or modify its definitions using the kubectl command line tool. Here’s an example (svc.yaml):
apiVersion: v1
kind: Service
metadata:
name: myapp-service
spec:
# This tells the service to send traffic to Pods whose labels exactly have the label 'app: web-server'
selector:
app: web-server
ports:
- protocol: TCP
port: 80
targetPort: 8080
type: ClusterIPThe type field indicates the type of Service you want to create, and the selector field specifies which Pods the Service forwards traffic to. In the above example, this Service will select only those Pods that have the label app: web-server. Navigate to the location you saved the YAML file and apply it:
$ kubectl apply -f svc.yamlAnother way you can create a Service is by exposing a Deployment imperatively using the following command:
$ kubectl expose deployment <deployment-name> --type=ClusterIP --name=<service-name> --target-port=8080 --port=80If you neglect to specify the --type flag, Kubernetes will create a Service of type ClusterIP by default. If you do not provide a targetPort value, it defaults to the value specified in the Deployment's port field for a container.
Then, verify that the Service was created successfully and note the automatically generated ClusterIP address:
$ kubectl get svc web-server
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE SELECTOR
web-server ClusterIP 34.118.226.21 <none> 80/TCP 55s app=web-serverThe ports sub-field within the spec field in the Service manifest includes port and targetPort sub-fields. The port represents the port that the Service exposes within the cluster. This is the <cluster-ip>:<port> that other Pods in the cluster use to interact with your application (34.118.226.21:80 in this case). The targetPort is the port your application listens on within the Pod; essentially, it's the Pod's internal port (8080 in our example, but it varies by application). Now, let’s take a closer look at each Service type.
ClusterIP
The Service we created in the previous section was a ClusterIP. This type of Service allows applications and other Services to communicate inside the cluster:
In the image above, you see how other Pods inside the cluster interact with the application we deployed. When pod-A starts a request, it links to the Service through the Service's Cluster-IP and port. The Service then sends the request to our application pods at pod-IP:targetPort.
ClusterIP Services are ideal for inter-service communication within the cluster, providing an internal network isolated from external traffic. You can view the IP addresses of Pods that your Service is targeting with the following command:
$ kubectl get endpointslices
NAME ADDRESSTYPE PORTS ENDPOINTS AGE
web-server-l759w IPv4 8080 10.177.129.9,10.177.128.30,10.177.128.203 15mAs you can see, the Service is targeting 3 Pods with these IP addresses: 10.177.129.9, 10.177.128.30, and 10.177.128.203. Here’s what happens as the Deployment is dynamically scaled:
As Pods are added and removed, the Service updates the available endpoints accordingly.
NodePort
A NodePort Service makes an internally running application accessible to the external world over a specific port named nodePort. In this case, any traffic received on the node’s IP and port is forwarded to a suitable Service. If you can reach any node in the cluster, you can communicate with a Service, which in turn sends traffic to Pods.
In the image above, you can see how external clients direct their traffic to the nodes inside the cluster, specifying the nodePort. This traffic then goes to a ClusterIP Service on its specified port. The Service finally guides it to a specific Pod or container listening on the targetPort.
NodePort builds on top of the ClusterIP Service type. So, when you create a NodePort Service, Kubernetes also creates a corresponding ClusterIP Service for the Service. You can define the nodePort either in the ports section of the Service manifest or by using the --nodePort flag if creating a Service imperatively. The default range for nodePort is 30000-32767. If you don't specify a specific port when creating a NodePort Service, one is automatically assigned from this range.
LoadBalancer
A LoadBalancer Service is a Service type used for making applications visible online. It permits external access to your application and automatically balances the traffic:
Kubernetes does not include a load balancer component, so one must be provided somehow. If you are running a managed cluster, like on GKE or EKS, a load balancer is automatically provisioned when you create a LoadBalancer Service:
$ kubectl get events -o custom-columns='TYPE:.type,OBJECT:.involvedObject.name,SOURCE:.source.component,MESSAGE:.message'
TYPE OBJECT SOURCE MESSAGE
Normal mysvc clouddns-controller DNS records updated
Normal mysvc sc-gateway-controller devns/mysvc
Normal mysvc service-controller Ensuring load balancer
Normal mysvc service-controller Ensured load balancerFor local Kind clusters, you need to run the cloud provider Kind binary to create load balancers for your Services. You may need to run the binary with the --gateway-channel="disabled” flag if you run into issues. For minikube clusters, you need to run minikube tunnel in a separate terminal.
Simultaneously, Kubernetes also sets up a NodePort Service to act as a conduit, redirecting traffic from the LoadBalancer to the internal ClusterIP Service, which sends the traffic to the Pods. LoadBalancer Services allow you to make your applications open to the Public without exposing your node IPs directly.
If you are running a local cluster with a tool like Minikube, you can use the minikube tunnel command.
If we edit the Service we created earlier to a LoadBalancer type, we now get an external IP address that external clients can use to communicate with our applications:
$ kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
my-svc LoadBalancer 34.118.237.158 34.155.81.80 80:32623/TCP 25hWhen you delete the Service, the LoadBalancer is automatically removed.
ExternalName
ExternalName is a type of Service that acts as a DNS alias. In other words, it offers a method to connect a Kubernetes Service to an external DNS name. This comes in handy when you want to link to external Services by name, without having to embed IP addresses or tamper with DNS settings inside your application Pods. Unlike other Services, ExternalName Services do not balance the traffic or proxy requests to any Pods or endpoints. Instead, it associates a Kubernetes Service with a DNS name.
Let’s consider an example. Imagine you're running a web application that needs to connect to an external OAuth2 identity provider for user verification. This OAuth2 identity provider has fixed endpoints, including the authorization server, token endpoint, and user info endpoint. To streamline the setup and ensure secure integration, you can employ an ExternalName Service. It would have a definition like that below:
apiVersion: v1
kind: Service
metadata:
name: oauth-identity-provider
spec:
type: ExternalName
# provide the address of any external API that you would like to use (like google api, dropbox api or any oauth service provider)
externalName: oauth2.provider.comYou can now tweak your web application settings to use the DNS name given by the ExternalName Service. The DNS name (oauth-identity-provider) will automatically resolve to the OAuth2 identity provider's authorization server URL. This service is typically used when interfacing with external clients or services. It differs slightly from other Service objects, offering a unique way to connect to external resources.
Conclusion
Services in Kubernetes are crucial for managing networking within a cluster. They offer a layer of abstraction that simplifies how you connect and access applications. Here’s a recap of what we’ve covered:
ClusterIP Services facilitate internal communication.
NodePort Services provide external access, perfect for testing and debugging.
LoadBalancer Services are your primary choice for making applications reachable from the internet. They balance the load and boost security.
ExternalName Services link seamlessly with external clients or services such as OAuth and external APIs.