Lesson 09 Ingress Resources
Theme:

Ingress Resources

HTTP routing, TLS termination, and production-ready Ingress templates in Helm

The Problem Ingress Solves

In Kubernetes, a Service of type ClusterIP is only reachable inside the cluster. Switching to LoadBalancer provisions an external IP, but each Service gets its own load balancer. For a microservices application with ten Services, that means ten cloud load balancers — ten external IPs, ten billing line items, and ten DNS records to manage.

Ingress solves this by placing a single reverse proxy (the Ingress controller) behind one LoadBalancer. Traffic enters through that single entry point and is routed to the correct backend Service based on rules you declare in an Ingress resource. Those rules can match on:

This architecture dramatically simplifies external access, reduces cost, and centralises TLS certificate management.

Ingress Controllers

An Ingress resource on its own does nothing. It is a declarative configuration object — a set of routing rules. An Ingress controller is the component that reads those rules and configures the underlying proxy. Without a controller running in your cluster, Ingress resources are ignored.

ControllerProxyBest For
ingress-nginxNGINXGeneral-purpose, widest community support
TraefikTraefikAuto-discovery, built-in Let's Encrypt, middleware
HAProxy IngressHAProxyHigh-throughput, low-latency L7
AWS ALB ControllerAWS ALBEKS environments, tight AWS integration
GCE IngressGoogle CLBGKE environments, Google Cloud integration

You install a controller into the cluster (typically via its own Helm chart) before creating Ingress resources. Many clusters run multiple controllers side by side — one for public traffic, another for internal.

IngressClass

When multiple controllers are installed, Kubernetes needs to know which controller should implement a given Ingress. The IngressClass resource links an Ingress to a specific controller:

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
  name: nginx
  annotations:
    ingressclass.kubernetes.io/is-default-class: "true"
spec:
  controller: k8s.io/ingress-nginx

An Ingress references its class via spec.ingressClassName. If no class is specified and a default IngressClass exists (annotated with is-default-class: "true"), the default controller picks it up. In a Helm chart, the class name should always be configurable through values.

Path Types

Every path in an Ingress rule requires a pathType. This field controls how the path is matched against incoming requests:

pathTypeBehaviourExample
Prefix Matches the URL path prefix split by /. A path of /api matches /api, /api/, and /api/users. Most common for APIs
Exact Matches the URL path exactly. /api matches only /api, not /api/. Health checks, specific endpoints
ImplementationSpecific Matching behaviour depends on the IngressClass. Avoid unless you need controller-specific regex matching. Legacy or advanced use cases

Required Field

Always specify pathType explicitly. It is a required field since networking.k8s.io/v1 and omitting it will cause a validation error.

The Helm Ingress Template

A well-structured Ingress template wraps the entire resource in a conditional so it is only rendered when the user opts in. Here is the full pattern:

{{- if .Values.ingress.enabled }}
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: {{ include "myapp.fullname" . }}
  labels:
    {{- include "myapp.labels" . | nindent 4 }}
  {{- with .Values.ingress.annotations }}
  annotations:
    {{- toYaml . | nindent 4 }}
  {{- end }}
spec:
  {{- if .Values.ingress.className }}
  ingressClassName: {{ .Values.ingress.className }}
  {{- end }}
  {{- if .Values.ingress.tls }}
  tls:
    {{- range .Values.ingress.tls }}
    - hosts:
        {{- range .hosts }}
        - {{ . | quote }}
        {{- end }}
      secretName: {{ .secretName }}
    {{- end }}
  {{- end }}
  rules:
    {{- range .Values.ingress.hosts }}
    - host: {{ .host | quote }}
      http:
        paths:
          {{- range .paths }}
          - path: {{ .path }}
            pathType: {{ .pathType }}
            backend:
              service:
                name: {{ include "myapp.fullname" $ }}
                port:
                  number: {{ .port | default 80 }}
          {{- end }}
    {{- end }}
  {{- if .Values.ingress.defaultBackend }}
  defaultBackend:
    service:
      name: {{ .Values.ingress.defaultBackend.service.name }}
      port:
        number: {{ .Values.ingress.defaultBackend.service.port.number }}
  {{- end }}
{{- end }}

Key patterns to note:

TLS Configuration

TLS is configured in two parts: the tls block in the Ingress spec and the Secret that holds the certificate. You can create the Secret manually or let cert-manager handle it automatically.

Manual TLS Secret

kubectl create secret tls myapp-tls \
  --cert=tls.crt \
  --key=tls.key

Automated with cert-manager

Add cert-manager annotations to your Ingress and it will provision and renew certificates automatically:

ingress:
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-prod
  tls:
    - secretName: myapp-tls
      hosts:
        - myapp.example.com

cert-manager watches for Ingress resources with its annotation, creates a Certificate resource, completes the ACME challenge, and stores the resulting certificate in the named Secret. The Ingress controller then loads the certificate and serves HTTPS traffic.

cert-manager Secret Ownership

When using cert-manager, the secretName in tls must not already exist as a manually-created Secret. cert-manager needs to own the Secret lifecycle.

Annotations Deep Dive

Annotations are the primary mechanism for configuring controller-specific features. Since the Ingress spec itself is minimal, annotations carry the bulk of the real-world configuration. Here are the most commonly used nginx-ingress annotations:

URL Rewriting

nginx.ingress.kubernetes.io/rewrite-target: /$2

Used with regex path capture groups to strip or rewrite path prefixes before forwarding to the backend.

HTTPS Redirect

nginx.ingress.kubernetes.io/ssl-redirect: "true"

Automatically redirects HTTP requests to HTTPS. Enabled by default when TLS is configured.

Request Size Limits

nginx.ingress.kubernetes.io/proxy-body-size: "50m"

Controls the maximum allowed request body size. Essential for file upload endpoints.

Rate Limiting

nginx.ingress.kubernetes.io/limit-rps: "10"
nginx.ingress.kubernetes.io/limit-connections: "5"

Protects backends from excessive traffic. Requests exceeding the limit receive a 503 response.

Basic Authentication

nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth
nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"

Requires HTTP Basic Auth using credentials stored in a Kubernetes Secret. Useful for protecting staging environments.

The Complete values.yaml Pattern

A production-ready values.yaml ingress block should expose every configurable aspect while providing sensible defaults:

ingress:
  enabled: false
  className: nginx
  annotations: {}
    # nginx.ingress.kubernetes.io/rewrite-target: /
    # cert-manager.io/cluster-issuer: letsencrypt-prod
  hosts:
    - host: myapp.example.com
      paths:
        - path: /
          pathType: Prefix
          port: 80
  tls: []
    # - secretName: myapp-tls
    #   hosts:
    #     - myapp.example.com
  defaultBackend: {}
    # service:
    #   name: default-backend
    #   port:
    #     number: 80

This pattern works with helm install --set ingress.enabled=true for quick enablement, and with a full values override file for production deployments. The commented-out examples serve as documentation for users of your chart.

Multi-Host Ingress

The hosts array naturally supports routing to multiple domains. Each host can have its own set of paths, and the TLS block can cover all of them with a single or multiple certificates:

ingress:
  enabled: true
  hosts:
    - host: www.example.com
      paths:
        - path: /
          pathType: Prefix
    - host: api.example.com
      paths:
        - path: /v1
          pathType: Prefix
          port: 8080
        - path: /v2
          pathType: Prefix
          port: 8080
  tls:
    - secretName: example-com-tls
      hosts:
        - www.example.com
        - api.example.com

A wildcard certificate (*.example.com) can simplify this further, covering all subdomains with a single TLS entry.

Default Backend

The defaultBackend field specifies where traffic goes when no rule matches. This is useful for serving custom 404 pages or catching misconfigured routes. If omitted, the Ingress controller typically returns its own default 404 page.

In your Helm template, make the default backend optional:

{{- if .Values.ingress.defaultBackend }}
  defaultBackend:
    service:
      name: {{ .Values.ingress.defaultBackend.service.name }}
      port:
        number: {{ .Values.ingress.defaultBackend.service.port.number }}
{{- end }}

Summary

Ingress is the standard Kubernetes mechanism for exposing HTTP and HTTPS routes from outside the cluster to Services within it. In a Helm chart, the Ingress template should be conditionally created, support multiple hosts and TLS entries, and expose annotations for controller-specific configuration. The values.yaml should default to enabled: false with commented examples showing common configurations.

Key points to remember: