The "Receptionist" Problem
Pods are mortal. They are created, they die, and they get replaced — by Deployments, by node failures, by rolling updates. Each time a Pod is recreated, it gets a new, randomly assigned IP address. This makes it impossible for other parts of your application to reliably connect to a Pod's IP directly.
A Service solves this problem by acting as a stable, persistent networking layer in front of a dynamic set of Pods.
A Service gets a single, stable IP address (called the ClusterIP) and a DNS name that never changes for the lifetime of the Service. It uses a label selector to identify which Pods it should route traffic to. As Pods come and go, the Service continuously updates its internal list of healthy Pod endpoints. Other applications just connect to the Service's stable DNS name, and the Service handles routing and load balancing.
A Service definition is mostly a selector (which label to match Pods against) and a port mapping (the port the Service listens on versus the port the container actually exposes). With this Service in place, any other Pod in the cluster can reach your application at http://my-app-service or http://my-app-service.default.svc.cluster.local (the full DNS name, where default is the namespace). Kubernetes' internal DNS server resolves this name to the Service's stable ClusterIP.