Skip to content

Latest commit

Β 

History

History
346 lines (242 loc) Β· 15.7 KB

File metadata and controls

346 lines (242 loc) Β· 15.7 KB

Lab 1 β€” DevOps Info Service: Web Application Development

difficulty topic points languages

Goal: Build the seed of your DevOps Info Service β€” a small HTTP service that reports its own metadata + the host it runs on. Every later lab grows this same service. Deliverable: A PR from lab01 with app_python/ (and optionally app_go/). The image you build over the next 16 weeks starts here.


Overview

In this lab you will practice:

  • Picking a Python web framework and justifying the choice
  • Reading host/runtime info from platform, socket, os
  • Wiring HTTP routes that return JSON (not HTML)
  • Pinning dependencies, configuring via environment variables, writing real docs

⚠️ Scope: no database, no auth, no Docker yet. That comes in Lab 2+. Don't get clever β€” keep the code short and obvious.


Project State

You should have from previous labs:

  • Nothing β€” this is week 1.

This lab adds:

  • app_python/ β€” Python service with GET / (info) and GET /health (probe)
  • (optional bonus) app_go/ β€” compiled-language sibling with the same endpoints

By Lab 9 this service runs on Kubernetes; by Lab 13 ArgoCD deploys it. So the choices you make this week β€” framework, layout, env-var config β€” outlive Lab 1.


Setup

You need only:

python3 --version         # 3.12+ (course standardizes on 3.13)
pip3 --version

πŸ“ python vs python3: on Debian/Ubuntu the binary is python3; on macOS/Windows it may be python. Pick whichever exists on your box β€” the rest of this lab writes python app.py for brevity, substitute python3 app.py if that's how your system spells it.

