Skip to main content

Command Palette

Search for a command to run...

Exposing Kubernetes Services Over the Internet Using MetalLB, NGINX Ingress, and Pangolin

Updated
4 min read
Exposing Kubernetes Services Over the Internet Using MetalLB, NGINX Ingress, and Pangolin
S

DevOps & Cloud Engineer — building scalable, automated, and intelligent systems. Developer of sorts | Automator | Innovator

This guide focuses on exposing applications running inside a Kubernetes cluster to the public internet in a controlled and secure way. The components used are MetalLB, the NGINX Ingress Controller, and Pangolin. The cluster itself is already provisioned and functioning, so we begin from the network exposure layer.

We will walk through the following steps:

  • Installing and configuring MetalLB to provide LoadBalancer IPs on bare metal

  • Deploying the NGINX Ingress Controller

  • Creating ingress resources for applications

  • Routing public traffic using Pangolin

The domain used in this configuration is nyzex.in, and the tunnel endpoint is mytunnel.nyzex.in.


1. Understanding the Networking Flow

The full flow of traffic will be:

MetalLB provides the LoadBalancer IP on the private network. The NGINX Ingress Controller watches for Ingress objects and routes traffic accordingly. Pangolin exposes the ingress to the public internet securely.

The Architecture

                   Public Internet
                           
                             (DNS: A record  150.x.x.x)
                           
                   nyzex.in / *.nyzex.in
                           
                           
                   Pangolin (Cloud VM)
                   TLS Termination Enabled
                           
                             (HTTPS Proxy)
                           
                ┌──────────┴──────────┐
                                     
                                     
       proxmox.nyzex.in       *.nyzex.in app endpoints
     (Proxmox Web UI 8006)            
                                      
                                      
                          MetalLB LoadBalancer IP Range
                            192.168.29.240-192.168.29.250
                                      
                                      
                                      
                        NGINX Ingress Controller (DaemonSet)
                        Running on Kubernetes Worker Nodes
                                      
                                       (Ingress Rules Route Traffic)
                                      
       ┌──────────────────────────────┴───────────────────────────────┐
                                                                     
                                                                     
  kubenav Service (ClusterIP)                                argocd-server Service (ClusterIP)
  Namespace: kubenav                                           Namespace: argocd
  Exposed via Ingress                                          Exposed via Ingress
  HTTPS handled by Pangolin                                   HTTPS handled by Pangolin

Explanation of the Flow

LayerRole
DNS (Cloudflare)Routes external requests to Pangolin.
PangolinTerminates TLS and forwards HTTP traffic to the MetalLB public IP.
MetalLBAssigns a local LAN IP to the NGINX ingress controller.
NGINX Ingress ControllerMatches incoming requests based on domain and path.
Ingress ResourcesDefine which service responds to which domain.
ClusterIP ServicesInternal-only services that receive traffic forwarded from NGINX.

Why This Architecture Works Well

  1. TLS is terminated once at Pangolin, reducing complexity in Kubernetes.

  2. MetalLB assigns real IPs, so all nodes can expose services consistently.

  3. Ingress provides routing, path control, and a cleaner security boundary.

  4. Kubernetes remains internal, while exposure is brokered through Pangolin.


2. Installing and Configuring MetalLB

MetalLB allows the cluster to assign external IP addresses to LoadBalancer services, even in environments without a native cloud load balancer.

First, install MetalLB:

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

After MetalLB is running, define the IP address pool. This range should be within your LAN network and unused by DHCP.

Create a file called metallb-config.yaml:

apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
  name: default-pool
  namespace: metallb-system
spec:
  addresses:
  - 192.168.29.240-192.168.29.250
---
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
  name: l2-advertisement
  namespace: metallb-system

Apply it:

kubectl apply -f metallb-config.yaml

This makes MetalLB assign IPs from this range whenever a LoadBalancer service is created.


3. Deploying the NGINX Ingress Controller

When installing the NGINX Ingress Controller using Helm, ensure that the service type is configured correctly to LoadBalancer at installation time. To allow external access via the MetalLB assigned address, the service type must be changed to LoadBalancer:

helm upgrade --install ingress-nginx ingress-nginx \
  --repo https://kubernetes.github.io/ingress-nginx \
  --namespace ingress-nginx --create-namespace \
  --set controller.hostNetwork=true \
  --set controller.kind=DaemonSet \
  --set controller.nodeSelector."kubernetes\.io/os"=linux \
  --set controller.hostPort.enabled=true \
  --set controller.hostPort.ports.http=80 \
  --set controller.hostPort.ports.https=443 \
  --set controller.service.type=LoadBalancer

This will allow MetalLB to assign an IP from the configured address pool, making the ingress reachable from outside the cluster.

Verify the assigned IP:

kubectl get svc -n ingress-nginx

Example output:

NAME                                 TYPE           CLUSTER-IP       EXTERNAL-IP       PORT(S)
ingress-nginx-controller             LoadBalancer   10.101.220.246   192.168.29.240    80:30080/TCP,443:30443/TCP

The EXTERNAL-IP is from MetalLB. This is what Pangolin will connect to.


4. Creating Ingress Resources

Example: Exposing Kubenav

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: kubenav
  namespace: kubenav
spec:
  ingressClassName: nginx
  rules:
    - host: kubenav.nyzex.in
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: kubenav
                port:
                  number: 14122

Apply:

kubectl apply -f kubenav-ingress.yaml

Note:

If ArgoCD is configured with TLS redirect enabled, disable it first:

kubectl patch configmap argocd-cmd-params-cm -n argocd --type merge -p '{"data": {"server.insecure": "true"}}'
kubectl rollout restart deployment argocd-server -n argocd

Similar to kubenav we create the argoCD ingress too!


5. Configuring Pangolin

Pangolin will expose these services securely from the public internet.

Steps:

  1. Create a new Resource in Pangolin

  2. Set the domain to match the ingress host, for example:

    • kubenav.nyzex.in or argocd.nyzex.in
  3. Target backend address should be:

http://192.168.29.240:80

This is the NGINX Ingress Controller LoadBalancer IP and port.

  1. Enable TLS termination in Pangolin (Pangolin handles HTTPS at the edge).

  2. Save and activate.

At this point, DNS for the domain must point to Pangolin.

Once that is complete, browsing to:

https://kubenav.nyzex.in

will load Kubenav.

https://argocd.nyzex.in

will load ArgoCD.


Conclusion

With MetalLB, we are able to assign IP addresses to LoadBalancer services on bare metal. With NGINX Ingress Controller, routing is handled cleanly inside the cluster. Pangolin exposes the traffic securely to the public internet.

This architecture balances internal simplicity with external security and scale flexibility.

More from this blog

C

CodeOps Studies

39 posts

Simple write-ups on day to day code or devops experiments, tests etc.