The controller runs as a deployment in the cluster, usually exposed by a single LoadBalancer or NodePort Service so it has one public IP. ingress-nginx, the most common controller, runs an NGINX process. When you create or change an Ingress object, the controller's control loop notices the change through a watch on the Kubernetes API and regenerates the NGINX config, then reloads it.
When a request arrives, the controller terminates TLS if a certificate is configured, reads the Host header and the request path, and matches them against the compiled rules. It then forwards the request to the backend Service. In practice most controllers skip the Service's cluster IP and proxy straight to the pod IPs they learn from the Service's Endpoints, which gives them better load balancing and faster failover when a pod dies.
TLS is handled by referencing a Kubernetes Secret that holds the certificate and key. Pairing the controller with cert-manager automates issuing and renewing free Let's Encrypt certificates, so HTTPS for a new hostname becomes a few lines of YAML instead of a manual certificate dance.
IngressClass tells the cluster which controller owns a given Ingress, so you can run more than one controller, for example a public-facing ingress-nginx and a separate internal one, without them fighting over the same rules.