2 modules · ~25 minutes
Table of Contents
- Overview
- Module 1 — Creating Kubernetes Services
- Module 2 — Managing Kubernetes Services
- Service Types — Reference
- Kubernetes Networking — Key Concepts
- kube-proxy
- Kubernetes Internal DNS
- EndpointSlices
- Ingress
- kubectl Commands — Quick Reference
- Diagrams
Overview
A Kubernetes Service is a network abstraction that defines:
- how external clients access Pods
- how internal Pods discover each other
Containers are ephemeral — they appear, complete a task, and disappear. Without a Service, clients can never reliably find the Pods they need. Services solve this by providing a stable IP address and DNS name that remain constant regardless of the underlying Pod lifecycle.
Services also provide:
- Load balancing — traffic distribution among replicas
- Session affinity — persistent client-pod relationships
- Health checks — exclusion of failing Pods
- Service discovery via DNS — resolution by service name
Module 1 — Creating Kubernetes Services
Understanding the Role of Kubernetes Services
The Resource Discovery Problem
Kubernetes Pods are designed to be ephemeral: they appear, complete a task, then disappear — sometimes in just seconds. This creates two fundamental problems:
- How do external clients find Pods? Pod IP addresses change on every restart.
- How do you control access? You need to ensure only legitimate clients access the right resources.
The Default Service — kubernetes.default.svc
When any Kubernetes cluster is created, a ClusterIP Service is automatically created. This service enables connections between cluster components and the Kubernetes API server. The DNS name of this service is:
kubernetes.default.svc
DNS name breakdown:
kubernetes— Service namedefault— Namespace it resides insvc— suffix indicating it’s a Service
All kubectl commands use this service under the hood.
Namespaces
kubectl get namespaces
Namespaces allow organizing and isolating deployments and resources, and applying granular access permissions. A new cluster includes several predefined Namespaces:
| Namespace | Usage |
|---|---|
default | Default namespace for user resources |
kube-system | Internal Kubernetes components (kube-proxy, CoreDNS, etc.) |
kube-public | Public resources accessible to everyone |
kube-node-lease | Lease objects for nodes |
Defining Services Using kubectl
Creating a Deployment and Exposing as NodePort
# 1. Create an nginx Deployment
kubectl create deployment my-nginx --image=nginx:latest --port=80
# 2. Expose as NodePort Service
kubectl expose deployment my-nginx \
--type=NodePort \
--port=80 \
--target-port=80 \
--name=my-nginx-service
# 3. List Services
kubectl get services
# 4. Get node IP
kubectl get nodes -o wide
# 5. Test access
curl http://<NODE_IP>:<NODE_PORT>
Default Values of kubectl expose
| Parameter | Default | Override |
|---|---|---|
--protocol | TCP | --protocol=UDP |
--type | ClusterIP | --type=NodePort or --type=LoadBalancer |
--port | Auto-detected from Pod exposed port | specify explicitly |
--target-port | Same as --port | specify if different |
Note: The
kubectl exposecommand doesn’t support the--node-portargument for manually assigning a NodePort. Use a YAML manifest for that.
Defining Services Using YAML Manifests
Example Architecture
Two deployments communicate via a Service:
- backend:
hashicorp/http-echoserver that responds with simple text - frontend:
curlimages/curlcontainer that calls the backend in a loop
The frontend uses the DNS name backend-service:80 — this works only if the Kubernetes Service creates a functioning DNS environment in the cluster.
Manifest — Backend Deployment
# backend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: backend
spec:
replicas: 1
selector:
matchLabels:
app: backend
template:
metadata:
labels:
app: backend
spec:
containers:
- name: http-echo
image: hashicorp/http-echo:0.2.3
args:
- "-listen=:8080"
- "-text=Hello from the Backend!"
ports:
- containerPort: 8080
Manifest — ClusterIP Service
# backend-service.yaml
apiVersion: v1
kind: Service
metadata:
name: backend-service
spec:
type: ClusterIP # Default type
selector:
app: backend # Matches the backend Pod label
ports:
- port: 80 # Service port (used by internal clients)
targetPort: 8080 # Container port in the backend Pod
Key mechanism: The
selectorfield (app: backend) is the only link between the Service and the Pods. The Service dynamically watches all Pods with this label.
Manifest — Frontend Deployment
# frontend-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: frontend
spec:
replicas: 1
selector:
matchLabels:
app: frontend
template:
metadata:
labels:
app: frontend
spec:
containers:
- name: curl-loop
image: curlimages/curl:7.86.0
command: ["/bin/sh"]
args:
- "-c"
- |
while true; do
echo "=== Calling backend-service:80 ==="
curl -s backend-service:80
echo
sleep 5
done
Deployment and Verification
kubectl apply -f backend-deployment.yaml
kubectl apply -f frontend-deployment.yaml
kubectl apply -f backend-service.yaml
# Check Pods
kubectl get pods
# Follow frontend logs (before Service: failure)
kubectl logs -f <frontend-pod-name>
# After Service deployment: success
# Output: "Hello from the Backend!"
Module 2 — Managing Kubernetes Services
Preparing Your Cluster for a LoadBalancer Service
Why Preparation Is Needed
LoadBalancer Services work natively with cloud providers (AWS, Azure, GCP) because they directly integrate external load balancer provisioning.
In bare metal or local VM environments, a LoadBalancer remains in Pending state indefinitely without additional infrastructure.
MetalLB — Load Balancer for Bare Metal Environments
MetalLB is an open source project that provides a LoadBalancer implementation for Kubernetes clusters running outside the cloud.
# 1. Apply MetalLB manifest
kubectl apply -f https://raw.githubusercontent.com/metallb/metallb/main/config/manifests/metallb-native.yaml
# 2. Verify MetalLB Pods
kubectl get pods -n metallb-system
Manifest — IP Address Pool (MetalLB)
# ip-pool.yaml
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: first-pool
namespace: metallb-system
spec:
addresses:
- 192.168.1.240-192.168.1.250
Manifest — L2Advertisement (MetalLB)
# l2advertisement.yaml
apiVersion: metallb.io/v1beta1
kind: L2Advertisement
metadata:
name: example
namespace: metallb-system
kubectl apply -f ip-pool.yaml
kubectl apply -f l2advertisement.yaml
MetalLB announces service IPs via the Layer 2 (ARP/NDP) protocol, making the load balancer accessible from the local network.
Launching a LoadBalancer Service
Deployment with Liveness and Readiness Probes
# nginx-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx
spec:
replicas: 3
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:latest
ports:
- containerPort: 80
livenessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 15
periodSeconds: 20
readinessProbe:
httpGet:
path: /
port: 80
initialDelaySeconds: 5
periodSeconds: 5
| Probe | Role | On Failure |
|---|---|---|
livenessProbe | Ensures container is alive | Kubernetes restarts the container |
readinessProbe | Ensures Pod is ready to receive traffic | Pod is removed from load balancing |
Important: Without
readinessProbe, a LoadBalancer has no way to know which Pods are actually available.
Manifest — LoadBalancer Service
# nginx-loadbalancer-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-loadbalancer-service
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
kubectl apply -f nginx-deployment.yaml
kubectl apply -f nginx-loadbalancer-service.yaml
# Verify (wait for EXTERNAL-IP)
kubectl get svc nginx-loadbalancer-service
# Test access
curl http://<EXTERNAL-IP>
Adding Session Affinity to a Kubernetes LoadBalancer
Session Affinity (or “sticky sessions”) ensures a client is always redirected to the same Pod. This is essential for:
- Maintaining connection sessions (logins)
- Improving performance through caching
- Facilitating debugging
Manifest — LoadBalancer with Session Affinity
# nginx-loadbalancer-affinity-service.yaml
apiVersion: v1
kind: Service
metadata:
name: nginx-loadbalancer-service
spec:
type: LoadBalancer
selector:
app: nginx
ports:
- protocol: TCP
port: 80
targetPort: 80
sessionAffinity: ClientIP
sessionAffinityConfig:
clientIP:
timeoutSeconds: 10800 # 3 hours of persistence
kubectl apply -f nginx-loadbalancer-affinity-service.yaml
Applying a Kubernetes LoadBalancer to the Cloud
On cloud providers (AKS, EKS, GKE), simply using type: LoadBalancer automatically provisions a cloud load balancer. No MetalLB needed.
Azure AKS example:
# The cloud provider auto-provisions an Azure Load Balancer
kubectl apply -f nginx-loadbalancer-service.yaml
kubectl get svc nginx-loadbalancer-service
# EXTERNAL-IP will be assigned automatically (~30 seconds)
Service Types — Reference
| Type | Accessibility | Use Case | EXTERNAL-IP |
|---|---|---|---|
| ClusterIP | Inside cluster only | Inter-service communication | None |
| NodePort | Via NodeIP:NodePort | Dev/testing external access | None (uses Node IP) |
| LoadBalancer | Via external load balancer | Production on cloud | Yes (provisioned by cloud) |
| ExternalName | Via CNAME | Access to external services | None |
ClusterIP — Default Service
apiVersion: v1
kind: Service
metadata:
name: my-clusterip-svc
spec:
type: ClusterIP # Default, can be omitted
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
NodePort Service
apiVersion: v1
kind: Service
metadata:
name: my-nodeport-svc
spec:
type: NodePort
selector:
app: my-app
ports:
- port: 80
targetPort: 8080
nodePort: 31569 # Optional: specific port 30000-32767
Access: http://<NODE_IP>:31569
ExternalName Service
apiVersion: v1
kind: Service
metadata:
name: externalname-svc
spec:
type: ExternalName
externalName: db.example.com # Returns CNAME pointing to this name
Kubernetes Networking — Key Concepts
CNI (Container Network Interface)
The CNI is the networking interface between Kubernetes and the network plugins responsible for assigning IP addresses to Pods and managing routing.
Popular CNI plugins:
| Plugin | Features |
|---|---|
| Calico | Network policies, BGP routing, high performance |
| Flannel | Simple, overlay network |
| Weave Net | Simple, encryption |
| Cilium | eBPF-based, advanced observability |
Network Policies
NetworkPolicy objects control network traffic between Pods and namespaces:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: deny-all-ingress
spec:
podSelector: {} # Applies to all pods in namespace
policyTypes:
- Ingress # Block all incoming traffic
kube-proxy
kube-proxy runs on each node and manages iptables/ipvs rules to route traffic to the correct Pod.
When you create a Service, kube-proxy:
- Detects the new Service via the API server
- Creates appropriate iptables rules on the node
- These rules transparently redirect traffic to one of the available Pods
Kubernetes Internal DNS
CoreDNS is the default DNS server in Kubernetes. It runs as Pods in kube-system and provides internal name resolution.
DNS format:
<service-name>.<namespace>.svc.<cluster-domain>
Examples:
# Full DNS name
backend-service.default.svc.cluster.local
# Short form (same namespace)
backend-service
# With explicit namespace
backend-service.production
Testing DNS resolution:
# From inside a Pod
kubectl exec -it <pod-name> -- nslookup backend-service
kubectl exec -it <pod-name> -- curl backend-service:80
EndpointSlices
EndpointSlices are the mechanism Kubernetes uses to track which Pod IPs are currently available for a Service.
# View EndpointSlices for a service
kubectl get endpointslices -l kubernetes.io/service-name=my-service
kubectl describe endpointslice <name>
The Service controller automatically creates and updates EndpointSlices when Pods are added, removed, or change state.
Ingress
Ingress provides HTTP/HTTPS routing at Layer 7 (application layer), allowing multiple services to be exposed through a single external IP.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: my-ingress
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
ingressClassName: nginx
rules:
- host: myapp.example.com
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: frontend-service
port:
number: 80
Difference between Service and Ingress:
| Aspect | Service | Ingress |
|---|---|---|
| Layer | L4 (TCP/UDP) | L7 (HTTP/HTTPS) |
| External IP | One per LoadBalancer | One for all services |
| SSL/TLS | Manual | Centralized (via cert-manager) |
| Path routing | No | Yes |
| Host routing | No | Yes |
kubectl Commands — Quick Reference
# List services
kubectl get services
kubectl get svc # Abbreviated
kubectl get svc -A # All namespaces
kubectl get svc -o wide # With additional info
# Describe a service
kubectl describe svc <name>
# Create a service from manifest
kubectl apply -f service.yaml
# Expose a deployment
kubectl expose deployment <name> --type=NodePort --port=80
# Delete a service
kubectl delete svc <name>
kubectl delete -f service.yaml
# View endpoints
kubectl get endpoints
kubectl get endpointslices
# Test access
kubectl port-forward svc/<name> 8080:80
curl http://localhost:8080
Search Terms
services · kubernetes · containers · service · manifest · loadbalancer · deployment · default · kubectl · metallb · affinity · clusterip · defining · network · nodeport · reference · session