Module 08 · Share the cluster · 60 min · You are: Platform and product
Capstone: a third team, on your own
In this module
- You learn: Nothing new on purpose: everything from modules 00 to 07 again, for a team with one requirement you have not met (traffic allowed from one other team only), with nobody telling you the order.
- You do: Build team-payments' tenant and app from a list of requirements, prove each one with a command, write the onboarding checklist for the next team, and clean up.
- You end with: A third tenant,
payments-dev, runspaymentswith 3 replicas, its own quota, RBAC, policies and Pod Security. checkout can call it; catalog and the ingress controller cannot. Your repo hasdocs/onboarding.md. The cluster is deleted; your repo keeps everything.
A platform team is judged by what happens when the next team arrives. You have built two tenants by following steps. The third one has no steps: a list of requirements, the acceptance checks, and the files you already wrote. One requirement is new, and it has a trap that the network module already hinted at.
Open this module in the interactive course to track progress.
Concepts
- The tenant kit
- Namespace with Pod Security labels, quota, LimitRange, RBAC, network policies: the platform team's half. Deployment, Service, ConfigMap, budget: the team's half.
- Order matters
- Namespace first, then quota and defaults, then RBAC, then the app, then the network policies last, testing after each. A policy applied before the app works hides which one broke it.
- An internal service
- No Ingress, a ClusterIP Service, and an ingress policy that names exactly which pods may call it.
- Both ends, again
- Allowing checkout into payments is half the work. checkout's own egress policy must also let the call out.
Files to know
Open them in your editor or on GitHub; the bullets say what to look at.
docs/onboarding.md(reference, your repo): The reference checklist. Write your own before you compare.tenants/payments/networkpolicy.yaml(reference, your repo): Do not open this before you are done.- One list item with both selectors: pods named checkout AND in checkout-dev L16–33
Steps
1. Onboard team-payments (Challenge)
team-payments runs an internal service, payments. It takes card data, so nobody reaches it from outside the cluster, and inside only checkout's pods may call it. Build its tenant and its app the way you built the other two. Do not open tenants/payments or apps/payments in the reference until you are done.
Requirements. (1) Namespace payments-dev, label team: payments, Pod Security restricted enforced. (2) Quota team-quota: room for 3 pods at 100m CPU plus one more during a rollout, and no more than 600m; at most 6 pods; no NodePort or LoadBalancer Services. A LimitRange with defaults. (3) Group team-payments may read the namespace; a ServiceAccount deployer may deploy into it; the workload runs as ServiceAccount payments with no token mounted. (4) payments: podinfo 6.17.0, 3 replicas, sized, three probes, spread over nodes, the restricted security context, banner payments (dev) from an immutable ConfigMap, a Service, a budget that lets a node be drained. No Ingress. (5) Network: payments-dev denies all incoming traffic except from pods labelled app.kubernetes.io/name: checkout in checkout-dev, on the pod port. Egress: DNS and its own namespace only. (6) And the part nobody mentions: checkout's own egress rules must let it call payments.
Done when:
kubectl -n checkout-dev exec deployment/checkout -- wget -qO- -T 3 http://payments.payments-dev/api/info | grep messageprintspayments (dev)- The same call from
catalog-dev(kubectl -n catalog-dev exec deployment/catalog -- ...) ends indownload timed out curl -s -o /dev/null -w '%{http_code}\n' http://payments.localtest.me:8090/prints404: no route from outsidekubectl auth can-i list pods -n payments-dev --as=dev --as-group=team-paymentssaysyes;get secretsinpayments-devsaysno;list pods -n checkout-devsaysnokubectl auth can-i list pods -n payments-dev --as=alice --as-group=team-checkoutsaysno- payments reads its settings from an immutable ConfigMap
payments-settings-v1, versioned in the name like checkout's kubectl -n payments-dev describe resourcequota team-quotashowsrequests.cpu300mused andpods3kubectl label --dry-run=server --overwrite namespace payments-dev pod-security.kubernetes.io/enforce=restrictedprints noWarning:kubectl drain k8s-worker --ignore-daemonsets --delete-emptydir-datafinishes whilepaymentskeeps 2 or more ready pods; thenkubectl uncordon k8s-workerkubectl -n payments-dev exec deployment/payments -- ls /var/run/secrets/kubernetes.io/serviceaccountsaysNo such file or directory
Hint 1
Order matters: namespace, then quota and limits, then RBAC, then the app, then policies last, testing after each. Copy catalog's folders and change names, like module 05.
Hint 2
The call from checkout times out with payments' ingress rule in place? Both ends must agree: look at restrict-egress in tenants/checkout/networkpolicy.yaml. And an ingress rule with namespaceSelector and podSelector in one list item means both must match.
One solution
Compare your folders with the reference. Every difference should be one you can explain:
cd ~/platformengr/k8s-platform && diff -r ~/platformengr/kubernetes-platform/tenants/payments tenants/payments; diff -r ~/platformengr/kubernetes-platform/apps/payments apps/payments
If you are stuck, copy them in and read them:
cp -r ~/platformengr/kubernetes-platform/tenants/payments tenants/ && cp -r ~/platformengr/kubernetes-platform/apps/payments apps/
And the line most people miss: checkout's way out to payments.
Add to ~/platformengr/k8s-platform/tenants/checkout/networkpolicy.yaml at the end of restrict-egress, after the - to: - podSelector: {} rule
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: payments-dev
podSelector:
matchLabels:
app.kubernetes.io/name: payments
ports:
- protocol: TCP
port: 9898
cd ~/platformengr/k8s-platform && git add -A && git commit -qm "Onboard team-payments; checkout may call payments" && git push -q
Apply in order: the namespace, the tenant, the app, then checkout's policy.
kubectl apply -f tenants/payments/namespace.yaml
kubectl apply -f tenants/payments/
kubectl apply -f apps/payments/
kubectl -n payments-dev rollout status deployment/payments --timeout=120s
kubectl apply -f tenants/checkout/networkpolicy.yaml
2. Write the onboarding checklist
The next team should not depend on your memory. Write what the platform team creates for a new team, what the team brings, in which order, and the command that proves each item with the answer you expect. Fill in the two lists.
Done when:
- A
## Platform teamsection and a## Product teamsection - Each item names the file it creates and a command that proves it
- At least one
kubectl auth can-iline whose expected answer isno
Create ~/platformengr/k8s-platform/docs/onboarding.md
# Onboarding a new team
What the platform team creates, what the team brings, in which order, and the command that
proves each item (with the answer you expect).
## Platform team
1. ...
## Product team
1. ...
cd ~/platformengr/k8s-platform && git add -A && git commit -qm "docs: onboarding checklist" && git push -q
3. Clean up, and what comes next
Delete the cluster. Your repo is what you keep: a new cluster plus your repo, applied in order, gives you this platform back, except the one Secret listed in docs/secrets.md.
kind delete cluster --name k8s
Simplified for the course
Three teams were onboarded by copying a folder and changing a name. That works for three. For thirty, the folder is generated from one small file per team: see Fleet GitOps.
Each time you deleted a file you also had to delete the object by hand, and every apply was yours to remember. That is the gap Argo CD closes.
Quiz
- payments accepts traffic from checkout's pods, yet the call from checkout times out. What is the first thing to check?
- The payments Service's type, which must be NodePort for calls across namespaces
- checkout's egress rules: both the caller's egress and the callee's ingress must allow the connection
- The namespace's Pod Security level, which blocks traffic from pods in other namespaces
- payments has 3 replicas. Which budget lets the platform team drain a node?
maxUnavailable: 1: one pod may be away, two keep servingminAvailable: 3: all pods must stay, so the drain waits until it is safemaxUnavailable: 0: nothing may be away, which is the safest setting for card data
- In what order do you build a new tenant?
- Network policies first, so that nothing is ever open, then the app, then the namespace
- The app first, to show the team progress, then the quota and RBAC around it
- Namespace, quota and defaults, RBAC, the app, then network policies, testing after each
Answers
- checkout's egress rules: both the caller's egress and the callee's ingress must allow the connection A connection is checked at both ends.
maxUnavailable: 1: one pod may be away, two keep serving A budget must allow at least one disruption, or every drain hangs.- Namespace, quota and defaults, RBAC, the app, then network policies, testing after each Each layer is tested before the next one can hide what broke.
Field note
If you finished this without the solution, you can onboard a team onto a shared cluster. If you needed it, that is normal: delete your payments folders, recreate the cluster from your repo next week, and do it again.