Kubernetes Networking: Hoe Services Elkaar Vinden
Service discovery, DNS debugging en de verschillen tussen ClusterIP, NodePort en LoadBalancer — praktisch uitgelegd met voorbeelden die je direct kunt gebruiken.
Jean-Pierre Broeders
Freelance DevOps Engineer
Kubernetes Networking: Hoe Services Elkaar Vinden
Pods deployen is stap één. Maar zodra service A met service B moet praten, wordt het interessant. Kubernetes heeft een eigen netwerkmodel en dat wijkt behoorlijk af van wat de meeste developers gewend zijn van traditionele servers.
Het netwerkmodel in een notendop
Elke Pod krijgt een eigen IP-adres. Geen NAT, geen port mapping tussen Pods. Ze kunnen elkaar gewoon bereiken alsof ze op hetzelfde netwerk zitten. Klinkt simpel, maar onder de motorkap draait er behoorlijk wat magie via CNI plugins als Calico, Flannel of Cilium.
Het probleem: Pod IPs zijn efemeer. Bij elke herstart krijgt een Pod een nieuw adres. Direct op IP-adressen verbinden is dus geen optie. Daar komen Services om de hoek kijken.
ClusterIP, NodePort en LoadBalancer
Drie typen Services, elk voor een ander scenario.
| Type | Bereikbaar vanaf | Typisch gebruik |
|---|---|---|
| ClusterIP | Alleen binnen het cluster | Interne communicatie tussen services |
| NodePort | Elke node op een vaste poort (30000-32767) | Quick & dirty externe toegang, dev/test |
| LoadBalancer | Extern via cloud load balancer | Productie-verkeer van buitenaf |
ClusterIP is verreweg het meest gebruikt. Een backend API die alleen door de frontend container benaderd wordt? ClusterIP. Geen reden om dat naar buiten te exposen.
apiVersion: v1
kind: Service
metadata:
name: user-api
namespace: production
spec:
type: ClusterIP
selector:
app: user-api
ports:
- port: 80
targetPort: 8080
protocol: TCP
Andere Pods bereiken deze service via user-api.production.svc.cluster.local of simpelweg user-api als ze in dezelfde namespace draaien.
NodePort opent een poort op alle nodes. Handig voor testen, maar in productie wil je dit eigenlijk nooit. Het beperkte poortbereik en het feit dat je node-IPs moet kennen maakt het onpraktisch.
LoadBalancer werkt alleen in cloud-omgevingen (of met MetalLB on-prem). Voor elke Service wordt een externe load balancer aangemaakt. Bij tientallen services wordt dat duur — één LoadBalancer per service is zelden wat je wilt.
Ingress: de betere oplossing voor HTTP-verkeer
In plaats van tien LoadBalancers gebruik je één Ingress Controller die HTTP-verkeer routeert op basis van hostnaam of pad.
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: web-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.nl
secretName: api-tls
rules:
- host: api.example.nl
http:
paths:
- path: /users
pathType: Prefix
backend:
service:
name: user-api
port:
number: 80
- path: /orders
pathType: Prefix
backend:
service:
name: order-api
port:
number: 80
Eén extern IP, meerdere services. TLS-terminatie op Ingress-niveau. Dat scheelt een hoop LoadBalancer-kosten en maakt het beheer overzichtelijk.
DNS debugging: als services elkaar niet vinden
Vroeg of laat gaat er iets mis met DNS in het cluster. De standaard aanpak om te debuggen:
# Start een debug pod
kubectl run dns-debug --image=busybox:1.36 --rm -it -- sh
# Vanuit de pod:
nslookup user-api.production.svc.cluster.local
nslookup kubernetes.default
cat /etc/resolv.conf
Dat resolv.conf bestand is cruciaal. Daar staat welke DNS-server (CoreDNS) gebruikt wordt en welke search domains actief zijn. Een veelgemaakte fout: de CoreDNS pods draaien niet of zitten vast in CrashLoopBackOff.
# Check CoreDNS status
kubectl get pods -n kube-system -l k8s-app=kube-dns
kubectl logs -n kube-system -l k8s-app=kube-dns --tail=50
Negen van de tien keer is het een van deze drie:
- CoreDNS pods zijn crashed (check logs)
- NetworkPolicy blokkeert DNS-verkeer op poort 53
- De service naam is verkeerd gespeld (ja, echt)
Headless Services voor StatefulSets
Soms moet je individuele Pods aanspreken in plaats van via een load-balanced Service. Denk aan databases of message brokers waar elke instance een eigen identiteit heeft.
apiVersion: v1
kind: Service
metadata:
name: postgres
spec:
clusterIP: None # Dit maakt het headless
selector:
app: postgres
ports:
- port: 5432
Met clusterIP: None maakt Kubernetes geen virtueel IP aan. In plaats daarvan geeft een DNS-lookup alle Pod-IPs terug. In combinatie met een StatefulSet worden individuele Pods bereikbaar via postgres-0.postgres.default.svc.cluster.local, postgres-1.postgres..., enzovoort.
NetworkPolicies: verkeer beperken
Standaard kan elke Pod met elke andere Pod communiceren. In productie is dat een risico. NetworkPolicies werken als een firewall binnen het cluster.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: user-api-policy
namespace: production
spec:
podSelector:
matchLabels:
app: user-api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080
Alleen Pods met label app: frontend mogen bij de user-api op poort 8080. Al het andere verkeer wordt geblokkeerd. Let op: niet elke CNI plugin ondersteunt NetworkPolicies. Flannel doet het standaard niet, Calico en Cilium wel.
Veelgemaakte fouten
Poort verwarring. Een Service heeft port (waar de Service op luistert) en targetPort (de poort van de container). Die hoeven niet gelijk te zijn, maar als ze niet kloppen bereikt het verkeer de Pod gewoon niet. Geen foutmelding, gewoon timeouts.
Namespace vergeten. Services zijn namespace-scoped. user-api vanuit een andere namespace bereiken vereist user-api.production of de volledige FQDN. Zonder namespace zoekt DNS alleen in de eigen namespace.
Selector mismatch. De selector van een Service moet exact matchen met de labels op de Pods. Eén typfout en de Service heeft geen endpoints. Snel te checken:
kubectl get endpoints user-api -n production
Lege endpoints? Dan matcht de selector niet of draaien er geen Pods met die labels.
Wrap-up
Kubernetes networking is niet ingewikkeld zodra het mentale model klopt: Pods hebben IPs, Services bieden stabiele adressen, DNS plakt het aan elkaar. De meeste problemen zitten in configuratiefouten — verkeerde poorten, missende labels, geblokkeerd DNS-verkeer. Met de debug-stappen hierboven kom je er in de meeste gevallen snel achter waar het misgaat.
