In this episode, we'll discuss Kubernetes Ingress, the API object for managing external HTTP/HTTPS access to services. We'll learn about Ingress controllers, routing rules, TLS termination, and best practices for exposing applications.

In the previous episode, we learned about Service for exposing and accessing applications within and outside the cluster. In episode 19, we'll discuss Ingress, which provides sophisticated HTTP/HTTPS routing to Services.
Note: Here I'll be using a Kubernetes Cluster installed through K3s.
While Services can expose applications using LoadBalancer or NodePort, Ingress provides a more powerful and flexible way to manage external access. Ingress acts as a smart HTTP router, enabling features like host-based routing, path-based routing, TLS termination, and more - all with a single external IP.
Ingress is a Kubernetes API object that manages external HTTP and HTTPS access to Services in a cluster. It provides HTTP routing rules to direct traffic to different Services based on hostnames, paths, and other criteria.
Think of Ingress like a reverse proxy or API gateway - it sits at the edge of your cluster, receives external HTTP/HTTPS traffic, and routes it to the appropriate Services based on rules you define. Instead of creating multiple LoadBalancers (one per Service), you use a single Ingress with routing rules.
Key characteristics of Ingress:
Let's understand the key differences:
| Aspect | Ingress | LoadBalancer Service |
|---|---|---|
| Layer | Layer 7 (HTTP/HTTPS) | Layer 4 (TCP/UDP) |
| Routing | Host and path-based | Port-based only |
| External IPs | One IP for many Services | One IP per Service |
| TLS | Built-in TLS termination | Requires external setup |
| Cost | Single load balancer | Multiple load balancers |
| Protocol | HTTP/HTTPS only | Any TCP/UDP protocol |
Example scenario:
api.example.com to API Service, web.example.com to Web Service - one IPIngress resources don't work alone - they require an Ingress Controller to function.
An Ingress Controller is a specialized load balancer that reads Ingress resources and implements the routing rules. It's the actual component that handles traffic.
Popular Ingress Controllers:
Important
Important: You must install an Ingress Controller before Ingress resources will work. Kubernetes doesn't include one by default.
For K3s (comes with Traefik by default):
Important
If you're using K3s, it's already default running Ingress Controller using Traefik like this:
➜ devnull@devnull ~ sudo kubectl get svc -n kube-system
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.43.0.10 <none> 53/UDP,53/TCP,9153/TCP 121d
metrics-server ClusterIP 10.43.122.234 <none> 443/TCP 121d
traefik LoadBalancer 10.43.230.117 10.10.10.4 80:31009/TCP,443:30158/TCP 121d
➜ devnull@devnull ~ sudo kubectl get ingressclass
NAME CONTROLLER PARAMETERS AGE
traefik traefik.io/ingress-controller <none> 121d
➜ devnull@devnull ~ sudo kubectl describe ingressclass traefik
Name: traefik
Labels: app.kubernetes.io/instance=traefik-kube-system
app.kubernetes.io/managed-by=Helm
app.kubernetes.io/name=traefik
helm.sh/chart=traefik-34.2.1_up34.2.0
Annotations: ingressclass.kubernetes.io/is-default-class: true
meta.helm.sh/release-name: traefik
meta.helm.sh/release-namespace: kube-system
Controller: traefik.io/ingress-controller
Events: <none># K3s includes Traefik, but you can install NGINX
sudo kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/cloud/deploy.yamlFor other clusters:
sudo kubectl apply -f https://raw.githubusercontent.com/kubernetes/ingress-nginx/controller-v1.8.1/deploy/static/provider/baremetal/deploy.yamlVerify installation:
sudo kubectl get pods -n ingress-nginxLet's create a simple Ingress to expose a Service.
apiVersion: apps/v1
kind: Deployment
metadata:
name: web-app
spec:
replicas: 3
selector:
matchLabels:
app: web
template:
metadata:
labels:
app: web
spec:
containers:
- name: app-hostname
image: ghcr.io/armandwipangestu/app-hostname:1.0.0
ports:
- containerPort: 3000
---
apiVersion: v1
kind: Service
metadata:
name: web-service
spec:
selector:
app: web
ports:
- port: 80
targetPort: 80Apply:
sudo kubectl apply -f web-app.ymlapiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
spec:
rules:
- host: web.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80Apply:
sudo kubectl apply -f basic-ingress.ymlVerify:
sudo kubectl get ingressOutput:
NAME CLASS HOSTS ADDRESS PORTS AGE
web-ingress <none> web.example.com 203.0.113.10 80 30sNow traffic to web.example.com routes to web-service.
Tip
If you want to access web.example.com, you can add static record on /etc/hosts that mapping to NodeIP, for example
10.10.10.4 web.example.comAfter that tried to access with curl like this
curl http://web.example.comOr if you don't want to used /etc/hosts, you can change directly the header from curl like this
curl -H "Host: web.example.com" http://10.10.10.4Then you will get response from Deployment Pod like this
Hello from pod: web-app-5f9556ff68-frrlbRoute different paths to different Services.
This routes:
example.com/api/* → api-serviceexample.com/web/* → web-serviceexample.com/* → frontend-serviceRoute different hostnames to different Services.
This routes:
api.example.com → api-serviceweb.example.com → web-serviceadmin.example.com → admin-serviceIngress supports three path types:
Matches based on URL path prefix:
pathType: Prefix
path: /apiMatches: /api, /api/users, /api/v1/users
Matches exact path only:
pathType: Exact
path: /apiMatches: /api only
Doesn't match: /api/, /api/users
Depends on Ingress Controller implementation:
pathType: ImplementationSpecific
path: /apiBehavior varies by controller.
Enable HTTPS with TLS certificates.
mkdir cert-self-signed
nvim cert-self-signed/openssl.cnfAfter that add configuration like this to the file openssl.cnf
[req]
default_bits = 2048
prompt = no
default_md = sha256
distinguished_name = dn
req_extensions = req_ext
[dn]
CN = example.com
[req_ext]
subjectAltName = @alt_names
[alt_names]
DNS.1 = example.com
DNS.2 = *.example.comNext generate using this command
openssl req -x509 -nodes -days 10365 \
-newkey rsa:2048 \
-keyout cert-self-signed/tls.key \
-out cert-self-signed/tls.crt \
-config cert-self-signed/openssl.cnf \
-extensions req_extsudo kubectl create secret tls example-tls \
--cert=cert-self-signed/tls.crt \
--key=cert-self-signed/tls.keysudo kubectl apply -f tls-ingress.ymlTip
HTTPS Request if you want valid used cert you should used SNI or Server Name Indication, because if change HTTP Header like curl request before using this
curl -H "Host: example.com" https://10.10.10.4 -k -vThat will not work and it will be fallback to default cert K3s which is from Traefik (if used default Ingress Controller on K3s), because that HTTP Header happen after process TLS Handshake finish.
curl -k --resolve example.com:443:10.10.10.4 https://example.com -vCustomize Ingress behavior with annotations (controller-specific).
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: annotated-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/force-ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "50m"
nginx.ingress.kubernetes.io/proxy-connect-timeout: "600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
spec:
rules:
- host: example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080Common annotations:
rewrite-target: Rewrite URL pathssl-redirect: Redirect HTTP to HTTPSproxy-body-size: Max request body sizerate-limit: Rate limitingwhitelist-source-range: IP whitelistingSpecify a default Service for unmatched requests.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: ingress-with-default
spec:
defaultBackend:
service:
name: default-service
port:
number: 80
rules:
- host: example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080Requests not matching any rule go to default-service.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: microservices-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
tls:
- hosts:
- app.example.com
secretName: app-tls
rules:
- host: app.example.com
http:
paths:
- path: /api/users(/|$)(.*)
pathType: Prefix
backend:
service:
name: user-service
port:
number: 8080
- path: /api/orders(/|$)(.*)
pathType: Prefix
backend:
service:
name: order-service
port:
number: 8080
- path: /api/products(/|$)(.*)
pathType: Prefix
backend:
service:
name: product-service
port:
number: 8080
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 3000# Production Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: production-ingress
namespace: production
spec:
tls:
- hosts:
- api.example.com
secretName: prod-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080
---
# Staging Ingress
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: staging-ingress
namespace: staging
spec:
rules:
- host: staging-api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 8080apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: auth-ingress
annotations:
nginx.ingress.kubernetes.io/auth-type: basic
nginx.ingress.kubernetes.io/auth-secret: basic-auth
nginx.ingress.kubernetes.io/auth-realm: "Authentication Required"
spec:
rules:
- host: admin.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: admin-service
port:
number: 80Create auth secret:
htpasswd -c auth admin
sudo kubectl create secret generic basic-auth --from-file=authTip
If you used default K3s Ingress Controller which is Traefik, you can create Ingress Basic Auth using CRD or Custom Resource Definition like this
apiVersion: traefik.io/v1alpha1
kind: Middleware
metadata:
name: basic-auth
spec:
basicAuth:
secret: basic-authAfter that you can access admin.example.com using command curl like this
curl -k --resolve admin.example.com:443:10.10.10.4 https://admin.example.com -u admin:admin -vsudo kubectl get ingresssudo kubectl describe ingress web-ingressOutput:
Name: web-ingress
Namespace: default
Address: 203.0.113.10
Default backend: default-http-backend:80 (<error: endpoints "default-http-backend" not found>)
Rules:
Host Path Backends
---- ---- --------
web.example.com
/ web-service:80 (10.42.0.10:80,10.42.0.11:80,10.42.0.12:80)
Annotations: <none>
Events:
Type Reason Age From Message
---- ------ ---- ---- -------
Normal Sync 2m nginx-ingress-controller Scheduled for syncsudo kubectl get ingress web-ingress -o yamlProblem: Ingress created but not working.
Solution: Install Ingress Controller first:
sudo kubectl get pods -n ingress-nginxProblem: Ingress can't find Service.
Solution: Verify Service exists:
sudo kubectl get service <service-name>Problem: Can't access via hostname.
Solution: Configure DNS or use /etc/hosts:
203.0.113.10 example.comProblem: HTTPS not working.
Solution: Verify secret exists:
sudo kubectl get secret example-tlsProblem: Wrong Service receives traffic.
Solution: Order paths from most specific to least specific:
paths:
- path: /api/v2 # More specific first
- path: /api # Less specific after
- path: / # Catch-all lastAlways enable HTTPS:
spec:
tls:
- hosts:
- example.com
secretName: example-tlsSeparate Ingress per environment:
# production namespace
name: production-ingress
namespace: production
# staging namespace
name: staging-ingress
namespace: stagingChoose descriptive Ingress names:
# Good
name: api-ingress
name: web-app-ingress
name: admin-portal-ingress
# Avoid
name: ingress1
name: ing
name: testIngress routes to Services, ensure Pods have limits:
resources:
requests:
memory: "256Mi"
cpu: "250m"
limits:
memory: "512Mi"
cpu: "500m"Protect against abuse:
annotations:
nginx.ingress.kubernetes.io/limit-rps: "10"
nginx.ingress.kubernetes.io/limit-connections: "5"Automate certificate management:
# Install cert-manager
kubectl apply -f https://github.com/cert-manager/cert-manager/releases/download/v1.13.0/cert-manager.yamlCheck Ingress Controller logs:
sudo kubectl logs -n ingress-nginx -l app.kubernetes.io/name=ingress-nginxsudo kubectl get pods -n ingress-nginx
sudo kubectl logs -n ingress-nginx <controller-pod>sudo kubectl port-forward service/<service-name> 8080:80
curl http://localhost:8080nslookup example.com
dig example.comsudo kubectl describe ingress <ingress-name>In episode 19, we've explored Ingress in Kubernetes in depth. We've learned what Ingress is, how it differs from Services, and how to use it for sophisticated HTTP/HTTPS routing.
Key takeaways:
Ingress is essential for exposing HTTP/HTTPS applications in Kubernetes. By understanding Ingress, you can build sophisticated routing configurations, manage TLS certificates, and efficiently expose multiple Services through a single entry point.
Are you getting a clearer understanding of Ingress in Kubernetes? Keep your learning momentum going and look forward to the next episode!