Skip to content

Latest commit

History

2 Commits

Folders and files

NameName
Last commit message
Last commit date

Repository files navigation

DevOps Take-Home Assignment

Galleri5 Technologies

Context: Galleri5 is a creator economy platform serving enterprise clients like L'Oreal, HUL,shein, and AJIO. Our backend is FastAPI + MongoDB + Redis, currently running on GCP. We're planning a migration to Azure.


The Platform

You'll be working with a simplified version of our platform — 3 services that work together:

┌─────────────────┐ ┌────────────────────┐ ┌─────────────────┐
│ Creator API │──────▶│ Analytics Service │──────▶│ Worker │
│ (FastAPI) │ │ (FastAPI) │ │ (Background) │
│ Port 8000 │ │ Port 8001 │ │ │
└────────┬────────┘ └─────────┬───────────┘ └────────┬────────┘
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ MongoDB │ │ Redis │ │ Redis │
│ │ │ (cache) │ │ (queue) │
└─────────┘ └─────────┘ └─────────┘
  • Creator API — Main API for managing creators and campaigns. Uses MongoDB for storage, Redis for caching, and calls Analytics Service for stats.
  • Analytics Service — Receives events from Creator API, stores analytics data in Redis, pushes tasks to a worker queue.
  • Worker — Background process that consumes events from Redis queue and does heavy processing (computing engagement scores, generating reports).

Application Code

The application code is provided below. Do not modify the app logic — your job is everything around it (containerization, infrastructure, deployment, monitoring).

Service 1: Creator API (creator-api/main.py)

