Exposing Kubernetes applications on-premise vs. public cloud vs. bare-metal

Compared to the Docker compose deployments, exposing the Kubernetes application might be overwhelming at first. You have multiple seemingly similar options, what Kubernetes resources to use, and not all features might be available based on the used infrastructure.

Basics of exposing Kubernetes application

When you create a pod (directly or, e.g., via Deployment), all of its services are available only on the internal IP address available from within the Kubernetes cluster. It means you are not able to access it from the outside, but since the IP address is assigned dynamically, you are not able to access it effectively even from the inside (e.g., accessing the database pod from the application pod).

Luckily Kubernetes supports exposing pod’s services via object Service, which should cover all the use cases by supporting the following types.


ClusterIP is the most basic type. By default, it creates an internal Load Balancer in front of your pods (you can imagine it as HAProxy in front of multiple VMs). This Load Balancer has its own IP address, and more importantly, Kubernetes creates a DNS record for this IP address that can be used as a single point of communication by other pods.

There is a special case of a ClusterIP object called headless service that will create just a DNS record pointing directly to the IP address of your pod. This is useful for single pod deployments or particularly useful for StatefulSet controllers.


  • ClusterIP object that routes traffic to the targetPort of the pods.

Primary use case:

  • Exposing pod services to other pods.


NodePort exposes ports on the hosting node similarly to the ports section in Docker compose. Port from the 30000–32768 range is auto-allocated (unless explicitly specified) and bound to the host’s external IP address.

In most cases, you’ll probably want to avoid NodePort. The restricted port range is making it useless for interfacing with users (some people have trouble remembering the four-digit PIN of the credit card, so good luck with that). In the multinode Kubernetes cluster, NodePort won’t give you a single point of communication for the users or external services (you would need to create and configure an external Load Balancer yourself).


  • ClusterIP object that routes traffic to the targetPort of the pods.
  • NodePort object. Traffic is routed from the external IP address and port to the ClusterIP object, which is consequently routed to the targetPort of the pods.

Primary use case:

  • Exposing pod services to the external Load Balancer.


LoadBalancer is the most advanced type of Service object, which covers all the issues of the NodePort. It creates an external Load Balancer bound to the external IP address and port (implementation heavily depends on used infrastructure, but about this later). LoadBalancer solves both issues of the NodePort. It can be bound to ports below 30000, and it creates a single point of access for users making the deployment highly available. The main downside of the LoadBalancer service is its absence in some Kubernetes clusters.


  • ClusterIP object that routes traffic to the targetPort of the pods.
  • NodePort object. Traffic is routed from the external IP address and port to the ClusterIP object, which is consequently routed to the targetPort of the pods.
  • External Load Balancer. Traffic is routed from the external Load Balancer to the NodePort object, from the NodePort object to the ClusterIP object, and finally from the ClusterIP object to the targetPort of the pods.

Primary use case:

  • Exposing pod services to the users or external services.


LoadBalancer seems to be a clear choice when exposing your pods to the users. There is one crucial downside, why using it directly is not practical. With the LoadBalancer service, you can serve only one service per port. This becomes an issue in the case of web services because you probably won’t dedicate the whole cluster to the single web application (except it belongs to the HR, you want to keep those guys happy).

Specifically for exposing web applications, Kubernetes contains an object called Ingress, which represents Reverse Proxy rules that supports routing HTTP requests based on hostname and path. Ingress is implemented by Ingress Controller, which is basically a pod running a specific Reverse Proxy (e.g., Nginx or Traefik).


  • Deployment running Ingress Controller (Reverse Proxy).
  • LoadBalancer service (by default) bound to ports 80 and 443, routing all traffic to Ingress Controller pods.
  • Reverse Proxy rules that route traffic from Ingress Controller pods to ClusterIP objects targeting pods with web applications.

Primary use case:

  • Exposing web applications to the users.

As you can see, even with Ingress, the LoadBalancer service is crucial for exposing your services to users. Let’s have a look at how it’s implemented in various kinds of infrastructures.

Public cloud

If you are deploying an application to Kubernetes public cloud provider, you are out of the woods in most cases. Creating LoadBalancer or Ingress Controller resources will automatically create a Load Balancer service in the cloud for you and point the requested ports to your Kubernetes cluster. There is a slight chance that your provider doesn’t support this auto-creation feature. In that case, you have two options. Create a Load Balancer in the cloud manually and point it to the NodePort of your Ingress Controller (you can set it up just with NodePorts) or switch your provider for a better one.

Private cloud

The private cloud is where it gets tricky. There are numerous private cloud solutions that differ in their implementations. Since OpenStack became the de-facto standard for private cloud deployments, I’ll describe OpenStack setup, and there is some chance that it’ll be similar for other deployments too.

One of the main features of OpenStack is its modularity. Each functionality is implemented by a different project (Nova for computing, Neutron for networking …). The benefit of this approach for the provider is that he can implement just the projects he needs for his business model to work. The disadvantage for the customer is usually the absence of some cloud services.

Load Balancer as a Service (LBaaS) is implemented by the project Octavia, which is, from my experience, often absent in the on-premise deployments. If your OpenStack provider supports Octavia, you are good to go to work the same way as with a public cloud provider. If your OpenStack provider doesn’t support Octavia, you have a problem. Before presenting a suggested solution, let’s dive into the source of the issue.

OpenStack networking works in a similar way as AWS networking. Virtual machines are (in most cases) running on user-defined private (internal) networks. Virtual machines are accessible from the outside world with public (external) IP addresses. Unlike AWS public addresses, which you can see directly in the operating system of your VM, OpenStack floating IP addresses (alternative to public addresses from AWS) are running in the OpenStack networking component, and traffic is routed to the associated VM port. Since it’s impossible to associate a single floating IP address to multiple ports, you cannot create your own LB (e.g., with the HAProxy) in the HA setup.

In this situation you have a single reasonable option — start Ingress Controller on NodePort and use some external Load Balancer (bare metal, public cloud). I don’t need to mention that it is going to be a pain to operate.


The situation with clusters running on bare-metal or virtualization (without user-defined networking) is a lot merrier. At least two projects are implementing the LoadBalancer service for these deployments — MetalLB¹ and OpenELB². Both of these services support two techniques to achieve HA:

  • BGP — needs to be supported by a physical router
  • L2 Mode

OpenELB additionally supports virtual IP failover in VIP Mode (based on Keepalived).

Local development

In the case of running a single worker local Kubernetes cluster for development, you probably don’t want to set up MetaLB or OpenELB services. Here you have at least two options:

  • Run Ingress Controller on HostNetwork³ — this runs Ingress Controller pods directly on the host network, binding to host ports 80 and 443.
  • Use lightweight distribution like k3s, which is deployed with the Klipper Service Load Balancer⁴. Klipper has one significant disadvantage compared to any other LoadBalancer implementation. It won’t create an actual Load Balancer but only forwarding iptables rules redirecting traffic from host ports to ClusterIP ports. This behavior is ok for single-node clusters making it the best solution for local development, which needs to be portable with public cloud provider deployments.


Exposing applications to external users is one of the most platform-dependent parts of Kubernetes. This article aimed to show you which Kubernetes object you’ll need to use for your use case and point you in the right direction in case your platform doesn’t support everything out of the box.



DevOps engineer, automation, and orchestration enthusiast. Love working with AWS and OpenStack.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Tomáš Sapák

DevOps engineer, automation, and orchestration enthusiast. Love working with AWS and OpenStack.