Skip to content

This project was originally developed as coursework at the University of Tartu.

License

Notifications You must be signed in to change notification settings

bykeny/reddit-like

Repository files navigation

Reddit-like Full-Stack Application

Tech Stack & Architecture

Backend
Django Python DRF Django Channels Linting Formatting

Frontend
Vue.js TypeScript Vite Axios Linting Formatting

Infrastructure & Services
Ubuntu Nginx Redis Let's Encrypt Kubernetes Docker

Database & Storage
SQLite PostgreSQL Longhorn

DevOps & CI/CD
GitLab CI Ansible Kaniko

Monitoring & Observability
Prometheus Grafana

Testing & Quality
Coverage Tests

Security & Auth
JWT HTTPS

License
License

A full-stack Reddit-like social media platform with real-time updates, user authentication, post privacy controls, and image uploads. Deployed on both traditional VM infrastructure (Weeks 1-8) and Kubernetes (Weeks 9-11).


Table of Contents


Quick Start (Kubernetes)

# Clone and setup
export KUBECONFIG=k8s/cicd-serviceaccount-kubeconfig
kubectl get pods -n team-30-staging

# Access application
curl https://team30-nginx.kubernetes.devops.cs.ut.ee/healthz

# Deploy (push to main branch triggers CI/CD or apply manually)
kubectl apply -f k8s/ -n team-30-staging

High-Level Architecture

Frontend & Backend Separation

The application is fully decoupled:

  • Frontend: Vue 3 + TypeScript SPA served via Nginx
  • Backend: Django REST Framework API with WebSocket support via Django Channels

Tech Stack

Layer Technology
Frontend Vue 3.5, TypeScript 5.9, Vite 7.1, Axios, Vue Router
Backend Django 5.2, Django REST Framework 3.16, Django Channels 4.3, Daphne 4.2
Database PostgreSQL (swapped from embedded SQLite3)
Real-time WebSockets via Django Channels + Redis (InMemoryChannelLayer in dev)
Auth JWT (via djangorestframework-simplejwt)
CI/CD GitLab CI with stages: setup → build → test → deploy
Infra Ubuntu 22.04 VM, Nginx, Ansible, Let's Encrypt (HTTPS)
Testing Django TestCase, coverage

Application Features

API Endpoints (/api/[Endpoint])

Method Endpoint Description Auth Required
POST /auth/register/ Register new user No
POST /auth/login/ Login with JWT No
POST /auth/logout/ Client-side token deletion Yes
POST /auth/change-password/ Change password Yes
GET /posts/ List posts (public + own if authed) No (read-only)
POST /posts/ Create new post Yes
POST /posts/{id}/like/ Like a post Yes
POST /posts/{id}/dislike/ Dislike a post Yes
POST /posts/{id}/toggle_privacy/ Toggle post privacy Yes (author)
DELETE /posts/{id}/ Delete post Yes (author)

All endpoints respect privacy: unauthenticated users see only public posts.

Key Backend Functions

The backend implements several critical functions that enforce business logic, ensure data integrity, and enable real-time features:

  • PostViewSet.perform_create(): Saves new posts and triggers WebSocket broadcast via broadcast_post_created().
  • PostViewSet.like() / dislike(): Use atomic database transactions and row-level locking (select_for_update) to safely handle concurrent votes.
  • PostViewSet.get_queryset(): Filters posts based on authentication and privacy (is_private), ensuring users only see allowed content.
  • broadcast_post_*() (in utils.py): Send serialized post data to WebSocket groups for real-time updates.
  • FeedConsumer event handlers: Receive ORM-triggered events and push them to connected clients over WebSockets.
  • Post.clean(): Validates post content, title, and image at the model level to enforce business rules.
  • Post.can_be_modified_by(): Centralizes ownership checks for edit/delete operations.
  • PostSerializer.get_user_vote(): Dynamically annotates each post with the current user’s vote status (like/dislike/neutral).

Full auto-generated API docs can be added via drf-spectacular (not included yet).


Authentication System

  • Registration & Login: JWT-based via djangorestframework-simplejwt
  • Password Hashing: Uses Django’s default PBKDF2-SHA256 (secure, FIPS-compliant)
  • Session Handling: Stateless JWT; no server-side sessions
  • Post Ownership:
    • All posts are linked to a User via author foreign key
    • is_anonymous is a display flag — the real author is always known server-side
    • Private posts (is_private=True) are only visible to the author

