AWS Builder Center
Slaying Demons & Deploying Pods: Running Real-Time DOOM-Triggered Kubernetes Grading on AWS Serverless

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

AWS AI Hero + Microsoft MVP + Google Developer Experts - GCP(AI)

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 ServiceCore PurposeKey Configuration / Feature
Amazon API Gateway (WebSocket API)Single entry point & bidirectional full-duplex messagingBypasses 29s HTTP integration timeout via persistent WebSocket frames
AWS Lambda (game-ws-handler)Fast-path edge request handler & lock managerFast 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 DynamoDBSingle-digit millisecond state & lock repositoryOn-Demand capacity mode, TTL for execution locks
Amazon S3Encrypted object storage for test suites & reportsPrivate 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:
  1. 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"
    }
  2. Local Game Pause: The game client immediately pauses local interactions and displays a status overlay.
  3. 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

  1. Instant Edge Response (<50ms): When the player triggers a test in DOOM, game-ws-handler authenticates 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.
  2. Unconstrained Background Execution: game-ws-handler asynchronously invokes game-command-handler via boto3. 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.
  3. 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
  • $connect Route: API Gateway invokes the connection handler, decrypts the Fernet API key to extract the user's email, and saves the connection row into GameWsConnectionTable in Amazon DynamoDB:
    • connection_id (Partition Key)
    • email
    • game
    • connected_at
  • subscribe Route: 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:
  1. Rate Limiting: A 2-second per-user cooldown check prevents accidental trigger spamming.
  2. 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}#mutation
3. 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.

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:
  1. Bootstrap Trigger (setup -\> ready):
    • Automatically prepares student cluster resources (setup).
    • Immediately verifies cluster readiness (ready).
    • Advances state to challenge so instructions display what the player needs to configure next.
  2. Gameplay Trigger (challenge -\> check):
    • Automatically runs cluster verification checks (check).
    • If check passes: Marks task COMPLETED and advances to the next task in the manifest.
    • If check fails: Rewinds status back to challenge, allowing the student to inspect error reports and try again.

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_SOURCE bypasses 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_STARTED tasks 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 DimensionExam ModeGame / DOOM Mode
Authentication SourceExamCodeTable + ExamSessionTableAccountTable + Encrypted API Key
Attempt EnforcementStrict max_attempts lockout (ABANDONED)Infinite Retries (_counts_attempts = False)
NPC / Assignment LocksSingle-task scoped per exam sessionBypassed via DOOM_TASK_SOURCE (npc = "doom")
Phase ExecutionSingle-step execution per runMulti-phase auto-chaining (setup-\>ready, challenge-\>check)
Answer Phase (test_03_answer.py)Evaluated for grading validationSilently Skipped (advances to next grading phase)
History RetentionSaved in TestRecordTableSaved 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:
  1. Private Test Source Fetching:
    • Pytest test files are packaged and stored in an encrypted Amazon S3 Bucket (GameSourceTable).
    • Upon invocation, Lambda downloads the task .zip archive and extracts it into /tmp/\<game\>.
  2. 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 /tmp and dynamically generates a temporary kubeconfig file.
  3. Pytest Execution & Report Upload:
    • Lambda invokes Pytest via python -m pytest programmatically:
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.

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_REQUEST

12. 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

GitHub Repo: https://github.com/wongcyrus/k8s-game-platform
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article