Kubernetes Load Balancer: Expert Guide With Examples
Tags: kubernetes, cloud, devops, tutorial
Published: 2023-02-10 11:55:06

<!-- wp:paragraph -->
<p>“Kubernetes load balancer” is a pretty broad term that refers to multiple things. In this article, we will look at two types of load balancers: one used to expose Kubernetes services to the external world and another used by engineers to balance network traffic loads to those services.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Keep reading to get proven best practices for dealing with a Kubernetes load balancer.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading" id="h-what-is-a-kubernetes-load-balancer">What is a Kubernetes load balancer?</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>In Kubernetes, containers are grouped into pods with shared storage and network resources, as well as specifications for how to run these containers. A group of related pods can form a&nbsp; Kubernetes service.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Since pods aren’t persistent – Kubernetes creates and destroys them automatically – their IP addresses aren’t persistent either. To expose pods, you need to use a Kubernetes resource called Service.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>A Kubernetes service allows you to expose a group of pods to external or internal usage. You can choose from a few types of services, so here’s a quick overview to get you started.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading" id="h-overview-of-kubernetes-services">Overview of Kubernetes Services</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p><strong>ClusterIP</strong> – this is a default type of K8s service which exposes a set of pods only internally. Here’s an example YAML definition for the ClusterIP Service:&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>apiVersion: v1
kind: Service
metadata:&nbsp;
&nbsp;name: my-internal-service
spec:
&nbsp;selector:&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;app: my-app
&nbsp;type: ClusterIP
&nbsp;ports:&nbsp;
&nbsp;- name: http
&nbsp;&nbsp;&nbsp;port: 80
&nbsp;&nbsp;&nbsp;targetPort: 80
&nbsp;&nbsp;&nbsp;protocol: TCP</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>ClusterIP is used for internal application communication and is not available outside of the cluster.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><strong>NodePort</strong> – the Service exposes a given port on each Node IP in the cluster.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:image -->
<figure class="wp-block-image"><img src="https://lh4.googleusercontent.com/7J3zk3Wx4GKskPdrHlSjTpsaU_jQRR0w7u5hagjYJPjMPgsZT915b4RdhEeoB_gWQ1rQgyuwV7wGXiFKgCgOVQi40CneLetZWkmFjUA6YvbjV6ckDvtaKRijhjdxtSEuPh1WaZYXhdYJ9jj6Suu41p4" alt=""/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Example YAML definition:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>apiVersion: v1
kind: Service
metadata:&nbsp;
&nbsp;name: my-nodeport-service
spec:
&nbsp;selector:&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;app: my-app
&nbsp;type: NodePort
&nbsp;ports:&nbsp;
&nbsp;- name: http
&nbsp;&nbsp;&nbsp;port: 80
&nbsp;&nbsp;&nbsp;targetPort: 80
&nbsp;&nbsp;&nbsp;nodePort: 30000
&nbsp;&nbsp;&nbsp;protocol: TCP</code></pre>
<!-- /wp:code -->

<!-- wp:paragraph -->
<p>Note that the NodePort Service has a lot of downsides:</p>
<!-- /wp:paragraph -->

<!-- wp:list -->
<ul><!-- wp:list-item -->
<li>you can only have one service per port</li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li>you can only use ports 30000–32767,</li>
<!-- /wp:list-item -->

<!-- wp:list-item -->
<li>if your Node/VM IP address changes, you need to deal with that.&nbsp;</li>
<!-- /wp:list-item --></ul>
<!-- /wp:list -->