Create the directory layout (you'll fill the files yourself):

app_python/
β”œβ”€β”€ app.py
β”œβ”€β”€ requirements.txt
β”œβ”€β”€ .gitignore
β”œβ”€β”€ README.md
β”œβ”€β”€ tests/
β”‚   └── __init__.py            # empty; tests come in Lab 3
└── docs/
    └── LAB01.md               # your submission report

Task 1 β€” Python Web Application (6 pts)

1.1 β€” Pick a framework and justify it

You may use Flask 3.1, FastAPI 0.136, or Django 5.2 (overkill for this; do it if you want the practice). In docs/LAB01.md, give your one-paragraph reasoning + a small comparison table covering at least: footprint, async support, OpenAPI/docs, learning curve.

1.2 β€” GET / returns the full info JSON

YOUR TASK: write a handler at the root path that returns JSON with five top-level keys: service, system, runtime, request, endpoints.

Requirements:

  • service: name, version, description, framework (your choice from 1.1)
  • system: hostname, platform, platform_version, architecture, cpu_count, python_version
  • runtime: uptime_seconds (int), uptime_human (e.g. "1 hour, 3 minutes"), current_time (ISO 8601 UTC), timezone
  • request: client_ip, user_agent, method, path
  • endpoints: list of {path, method, description} for every route you publish

Hints:

  • platform.system(), platform.machine(), platform.python_version() cover most of system
  • socket.gethostname() for the hostname
  • Uptime = datetime.now(timezone.utc) - START_TIME captured at module import β€” be timezone-aware throughout (mixing naive + aware datetimes raises TypeError)
  • Per-framework request access: Flask β†’ request.remote_addr / request.headers.get('User-Agent'); FastAPI β†’ request.client.host / request.headers.get('user-agent')
πŸ’‘ Stuck on the structure?

Sketch β€” no Python code given, intentionally:

imports β†’ app object β†’ constants (SERVICE_NAME, START_TIME, ...) β†’ helper get_system_info() β†’ helper get_uptime() β†’ route handler returning the 5-key dict β†’ error handlers β†’ __main__ block

Lecture 1 slide on the lifecycle, lecture 2 on configuration β€” both apply here.

1.3 β€” GET /health returns a small JSON + HTTP 200

YOUR TASK: write a second handler that returns {status, timestamp, uptime_seconds} and status code 200. This will be your Kubernetes liveness/readiness target in Lab 9 β€” keep it cheap (no DB calls, no external HTTP).

1.4 β€” Configurable via environment variables

YOUR TASK: read HOST, PORT, DEBUG from the environment with sensible defaults (0.0.0.0, 5000, False). The app must run unchanged with:

python app.py                            # default 0.0.0.0:5000
PORT=8080 python app.py                  # custom port
HOST=127.0.0.1 PORT=3000 python app.py   # both

Hint: cast PORT to int and DEBUG to bool by lowercase string comparison (os.getenv("DEBUG","False").lower() == "true") β€” bool("False") is True in Python; that's the easy way to ship a debug-mode bug to production.

1.5 β€” Error handlers and logging

YOUR TASK: register handlers for 404 and 500 that return JSON (matching the rest of the API), and configure stdlib logging at INFO level with a format including timestamp/level. Don't print().

1.6 β€” Proof of work

Paste into docs/LAB01.md:

  • Four CLI captures, with the exact commands you ran:
    • curl -s http://localhost:5000/ | jq . β€” full JSON, your real hostname + uptime
    • curl -s http://localhost:5000/health | jq .
    • curl -s -o /dev/null -w "HTTP %{http_code}\n" http://localhost:5000/nope β€” proves the 404 handler
    • PORT=8080 python app.py started in another shell + a curl showing the new port served β€” proves env-var config
  • The framework comparison table from 1.1
  • 2–3 sentences on the biggest gotcha you hit and how you solved it
πŸ’‘ Hints if you're stuck
  • Make the framework choice early β€” switching mid-lab burns time.
  • Build GET / last. Build GET /health first (5 lines), then incrementally add system info, uptime, request info.
  • If JSON output looks "weird" (e.g., bytes prefix b'...'), you're returning a string instead of a dict β€” let the framework jsonify.

Task 2 β€” Documentation & Best Practices (4 pts)

2.1 β€” app_python/README.md

YOUR TASK: write the user-facing README with these sections (no fluff, prefer commands over prose):

  1. Overview β€” one paragraph
  2. Prerequisites β€” Python version, OS
  3. Installation β€” python -m venv venv … pip install -r requirements.txt
  4. Running β€” default + custom-port examples
  5. API Endpoints β€” table of Method | Path | Description
  6. Configuration β€” table of env vars + defaults + purpose

2.2 β€” Code-quality hygiene

YOUR TASK: ship the things every Python service needs.

File Must contain
requirements.txt The framework pinned to an exact patch (e.g. Flask==3.1.3, not Flask>=3). One framework only β€” drop the rest.
.gitignore __pycache__/, *.pyc, venv/, *.log, IDE dirs (.vscode/, .idea/), .DS_Store
Imports in app.py Grouped (stdlib β†’ third-party β†’ local), PEP 8 compliant

2.3 β€” app_python/docs/LAB01.md β€” your submission report

Required sections, in order:

  1. Framework Selection β€” your choice + comparison table
  2. Best Practices Applied β€” bulleted list, each with one sentence of why
  3. API Documentation β€” request + response example per endpoint
  4. Testing Evidence β€” the four CLI captures from 1.6
  5. Challenges & Solutions β€” at least one real one (not "I was new to Python")
  6. GitHub Community β€” one paragraph; see 2.4

2.4 β€” GitHub community engagement

This is the social half of being a developer. The actions are public on your profile.

YOUR TASK:

  1. Star the course repository.
  2. Star the simple-container-com/api project β€” a promising open-source tool for container management.
  3. Follow your professor and TAs on GitHub:
  4. Follow β‰₯ 3 classmates from this course.
  5. In your docs/LAB01.md "GitHub Community" section, put your GitHub username on the first line (so the TA can verify your stars/follows in <30s) then write 2–3 sentences answering: Why does starring a project actually help its maintainers? What concrete benefit do you get from following other developers? β€” your own words, not the lecture's.

2.5 β€” Proof of work

Paste into docs/LAB01.md:

  • The docs/LAB01.md itself satisfies all six required sections β€” that is the proof.
  • Output of find app_python -maxdepth 2 -type f | sort showing every required file is present.

Bonus Task β€” Compiled-Language Sibling (2 pts)

Re-implement the same service in a compiled language, in a sibling directory (e.g. app_go/).

Why bother? Lab 2's multi-stage Docker bonus shrinks a Go service from ~900 MB to ~15 MB β€” that doesn't work without a static binary to start with.

YOUR TASK:

  • Same two endpoints (/, /health)
  • Same JSON shape as your Python version (so the same curl calls work against both)
  • A README in the sibling dir explaining build + run
  • A go.mod (and go.sum if you pull deps) β€” Lab 2's bonus needs them as the build context
  • In docs/LAB01.md, add a one-line artifact size for both. Measure the same way for each so the comparison is apples-to-apples:
    • Python: du -sh app_python/venv (the venv carries everything beyond the interpreter)
    • Compiled: ls -lh app_go/<binary> (the single static binary)
    • The size delta is the whole point. The real Docker-image comparison comes in Lab 2.

Choose one of:

Language Idiomatic HTTP Notes
Go (recommended) net/http Single static binary; instant compile; smallest distroless image later
Rust actix-web / axum Strong types; longer compile; great for the security-minded
Java + Spring Boot spring-boot-starter-web Industry-standard for enterprise β€” heavier runtime
C# + ASP.NET Core WebApplication.Create Cross-platform .NET; comparable to Java but newer ergonomics

Hints (no full code):

  • For Go, runtime.NumCPU(), runtime.GOOS, runtime.GOARCH, os.Hostname() mirror Python's os/platform. JSON keys differ in casing β€” use struct tags (json:"hostname") to keep the same wire shape.
  • For Java/C#, start from spring init / dotnet new web β€” don't hand-write pom.xml/csproj.

How to Submit

git switch -c lab01
git add app_python/
git add app_go/                   # only if you did the bonus
git commit -m "feat(lab01): devops info service β€” python (+ go bonus)"
git push -u origin lab01

Open two PRs:

  • your-fork:lab01 β†’ course-repo:master (reviewed)
  • your-fork:lab01 β†’ your-fork:master (merges into your own main when done)

PR checklist:

- [ ] Task 1 done β€” /, /health, env config, error handlers, logging
- [ ] Task 2 done β€” README, requirements.txt, .gitignore, docs/LAB01.md, GitHub social
- [ ] Bonus done β€” app_go/ (or other) with same endpoints + size comparison

Acceptance Criteria

Task 1 (6 pts)

  • βœ… Service starts with python app.py and serves on 0.0.0.0:5000
  • βœ… GET / returns HTTP 200 with all five top-level keys populated from your real machine
  • βœ… GET /health returns HTTP 200 with the three required fields
  • βœ… 404 handler returns JSON (not HTML)
  • βœ… PORT env var overrides the port
  • βœ… Logging is configured (you'll see startup log line in stdout)

Task 2 (4 pts)

  • βœ… README.md has all six sections
  • βœ… requirements.txt pins an exact version
  • βœ… .gitignore covers Python + IDE + OS artifacts
  • βœ… docs/LAB01.md has all six sections including the GitHub Community paragraph
  • βœ… Stars + follows visible on student's GitHub profile

Bonus Task (2 pts)

  • βœ… Sibling app builds and serves the same two endpoints
  • βœ… Wire-compatible JSON (same curl … | jq works against both)
  • βœ… Artifact-size comparison documented (du -sh venv vs ls -lh <binary>)
  • βœ… Build manifest present (go.mod / Cargo.toml / pom.xml / *.csproj) so Lab 2's bonus has a build context to COPY

Rubric

Task Points Criteria
Task 1 β€” Python web app 6 Both routes correct, env-var config, error handlers, logging
Task 2 β€” Docs & hygiene 4 All file/section requirements met; pinned deps; complete LAB01.md
Bonus β€” Compiled sibling 2 Same endpoints, wire-compatible JSON, size comparison
Total 12 10 main + 2 bonus

Resources

πŸ“š Documentation
⚠️ Common Pitfalls (from real dry-runs)
  • Naive vs aware datetimes β€” datetime.now() - START_TIME raises TypeError if one is timezone-aware and the other isn't. Pick one (use datetime.now(timezone.utc) everywhere) and stay there.
  • bool("False") == True β€” never write DEBUG = bool(os.getenv("DEBUG", "False")). Use a lowercase string compare.
  • Flask 3 deprecation warning on flask.__version__ β€” use importlib.metadata.version("flask") if you need to print the framework version.
  • Returning a dict directly from FastAPI vs Flask β€” FastAPI auto-serializes; Flask needs jsonify(...). Don't return a bare dict from a Flask handler.
  • socket.gethostname() inside a container later returns the container ID, not your laptop name β€” that's correct, not a bug. You'll see it again in Lab 2.
  • Port 5000 occupied on macOS β€” macOS Monterey+ runs the AirPlay Receiver on :5000. The Flask default conflicts; you'll see your curl hit Apple's service instead. Either turn AirPlay Receiver off (System Settings β†’ General β†’ AirDrop & Handoff) or pick PORT=5050 for your default run.
  • pip install -r requirements.txt outside the venv β€” installs Flask system-wide, then python app.py may still pick up a stale Flask elsewhere. Always activate the venv (source venv/bin/activate) before pip install and before python app.py.
  • Don't return a Response from @app.errorhandler's default β€” Flask's 500 handler must return a tuple (body, status). Returning a bare jsonify(...) sends HTTP 200 with the error JSON β€” a subtle bug that breaks the Lab 9 probe later.
πŸ› οΈ Dev tools worth knowing
  • jq β€” JSON CLI; curl ... | jq . is your friend
  • HTTPie β€” friendlier than curl for ad-hoc testing
  • Ruff β€” fast Python linter (used in Lab 3 CI)

Looking Ahead

Lab What it adds to this service
2 Multi-stage Dockerfile, image scan, push to a registry
3 CI: pytest + lint + image build + Trivy gate on every PR
7 / 8 Structured JSON logs (Loki + Alloy) and a /metrics endpoint (Prometheus)
9 / 10 Deploy to Kubernetes (k3d) + Helm 4 chart
11 / 12 OpenBao for secrets, ConfigMaps + PVCs for state
13 / 14 ArgoCD GitOps + canary rollouts

Keep the code simple. You'll come back and rewrite parts of it; that's the point.