🚀 Executive Summary

TL;DR: Exposing Kubernetes services on a KIND cluster running on a bare-metal VPS often results in LoadBalancer services stuck in due to the absence of a cloud-provider load balancer. This guide offers solutions ranging from temporary kubectl port-forwarding to a production-ready MetalLB setup, or KIND’s native extraPortMappings for static port exposure.

🎯 Key Takeaways

  • KIND clusters on bare-metal VPS environments inherently lack the external load balancer functionality expected by LoadBalancer services, leading to a state.
  • MetalLB is the recommended production-like solution for bare-metal KIND clusters, enabling LoadBalancer services to acquire routable IP addresses from a user-defined pool via ARP announcements.
  • For exposing a fixed, small number of ports, KIND’s `extraPortMappings` configuration can directly map host VPS ports to the KIND control-plane node, typically used with an Ingress controller.

Exposing Services on a KIND Cluster on Contabo VPS, MetalLB vs cloud-provider-kind?

Struggling to expose services on a KIND Kubernetes cluster running on a VPS? This guide breaks down why it’s a pain and provides three real-world solutions, from quick port-forwards to a production-ready MetalLB setup.

Exposing Services on KIND: MetalLB, Port-Forwards, and Sanity on a Bare-Metal VPS

I remember it like it was yesterday. It was 10 PM, and I was trying to get a proof-of-concept running on a new Contabo VPS for a demo the next morning. The app was deployed, the pods were green, everything looked perfect… inside the cluster. But trying to hit the service from my browser? Nothing. Just a spinning wheel of doom. The `LoadBalancer` service I created was stuck in `` state, mocking me. It’s a classic “welcome to bare-metal Kubernetes” moment, where you realize all the cloud-provider magic you take for granted is suddenly gone. You’re the cloud provider now.

So, What’s Actually Going On?

Before we dive into the fixes, let’s understand the “why.” When you run KIND (Kubernetes in Docker), you’re creating a nested reality. Your Contabo VPS has a public IP address. On that VPS, Docker creates its own private network. Then, KIND spins up Docker containers (your “nodes”) on that private network. Finally, Kubernetes itself creates *another* network layer inside those nodes for your pods and services.

When you create a service of type `LoadBalancer`, Kubernetes expects something external—a cloud provider’s load balancer—to see this request and assign a real, routable IP address. On a bare-metal VPS, there is no such “something.” Kubernetes makes the request, and nobody answers. That’s why your service’s external IP stays `` forever.

Our job is to bridge this gap. Here are three ways to do it, ranging from a quick hack to the proper, long-term solution.

Solution 1: The “It’s 2 AM and I Just Need It to Work” Fix

Sometimes, you just need to see if the thing works. You don’t need a permanent IP or a fancy setup. You just need access *right now*. For this, my old friend kubectl port-forward is your best bet.

This command directly connects a port on your local machine (in this case, the VPS itself) to a port on a pod, service, or deployment inside the cluster.

Let’s say you have a service named `my-nginx-service` on port 80 in the `default` namespace. To access it from your VPS on port 8080, you run this:

kubectl port-forward service/my-nginx-service 8080:80

Now, on your VPS, you can access http://localhost:8080, and it will hit your service. Simple.

Warning: This is a temporary, foreground process. As soon as you close that terminal session or press Ctrl+C, the connection is gone. It’s fantastic for quick debugging but is absolutely not a permanent solution for exposing an application.

Solution 2: The “Do It Right” Fix with MetalLB

This is the real solution and the one you should aim for. MetalLB is a load-balancer implementation for bare-metal Kubernetes clusters. It simulates the behavior of a cloud provider’s load balancer by taking a pool of IP addresses you control and assigning them to services of type `LoadBalancer`.

Step 1: Install MetalLB

First, get MetalLB installed on your cluster. The manifest command is the easiest way:

kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/v0.13.12/config/manifests/metallb-native.yaml

After a minute, you’ll see the MetalLB pods running in the `metallb-system` namespace.

Step 2: Configure MetalLB

This is the crucial part. You need to tell MetalLB which IP addresses it’s allowed to use. Let’s say your Contabo VPS has a public IP of `203.0.113.10` and a private IP of `192.168.1.100` on its main interface. You can tell MetalLB to use a small range from that same subnet. You create a file, let’s call it `metallb-config.yaml`:

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: vps-ip-pool
  namespace: metallb-system