"""Galleri5 Creator API — Main serviceHandles creator CRUD and campaign management.Talks to MongoDB for storage, Redis for caching, and Analytics Service for stats."""fromfastapiimportFastAPI, HTTPException, BackgroundTasksfrompydanticimportBaseModelfromtypingimportOptionalimportosimporttimeimporthttpximportjsonimportlogginglogging.basicConfig(
level=os.getenv("LOG_LEVEL", "INFO"),
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
logger=logging.getLogger("creator-api")
app=FastAPI(title="Galleri5 Creator API", version="1.0.0")
# --- Config ---MONGO_URI=os.getenv("MONGODB_URI", "mongodb://mongo:27017")
REDIS_HOST=os.getenv("REDIS_HOST", "redis")
REDIS_PORT=int(os.getenv("REDIS_PORT", "6379"))
ANALYTICS_SERVICE_URL=os.getenv("ANALYTICS_SERVICE_URL", "http://analytics-service:8001")
API_SECRET=os.getenv("API_SECRET", "")
ENVIRONMENT=os.getenv("ENVIRONMENT", "development")
try:
frommotor.motor_asyncioimportAsyncIOMotorClientmongo_client=AsyncIOMotorClient(MONGO_URI, serverSelectionTimeoutMS=3000)
db=mongo_client.galleri5MONGO_AVAILABLE=TrueexceptImportError:
mongo_client=Nonedb=NoneMONGO_AVAILABLE=Falselogger.warning("motor not installed — MongoDB features disabled")
try:
importredisasredis_libredis_client=redis_lib.Redis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True, socket_timeout=3)
REDIS_AVAILABLE=TrueexceptImportError:
redis_client=NoneREDIS_AVAILABLE=Falselogger.warning("redis not installed — caching disabled")
# --- In-memory data ---CREATORS_DATA= {
"c001": {"id": "c001", "name": "Priya Sharma", "platform": "instagram", "followers": 125000, "category": "fashion", "engagement_rate": 3.2},
"c002": {"id": "c002", "name": "Rohan Mehta", "platform": "youtube", "followers": 890000, "category": "tech", "engagement_rate": 4.1},
"c003": {"id": "c003", "name": "Ananya Reddy", "platform": "instagram", "followers": 45000, "category": "beauty", "engagement_rate": 5.8},
"c004": {"id": "c004", "name": "Vikram Das", "platform": "youtube", "followers": 2100000, "category": "gaming", "engagement_rate": 2.9},
"c005": {"id": "c005", "name": "Meera Joshi", "platform": "instagram", "followers": 310000, "category": "food", "engagement_rate": 4.5},
}
CAMPAIGNS_DATA= {
"camp01": {"id": "camp01", "client": "L'Oreal", "name": "Summer Beauty 2025", "status": "active", "budget": 500000, "creators": ["c001", "c003"]},
"camp02": {"id": "camp02", "client": "HUL", "name": "Social Trends Q1", "status": "active", "budget": 750000, "creators": ["c002", "c005"]},
"camp03": {"id": "camp03", "client": "AJIO", "name": "Fashion Forward", "status": "completed", "budget": 300000, "creators": ["c001", "c004"]},
}
START_TIME=time.time()
classCreatorCreate(BaseModel):
name: strplatform: strfollowers: intcategory: strengagement_rate: float=0.0classCampaignCreate(BaseModel):
client: strname: strbudget: floatcreator_ids: list[str]
defcache_get(key: str):
ifnotREDIS_AVAILABLEornotredis_client:
returnNonetry:
val=redis_client.get(key)
returnjson.loads(val) ifvalelseNoneexceptExceptionase:
logger.warning(f"Redis GET failed: {e}")
returnNonedefcache_set(key: str, value: dict, ttl: int=300):
ifnotREDIS_AVAILABLEornotredis_client:
returntry:
redis_client.setex(key, ttl, json.dumps(value))
exceptExceptionase:
logger.warning(f"Redis SET failed: {e}")
asyncdefnotify_analytics(event_type: str, data: dict):
try:
asyncwithhttpx.AsyncClient(timeout=5) asclient:
awaitclient.post(
f"{ANALYTICS_SERVICE_URL}/events",
json={"event_type": event_type, "data": data, "timestamp": time.time()}
)
exceptExceptionase:
logger.warning(f"Analytics notification failed: {e}")
@app.get("/health")asyncdefhealth():
checks= {
"status": "healthy",
"version": os.getenv("APP_VERSION", "1.0.0"),
"environment": ENVIRONMENT,
"uptime_seconds": round(time.time() -START_TIME, 2),
}
ifMONGO_AVAILABLEandmongo_client:
try:
awaitmongo_client.admin.command("ping")
checks["mongodb"] ="connected"exceptException:
checks["mongodb"] ="disconnected"checks["status"] ="degraded"else:
checks["mongodb"] ="not_configured"ifREDIS_AVAILABLEandredis_client:
try:
redis_client.ping()
checks["redis"] ="connected"exceptException:
checks["redis"] ="disconnected"checks["status"] ="degraded"else:
checks["redis"] ="not_configured"try:
asyncwithhttpx.AsyncClient(timeout=3) asclient:
resp=awaitclient.get(f"{ANALYTICS_SERVICE_URL}/health")
checks["analytics_service"] ="connected"ifresp.status_code==200else"unhealthy"exceptException:
checks["analytics_service"] ="disconnected"returnchecks@app.get("/creators")asyncdeflist_creators(platform: Optional[str] =None, category: Optional[str] =None, min_followers: Optional[int] =None):
cache_key=f"creators:{platform}:{category}:{min_followers}"cached=cache_get(cache_key)
ifcached:
logger.info(f"Cache HIT: {cache_key}")
returncachedresults=list(CREATORS_DATA.values())
ifplatform:
results= [cforcinresultsifc["platform"] ==platform.lower()]
ifcategory:
results= [cforcinresultsifc["category"] ==category.lower()]
ifmin_followers:
results= [cforcinresultsifc["followers"] >=min_followers]
response= {"creators": results, "count": len(results), "cached": False}
cache_set(cache_key, response, ttl=120)
returnresponse@app.get("/creators/{creator_id}")asyncdefget_creator(creator_id: str):
cached=cache_get(f"creator:{creator_id}")
ifcached:
returncachedifcreator_idnotinCREATORS_DATA:
raiseHTTPException(status_code=404, detail=f"Creator {creator_id} not found")
creator=CREATORS_DATA[creator_id]
cache_set(f"creator:{creator_id}", creator, ttl=300)
returncreator@app.post("/creators")asyncdefcreate_creator(creator: CreatorCreate, background_tasks: BackgroundTasks):
creator_id=f"c{len(CREATORS_DATA) +1:03d}"new_creator= {"id": creator_id, **creator.dict()}
CREATORS_DATA[creator_id] =new_creatorbackground_tasks.add_task(notify_analytics, "creator_added", new_creator)
logger.info(f"Created creator: {creator_id}{creator.name}")
returnnew_creator@app.get("/campaigns")asyncdeflist_campaigns(status: Optional[str] =None, client: Optional[str] =None):
results=list(CAMPAIGNS_DATA.values())
ifstatus:
results= [cforcinresultsifc["status"] ==status.lower()]
ifclient:
results= [cforcinresultsifc["client"].lower() ==client.lower()]
return {"campaigns": results, "count": len(results)}
@app.get("/campaigns/{campaign_id}")asyncdefget_campaign(campaign_id: str):
ifcampaign_idnotinCAMPAIGNS_DATA:
raiseHTTPException(status_code=404, detail=f"Campaign {campaign_id} not found")
campaign=CAMPAIGNS_DATA[campaign_id].copy()
campaign["creator_details"] = [CREATORS_DATA[cid] forcidincampaign["creators"] ifcidinCREATORS_DATA]
returncampaign@app.post("/campaigns")asyncdefcreate_campaign(campaign: CampaignCreate, background_tasks: BackgroundTasks):
invalid= [cidforcidincampaign.creator_idsifcidnotinCREATORS_DATA]
ifinvalid:
raiseHTTPException(status_code=400, detail=f"Invalid creator IDs: {invalid}")
campaign_id=f"camp{len(CAMPAIGNS_DATA) +1:02d}"new_campaign= {"id": campaign_id, "client": campaign.client, "name": campaign.name, "status": "active", "budget": campaign.budget, "creators": campaign.creator_ids}
CAMPAIGNS_DATA[campaign_id] =new_campaignbackground_tasks.add_task(notify_analytics, "campaign_created", new_campaign)
logger.info(f"Created campaign: {campaign_id}{campaign.name} for {campaign.client}")
returnnew_campaign@app.get("/analytics/summary")asyncdefanalytics_summary():
cached=cache_get("analytics:summary")
ifcached:
returncachedtry:
asyncwithhttpx.AsyncClient(timeout=10) asclient:
resp=awaitclient.get(f"{ANALYTICS_SERVICE_URL}/summary")
ifresp.status_code==200:
data=resp.json()
cache_set("analytics:summary", data, ttl=60)
returndataexceptExceptionase:
logger.error(f"Analytics service unavailable: {e}")
total=len(CREATORS_DATA)
total_followers=sum(c["followers"] forcinCREATORS_DATA.values())
return {"source": "local_fallback", "total_creators": total, "total_followers": total_followers, "avg_followers": total_followers//totaliftotal>0else0, "active_campaigns": len([cforcinCAMPAIGNS_DATA.values() ifc["status"] =="active"])}

Requirements (creator-api/requirements.txt):

fastapi==0.115.0
uvicorn[standard]==0.30.0
pydantic==2.9.0
httpx==0.27.0
motor==3.6.0
redis==5.1.0

Service 2: Analytics Service (analytics-service/main.py)

"""Galleri5 Analytics ServiceReceives events from Creator API, computes aggregations.Stores event data in Redis and serves analytics endpoints."""fromfastapiimportFastAPIfromtypingimportOptionalimportosimporttimeimportjsonimportlogginglogging.basicConfig(
level=os.getenv("LOG_LEVEL", "INFO"),
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
logger=logging.getLogger("analytics-service")
app=FastAPI(title="Galleri5 Analytics Service", version="1.0.0")
REDIS_HOST=os.getenv("REDIS_HOST", "redis")
REDIS_PORT=int(os.getenv("REDIS_PORT", "6379"))
ENVIRONMENT=os.getenv("ENVIRONMENT", "development")
try:
importredisasredis_libredis_client=redis_lib.Redis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True, socket_timeout=3)
REDIS_AVAILABLE=TrueexceptImportError:
redis_client=NoneREDIS_AVAILABLE=FalseSTART_TIME=time.time()
event_counters= {"creator_added": 0, "campaign_created": 0, "total_events": 0}
recent_events= []
@app.get("/health")defhealth():
checks= {"status": "healthy", "service": "analytics", "version": os.getenv("APP_VERSION", "1.0.0"), "environment": ENVIRONMENT, "uptime_seconds": round(time.time() -START_TIME, 2)}
ifREDIS_AVAILABLEandredis_client:
try:
redis_client.ping()
checks["redis"] ="connected"exceptException:
checks["redis"] ="disconnected"checks["status"] ="degraded"else:
checks["redis"] ="not_configured"returnchecks@app.post("/events")asyncdefingest_event(event: dict):
event_type=event.get("event_type", "unknown")
timestamp=event.get("timestamp", time.time())
logger.info(f"Event received: {event_type}")
event_counters["total_events"] +=1event_counters[event_type] =event_counters.get(event_type, 0) +1ifREDIS_AVAILABLEandredis_client:
try:
redis_client.incr(f"analytics:count:{event_type}")
redis_client.incr("analytics:count:total")
redis_client.lpush("analytics:recent_events", json.dumps({"event_type": event_type, "timestamp": timestamp, "data_summary": str(event.get("data", {}))[:200]}))
redis_client.ltrim("analytics:recent_events", 0, 99)
redis_client.lpush("analytics:worker_queue", json.dumps(event))
exceptExceptionase:
logger.warning(f"Redis write failed: {e}")
recent_events.append({"event_type": event_type, "timestamp": timestamp})
iflen(recent_events) >100:
recent_events.pop(0)
return {"status": "accepted", "event_type": event_type}
@app.get("/summary")asyncdefget_summary():
ifREDIS_AVAILABLEandredis_client:
try:
return {"source": "redis", "total_events": int(redis_client.get("analytics:count:total") or0), "creators_added": int(redis_client.get("analytics:count:creator_added") or0), "campaigns_created": int(redis_client.get("analytics:count:campaign_created") or0)}
exceptException:
passreturn {"source": "memory", **event_counters}
@app.get("/events/recent")asyncdefget_recent_events(limit: int=20):
ifREDIS_AVAILABLEandredis_client:
try:
events=redis_client.lrange("analytics:recent_events", 0, limit-1)
return {"events": [json.loads(e) foreinevents], "source": "redis"}
exceptException:
passreturn {"events": recent_events[-limit:], "source": "memory"}
@app.get("/queue/size")asyncdefget_queue_size():
ifREDIS_AVAILABLEandredis_client:
try:
return {"queue_size": redis_client.llen("analytics:worker_queue"), "queue_name": "analytics:worker_queue"}
exceptException:
return {"queue_size": -1, "error": "redis_unavailable"}
return {"queue_size": -1, "error": "redis_not_configured"}

Requirements (analytics-service/requirements.txt):

fastapi==0.115.0
uvicorn[standard]==0.30.0
redis==5.1.0

Service 3: Worker (worker/main.py)

"""Galleri5 Analytics WorkerBackground worker that processes events from Redis queue."""importosimporttimeimportjsonimportsignalimportlogginglogging.basicConfig(
level=os.getenv("LOG_LEVEL", "INFO"),
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s"
)
logger=logging.getLogger("analytics-worker")
REDIS_HOST=os.getenv("REDIS_HOST", "redis")
REDIS_PORT=int(os.getenv("REDIS_PORT", "6379"))
QUEUE_NAME="analytics:worker_queue"POLL_INTERVAL=int(os.getenv("POLL_INTERVAL", "2"))
shutdown=Falsedefhandle_signal(signum, frame):
globalshutdownlogger.info(f"Received signal {signum}, shutting down gracefully...")
shutdown=Truesignal.signal(signal.SIGTERM, handle_signal)
signal.signal(signal.SIGINT, handle_signal)
defprocess_event(event: dict):
event_type=event.get("event_type", "unknown")
data=event.get("data", {})
logger.info(f"Processing event: {event_type}")
ifevent_type=="creator_added":
name=data.get("name", "unknown")
followers=data.get("followers", 0)
engagement=data.get("engagement_rate", 0)
score= (followers*engagement) /100logger.info(f"Computed score for {name}: {score:.0f}")
time.sleep(1)
elifevent_type=="campaign_created":
client=data.get("client", "unknown")
budget=data.get("budget", 0)
creator_count=len(data.get("creators", []))
logger.info(f"Campaign for {client}: budget {budget}, {creator_count} creators")
time.sleep(2)
else:
logger.warning(f"Unknown event type: {event_type}")
time.sleep(0.5)
try:
redis_client.incr(f"worker:processed:{event_type}")
redis_client.incr("worker:processed:total")
exceptExceptionase:
logger.warning(f"Failed to update processed count: {e}")
defmain():
globalredis_clientimportredisasredis_libredis_client=redis_lib.Redis(host=REDIS_HOST, port=REDIS_PORT, decode_responses=True, socket_timeout=5)
retries=0whileretries<30:
try:
redis_client.ping()
logger.info("Connected to Redis")
breakexceptException:
retries+=1logger.info(f"Waiting for Redis... (attempt {retries}/30)")
time.sleep(2)
else:
logger.error("Could not connect to Redis after 30 attempts")
returnlogger.info(f"Worker started. Polling queue: {QUEUE_NAME}")
whilenotshutdown:
try:
result=redis_client.brpop(QUEUE_NAME, timeout=5)
ifresult:
_, raw=resultevent=json.loads(raw)
process_event(event)
else:
logger.debug("Queue empty, waiting...")
exceptjson.JSONDecodeErrorase:
logger.error(f"Invalid JSON in queue: {e}")
exceptExceptionase:
logger.error(f"Worker error: {e}")
time.sleep(POLL_INTERVAL)
logger.info("Worker shutdown complete")
if__name__=="__main__":
main()

Requirements (worker/requirements.txt):

redis==5.1.0

Starting docker-compose.yml

This is a dev-quality compose file left by a developer. It works... sometimes. It needs production hardening.

version: "3.8"services:
creator-api:
build: ./creator-apiports:
- "8000:8000"environment:
- ENVIRONMENT=development
- MONGODB_URI=mongodb://mongo:27017
- REDIS_HOST=redis
- ANALYTICS_SERVICE_URL=http://analytics-service:8001
- API_SECRET=supersecretkey123depends_on:
- mongo
- redisanalytics-service:
build: ./analytics-serviceports:
- "8001:8001"environment:
- ENVIRONMENT=development
- REDIS_HOST=redis
- API_SECRET=supersecretkey123worker:
build: ./workerenvironment:
- REDIS_HOST=redismongo:
image: mongo:7ports:
- "27017:27017"# no auth, no volume — data lost on restartredis:
image: redis:7ports:
- "6379:6379"# no password, no persistence# nginx not configured — services exposed directly

Starting Dockerfiles

Each service has the same basic Dockerfile. These need production hardening.

# creator-api/Dockerfile and analytics-service/DockerfileFROM python:3.11
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
# Note: analytics-service should use port 8001
# worker/DockerfileFROM python:3.11
WORKDIR /app
COPY . .
RUN pip install -r requirements.txt
CMD ["python", "main.py"]

Smoke Test Script (scripts/test.sh)

#!/bin/bashset -e
BASE_URL="${1:-http://localhost:8000}"
ANALYTICS_URL="${2:-http://localhost:8001}"
PASS=0
FAIL=0
check() {
local desc="$1"local url="$2"local expected_code="${3:-200}"
response=$(curl -s -o /dev/null -w "%{http_code}""$url"2>/dev/null ||echo"000")if [ "$response"="$expected_code" ];thenecho"PASS: $desc (HTTP $response)"
PASS=$((PASS +1))elseecho"FAIL: $desc (expected $expected_code, got $response)"
FAIL=$((FAIL +1))fi
}
echo"=========================================="echo" Galleri5 Platform Smoke Test"echo"=========================================="echo""echo"--- Creator API ---"
check "Health check""$BASE_URL/health"
check "List creators""$BASE_URL/creators"
check "Get creator by ID""$BASE_URL/creators/c001"
check "Filter by platform""$BASE_URL/creators?platform=instagram"
check "Invalid creator 404""$BASE_URL/creators/invalid" 404
check "List campaigns""$BASE_URL/campaigns"
check "Get campaign""$BASE_URL/campaigns/camp01"
check "Analytics summary""$BASE_URL/analytics/summary"echo""echo"--- Analytics Service ---"
check "Analytics health""$ANALYTICS_URL/health"
check "Analytics summary""$ANALYTICS_URL/summary"
check "Recent events""$ANALYTICS_URL/events/recent"
check "Queue size""$ANALYTICS_URL/queue/size"echo""echo"--- Create Operations ---"
code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/creators" \ -H "Content-Type: application/json" \ -d '{"name":"Test Creator","platform":"instagram","followers":50000,"category":"fashion","engagement_rate":3.5}')if [ "$code"="200" ];thenecho"PASS: Create creator"; PASS=$((PASS+1));elseecho"FAIL: Create creator (got $code)"; FAIL=$((FAIL+1));fi
code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$BASE_URL/campaigns" \ -H "Content-Type: application/json" \ -d '{"client":"TestCorp","name":"Test Campaign","budget":100000,"creator_ids":["c001","c002"]}')if [ "$code"="200" ];thenecho"PASS: Create campaign"; PASS=$((PASS+1));elseecho"FAIL: Create campaign (got $code)"; FAIL=$((FAIL+1));fiecho""echo"Waiting 5s for worker to process..."
sleep 5
check "Analytics updated""$ANALYTICS_URL/summary"echo""echo"=========================================="echo" Results: $PASS passed, $FAIL failed"echo"=========================================="
[ "$FAIL"-gt 0 ] &&exit 1

Your Task

Your job is to take this from "developer's laptop" to "production-ready deployment."

Step 1 — Set Up & Fix (Required)

  1. Create the project structure with all the code above

  2. Make the existing setup production-worthy:

    • Dockerfiles: Optimize all 3 (multi-stage builds, small images, non-root users, proper layering)
    • docker-compose.yml: Fix the security and reliability issues:
      • MongoDB should have authentication and persistent volume
      • Redis should have a password and persistence
      • Secrets should NOT be hardcoded
      • Services should have health checks, restart policies, and resource limits
      • Add NGINX as reverse proxy in front of Creator API
    • Networking: Only NGINX should be exposed to the host. Other services should be internal only.
  3. Verify everything works: bash scripts/test.sh

Step 2 — Infrastructure as Code (Required)

Write Terraform code to provision cloud infrastructure for this platform.

We prefer Azure (since we're migrating to Azure), but GCP or AWS is acceptable.

Infrastructure should include:

  • Container runtime (AKS / Cloud Run / ECS — your choice, justify it)
  • Container registry
  • Managed Redis
  • Secrets management
  • Networking (VNet/VPC, subnets, security rules)
  • Logging configuration

You don't need to terraform apply — we'll review the code quality and design decisions.

Step 3 — Kubernetes Manifests (Required if you chose K8s in Step 2)

Write Kubernetes manifests to deploy all 3 services:

  • Deployments with health checks, resource limits, and proper env config
  • Services (ClusterIP for internal, Ingress for external)
  • HPA for Creator API (scale on CPU)
  • Secrets (not hardcoded)
  • Worker scaling strategy (mention KEDA if you know it)

If you chose serverless (Cloud Run / ECS Fargate), write the equivalent config.

Step 4 — CI/CD Pipeline (Required)

Create a GitHub Actions pipeline:

On Pull Request:

  • Lint Python code
  • Build all 3 images
  • Run smoke test against docker-compose

On Push to main:

  • Build and tag images (Git SHA, not latest)
  • Push to container registry
  • Deploy

Step 5 — Observability (Required)

At minimum:

  • Structured logging across all services
  • Health checks wired into infrastructure
  • Basic monitoring setup

Bonus: metrics, alert rules, dashboards

Step 6 — Documentation (Required)

Provide a SOLUTION.md explaining:

  • Architecture overview (diagram preferred)
  • Why you chose specific tools and configurations
  • Security improvements you made
  • How services communicate and what happens if one goes down
  • How you would scale this for 10x traffic
  • What you'd do differently with more time
  • Estimated monthly cloud costs
  • Time spent on each step

Bonus (Optional)

  • Auto-scaling configuration (HPA, KEDA)
  • Rollback strategy
  • Network policies
  • Rate limiting (NGINX or app level)
  • Container security scanning in CI/CD (Trivy)
  • Helm chart
  • Load testing script


Getting Started

# Create the project structure from the code above
mkdir -p galleri5-devops-task/{creator-api,analytics-service,worker,scripts}
# Copy each service's code, requirements.txt, and Dockerfile# Copy docker-compose.yml and test.sh# Then:
docker compose up --build
bash scripts/test.sh
# Now make it better.

Submission

Push a private GitHub repository containing:

Required

  • Fixed Dockerfiles (all 3 services)
  • Fixed docker-compose.yml (with NGINX, secrets, volumes, health checks)
  • NGINX configuration
  • Terraform code (terraform/ directory)
  • Kubernetes manifests or equivalent deployment config (k8s/ directory)
  • CI/CD pipeline (.github/workflows/)
  • Smoke test passing (scripts/test.sh should work with your setup)
  • SOLUTION.md with your architecture decisions

Optional (Bonus)

  • Monitoring setup (Prometheus/Grafana config)
  • Helm chart
  • Network policies
  • Load testing script
  • Architecture diagram

Steps

  1. Create a private GitHub repository
  2. Add sandeep.mannepalli@galleri5.com as a collaborator
  3. Share the repo link to sandeep.mannepalli@galleri5.com

Questions?

If anything is unclear, email us (sandeep.mannepalli@galleri5.com)

Good luck!

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors