SaveMyCert
Log in
5 of 5 free questions left today·for 10 a day
Container Orchestration

Kubernetes Networking: Services, kube-proxy, CNI, DNS, and Ingress

16 min readKCNA · Container OrchestrationUpdated

Kubernetes networking follows one flat model: every Pod receives its own IP address, and any Pod can reach any other Pod in the cluster without network address translation (NAT). On top of that foundation, Services give groups of Pods a stable virtual IP and DNS name, kube-proxy programs the rules that make those virtual IPs work, CoreDNS resolves service names, and Ingress or its successor, the Gateway API, routes external HTTP traffic to the right Service. The layer below the model, assigning Pod IPs and wiring nodes together, is delegated to a Container Network Interface (CNI) plugin such as Calico, Cilium, or Flannel. For the KCNA exam you need recognition-level command of this stack: what each component does, which Service type fits a given situation, and how a NetworkPolicy flips a Pod from accepting all traffic to denying everything not explicitly allowed. This lesson walks the path traffic takes so each component has an obvious place.

What you’ll learn
  • Explain the Kubernetes flat network model, the no-NAT rule and why Pod-to-Pod traffic needs no Service
  • Distinguish ClusterIP, NodePort, LoadBalancer, headless, and ExternalName Services, including how a LoadBalancer reaches node ports
  • Describe how labels and readiness decide a Service's endpoints and how kube-proxy (iptables, nftables, IPVS) implements Services
  • Use CoreDNS names for Services and for StatefulSet Pods behind a headless Service
  • Identify the role of the CNI and recognize plugins such as Calico, Cilium, and Flannel
  • Contrast Ingress with the Gateway API and explain why both need a controller
  • Write NetworkPolicy peers correctly across namespaces and keep DNS working under default-deny egress

The Kubernetes network model: one flat network

Kubernetes imposes a small set of networking rules and leaves the implementation to plugins. The rules: every Pod gets its own cluster-wide IP address; every Pod can communicate with every other Pod, on any node, without NAT; and agents on a node, such as the kubelet, can reach all Pods on that node. The result is one flat network in which Pods behave like small hosts with real addresses.

This is deliberately different from the classic single-host Docker model, where containers hide behind the host IP and you publish ports with flags like -p 8080:80. In Kubernetes there is no port mapping between Pods: a container listening on port 8080 is reachable at its Pod IP on port 8080 from anywhere in the cluster. Containers in the same Pod share one network namespace, so they share the Pod IP and talk to each other over localhost, which also means two containers in one Pod cannot bind the same port: the second one to try fails with an "address already in use" error. The shared namespace is held open by a tiny infrastructure container, the pause container, which the runtime starts first; the application containers join its network namespace, so the Pod keeps its IP even when an individual container restarts.

Because the network is flat, Pod-to-Pod traffic needs no Service at all. A Pod can connect straight to another Pod's IP, even on a different node, and the packet travels over the routes the CNI plugin set up. kube-proxy plays no part in that path: it only acts on traffic addressed to a Service virtual IP, never on traffic addressed to a Pod IP.

The flat model keeps application configuration simple, but Pod IPs are ephemeral. When a Pod is evicted, rescheduled, or replaced during a Deployment rollout, its replacement comes up with a new IP. Nothing in the cluster should hard-code a Pod IP; clients should call a Service name instead. That instability is the problem Services exist to solve, and it is the thread that connects everything else in this lesson: Services give the flat network stable names, and Ingress and NetworkPolicies shape what is allowed to flow across it.

CNI: the plugin that actually wires Pod networking

Kubernetes defines the network model but does not implement it. That job belongs to a Container Network Interface (CNI) plugin. CNI is a CNCF specification: when the kubelet asks the container runtime to create a Pod, the configured CNI plugin is invoked to attach the Pod to the network, assign it an IP address from the cluster's Pod address range, and set up the routes or tunnels that let Pods on different nodes reach each other without NAT.

