The node’s :80 and :443
Traefik is a DaemonSet holding the node’s ports directly. It has no Service at all.
The packet has arrived at the node on port 443. Nothing in Kubernetes is listening there by default, so something has to claim it. Traefik does, with hostPort.
The mechanism is worth being precise about, because most people assume hostPort means the container binds a privileged port and it does not. The container binds 8443, unprivileged. The CNI’s portmap plugin installs a DNAT rule on the node that rewrites traffic for node:443 to the pod’s :8443. So the pod needs no NET_BIND_SERVICE, no hostNetwork: true, and no root — it can drop every capability and still serve the public internet. That is a better security posture than the obvious alternatives, and it comes free with the port model.
DaemonSet, not Deployment. hostPort carries an invariant: at most one pod per node can hold a given port. A Deployment can violate that — scale it to two, both land on the only node, and the second sits Pending forever with an ErrorPodScheduled port-conflict message that reads like a scheduling problem rather than a design problem. A DaemonSet cannot express the mistake: one pod per node is what a DaemonSet is. The workload type is doing the work an admission policy would otherwise have to.
service.enabled: false. There is no Service in front of Traefik. Not a ClusterIP, not a NodePort, nothing. Nothing in the cluster can reach the ingress controller by name, and nothing needs to — traffic arrives from the node’s network edge, upstream of kube-proxy entirely. If you go looking for the ingress the way you would on any other cluster, by finding its Service, you will conclude the ingress is not installed.
That last setting has a consequence that bites later: with no Service, providers.kubernetesIngress.publishedService must also be off. Left on, Traefik tries to read a Service that does not exist in order to write status.loadBalancer.ingress back onto every Ingress object. Those statuses stay empty. Anything that waits on one — a kubectl wait --for=jsonpath, a readiness gate in a CI script, an external-dns that looks for a published address — waits forever against a cluster that is working perfectly.
deployment:
kind: DaemonSet # one per node is the hostPort invariant
service:
enabled: false # there is no Service. nothing routes to Traefik.
ports:
web:
port: 8000 # what the container binds
hostPort: 80 # what the node answers on
expose:
default: false # no Service to expose it through
websecure:
port: 8443
hostPort: 443
expose:
default: false
tls:
enabled: true
Check yourself
3 questions. One attempt each is recorded; the explanation is the point, not the score.
Traefik answers on the node’s port 443 while dropping every Linux capability. How?
What goes wrong if this were a Deployment with 2 replicas instead of a DaemonSet?
With service.enabled: false, why must publishedService also be off?