Kubernetes Networking for People Who Just Want a URL

2026/02/18

BUILDkubernetes

I deployed Nexus to Kubernetes. The pod was running. Green checkmarks everywhere. Then someone asked “what’s the URL?” and I realized I had no idea how any of the networking worked. IP addresses, ports, DNS, TLS, ingress – I’d been copy-pasting YAML without understanding what connects a running pod to a URL you can type in a browser.

So I went and learned it. Here’s the mental model – from pod to browser, every layer explained.

The problem – your pod is invisible

You deploy a pod. It gets an IP address like 10.244.3.17. Great. But:

You need four things to go from running pod to usable URL:

Pod (has an internal IP, ephemeral)
    → Service (stable internal endpoint)
        → Ingress (external access + hostname)
            → DNS (human-readable URL)
                → TLS (HTTPS, encryption)

Each layer solves one problem. Let’s go through them.

Layer 1: Service – a stable address

A Kubernetes Service gives your pod a stable internal IP and name. Even if the pod dies and a new one starts with a different IP, the Service keeps pointing at whatever’s alive.

apiVersion: v1
kind: Service
metadata:
  name: nexus-repository-manager
  namespace: nexus
spec:
  selector:
    app: nexus-repository-manager    # "find pods with this label"
  ports:
    - port: 8081                     # the port the Service listens on
      targetPort: 8081               # the port the pod listens on

Now instead of 10.244.3.17:8081 (which changes), you can use nexus-repository-manager.nexus.svc.cluster.local:8081 (which doesn’t). That’s the Service’s internal DNS name – Kubernetes creates it automatically.

But this still only works inside the cluster. A pod can reach it. Your browser can’t.

Layer 2: Ingress – the front door

Ingress is the thing that makes your app reachable from outside the cluster. It’s a set of routing rules: “when someone hits this hostname, send them to this Service.”

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nexus-repository-manager
  namespace: nexus
  annotations:
    nginx.ingress.kubernetes.io/proxy-body-size: "0"    # unlimited uploads
spec:
  ingressClassName: nginx
  rules:
    - host: nexus-falconstaging.mai.microsoft.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nexus-repository-manager
                port:
                  number: 8081

Read it like English: “When someone requests nexus-falconstaging.mai.microsoft.com, send all traffic (/) to the nexus-repository-manager Service on port 8081.”

But Ingress is just a config – it doesn’t do the actual routing. Something needs to read these rules and act on them. That’s the Ingress Controller.

The Ingress Controller – who’s actually listening

The Ingress Controller is a pod running in your cluster (usually nginx or Traefik) that watches for Ingress resources and configures itself accordingly. Think of it as a reverse proxy that auto-configures based on your YAML.

Browser → Load Balancer (Azure/AWS) → Ingress Controller (nginx pod)
                                          │
                                          ├── host: nexus.domain.com → nexus Service
                                          ├── host: neptune.domain.com → neptune Service
                                          └── host: grafana.domain.com → grafana Service

One Ingress Controller handles all your apps. Each Ingress resource adds a new routing rule. You don’t set up the controller – your cluster admin does. You just create Ingress resources and the controller picks them up.

Layer 3: DNS – names to numbers

Ingress gives you a hostname rule, but someone still needs to tell the internet “this hostname maps to this IP address.” That’s DNS.

DNS (Domain Name System) is the internet’s phone book. Your browser asks “what’s the IP for nexus-falconstaging.mai.microsoft.com?” and DNS responds with the load balancer’s IP.

Browser: "What's nexus-falconstaging.mai.microsoft.com?"
    → DNS: "It's 20.42.xxx.xxx" (the load balancer IP)
        → Browser connects to that IP
            → Load balancer forwards to Ingress Controller
                → Ingress Controller routes to your Service
                    → Service routes to your Pod

Who creates DNS records?

Two options:

Manual: Someone creates an A record in your DNS provider (Azure DNS, Route53, Cloudflare) pointing the hostname at your cluster’s load balancer IP. You ask your IT team or cloud admin to do this.

Automatic (external-dns): A tool called external-dns runs in your cluster, watches for Ingress resources, and automatically creates DNS records. Create an Ingress with a hostname, external-dns sees it, creates the DNS record. Delete the Ingress, external-dns deletes the record.