Security: Passwords are never stored in plaintext. All auth endpoints validate input and use secure hashing.


Real-Time Updates

  • Technology: Django Channels + WebSockets
  • Flow:
    1. User performs action (e.g., likes a post)
    2. Backend updates DB and calls broadcast_post_updated(post)
    3. broadcast_* functions send serialized post data to feed_updates channel group
    4. All connected WebSocket clients receive update instantly
  • Frontend: websocket.ts manages connection, reconnection, and event dispatching

Supports real-time: post creation, voting, privacy toggle, and deletion.


Database & Data Model

### Why SQLite?

  • Lightweight, file-based, zero-config — ideal for development and small-scale deployment
  • Easily replaceable with PostgreSQL in production (just change DATABASES in settings.py)

Why PostgreSQL?

  • Production-ready: Robust, reliable, battle-tested
  • Scalability: Better performance with larger datasets
  • Concurrency: Handles concurrent connections safely with ACID transactions
  • Separation of Concerns: Database runs in its own container (resilience, independent scaling)

Architecture:

  • Database: PostgreSQL 16 running as a Kubernetes StatefulSet (1 replica)
  • Storage: 1Gi Longhorn persistent volume (postgres-storage-postgres-0)
  • Credentials: Managed via app-secret Kubernetes Secret (username: app, password: manually generated)
  • Configuration: app-config ConfigMap stores connection details (DB_HOST, DB_PORT, DB_NAME)

Schema Overview

Post

author = ForeignKey(User)
is_anonymous = BooleanField()
is_private = BooleanField()
title = CharField(max_length=50)
content = TextField(blank=True)
image = ImageField(upload_to="post_images/", blank=True, null=True)
created_at = DateTimeField(auto_now_add=True)

Vote

user = ForeignKey(User)
post = ForeignKey(Post)
vote_type = CharField(choices=[("like", "Like"), ("dislike", "Dislike")])
created_at = DateTimeField(auto_now_add=True)
# Unique constraint: (user, post)

Validation: Custom clean() methods enforce business rules (e.g., title required, image ≤5MB).


Private Posts & Authorization

  • Visibility:
    • Public posts: visible to all
    • Private posts: only visible to authenticated author
  • Modification:
    • Post.can_be_modified_by(user) checks self.author == user
    • Backend enforces this in toggle_privacy and destroy actions
  • Frontend: Privacy toggle button only appears for author

No client-side bypass possible — all checks are enforced server-side.


Image Storage

  • Uploads: Handled via Django’s ImageField
  • Storage: Files saved to ./media/post_images/ on the VM
  • Serving: Nginx serves /media/ directly (bypassing Django)
  • Validation:
    • Max size: 5 MB
    • Allowed formats: JPEG, PNG, GIF
    • At least content or image must be provided

Development Workflow

Local Setup (Full Stack)

  1. Clone repo

    git clone <repo-url>
    cd devops2025
  2. Backend (Django)

    cd reddit_like
    python -m venv venv
    source venv/bin/activate  # Linux/macOS
    pip install -r ../requirements.txt
    python manage.py migrate
    python manage.py runserver  # Runs on http://127.0.0.1:8000
  3. Frontend (Vue)

    cd ../reddit_like_frontend/reddit_like_frontend
    npm install
    npm run dev  # Runs on http://localhost:5173
  4. Access App: Open http://localhost:5173

Hot-reload enabled for both frontend (Vite) and backend (Django dev server).

Testing & Linting

Task Command
Run all tests cd reddit_like && python manage.py test
Code coverage coverage run --source='.' manage.py test
Backend lint ruff check .
Backend format ruff format .
Frontend lint npm run lint:check
Frontend format npm run format:check
TS check npm run vue-tsc

Pre-commit hooks auto-lint/format on git commit (run pre-commit install).


Deployment & Infrastructure

Legacy VM Deployment (Weeks 1-8)

  • OS: Ubuntu 22.04
  • Web Server: Nginx (serves static frontend + proxies /api/ to Daphne)
  • App Server: Daphne (ASGI for WebSockets + HTTP)
  • Process Manager: systemd (daphne.service)
  • Firewall: UFW (allows 22, 80, 443)
  • HTTPS: Let's Encrypt via Certbot (auto-renewal)
  • URL: https://team30vm.cloud.ut.ee