For KCNA you need recognition-level knowledge of the common plugins, not their internals:

  • Flannel: a simple overlay network focused on basic Pod-to-Pod connectivity. It does not enforce NetworkPolicies on its own.
  • Calico: a widely used plugin that provides routed networking and full NetworkPolicy enforcement, and can extend policy features beyond the built-in API.
  • Cilium: built on eBPF in the Linux kernel, offering networking, NetworkPolicy enforcement, and deep observability of traffic flows.

Two exam-relevant consequences follow. First, a cluster without a working CNI plugin cannot run normal workloads: nodes report NotReady and new Pods never receive IP addresses. This is exactly what you see right after kubeadm init: kubeadm installs kube-proxy and CoreDNS as add-ons, but it does not install a Pod network add-on. The nodes stay NotReady (and the CoreDNS Pods stay Pending) until you apply a CNI plugin of your choice. Second, NetworkPolicy is enforced by the CNI plugin, not by Kubernetes itself, so creating a NetworkPolicy object in a cluster whose plugin ignores policies changes nothing. If a question asks which component assigns Pod IPs or implements the network model, the answer is the CNI plugin, not kube-proxy and not the kube-apiserver.

Services: a stable front door for ephemeral Pods

A Service gives a set of Pods a single stable virtual IP (the ClusterIP) and a DNS name that outlive any individual Pod. The Service finds its Pods with a label selector: every ready Pod whose labels match becomes an endpoint, and Kubernetes tracks those Pod IPs in EndpointSlice objects that update automatically as Pods come and go. Clients talk to the Service address; they never need to know which Pods are behind it right now.

apiVersion: v1
kind: Service
metadata:
  name: api
spec:
  selector:
    app: api
  ports:
    - port: 80
      targetPort: 8080

Here anything in the cluster can call the api Service on port 80, and the traffic is delivered to port 8080 on one of the matching Pods. Traffic is spread across the ready endpoints roughly evenly.

Endpoint membership is decided by labels and readiness alone. The Service does not care which controller created a Pod: a Deployment's replicas, a Pod you started by hand, or a debugging Pod that happens to carry app: api all qualify equally. A Pod with no readiness probe counts as ready as soon as its containers are running, so a stray Pod with matching labels starts receiving a share of the Service's traffic, and if nothing in it listens on the target port those connections are refused. The reverse mistake is just as common: if the selector matches no Pod labels, or no matching Pod is ready, the Service has no endpoints and connections to it fail.

kube-proxy is the component that makes the virtual IP work. It runs on every node, watches the API server for Services and EndpointSlices, and programs the node's packet-handling rules so that a connection to the ClusterIP is rewritten to go to a real Pod IP. It has several modes on Linux: iptables (the default), nftables (generally available since Kubernetes v1.33, the modern replacement for the iptables backend), and IPVS (deprecated since v1.35). Some CNI plugins, such as Cilium, can replace kube-proxy entirely with their own eBPF implementation. Despite its name, kube-proxy does not sit in the data path passing packets along in userspace in modern clusters; it programs the kernel rules and the kernel forwards the traffic. On the exam, kube-proxy is the answer to which node component implements Services.

Service types compared: ClusterIP, NodePort, LoadBalancer, and friends

Every Service has a type, and the first three types build on each other. Choosing among them is a classic KCNA question, so learn them as a ladder of exposure:

TypeReachable fromHow it worksTypical use
ClusterIP (default)Inside the cluster onlyVirtual IP programmed by kube-proxyInternal service-to-service traffic
NodePortOutside, via any node IPOpens the same port (default range 30000-32767) on every node, on top of a ClusterIPSimple external access, dev and test
LoadBalancerOutside, via one external IPAsks the cloud provider to provision a load balancer for the Service; by default it sends traffic to the node port opened on every nodeProduction external exposure on a cloud