<!-- wp:paragraph -->
<p>That’s why it’s not recommended for production use cases.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><strong>LoadBalancer</strong> – this Service exposes a set of pods using an external load balancer. All managed Kubernetes offerings have their own implementation of it (for EKS you can use NLB, ALB, etc.)&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In most cases, they’re created by the cloud provider. But there are also some projects that aim to expose it on bare metal clusters - <strong>metallb</strong> is a good example (I share more examples at the end of this article).&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:image -->
<figure class="wp-block-image"><img src="https://lh4.googleusercontent.com/IZh2dakR4vHimUCHMq2nk-4ozs_gvQ5kyPPlHweayGHsYG93gTlem4fO0x1iq-wTrgmP8s-n5JNFv2ANaM0yqubuCiFFhGYVG_giXYRtltVxmkOQ7BhajDof3s78EGILRyXGo7YL0-s3AbC45kQBEcM" alt=""/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>But that’s not the end of the story.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Kubernetes also has an API object called <strong>Ingress</strong>. Ingress is built on top of the Kubernetes Service (to expose Ingress, you need to use the Kubernetes Service). The main responsibility of Ingress is distributing network traffic to services according to predetermined routing rules or algorithms. </p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>It also exposes pods to external traffic, usually via HTTP. Depending on your business goals and environment specifics, you can use different load distribution strategies.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:image -->
<figure class="wp-block-image"><img src="https://lh4.googleusercontent.com/GQY-aGMlp1ynnSR3b-6vOoyPAKm-QXVSgXFl3sKfqKk5trDZj6AJozYD6QqVV0rRgNZ4J1Zz4PYTZJwvMSionOYopccX25Qi-5zycJtYv0yC7HINQNQPBUvludDFsVQM5UAIT4-tl254oZkrAFXwTjA" alt=""/></figure>
<!-- /wp:image -->

<!-- wp:paragraph -->
<p>Example YAML definition:</p>
<!-- /wp:paragraph -->

<!-- wp:code -->
<pre class="wp-block-code"><code>apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
&nbsp;name: minimal-ingress
&nbsp;annotations:
&nbsp;&nbsp;&nbsp;nginx.ingress.kubernetes.io/rewrite-target: /
spec:
&nbsp;ingressClassName: nginx-example
&nbsp;rules:
&nbsp;- http:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;paths:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;- path: /testpath
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;pathType: Prefix
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;backend:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;service:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;name: test
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;port:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;number: 80</code></pre>
<!-- /wp:code -->

