Kubernetes Networking Basics for Small Teams

Published 8 min read
kubernetesnetworkingingressnetwork policytroubleshooting

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:

  • Every pod gets its own IP address
  • Pods can communicate with all other pods without NAT
  • Agents on a node can communicate with all pods on that node
  • 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

    TypeScopeUse Case

    ClusterIPInternal onlyService-to-service communication
    NodePortExternal via node IP:portDevelopment, simple external access
    LoadBalancerExternal via cloud LBProduction external services
    ExternalNameDNS aliasReferencing 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

    ControllerStrengthsBest For

    NGINX IngressMature, well-documented, broad feature setGeneral-purpose web applications
    TraefikAuto-discovery, middleware chain, dashboardTeams wanting convention over configuration
    HAProxyHigh performance, TCP/UDP supportPerformance-critical workloads
    Cilium/EnvoyeBPF-based, service mesh integrationTeams 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.