spec:
  addresses:
  - 203.0.113.10-203.0.113.15 # Or a range on your VPS's private network
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: main-advertisement
  namespace: metallb-system

Pro Tip: The IP range you provide in `IPAddressPool` must be on the same Layer 2 network as your VPS host. For a simple setup, just using the single public IP of your VPS (`203.0.113.10-203.0.113.10`) is often the easiest way to start. MetalLB will then use ARP to announce that it “owns” that IP for any service that requests it.

Apply the configuration:

kubectl apply -f metallb-config.yaml

Now, any `LoadBalancer` service you create will almost instantly get an IP from that pool. The `` status will change to a real IP, and you can access your service from the public internet.

Solution 3: The “KIND-Native” Fix

There’s another way that’s specific to KIND and simpler than MetalLB if you only need to expose a few, static ports. You can configure the KIND cluster itself to map ports from the host VPS directly to the KIND “nodes” (which are Docker containers).

This is done when you create the cluster. You need a config file, let’s call it `kind-cluster-config.yaml`:

kind: Cluster
apiVersion: kind.x-k8s.io/v1alpha4
nodes:
- role: control-plane
  kubeadmConfigPatches:
  - |
    kind: InitConfiguration
    nodeRegistration:
      kubeletExtraArgs:
        node-labels: "ingress-ready=true"
  extraPortMappings:
  - containerPort: 80
    hostPort: 80
    protocol: TCP
  - containerPort: 443
    hostPort: 443
    protocol: TCP

Then you create your cluster with this config:

kind create cluster --config kind-cluster-config.yaml

With this setup, any traffic hitting port 80 or 443 on your VPS (`dev-vps-01`) will be forwarded to ports 80 and 443 on the KIND control-plane node. You would then typically deploy an Ingress controller (like Nginx) using a `NodePort` service that listens on those ports. This method effectively “punches a hole” through the Docker network layer for specific ports.

Which One Should You Choose?

Here’s my simple breakdown:

Method Best For Complexity
kubectl port-forward Quick, temporary debugging. Very Low
KIND extraPortMappings Exposing a small, fixed number of ports (e.g., for an Ingress controller). Low
MetalLB The most flexible, “production-like” way to handle dynamic service exposure. Medium

For any serious development or staging environment on a VPS, my vote is always for MetalLB. It most closely mimics a real cloud environment and teaches the right habits. But don’t be ashamed to use the other tools when they fit the job. The best engineers I know aren’t the ones who know the most complex solution, but the ones who know which solution is the right size for the problem at hand.

Darian Vance - Lead Cloud Architect

Darian Vance

Lead Cloud Architect & DevOps Strategist

With over 12 years in system architecture and automation, Darian specializes in simplifying complex cloud infrastructures. An advocate for open-source solutions, he founded TechResolve to provide engineers with actionable, battle-tested troubleshooting guides and robust software alternatives.


🤖 Frequently Asked Questions

âť“ Why do my Kubernetes LoadBalancer services stay in on a KIND cluster on a VPS?

On a bare-metal VPS, KIND clusters lack an integrated cloud provider to assign external IP addresses to `LoadBalancer` services, causing them to remain in a `` state as Kubernetes waits for an external load balancer to provision an IP.

âť“ How do MetalLB, `kubectl port-forward`, and KIND `extraPortMappings` compare for exposing services?

`kubectl port-forward` is a temporary solution for debugging. KIND `extraPortMappings` is suitable for exposing a small, fixed number of ports via cluster configuration. MetalLB provides a flexible, production-like solution for dynamic `LoadBalancer` service exposure by assigning IPs from a defined pool.

âť“ What is a common implementation pitfall when configuring MetalLB on a VPS?

A common pitfall is defining an `IPAddressPool` in MetalLB that is not on the same Layer 2 network as your VPS host. The IP range must be routable and on the same subnet as your VPS’s network interface for MetalLB to correctly announce ownership via ARP.

Leave a Reply

Discover more from TechResolve - SaaS Troubleshooting & Software Alternatives

Subscribe now to keep reading and get access to the full archive.

Continue reading