Skip to main content

Kubernetes Services: Expose & Network Python Apps

A Kubernetes Service is an abstraction that exposes a set of pods and enables other applications to discover and communicate with them. Without a Service, accessing a pod's IP address is fragile because pods are ephemeral—they can be replaced at any time. A Service provides a stable virtual IP (ClusterIP) and DNS name, so other pods can reach your application regardless of which physical pods are running.

Why Do You Need Kubernetes Services?

Pods in Kubernetes are mortal. When a pod crashes, the Deployment controller creates a new pod with a different IP address. If your Python frontend directly referenced a backend pod's IP, it would break after the pod restarted. A Service solves this by maintaining a stable endpoint that routes traffic to the current set of healthy pods. The Service also load-balances traffic across replicas, distributing requests evenly.

I deployed a Python microservices application where the API gateway initially communicated directly with backend pods via hardcoded IP addresses. After the first pod restart, the gateway lost connectivity. Adding a Service fixed the issue instantly—the gateway now talks to the Service, and the Service automatically routes to healthy pods.

Understanding Kubernetes Service Types

Kubernetes provides three primary Service types:

Service TypeScopeUse Case
ClusterIPInternal cluster onlyPod-to-pod communication within the cluster
NodePortExposes on every node's IP:portTesting, small deployments without load balancer
LoadBalancerExternal load balancerProduction, public-facing Python web applications

ClusterIP is the default and most common. It provides an internal IP reachable only from within the cluster. NodePort exposes your application on a static port on each node's IP, allowing external access without a cloud load balancer (useful for on-premises or testing). LoadBalancer provisioning an external load balancer (AWS ELB, Google Cloud LB) and assigns a public IP or domain name.

Creating a ClusterIP Service for Python Pod Communication

Here's a ClusterIP Service that exposes a Python FastAPI deployment internally:

apiVersion: v1
kind: Service
metadata:
name: python-api-service
namespace: default
spec:
type: ClusterIP
selector:
app: python-api
ports:
- name: http
port: 80
targetPort: http
protocol: TCP
sessionAffinity: None

This Service creates a stable IP (e.g., 10.0.0.42) and DNS name (python-api-service.default.svc.cluster.local) that routes traffic to all pods matching the label app: python-api. The port: 80 is the port on the Service; the targetPort: http refers to the named port in the pod spec (or you can use a port number directly).

Other pods can now reach this Service via:

import requests

# Within the cluster, use the Service DNS name
response = requests.get("http://python-api-service/api/data")
print(response.json())

Kubernetes resolves python-api-service to the Service's ClusterIP automatically. You can also use the fully-qualified name python-api-service.default.svc.cluster.local for explicit resolution.

Exposing Python Applications Externally with NodePort

If you need external access without a cloud load balancer, use a NodePort Service:

apiVersion: v1
kind: Service
metadata:
name: python-web-nodeport
spec:
type: NodePort
selector:
app: python-web
ports:
- port: 8080
targetPort: 8000
nodePort: 30080
protocol: TCP

This Service exposes your Python web app on port 30080 on every node. You can access it from outside the cluster via <node-ip>:30080. The nodePort field (30000-32767) is optional; Kubernetes assigns a random port if you omit it.

Using LoadBalancer for Production Python Services

In cloud environments (AWS, GCP, Azure), a LoadBalancer Service provisions an external load balancer and assigns a public IP or DNS name:

apiVersion: v1
kind: Service
metadata:
name: python-api-lb
spec:
type: LoadBalancer
selector:
app: python-api
ports:
- port: 443
targetPort: 8000
protocol: TCP
sessionAffinity: ClientIP

After applying this Service, your cloud provider automatically provisions a load balancer. Use kubectl get svc python-api-lb to see the external IP:

$ kubectl get svc python-api-lb
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
python-api-lb LoadBalancer 10.0.0.50 203.0.113.42 443:30001/TCP 2m

Clients access your Python application at 203.0.113.42:443. The load balancer distributes traffic across pods, and Kubernetes keeps the endpoint list updated automatically.

Service Discovery: How Kubernetes DNS Works

Kubernetes runs a DNS service (CoreDNS by default) that resolves Service names. When a pod tries to reach python-api-service, the DNS resolver returns the ClusterIP. This happens transparently—your Python application needs no special configuration.

DNS names follow the pattern: <service-name>.<namespace>.svc.cluster.local. From the same namespace, you can use the short name <service-name>.

How Service Traffic Routes to Healthy Pods

When you create a Service, Kubernetes automatically creates an Endpoints object that lists the Service's backing pods. The kube-proxy component (running on each node) watches Endpoints and updates iptables rules to route traffic to pod IPs. If a pod fails the readiness probe, Kubernetes removes it from the Endpoints list, and no new traffic flows to it.

Here's a Python Deployment with a Service:

apiVersion: apps/v1
kind: Deployment
metadata:
name: python-api-deployment
spec:
replicas: 3
selector:
matchLabels:
app: python-api
template:
metadata:
labels:
app: python-api
spec:
containers:
- name: app
image: my-registry/python-api:1.0.0
ports:
- name: http
containerPort: 8000
---
apiVersion: v1
kind: Service
metadata:
name: python-api-service
spec:
type: ClusterIP
selector:
app: python-api
ports:
- port: 80
targetPort: http

The Service's selector: app: python-api matches the Deployment's pod labels, so the Service automatically discovers and routes to all replicas.

Key Takeaways

  • A Service provides a stable endpoint for accessing pods, insulating clients from pod IP changes.
  • ClusterIP (default) enables internal pod-to-pod communication within the cluster.
  • NodePort exposes applications on a static port on each node for testing or on-premises deployments.
  • LoadBalancer provisions a public-facing external load balancer (AWS/GCP/Azure).
  • Kubernetes DNS automatically resolves Service names; use short names within the namespace or fully-qualified names across namespaces.

Frequently Asked Questions

How does Kubernetes choose which pod to send traffic to?

Kubernetes uses kube-proxy to load-balance traffic across healthy pods. By default, the load balancing is round-robin: each new request goes to the next pod in the list. You can change this with sessionAffinity: ClientIP to stick a client to the same pod (useful for stateful applications).

What is the difference between a Service port and a container port?

The Service port is the port your application reaches the Service on. The container port (targetPort) is the port inside the pod. For example, a Service might listen on port 80, but the Python app inside the pod listens on port 8000. The Service automatically translates port 80 traffic to port 8000.

Can a Service route traffic to pods in other namespaces?

By default, a Service's selector only matches pods in the same namespace. To route to pods in a different namespace, you would use an Ingress (covered in the next article) or manually create an Endpoints object pointing to external pod IPs.

How do I access a Service from outside the cluster?

That depends on the Service type. ClusterIP is accessible only within the cluster. NodePort is accessible via <node-ip>:<nodePort> from outside. LoadBalancer is accessible via the external IP/DNS name. For production, Ingress is typically preferred for HTTP/HTTPS routing.

What happens if all pods behind a Service fail?

The Service still exists, but the Endpoints list becomes empty. Traffic sent to the Service fails because there are no healthy pods to route to. Kubernetes does not automatically delete the Service; you manually update or delete it.

Further Reading