<!-- wp:heading -->
<h2 class="wp-block-heading" id="h-load-balancer-traffic-distribution-strategies">Load balancer traffic distribution strategies&nbsp;</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Efficient distribution of network traffic among several backend services is key to maximize scalability and availability.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>You have a lot of freedom in load balancing external traffic to pods, but each strategy comes with its advantages and tradeoffs. It all depends on your load, requirements, and preferences.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>The choice of the load balancing algorithm is one you should make carefully – otherwise, you’ll end up with an unbalanced load distribution or a single web server running hot.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Here’s a selection of load balancing algorithms you should consider.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading" id="h-round-robin">Round Robin</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Using this scheduling algorithm, you follow a sequence of eligible servers that get new connections. Note that this solution is static - it doesn’t really account for speed or performance variations among these individual servers. It simply makes sure that requests come to the servers in order.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Round robin is unable to differentiate between slow and fast servers, so it allocates an equal number of connections to each of them. If you’re expecting high-performance production traffic, it might not be the best choice.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4 class="wp-block-heading" id="h-l4-round-robin-load-balancer">L4 Round Robin load balancer</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>One of the basic load balancing tactics in Kubernetes. It fields all the requests sent to the service and routes them. The kube-proxy implements virtual IPs for services with the help of iptables rules, adding some complexity to the process. It also adds additional latency with every request, which might pile up into a problem if the number of services keeps growing.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":4} -->
<h4 class="wp-block-heading" id="h-l7-round-robin-load-balancing">L7 Round Robin load balancing</h4>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>The L7 proxy directs traffic to Kubernetes pods by bypassing the kube-proxy via an API gateway and managing requests for available pods. The load balancer also tracks pods, which are available with the Kubernetes Endpoints API. When it receives a request for a given Kubernetes service, it round robins the request among the relevant pods to find an available one.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading" id="h-l4-kube-proxy-and-ipvs">L4 kube-proxy and IPVS</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>By default, kube-proxy uses iptables for routing, but it can also use an IP Virtual Server (IPVS). The perk of IPVS is scalability: runs in O(1) time without being influenced by the number of routing rules required. This number is directly proportional to the number of services.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If you’re running a huge Kubernetes cluster with thousands of services, IPVS is a good option. Still, IPVS is L4-level routing, so it’s subject to some limitations.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading" id="h-ring-hash">Ring hash</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>This scheduling algorithm is based on a hash, which derives from a specified key. A hash allows the distribution of new connections across servers. Ring hash is a good solution for a large number of servers and dynamic content since it brings together load balancing and persistence. Many e-commerce applications or services that need a per-client state use it.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>When a server needs to be added or taken away, consistent hashing doesn't have to recalculate the whole hash table. So, it doesn’t affect other connections. Note that ring hash may add some latency to requests when running at scale. Also, the algorithm generates relatively large lookup tables that may not fit into your CPU processor cache.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading" id="h-maglev">Maglev</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Similarly to ring hash, Maglev is a consistent hashing algorithm that was originally developed by Google. The idea behind it was increased speed over ring hash on hash table lookups. The other goal of its creators was to minimize the algorithm’s memory footprint.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If you decide to use Maglev for microservices, expect high costs that come from generating a lookup table when a node fails. Since K8s pods are relatively transient in nature, using Maglev might not be the best idea.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading" id="h-least-connection">Least connection</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>This dynamic load balancing algorithm distributes client requests to pods with the lowest number of active connections and the smallest connection load. Thanks to this, it’s adaptive to slower or unhealthy servers. But when all of your pods are equally healthy, the load will be distributed equally.</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading" id="h-best-practices-for-handling-a-kubernetes-load-balancer">Best practices for handling a Kubernetes load balancer</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>When implementing Kubernetes load balancers, take a few configuration steps to make sure your K8s deployment uses the load balancers you pick to the fullest.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Here are a few best practices for working with load balancers in Kubernetes.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading" id="h-check-whether-the-load-balancer-is-enabled">Check whether the load balancer is enabled</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>This one seems too obvious to be included in this list, but it’s a key step. You need to have the service load balancer enabled in your K8s system. Your load balancer needs to support contained environments and service discovery. Also, your application should be designed for containerization.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Each cloud service provider has its own implementation of load balancer – most of them allow fine tuning by using service annotations</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading" id="h-enable-a-readiness-probe">Enable a readiness probe</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>A readiness probe informs K8s whether the application is ready to serve traffic or not. You need to enable the readiness probes as they pass traffic to the pod. To do that, you need to have it defined in any of your K8s deployments.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>If you don’t have a probe in place, the user will reach the pod but won’t get a healthy server response. That’s because the job of the readiness probe is to signal to Kubernetes when to put a pod behind the load balancer and a service behind the proxy.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading" id="h-enable-a-liveness-probe">Enable a liveness probe</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Another key probe you should enable is a liveness probe. It lets Kubernetes know if a pod is healthy enough to continue working or if restarting it is a better idea. It carries out a simple or complex check based on bash commands.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>This probe is there to help K8s determine whether load balancing works well or whether some of its components require support. Liveness probes increase availability even if your application contains bugs.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading" id="h-apply-network-policy">Apply network policy</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>To protect your K8s deployment, the load balancer must be able to apply security group policies to virtual machines or worker nodes.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>In an ideal scenario, you should limit the inbound and outbound traffic to the minimum requirement. What’s the benefit of placing such a limitation? It helps you prevent accidental exposure of unwanted services to the outbound traffic flow.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Kubernetes comes with network security policy functionality capable of serving all resources within deployments. You also need to make sure that your Kubernetes cluster is provisioned with a network plugin that supports network policies.</p>
<!-- /wp:paragraph -->

