
Slaying Demons & Deploying Pods: Running Real-Time DOOM-Triggered Kubernetes Grading on AWS Serverless
Serverless Game Backends, Real-Time Systems, DOOM Integration, AWS Lambda Durable Execution, Event-Driven Architecture
1. Introduction & Executive Summary
Modern web-based games demand sub-second responsiveness, interactive feedback loops, and smooth state updates. However, when a game is designed to teach or grade real-world technical skills—such as evaluating live Kubernetes clusters triggered directly by player actions in DOOM—a fundamental engineering challenge arises:
- In-Game Actions occur in milliseconds (e.g., picking up armor, defeating an enemy, or triggering a DOOM sector event).
- Infrastructure Operations (e.g., provisioning pods, testing network policies, verifying ingress controllers, running Pytest suites against Kubernetes API endpoints) frequently take between 30 seconds to several minutes.
The 29-Second API Gateway Hard Timeout Constraint
In standard request-response architectures using synchronous HTTP REST APIs, Amazon API Gateway enforces a strict, non-configurable hard limit of 29 seconds for integration timeouts. If a synchronous HTTP request exceeds 29 seconds—which is common during complex Kubernetes cluster verification or pod convergence—API Gateway forcibly terminates the connection and returns a
504 Gateway Timeout error to the client, even if the backend Lambda function is still actively running.Traditional architectures attempt to bypass this timeout by maintaining always-on server clusters running persistent WebSocket workers, background task queues (e.g., Redis + Celery), and polling loops. However, always-on servers introduce idle infrastructure costs, complex cluster management, and scaling overhead.
In this article, we demonstrate how to overcome API Gateway's 29-second timeout constraint using a 100% AWS-Native Serverless Architecture. By combining Amazon API Gateway WebSockets, AWS Lambda Durable Execution, Amazon DynamoDB, and Amazon S3, we built an event-driven game backend that orchestrates long-running, stateful Kubernetes task verification triggered by in-game DOOM events with zero idle compute cost.
2. High-Level AWS Serverless Architecture
The entire platform is defined as Infrastructure-as-Code using the AWS Serverless Application Model (AWS SAM). The architecture decouples real-time WebSocket connection handling from long-running task execution using an asynchronous, event-driven pattern.
Importantly, all client traffic flows strictly through Amazon API Gateway WebSockets. The durable worker Lambda does not communicate directly with the browser; instead, it uses the API Gateway Management API to post updates back to API Gateway, which delivers them over the persistent WebSocket connection.