Kubernetes Deployment (Weeks 9-11)

Week 9: Containerization & Kubernetes Deployment

Container Image Building

The GitLab CI/CD pipeline uses Kaniko to build Docker images without requiring Docker daemon access.

Build Stages:

  • build_backend: Builds Django backend from reddit_like/Dockerfile
  • build_frontend: Multi-stage build (Node.js → Nginx) from reddit_like_frontend/Dockerfile
  • Image Tags: $CI_COMMIT_SHA and latest
  • Registry: registry.gitlab.cs.ut.ee/devops/teams/team-30/devops2025/
Kubernetes Deployment Architecture

Key Manifest Files:

  • k8s-backend-deployment.yaml / k8s-frontend-deployment.yaml - Pod definitions with health probes
  • k8s-backend-service.yaml / k8s-frontend-service.yaml - Internal services (ClusterIP)
  • nginx-ingress.yaml - External routing with TLS termination
  • django-config.yaml / django-secret.yaml - Configuration and secrets
  • k8s-media-pvc.yaml - 5Gi persistent storage for media files
  • postgres-cluster.yaml - CloudNativePG database cluster

Private Registry Authentication: Deployments use imagePullSecrets: gitlab-registry to pull images from GitLab Container Registry. Secret created with GitLab Deploy Token.


Week 10: Advanced Kubernetes Configuration

High-Level Kubernetes Architecture