Two more variants appear at recognition depth. A headless Service sets clusterIP: None: no virtual IP is allocated and no load balancing happens; instead, DNS returns the individual Pod IPs directly. StatefulSets use headless Services so each Pod gets its own stable DNS name, which matters for databases where clients must address a specific replica. The StatefulSet's serviceName field must name the headless Service; each replica is then resolvable as <pod>.<service>.<namespace>.svc.cluster.local, for example db-0.db.shop.svc.cluster.local, and a new replica gets its name automatically. Looking up the bare Service name (db.shop.svc.cluster.local) instead returns all ready replicas. An ExternalName Service has no selector and no Pod endpoints at all; it simply returns a DNS CNAME record pointing at an external hostname, letting in-cluster clients use a consistent internal name for something outside the cluster.

Remember the dependency direction: LoadBalancer includes NodePort behavior, and NodePort includes ClusterIP behavior. With the default allocateLoadBalancerNodePorts: true, the cloud load balancer's targets are the node ports allocated on every node, from where kube-proxy rules deliver traffic to a ready Pod. (Some cloud integrations can instead target Pod IPs directly, but the node-port path is the default model.) The load balancer serves the Service as a whole: it does not give each Pod a public IP, and traffic never passes through the API server. A LoadBalancer Service on a bare-metal cluster with no provider integration stays pending, because there is no cloud to provision the load balancer.

Cluster DNS: CoreDNS and service discovery

Stable virtual IPs are only half of service discovery; the other half is names. Every conformant cluster runs a DNS server, and CoreDNS is the standard choice. It runs as a Deployment in the kube-system namespace, is exposed through a Service (traditionally named kube-dns), and every Pod's DNS configuration is pointed at it automatically. When you create a Service, CoreDNS starts answering for it immediately.

The naming pattern is fixed and worth memorizing: a Service named api in namespace shop resolves at the fully qualified name api.shop.svc.cluster.local. Pods in the same namespace can use the short name api; Pods in other namespaces must include the namespace, such as api.shop. This is why application configuration in Kubernetes references service names rather than IP addresses: the name survives every Pod restart and even deletion and re-creation of the Service.

DNS behavior also changes with the Service type. A normal ClusterIP lookup returns the single virtual IP. A headless Service lookup returns the set of Pod IPs, one record per ready Pod, and for a StatefulSet each Pod also gets its own name of the form db-0.db.shop.svc.cluster.local. Generic Pod records under pod.cluster.local are different: they are built from the Pod's IP address (for example 10-244-1-5.shop.pod.cluster.local), so they change whenever the IP changes and are no use as a stable address. An ExternalName lookup returns a CNAME to the configured external hostname. For the exam, associate CoreDNS with one job: resolving service names inside the cluster. It is not a load balancer, it does not program packet rules, and it is not the component that assigns Pod IPs.

Ingress: HTTP routing into the cluster

Exposing one Service externally is what NodePort and LoadBalancer do. But a real platform hosts many HTTP applications, and paying for one cloud load balancer per Service scales badly. Ingress solves this at layer 7: it is an API object holding HTTP routing rules that map hostnames and URL paths to Services, and it can terminate TLS. One external entry point can serve shop.example.com and blog.example.com, or route /api and /static to different Services.

The critical exam fact: an Ingress object does nothing by itself. It is only configuration. A cluster must run an Ingress controller, such as Traefik, HAProxy Ingress, or a cloud provider's controller, which watches Ingress objects and configures an actual proxy to implement the rules. Create an Ingress in a cluster with no controller and traffic goes nowhere. Note that the long-popular community ingress-nginx controller has been retired by the Kubernetes project (announced November 2025, with no further releases or security fixes after March 2026), so it is no longer a choice for a new cluster.

AspectServiceIngress
LayerLayer 4, TCP and UDPLayer 7, HTTP and HTTPS
Routing based onPortsHostnames and URL paths
TLS terminationNoYes
Needs extra componentNo, kube-proxy is built inYes, an Ingress controller

