Skip to content

aahj/kubernetes

Repository files navigation

Multi-container Fibonacci demo on Kubernetes

What does this project do, and why did you build it?

This repo is a small multi-service application used to learn Docker and Kubernetes. The user-facing piece is a React app where you submit a numeric index; an Express API accepts it, persists submitted indices in PostgreSQL, stores a “pending” placeholder in Redis, and publishes the index on a Redis channel. A separate worker process subscribes to that channel, computes a Fibonacci number (with a deliberately expensive recursive implementation so work is noticeable), and writes the result back into Redis. The UI reads both the historical list of indices from Postgres and the latest calculated values from Redis.

It was built as a course-style exercise: the same problem is solved with several moving parts (web, API, background worker, cache, database) so you can practice container images, Deployments, Services, Ingress, Secrets, PersistentVolumeClaims, and CI/CD to a real cluster.


What does the architecture look like?

flowchart LR
  subgraph ingress [Ingress]
    IN[nginx ingress]
  end
  subgraph cluster [Cluster]
    CL[client pods\nnginx + static React]
    SV[server pods\nExpress :5000]
    WK[worker pod\nRedis subscriber]
    RD[(redis)]
    PG[(postgres + PVC)]
  end
  U[Browser] --> IN
  IN -->|"/" SPA| CL
  IN -->|"/api/*"| SV
  CL -->|same-origin /api| IN
  SV --> PG
  SV --> RD
  SV -->|publish insert| RD
  WK -->|subscribe insert| RD
  WK -->|hset results| RD
Loading
  • client/ — Create React App, production image is multi-stage: build with Node, serve static files with nginx on port 3000 (client/Dockerfile, client/nginx/default.conf).
  • server/Express API on port 5000; uses pg for Postgres and redis for cache + pub/sub (server/index.js).
  • worker/ — Long-running Node process: subscribes to Redis insert, runs fib(), updates the Redis hash values (worker/index.js).
  • k8s/ — Manifests for Deployments, ClusterIP Services, Ingress, PVC, and Postgres password via Secret pgpassword.
  • Ingress (k8s/ingress-service.yml) sends /api/... to the server service and everything else to the client service, with path rewriting compatible with the ingress-nginx controller.

Replica counts in manifests: 3 each for client and server, 1 for worker, Redis, and Postgres (Postgres uses a 2Gi PVC).


How do I run this locally, and how do I deploy it?

Run locally (without Kubernetes)

There is no docker-compose file in this repo; you run dependencies and apps yourself.

  1. PostgreSQL and Redis running and reachable (local install or your own containers).
  2. Environment variables for the API match server/keys.js: PGUSER, PGHOST, PGDATABASE, PGPASSWORD, PGPORT, REDIS_HOST, REDIS_PORT.
  3. From each directory, install and start:
    • Server: cd server && npm install && npm run dev (or npm start for production mode without nodemon).
    • Worker: cd worker && npm install && npm start.
    • Client: cd client && npm install && npm start (dev server, typically http://localhost:3000).

The React app calls /api/... relative to the page origin. In the cluster, Ingress makes that the same host as the UI. On your laptop, the CRA dev server does not proxy to the API unless you add a proxy field in client/package.json (e.g. to http://localhost:5000) or change the client to use an explicit API base URL—otherwise API requests from the browser will not reach Express.

  1. Tests: the CI workflow builds client/Dockerfile.dev and runs npm test with CI=true.

Deploy to Kubernetes (as in this repo)

The manifests target a cluster that already has:

  • An ingress controller (annotations assume ingress-nginx).
  • A Secret named pgpassword with key PGPASSWORD (see notes.txt for an example imperative command).

Typical flow:

  1. kubectl apply -f k8s
  2. Build and push your own images (or use the pipeline), then roll out:
    • kubectl set image deployment/server-deployment server=<your-registry>/multi-server:<tag>
    • Same pattern for client-deployment / worker-deployment.

GitHub Actions (.github/workflows/deploy.yaml): on push to branch main, it runs client tests in Docker, authenticates to Google Cloud, configures docker for GCR/GAR, fetches credentials for GKE cluster multi-cluster in us-central1-c, builds three images tagged rallycoding/multi-*-k8s-gh:latest and :${{ env.SHA }}, pushes them, then kubectl apply -f k8s and kubectl set image for each deployment.

Legacy Travis CI (.travis.yml): decrypts a GCP service account, gets cluster credentials, runs the same style of test, then bash ./deploy.sh on main. deploy.sh builds/pushes rallycoding/multi-*-k8s tags and updates deployments.

You will need your own GCP project, cluster, Docker registry, and GitHub secrets (DOCKER_USERNAME, DOCKER_PASSWORD, GKE_SA_KEY, etc.)—the values in the workflow are examples from the course and will not work on your account without replacement.

Note: The github actions and travis ci aren't tested in this repo.

What decisions did you make, and why?

Decision Why
Split web / API / worker Mirrors real systems: UI, synchronous API, and async CPU-ish work scaled and deployed independently.
Redis for pub/sub + hot state Fast channel between API and worker; hash holds “current” computed values for the UI without hammering Postgres on every poll.
Postgres for submitted indices Durable list of “indexes we have seen”; survives Redis restarts for that part of the data model.
ClusterIP + Ingress Internal services are not exposed directly; one entry point (Ingress) routes by path, which is a common production pattern.
nginx for production static assets Smaller runtime than react-scripts in production; standard static hosting.
PVC for Postgres Pod restarts do not wipe the database when using a volume claim.
Secrets for PGPASSWORD Avoids putting the database password in plain text in Deployment manifests.
CI: Dockerized test + deploy to GKE Validates the client in a container similar to CI, then delivers the same images to the cluster.

What would you improve if you continued working on it?

  • Local developer experience: Add docker-compose.yml (or a small Makefile) so Redis, Postgres, server, worker, and client start with one command; add a CRA proxy or REACT_APP_API_URL so local /api works without manual tweaks.
  • Security and ops: Rotate example registry/project names out of workflows; pin image digests e.g,. image: nginx@sha256:3c3c9f...; add resource requests/limits and liveness/readiness probes.
  • Ingress / API: Align branch names (main vs master) with the default branch; fix any image name drift between raw manifests (rallycoding/multi-server) and CI tags (…-k8s-gh) so a fresh apply before set image always pulls an existing image.
  • Application code: Replace recursive Fibonacci with an iterative (or memoized) version for realistic performance; use async/await consistently on the server for Redis; add migrations (e.g. node-pg-migrate,Dbmate) instead of CREATE TABLE in the connect handler.
  • Observability: Structured logging, metrics, and tracing from API and worker; optional HorizontalPodAutoscaler for server/client based on CPU or latency.

Repository layout

Path Role
client/ React SPA + Dockerfiles (dev test + prod nginx)
server/ Express API + Dockerfile
worker/ Redis consumer + Dockerfile
k8s/ Kubernetes manifests
.github/workflows/deploy.yaml GKE deploy pipeline
.travis.yml Legacy Travis + deploy.sh
deploy.sh Build, push, apply, rolling image update
notes.txt kubectl cheat sheet

About

This repo is a small multi-service application used to learn Docker and Kubernetes.

Topics

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors