Hop 1 of 8, Lookup

Recruiter view
Recruiter view

I keep things upUp since 3 Nov 2026

Anshul Singh Chauhan

DevOps/cloud engineer. Production Azure integrations at ATH Infosystems. EKS, Terraform and CI/CD in my own projects.

New Delhi, India Aiming for Japan by 2029–30, studying for JLPT N5.

Short version for recruiters or press R

For recruiters

Recorded

Real past work, written up afterwards.

  • Cut a production integration's run time by about 93%, from over 16 minutes to about 1.
  • Took critical vulnerabilities from 12 to 0 in 2 weeks.
  • Restored 21 of 23 overwritten identity values across 17 people, with 0 errors. The other 2 were already right.

Or write to anshulchauhanworks@gmail.com

See the full journey

Live Measured in your browser.

Voxel model of six server racks on a plate.Voxel model of six server racks on a plate.

You got here in ms, measured just now.This page measures itself in your browser. This site's Terraform, pipeline and scans are public.

Live Measured in your browser, or written by this site's pipeline.

These numbers are measured in your browser, so they need JavaScript.

How this page reached you, measured in your browser
StepMeasured

This site right now

Up since
3 Nov 2026
Last deploy
3 Nov 2026, 19:36 UTC
Last commit
ed98b76
  • Pipeline passing
  • Trivy passing
An amber packet past a locked gateway.An amber packet past a locked gateway.

An EKS lab with tiers split by taints and affinity. TLS ends at the ALB, and here's what's still exposed.

Recorded Real config from the Three Tier Lab repo, with IDs, ARNs and domains removed.

Voxel model of a VPC, with a public layer above a private layer, and an ALB on top.Voxel model of a VPC, with a public layer above a private layer, and an ALB on top.

Top layer, the public subnets: the ALB along the back, the NAT Gateway, the bastion and two frontend nodes. Bottom layer, the private subnets: two API and database nodes, each with its taint marker on top.

The parts, and why each is there

ALB

AWS Load Balancer Controller creates it from the Ingress, so routes live with the app's manifests, and TLS ends there with an ACM certificate.

YAML manifests/ingress.yaml, excerpt
kind: Ingressmetadata:  annotations:    alb.ingress.kubernetes.io/scheme: internet-facing    alb.ingress.kubernetes.io/target-type: ip    alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'    alb.ingress.kubernetes.io/certificate-arn: <removed>    alb.ingress.kubernetes.io/ssl-redirect: "443"spec:  ingressClassName: alb  rules:    - host: <removed>      http:        paths:          - path: /            pathType: Prefix            backend:              service:                name: frontend                port:                  number: 3000
NAT Gateway

Private-subnet API and database nodes reach out for images and patches without public IPs.

README only: documented, no manifest exists

  • In a public subnet.
  • Egress for the private nodes.
  • Used for ECR pulls and OS updates.
Bastion

The EKS API is private, so kubectl needs one controlled way in.

README only: documented, no manifest exists

  • IMDSv2 enforced (HttpTokens: required).
  • SSH restricted to the operator's IP.
  • SSM Session Manager as secondary access.
Taints and affinity

Keep the API and database on their own private nodes.

Node group: taint dedicated=db-api:NoSchedule, label workload=db-api.

YAML Pod spec, excerpt
affinity:  nodeAffinity:    requiredDuringSchedulingIgnoredDuringExecution:      nodeSelectorTerms:        - matchExpressions:            - key: workload              operator: In              values:                - db-apitolerations:  - key: dedicated    operator: Equal    value: db-api    effect: NoSchedule

What breaks here

  • TLS ends at the ALB. Traffic behind it is plain HTTP.
  • MongoDB uses a HostPath volume, so its data is lost if the node is replaced.
  • There's no cluster autoscaler, so node count doesn't follow load.
  • The frontend runs on a public-subnet node, which widens the blast radius.
  • Both deployments use an image pull secret, so image delivery isn't credential-free.

Autoscaling reacts late. Jump the dial and watch latency climb before pods arrive.

Simulated Runs in your browser on the repo's real config. Assumptions are listed below, dated 2 Nov 2026.

This demo runs in your browser and needs JavaScript. The picture shows its resting state.

  • Ready
  • Ready

200: 2 pods' limit 600: 6 pods' limit

Jump past the first mark. Turn it slowly and the pods keep up.
Latency
21 ms
Ready pods
2 of 2
Timed out
0
New pods took
no climb yet
Peak latency
no climb yet
Simulated time
0 s
Clock speed
5× real time
A floor with six pod slots in a row, two of them holding ready pods, and an empty queue pad where traffic enters.A floor with six pod slots in a row, two of them holding ready pods, and an empty queue pad where traffic enters.

This picture shows the resting state. The pods list is live.

Steady. 2 pods are handling 10 requests a second.

What breaks here

  • The CPU target is measured against the 100m request, not the 500m limit. At target, six pods use 360m of a 3,000m combined limit, so the autoscaler runs out of pods long before they run out of headroom.
  • The readiness probe calls /ok, which doesn't check MongoDB. A pod can be Ready with the database down.

Assumptions

From the repo
Minimum 2 pods, maximum 6, CPU target 60%. CPU request 100m, limit 500m. Readiness probe on /ok: first check at 5 s, then every 5 s.
Kubernetes defaults
The controller runs every 15 s with a tolerance of 0.1. Scale-down waits 300 s. Scale-up adds the larger of 4 pods or 100% every 15 s. Pods not yet ready count as 0% on the way up.
Assumed, 2 Nov 2026
Metrics every 15 s, the value in metrics-server's shipped manifest. 5 ms of CPU per request, 20 ms base latency, 12 s for a container to serve, 30 s client timeout, 5m idle CPU, 1 s to create a replacement. MongoDB and node capacity aren't modelled. Latency values depend on these assumptions.

Redis down means 503: the limiter fails closed before the cache can fail open.

Simulated Runs in your browser on the repo's real config. Latency is illustrative, assumed 2 Nov 2026.

This demo runs in your browser and needs JavaScript. The picture shows its resting state.

Start here: switch Redis off, then send a request. The limiter counts in Redis, so without it every request is refused.

Redis keys and health

tasks:list
empty
Limiter
0 of 100 this minute
Redis
up
/health
200

Send requests

Last result

none No request yet.

A raised deck with a striped boom gate beside a rail, a Redis shelf with six unlit segments, and a Postgres block on the floor below, where the rail ends.A raised deck with a striped boom gate beside a rail, a Redis shelf with six unlit segments, and a Postgres block on the floor below, where the rail ends.

The gate is the rate limiter, the shelf is Redis with a segment for every 5 s of cache left, and the block below is Postgres. This picture shows the resting state.

Click the gate to send a request, or the shelf to switch Redis. Each request stops where the app stopped it.

Request trace

    Send a request to trace it.

    App log

    What the app logs: failures and blocks, shown without dates or tracebacks. A request that works logs nothing.

      Nothing logged yet.

      What breaks here

      • One Redis does two jobs. When it goes, the limiter takes down every route except /health, and /health reports 503 itself.
      • Fixed windows reset on the minute, so 100 requests at :59 and 100 at :00 all pass.
      • The limiter trusts the first X-Forwarded-For value, so a client that sends its own header gets its own counter.

      Assumptions

      From the repo
      A fixed window per client IP, key rate_limit:{ip}:{minute}, 100 requests a minute. INCR runs before the check, so a blocked request still counts. GET /tasks keeps tasks:list for 30 s, and a write removes it. Redis runs with --appendonly yes, so keys and their expiry times survive a restart. The README calls the limiter a token bucket; the code is a fixed window.
      Assumed, 2 Nov 2026
      Latency is illustrative: limiter 1 ms, Redis read 1 ms, Redis write 1 ms, Postgres 12 ms, app 2 ms. So a hit takes 4 ms, a miss 17 ms, a write 16 ms, and a 503 or 429 1 ms. "Redis down" means it refuses connections, so each error is immediate. The real client sets no socket timeout, so a hung Redis would behave differently. A burst sends its 120 requests one after another, from one IP. 203.0.113.7 is an example IP.

      Production work and postmortems.

      Recorded

      Every hour, a Lambda checks for idle EC2. This is the exact Slack alert it builds.

      Simulated

      200 OK. Resume, links, book a time.

      Live