Concrete scenario: a user requests https://shop.example.com/api/cart. DNS points at a cloud load balancer created for the Ingress controller's own Service. The controller terminates TLS, matches the host and the /api path prefix against Ingress rules, and forwards the request to the api ClusterIP Service, whose kube-proxy rules deliver it to a ready Pod. The Ingress API is stable but feature-frozen: new routing capabilities go into the Gateway API instead, described next.

Gateway API: the role-oriented successor to Ingress

The Gateway API is the Kubernetes project's successor to Ingress: a set of API resources that split HTTP (and other) routing into objects owned by different roles, and that standardize features Ingress could only offer through controller-specific extensions. Like Ingress, it needs a controller (an implementation such as Envoy Gateway, Istio, Cilium, Traefik or a cloud provider) to do anything.

ResourceTypically owned byWhat it defines
GatewayClassInfrastructure providerWhich controller implements Gateways of this class
GatewayPlatform or cluster operatorThe shared entry point: listeners (ports, protocols, hostnames), TLS certificates, and which namespaces may attach routes
HTTPRouteApplication team, in its own namespaceHost, path and header matches and the backend Services, attached to a Gateway through parentRefs

The role split is the point. A platform team owns one Gateway and its certificates; app teams write HTTPRoutes in their own namespaces and attach them to that Gateway (when its listeners allow routes from those namespaces), without ever editing the shared configuration. With Ingress, by contrast, one object mixes the entry point, TLS and every team's rules.

Traffic splitting is the other headline difference. An HTTPRoute rule can list several backendRefs, each with a weight, so sending 90% of requests to a stable Service and 10% to a canary is standard, portable configuration. The Ingress API has no weight field at all: its backend names only a Service (or resource), so weighted canaries on Ingress depend on extensions specific to one controller.

NetworkPolicies: from default-allow to default-deny

The flat network model is maximally open: by default, every Pod accepts traffic from every other Pod in the cluster. NetworkPolicy is the namespaced API object that restricts this. A policy selects Pods with a podSelector and then lists the ingress sources or egress destinations that remain allowed, expressed as Pod selectors, namespace selectors, or IP blocks.

How a rule picks its peers matters:

  • A podSelector on its own, including an empty one, matches Pods in the policy's own namespace only.
  • To admit traffic from another namespace you need a namespaceSelector, typically on the label kubernetes.io/metadata.name, which Kubernetes sets automatically on every namespace. Put a podSelector in the same peer entry to narrow it to particular Pods in that namespace (both must match); as separate entries they are alternatives.
  • An ipBlock matches CIDR ranges and is meant for addresses outside the cluster; Pod IPs are ephemeral, so select Pods by label instead.

The behavior switch is the single most tested fact here. A Pod selected by no NetworkPolicy accepts everything: default-allow. The moment any policy selects a Pod, that Pod becomes default-deny for the traffic directions the policy covers, and only what some policy explicitly allows gets through. Policies are additive; multiple policies selecting the same Pod combine into the union of their allow rules, and there is no deny rule to write. An empty podSelector selects every Pod in the namespace, which is how the common default-deny baseline is written:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-ingress
  namespace: shop
spec:
  podSelector: {}
  policyTypes:
    - Ingress

Scenario: the shop namespace runs a frontend, an api, and a database. After applying the default-deny policy above, nothing in the namespace accepts inbound traffic. You then add one policy allowing Pods labeled app: frontend to reach app: api, and another allowing app: api to reach app: db on port 5432. A compromised frontend Pod can no longer talk to the database directly, because no policy allows that path. Egress policies have a trap of their own. A default-deny egress policy (empty podSelector, policyTypes: [Egress]) also blocks DNS, because a name lookup is egress traffic to the CoreDNS Pods in kube-system on port 53. Unless a rule allows UDP and TCP port 53 to those Pods (a namespaceSelector for kube-system plus a podSelector for k8s-app: kube-dns), Pods fail to resolve even names they are otherwise allowed to reach. Remember also that enforcement depends on the CNI plugin: Calico and Cilium enforce policies, plain Flannel does not.

