Small teams only need a few networking primitives to operate Kubernetes confidently: pod networking, Services, Ingress, and NetworkPolicies. The Kubernetes networking model can seem complex, but for most web applications, you interact with just four abstractions. Understanding these well eliminates 90% of networking issues teams encounter in production.
---
The Kubernetes Networking Model
Kubernetes networking has three fundamental rules:
The CNI (Container Network Interface) plugin implements these rules. Common choices include Calico, Cilium, and Flannel. For small teams, the default CNI provided by your managed Kubernetes service works fine.
Pod IPs Are Ephemeral
Pod IPs change every time a pod is recreated. Never hardcode pod IPs in configuration. Instead, use Services to create stable endpoints.
<h1>Bad: hardcoded pod IP in config</h1>
<h1>database_host: 10.244.1.15</h1>
<h1>Good: use the Service DNS name</h1>
<h1>database_host: postgres.database.svc.cluster.local</h1>
Services: Stable Internal Endpoints
A Service provides a stable DNS name and IP that routes traffic to a set of pods matched by label selectors.
Service Types
| Type | Scope | Use Case |
| ClusterIP | Internal only | Service-to-service communication |
| NodePort | External via node IP:port | Development, simple external access |
| LoadBalancer | External via cloud LB | Production external services |
| ExternalName | DNS alias | Referencing external services |
ClusterIP Service Example
apiVersion: v1
kind: Service
metadata:
name: api-server
namespace: production
spec:
type: ClusterIP
selector:
app: api-server
tier: backend
ports:
- name: http
port: 80
targetPort: 8000
protocol: TCP
- name: metrics
port: 9090
targetPort: 9090
protocol: TCP
This Service creates the DNS entry `api-server.production.svc.cluster.local`. Any pod in the cluster can reach it via `http://api-server.production.svc.cluster.local` or, from within the same namespace, simply `http://api-server`.
Service DNS Resolution
<h1>From inside a pod, test DNS resolution</h1>
nslookup api-server.production.svc.cluster.local
<h1>Short form from the same namespace</h1>
curl http://api-server:80/health
<h1>Cross-namespace access</h1>
curl http://api-server.production.svc.cluster.local:80/health
Ingress: External HTTP/S Access
Ingress manages external access to services, typically HTTP and HTTPS. It provides host-based and path-based routing, TLS termination, and request routing.
Choosing an Ingress Controller
| Controller | Strengths | Best For |
| NGINX Ingress | Mature, well-documented, broad feature set | General-purpose web applications |
| Traefik | Auto-discovery, middleware chain, dashboard | Teams wanting convention over configuration |
| HAProxy | High performance, TCP/UDP support | Performance-critical workloads |
| Cilium/Envoy | eBPF-based, service mesh integration | Teams already using Cilium CNI |
For small teams, pick one controller and standardize. NGINX Ingress Controller is the most common choice and has the largest community knowledge base.
Ingress Resource Example
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: app-ingress
namespace: production
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "10m"
nginx.ingress.kubernetes.io/rate-limit: "100"
cert-manager.io/cluster-issuer: "letsencrypt-prod"
spec:
ingressClassName: nginx
tls:
- hosts:
- app.example.com
- api.example.com
secretName: app-tls-cert
rules:
- host: app.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: frontend
port:
number: 80
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-server
port:
number: 80
TLS with cert-manager
Automate TLS certificates with cert-manager and Let's Encrypt:
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: [email protected]
privateKeySecretRef:
name: letsencrypt-prod-key
solvers:
- http01:
ingress:
class: nginx
NetworkPolicies: Traffic Boundaries
By default, all pods can talk to all other pods. NetworkPolicies restrict this to only the communication paths your application needs.
Default Deny Policy
Start with a default deny policy for each namespace, then add allow rules:
<h1>Default deny all ingress and egress in namespace</h1>
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
Allow Required Traffic
<h1>Allow API server to receive traffic from ingress</h1>
<h1>and connect to database and Redis</h1>
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-server-policy
namespace: production
spec:
podSelector:
matchLabels:
app: api-server
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- port: 8000
protocol: TCP
egress:
# Allow DNS
- to: []
ports:
- port: 53
protocol: UDP
- port: 53
protocol: TCP
# Allow PostgreSQL
- to:
- podSelector:
matchLabels:
app: postgres
ports:
- port: 5432
protocol: TCP
# Allow Redis
- to:
- podSelector:
matchLabels:
app: redis
ports:
- port: 6379
protocol: TCP
Always explicitly allow DNS egress (port 53 UDP and TCP). Forgetting DNS in egress rules is the most common NetworkPolicy mistake and causes mysterious connection failures.
Troubleshooting Checklist
When something does not connect in Kubernetes, follow this systematic checklist:
Step 1: Verify Pod Health
<h1>Check pod status and restarts</h1>
kubectl get pods -n production -o wide
<h1>Check readiness and liveness probe status</h1>
kubectl describe pod api-server-abc123 -n production | grep -A5 "Conditions"
<h1>Check pod logs for startup errors</h1>
kubectl logs api-server-abc123 -n production --tail=50
Step 2: Check Service Selectors and Endpoints
<h1>Verify Service has endpoints (healthy pods)</h1>
kubectl get endpoints api-server -n production
<h1>Compare Service selector with pod labels</h1>
kubectl get svc api-server -n production -o yaml | grep -A5 "selector"
kubectl get pods -n production --show-labels | grep api-server
If endpoints are empty, the Service selector does not match any running pods. Fix the labels.
Step 3: Test DNS and Connectivity
<h1>Launch a debug pod for network testing</h1>
kubectl run debug --rm -it --image=nicolaka/netshoot -n production -- bash
<h1>Inside the debug pod:</h1>
nslookup api-server.production.svc.cluster.local
curl -v http://api-server:80/health
ping api-server
Step 4: Validate Ingress
<h1>Check Ingress status and assigned address</h1>
kubectl get ingress -n production
<h1>Check Ingress controller logs</h1>
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=20
<h1>Test from outside the cluster</h1>
curl -v -H "Host: api.example.com" http://<ingress-ip>/health
Step 5: Inspect NetworkPolicy Effects
<h1>List all NetworkPolicies in namespace</h1>
kubectl get networkpolicy -n production
<h1>Describe specific policy to see rules</h1>
kubectl describe networkpolicy api-server-policy -n production
<h1>Temporarily remove policy to test (use with caution)</h1>
kubectl delete networkpolicy default-deny-all -n production
Reference operational tooling on the DevOps Tools page and implementation details in the Documentation. For architecture help, review the Services Overview or reach out via Contact.
Frequently Asked Questions
What CNI plugin should a small team use?
Use whatever your managed Kubernetes provider includes by default. For self-managed clusters, Calico is the most widely deployed and supports NetworkPolicies out of the box. Cilium is a strong choice if you want eBPF-based observability, but adds operational complexity. Do not change CNI plugins in production without thorough testing.
Do I need a service mesh?
Most small teams do not need a service mesh. A service mesh adds mTLS, traffic management, and observability at the cost of significant operational complexity. If you need mTLS between services, consider simpler alternatives like Kubernetes NetworkPolicies combined with application-level TLS. Add a service mesh only when you have a clear need and the team capacity to operate it.
How do I expose non-HTTP services like PostgreSQL?
Use a Service of type `LoadBalancer` with TCP passthrough, or use a NodePort. For databases, exposing them only internally via ClusterIP is strongly recommended. External database access should go through a VPN or SSH tunnel, not a public LoadBalancer.
Can I use Ingress for WebSocket connections?
Yes. NGINX Ingress Controller supports WebSocket connections by default. Add the annotations `nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"` and `nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"` to prevent the connection from timing out. Verify that your upgrade and connection headers pass through correctly.
How do I debug DNS failures inside the cluster?
First check that CoreDNS pods are running: `kubectl get pods -n kube-system -l k8s-app=kube-dns`. If CoreDNS is healthy, launch a debug pod and run `nslookup kubernetes.default.svc.cluster.local`. If that fails, check NetworkPolicies for blocked DNS egress on port 53. If it succeeds but your service name fails, verify the Service exists in the correct namespace.
Final Takeaway
Kubernetes networking becomes manageable when you keep patterns consistent and debug flow repeatable. Use ClusterIP for internal traffic, one standardized Ingress controller for external HTTP, and default-deny NetworkPolicies with explicit allow rules. When something breaks, follow the checklist: pod health, then service endpoints, then DNS, then ingress, then network policies.