Module 05 · Platform foundation · 45 min · You are: Platform team
Guardrails and the namespace baseline
In this module
- You learn: How the platform gives every product the same namespace floor (quota, limits, network policy, access, Pod Security) from a single chart, and how policies stop anyone eroding it. Audit in dev, Enforce in prod.
- You do: Render the baseline for an example product and read it, propose the policies and the chart by pull request, approve the production gate, and check the policies on each cluster.
- You end with: Kyverno policies run on both clusters (Audit in dev, Enforce in prod) and the
product-namespacechart is ready to use. No product uses it yet: that is the next module.
Self-service only works with a consistent floor. Instead of each team writing its own namespace YAML (and forgetting the network policy), the platform renders one baseline from a few product fields, and policies make sure nobody erodes it with kubectl.
Open this module in the interactive course to track progress and switch tracks.
Change journey practised: Requested → Proposed → Validated → Previewed → Approved → Merged → Reconciled → Healthy
Concepts
- Baseline as a chart
charts/product-namespacerenders Namespace, ResourceQuota, LimitRange, three NetworkPolicies and a RoleBinding from the product's name, owner, cost center, Pod Security level and size.- Size profiles
- Teams pick
small,mediumorlarge. The platform owns what each means. More quota is a one-word pull request, reviewed. - Policy as code
- Kyverno:
protect-platform-baselineblocks changes to baseline objects by anyone but the platform;require-requests-limitsanddisallow-latest-tagare Audit in dev and Enforce in prod. - Same rules in CI
validate.shruns the prod policies with the Kyverno CLI against every product's manifests, so violations fail the pull request, not the sync.
Files to know
Open them in your editor or on GitHub; the bullets say what to look at.
charts/product-namespace/templates/namespace.yaml(reference, your repo): The namespace every product gets.charts/product-namespace/values.yaml(reference, your repo): What small, medium and large mean.charts/product-namespace/templates/rbac.yaml(reference, your repo): The owning team gets edit in its namespace, nothing more.- Built-in edit role, bound to the owner group L11–18
addons/kyverno-policies/base/protect-platform-baseline.yaml(reference, your repo): Only the platform may change the baseline.addons/kyverno-policies/overlays/dev/kustomization.yaml(reference, your repo): dev is relaxed with one patch.- Two workload rules switch to Audit in dev L11–18
charts/product-project/templates/appproject.yaml(reference, your repo): The fence around each product's Applications .
Steps
1. Propose the guardrails, and read what every product will get
Before pushing, render the baseline for the reference's example product. Read the output: Namespace with labels, quota, limits, three network policies, a RoleBinding.
cd ~/work/fleet-gitops && git switch main && git pull -q
git switch -c guardrails
Copy the policies, the baseline chart and the ApplicationSet that deploys the policies:
git checkout reference/main -- charts addons/kyverno-policies applicationsets/kyverno-policies-appset.yaml
scripts/set-repo-url.sh
Save the example product's declaration to a temporary file. A product is described by a short file like this; you will write one yourself in the next module.
git show reference/main:products/checkout/product.yaml > /tmp/checkout-product.yaml
Render the baseline. This turns that short file into everything a product namespace gets. Find: the Namespace and its labels, a ResourceQuota, a LimitRange, three NetworkPolicies, a RoleBinding.
helm template checkout charts/product-namespace -f /tmp/checkout-product.yaml --set environment=prod --set size=medium
Run the checks. Expect them to pass:
scripts/validate.sh
git add -A && git commit -m "Guardrails: Kyverno policies and the product namespace baseline"
git push -u origin HEAD
2. Merge and check the policies on each cluster
This is a prod change (it touches addons/*/overlays/prod), so production-gate asks for your approval first: approve it in the pull request's checks.
The policies on prod. Expect a few ClusterPolicies, all with READY true:
kubectl --context kind-prod get clusterpolicies
The same policy on dev. Expect Audit: it reports a violation but lets it through.
kubectl --context kind-dev get clusterpolicy disallow-latest-tag -o jsonpath='{.spec.validationFailureAction}'; echo
And on prod. Expect Enforce: it refuses the violation.
kubectl --context kind-prod get clusterpolicy disallow-latest-tag -o jsonpath='{.spec.validationFailureAction}'; echo
Break it on purpose: Deploy an unpinned image by hand
Someone with cluster access tries a quick test in a product namespace: nginx:latest, no resource requests, in dev and in prod.
You practise: reading an admission refusal, the difference between Audit and Enforce, and what a workload needs to pass the guardrails.
1. Break it
for ctx in kind-dev kind-prod; do
kubectl --context $ctx create namespace policy-test
kubectl --context $ctx label namespace policy-test fleet.gitops/type=product
kubectl --context $ctx -n policy-test run web --image=nginx:latest
done
2. What you see
dev: the pod is created, with warnings that name two policies. prod: the request is refused, naming the same two policies. Expected (by whoever ran it): nginx running in both.
3. Fix it yourself
Get the same web server admitted in prod's policy-test namespace without changing, disabling or excepting any policy. Then delete policy-test on both clusters.
Hint 1
Read the refusal word by word: it names each policy and what it wants. kubectl --context kind-prod get clusterpolicies lists them.
Hint 2
One wants a pinned image tag; the other wants cpu and memory requests and a memory limit. kubectl run has no resource flags, but --overrides takes the pod spec as JSON.
The fix
kubectl --context kind-prod -n policy-test run web --image=nginx:1.27.3 \
--overrides='{"spec":{"containers":[{"name":"web","image":"nginx:1.27.3","resources":{"requests":{"cpu":"50m","memory":"64Mi"},"limits":{"memory":"128Mi"}}}]}}'
Expect web to be Running:
kubectl --context kind-prod -n policy-test get pod web
Clean up:
kubectl --context kind-dev delete namespace policy-test
kubectl --context kind-prod delete namespace policy-test
After you fixed it: the five questions
- What failed?
- A workload without a pinned image or resource requests tried to enter a product namespace.
- How was it detected?
- Kyverno at admission: dev admitted it with warnings (Audit), prod refused it (Enforce). For changes that come through Git, CI catches it first.
- Who responds?
- Whoever deployed it; the platform team owns the policies.
- How is it corrected?
- Pin the tag and set requests and a memory limit.
- How is it prevented?
- Policies in Audit on dev and Enforce on prod: teams see warnings early and cannot ship the problem to prod.
Quiz
- A team needs more CPU quota in prod. What is the right change?
- A pull request changing
sizefor prod in their product.yaml - Run kubectl edit resourcequota in prod, then copy the new value into Git afterwards
- Ask Kyverno to raise it, since the policy engine owns every quota in the cluster
- A pull request changing
- Why are some policies Audit in dev and Enforce in prod?
- Kyverno cannot enforce policies in dev, so Audit is the only mode available there
- Teams can experiment in dev and still see warnings; prod gets the hard rule
- Prod clusters are faster, so they can afford the cost of checking every request
- Why is the namespace annotated Prune=false?
- It speeds up syncs, because Argo CD skips namespaces when it looks for objects to prune
- Kyverno requires the annotation, or it rejects the namespace in the admission step
- Removing a product from Git must not silently delete its data
Answers
- A pull request changing
sizefor prod in their product.yaml Quota is part of the platform baseline. The policy blocks the kubectl route on purpose. - Teams can experiment in dev and still see warnings; prod gets the hard rule Same policies, different strictness per environment, through Kustomize overlays.
- Removing a product from Git must not silently delete its data Deleting a namespace is a deliberate, reviewed platform step.
Field note
Older versions of this course generated network policies with Kyverno from namespace labels. It worked, but nobody could see the policies in Git. A chart you can helm template is easier to review and to explain.