Networking is the backbone of modern technology. It's what allows communications between devices and applications, whether they're sitting next to each other or on opposite sides of the world. Essentially, networking is all about connecting things together.
In the context of Kubernetes, networking is even more important. Kubernetes is a container orchestration platform. This means it's responsible for managing and scaling a large number of containers running in a cluster. These containers need to be able to communicate with each other, as well as with other parts of the infrastructure, such as external services and databases. Kubernetes networking allows this communication to happen seamlessly and securely.
To learn more about Kubernetes architecture, watch this video.
In this blog post, we will take a closer look at:
- Container-to-container communication
- Pod-to-Pod communication
- Pod-to-Service communication
- External-to-Service communication
However, before we dive into the specifics, let’s get an understanding of the basic networking terminology that will be used throughout this post.
Basic Networking Terminology
A host refers to a device or computer that is connected to a network and is capable of communicating with other devices on that network. A host can be a server, a personal computer, a mobile device, or any other device that has the capability to connect to a network. For example, there could be multiple hosts, such as servers, workstations, and laptops inside a company's internal network.
A host is identified on a network by its IP address, which is a unique numerical label.
An IP address is used to identify and locate a device on a network. For example, imagine you're using your laptop at work. And you need to access the company's internal file server. To connect to it you can use its IP address.
An IP address is usually written in the format of four sets of numbers separated by dots, like this: 192.168.0.1. This format is called IPv4, and it is the most widely used IP address format today. Note that each of the four sets of numbers in an IPv4 address can range from 0 to 255.
An additional format called IPv6 exists, and this will gradually be used more and more often in the future. IPv6 addresses are a bit more complex and use hexadecimal digits instead of decimal. Decimal notation uses digits between 0 and 9, while hexadecimal uses digits from 0 to 9, but also A, B, C, D, E, and F, which are used as additional digits. An example of an IPv6 address is 2001:0db8:0000:0000:0000:ff00:0042:8329. We can see that many more numbers are used this time, and they are separated by colons (":") instead of dots.
Now let's think about what happens when a host receives some data over the network. Let's imagine some server just received data on its IP address. This server has tens of applications running on it. A new dilemma arises. What should it do with this data? What application should receive it? This problem is solved with the help of ports.
An IP address identifies a host, but a port identifies an application on that host.
When you type "kodekloud.com" in your web browser, your computer sends data to a host, one of KodeKloud's servers. But for this data's destination, it doesn't just use an IP address, but also a port. In this case, port 443, which is used for encrypted HTTPS connections. When KodeKloud's server sees this data was sent to port 443, it instantly knows that it should forward it to a web server application running on it, such as Nginx, or Apache.
Now that we have a fundamental understanding of key networking concepts, let's dive into the details of different types of communication within Kubernetes.
In Kubernetes, a Pod is the basic unit of deployment. It's essentially a group of one or more containers that are deployed together in the same isolated space. These containers inside a Pod share the same network namespace. While not technically accurate, for simplification purposes you can think of it as if the Pod is a very, very small virtual machine.
And containers running on that same Pod, it's like they would be running on the same, super small virtual computer. So they behave as applications that run on the same operating system. And when two applications running on the same operating system want to talk to each other, they can communicate by using what is called the "localhost". Basically, instead of sending data to an external machine, they send data to the same machine they are running on. Here's an example of how containers in a Pod can communicate with each other using localhost.
Imagine you have a Pod with two containers: a web server and a database server. The web server reserved port 80 for itself and the database server reserved port 3306.
Now let's say the web server application needs to retrieve some information from a database. It needs to contact the database server application. If this would be running on a separate machine, it would need to contact it by using its external IP address. But both of our containers run in the same Pod. So they behave as if they are two applications running on the same host. This means they can use localhost to talk to each other. So the web server application simply needs to connect to localhost, port 3306 to retrieve that data from the database server application.
All Pods in a Kubernetes cluster reside in a single, flat, shared network-address space. A flat network-address space means that all Pods within a Kubernetes cluster are assigned unique IP addresses and can access every other Pod at the other Pod’s IP address. So they behave as if they would be devices that are located in the same building and are connected to the same network. Even if some Pods live on one server, and some other Pods live on an entirely different server, which are separated by additional, external networks. Pods still behave as if they are connected to the same, single internal network. This abstraction makes communication between Pods much more straightforward.
Pod-to-Service Communication (ClusterIP Service)
Pods are ephemeral. They are created when needed and destroyed when not needed. So they are not a stable thing. We can never know when a Pod might disappear. This creates a networking problem. If our destination (Pod) can disappear at any time, how do we make sure network traffic always reaches this target? We can't, with Pods alone. The solution to this problem is a Kubernetes object called a ClusterIP Service. Let's explore why it exists and what it does.
There are many cases where multiple Pods provide the same functionality. Why have multiple Pods, all doing the same thing? We might need more than one for availability, scalability reasons, as well as other motivations. For example, it's better to run 10 Pods providing access to a database, rather than just one. If 1 Pod malfunctions, 9 will still work, so the service will still be available. If there's high demand for database access, Kubernetes can spin up 10 more Pods, on other servers. So now we have 20 Pods, distributed across many different servers, and the workload is handled easier, as there is more processing power available.
But notice the same problem. We can't just pick a Pod, and tell some application in some other Pod: "Connect to IP a.b.c.d". That destination Pod can disappear at any time, along with its IP address, and the application will fail to connect. But the ClusterIP Service object solves this issue.
When a ClusterIP Service is created, it gets a static IP which never changes during the lifetime of the Service. Instead of connecting directly to Pods, clients connect to the ClusterIP Service through its IP address. The ClusterIP Service then makes sure that one of the Pods receives the connection, regardless of where the Pod is running and what its IP address is.
In a nutshell, the ClusterIP Service works as a front gate to those Pods, a stable location to connect to. An application has a guarantee that it can always reach the fixed IP of this ClusterIP Service. And the ClusterIP Service can figure out what exact Pod it should forward that traffic to, based on what it can find available.
In Pod-to-Service communication, we discussed how Services in Kubernetes can be used to provide access to Pods from within the cluster. However, in many cases, you'll also want to expose certain Services to external clients. Exposing a Service to external clients allows them to access the application, even if they are located outside of the cluster network.
There are several ways to expose a Service to external clients in Kubernetes:
A NodePort Service is a way to expose a Service to external clients by making Kubernetes reserve a specific port on all of its nodes (the servers that are actually hosting Pods). This reserved port is called the NodePort, and it is used to forward incoming connections to a Service, which in turn will forward traffic to one of the Pods behind it.
When you create a NodePort Service, you specify the port number that you want to open/expose on your nodes. For example, you might choose to use port number 30001 as the NodePort. Once the NodePort Service is created, Kubernetes reserves port 30001 on all of its nodes and configures them the following way: all external traffic coming into the nodes, on port 30001, will be forwarded to a particular Service.
To summarize, a NodePort Service exposes a "regular" internal Service to the outside world. And traffic goes like this: External client 🡪 Worker Node IP 🡪 NodePort 🡪 ClusterIP Service 🡪 Pod.
To access the Service from an external client, you need to connect to the IP address of one of the nodes in the cluster and the port defined in NodePort. For example, if the IP address of a node in the cluster is 10.0.0.5, you would connect to 10.0.0.5:30001 (IP 10.0.0.5, Port 30001) to access the Service.
It's worth noting that a NodePort Service does not provide any sort of load balancing across the nodes. All the incoming traffic goes to the node where the Service is running. This can lead to uneven distribution of traffic across the nodes and can cause certain nodes to become overloaded while others are underutilized.
A LoadBalancer Service is a way to distribute traffic across multiple nodes in your cluster. For example, it can send some requests to Node 1, some to Node 2, and some to Node 3, in an attempt to not overload any single node, and evenly distribute the workload. And the traffic coming from an external source can go through a path like this: External client 🡪 Loadbalancer 🡪 Worker Node IP 🡪 NodePort 🡪 ClusterIP Service 🡪 Pod.
However, for a LoadBalancer Service to work, it is required to use Kubernetes on a cloud platform that supports the LoadBalancer Service type. This is because the LoadBalancer Service relies on the cloud platform where the Kubernetes cluster is provisioned to create and manage the load balancer.
For example, if you're running a Kubernetes cluster on a cloud platform like AWS, you can create a LoadBalancer Service which then creates an Elastic Load Balancer to route traffic to the nodes.
It's important to keep in mind that the load-balancing logic will be implemented by the cloud provider and not by Kubernetes.
For a more detailed description of Kubernetes networking logic, you can read this blog about Kubernetes Ingress. This also covers some additional things about the LoadBalancer, limitations, and how to overcome some limitations with the Ingress object.
Kubernetes networking is a crucial aspect of deploying and managing containerized applications. And while it may be a vast and complex topic, I hope this post has provided a solid foundation for you to continue your learning and master advanced networking concepts. To further your understanding of Kubernetes, check out the following courses from KodeKloud: