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

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
| Layer | Role |
| DNS (Cloudflare) | Routes external requests to Pangolin. |
| Pangolin | Terminates TLS and forwards HTTP traffic to the MetalLB public IP. |
| MetalLB | Assigns a local LAN IP to the NGINX ingress controller. |
| NGINX Ingress Controller | Matches incoming requests based on domain and path. |
| Ingress Resources | Define which service responds to which domain. |
| ClusterIP Services | Internal-only services that receive traffic forwarded from NGINX. |
Why This Architecture Works Well
TLS is terminated once at Pangolin, reducing complexity in Kubernetes.
MetalLB assigns real IPs, so all nodes can expose services consistently.
Ingress provides routing, path control, and a cleaner security boundary.
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:
Create a new Resource in Pangolin
Set the domain to match the ingress host, for example:
kubenav.nyzex.inorargocd.nyzex.in
Target backend address should be:
http://192.168.29.240:80
This is the NGINX Ingress Controller LoadBalancer IP and port.
Enable TLS termination in Pangolin (Pangolin handles HTTPS at the edge).
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.