Putting it together: which component owns which job

KCNA networking questions are almost always about ownership: which piece of the stack is responsible for a given behavior. Fix the following mapping and most questions become elimination exercises.

  • CNI plugin (Calico, Cilium, Flannel): implements the flat network model, assigns Pod IPs, connects nodes, and enforces NetworkPolicies where supported.
  • Service: a stable virtual IP and name for a labeled set of Pods; the object you create.
  • kube-proxy: the per-node agent that programs iptables, nftables or IPVS rules so Service IPs actually deliver traffic to Pod endpoints; it does not touch traffic sent directly to a Pod IP.
  • CoreDNS: resolves service.namespace.svc.cluster.local names to Service or Pod IPs.
  • Ingress controller or Gateway API implementation: the layer 7 proxy that implements host and path routing, TLS termination and, with the Gateway API, weighted traffic splits.
  • NetworkPolicy: the API object that restricts Pod traffic; inert until a CNI plugin enforces it.

Trace one full journey to cement it. A Pod calls api.shop: CoreDNS resolves the name to a ClusterIP; kernel rules programmed by kube-proxy rewrite the connection to a healthy Pod endpoint from the EndpointSlice; the packet crosses nodes over routes the CNI plugin set up, without NAT between Pods; and if a NetworkPolicy selects the destination Pod, the CNI plugin permits the connection only if some rule allows this source. External HTTP traffic simply adds the Ingress controller in front of the same chain. Every exam option that assigns one component another component's job is wrong by definition.

Tip. KCNA tests networking as component-to-job matching: which component implements Services (kube-proxy), which assigns Pod IPs (the CNI plugin), which resolves service names (CoreDNS), and which object routes HTTP by host and path (Ingress or the Gateway API, both needing a controller). Scenario stems ask you to pick a Service type for an exposure need, predict which Pods a Service or NetworkPolicy selects, address one StatefulSet replica by DNS name, keep DNS working under a default-deny egress policy, or choose the Gateway API when platform and app teams must own separate parts of routing or traffic must be split by weight. Trigger words include flat network, without NAT, NotReady, ClusterIP, NodePort range, headless, serviceName, CNAME, namespaceSelector, HTTPRoute, and default-allow versus default-deny.

Key takeaways
  • Every Pod gets its own IP address, and Pods reach each other across nodes without NAT and without a Service; that flat model is implemented by the CNI plugin, and kubeadm leaves nodes NotReady until you install one.
  • A Service provides a stable virtual IP and DNS name in front of ephemeral Pods; any running Pod whose labels match becomes an endpoint, whichever controller created it, and a Pod without a readiness probe counts as ready once running.
  • kube-proxy runs on every node and programs iptables (default), nftables or IPVS (deprecated) rules that turn Service IPs into Pod endpoints; it never handles traffic sent directly to a Pod IP.
  • ClusterIP is internal-only and the default; NodePort opens a high port (30000-32767) on every node; LoadBalancer provisions a cloud load balancer that by default targets those node ports.
  • A headless Service (clusterIP: None) returns Pod IPs directly in DNS and gives StatefulSet Pods names like db-0.db.shop.svc.cluster.local; ExternalName returns a CNAME to an outside hostname.
  • CoreDNS resolves names like api.shop.svc.cluster.local; short names work within the same namespace.
  • Ingress defines layer 7 host and path routing but needs a controller (ingress-nginx is retired); the Gateway API splits a platform-owned Gateway from team-owned HTTPRoutes and supports weighted backends as standard.
  • Pods are default-allow until a NetworkPolicy selects them; a bare podSelector matches only the policy's own namespace, cross-namespace peers need a namespaceSelector, and default-deny egress must explicitly allow DNS on port 53.

Frequently asked questions

