Hyun Soo Kim

Architecture

A serverless backend on AWS, built with Terraform. The page in your browser never reads alert payloads itself: it sends them to the backend, and the backend sends back only the state change to draw. Blue boxes are code I wrote; purple boxes are AWS managed services. The numbers follow one signal from start to finish; the steps are explained right below the diagram.

The signal path

Signal path through the backend Your browser loads the site from CloudFront and S3 and opens a WebSocket session. A button press or a curl command posts a payload to API Gateway HTTP, which calls the signal Lambda. The Lambda checks the session and rate limit in DynamoDB, converts the payload to a state change, and pushes it through API Gateway WebSocket back to that one browser. Real alert sources are not connected yet. AWS · US-EAST-2 Your browser 3D scene + session Your terminal curl with an example payload Real alert sources Not connected yet CloudFront + S3 Static site, private bucket API Gateway · WebSocket Pushes to one browser API Gateway · HTTP POST /signal/{session} DynamoDB Sessions · TTL · 5/s Lambda · ws One session per tab Lambda · signal Adapter: payload → state page push result to your tab 1 2 3 4 5 6 3D reacts

What happens when you press a signal

  1. The payload is sent in its real format. The page (or your curl) posts the example exactly as the real tool would send it — an Alertmanager webhook, a CloudWatch alarm state-change event, or a Kubernetes HPA event — to POST https://api.hyunsookimdevops.fyi/signal/<your session>.
  2. API Gateway calls the signal Lambda. An HTTP API with a throttle on the whole API in front of a single function.
  3. The session and rate limit are checked in DynamoDB. Unknown or expired sessions are refused. Each session can send up to 5 signals per second, counted with a conditional update.
  4. The adapter turns the payload into one state change. It reads only the fields it needs (for example labels.alertname, detail.state.value, reason) and produces { signal: "channel-1-down", on: true }. Anything it does not recognise is rejected, and you see a “Rejected” card.
  5. The result is pushed through API Gateway WebSocket to your tab only. The session knows which connection is yours, so a curl from your terminal changes your screen and nobody else's.
  6. The 3D scene reacts. The page applies the state and shows a card with the source, the alert name and the round-trip time.

What each part does

Your browser

Loads the static site and draws the 3D scene with Three.js. Each tab opens one WebSocket session and only draws the state it receives.

CloudFront + S3

Serves the site over HTTPS from a private bucket that only CloudFront can read (Origin Access Control). DNS is on Cloudflare; the certificate is in ACM.

API Gateway · HTTP

The public entry point at api.hyunsookimdevops.fyi, for signals. CORS is limited to this site, and the API is throttled.

Lambda · signal

The adapter. Reads Alertmanager, CloudWatch and Kubernetes payloads, turns each into one state change, and pushes it to the right browser.

DynamoDB

One small table: which WebSocket connection owns each session, a per-second request counter, and a TTL so old sessions delete themselves.

WebSocket + Lambda · ws

Keeps a live connection to each tab. Creates the session, answers keep-alive pings, and cleans up when the tab closes, so the backend can push to one browser.

Design choices

Serverless, pay per request

Nothing runs while nobody visits. A thousand visitors sending thirty signals each costs well under a dollar, and the account has a hard monthly spend limit.

The browser only draws

The browser is treated as untrusted. It holds no secrets — no tokens, no keys — and only draws { signal, on }. Reading Alertmanager, CloudWatch and Kubernetes formats happens in the Lambda, behind the trust boundary, so copying the front-end code gives you a picture, not the integration. The server answers per session, and CORS allows only this site.

One session per tab, little data kept

Sessions expire after two hours with DynamoDB TTL. No IP addresses are stored, and logs are kept for seven days.

Everything in Terraform

Modules for the static site, the API and the realtime path. Remote state in S3 with a lock file. Each Lambda has its own least-privilege role. Local credentials are short-lived (aws login), with no long-term access keys.

A real constraint I hit

This AWS account was created with the new sign-up experience, which applies AWS-managed SCPs: us-east-1 allows only global services. The first state-bucket apply was denied, so regional resources moved to us-east-2, and only the CloudFront certificate stays in us-east-1.

Not connected yet