The previous lesson deliberately left a loose end: ExternalName works because CoreDNS returns a CNAME, headless services work because CoreDNS publishes one A record per pod, and bookings-api finds bookings-postgres because somebody resolves that name. We have spent three modules writing bookings-postgres:5432 in configuration without ever explaining who translates that into an IP address. This lesson opens the box: what CoreDNS is, how it is configured, what names it generates for each kind of object, what is really inside a pod's /etc/resolv.conf, why the ndots: 5 setting can multiply DNS queries by five when bookings-api calls the payment gateway, and how to diagnose the three DNS failures you will run into again and again.
Contents
- Why discovery is done over DNS
- CoreDNS: what it is and where it lives
- The
Corefile: the cluster DNS configuration - The naming scheme and the short forms
- Records for Services, headless Services and pods
- SRV records and named ports
- The pod's
/etc/resolv.confand the effect ofndots: 5 dnsPolicyanddnsConfig- The environment variables inherited from Docker
- Diagnosis: the three typical failures
- Scale: NodeLocal DNSCache and CoreDNS replicas
- Why discovery is done over DNS
The alternative would be configuring IP addresses, and it does not work: pod IPs are ephemeral (02-05). A rollout, an eviction or a node restart changes them. We could pin the Service IP, which is stable for as long as the Service exists, but that does not hold up either:
| Problem with configuring the ClusterIP | Consequence |
|---|---|
| It is assigned when the Service is created | The bookings-api manifest cannot be written until the bookings-postgres one exists |
| It is different in every environment | The ConfigMap for dev, pre and pro diverges needlessly |
| It is lost when the Service is deleted and recreated | Rebuilding the namespace breaks the platform |
| It does not survive a new cluster | Disaster recovery is impossible without editing configuration |
With DNS, bookings-api has had bookings-postgres written in its ConfigMap since 03-01, and that name is valid in all three environments, in a brand-new cluster and after recreating the Service: it is the only stable reference that exists in Kubernetes. On top of that, DNS is universal: it needs no library, no SDK, and no awareness that you are in Kubernetes.
- CoreDNS: what it is and where it lives
CoreDNS is a DNS server written in Go, modular through plugins, and it has been the default DNS of Kubernetes since version 1.13 (it replaced kube-dns). It runs as a normal Deployment in the kube-system namespace, exposed by a Service called kube-dns (the name was kept for compatibility).
NAME READY UP-TO-DATE AVAILABLE AGE
deployment.apps/coredns 1/1 1 1 21d
NAME TYPE CLUSTER-IP PORT(S) AGE
service/kube-dns ClusterIP 10.96.0.10 53/UDP,53/TCP,9153/TCP 21d
NAME READY STATUS RESTARTS AGE
pod/coredns-668d6bf9bc-hn2fk 1/1 Running 0 3dThree observations that clear up a lot of doubts:
- CoreDNS is a workload like any other. It can crash, run out of resources, be evicted. When CoreDNS suffers, the whole platform looks broken at once, and that global symptom is what should make you look here.
- The IP
10.96.0.10is the tenth address of theserviceCIDRby convention, and it is the one the kubelet injects into every pod. - Port
9153exposes Prometheus metrics (07-03): query rate, latency, errors. It is one of the first things worth monitoring.
flowchart LR
POD["Pod bookings-api"] -->|"query to 10.96.0.10:53"| SVC["Service kube-dns"]
SVC -->|"kube-proxy DNAT"| CD["CoreDNS pod"]
CD -->|"kubernetes plugin:<br/>watch on Services and EndpointSlices"| API["kube-apiserver"]
CD -->|"forward plugin:<br/>external names"| UP["Node resolver<br/>(host /etc/resolv.conf)"]
UP --> INT["Internet:<br/>pagos.proveedorexterno.example"]
Notice the apparent circularity: pods reach CoreDNS through a ClusterIP, which needs kube-proxy but does not need DNS. There is no chicken-and-egg problem because the IP 10.96.0.10 is injected literally into the resolv.conf.
- The
Corefile: the cluster DNS configuration
Corefile: the cluster DNS configurationCoreDNS is configured with a file called Corefile, which lives in a ConfigMap and is mounted into the pod. You can read it as it is:
.:53 {
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}Read this as a chain of plugins evaluated in order for every query arriving on port 53.
| Plugin | What it does |
|---|---|
errors |
Records errors in the log |
health / ready |
Endpoints for the Deployment's probes; lameduck waits 5 s before dying so no queries are lost |
kubernetes cluster.local |
The heart. Watches Services and EndpointSlices in the API and answers names in the cluster.local domain |
pods insecure |
Enables pod records by IP (section 5) |
ttl 30 |
Lifetime of cluster answers: 30 seconds |
prometheus :9153 |
Exposes metrics |
forward . /etc/resolv.conf |
Anything that is not cluster.local is forwarded to whatever DNS the node uses. That is how pagos.proveedorexterno.example gets resolved |
cache 30 |
Answer cache, 30 s |
loop |
Detects forwarding loops and aborts startup |
reload |
Reloads the Corefile without restarting the pod (takes ~2 min to notice the change) |
loadbalance |
Shuffles the order of the A records in each answer |
A practical Rutas Norte case: the office has an internal DNS at 10.20.30.5 that resolves *.interno.rutasnorte.example, where the legacy billing database lives. A specific block is added with kubectl edit configmap coredns -n kube-system:
The reload plugin picks it up on its own. Careful: this is cluster-wide configuration; a syntax error leaves CoreDNS in CrashLoopBackOff and the whole platform with it. Check the log after every change.
- The naming scheme and the short forms
The canonical name of a Service always has this structure:
<service>.<namespace>.svc.<cluster-domain>
bookings-postgres.rutas-norte-pro.svc.cluster.local
| | | |
| | | +-- cluster domain (configurable)
| | +----------- object type: svc
| +---------------------- namespace
+--------------------------------------- Service nameThe short forms work thanks to the search list in resolv.conf (section 7). From a pod in rutas-norte-pro:
| You write | Does it work? | What it resolves |
|---|---|---|
bookings-postgres |
Yes | The one in the same namespace |
bookings-postgres.rutas-norte-pro |
Yes | Explicit; works across namespaces |
bookings-postgres.rutas-norte-pro.svc |
Yes | Explicit |
bookings-postgres.rutas-norte-pro.svc.cluster.local |
Yes | Canonical and unambiguous |
bookings-postgres.rutas-norte-dev |
Yes | Another namespace: namespaces do not isolate the network (02-06) |
That last case deserves emphasis. A pod in rutas-norte-dev can resolve and connect to bookings-postgres.rutas-norte-pro. Separation by namespace is organisational, not a security boundary. Only the NetworkPolicies of 04-06 really prevent it.
Practical rule for Rutas Norte:
- Within the same environment, use the short name:
bookings-postgres,redis-cache. The same ConfigMap servesdev,preandprounchanged. - To cross namespaces, always use the full form with
.svc.cluster.local. It is explicit and, as we will see, faster.
- Records for Services, headless Services and pods
Normal Service: one A with the ClusterIP
kubectl run t --rm -it --restart=Never -n rutas-norte-pro \
--image=nicolaka/netshoot -- dig +short bookings-postgres.rutas-norte-pro.svc.cluster.localA single answer: the virtual IP. Balancing between pods is done afterwards by kube-proxy, as you saw in 04-01. DNS plays no part in the distribution.
Headless Service: one A per pod
$ dig +short bookings-postgres-headless.rutas-norte-pro.svc.cluster.local
10.244.0.14
10.244.0.18
10.244.0.23Three A records, one per ready pod (ready: true in the EndpointSlice). Here DNS does take part in discovery, and it is the client that chooses. The loadbalance plugin shuffles the order in each answer so that clients which only look at the first record still spread out somewhat.
A pod that is not ready disappears from this list, unless the Service has publishNotReadyAddresses: true (needed so that database replicas can discover each other during startup, something we will see in 06-01).
Pod records
With pods insecure in the Corefile, every pod has a name derived from its IP with the dots replaced by hyphens: 10-244-0-14.rutas-norte-pro.pod.cluster.local resolves to 10.244.0.14. Note pod instead of svc. Its use is very limited: nobody types that by hand. Its real importance arrives with StatefulSets, where each pod gets a stable, readable name inside a headless Service:
bookings-postgres-0.bookings-postgres-headless.rutas-norte-pro.svc.cluster.local
bookings-postgres-1.bookings-postgres-headless.rutas-norte-pro.svc.cluster.localThat is the network identity that makes a clustered database possible, and it arrives in 06-01.
| Object | Format | Returns |
|---|---|---|
| Normal Service | <svc>.<ns>.svc.cluster.local |
1 A: the ClusterIP |
| Headless Service | <svc>.<ns>.svc.cluster.local |
N A: one per ready pod |
| Pod in a headless Service | <pod>.<svc>.<ns>.svc.cluster.local |
1 A: the pod's IP |
| Pod by IP | <ip-with-hyphens>.<ns>.pod.cluster.local |
1 A: that very IP |
ExternalName |
<svc>.<ns>.svc.cluster.local |
CNAME to the external domain |
- SRV records and named ports
SRV records publish the port as well as the address. They are generated for every named port of a Service, with the format _<port>._<protocol>.<service>.<ns>.svc.cluster.local.
Bringing back the multi-port bookings-api from 04-02, with ports http (3000) and metrics (9090):
The format is priority weight port target. A client that knows how to read SRV discovers the port without having it configured. In a headless Service, an SRV returns one line per pod, with each one's port:
0 33 5432 10-244-0-14.bookings-postgres-headless.rutas-norte-pro.svc.cluster.local.
0 33 5432 10-244-0-18.bookings-postgres-headless.rutas-norte-pro.svc.cluster.local.
0 33 5432 10-244-0-23.bookings-postgres-headless.rutas-norte-pro.svc.cluster.local.Few applications query SRV; most configure the port explicitly. But it is the mechanism used by several operators and discovery libraries, and it is the concrete reason we insist on always naming ports.
- The pod's
/etc/resolv.conf and the effect of ndots: 5
/etc/resolv.conf and the effect of ndots: 5The kubelet writes this file inside every container:
nameserver 10.96.0.10
search rutas-norte-pro.svc.cluster.local svc.cluster.local cluster.local
options ndots:5Three lines, three decisions:
nameserver 10.96.0.10: the ClusterIP ofkube-dns. It is what makes the short forms resolve.search: the suffixes the resolver will try. The first is the pod's own namespace: that is why a barebookings-postgresworks insiderutas-norte-pro.options ndots:5: here is the trap.
What ndots means
ndots:5 tells the resolver: "if the name you are asked for has fewer than 5 dots, try it first with each search suffix, and only at the end as it is".
Count the dots in pagos.proveedorexterno.example: two. Fewer than five. So the resolver does this:
1. pagos.proveedorexterno.example.rutas-norte-pro.svc.cluster.local -> NXDOMAIN
2. pagos.proveedorexterno.example.svc.cluster.local -> NXDOMAIN
3. pagos.proveedorexterno.example.cluster.local -> NXDOMAIN
4. pagos.proveedorexterno.example -> 198.51.100.44 OKFour queries, three useless. And worse: each one is made for A and for AAAA, so in practice that is 8 queries where 2 would have done. The first three also travel to CoreDNS, which queries the API and returns NXDOMAIN.
Now transfer this to Rutas Norte on an August bank holiday: bookings-api calls the payment gateway on every purchase. With 2,000 purchases per minute and no resolution cache in the application, that is 16,000 queries per minute where there should be 4,000. CoreDNS saturates, resolution latency rises, and the whole cluster starts to crawl for a reason nobody connects with DNS.
The fix: the trailing dot
A name ending in a dot is an absolute FQDN: the resolver adds no suffixes to it.
# bookings-api ConfigMap: note the trailing dot
PAYMENT_GATEWAY_URL: "https://pagos.proveedorexterno.example./charges"One query. Done.
| Approach | Queries per resolution | When to use it |
|---|---|---|
| External name without a trailing dot | Up to 4 (x2 with AAAA) | Never, if you can avoid it |
| External name with a trailing dot | 1 | Always for external domains |
Short internal name (redis-cache) |
1 (hits on the first suffix) | Within the same namespace |
| Full internal name with a trailing dot | 1 | Across namespaces, and on critical paths |
dnsConfig with ndots: 2 |
1 for external names | When you cannot touch the URLs |
Warning: if you lower ndots to 1, short names such as redis-cache (zero dots) still work, but bookings-postgres.rutas-norte-pro (one dot) would stop resolving through search and would need the FQDN. Lowering it to 2 is a common and safe compromise.
dnsPolicy and dnsConfig
dnsPolicy and dnsConfigdnsPolicy is a field in the pod spec that decides which resolv.conf it receives.
| Value | What it does | When to use it |
|---|---|---|
ClusterFirst |
The default: points at CoreDNS, which forwards external names | Practically always |
ClusterFirstWithHostNet |
The same, but for pods with hostNetwork: true |
Host-network pods that need cluster DNS |
Default |
Inherits the node's resolv.conf. It does not resolve cluster names |
Infrastructure pods that do not talk to Services |
None |
Ignores everything and uses only the dnsConfig you write |
Very specific DNS setups |
The ClusterFirstWithHostNet case is a classic trap: a pod with hostNetwork: true and dnsPolicy: ClusterFirst (the default value) receives the node's resolv.conf and therefore cannot resolve any Service. If you turn on host networking, you have to change the policy too.
dnsConfig lets you tune the resulting file:
# k8s/base/bookings-api-deployment.yaml (fragment)
spec:
template:
spec:
dnsPolicy: ClusterFirst
dnsConfig:
options:
- name: ndots
value: "2" # cuts the useless queries to the gateway
- name: single-request-open-tcp
searches:
- interno.rutasnorte.example # extra suffix for the office DNSWith dnsPolicy: None you must supply at least one nameserver in dnsConfig, or the pod will not start.
- The environment variables inherited from Docker
Kubernetes injects into every container environment variables with the address and port of every Service that existed in its namespace at the moment the pod was created. It is a leftover from Docker container linking.
BOOKINGS_POSTGRES_PORT_5432_TCP_ADDR=10.96.140.22
BOOKINGS_POSTGRES_PORT_5432_TCP_PORT=5432
BOOKINGS_POSTGRES_SERVICE_HOST=10.96.140.22
BOOKINGS_POSTGRES_SERVICE_PORT=5432
REDIS_CACHE_SERVICE_HOST=10.96.77.31
REDIS_CACHE_SERVICE_PORT=6379
WEB_STORE_SERVICE_HOST=10.96.88.7
WEB_STORE_SERVICE_PORT=80They look convenient and must never be used. Four reasons:
- Only the Services created BEFORE the pod exist. If you deploy
bookings-apiand thenredis-cache, the variable is not there. And creation order is not something you control reliably. - They are frozen when the pod starts. If the ClusterIP changes (the Service is recreated), the variable keeps the old value and the pod points at a dead IP until it restarts.
- They pollute the environment. With 50 Services in the namespace that is hundreds of variables per container. There are documented cases of startup failures from exceeding the environment size.
- They break portability. That
BOOKINGS_POSTGRES_SERVICE_HOSTmeans nothing outside Kubernetes.
Always use the DNS name, which does not depend on ordering, resolves fresh and works the same anywhere. The variable noise is removed with enableServiceLinks: false in the pod spec, recommended in every Rutas Norte Deployment.
- Diagnosis: the three typical failures
The working pod, as in 04-01:
kubectl run dnsdebug --rm -it --restart=Never -n rutas-norte-pro \
--image=nicolaka/netshoot -- bashBasic checks inside:
cat /etc/resolv.conf # nameserver, search, ndots
nslookup bookings-postgres # short form, same namespace
dig +search +short bookings-postgres # +search mimics the real behaviour
dig @10.96.0.10 bookings-postgres.rutas-norte-pro.svc.cluster.localFailure 1: nslookup resolves NOTHING, internal or external
Cause: CoreDNS is down, saturated or unreachable. It is a global failure, and its signature is that every component fails at once.
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
kubectl get endpointslices -n kube-system -l kubernetes.io/service-name=kube-dnsThe usual suspects: CoreDNS pods in CrashLoopBackOff from a badly edited Corefile; the kube-dns Service with no endpoints; CoreDNS OOMKilled for lack of memory under load; or —and this will be very relevant in 04-06— an egress NetworkPolicy blocking port 53. That last case is so frequent that we will devote a whole section to it.
Failure 2: it resolves external names but not cluster ones
$ nslookup pagos.proveedorexterno.example
Address: 198.51.100.44 # OK
$ nslookup bookings-postgres
** server can't find bookings-postgres: NXDOMAINCause: the pod's resolv.conf is not the cluster one. It is nearly always dnsPolicy: Default set by accident, or hostNetwork: true without ClusterFirstWithHostNet. Check it:
kubectl get pod <pod> -n rutas-norte-pro \
-o jsonpath='{.spec.dnsPolicy}{" hostNetwork="}{.spec.hostNetwork}{"\n"}'A second possible cause: the name is wrong. A typo, a Service in another namespace, or a Service that simply does not exist.
Failure 3: it resolves but the connection fails or is painfully slow
$ nslookup bookings-postgres
Address: 10.96.140.22 # resolves fine
$ nc -zv bookings-postgres 5432
... hangsDNS is not the problem. You have already proved that resolution works; the failure is in the Service (empty endpoints, 02-05), in the CNI or in a network policy. This is the most misread case: DNS gets the blame because "it does not connect by name", when nslookup has already answered correctly.
The slowness variant can indeed be DNS: if resolution takes seconds, look at ndots and at external domains without a trailing dot (section 7), or look for saturation in the CoreDNS metrics.
| Symptom | Internal resolution | External resolution | Probable cause |
|---|---|---|---|
| Nothing resolves | Fails | Fails | CoreDNS down / port 53 blocked |
| Only cluster names fail | Fails | Works | Wrong dnsPolicy or hostNetwork |
| Resolves but does not connect | Works | Works | Not DNS: Service, CNI or NetworkPolicy |
| Everything is slow | Slow | Very slow | ndots:5 without a trailing dot; CoreDNS saturated |
- Scale: NodeLocal DNSCache and CoreDNS replicas
In high-load clusters DNS becomes a bottleneck. Two standard measures:
CoreDNS replicas. The default is 2 (1 in minikube). It scales with the size of the cluster, watching its metrics before it starts to hurt: kubectl scale deployment coredns -n kube-system --replicas=3. There is a cluster-proportional-autoscaler, which adjusts the replicas according to the number of nodes and cores, and it is what the managed distributions use.
NodeLocal DNSCache. A DaemonSet that puts a DNS cache on every node. Pods query that local cache (on a link-local IP, typically 169.254.20.10) instead of crossing the network to CoreDNS.
| Benefit | Detail |
|---|---|
| Lower latency | The query never leaves the node if it is cached |
| Less load on CoreDNS | Only cache misses get through |
Fewer conntrack problems |
It uses TCP towards CoreDNS, avoiding the well-known UDP race condition |
| Isolates incidents | A CoreDNS restart does not interrupt cached resolutions |
For Rutas Norte, with its bank-holiday peaks, it is the measure to consider alongside the autoscaling of 09-01.
Common Mistakes and Tips
- Blaming DNS when
nslookupanswers correctly. If it resolves, DNS has done its job. Carry on to the Service and its endpoints. - Editing the
Corefilewithout checking the log. A syntax error takes CoreDNS down and the whole cluster with it. After everykubectl edit configmap coredns, look atkubectl logs -n kube-system -l k8s-app=kube-dns. - External domains without a trailing dot. They multiply queries by four (or eight). In Rutas Norte, the payment gateway URL must carry the dot.
- Using
hostNetwork: truewithoutdnsPolicy: ClusterFirstWithHostNet. The pod loses the ability to resolve Services and the error is baffling. - Using the
*_SERVICE_HOSTvariables. They depend on creation order and get frozen. Use DNS names and switch them off withenableServiceLinks: false. - Caching resolutions forever in the application. Some runtimes (the JVM with its historical default configuration) cache with no expiry and keep using a dead IP. Tune the resolver TTL of your language.
- Assuming the namespace isolates. A pod in
devresolves and connects tobookings-postgres.rutas-norte-pro. Only 04-06 cuts that off. - Tip: whenever in doubt, run
dig +searchand not plaindig. Without+searchthe suffixes are not applied and you will be testing something different from what your application does. - Tip: watch
coredns_dns_request_duration_secondsand theNXDOMAINrate. A spike inNXDOMAINusually means somebody has deployed an application with external names lacking a trailing dot.
Exercises
Exercise 1: Map out the platform's names
From an ephemeral pod in rutas-norte-pro, resolve bookings-postgres in its four forms (short, with namespace, with .svc, full) and check that they all give the same IP. Then resolve bookings-postgres.rutas-norte-dev and explain what the result proves.
Exercise 2: Measure the cost of ndots: 5
From an ephemeral pod, count how many queries resolving pagos.proveedorexterno.example triggers without a trailing dot and with one. Work out the saving for 2,000 purchases per minute and propose two ways of fixing it in the bookings-api manifest.
Exercise 3: Diagnose three pods with broken DNS
Three test pods are deployed: A with hostNetwork: true and the default dnsPolicy; B a normal one but querying a non-existent Service; C a normal one, with the right Service but whose Deployment is scaled to zero. Predict the symptom of each and verify it.
Solutions
Exercise 1
kubectl run t --rm -it --restart=Never -n rutas-norte-pro \
--image=nicolaka/netshoot -- sh -c '
for n in bookings-postgres \
bookings-postgres.rutas-norte-pro \
bookings-postgres.rutas-norte-pro.svc \
bookings-postgres.rutas-norte-pro.svc.cluster.local \
bookings-postgres.rutas-norte-dev; do
printf "%-56s %s\n" "$n" "$(dig +search +short $n | head -1)"
done'bookings-postgres 10.96.140.22
bookings-postgres.rutas-norte-pro 10.96.140.22
bookings-postgres.rutas-norte-pro.svc 10.96.140.22
bookings-postgres.rutas-norte-pro.svc.cluster.local 10.96.140.22
bookings-postgres.rutas-norte-dev 10.96.19.88The first four are the same IP: the short forms are completed with the search list. The fifth proves that the development Service resolves from production, with a different IP: namespaces do not isolate the network. And if you also try nc -zv bookings-postgres.rutas-norte-dev 5432, it will connect.
Exercise 2
kubectl run t --rm -it --restart=Never -n rutas-norte-pro --image=nicolaka/netshoot -- sh -c '
dig +search pagos.proveedorexterno.example | grep -c "^;.*IN.*A" # no dot
dig pagos.proveedorexterno.example. | grep -c "^;.*IN.*A"' # with dotWithout the dot: 4 attempts (3 NXDOMAIN + 1 hit), times 2 counting AAAA = 8 queries. With the dot: 2. A 75% saving.
At 2,000 purchases per minute: 16,000 queries/min against 4,000. Twelve thousand useless queries every minute hitting CoreDNS.
Two fixes in the manifest:
# Option A (preferred): trailing dot in the ConfigMap URL
PAYMENT_GATEWAY_URL: "https://pagos.proveedorexterno.example./charges"
# Option B: lower ndots for the whole pod
spec:
dnsConfig:
options:
- name: ndots
value: "2"A is surgical and affects nothing else; B is useful when you cannot touch the URLs, but remember that it breaks short forms containing a dot.
Exercise 3
| Pod | Symptom | Cause |
|---|---|---|
A (hostNetwork) |
Resolves pagos.proveedorexterno.example but gives NXDOMAIN for bookings-postgres |
It uses the node's resolv.conf; it needs dnsPolicy: ClusterFirstWithHostNet |
| B (non-existent Service) | NXDOMAIN only for that name; everything else resolves |
The name does not exist: a typo or the wrong namespace |
| C (Deployment at zero) | Resolves correctly to the ClusterIP, but the connection times out | DNS works; the Service has no endpoints. It is not a DNS problem |
# Verifying C: the key is in the endpoints, not in DNS
kubectl get endpointslices -n rutas-norte-pro \
-l kubernetes.io/service-name=bookings-api
# ENDPOINTS: <none>Case C is the most valuable lesson of the three: resolving is not connecting.
Conclusion
You now know who translates bookings-postgres into an IP address. CoreDNS is an ordinary Deployment in kube-system, exposed by the kube-dns Service at 10.96.0.10, configured by a Corefile whose kubernetes plugin watches Services and EndpointSlices in order to answer the cluster.local domain, and whose forward plugin sends everything else to the node's resolver. The fact that it is a run-of-the-mill workload has an operational consequence: it can crash, saturate or run out of memory, and when that happens the whole platform looks broken at once.
You have a firm grip on the naming scheme <service>.<namespace>.svc.cluster.local and you know that the short forms work thanks to the search list in resolv.conf, not by magic. You know the records each object generates: one A with the ClusterIP for a normal Service, one A per ready pod for a headless one, pod names derived from the IP, and SRV records that publish the port of named ports. And you understand what resolution does not do: it does not distribute load in a normal Service (that is kube-proxy) and it does not guarantee connectivity.
The section that will save you the most is ndots: 5. An external domain with fewer than five dots triggers four resolution attempts, eight queries counting AAAA, and during the Rutas Norte bank-holiday peaks that is thousands of useless queries per minute against CoreDNS. The trailing dot in pagos.proveedorexterno.example. brings it down to one. You know how to adjust dnsPolicy and dnsConfig, you have seen why hostNetwork: true without ClusterFirstWithHostNet leaves the pod with no access to cluster DNS, and why the *_SERVICE_HOST variables inherited from Docker must never be used —they depend on creation order and get frozen— to the point of switching them off with enableServiceLinks: false. You also have the three typical failures with their unmistakable signatures, and on the horizon NodeLocal DNSCache and CoreDNS scaling for when the load bites.
With the network understood (04-01), the Service types mastered (04-02) and discovery solved, there are no excuses left: it is time to open the platform to the world. In 04-04 we will deploy an Ingress controller and finally publish www.rutasnorte.example against web-store and api.rutasnorte.example against bookings-api, with a single entry point, routed by domain and by path. Rutas Norte's customers will be able, for the first time in the course, to buy a ticket.
Kubernetes Course
Module 1: Introduction to Kubernetes
- What Is Kubernetes?
- Kubernetes Architecture
- Key Concepts and Terminology
- Setting Up a Kubernetes Cluster
- The Kubernetes CLI: kubectl
- Objects, YAML Manifests and the Declarative Model
- The Course Project: the Rutas Norte Platform
Module 2: Core Kubernetes Components
- Pods
- ReplicaSets
- Deployments
- Updates, Rollbacks and Deployment Strategies
- Services
- Namespaces
- Labels, Selectors and Annotations
Module 3: Configuration and Secret Management
- ConfigMaps
- Secrets
- Environment Variables
- Resource Quotas and Limits
- LimitRanges and Quality of Service (QoS) Classes
- ServiceAccounts and API Access from Pods
Module 4: Networking in Kubernetes
- Cluster Networking
- Service Types
- Internal DNS and Service Discovery
- Ingress Controllers
- TLS and Certificate Management with cert-manager
- Network Policies
Module 5: Storage in Kubernetes
- Volumes
- Persistent Volumes
- Persistent Volume Claims
- Storage Classes
- Dynamic Provisioning, Expansion and Snapshots
- Backup and Restore of Persistent Data
Module 6: Advanced Kubernetes Concepts
- StatefulSets
- DaemonSets
- Jobs and CronJobs
- Init Containers, Sidecars and Multi-Container Patterns
- Scheduling: Affinity, Taints and Tolerations
- Custom Resource Definitions (CRDs)
- Operators and the Controller Pattern
Module 7: Monitoring and Logging
- Health Checks and Probes
- Metrics Server and kubectl top
- Monitoring with Prometheus
- Visualization and Alerting with Grafana and Alertmanager
- Centralized Logging with Elasticsearch, Fluentd and Kibana (EFK)
- Application Debugging and Cluster Events
Module 8: Kubernetes Security
- Role-Based Access Control (RBAC)
- Security Contexts and Container Hardening
- Pod Security Policies and Pod Security Standards
- Network Security
- Image Security
- Auditing, Scanning and Vulnerability Management
Module 9: Scaling and Performance
- Horizontal Pod Autoscaling
- Vertical Pod Autoscaling
- Cluster Autoscaling
- Event-Driven and Custom-Metric Scaling with KEDA
- High Availability: PodDisruptionBudgets and Topology
- Performance Tuning
Module 10: Kubernetes Ecosystem and Tooling
- Minikube and Local Environments with kind
- Kubeadm
- Helm
- Kustomize
- GitOps with Argo CD and Flux
- Managed Kubernetes: EKS, AKS and GKE
Module 11: Case Studies and Real-World Applications
- Deploying a Web Application
- Running Stateful Applications
- CI/CD with Kubernetes
- Deployment Strategies: Blue-Green and Canary
- Multi-Cluster Management
- Production Operations: Incidents, Runbooks and Costs
