65.2 Host and path routing with DNS records: A, CNAME, alias and TTL
Why: the problem
An Ingress rule only works when the request arrives with the right host name, and that name only reaches your cluster when DNS points it there. Zomato serves zomato.com, www.zomato.com, api.zomato.com and a blog, and every one of those names is a DNS record somewhere pointing at a load balancer. If the DNS record points at an old load balancer, the best Ingress rules in the world never see a single request. DNS has a few record types you must know. An A record maps a name to an IPv4 address, and AAAA to IPv6. A CNAME maps a name to another name, so www.shop.example.com can point at shop.example.com. A CNAME cannot sit at the zone apex (example.com itself), which is a problem because AWS load balancers give you a host name, not a fixed IP. Route 53 solves it with an alias record, an A record that follows an AWS resource such as an NLB or ALB and costs nothing to query. TTL (time to live) tells resolvers how many seconds to cache the answer; a TTL of 300 means a change can take up to five minutes to reach everyone, so teams lower TTL a day before a migration. On the Ingress side, host rules decide which set of paths applies. You can list many hosts in one Ingress or split them across Ingress objects owned by different teams; the controller merges them. A wildcard host such as *.shop.example.com matches one label (a.shop.example.com but not a.b.shop.example.com). Paths then pick the Service, and some controllers can rewrite the path, so /api/orders reaches the api pod as /orders. In this lesson we route three hosts on the kind cluster from 65.1, test them with curl --resolve so no real DNS is needed, add a wildcard rule and a rewrite, and then read real DNS records with dig to see A, CNAME, alias and TTL in action.
What: every term in plain English
- Host rule — An Ingress rule that applies only to one host name.
- Wildcard host — *.example.com, matching exactly one extra label.
- A record — DNS name to IPv4 address.
- AAAA record — DNS name to IPv6 address.
- CNAME — DNS name to another name; not allowed at the zone apex.
- Alias record — Route 53 record that points an A or AAAA name at an AWS resource such as an ALB or NLB.
- Zone apex — The bare domain, such as example.com, without a sub-name.
- TTL — Seconds a resolver may cache a DNS answer.
- curl --resolve — Forces curl to use a given IP for a host name, skipping DNS, while sending the real Host header and SNI.
- Rewrite — Changing the request path before it reaches the backend.
How: step by step
- Reuse the ch65 cluster and controller. Controller ready.
- Deploy web, api and admin apps. Three Services.
- Write host rules for three names. One Ingress per team.
- Test with curl --resolve. Each host reaches its app.
- Add a wildcard host. Tenant sub-names work.
- Add a path rewrite. Backend sees a clean path.
- Read real DNS with dig. A, CNAME and TTL visible.
- Plan a TTL change for migration. Low TTL before cutover.
Before you write any rule, draw the request path on paper: the browser asks a resolver for shop.example.com, the resolver asks the root, then the .com servers, then the name servers listed in NS records for example.com, and gets back an alias or A record pointing at the load balancer. The browser then opens a TCP connection to that address, sends TLS with the server name (SNI) and finally sends the HTTP Host header. The load balancer forwards to the Ingress controller, which matches host and path and proxies to a pod IP. Every one of these hops can fail separately, which is why the lab tests them separately: dig for DNS, curl --resolve for the controller, kubectl get endpointslices for the pods. When Nykaa or Myntra gets a report that the site is down for some users only, the cause is very often a stale DNS cache or a record pointing at an old load balancer, not the cluster at all. Keep a habit of checking the answer from more than one resolver, for example dig @1.1.1.1 and dig @8.8.8.8, and compare the TTLs you see.
Example 1: Three host names, three teams, one controller
Goal. Three host names, three teams, one controller.
apps.sh
for a in web api admin; do
kubectl create deployment $a -n shop --image=hashicorp/http-echo:1.0 -- /http-echo -listen=:8080 -text="$a says hi" --dry-run=client -o yaml | kubectl apply -f -
kubectl expose deployment $a -n shop --port=80 --target-port=8080 --dry-run=client -o yaml | kubectl apply -f -
done
hosts.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: storefront
namespace: shop
spec:
ingressClassName: nginx
rules:
- host: shop.example.com
http:
paths:
- {path: /, pathType: Prefix, backend: {service: {name: web, port: {number: 80}}}}
- host: www.shop.example.com
http:
paths:
- {path: /, pathType: Prefix, backend: {service: {name: web, port: {number: 80}}}}
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api
namespace: shop
spec:
ingressClassName: nginx
rules:
- host: api.shop.example.com
http:
paths:
- {path: /, pathType: Prefix, backend: {service: {name: api, port: {number: 80}}}}
- host: admin.shop.example.com
http:
paths:
- {path: /, pathType: Prefix, backend: {service: {name: admin, port: {number: 80}}}}
What each line does
Two Ingress objects— The storefront team and the API team own separate files; the controller merges them.www and apex— Both hosts point at web; in DNS www is usually a CNAME to the apex.Flow-style YAML— {path: /, ...} is the same YAML, only shorter.--dry-run piped to apply— Makes the script safe to run again.No DNS yet— We test with curl --resolve, which sends the real Host header.
Green output
$ bash apps.sh && kubectl apply -f hosts.yaml
ingress.networking.k8s.io/storefront created
ingress.networking.k8s.io/api created
$ for h in shop www.shop api.shop admin.shop; do curl -s --resolve $h.example.com:80:127.0.0.1 http://$h.example.com/; done
web says hi
web says hi
api says hi
admin says hi
$ kubectl get ingress -n shop
NAME CLASS HOSTS ADDRESS
storefront nginx shop.example.com,www.shop.example.com localhost
api nginx api.shop.example.com,admin.shop.example.com localhost
Expected result. Four names reach three apps through one controller. curl --resolve let us test real host names with no DNS change at all, which is exactly how you test before a cutover.
Example 2: Wildcard tenants and a path rewrite
Goal. Wildcard tenants and a path rewrite.
tenants.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tenants
namespace: shop
spec:
ingressClassName: nginx
rules:
- host: "*.stores.example.com"
http:
paths:
- {path: /, pathType: Prefix, backend: {service: {name: web, port: {number: 80}}}}
rewrite.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: api-rewrite
namespace: shop
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
rules:
- host: shop.example.com
http:
paths:
- path: /api(/|$)(.*)
pathType: ImplementationSpecific
backend: {service: {name: api, port: {number: 80}}}
What each line does
*.stores.example.com— Each seller gets a sub-name such as rajesh.stores.example.com with one rule.Quoted wildcard— YAML treats a leading * as an alias, so the host must be quoted.One label only— a.b.stores.example.com does not match the wildcard.rewrite-target /$2— The second regex group becomes the new path, so /api/orders reaches the pod as /orders.ImplementationSpecific— Required because the path is a regex, which is controller-specific.
Green output
$ kubectl apply -f tenants.yaml -f rewrite.yaml
$ curl -s --resolve rajesh.stores.example.com:80:127.0.0.1 http://rajesh.stores.example.com/
web says hi
$ curl -s -o /dev/null -w "%{http_code}\n" --resolve a.b.stores.example.com:80:127.0.0.1 http://a.b.stores.example.com/
404
$ curl -s --resolve shop.example.com:80:127.0.0.1 http://shop.example.com/api/orders
api says hi
$ kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=1
"GET /api/orders HTTP/1.1" 200 ... [shop-api-80] ... 10.244.1.7:8080
Expected result. The wildcard served any seller name with a single rule and refused two-level names. The rewrite let the API team keep clean routes inside the pod while the public URL keeps the /api prefix.
Example 3: Read real DNS records and plan a TTL change
Goal. Read real DNS records and plan a TTL change.
dns.sh
dig +noall +answer example.com A
dig +noall +answer www.example.com
dig +noall +answer example.com NS
dig +short TXT example.com
migration-plan.txt
Day -1 Lower TTL on shop.example.com from 3600 to 60
Day 0 Check new cluster with curl --resolve shop.example.com:443:NEW_IP
Day 0 Change the alias record to the new load balancer
Day 0 Watch old controller logs drain to zero over 1 to 2 minutes
Day +1 Raise TTL back to 300
What each line does
+noall +answer— Prints only the answer lines: name, TTL, class, type, value.TTL column— The second field; it counts down in resolver caches.NS records— Which name servers own the zone, for example Route 53 servers.Lower TTL first— Caches still hold the old long TTL, so lower it a full old-TTL before the change.Drain check— Old controller logs show when users have moved.
Green output
$ bash dns.sh
example.com. 2735 IN A 93.184.215.14
www.example.com. 2735 IN CNAME example.com.
example.com. 2735 IN A 93.184.215.14
example.com. 86400 IN NS a.iana-servers.net.
example.com. 86400 IN NS b.iana-servers.net.
"v=spf1 -all"
Expected result. dig shows the record type, the target and how long caches may keep it. Lowering TTL a day before a load balancer change is what lets Flipkart or Zomato move traffic in minutes instead of hours.
Every command, step by step (kind lab with kubectl)
-
Apps
bash apps.shWhat it does: Three Services.
service/admin created -
Hosts
kubectl apply -f hosts.yamlWhat it does: Two Ingresses.
ingress.networking.k8s.io/api created -
Test apex
curl -s --resolve shop.example.com:80:127.0.0.1 http://shop.example.com/What it does: web.
web says hi -
Test API
curl -s --resolve api.shop.example.com:80:127.0.0.1 http://api.shop.example.com/What it does: api.
api says hi -
Wildcard
kubectl apply -f tenants.yamlWhat it does: Tenants.
ingress.networking.k8s.io/tenants created -
Tenant
curl -s --resolve rajesh.stores.example.com:80:127.0.0.1 http://rajesh.stores.example.com/What it does: Matches.
web says hi -
Rewrite
kubectl apply -f rewrite.yamlWhat it does: Regex path.
ingress.networking.k8s.io/api-rewrite created -
Rewritten
curl -s --resolve shop.example.com:80:127.0.0.1 http://shop.example.com/api/ordersWhat it does: Clean path.
api says hi -
A record
dig +noall +answer example.com AWhat it does: Name to IP.
example.com. 2735 IN A 93.184.215.14 -
CNAME
dig +noall +answer www.example.comWhat it does: Name to name.
www.example.com. 2735 IN CNAME example.com.
When it is red 1
404 for a host that is in the Ingress
Cause. Typo in host, or a wildcard used for two labels.
Fix, step by step
- kubectl get ingress -A -o wide.
- Wildcards match one label only.
When it is red 2
error converting YAML: did not find expected alphabetic or numeric character
Cause. Unquoted wildcard host.
Fix, step by step
- Write host: "*.stores.example.com".
When it is red 3
Rewrite sends /api/orders to the pod as /api/orders
Cause. use-regex missing or path not a regex.
Fix, step by step
- Add use-regex true and the capture groups.
When it is red 4
DNS change done but users still hit the old cluster
Cause. Resolvers cache the old answer until the old TTL expires.
Fix, step by step
- Lower TTL a day earlier.
- Keep the old cluster serving until logs drain.
When it is red 5
CNAME at apex rejected by DNS provider
Cause. CNAME is not allowed at the zone apex.
Fix, step by step
- Use a Route 53 alias record for AWS load balancers.
Practice tasks
Easy Task 1: Hosts
Why: Route.
What to do: Apply hosts.yaml.
- Two Ingresses.
Solution
kubectl get ingress -n shopEasy Task 2: Resolve
Why: Test.
What to do: Curl every host with --resolve.
- 127.0.0.1.
Solution
curl -s --resolve api.shop.example.com:80:127.0.0.1 http://api.shop.example.com/Easy Task 3: dig
Why: Read.
What to do: Read A and CNAME records for a public site.
- +noall +answer.
Solution
dig +noall +answer www.example.comMedium Task 4: Wildcard
Why: Tenants.
What to do: Apply tenants.yaml and test one and two labels.
- Quote the host.
Solution
curl -s --resolve a.stores.example.com:80:127.0.0.1 http://a.stores.example.com/Medium Task 5: Rewrite
Why: Clean.
What to do: Apply rewrite.yaml and read controller logs.
- $2.
Solution
kubectl logs -n ingress-nginx deploy/ingress-nginx-controller --tail=5Medium Task 6: TTL
Why: Plan.
What to do: Write a cutover plan with TTL steps.
- Lower a day early.
Solution
cat migration-plan.txtHard Task 7: Split
Why: Teams.
What to do: Move admin to its own Ingress in a new namespace.
- Service in same namespace as Ingress.
Solution
kubectl get ingress -AHard Task 8: Zomato
Why: Main task.
What to do: Two hosts, wildcard, rewrite and TTL plan.
- curl --resolve proof.
Solution
kubectl get ingress -A -o wideHomework: try at home
Zomato is moving restaurant.zomato-lab.example.com and api.zomato-lab.example.com to a new cluster: write host rules for both plus a wildcard for city sub-names, add a rewrite for /api, test every name with curl --resolve, and write a TTL plan for the DNS cutover.
Recap
- Host rules choose the rule set and paths choose the Service.
- Many Ingress objects can share one controller and are merged.
- A, AAAA and CNAME records point names; Route 53 alias records point at AWS load balancers, even at the apex.
- TTL sets cache time; lower it before a migration.
- curl --resolve tests real host names before DNS changes.
FAQ
Q. Should each team have its own Ingress?
Usually yes. Separate objects per team or namespace keep ownership clear; the controller merges them into one config.
Q. Can an Ingress point to a Service in another namespace?
No. The backend Service must be in the same namespace as the Ingress. Gateway API allows cross-namespace routes with a ReferenceGrant.
Q. Is a rewrite always a good idea?
Use it sparingly. It hides real paths from the app, which can break redirects and links. Many teams prefer apps that understand their prefix.
Q. What TTL should I use normally?
300 seconds is a common balance. Alias records in Route 53 use the target resource's TTL.
Interview questions
-
Explain A, CNAME and alias records.
Model answer: A maps to an IP, CNAME maps to another name and is not allowed at the apex, alias is Route 53's apex-safe pointer to AWS resources.
-
How would you migrate a domain to a new cluster with no downtime?
Model answer: Lower TTL early, test the new cluster with curl --resolve, switch the record, keep the old cluster until traffic drains.
-
How does the controller pick a backend?
Model answer: Host header first, then the longest matching path.
-
What does a wildcard host match?
Model answer: Exactly one extra label.
Quick quiz
Q1. Which record maps a name to another name?
CNAME.
Q2. Can a CNAME sit at example.com?
No; use an alias record on Route 53.
Q3. What does TTL control?
How long resolvers cache the answer.
Q4. How do you test a host with no DNS?
curl --resolve.
Q5. Does *.a.com match x.y.a.com?
No, one label only.
Samajla ka?