Inhoudsopgave
- Doel
- Vereisten
- Wat namespaces wel en niet isoleren
- Stap 1: maak de tenant-namespace aan
- Stap 2: pas een LimitRange toe
- Stap 3: pas een ResourceQuota toe
- Stap 4: vergrendel netwerkverkeer
- Stap 5: beperk RBAC tot de namespace
- Stap 6: handhaaf Pod Security Standards
- Stap 7: controleer de volledige stack
- Rolling updates en quota-ruimte
- Quotagebruik monitoren
- Soft vs. hard multi-tenancy
- Wanneer escaleren
Doel
Aan het einde van deze gids heb je een volledig ingerichte tenant-namespace waarin resourceverbruik begrensd is, netwerkverkeer standaard geblokkeerd wordt, API-toegang beperkt is tot een team, en runtime-privileges zijn ingeperkt. Hetzelfde patroon werkt voor een team, een omgeving (staging, productie) of een intern product.
Vereisten
- Een Kubernetes-cluster met v1.30 of nieuwer,
kubectl-toegang en rechten om namespaces, ResourceQuotas, LimitRanges, NetworkPolicies, Roles en RoleBindings aan te maken - Een CNI-plugin die NetworkPolicy afdwingt (Calico, Cilium of Antrea). Zonder zo'n plugin worden NetworkPolicy-objecten wel opgeslagen in de API maar hebben ze geen enkel effect op verkeer. Flannel en kubenet dwingen ze niet af.
- Bekendheid met resource requests en limits. Als het verschil tussen een request en een limit niet helder is, lees dan eerst dat artikel; kort gezegd: requests sturen scheduling, limits sturen runtime-handhaving.
Wat namespaces wel en niet isoleren
Namespaces bieden logische segmentatie van API-resources binnen een enkel control plane. Ze scopen namen (twee namespaces kunnen allebei een Service api hebben), ze scopen RBAC Roles en RoleBindings, en ze scopen ResourceQuotas, LimitRanges en NetworkPolicies.
Dat is het.
Namespaces isoleren standaard geen netwerkverkeer. Elke pod kan elke andere pod in elke namespace bereiken zonder beperking. Ze isoleren geen node-resources (workloads uit verschillende namespaces delen dezelfde nodes). Ze isoleren de host-kernel niet. Een namespace op zichzelf is een label, geen hek. Alles op deze pagina bestaat om dat label om te zetten in daadwerkelijke isolatie.
Stap 1: maak de tenant-namespace aan
kubectl create namespace team-payments
kubectl label namespace team-payments \
team=payments \
cost-center=FIN-200
De labels team en cost-center zijn niet verplicht voor Kubernetes, maar ze geven NetworkPolicy namespaceSelector-regels iets om op te matchen en maken kostenallocatie-tooling (zoals Kubecost) bruikbaar. Sinds Kubernetes 1.21+ voegt het cluster automatisch kubernetes.io/metadata.name: team-payments toe aan elke namespace, dus dat label hoef je niet handmatig te zetten.
Verwachte output:
namespace/team-payments created
namespace/team-payments labeled
Stap 2: pas een LimitRange toe
Een LimitRange beperkt individuele containers en pods, niet namespace-totalen. Het dient hier twee doelen: standaard resource-requests en -limits injecteren voor containers die ze weglaten, en een plafond instellen zodat een enkele container niet 64 GiB geheugen claimt in een namespace die op 20 GiB staat.
Pas de LimitRange voor de ResourceQuota toe. De volgorde doet ertoe. Wanneer er een ResourceQuota bestaat voor cpu of memory, weigert de admission controller elke pod die geen resource-requests specificeert. LimitRange-defaults worden geïnjecteerd voor de quotacheck, dus door de LimitRange eerst neer te zetten voorkom je dat bestaande workloads breken als de quota erbij komt.
# limitrange.yaml
apiVersion: v1
kind: LimitRange
metadata:
name: default-limits
namespace: team-payments
spec:
limits:
- type: Container
default: # geïnjecteerd als limits wanneer de container er geen heeft
cpu: 500m
memory: 512Mi
defaultRequest: # geïnjecteerd als requests wanneer de container er geen heeft
cpu: 100m
memory: 128Mi
max: # plafond per container
cpu: "4"
memory: 8Gi
min: # bodem per container
cpu: 50m
memory: 64Mi
kubectl apply -f limitrange.yaml
Verwachte output:
limitrange/default-limits created
Twee dingen om op te letten. Houd precies een LimitRange per namespace aan. Meerdere LimitRange-objecten in dezelfde namespace leiden tot niet-deterministische default-injectie, en uitzoeken welke defaults winnen is geen productieve manier om een middag door te brengen.
Daarnaast: wanneer default en defaultRequest identiek zijn (beiden bijvoorbeeld 500m CPU), krijgt elke pod die resource-specs weglaat Guaranteed QoS, wat invloed heeft op eviction-prioriteit bij geheugendruk op de node. Wil je Burstable QoS als standaard (de gebruikelijkere keuze voor generieke workloads), zet defaultRequest dan lager dan default. Voor een volledige uitleg van QoS-klassen, zie resource requests en limits.
De request-overschrijdt-limit-val
Als een developer requests.cpu: 700m specificeert zonder een limit, injecteert de LimitRange limits.cpu: 500m vanuit het default-veld. Validatie faalt vervolgens omdat de request (700m) de limit (500m) overschrijdt. De foutmelding ziet er zo uit:
spec.containers[0].resources.requests: Invalid value: "700m":
must be less than or equal to cpu limit
De developer heeft zelf geen limit gezet. De LimitRange deed dat. De oplossing: specificeer altijd zowel requests als limits in de pod-spec, of zorg dat de LimitRange default-waarden minstens zo hoog zijn als defaultRequest.
Stap 3: pas een ResourceQuota toe
ResourceQuota begrenst het totale resourceverbruik over de hele namespace. Het wordt afgedwongen bij admission door de ResourceQuota-admission controller (standaard ingeschakeld in de meeste distributies). Elk verzoek dat het verbruik boven de harde limiet zou duwen, wordt geweigerd met HTTP 403 Forbidden.
# resourcequota.yaml
apiVersion: v1
kind: ResourceQuota
metadata:
name: team-payments-quota
namespace: team-payments
spec:
hard:
requests.cpu: "10"
requests.memory: 20Gi
limits.cpu: "20"
limits.memory: 40Gi
pods: "100"
services: "10"
persistentvolumeclaims: "5"
kubectl apply -f resourcequota.yaml
Verwachte output:
resourcequota/team-payments-quota created
Controleer het verbruik direct:
kubectl describe resourcequota team-payments-quota -n team-payments
Je zou Used: 0 moeten zien voor elke resource. Als er al pods draaien in de namespace, wordt hun verbruik hier weergegeven maar ze worden niet verwijderd; quota is niet retroactief.
Een gedrag dat mensen verrast. Het aanmaken van een Deployment die de quota overschrijdt laat de Deployment zelf gewoon slagen. Het Deployment-object wordt aangemaakt. Alleen de Pod-creatie daarbinnen faalt. kubectl get deployment toont de resource, maar kubectl describe deployment of kubectl get events -n team-payments onthult de 403-fout. Controleer altijd events wanneer een deployment er gezond uitziet maar nul ready replicas heeft.
Stap 4: vergrendel netwerkverkeer
Zonder NetworkPolicy kunnen pods in team-payments elke pod in elke andere namespace bereiken, en andersom. Een default-deny-policy dicht dat gat.
# deny-all.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: team-payments
spec:
podSelector: {} # selecteert alle pods in de namespace
policyTypes:
- Ingress
- Egress
Dit blokkeert al het verkeer in beide richtingen. Inclusief DNS. Pas direct een DNS-egress-regel toe, anders breekt de namespace:
# allow-dns.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: team-payments
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
kubectl apply -f deny-all.yaml -f allow-dns.yaml
Verwachte output:
networkpolicy.networking.k8s.io/default-deny-all created
networkpolicy.networking.k8s.io/allow-dns created
Voeg van hier uit specifieke ingress- en egress-regels toe voor de flows die de workloads van de tenant daadwerkelijk nodig hebben. De NetworkPolicy-gids behandelt ingress-regels, egress-regels en namespace-selectors in detail.
Stille faalmodus. Als je cluster Flannel of kubenet gebruikt, bestaan deze NetworkPolicy-objecten in de API maar doen ze niets. Verifieer dat je CNI policies afdwingt voordat je erop vertrouwt. Een snelle test: deploy een pod in een andere namespace en probeer een service in team-payments te curlen. Als de verbinding lukt na het toepassen van de deny-all-policy, dan dwingt je CNI niets af.
Stap 5: beperk RBAC tot de namespace
RBAC is de fundamentele isolatielaag voor het control plane. Zonder RBAC kan elk teamlid met kubectl-toegang resources over namespaces heen lezen of wijzigen, en kan elk ander isolatiemechanisme op deze pagina omzeild worden.
# role.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
name: developer
namespace: team-payments
rules:
- apiGroups: ["apps"]
resources: ["deployments", "replicasets"]
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
- apiGroups: [""]
resources: ["pods", "pods/log", "services", "configmaps"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["pods/exec"]
verbs: ["create"] # sta kubectl exec toe voor debugging
# rolebinding.yaml
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
name: payments-developers
namespace: team-payments
subjects:
- kind: Group
name: team-payments-devs # uit je identity provider
apiGroup: rbac.authorization.k8s.io
roleRef:
kind: Role
name: developer
apiGroup: rbac.authorization.k8s.io
kubectl apply -f role.yaml -f rolebinding.yaml
Een namespace-scoped Role geeft nul rechten buiten team-payments. Het team kan geen pods listen in team-orders, geen secrets lezen in kube-system, en niet de ResourceQuota verwijderen die je net hebt aangemaakt (tenzij je expliciet die verb op resourcequotas-resources toekent, wat je dus niet moet doen).
Gebruik voor workload-pods aparte ServiceAccounts met automountServiceAccountToken: false tenzij de pod daadwerkelijk API-toegang nodig heeft. Het RBAC-artikel behandelt serviceaccountpatronen uitgebreid.
De quota beschermen tegen namespace-admins
Als een tenant de ingebouwde admin-ClusterRole gebonden heeft in zijn namespace, kan die de ResourceQuota verwijderen en alle limieten opheffen. Voorkom dit met een ValidatingAdmissionPolicy (stabiel sinds Kubernetes 1.30) of een Kyverno/Gatekeeper-policy die DELETE op ResourceQuota-objecten blokkeert voor niet-cluster-admins.
Stap 6: handhaaf Pod Security Standards
Pod Security Standards beperken wat een pod mag doen tijdens runtime: host-namespaces, privileged containers, capability-escalatie. Ze toepassen per namespace is een enkel label:
kubectl label namespace team-payments \
pod-security.kubernetes.io/enforce=baseline \
pod-security.kubernetes.io/warn=restricted
Dit dwingt het baseline-profiel af (blokkeert bekende privilege-escalatievectoren) en waarschuwt bij schendingen van het strengere restricted-profiel zodat het team geleidelijk kan migreren. Voor een volledig overzicht van alle drie de profielen en handhavingsmodi, zie de Pod Security Standards-gids.
Verwachte output:
namespace/team-payments labeled
Stap 7: controleer de volledige stack
Voer deze checks uit om te bevestigen dat elke laag op zijn plek zit:
# ResourceQuota toegepast
kubectl describe resourcequota -n team-payments
# LimitRange toegepast
kubectl describe limitrange -n team-payments
# NetworkPolicy-regels aanwezig
kubectl get networkpolicy -n team-payments
# RBAC-bindings beperkt tot namespace
kubectl get rolebinding -n team-payments
# Pod Security Standard-labels
kubectl get namespace team-payments --show-labels | grep pod-security
Deploy een testpod om de hele admission-pipeline te testen:
kubectl run test-pod --image=nginx:1.27 -n team-payments
kubectl describe pod test-pod -n team-payments
Controleer in de podbeschrijving dat Requests en Limits aanwezig zijn (geïnjecteerd door de LimitRange). Als de pod start, heeft je quota ruimte en staat het securityprofiel het toe. Ruim op:
kubectl delete pod test-pod -n team-payments
Rolling updates en quota-ruimte
Tijdens een RollingUpdate maakt Kubernetes nieuwe pods aan voordat oude worden beëindigd (gestuurd door maxSurge). Als de quota precies op het steady-state-verbruik staat, duwen de nieuwe pods het verbruik tijdelijk boven het plafond en loopt de update vast. De Deployment-controller probeert pod-creatie opnieuw met exponential backoff, en het kan tot 16 minuten duren voor de volgende poging.
Drie aanpakken:
- Voeg ruimte toe. Zet de quota 20-30% boven het steady-state-verbruik. Dit is de simpelste oplossing en werkt voor de meeste teams.
- Zet
maxSurge: 0. Er worden geen nieuwe pods gemaakt voordat oude stoppen, dus verbruik gaat nooit boven steady-state. De keerzijde: rolling updates veroorzaken korte downtime. - Automatiseer quota-bumps. Je CI/CD-pipeline verhoogt de quota tijdelijk voor de deploy en herstelt ze daarna. Meer bewegende delen, maar het houdt quota's strak buiten deploymentvensters.
Quotagebruik monitoren
Wanneer een namespace zijn quota raakt, falen nieuwe pods stil op Deployment-niveau. Proactief monitoren voorkomt onverwachte uitval.
# Snelle check over alle namespaces
kubectl get resourcequotas -A
# Events voor quotaschendingen in de namespace
kubectl get events -n team-payments --field-selector reason=FailedCreate
Als je Prometheus draait met kube-state-metrics, geeft de kube_resourcequota-metric verbruik versus harde limieten per namespace. Een handige alertdrempel:
kube_resourcequota{type="used"} / kube_resourcequota{type="hard"} > 0.85
Dit vuurt wanneer een quotadimensie 85% benutting bereikt, zodat het team tijd heeft om op te ruimen of een quotaverhoging aan te vragen. De Prometheus-monitoringgids behandelt de installatie van kube-state-metrics en alertingregels.
Soft vs. hard multi-tenancy
Alles op deze pagina is soft multi-tenancy: een enkel gedeeld control plane met namespace-isolatie via RBAC, NetworkPolicy, quota's en admission policies. De Kubernetes-documentatie noemt dit geschikt voor vertrouwde tenants binnen dezelfde organisatie.
Voor onvertrouwde tenants (SaaS-klanten, externe contractors, compliance-geïsoleerde workloads) zijn namespaces niet genoeg. Een gecompromitteerde pod kan nog steeds kernel-exploits proberen tegen de gedeelde host, en een verkeerd geconfigureerde ClusterRoleBinding kan toegang lekken over namespaces heen. Hard multi-tenancy betekent dat elke tenant zijn eigen API-server krijgt via virtuele clusters (vcluster) of dedicated fysieke clusters.
Het beslismoment is vertrouwen. Als de tenants interne teams zijn die niet bewust proberen uit hun boundary te ontsnappen, is soft multi-tenancy met alles op deze pagina een verdedigbare architectuur. Als een tenantbreuk onder geen enkele omstandigheid andere tenants mag bereiken, heb je harde isolatie nodig.
Wanneer escaleren
Als je alle lagen hebt toegepast en nog steeds cross-tenant-toegang of resource-uithongering ziet, verzamel dan deze informatie voordat je je platformteam inschakelt:
kubectl describe resourcequota -n <namespace>outputkubectl describe limitrange -n <namespace>outputkubectl get networkpolicy -n <namespace> -o yamlkubectl get events -n <namespace> --field-selector reason=FailedCreate- CNI-pluginnaam en -versie (
kubectl get pods -n kube-systemen controleer de CNI-daemonset) - Pod Security Standard-labels op de namespace (
kubectl get namespace <name> --show-labels) - Clusterversie (
kubectl version)