<!-- wp:heading {"level":3} -->
<h3 class="wp-block-heading" id="h-enable-cpu-memory-requests">Enable CPU/Memory requests&nbsp;</h3>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>That way, the containers will be able to request resources automatically. This helps free up the CPU and memory resources the system needs. Also, enabling these requests lets you define these resources so the pod never runs low in memory. On top of that, you eliminate the risk of CPU or memory taking over all resources on a node and leading to an error or failure.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:heading -->
<h2 class="wp-block-heading" id="h-optimize-kubernetes-beyond-load-balancers">Optimize Kubernetes beyond load balancers</h2>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>When dealing with workloads that require high availability, it’s important to spread pods between different availability zones (AZs). This is how you ensure that the application is accessible even if one of the AZs is down. CAST AI supports such <a href="https://docs.cast.ai/docs/pod-placement#highly-available-pod-scheduling">scheduling of pods</a> similarly to the K8s scheduler.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Having pods distributed across different availability zones implicates the use of LoadBalancer, which supports distributing traffic between different zones. In most cases, it should be working out of the box, as most clouds have a load balancer that supports distributing traffic between zones. Still, it’s worth double-checking that.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Additionally, apart from utilizing different AZs, CAST AI allows you to split the workloads evenly among different subnets so that all of the subnets are well used. You can find more information about subnet usage calculations <a href="https://docs.cast.ai/docs/subnets#subnets-usage-calculation">here</a>.&nbsp;&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:group {"style":{"spacing":{"padding":{"top":"2rem","right":"2rem","bottom":"2rem","left":"2rem"},"margin":{"top":"2rem","bottom":"2rem"}}},"backgroundColor":"cast-black","className":"pattern-cta-text-btn","layout":{"type":"flex","flexWrap":"wrap","justifyContent":"space-between","verticalAlignment":"center"}} -->
<div class="wp-block-group pattern-cta-text-btn has-cast-black-background-color has-background" style="margin-top:2rem;margin-bottom:2rem;padding-top:2rem;padding-right:2rem;padding-bottom:2rem;padding-left:2rem"><!-- wp:group {"style":{"spacing":{"padding":{"top":"0rem","right":"0rem","bottom":"0rem","left":"0rem"},"blockGap":"0.5rem"}},"layout":{"type":"constrained","contentSize":"520px"}} -->
<div class="wp-block-group" style="padding-top:0rem;padding-right:0rem;padding-bottom:0rem;padding-left:0rem"><!-- wp:heading {"level":5} -->
<h5 class="wp-block-heading" id="h-check-your-kubernetes-cluster-against-configuration-and-security-best-practices">Check your Kubernetes cluster against <mark style="background-color:rgba(0, 0, 0, 0)" class="has-inline-color has-cast-green-color">configuration and security</mark> best practices</h5>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Connect and scan your cluster in 5 min, no credit card required.</p>
<!-- /wp:paragraph --></div>
<!-- /wp:group -->

<!-- wp:buttons {"layout":{"type":"flex","justifyContent":"left","orientation":"horizontal","flexWrap":"nowrap"}} -->
<div class="wp-block-buttons"><!-- wp:button {"width":100} -->
<div class="wp-block-button has-custom-width wp-block-button__width-100"><a class="wp-block-button__link wp-element-button" href="https://console.cast.ai/?screen_hint=SIGNUP" target="_blank" rel="noreferrer noopener">Get started</a></div>
<!-- /wp:button --></div>
<!-- /wp:buttons --></div>
<!-- /wp:group -->

<!-- wp:heading {"level":1} -->
<h1 class="wp-block-heading" id="h-bonus-load-balancing-in-your-homelab">Bonus: load balancing in your homelab</h1>
<!-- /wp:heading -->

<!-- wp:paragraph -->
<p>Deploying a production-grade Kubernetes cluster and using a proper load balancer is one challenge – but what if you want to learn something more about setting up a K8s cluster? Homelab could be an answer to that question.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Creating a K8s cluster just for fun can be challenging but also rewarding. Setting up proper LB in the home network is also difficult because you are unlikely to have enterprise-grade network equipment in the home.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>So, the easiest way to expose your pet project from the home cluster would probably be by using K8s service with the <strong>NodePort</strong> type. There would be no problem with dynamic IP because you would have nodes with static IPs.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>But what if we want to go one step further? And want to use something more similar to a production-grade cluster? For this, you can use a project called Metallb. The project is in beta, but should work fine in a homelab. Metallb has two modes of working L2 for which a home router will be enough. In a nutshell, it means that the machine simply has multiple IP addresses.</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p>Or you can use the more advanced mode called BGP. There you have true load balancing across multiple nodes, but the router needs to have BGP support.&nbsp;</p>
<!-- /wp:paragraph -->

<!-- wp:paragraph -->
<p><strong>We hope this article helped you get to the bottom of the Kubernetes load balancing options, and that you’re ready to use all the load balancers in your next project.</strong></p>
<!-- /wp:paragraph -->