Kubernetes Ingress: exposing your application to the world
1. Overview
When you have a Service running in your cluster but want external users to access it via a pretty domain like api.nonladev.com instead of a messy IP:port, that’s when you need Ingress. This guide explains what Ingress is, how it differs from Service type LoadBalancer, how to install an Ingress Controller, and how to write Ingress resources for common scenarios: path-based routing, host-based routing, and TLS. By the end, you’ll be able to expose a Spring Boot application to the outside world with a custom domain and HTTPS.
2. Why You Need Ingress
2.1. The Problem with Service
Service types NodePort and LoadBalancer work well, but have limitations:
LoadBalancer: each Service needs its own external IP → costly if you have many services (each cloud LB charges separately).NodePort: exposes an odd port (30000-32767), has no TLS termination, and offers no routing based on domain/path.
Suppose you have 5 microservices to expose. Using LoadBalancer means 5 cloud load balancers and 5 external IPs. Ingress solves this by using a single entry point to route traffic to multiple internal Services, based on hostname or path.
2.2. What Is Ingress?
Ingress is an API object that defines HTTP/HTTPS routing rules from outside into Services within your cluster. Ingress itself does nothing — it needs an Ingress Controller (a Pod running a reverse proxy like NGINX, Traefik, or HAProxy) to read the rules and enforce them.
Client → Ingress Controller (LoadBalancer/NodePort) → Ingress rules → Service → Pod
Note: If your cluster hasn’t installed an Ingress Controller, creating an Ingress resource won’t do anything — it’s just config “lying dormant” waiting for the controller to read it.
3. Installing an Ingress Controller
The most popular choice is ingress-nginx. Install it using Helm:
<!-- not applicable — use Helm/kubectl, not Maven dependency -->
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx --create-namespace
This command creates the ingress-nginx namespace, deploys the Ingress Controller along with a Service of type LoadBalancer to receive external traffic. Check the status:
kubectl get pods -n ingress-nginx
kubectl get svc -n ingress-nginx
NAME TYPE EXTERNAL-IP PORT(S)
ingress-nginx-controller LoadBalancer 34.123.45.67 80:31234/TCP,443:31235/TCP
This external IP is the single entry point for all Ingresses in your cluster.
4. Sample Deployment and Service
Before writing an Ingress, you need a Service to route to. Suppose you have an order-service application:
apiVersion: apps/v1
kind: Deployment
metadata:
name: order-service
spec:
replicas: 2
selector:
matchLabels:
app: order-service
template:
metadata:
labels:
app: order-service
spec:
containers:
- name: order-service
image: nonladev/order-service:1.0
ports:
- containerPort: 8080
---
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order-service
ports:
- port: 80
targetPort: 8080
The order-service Service exposes port 80 and forwards to container port 8080. Note that this Service does not need type LoadBalancer — using ClusterIP (the default) is sufficient, since the Ingress Controller will reach the Service via the internal network.
5. Basic Ingress Resource — Host-Based Routing
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: order-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: api.nonladev.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: order-service
port:
number: 80
Let’s break it down:
ingressClassName: nginx— specifies which controller handles this rule (important if your cluster has multiple controllers).rules.host— only traffic with headerHost: api.nonladev.comwill match this rule.path+pathType: Prefix— any request starting with/(i.e., all requests) will be forwarded toorder-service:80.- The
rewrite-targetannotation — specific to ingress-nginx, used to rewrite the URL before forwarding (different controllers have different annotations).
Apply and test:
kubectl apply -f order-ingress.yaml
curl -H "Host: api.nonladev.com" http://34.123.45.67/
{"service":"order-service","status":"UP"}
In a production environment, you’d point the DNS record api.nonladev.com to the Ingress Controller’s external IP, and you wouldn’t need to fake the Host header like above.
6. Path-Based Routing — Multiple Services Sharing a Domain
A common scenario: one domain, multiple services on different paths.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-gateway-ingress
spec:
ingressClassName: nginx
rules:
- host: api.nonladev.com
http:
paths:
- path: /orders
pathType: Prefix
backend:
service:
name: order-service
port:
number: 80
- path: /customers
pathType: Prefix
backend:
service:
name: customer-service
port:
number: 80
Requests to api.nonladev.com/orders/123 route to order-service, while /customers/55 routes to customer-service. pathType: Prefix means matching by prefix — different from Exact (must match precisely) and ImplementationSpecific (controller interprets freely, often used for regex).
Note: The order of paths doesn’t affect matching in Ingress v1 — the controller automatically selects the longest matching path first. But write your paths clearly and without overlap to avoid hard-to-debug issues.
7. TLS — HTTPS for Ingress
To enable HTTPS, you need a Secret containing the certificate and key, then reference it in the Ingress.
kubectl create secret tls nonladev-tls \
--cert=tls.crt --key=tls.key
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: order-ingress-tls
spec:
ingressClassName: nginx
tls:
- hosts:
- api.nonladev.com
secretName: nonladev-tls
rules:
- host: api.nonladev.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: order-service
port:
number: 80
spec.tls declares which hosts use which certificates. The controller automatically handles TLS termination — decrypting HTTPS at the Ingress and forwarding plain HTTP to the internal Service. Your Pods don’t need to know anything about TLS.
In practice, you usually use cert-manager to automatically obtain Let’s Encrypt certificates instead of creating the Secret manually:
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: order-ingress-tls
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- api.nonladev.com
secretName: nonladev-tls
rules:
- host: api.nonladev.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: order-service
port:
number: 80
The cluster-issuer annotation requests cert-manager to automatically create and renew the nonladev-tls Secret — no manual intervention needed.
8. Writing Tests to Verify Your Exposed Endpoints
Once your Ingress is working, you can write a simple integration test to verify your controller handles the paths correctly (test at the Spring Boot application level, not the YAML directly):
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class OrderControllerIT {
@Autowired
private TestRestTemplate restTemplate;
@Test
void shouldReturnOrderWhenCalledWithOrdersPrefix() {
ResponseEntity<String> response =
restTemplate.getForEntity("/orders/123", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
assertThat(response.getBody()).contains("orderId");
}
}
This test ensures your Spring Boot application correctly handles the /orders/123 path that the Ingress rewrites to — separating backend code errors from Ingress configuration errors.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
9. Common Mistakes
- Forgetting
ingressClassName: if your cluster has multiple Ingress Controllers (e.g., both nginx and traefik), the Ingress won’t know which controller to use → either nothing happens or both process it incorrectly. - Wrong Service type: leaving the backend Service as
LoadBalanceris unnecessary and wasteful — only useClusterIP. - Mismatched
pathType: usingExactwhen you needPrefixcauses sub-paths (like/orders/123) to return 404 because only/ordersmatches exactly. - DNS not set up yet: test with
curl -H "Host: ..."before configuring real DNS to avoid wasting time waiting for DNS propagation. - Wrong rewrite annotation for your controller: ingress-nginx annotations don’t work with Traefik or AWS ALB Ingress Controller — each controller has its own set of annotations, read the relevant docs carefully.
When NOT to use Ingress: if your application uses a protocol other than HTTP/HTTPS (e.g., raw TCP, complex gRPC streaming, or UDP), standard Ingress won’t work well — consider
Gateway APIor a directLoadBalancerService type for TCP/UDP.
10. Conclusion
Ingress is a centralized HTTP/HTTPS routing layer that lets you expose multiple Services to the outside via a single entry point, saving on LoadBalancer costs and making it easy to manage domains, paths, and TLS. Key points to remember: Ingress resources are just rules; you need an Ingress Controller to enforce them; choose the right ingressClassName and pathType; use cert-manager to automate TLS. For non-HTTP applications, consider Gateway API instead of forcing Ingress.