If your cluster has external-dns configured, you don’t need to do anything – just create the Ingress and the DNS record appears. That’s how most production clusters work.

How to tell which one your cluster uses: If other apps in your cluster already have working hostnames (like neptune-falconstaging.mai.microsoft.com), external-dns is almost certainly running. New hostnames under the same domain should just work.

Layer 4: TLS – the lock icon

TLS (Transport Layer Security) is what puts the “S” in HTTPS. It does two things:

  1. Encrypts traffic between browser and server (nobody can sniff your data)
  2. Verifies identity (you’re really talking to your server, not an imposter)

TLS uses certificates – a file that says “I am nexus-falconstaging.mai.microsoft.com and here’s cryptographic proof.” Certificates are issued by a Certificate Authority (CA) that browsers trust.

# In your Ingress:
spec:
  tls:
    - hosts:
        - nexus-falconstaging.mai.microsoft.com
      secretName: nexus-tls    # Kubernetes Secret containing the cert

Where do certificates come from?

cert-manager: A tool running in your cluster that automatically requests and renews certificates from Let’s Encrypt (free, public CA). You create an Ingress with a TLS section, cert-manager sees it, gets a cert, stores it as a Kubernetes Secret.

Ingress controller default cert: Some clusters configure the nginx Ingress Controller with a wildcard certificate (e.g., *.mai.microsoft.com). Any hostname under that domain is automatically covered. You set secretName: null or omit it, and the controller uses its default.

Manual: Your IT team provides a certificate file. You create a Kubernetes Secret with it and reference it in your Ingress.

In our case, the cluster uses the ingress controller’s default cert – other apps like Neptune don’t specify a secretName, so neither do we.

Putting it all together

Here’s the complete path from browser to pod:

You type: https://nexus-falconstaging.mai.microsoft.com

1. Browser asks DNS: "What IP is this?"
   DNS says: "20.42.xxx.xxx" (load balancer)

2. Browser connects to load balancer on port 443 (HTTPS)
   TLS handshake happens (verify cert, encrypt connection)

3. Load balancer forwards to Ingress Controller (nginx pod)

4. Ingress Controller checks hostname against its rules
   Matches: nexus-falconstaging → nexus-repository-manager Service

5. Service finds a healthy pod matching its selector
   Routes to pod IP 10.244.3.17:8081

6. Pod responds → travels back through the same chain → browser renders page

Every layer you configured – Service, Ingress, DNS, TLS – is one hop in this chain. If any one breaks, the request doesn’t make it through.

Common failure modes

When your URL doesn’t work, it’s always one of these layers:

Symptom Broken layer Check
DNS_PROBE_FINISHED_NXDOMAIN DNS No DNS record exists for hostname
ERR_CONNECTION_REFUSED Load balancer / Ingress Controller Ingress Controller isn’t running or LB misconfigured
404 Not Found Ingress rules Hostname doesn’t match any Ingress
502 Bad Gateway Service → Pod Pod is down or Service selector doesn’t match
SSL certificate problem TLS Wrong cert, expired cert, or self-signed

Debugging order

Go from bottom to top – if the pod isn’t running, nothing above it matters:

# 1. Is the pod running?
kubectl get pods -n nexus

# 2. Is the Service pointing at it?
kubectl get endpoints -n nexus

# 3. Is the Ingress configured?
kubectl get ingress -n nexus

# 4. Does DNS resolve?
nslookup nexus-falconstaging.mai.microsoft.com

# 5. Is TLS working?
curl -v https://nexus-falconstaging.mai.microsoft.com

The “I just want a URL” cheat sheet

If you’re deploying an app and just want a working URL, here’s all you need in your Helm values:

ingress:
  enabled: true
  ingressClassName: nginx                              # your cluster's ingress class
  hostRepo: myapp-falconstaging.mai.microsoft.com      # follow your cluster's naming pattern
  annotations:
    nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
  tls:
    - hosts:
        - myapp-falconstaging.mai.microsoft.com

Find the pattern by looking at how other apps in the same cluster configure their ingress. Copy their structure, change the hostname. That’s it.

The layers still exist underneath – Service, Ingress Controller, DNS, TLS – but if your cluster is already running other apps with working URLs, all that infrastructure is in place. You’re just adding another routing rule on top.