3. AWS Service Matrix
Each AWS service in this architecture fulfills a strictly scoped role:
| AWS Service | Core Purpose | Key Configuration / Feature |
|---|---|---|
| Amazon API Gateway (WebSocket API) | Single entry point & bidirectional full-duplex messaging | Bypasses 29s HTTP integration timeout via persistent WebSocket frames |
AWS Lambda (game-ws-handler) | Fast-path edge request handler & lock manager | Fast response (<50ms), rate limiting, execution guard acquisition |
AWS Lambda (game-command-handler) | Long-running durable task runner & grader | @durable_execution wrapper, up to 15-min execution timeout for Pytest |
| Amazon DynamoDB | Single-digit millisecond state & lock repository | On-Demand capacity mode, TTL for execution locks |
| Amazon S3 | Encrypted object storage for test suites & reports | Private game source archives (.zip), HTML test output |
| AWS SAM (Serverless Application Model) | Infrastructure-as-Code (IaC) | Declarative deployment of Lambdas, API Gateways, IAM, and DynamoDB |
4. The Abstract Client Trigger Model
To keep the game engine clean and decoupled from backend infrastructure details, the DOOM game client communicates strictly via a lightweight Event Trigger Abstraction:
- In-Game Action: When a player interacts with a target element (e.g., picking up an item or triggering a DOOM event), the game client constructs a simple JSON frame:
{
"action": "talk",
"apiKey": "USER_ENCRYPTED_API_KEY",
"game": "game01",
"npc": "doom"
} - Local Game Pause: The game client immediately pauses local interactions and displays a status overlay.
- Asynchronous Waiting: The game client listens on the WebSocket channel for state updates pushed asynchronously by AWS Lambda via API Gateway.
By isolating the client from task execution logic, the game engine remains lightweight while AWS handles all orchestration, state tracking, and Kubernetes verification in the cloud.
5. Overcoming the 29-Second Timeout: Edge Real-Time Layer & Concurrency Locks
When executing Kubernetes tests that can run for up to 15 minutes (AWS Lambda's maximum execution timeout limit), relying on synchronous HTTP request-response patterns guarantees failure due to API Gateway's 29-second HTTP integration limit.
By switching to Amazon API Gateway WebSockets, the architecture completely decouples request ingestion from task execution:

5.1 How Asynchronous WebSocket Dispatch Defeats the 29s Limit
- Instant Edge Response (<50ms): When the player triggers a test in DOOM,
game-ws-handlerauthenticates the connection, acquires the DynamoDB lock, and returns an immediate"QUEUED"acknowledgement over the WebSocket channel. This finishes the API Gateway synchronous route execution in under 50 milliseconds—well below the 29-second threshold. - Unconstrained Background Execution:
game-ws-handlerasynchronously invokesgame-command-handlerviaboto3. Because the worker runs asynchronously outside the HTTP request-response loop, it has access to AWS Lambda's full 15-minute execution budget to perform intensive Pytest evaluations, retry transient cluster connectivity, and wait for Kubernetes pod readiness. - Out-of-Band Push Notifications: As grading progresses, the durable worker calls the API Gateway Management API (
post_to_connection) to stream live progress updates (RUNNING,PASSED,FAILED,COMPLETED) directly back through API Gateway to the browser connection.
5.2 Connection Lifecycle & Registration
When a user opens DOOM, the browser connects to Amazon API Gateway WebSockets:
1
wss://\<api-id\>.execute-api.us-east-1.amazonaws.com/Prod?apiKey=YOUR_ENCRYPTED_KEY&game=game01$connectRoute: API Gateway invokes the connection handler, decrypts the Fernet API key to extract the user's email, and saves the connection row intoGameWsConnectionTablein Amazon DynamoDB:connection_id(Partition Key)emailgameconnected_at
subscribeRoute: Returns an immediate snapshot of the user's current progress (TaskStateTable) so browser refreshes instantly recover the exact question, instructions, and score without losing context.
5.3 Concurrency Safety with DynamoDB Execution Guard Locks
Because multiple DOOM item pickups or trigger lines can be hit in rapid succession, running multiple concurrent Pytest commands against the same Kubernetes cluster would cause race conditions (e.g., two tests creating conflicting namespaces simultaneously).
To solve this,
game-ws-handler enforces a strict Execution Guard Lock pattern using Amazon DynamoDB:- Rate Limiting: A 2-second per-user cooldown check prevents accidental trigger spamming.
- Execution Guard Lock: Before invoking the durable worker, the handler attempts to create an atomic conditional write in DynamoDB under the key scope:
1
game#{email}#{game}#mutation3. Automatic TTL Safeguard: The lock record is created with a 3-Minute Time-To-Live (TTL). If a Lambda execution unexpectedly crashes, DynamoDB automatically expires the lock after 180 seconds, ensuring students are never permanently locked out of their tasks.
6. Dual State Machine & Task Progression Engine
To maintain deterministic task progression across multiple serverless invocations, the platform uses a Dual State Machine architecture managed by
TaskStateMachine.
6.1 High-Level Task Lifecycle
A exercise task transitions through four macro-level statuses:
NOT_STARTED: Initial state before the player attempts the task. Task instructions render dynamic Jinja2 session variables (e.g.,{{ namespace }}).IN_PROGRESS: The task is active. The user is actively modifying their Kubernetes cluster to satisfy task requirements.COMPLETED: All phases (setup,ready,challenge,check) passed successfully. Points are awarded.ABANDONED: (Exam Mode) Maximum allowed attempts exceeded. Task locks until an explicit reset occurs.
6.2 Granular Phase Lifecycle
Inside an active task, individual test files (
test_01_setup.py, test_02_ready.py, test_04_challenge.py, test_05_check.py) transition through sub-states:PENDING: Phase is queued for execution.RUNNING: AWS Lambda is actively executing the Pytest suite against the target cluster.PASSED: The Pytest suite returned exit code 0.FAILED: The Pytest suite failed assertions. Detailed report uploaded to S3.
All state transitions are persisted transactionally in the Amazon DynamoDB
TaskStateTable.7. DOOM Execution Engine (DOOM_TASK_SOURCE) & Phase Auto-Chaining
For action-oriented gameplay, the backend provides specialized handling under
DOOM_TASK_SOURCE (npc = "doom") in game-command-handler/app.py.
For more details of game phrase design, please read Kubernetes Isekai (異世界): Gamifying Kubernetes Education in Free AWS Academy Learner Lab
7.1 Multi-Phase Auto-Chaining
Rather than forcing the user to trigger four separate manual interactions per task, the durable Lambda worker auto-chains setup and evaluation phases within a single execution invocation:
- Bootstrap Trigger (
setup -\> ready):- Automatically prepares student cluster resources (
setup). - Immediately verifies cluster readiness (
ready). - Advances state to
challengeso instructions display what the player needs to configure next.
- Automatically prepares student cluster resources (
- Gameplay Trigger (
challenge -\> check):- Automatically runs cluster verification checks (
check). - If
checkpasses: Marks taskCOMPLETEDand advances to the next task in the manifest. - If
checkfails: Rewinds status back tochallenge, allowing the student to inspect error reports and try again.
- Automatically runs cluster verification checks (
7.2 Infinite Retries (DOOM_TASK_SOURCE Bypass Rules)
Unlike structured exam modes, learning via fast-paced action games requires continuous experimentation without fear of lockout:
- Bypassing NPC Lockout:
DOOM_TASK_SOURCEbypasses single-NPC assignment locks (NpcLockTable), allowing seamless task progression. - Bypassing Attempt Lockout: Attempt counting (
_counts_attempts) is disabled, permitting infinite retries so students can debug their Kubernetes YAML manifests and re-trigger checks as many times as needed. - Dynamic Parameter Rendering: Instructions for
NOT_STARTEDtasks render Jinja2 parameters dynamically from session data (e.g.,{{ namespace }},{{ pod_name }}) before execution begins, ensuring students always see actual cluster target values.
8. Exam Mode vs. Game/DOOM Mode Execution Matrix
The platform supports two distinct operational modes using shared core database tables and service layers:

| Operational Dimension | Exam Mode | Game / DOOM Mode |
|---|---|---|
| Authentication Source | ExamCodeTable + ExamSessionTable | AccountTable + Encrypted API Key |
| Attempt Enforcement | Strict max_attempts lockout (ABANDONED) | Infinite Retries (_counts_attempts = False) |
| NPC / Assignment Locks | Single-task scoped per exam session | Bypassed via DOOM_TASK_SOURCE (npc = "doom") |
| Phase Execution | Single-step execution per run | Multi-phase auto-chaining (setup-\>ready, challenge-\>check) |
Answer Phase (test_03_answer.py) | Evaluated for grading validation | Silently Skipped (advances to next grading phase) |
| History Retention | Saved in TestRecordTable | Saved in TestRecordTable (mode="exercise") |
9. Executing Kubernetes Unit Tests inside AWS Lambda
Executing Pytest against remote Kubernetes clusters directly from an ephemeral AWS Lambda container requires careful filesystem and network management:
- Private Test Source Fetching:
- Pytest test files are packaged and stored in an encrypted Amazon S3 Bucket (
GameSourceTable). - Upon invocation, Lambda downloads the task
.ziparchive and extracts it into/tmp/\<game\>.
- Pytest test files are packaged and stored in an encrypted Amazon S3 Bucket (
- Dynamic Kubeconfig Assembly:
- Lambda loads the student's K8s endpoint URL, client certificate, and client key from
AccountTable. - It writes client cert/key files to
/tmpand dynamically generates a temporarykubeconfigfile.
- Lambda loads the student's K8s endpoint URL, client certificate, and client key from
- Pytest Execution & Report Upload:
- Lambda invokes Pytest via
python -m pytestprogrammatically:
- Lambda invokes Pytest via
1
2
3
4
5
pytest.main([
"--import-mode=importlib",
f"--html=/tmp/report_{task_id}.html",
f"/tmp/{game}/tests/{task_id}/{phase_file}"
])- If assertions fail, the generated HTML test report is uploaded to Amazon S3 (
TestResultBucket), and a pre-signed S3 URL is returned in the WebSocket payload so the player can view exact Pytest tracebacks.
- If assertions fail, the generated HTML test report is uploaded to Amazon S3 (
10. AWS Best Practices & Performance Optimizations
Designing long-running durable Lambda functions for real-time game backends revealed three critical serverless design patterns:
10.1 Lazy Initialization for Cold Start Reduction
To minimize cold start latency on AWS Lambda, heavy SDK initializations (
boto3.resource('dynamodb'), Pytest test runner dependencies, and service classes) are instantiated lazily upon first invocation rather than at top-level module import time.10.2 Flat Durable Step Wrappers
When using AWS Lambda Durable Execution decorators (
@durable_execution), nesting decorated step helpers inside other durable step functions causes runtime function-object serialization failures. The architecture enforces a flat step pattern, wrapping orchestration logic in a single top-level step wrapper and keeping internal helper logic plain Python code.10.3 Ephemeral /tmp Filesystem Hygiene across Warm Invocations
When AWS Lambda instances are reused across warm invocations, stale bytecode (
__pycache__) from previously extracted test archives can cause Python import conflicts. The handler explicitly wipes the managed /tmp/\<game\> extracted directory tree before downloading fresh archives, guaranteeing clean test execution environments across warm Lambda containers.11. Infrastructure as Code (AWS SAM template.yaml)
The complete architecture is deployed as a single stack using AWS SAM. Below is an abbreviated template highlighting key resource definitions:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: Real-Time Serverless Game Backend with Lambda Durable Execution
Resources:
# 1. API Gateway WebSocket API
GameWebSocketApi:
Type: AWS::ApiGatewayV2::Api
Properties:
Name: GameWebSocketApi
ProtocolType: WEBSOCKET
RouteSelectionExpression: "$request.body.action"
# 2. Fast-Path WebSocket Handler Lambda
GameWsHandlerFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: game-ws-handler/
Handler: app.lambda_handler
Runtime: python3.12
Timeout: 10
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref GameWsConnectionTable
- DynamoDBCrudPolicy:
TableName: !Ref ExecutionGuardTable
- LambdaInvokePolicy:
FunctionName: !Ref GameCommandHandlerFunction
# 3. Durable Command Worker Lambda
GameCommandHandlerFunction:
Type: AWS::Serverless::Function
Properties:
CodeUri: game-command-handler/
Handler: app.lambda_handler
Runtime: python3.12
Timeout: 900
MemorySize: 1024
Policies:
- DynamoDBCrudPolicy:
TableName: !Ref TaskStateTable
- S3ReadPolicy:
BucketName: !Ref TestSourceBucket
- S3WritePolicy:
BucketName: !Ref TestResultBucket
# 4. DynamoDB Task State Table
TaskStateTable:
Type: AWS::DynamoDB::Table
Properties:
AttributeDefinitions:
- AttributeName: email
AttributeType: S
- AttributeName: gameTask
AttributeType: S
KeySchema:
- AttributeName: email
KeyType: HASH
- AttributeName: gameTask
KeyType: RANGE
BillingMode: PAY_PER_REQUEST12. Conclusion
By leveraging Amazon API Gateway WebSockets, AWS Lambda Durable Execution, Amazon DynamoDB, and Amazon S3, we built an event-driven game backend that seamlessly bridges the gap between fast-paced 3D DOOM gameplay and long-running Kubernetes infrastructure validation.
Key Architectural Takeaways:
- Strict Single Entry Point: The browser DOOM client connects exclusively to Amazon API Gateway WebSockets. Durable Lambda workers stream updates back via API Gateway's Management API.
- Overcoming the 29s Limit: Decoupling requests via API Gateway WebSockets allows instant acknowledgements (<50ms) while durable Lambda workers utilize up to 15 minutes of execution time for long-running Kubernetes cluster evaluations.
- Zero Idle Costs: The entire backend runs on pay-per-use AWS serverless primitives—no EC2 clusters, no persistent WebSocket servers.
- Concurrency Safety: DynamoDB-backed Execution Guard locks guarantee single-threaded cluster modifications without race conditions.
- Resilient State Management: Dual state machines ensure deterministic task progression, auto-chaining, and infinite retries for continuous hands-on learning.
Resources & References
- AWS Serverless Application Model (SAM)
- Amazon API Gateway WebSocket APIs
- AWS Lambda Durable Execution Patterns
- Amazon DynamoDB On-Demand Capacity
- doom.ts source
GitHub Repo: https://github.com/wongcyrus/k8s-game-platform
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article