Creating Your First Challenge
Step-by-step guide to building a Kubeasy challenge from scratch.
This guide walks you through creating a complete Kubeasy challenge. By the end, you'll have a working challenge ready for submission.
Before You Start
Make sure you have:
- kubectl installed
- Kind for local Kubernetes
- Kubeasy CLI installed
- Basic familiarity with Kubernetes
Step 1: Design Your Challenge
Before writing any YAML, answer these questions:
-
What Kubernetes concept does this teach?
- Example: Resource limits, RBAC, probes
-
What's the broken scenario?
- Example: Pod gets OOMKilled due to low memory limit
-
How do we verify it's fixed?
- Example: Pod runs without crash events
-
Is this realistic?
- Does this problem occur in production?
Example Design
Let's create a challenge about resource limits:
- Concept: Kubernetes resource management
- Problem: Pod keeps crashing due to insufficient memory
- Success: Pod runs stably without OOM events
- Realistic: Yes - very common in production
Step 2: Create the Directory Structure
# Clone the challenges repo
git clone https://github.com/kubeasy-dev/challenges.git
cd challenges
# Create your challenge folder
mkdir -p memory-pressure/manifests
mkdir -p memory-pressure/policiesYou should have:
memory-pressure/
├── manifests/
└── policies/Step 3: Write challenge.yaml
Create memory-pressure/challenge.yaml:
title: Memory Pressure
description: |
A data processing application keeps crashing.
The ops team says it worked fine in development.
theme: resources-scaling
difficulty: easy
estimated_time: 15
initial_situation: |
A data processing pod is deployed.
It starts but crashes within seconds.
The pod enters CrashLoopBackOff state.
objective: |
Make the application run stably.
Understand why Kubernetes keeps killing the pod.
validations:
- key: pod-ready
title: "Pod Running"
description: "The pod must be in Ready state"
order: 1
type: status
spec:
target:
kind: Pod
labelSelector:
app: data-processor
conditions:
- type: Ready
status: "True"
- key: no-oom
title: "Stable Operation"
description: "No crash or eviction events"
order: 2
type: event
spec:
target:
kind: Pod
labelSelector:
app: data-processor
forbiddenReasons:
- "OOMKilled"
- "Evicted"
sinceSeconds: 300
- key: low-restarts
title: "Low Restart Count"
description: "Pod must not restart excessively"
order: 3
type: metrics
spec:
target:
kind: Pod
labelSelector:
app: data-processor
metricChecks:
- metric: restartCount
operator: LessThan
value: 3Key points:
- Description mentions symptoms, not the cause
- Objective states the goal, not the solution
- Validation titles are generic (don't reveal the fix)
Step 4: Create the Broken Manifests
Create memory-pressure/manifests/deployment.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
name: data-processor
spec:
replicas: 1
selector:
matchLabels:
app: data-processor
template:
metadata:
labels:
app: data-processor
spec:
containers:
- name: processor
image: python:3.11-slim
command:
- python
- -c
- |
import time
# Allocate memory to simulate real app
data = []
for i in range(50):
data.append("x" * 1024 * 1024) # ~50MB
time.sleep(0.1)
print("Processing complete!")
while True:
time.sleep(60)
resources:
limits:
memory: "32Mi" # BUG: Too low for this workload!
cpu: "100m"
requests:
memory: "32Mi"
cpu: "50m"This deployment will crash because:
- The app needs ~50MB of memory
- The limit is only 32Mi
- Kubernetes kills the container when it exceeds the limit
Need a Custom Image?
If inline commands aren't enough, you can create a custom Docker image. Add an image/ directory with a Dockerfile in your challenge folder - the CI will automatically build and publish it to ghcr.io/kubeasy-dev/<challenge-name>:latest.
See Challenge Structure - Custom Images for details.
Step 5: Add Bypass Protection
Create memory-pressure/policies/protect.yaml:
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: protect-memory-pressure
spec:
validationFailureAction: Enforce
rules:
- name: preserve-image
match:
resources:
kinds: ["Deployment"]
names: ["data-processor"]
namespaces: ["challenge-*"]
validate:
message: "Cannot change the container image"
pattern:
spec:
template:
spec:
containers:
- name: processor
image: "python:3.11-slim"This prevents users from replacing the application with a simpler one that uses less memory.
Step 6: Test Locally
Start the environment
# Initialize Kubeasy (creates Kind cluster + installs components)
kubeasy init
# Login to Kubeasy
kubeasy loginDeploy your challenge
kubeasy challenge start memory-pressureVerify the problem exists
# Check pod status
kubectl get pods -n challenge-memory-pressure
# You should see CrashLoopBackOff
NAME READY STATUS RESTARTS AGE
data-processor-xxx 0/1 CrashLoopBackOff 3 2m
# Check events
kubectl describe pod -n challenge-memory-pressure -l app=data-processor
# Look for OOMKilledApply the fix
kubectl patch deployment data-processor -n challenge-memory-pressure \
--type='json' -p='[
{"op": "replace", "path": "/spec/template/spec/containers/0/resources/limits/memory", "value": "128Mi"},
{"op": "replace", "path": "/spec/template/spec/containers/0/resources/requests/memory", "value": "64Mi"}
]'Verify the fix works
# Wait for pod to stabilize
sleep 30
# Check pod is running
kubectl get pods -n challenge-memory-pressure
# Should show Running, not CrashLoopBackOffSubmit and verify validations
kubeasy challenge submit memory-pressureAll validations should pass.
Clean up
kubeasy challenge reset memory-pressureStep 7: Submit Your Challenge
Once testing is complete:
git checkout -b challenge/memory-pressure
git add memory-pressure/
git commit -m "feat: add memory-pressure challenge"
git push origin challenge/memory-pressureOpen a Pull Request on GitHub with:
## Challenge: Memory Pressure
### What does this challenge teach?
Understanding Kubernetes resource limits and how to debug OOMKilled pods.
### Difficulty and estimated time
- Difficulty: Easy
- Estimated time: 15 minutes
### Testing
- [x] Challenge deploys with broken state
- [x] OOMKilled events occur as expected
- [x] Fix (increase memory limit) resolves the issue
- [x] All validations pass after fix
- [x] Kyverno policy prevents image changesCommon Mistakes
1. Revealing the solution
Bad:
description: |
The memory limit is too low. Increase it to fix the crash.Good:
description: |
The application keeps crashing. It worked fine in development.2. Validation titles that spoil the fix
Bad:
- key: memory-check
title: "Memory Limit Set to 128Mi"Good:
- key: stable-operation
title: "Stable Operation"3. Missing bypass protection
Users can replace your broken app with one that works. Always add Kyverno policies to prevent this.
4. Too complex
Don't combine multiple unrelated issues. One concept per challenge.
Checklist Before Submitting
-
challenge.yamlhas all required fields - Description doesn't reveal the root cause
- Validation titles don't reveal the solution
- Manifests create a reproducible broken state
- Kyverno policies prevent obvious bypasses
- Challenge tested locally end-to-end
- All validations pass after applying the fix
Next Steps
- Read Validation Rules for more validation types
- See Challenge Structure for complete reference
- Review existing challenges for inspiration