Workshop-Wiki · Englisch

Model Solution: Kubernetes Assessment 02 – Intermediate

Difficulty level: Intermediate Language: English Type: model solution / expected answer guide

Workshop-Beispiele als ZIP
Lernstand in der Web-App

Kapitel als gelesen markieren, Lesefortschritt sehen und Praxismodule abhaken: Diese Funktionen stehen nach Aktivierung der optionalen Web-App zur Verfügung. Der Workshop-Fortschritt ergibt sich automatisch aus den Kapiteln aller Dokumentseiten. Deine Auswahl bleibt auf diesem Gerät; es gibt kein Konto und keinen Serverabgleich. Eine Installation ist nicht erforderlich. Beim Widerruf in den Datenschutz-Einstellungen wird der Lernstand gelöscht.

Lesefortschritt0%
0%
0 / 8 Kapitel gelesen
Weitere SprachfassungDeutsch ↗Griechisch ↗

Model Solution: Kubernetes Assessment 02 – Intermediate

Difficulty level: Intermediate
Language: English
Type: model solution / expected answer guide

This file is the matching solution for 02-intermediate-kubernetes-assessment-EN.md.
The manifests are lab examples. Tool and cluster dependencies such as StorageClass, Ingress Controller, Gateway API or NetworkPolicy support must be checked in the respective lab.


1. Usage Notes

Recommended namespace:

bash
export NS=exam-intermediate

A complete solution does not have to use exactly the same images as long as they are safe, demonstrate the required Kubernetes concepts and can be validated reproducibly. Where a tool is unavailable, a technically correct substitute assessment is acceptable.


01 / 08

2. Part A: Model Answers to Knowledge Questions

1. Deployment, StatefulSet, Job and CronJob

Resource Use case
Deployment continuously running stateless services with rollout and rollback
StatefulSet workloads with stable identity and stable PVC assignment
Job one-time or finite task, for example a migration
CronJob scheduled recurring jobs

A Deployment is unsuitable for one-time migrations because it tries to keep processes alive continuously.

2. Init Containers, Sidecars and Ephemeral Containers

Init Containers run before the app containers and must complete successfully. Sidecars run in parallel with the app and share network, volumes and Pod lifecycle. Ephemeral Containers are added afterwards for debugging and are not part of the normal deployment model.

3. Startup, Readiness and Liveness Probe

Probe Effect
Startup Probe protects slow starts; Readiness/Liveness are held back until it succeeds
Readiness Probe decides whether the Pod receives Service traffic
Liveness Probe decides whether kubelet restarts the container

4. Risk of aggressive Liveness Probes

An overly aggressive Liveness Probe can restart healthy containers under load or during slow startup. That can create crash loops, unnecessary downtime and amplified load.

5. Effect of Requests on scheduling and HPA

CPU and Memory Requests are planning values for the scheduler. CPU Requests are also the basis for CPU-based HPA utilization. Missing or unrealistic Requests lead to poor placement, misleading autoscaling and noisy-neighbor problems.

6. QoS classes

QoS Condition Effect
Guaranteed CPU and Memory Requests equal Limits for all containers highest stability under resource pressure
Burstable Requests are set but not identical to Limits medium priority
BestEffort no Requests and no Limits evicted first under resource pressure

7. LimitRange and ResourceQuota

LimitRange sets defaults and limits for individual Pods or containers in a namespace. ResourceQuota limits the total consumption of a namespace, for example Pods, CPU, Memory or PVCs.

8. nodeSelector, affinity, taints and tolerations

nodeSelector is a simple hard node selection. Affinity is more expressive and can define hard or soft rules. Taints mark Nodes so that Pods without a matching toleration are not scheduled there. Tolerations allow placement on tainted Nodes, but they do not force it by themselves.

9. Why a Pod can remain Pending

Typical causes are insufficient resources, non-matching node selectors, affinity rules, missing tolerations for taints, missing PVCs or StorageClass problems. Diagnosis is done through kubectl describe pod and events.

10. Service, EndpointSlice, Pod readiness and DNS

A Service defines a stable address and a selector. EndpointSlices contain the actually reachable Pod endpoints. Only ready Pods are normally included for Service traffic. CoreDNS resolves Service names.

11. DNS egress under default deny

With egress default deny, DNS queries are blocked unless explicitly allowed. Without DNS egress, Pods cannot resolve Service names such as backend.

12. Ingress resource and Ingress Controller

The Ingress resource describes HTTP routing rules. An Ingress Controller implements these rules as proxy or load-balancer configuration. Without a controller, the resource has little practical effect.

13. PV, PVC, StorageClass and CSI

Term Role
PV provisioned storage object in the cluster
PVC storage request by a Pod or workload
StorageClass describes dynamic provisioning and parameters
CSI interface/plugin for storage systems

14. Why hostPath is not a production solution

hostPath mounts a Node filesystem directly. It is not portable, binds data to a Node and can create security risks. It is practical in labs, but not a generic production storage model.

15. Checks for PVC Pending, Service without Endpoints and CreateContainerConfigError

Error pattern Check
PVC Pending kubectl describe pvc, StorageClass, provisioner, events
Service without Endpoints Pod Labels, Service selector, readiness, EndpointSlices
CreateContainerConfigError missing ConfigMap/Secret, wrong key, invalid env/volume reference

02 / 08

3. Part B: Architecture Decision

Model for architecture-decision.md:

markdown
# Architecture Decision – Shop Lab

| Requirement | Decision | Justification |
| --- | --- | --- |
| HTTP frontend runs continuously and is updated through rolling updates | Deployment | stateless, replicas, rollout, rollback |
| Backend API with health checks and resources | Deployment + Service | continuously running internal service, Readiness for traffic, Liveness for real defects |
| Migration once per release | Job | successful completion is the target state |
| Cleanup hourly without parallel jobs | CronJob with `concurrencyPolicy: Forbid` | scheduled jobs, no overlap |
| Small stateful workload with data after Pod recreation | StatefulSet with `volumeClaimTemplates` | stable Pod identity and PVC assignment |
| External reachability | Ingress or Gateway API | resource requires a matching controller |
| Protect namespace from overuse | LimitRange + ResourceQuota | defaults and total limits |
| Frontend may reach only backend and DNS | NetworkPolicy default deny + allow rules | allow ingress/egress deliberately |

03 / 08

4. Part C: Practical Solution

C1: Namespace, LimitRange and ResourceQuota

namespace.yml

yaml
apiVersion: v1
kind: Namespace
metadata:
  name: exam-intermediate
  labels:
    environment: exam
    level: intermediate

resources/limitrange.yml

yaml
apiVersion: v1
kind: LimitRange
metadata:
  name: default-container-limits
  namespace: exam-intermediate
spec:
  limits:
    - type: Container
      defaultRequest:
        cpu: 100m
        memory: 128Mi
      default:
        cpu: 500m
        memory: 512Mi
      max:
        cpu: "2"
        memory: 2Gi

resources/resourcequota.yml

yaml
apiVersion: v1
kind: ResourceQuota
metadata:
  name: exam-quota
  namespace: exam-intermediate
spec:
  hard:
    pods: "10"
    requests.cpu: "2"
    requests.memory: 2Gi
    limits.cpu: "4"
    limits.memory: 4Gi

Apply:

bash
kubectl apply -f namespace.yml
kubectl apply -f resources/limitrange.yml
kubectl apply -f resources/resourcequota.yml
kubectl describe limitrange default-container-limits -n exam-intermediate
kubectl describe resourcequota exam-quota -n exam-intermediate

Quota violation example:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: too-large
  namespace: exam-intermediate
spec:
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
      resources:
        requests:
          cpu: "3"
          memory: 3Gi
        limits:
          cpu: "3"
          memory: 3Gi

Expected: The API Server rejects the Pod because of the ResourceQuota.

C2: Frontend and backend with probes

workloads/backend.yml

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: backend
  namespace: exam-intermediate
  labels:
    app: backend
    component: api
    environment: exam
spec:
  replicas: 2
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
        component: api
        environment: exam
    spec:
      containers:
        - name: backend
          image: registry.k8s.io/e2e-test-images/agnhost:2.53
          args: ["netexec", "--http-port=8080"]
          ports:
            - name: http
              containerPort: 8080
          startupProbe:
            httpGet:
              path: /healthz
              port: 8080
            failureThreshold: 30
            periodSeconds: 2
          readinessProbe:
            httpGet:
              path: /healthz
              port: 8080
            periodSeconds: 5
          livenessProbe:
            httpGet:
              path: /healthz
              port: 8080
            initialDelaySeconds: 15
            periodSeconds: 10
          resources:
            requests:
              cpu: 100m
              memory: 128Mi
            limits:
              cpu: 500m
              memory: 256Mi

workloads/frontend.yml

yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
  namespace: exam-intermediate
  labels:
    app: frontend
    component: web
    environment: exam
spec:
  replicas: 1
  selector:
    matchLabels:
      app: frontend
  template:
    metadata:
      labels:
        app: frontend
        component: web
        environment: exam
    spec:
      containers:
        - name: frontend
          image: curlimages/curl:8.8.0
          command: ["sh", "-c", "sleep 3600"]
          readinessProbe:
            exec:
              command: ["sh", "-c", "command -v curl >/dev/null"]
            periodSeconds: 10
          livenessProbe:
            exec:
              command: ["sh", "-c", "test -d /tmp"]
            initialDelaySeconds: 10
            periodSeconds: 20
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 200m
              memory: 128Mi

networking/services.yml

yaml
apiVersion: v1
kind: Service
metadata:
  name: backend
  namespace: exam-intermediate
spec:
  selector:
    app: backend
  ports:
    - name: http
      port: 8080
      targetPort: 8080
---
apiVersion: v1
kind: Service
metadata:
  name: frontend
  namespace: exam-intermediate
spec:
  selector:
    app: frontend
  ports:
    - name: http
      port: 80
      targetPort: 8080

Apply and check:

bash
kubectl apply -f workloads/backend.yml
kubectl apply -f workloads/frontend.yml
kubectl apply -f networking/services.yml
kubectl rollout status deployment/backend -n exam-intermediate
kubectl rollout status deployment/frontend -n exam-intermediate
kubectl get deploy,pod,svc,endpoints,endpointslices -n exam-intermediate

Readiness versus Liveness:

  • Readiness removes Pods from Service traffic without restarting them.
  • Liveness restarts containers and must only detect real, non-self-healing defects.

C3: Job and CronJob

workloads/migration-job.yml

yaml
apiVersion: batch/v1
kind: Job
metadata:
  name: migration
  namespace: exam-intermediate
spec:
  backoffLimit: 2
  template:
    spec:
      restartPolicy: OnFailure
      containers:
        - name: migration
          image: busybox:1.36
          command: ["sh", "-c", "echo Migration starts; sleep 3; echo Migration successful"]
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 200m
              memory: 128Mi

workloads/cleanup-cronjob.yml

yaml
apiVersion: batch/v1
kind: CronJob
metadata:
  name: cleanup
  namespace: exam-intermediate
spec:
  schedule: "0 * * * *"
  concurrencyPolicy: Forbid
  successfulJobsHistoryLimit: 3
  failedJobsHistoryLimit: 1
  jobTemplate:
    spec:
      template:
        spec:
          restartPolicy: OnFailure
          containers:
            - name: cleanup
              image: busybox:1.36
              command: ["sh", "-c", "date; echo cleanup done"]
              resources:
                requests:
                  cpu: 50m
                  memory: 64Mi
                limits:
                  cpu: 200m
                  memory: 128Mi

Check:

bash
kubectl apply -f workloads/migration-job.yml
kubectl apply -f workloads/cleanup-cronjob.yml
kubectl get job,cronjob,pod -n exam-intermediate
kubectl logs job/migration -n exam-intermediate

C4: Scheduling check

Label a node:

bash
NODE=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
kubectl label node "$NODE" exam-role=apps
kubectl get nodes --show-labels | grep exam-role

Pod with matching selector:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: scheduled-ok
  namespace: exam-intermediate
spec:
  nodeSelector:
    exam-role: apps
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
      resources:
        requests:
          cpu: 50m
          memory: 64Mi
        limits:
          cpu: 100m
          memory: 128Mi

Pod with non-matching selector:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: scheduled-pending
  namespace: exam-intermediate
spec:
  nodeSelector:
    exam-role: does-not-exist
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
      resources:
        requests:
          cpu: 50m
          memory: 64Mi
        limits:
          cpu: 100m
          memory: 128Mi

Taint and toleration:

bash
kubectl taint node "$NODE" dedicated=apps:NoSchedule

Pod with toleration:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: tolerated
  namespace: exam-intermediate
spec:
  nodeSelector:
    exam-role: apps
  tolerations:
    - key: dedicated
      operator: Equal
      value: apps
      effect: NoSchedule
  containers:
    - name: app
      image: busybox:1.36
      command: ["sh", "-c", "sleep 3600"]
      resources:
        requests:
          cpu: 50m
          memory: 64Mi
        limits:
          cpu: 100m
          memory: 128Mi

Validation:

bash
kubectl get pods -o wide -n exam-intermediate
kubectl describe pod scheduled-pending -n exam-intermediate
kubectl get events -n exam-intermediate --sort-by=.metadata.creationTimestamp

Rollback node changes:

bash
kubectl taint node "$NODE" dedicated=apps:NoSchedule-
kubectl label node "$NODE" exam-role-

C5: NetworkPolicy default deny plus allowances

networking/networkpolicies.yml

yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny
  namespace: exam-intermediate
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns-egress
  namespace: exam-intermediate
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-egress-to-backend
  namespace: exam-intermediate
spec:
  podSelector:
    matchLabels:
      app: frontend
  policyTypes:
    - Egress
  egress:
    - to:
        - podSelector:
            matchLabels:
              app: backend
      ports:
        - protocol: TCP
          port: 8080
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backend-ingress-from-frontend
  namespace: exam-intermediate
spec:
  podSelector:
    matchLabels:
      app: backend
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              app: frontend
      ports:
        - protocol: TCP
          port: 8080

Check:

bash
kubectl apply -f networking/networkpolicies.yml
kubectl get networkpolicy -n exam-intermediate
FRONTEND=$(kubectl get pod -l app=frontend -n exam-intermediate -o jsonpath='{.items[0].metadata.name}')
kubectl exec "$FRONTEND" -n exam-intermediate -- nslookup backend
kubectl exec "$FRONTEND" -n exam-intermediate -- curl -s --max-time 5 http://backend:8080/hostname

CNI classification:

bash
kubectl get pods -A | grep -Ei 'calico|cilium|weave|antrea|flannel|kindnet'

A good solution documents whether the concrete CNI enforces NetworkPolicies. For example, Flannel without a policy extension is evaluated differently from Calico or Cilium.

C6: Ingress or Gateway API

Check controller availability:

bash
kubectl get ingressclass
kubectl get gatewayclass 2>/dev/null || true
kubectl get pods -A | grep -Ei 'ingress|gateway|nginx|traefik|envoy|haproxy'

Example networking/ingress-or-gateway.yml as Ingress

yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: shop
  namespace: exam-intermediate
spec:
  rules:
    - host: shop.localtest.me
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: backend
                port:
                  number: 8080

Check:

bash
kubectl apply -f networking/ingress-or-gateway.yml
kubectl describe ingress shop -n exam-intermediate
kubectl get ingress shop -n exam-intermediate

If no controller exists:

markdown
No Ingress or Gateway Controller is installed in the lab. The resource can be accepted by the API Server, but it has no practical routing effect because no controller translates it into proxy or load-balancer configuration.

Gateway API example, if Gateway API is available

yaml
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: shop
  namespace: exam-intermediate
spec:
  parentRefs:
    - name: shared-gateway
      namespace: gateway-system
  hostnames:
    - shop.localtest.me
  rules:
    - backendRefs:
        - name: backend
          port: 8080

C7: PVC and StatefulSet

Check StorageClasses:

bash
kubectl get storageclass

storage/statefulset.yml

yaml
apiVersion: v1
kind: Service
metadata:
  name: data-demo
  namespace: exam-intermediate
spec:
  clusterIP: None
  selector:
    app: data-demo
  ports:
    - name: dummy
      port: 80
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: data-demo
  namespace: exam-intermediate
spec:
  serviceName: data-demo
  replicas: 1
  selector:
    matchLabels:
      app: data-demo
  template:
    metadata:
      labels:
        app: data-demo
    spec:
      containers:
        - name: app
          image: busybox:1.36
          command: ["sh", "-c", "while true; do sleep 3600; done"]
          volumeMounts:
            - name: data
              mountPath: /data
          resources:
            requests:
              cpu: 50m
              memory: 64Mi
            limits:
              cpu: 100m
              memory: 128Mi
  volumeClaimTemplates:
    - metadata:
        name: data
      spec:
        accessModes: ["ReadWriteOnce"]
        resources:
          requests:
            storage: 1Gi

Apply and validate:

bash
kubectl apply -f storage/statefulset.yml
kubectl rollout status statefulset/data-demo -n exam-intermediate
POD=data-demo-0
kubectl exec "$POD" -n exam-intermediate -- sh -c 'echo persistent > /data/test.txt'
kubectl delete pod "$POD" -n exam-intermediate
kubectl wait --for=condition=Ready pod/data-demo-0 -n exam-intermediate --timeout=90s
kubectl exec data-demo-0 -n exam-intermediate -- cat /data/test.txt

Expected: The file still exists after Pod recreation if dynamic provisioning and storage behavior support it.

C8: Helm and Kustomize rendering

Kustomize example:

text
packaging/kustomize/base/deployment.yml
packaging/kustomize/base/service.yml
packaging/kustomize/base/kustomization.yml
packaging/kustomize/overlays/dev/kustomization.yml

Render:

bash
kubectl kustomize packaging/kustomize/overlays/dev > packaging/kustomize-output.md

Helm example:

bash
helm create packaging/shop-demo
helm template shop-demo packaging/shop-demo > packaging/helm-output.md

Comparison:

Criterion Kustomize Helm
Model patch and overlay existing YAML template-based chart package
Reviewability rendered YAML is often close to source rendered YAML must be reviewed carefully
Parameterization overlays and patches values files and templates
Error risk patch drift and overlay complexity template logic and values mistakes

04 / 08

5. Part D: Troubleshooting Course

Error pattern Symptom Diagnosis Cause Fix Validation
wrong nodeSelector Pod remains Pending kubectl describe pod, events no Node matches the selector correct selector or label Node Pod scheduled
wrong image tag ImagePullBackOff Pod events show pull failure tag/image does not exist set valid image tag Pod running
wrong startup command CrashLoopBackOff kubectl logs --previous, exit code process exits fix command/config restarts stop
wrong StorageClass PVC remains Pending kubectl describe pvc, events no provisioner/class use valid StorageClass or omit if default exists PVC Bound
wrong Service selector Service has no Endpoints labels, selector, EndpointSlices selector mismatch correct selector Endpoints populated

05 / 08

6. Expected Submission Structure

text
02-intermediate-submission/
  answers.md
  architecture-decision.md
  workloads/
    frontend.yml
    backend.yml
    migration-job.yml
    cleanup-cronjob.yml
  resources/
    limitrange.yml
    resourcequota.yml
  networking/
    services.yml
    networkpolicies.yml
    ingress-or-gateway.yml
  storage/
    statefulset.yml
  packaging/
    kustomize-output.md
    helm-output.md
  troubleshooting.md
  evidence.md
  cleanup.md

06 / 08

7. Cleanup

Preferred cleanup:

bash
kubectl delete namespace exam-intermediate

If node labels or taints were changed, always roll them back explicitly:

bash
NODE=$(kubectl get nodes -o jsonpath='{.items[0].metadata.name}')
kubectl taint node "$NODE" dedicated=apps:NoSchedule- || true
kubectl label node "$NODE" exam-role- || true

Verify:

bash
kubectl get namespace exam-intermediate
kubectl get nodes --show-labels
kubectl describe node "$NODE" | grep -i taint -A2

07 / 08

8. Grading and Examiner Notes

A strong solution:

  • selects workload resources based on lifecycle and operating model,
  • separates Readiness, Liveness and Startup Probe cleanly,
  • sets meaningful Requests and Limits,
  • demonstrates quota rejection,
  • diagnoses scheduling via events rather than guessing,
  • treats NetworkPolicy and DNS separately,
  • understands that Ingress/Gateway requires a controller,
  • demonstrates or correctly qualifies persistent storage behavior,
  • renders Helm and Kustomize before applying resources,
  • documents troubleshooting as symptom, diagnosis, cause, fix and validation.

Common deductions:

Issue Deduction
Deployment used for a one-time migration medium
Readiness and Liveness explained as equivalent high
NetworkPolicy misses DNS egress medium to high
no CNI enforcement note medium
node taint not rolled back high
PVC behavior asserted without evidence medium
no rendered Helm/Kustomize output low to medium
08 / 08

K8S · Orthevs Academy

31 Module als durchgängiger Lernpfad.

Die Reihenfolge bleibt unverändert: von der Laborgrundstruktur über fachliche Grundlagen und Betriebsmodelle bis zum abschließenden Troubleshooting. Die technischen Verzeichnisnamen werden bewusst beibehalten.

Orthevs – Engineering als Grundlage für das, was als Nächstes kommt.

Orthevs als App

Orthevs installieren

Die Orthevs Web-App kann auf unterstützten Mobilgeräten und Desktop-Browsern installiert werden.

iPhone und iPad

Öffnen Sie Orthevs in Safari. Tippen Sie auf Teilen, dann auf ‚Zum Home-Bildschirm‘. Aktivieren Sie gegebenenfalls ‚Als Web-App öffnen‘ und tippen Sie auf ‚Hinzufügen‘.

Android

Für die Web-App öffnen Sie das Browsermenü und wählen ‚App installieren‘ oder ‚Zum Startbildschirm hinzufügen‘. Bestätigen Sie die Installation.

Desktop-Browser

Öffnen Sie das Browsermenü oder das Installationssymbol in der Adressleiste und wählen Sie ‚App installieren‘ beziehungsweise ‚Zum Dock hinzufügen‘.

Die Web-App-Installation hängt vom Browser ab. Falls kein Installationsdialog erscheint, nutzen Sie die angezeigten Schritte im Browser.