Traffic Flow Diagram:

                                    ┌─────────────────────────────────────┐
                                    │  External User (HTTPS)              │
                                    └───────────────┬─────────────────────┘
                                                    │
                                    ┌───────────────▼─────────────────────┐
                                    │  Nginx Ingress Controller           │
                                    │  (TLS Termination, Let's Encrypt)   │
                                    │  team30-nginx.kubernetes...         │
                                    └───────┬──────────────┬──────────────┘
                                            │              │
                            ┌───────────────┴───┐      ┌───▼──────────────┐
                            │  Path: /          │      │  Path: /api      │
                            │  Path: /ws        │      │  Path: /media    │
                            │  Path: /healthz   │      │  Path: /readyz   │
                            │                   │      │  Path: /metrics  │
                            └───────┬───────────┘      └───┬──────────────┘
                                    │                      │
                    ┌───────────────▼──────────┐   ┌───────▼────────────────┐
                    │  vue-frontend-svc        │   │  django-backend-svc    │
                    │  (ClusterIP:80)          │   │  (ClusterIP:8000)      │
                    └───────────┬──────────────┘   └───────┬────────────────┘
                                │                          │
                    ┌───────────▼──────────────┐   ┌───────▼────────────────┐
                    │  vue-frontend Deployment │   │  django-backend Deploy │
                    │  (1 replica)             │   │  (1 replica)           │
                    │  Nginx container         │   │  Daphne ASGI container │
                    └──────────────────────────┘   └───────┬────────────────┘
                                                            │
                                                    ┌───────▼────────────────┐
                                                    │  PostgreSQL Deployment │
                                                    │     (StatefulSet)      │
                                                    │           +            │
                                                    │(CloudNativePG Cluster )│
                                                    │           +            │
                                                    │     Longhorn PVC       │
                                                    └────────────────────────┘

Component Responsibilities:

  • Ingress (nginx-ingress.yaml):

    • TLS Termination: Manages Let's Encrypt SSL certificates via cert-manager
    • Host-based Routing: Routes traffic for team30-nginx.kubernetes.devops.cs.ut.ee
    • Path-based Routing:
      • /vue-frontend-svc:80 (frontend static files)
      • /apidjango-backend-svc:8000 (REST API)
      • WebSocket connections also proxied to backend
    • Annotations: kubernetes.io/ingress.class: nginx, cert-manager.io/cluster-issuer: letsencrypt-prod
  • Services (ClusterIP):

    • vue-frontend-svc: Internal load balancer for frontend pods (port 80)
    • django-backend-svc: Internal load balancer for backend pods (port 8000)
    • Services use label selectors to discover pods: selector: app: django-backend
  • Deployments:

    • vue-frontend: Manages Nginx containers serving Vue.js SPA
    • django-backend: Manages Daphne ASGI containers with Django app
    • Deployments ensure desired replica count (1 pod each currently)
    • Use Recreate strategy for backend to avoid PVC conflicts
Configuration Management

ConfigMaps:

  • django-config: Application settings (MEDIA_ROOT, DJANGO_DEBUG, ALLOWED_HOSTS, DATABASE_URL)
  • app-config: Database connection details (DB_HOST, DB_PORT, DB_NAME)

Secrets:

  • django-secret: Django secret key (base64-encoded)
  • app-secret: PostgreSQL credentials (username, password)

GitLab CI/CD Variables:

  • KUBE_URL, KUBE_TOKEN, KUBE_CA, KUBE_NAMESPACE - Kubernetes authentication
  • All sensitive values are masked and protected
  • Deployment applies manifests via kubectl apply -f k8s/
CI/CD Pipeline Automation

Deployment Process:

  1. Generate kubeconfig from CI/CD variables (KUBE_URL, KUBE_TOKEN, KUBE_CA)
  2. Run kubectl apply -f k8s/ -n team-30-staging
  3. Applies all manifests (Deployments, Services, Ingress, ConfigMaps, Secrets, PVC)

RBAC Setup:

  • ServiceAccount: cicd-serviceaccount with deployment permissions
  • Role grants: get, list, watch, create, update, patch, delete on resources
  • Authentication via long-lived ServiceAccount token

Week 11: Observability, Storage & Database Separation

Database Separation: SQLite → PostgreSQL

Why PostgreSQL?

  • Production-ready: Robust, reliable, battle-tested
  • Scalability: Better performance with larger datasets
  • Concurrency: Handles concurrent connections safely with ACID transactions
  • Separation of Concerns: Database runs in its own container (resilience, independent scaling)

Architecture:

  • Database: PostgreSQL 16 running as a Kubernetes StatefulSet (1 replica)
  • Storage: 1Gi Longhorn persistent volume (postgres-storage-postgres-0)
  • Credentials: Managed via app-secret Kubernetes Secret (username: app, password: auto-generated)
  • Configuration: app-config ConfigMap stores connection details (DB_HOST, DB_PORT, DB_NAME)

Code Changes:

# reddit_like/settings.py - Dynamic database selection
DB_ENGINE = os.getenv("DB_ENGINE", "django.db.backends.sqlite3")

if DB_ENGINE == "django.db.backends.postgresql":
    DATABASES = {
        "default": {
            "ENGINE": "django.db.backends.postgresql",
            "NAME": os.getenv("DB_NAME", "appDB"),
            "USER": os.getenv("DB_USER", "app"),
            "PASSWORD": os.getenv("DB_PASSWORD", ""),
            "HOST": os.getenv("DB_HOST", "localhost"),
            "PORT": os.getenv("DB_PORT", "5432"),
        }
    }
else:
    DATABASES = {
        "default": {
            "ENGINE": "django.db.backends.sqlite3",
            "NAME": BASE_DIR / "db.sqlite3",
        }
    }

Manifest Files:

  • postgres-deployment.yaml - StatefulSet for PostgreSQL 16-alpine with health probes
  • postgres-service.yaml - Headless service for DNS-based pod discovery
  • django-migration-job.yaml - One-time job to run python manage.py migrate against new PostgreSQL

Data Persistence:

  • All Django models (Post, Vote, User, etc.) migrated to PostgreSQL
  • Tables persist across Pod restarts thanks to StatefulSet + Longhorn PVC
  • Tested: Pod deletion and restart — all data remains intact

Health Check Implementation

Endpoints:

  • /healthz (Liveness): Returns 200 OK if app process is alive. Kubernetes restarts pod on failure.
  • /readyz (Readiness): Returns 200 OK if database is accessible, 503 otherwise. Updates django_db_ready metric.

Probe Configuration (k8s-backend-deployment.yaml):

livenessProbe:
  httpGet:
    path: /healthz
    port: 8000
  initialDelaySeconds: 15
  periodSeconds: 10
  timeoutSeconds: 2
  failureThreshold: 3

readinessProbe:
  httpGet:
    path: /readyz
    port: 8000
  initialDelaySeconds: 20
  periodSeconds: 10
  timeoutSeconds: 2
  failureThreshold: 3

How It Works:

  • Liveness Probe (every 10s): If fails 3 times → Kubernetes restarts pod
  • Readiness Probe (every 10s): If fails → pod removed from service load balancing (traffic rerouted)
  • Both probes help Kubernetes maintain application health automatically

Testing:

# Terminal 1: Port-forward backend
kubectl port-forward -n team-30-staging deployment/django-backend 8000:8000

# Terminal 2: Test endpoints
curl -v http://localhost:8000/healthz  # Should return 200 with {"status": "alive"}
curl -v http://localhost:8000/readyz   # Should return 200 with {"status": "ready", "database": "connected"}

Metrics & Monitoring

Custom Metrics (exposed at /metrics on port 8000):

  1. app_posts_total - Total posts in database

    • Benefit: Shows user interaction and verifies posting functionality is working
  2. app_votes_total - Total votes (likes + dislikes)

    • Benefit: Shows user engagement and verifies voting functionality is working
  3. http_request_latency_seconds - Request latency histogram

    • Benefit: Monitors P90 latency (how long it takes to serve 90% of requests) to detect if performance is affecting user experience
  4. http_requests_total - Total HTTP requests by method/path/status

    • Benefit: Shows requests per endpoint (detect unusual activities) and requests per second (detect service overload or downtime)
  5. django_db_ready (bonus) - Database readiness (1=ready, 0=not ready)

    • Benefit: Monitors database connectivity in real-time

Prometheus Scraping:

# k8s-backend-service.yaml
apiVersion: v1
kind: Service
metadata:
  name: django-backend-svc
  namespace: team-30-staging
  annotations:
    prometheus.io/scrape: "true"
    prometheus.io/port: "8000"
    prometheus.io/path: "/metrics"
spec:
  selector:
    app: django-backend
  ports:
    - name: http
      port: 8000
      targetPort: 8000

Grafana Dashboard (14 Panels):

  1. HTTP Error Distribution by Code & Endpoint (Last 5m)

    • Shows: Tracks HTTP errors (4xx/5xx) by status code and API path over time.
    • Usage: Helps identify problematic endpoints.
  2. Top 5 API Endpoints by Request Rate (Last 5m)

    • Shows: The top 5 endpoints by request rate (over the last 5 minutes). The endpoints are filtered to specific paths (like post-list, post-detail, etc.).
    • Usage: Highlights high-traffic areas for optimization.
  3. API Success Rate (2xx Responses, Last 5m)

    • Shows: The percentage of successful HTTP requests (status codes 2xx) over the last 5 minutes.
    • Usage: Aim for >95% (green). Yellow warns of potential issues; red indicates high failures.
  4. API Error Rate (4xx/5xx, Last 5m)

    • Shows: The percentage of HTTP errors (status codes 4xx and 5xx) over the last 5 minutes.
    • Usage: Monitor for spikes in user-facing errors.
  5. PostgreSQL Database Storage Usage (%)

    • Shows: The percentage of used storage for the PostgreSQL database.
    • Usage: Monitor to avoid capacity issues.
  6. Media Storage Usage (Images/GIFs, %)

    • Shows: The percentage of used storage for media (like images, gifs).
    • Usage: Monitor to avoid capacity issues.
  7. PostgreSQL Database Status

    • Shows: 1 = Up and ready; 0 = Down or unreachable. Based on app's DB health check.
    • Usage: Monitor database connectivity.
  8. Application Pod Status

    • Shows: 1 = App is up and scraped by Prometheus; 0 = Down.
    • Usage: Monitor application availability.
  9. 90th Percentile API Response Time (Last 5m)

  • Shows: The 90th percentile of HTTP request latency over the last 5 minutes. The 90th percentile response time indicates that 90% of the recorded response times fall below a specific duration.
  • Usage: spikes may indicate performance bottlenecks
  1. Total HTTP Requests per Second
  • Shows: The overall rate of HTTP requests per second.
  • Usage: Useful for spotting usage peaks or drops.
  1. Application Memory Usage (RSS)
  • Shows: The resident memory usage of the application.
  • Usage: Monitor for leaks; compare to pod limits.
  1. Application CPU Usage (Cores)
  • Shows the CPU usage (rate of CPU seconds) of the application.
  • Usage: Values >1 indicate high load across replicas.
  1. Total Posts Created
  • Shows: Cumulative count of posts created in the application.
  • Usage: Tracks user engagement and content creation trends.
  1. Total User Votes
  • Shows: Cumulative count of votes (likes + dislikes) cast by users.
  • Usage: Monitors user interaction and engagement levels.

Dashboard URL: https://grafana.kubernetes.devops.cs.ut.ee/ (Team 30 dashboard)


Persistent Storage

Storage Architecture:

Component Storage Type Size Purpose Persistence
PostgreSQL DB Longhorn PVC 1Gi Stores all application data (posts, votes, users) ✅ Persistent across restarts
Media Files Longhorn PVC 5Gi User-uploaded images ✅ Persistent across restarts

Longhorn Implementation:

# postgres-deployment.yaml
volumeClaimTemplates:
  - metadata:
      name: postgres-storage
    spec:
      storageClassName: longhorn
      accessModes:
        - ReadWriteOnce
      resources:
        requests:
          storage: 1Gi

# k8s-media-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: media-pvc
  namespace: team-30-staging
spec:
  storageClassName: longhorn
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 5Gi

How Longhorn Works:

  1. Replication: Data automatically replicated across cluster nodes (default 3 replicas)
  2. Snapshots: Supports backup snapshots for disaster recovery
  3. Expansion: Volumes can be expanded online without downtime
  4. Monitoring: Integrated with Prometheus for storage metrics

Data Persistence Verification:

# 1. Get current post count
kubectl exec -it deployment/django-backend -n team-30-staging -- python manage.py shell -c "from feed.models import Post; print(f'Posts: {Post.objects.count()}')"

# 2. Delete PostgreSQL pod
kubectl delete pod postgres-0 -n team-30-staging

# 3. Wait for pod to restart
kubectl wait --for=condition=Ready pod postgres-0 -n team-30-staging --timeout=300s

# 4. Verify data is still there
kubectl exec -it deployment/django-backend -n team-30-staging -- python manage.py shell -c "from feed.models import Post; print(f'Posts after restart: {Post.objects.count()}')"
# Count should be the same — data persisted!

Storage Monitoring:

# List persistent volumes
kubectl get pvc -n team-30-staging

# Check Longhorn volume usage
kubectl get volumesnapshotcontents -n team-30-staging

CI/CD Pipeline (GitLab)

Unified Pipeline (Main Branch)

The pipeline now supports both VM and Kubernetes deployments with the following stages:

Stages

  1. Setup

    • Validates CI variables (DEPLOY_HOST, SSH_PRIVATE_KEY, etc.)
    • Manual trigger for VM Setup (Ansible provisioning) only on Ansible-Setup branch
  2. Code Quality

    • Backend & Frontend linting/formatting checks
      • Backend Ruff formatting and linting checks
      • Frontend ESLint + Prettier checks
  3. Test

    • Django unit & integration tests (with coverage ≥50%)
    • Secret detection
    • SAST scan
  4. Build Backend:

    • Kaniko builds Django backend image ($CI_COMMIT_SHA + latest)
  5. Build Frontend:

    • Kaniko builds Vue.js frontend image ($CI_COMMIT_SHA + latest)
  6. Deploy

    • VM Deploy: Builds Vue frontend, rsyncs to VM, restarts services
    • Kubernetes Deploy: Generates kubeconfig, runs kubectl apply -f k8s/

CI/CD Variables

VM Deployment:

  • DEPLOY_HOST, DEPLOY_USER, SSH_PRIVATE_KEY, SECRETS_FILE, DJANGO_ENV_FILE (protected & masked that are not files)

Kubernetes Deployment:

  • KUBE_URL, KUBE_TOKEN, KUBE_CA, KUBE_NAMESPACE (masked & protected)

Workflow: Code push → CI/CD variable checks & VM setup → Lint/Test → Build containers → Deploy to VM and K8s → Live application


Accessing the Application

Legacy VM Deployment

  • URL: https://team30vm.cloud.ut.ee
  • HTTPS: Enforced (HTTP → HTTPS redirect)
  • Certificates: Auto-managed by Certbot

Kubernetes Deployment

  • URL: https://team30-nginx.kubernetes.devops.cs.ut.ee
  • TLS: Managed by cert-manager with Let's Encrypt
  • Endpoints:
    • / - Vue.js frontend (SPA)
    • /api/ - Django REST API
    • /ws/ - WebSocket connections (real-time updates)
    • /media/ - User-uploaded images (served from Longhorn PVC)
    • /healthz - Liveness check (backend health)
    • /readyz - Readiness check (DB connectivity)
    • /metrics - Prometheus metrics (not exposed via Ingress for security)

Monitoring & Observability

  • Grafana Dashboard: https://grafana.kubernetes.devops.cs.ut.ee/
    • Team 30 dashboard with 10 panels (metrics, health, storage, resources)
  • Prometheus: Metrics scraped every 30s from backend service
  • Longhorn UI: Storage management and volume monitoring (admin access only)

AI Usage Disclosure

The following parts were assisted by AI (Qwen3, GitHub Copilot, Grok):

  • Initial scaffolding of Vue components (PostItem.vue, FeedView.vue)
  • Drafting of Ansible tasks, templates and playbook
  • Boilerplate for Django models and serializers
  • CI/CD pipeline structure and comments
  • Docker containerization (Dockerfiles for backend and frontend)
  • Kubernetes manifests (Deployments, Services, Ingress, ConfigMaps, Secrets)
  • Health check endpoints and Prometheus metrics implementation

All AI-generated code was:

  • Reviewed, modified, and tested by team members
  • Annotated with detailed docstrings and comments
  • Validated against security and functional requirements


Kubernetes Operations Guide

Common Commands:

# View resources
kubectl get pods -n team-30-staging
kubectl logs -f deployment/django-backend -n team-30-staging
kubectl describe pod <pod-name> -n team-30-staging

# Exec into pod
kubectl exec -it deployment/django-backend -n team-30-staging -- /bin/bash

# Port forwarding
kubectl port-forward -n team-30-staging deployment/django-backend 8000:8000

# Scaling
kubectl scale deployment/django-backend --replicas=3 -n team-30-staging

# Apply changes (automatically excludes migration job)
kubectl apply -f k8s/ -n team-30-staging

# Rollback
kubectl rollout undo deployment/django-backend -n team-30-staging

Database Migrations:

# Run migrations manually (requires Jobs permission in RBAC)
kubectl apply -f k8s/django-migration-job.yaml -n team-30-staging

# Check migration job status
kubectl get jobs -n team-30-staging
kubectl logs job/django-migration -n team-30-staging

# Clean up completed migration job
kubectl delete job django-migration -n team-30-staging

RBAC Setup (one-time, requires cluster admin):

# Apply RBAC role and rolebinding for CI/CD ServiceAccount
# This grants permissions for Deployments, Services, Jobs, etc.
kubectl apply -f k8s/cicd-role.yaml -n team-30-staging

# Verify permissions
kubectl auth can-i create jobs --as=system:serviceaccount:team-30-staging:cicd-serviceaccount -n team-30-staging

Troubleshooting:

  • ImagePullBackOff: Check gitlab-registry secret, recreate if expired
  • CrashLoopBackOff: Check logs with kubectl logs <pod-name>
  • DB issues: Check PostgreSQL cluster status with kubectl get cluster -n team-30-staging

Security Practices

Application:

  • XSS mitigation (Django/Vue auto-escaping)
  • CSRF protection enabled
  • JWT authentication with secure signing
  • Input validation via Django models and DRF serializers

Infrastructure:

  • VM: UFW firewall, SSH key-only auth, Let's Encrypt HTTPS
  • Kubernetes: RBAC for CI/CD, secrets management, private registry with imagePullSecrets, TLS via cert-manager

Secrets Management:

  • ✅ In Git: ConfigMaps, manifest structure
  • ❌ Not in Git: Secret values, passwords, tokens, kubeconfig
  • All CI/CD variables masked and protected

Architecture Comparison: VM vs. Kubernetes

VM Deployment (Weeks 1-8):

  • Single Ubuntu server with Nginx + Daphne + SQLite
  • Simple setup, low overhead, easier debugging
  • No HA, manual scaling, downtime during deploys

Kubernetes Deployment (Weeks 9-11):

  • Distributed pods across cluster with PostgreSQL + Longhorn storage
  • High availability, horizontal scaling, zero-downtime deployments
  • Self-healing, comprehensive monitoring (Prometheus/Grafana)
  • Higher complexity, more operational overhead

Key Changes: SQLite → PostgreSQL, local filesystem → Longhorn PVC, Ansible → GitLab CI + Kaniko + kubectl


📄 License

MIT License — see LICENSE for details.

About

This project was originally developed as coursework at the University of Tartu.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •