In this post, we’ll dive into the concept of ingress in Kubernetes. One of the most common questions is understanding the difference between services and ingress, and knowing when to use each. We’ll start with a quick review of services and gradually explore the role and functionality of ingress.


Services and Ingress: A Comparison

Scenario: Deploying an Online Store

Let’s imagine you’re deploying an application for a company with an online store at myonlinestore.com. The application is deployed as a pod within a Kubernetes deployment, and you also deploy a MySQL database pod. To make the database accessible to your application, you create a ClusterIP service called mysql-service. To expose your application to external users, you set up a NodePort service and assign it port 38080.

Ingress

Now, users can access your application at http://<node-ip>:38080. To handle growing traffic, you scale your application by increasing the number of pod replicas. The service ensures traffic is load-balanced across all the pods.


The Need for a Better Approach

In production, you don’t want users to rely on IP addresses and port numbers. To simplify access, you configure a DNS server to point to the node IPs, allowing users to access the application at myonlinestore.com:38080. However, you still want to eliminate the need for a port number. To achieve this, you introduce a proxy server that forwards requests from port 80 to 38080. Now users can access your application simply via myonlinestore.com.

Ingress


Leveraging Load Balancers in the Cloud

If you use a public cloud environment like Amazon Web Services, you can create a LoadBalancer service to expose your application externally. While Kubernetes provisions a cloud-based load balancer, such as an NLB or ALB, that provides an external IP for your application, it internally uses a NodePort service to route traffic from the load balancer to the nodes and then to the pods. This process is abstracted from users, who only interact with the load balancer’s external IP or DNS. You can configure your DNS to point to this IP, enabling users to access the application at myonlinestore.com.

Ingress


Scaling Beyond a Single Service

As your business grows, you might introduce additional services. For example, suppose you want to make your original application accessible at myonlinestore.com/wear, while your developers have created a completely separate video streaming application, accessible at myonlinestore.com/watch. Although the new application has nothing to do with the original one, you want to share the same Kubernetes cluster resources. To do this, you deploy the new application as a separate deployment within the same cluster.

You create a LoadBalancer service called video-service, and Kubernetes assigns port 38282 for this service. It also provisions a new cloud-based load balancer with a unique IP address for the video service. While this setup works, you now have two separate load balancers, one for each application. And to route user traffic based on the URL they type (/wear for the original application and /watch for the video service), you would need yet another proxy or load balancer that can redirect traffic based on URLs. Every time you introduce a new service, this setup would require reconfiguring the load balancer.

Ingress

Additionally, you need to configure SSL for secure access (HTTPS), and deciding where to implement it—at the application level, load balancer, or proxy server—adds complexity. Since each load balancer incurs additional costs, managing multiple load balancers can quickly inflate your cloud bill.


Simplifying Traffic Management with Ingress

Ingress simplifies managing external traffic by providing a single, externally accessible URL to route traffic to multiple services within a Kubernetes cluster. With ingress, you can define routing rules based on URL paths (e.g., /watch or /wear) or domain names (e.g., watch.myonlinestore.com or wear.myonlinestore.com). Additionally, ingress supports SSL/TLS termination, functioning as a Kubernetes-native layer 7 load balancer.

However, to make ingress accessible outside the cluster, you need to expose it through a NodePort service or a cloud-based load balancer.

Ingress


Deploying an NGINX Ingress Controller

Kubernetes does not include an ingress controller by default. You need to deploy one, such as NGINX, AWS ALB/NLB, GCE, HAProxy, Contour, Traefik, or Istio. The ingress controller monitors the cluster for ingress resources and configures its underlying load balancer to implement the defined routing rules.

Using NGINX as an example, we will deploy an ingress controller and expose it to handle external traffic.

Step 1: Define the Ingress Controller Deployment

The ingress controller runs as a pod in a Kubernetes deployment. Here’s the YAML definition to deploy the NGINX ingress controller:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-ingress-controller
spec:
  replicas: 1
  selector:
    matchLabels:
      name: nginx-ingress
  template:
    metadata:
      labels:
        name: nginx-ingress
    spec:
      containers:
        - name: nginx-ingress-controller
          image: quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.21.0
          args:
            - /nginx-ingress-controller
            - --configmap=$(POD_NAMESPACE)/nginx-configuration
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          ports:
            - name: http
              containerPort: 80
            - name: https
              containerPort: 443

This deployment:

  • Runs one replica of the ingress controller pod.
  • Uses the nginx-ingress-controller image.
  • Passes required arguments to configure the controller.
  • Dynamically retrieves the pod’s name and namespace using environment variables.
  • Opens ports 80 (HTTP) and 443 (HTTPS) for external traffic.

Ingress

Step 2: Configure a ConfigMap for NGINX

