Ingress and Networking

Ingress resources routing to multiple Services, TLS termination, and Network Policies briefly.

Why Services alone aren't enough for HTTP routing

The Services page covered LoadBalancer as the way to expose a Service to the public internet — but a LoadBalancer Service provisions one real external load balancer per Service, gives you no control over path- or host-based routing, and has no built-in concept of TLS at all. A typical application needs several distinct HTTP routes (an API, a web frontend, an admin panel) reachable under one domain, often each backed by a different Service — provisioning a separate cloud load balancer per route is both expensive and the wrong tool for what is fundamentally an HTTP routing problem, not a networking one. Ingress is Kubernetes' answer: a single resource that describes HTTP(S) routing rules across multiple Services, backed by one shared entry point.

Ingress Controllers and Ingress resources

An Ingress resource just declares routing rules — it does nothing on its own. Making those rules actually work requires an Ingress Controller running in the cluster (commonly ingress-nginx, or a cloud provider's own controller) that watches for Ingress resources and configures a real reverse proxy accordingly. This mirrors the same declarative pattern the rest of Kubernetes uses: you describe desired routing state, and a controller reconciles it into an actual running configuration — install an Ingress Controller once per cluster, then define as many Ingress resources as you have routing needs.

A complete routing example

Here's an Ingress routing two different hostnames to two different Services, plus a path-based split on one of them:

YAML
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-app-ingress
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /$1
spec:
  ingressClassName: nginx
  rules:
    - host: app.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: frontend-service
                port:
                  number: 80
          - path: /api/(.*)
            pathType: ImplementationSpecific
            backend:
              service:
                name: api-service
                port:
                  number: 8080
    - host: admin.example.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: admin-service
                port:
                  number: 80
  • ingressClassName: nginx — tells Kubernetes which installed Ingress Controller should handle this resource, relevant when a cluster runs more than one.
  • rules[].host — routes based on the Host header, the same way an Nginx server_name block does (see the Nginx track for the underlying concept) — app.example.com and admin.example.com are handled by entirely separate rule blocks here, each able to route to different Services.
  • paths[].path + pathType — routes based on the URL path within a matched host. Prefix matches anything starting with that path; here, /api/(.*) on app.example.com sends anything under /api/ to api-service, while everything else on that host falls through to frontend-service.
  • backend.service.name / port.number — the existing Service (defined exactly like the Services covered in the earlier Pods/Deployments/Services page) that requests matching this rule get forwarded to. Ingress routes to Services, never directly to Pods.
Bash
kubectl apply -f ingress.yaml
kubectl get ingress
Plaintext
NAME              CLASS   HOSTS                              ADDRESS         PORTS   AGE
my-app-ingress    nginx   app.example.com,admin.example.com   34.120.11.45    80      2m

One shared external address now fronts every route defined across both hostnames — a single load balancer doing the job that would otherwise take one LoadBalancer Service per backend.

TLS on an Ingress

Ingress can also terminate HTTPS directly, referencing a Secret holding a TLS certificate and key (the same kind of Secret covered on the ConfigMaps/Secrets page, just with a reserved kubernetes.io/tls type):

YAML
spec:
  tls:
    - hosts:
        - app.example.com
      secretName: app-example-com-tls
  rules:
    - host: app.example.com
      # ... same rules as above

In practice, most clusters automate certificate issuance and renewal for this with cert-manager, an add-on that watches Ingress resources and requests/renews certificates (commonly from Let's Encrypt) automatically rather than requiring the certificate Secret to be created and rotated by hand.

Network Policies, briefly

By default, every Pod in a cluster can send traffic to every other Pod, with nothing restricting it — an Ingress and Services control how traffic gets into the cluster and to a Service, but say nothing about which Pods are allowed to talk to which other Pods once inside. A NetworkPolicy closes that gap by explicitly allowing only specified traffic, denying everything else to a selected set of Pods:

YAML
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-api-only
spec:
  podSelector:
    matchLabels:
      app: api-service
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend-service
      ports:
        - protocol: TCP
          port: 8080

This policy targets Pods labeled app: api-service and allows inbound traffic only from Pods labeled app: frontend-service, on port 8080 — any other Pod in the cluster (including, notably, a compromised or misconfigured one) is denied by default the moment any NetworkPolicy selects that Pod at all. NetworkPolicies require a compatible CNI network plugin to actually enforce them (not every cluster networking setup does); applying one on a cluster whose network plugin doesn't support NetworkPolicies has no effect and silently leaves traffic unrestricted.

Common mistakes

  • Installing Ingress resources with no Ingress Controller running in the cluster — the resource is accepted by the API server but nothing actually implements the routing; kubectl get ingress shows no ADDRESS at all.
  • Forgetting ingressClassName in a cluster running multiple Ingress Controllers — the resource can sit unclaimed, matched by none of them.
  • Assuming Ingress replaces Services entirely — Ingress routes HTTP(S) to existing Services, it doesn't replace the need to define them.
  • Writing a NetworkPolicy that only restricts ingress traffic to a sensitive Pod (like a database) while leaving every other Pod's egress completely unrestricted — a policy is only as protective as every relevant direction it actually covers.