What is the difference between a Service and an Ingress in Kubernetes?

A Service works at layer 4: it gives a set of Pods one stable IP and DNS name and spreads TCP or UDP traffic across them. An Ingress works at layer 7: it holds HTTP routing rules that map hostnames and URL paths to Services and can terminate TLS. A Service functions on its own through kube-proxy, while an Ingress is only configuration and requires an Ingress controller such as Traefik or HAProxy Ingress to take effect. In practice they combine: an Ingress routes external HTTP requests to ClusterIP Services.

What does kube-proxy actually do?

kube-proxy is an agent that runs on every node and implements Services. It watches the API server for Services and EndpointSlices and programs the node's packet-handling rules, using iptables by default, nftables (generally available since v1.33) or the deprecated IPVS mode, so that connections to a Service's virtual IP are redirected to one of the ready Pod endpoints. In modern clusters it does not forward packets itself in userspace; the kernel rules it programs do the forwarding. It does not assign Pod IPs, does not resolve DNS names, and does not touch traffic sent directly to a Pod IP.

Why does Kubernetes need a CNI plugin?

Kubernetes defines a network model, in which every Pod has its own IP and Pods communicate without NAT, but deliberately does not implement it. A Container Network Interface (CNI) plugin such as Calico, Cilium, or Flannel does the actual work: assigning each Pod an IP address, connecting Pods across nodes, and, where supported, enforcing NetworkPolicies. Without a working CNI plugin, nodes remain NotReady and new Pods never receive IP addresses, so ordinary workloads cannot run. kubeadm, for example, installs kube-proxy and CoreDNS but no Pod network add-on, so you must apply one after kubeadm init.

Are NetworkPolicies enabled by default in Kubernetes?

No policy exists by default, so all Pod-to-Pod traffic is allowed: Kubernetes clusters start default-allow. A Pod only becomes restricted once at least one NetworkPolicy selects it; from then on, traffic in the directions that policy covers is denied unless some policy explicitly allows it. Enforcement also depends on the CNI plugin: Calico and Cilium enforce NetworkPolicies, while plain Flannel does not, in which case created policies have no effect.

What is a headless Service used for?

A headless Service is created by setting clusterIP: None. Kubernetes allocates no virtual IP and does no load balancing; instead, a DNS lookup of the Service name returns the IP addresses of the individual ready Pods. Its main use is with StatefulSets: the StatefulSet's serviceName points at the headless Service, and each replica gets a stable DNS name of the form <pod>.<service>.<namespace>.svc.cluster.local, such as db-0.db.shop.svc.cluster.local, so clients of a database or other clustered stateful system can address one specific replica.

What is the difference between the Gateway API and Ingress?

Both route external HTTP traffic to Services and both need a controller to work, but the Gateway API is the more expressive, role-oriented successor to Ingress. It splits the configuration: a platform team owns a Gateway (listeners, ports, TLS certificates), while application teams attach HTTPRoutes from their own namespaces without editing that shared object. HTTPRoutes also support weighted backendRefs, so a 90/10 canary split is standard configuration, whereas the Ingress API has no weight field and needs controller-specific extensions for it.

Why does DNS stop working after a default-deny egress NetworkPolicy?

A DNS lookup is outbound traffic from the Pod to the CoreDNS Pods in the kube-system namespace on port 53. A default-deny egress policy blocks all outbound traffic that no rule allows, so lookups fail even for Services the Pod is otherwise allowed to reach. The fix is an egress rule allowing UDP and TCP port 53 to the CoreDNS Pods, typically selected with a namespaceSelector for kube-system and a podSelector for k8s-app: kube-dns.

Source

This lesson covers the "Container Orchestration" domain of the official KCNA exam guide. Vendors revise their guides — check the source for the current version.

Test yourself on this topic
Practice now

Sign up free to mark lessons complete, bookmark topics and track your exam readiness.

Spotted a mistake in this lesson?