The NGINX ingress controller uses a ConfigMap to store configuration data such as SSL settings, log paths, and session timeouts. Initially, the ConfigMap can be blank, but it simplifies future updates without modifying the deployment.

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-configuration
data:
  # Path for error logs
  err-log-path: "/var/log/nginx/error.log"

  # Keep-alive timeout in seconds
  keep-alive: "75"

  # SSL protocols to support
  ssl-protocols: "TLSv1.2 TLSv1.3"

Ingress

Step 3: Expose the Ingress Controller

To expose the ingress controller to external traffic, create a NodePort service:

apiVersion: v1
kind: Service
metadata:
  name: nginx-ingress
spec:
  type: NodePort
  ports:
    - port: 80
      targetPort: 80
      protocol: TCP
      name: http
    - port: 443
      targetPort: 443
      protocol: TCP
      name: https
  selector:
    name: nginx-ingress

This service forwards traffic from external sources to the ingress controller’s pods, enabling the controller to process ingress rules.

Ingress

Step 4: Grant Permissions

The ingress controller requires a service account to monitor ingress resources and manage configurations dynamically. While the example below provides basic permissions, you will also need to configure Roles, ClusterRoles, RoleBindings, and ClusterRoleBindings based on your specific use case and authentication requirements.

apiVersion: v1
kind: ServiceAccount
metadata:
  name: nginx-ingress-serviceaccount

Ingress

Note: For production environments, ensure you define the appropriate RBAC permissions to restrict access based on the principle of least privilege. Tailor Roles and ClusterRoles to match your ingress controller’s functionality and scope within your cluster. This will help minimize security risks by granting only the permissions necessary for the controller to operate, while preventing unauthorized access to sensitive resources.

Step 5: Creating Ingress Resources

Once the ingress controller is deployed and exposed, you can define ingress resources to specify routing rules on the controller. These rules determine how traffic is distributed based on paths or hostnames.

Example 1: Path-Based Routing

Route traffic based on URL paths: Used to direct traffic to different services within a Kubernetes cluster based on URL paths while using a single domain name.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: example-ingress
spec:
  rules:
    - host: myonlinestore.com # Single host for all paths
      http:
        paths:
          - path: /wear # Requests to /wear are routed to the wear-service
            pathType: Prefix
            backend:
              service:
                name: wear-service
                port:
                  number: 80
          - path: /watch # Requests to /watch are routed to the watch-service
            pathType: Prefix
            backend:
              service:
                name: watch-service
                port:
                  number: 80

In this example:

  • All traffic is directed to myonlinestore.com.
  • Requests to myonlinestore.com/wear are routed to the wear-service.
  • Requests to myonlinestore.com/watch are routed to the watch-service.

This approach is ideal for hosting multiple applications or components under a single domain, with distinct URL paths differentiating them (e.g., an online store and a video streaming service).

Example 2: Host-Based Routing

Route traffic based on domain names:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: host-based-ingress
spec:
  rules:
    - host: wear.myonlinestore.com # Requests to this domain are routed to wear-service
      http:
        paths:
          - path: / # All paths (/) on this host are routed to wear-service
            pathType: Prefix
            backend:
              service:
                name: wear-service
                port:
                  number: 80
    - host: watch.myonlinestore.com # Requests to this domain are routed to watch-service
      http:
        paths:
          - path: / # All paths (/) on this host are routed to watch-service
            pathType: Prefix
            backend:
              service:
                name: watch-service
                port:
                  number: 80

In this example:

  • Traffic to wear.myonlinestore.com is routed to the wear-service.
  • Traffic to watch.myonlinestore.com is routed to the watch-service.

This method is suitable when you need to host multiple services or applications with unique subdomains, providing a more isolated and specific entry point for each service.

Example 3: Default Backend Configuration

When traffic does not match any of the rules defined in an Ingress resource, Kubernetes can route the request to a default backend.

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: default-backend-ingress
spec:
  defaultBackend:
    service:
      name: default-backend-service # The service handling unmatched traffic
      port:
        number: 80
  rules:
    - host: myonlinestore.com
      http:
        paths:
          - path: /wear
            pathType: Prefix
            backend:
              service:
                name: wear-service
                port:
                  number: 80
          - path: /watch
            pathType: Prefix
            backend:
              service:
                name: watch-service
                port:
                  number: 80

The default backend is typically used to:

  • Display a custom error page.
  • Serve a generic message.
  • Return a 404 Not Found response for unmatched requests.

This is especially useful to handle edge cases where users might access invalid paths or domains.


Summary

With an NGINX ingress controller in place, managing external traffic becomes significantly simpler. By using ingress resources, you can route requests efficiently to multiple services in your cluster based on URL paths or domain names, all while centralizing SSL/TLS termination. This setup eliminates the complexity of multiple load balancers, reduces costs, and provides a scalable solution for handling traffic. Ingress ensures a seamless user experience by consolidating traffic management into a single, Kubernetes-native approach.