Merge pull request #37 from se4ai2526-uniba/aggiornamento-doc
Browse filesRefactor repository structure for Docker integration and update documentation
- .github/workflows/ci.yml +28 -2
- Dockerfile +4 -4
- README.md +102 -28
- .dockerignore → docker/.dockerignore +2 -2
- Dockerfile.streamlit → docker/Dockerfile.streamlit +0 -0
- docker/README.md +91 -0
- docker-compose.yml → docker/docker-compose.yml +13 -14
- nginx.conf → docker/nginx.conf +0 -0
- {scripts → docker/scripts}/start_space.sh +1 -1
- tests/integration/test_feature_pipeline.py +12 -9
.github/workflows/ci.yml
CHANGED
|
@@ -86,8 +86,34 @@ jobs:
|
|
| 86 |
|
| 87 |
- name: Pull Models with DVC
|
| 88 |
run: |
|
| 89 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
|
| 91 |
- name: Build Docker Image
|
| 92 |
run: |
|
| 93 |
-
docker build
|
|
|
|
| 86 |
|
| 87 |
- name: Pull Models with DVC
|
| 88 |
run: |
|
| 89 |
+
# Clean any potentially corrupted cache
|
| 90 |
+
rm -rf .dvc/cache/files/md5/e1 || true
|
| 91 |
+
|
| 92 |
+
# Retry logic for DVC pull to handle intermittent server errors
|
| 93 |
+
max_attempts=3
|
| 94 |
+
attempt=0
|
| 95 |
+
until [ $attempt -ge $max_attempts ]
|
| 96 |
+
do
|
| 97 |
+
dvc pull models/random_forest_embedding_gridsearch.pkl models/label_names.pkl && break
|
| 98 |
+
attempt=$((attempt+1))
|
| 99 |
+
echo "DVC pull attempt $attempt failed. Retrying in 10 seconds..."
|
| 100 |
+
# Clean cache on retry
|
| 101 |
+
rm -rf .dvc/cache || true
|
| 102 |
+
sleep 10
|
| 103 |
+
done
|
| 104 |
+
if [ $attempt -ge $max_attempts ]; then
|
| 105 |
+
echo "DVC pull failed after $max_attempts attempts"
|
| 106 |
+
exit 1
|
| 107 |
+
fi
|
| 108 |
+
|
| 109 |
+
- name: Verify Models Downloaded
|
| 110 |
+
run: |
|
| 111 |
+
if [ ! -f "models/random_forest_embedding_gridsearch.pkl" ] || [ ! -f "models/label_names.pkl" ]; then
|
| 112 |
+
echo "ERROR: Required model files not found after DVC pull"
|
| 113 |
+
exit 1
|
| 114 |
+
fi
|
| 115 |
+
echo "All required model files present"
|
| 116 |
|
| 117 |
- name: Build Docker Image
|
| 118 |
run: |
|
| 119 |
+
docker build -t hopcroft-app:latest .
|
Dockerfile
CHANGED
|
@@ -59,14 +59,14 @@ COPY --chown=user:user . .
|
|
| 59 |
RUN chown -R user:user /app
|
| 60 |
|
| 61 |
# Fix line endings and permissions for the start script
|
| 62 |
-
RUN dos2unix scripts/start_space.sh && \
|
| 63 |
-
chmod +x scripts/start_space.sh
|
| 64 |
|
| 65 |
# Install the project itself
|
| 66 |
RUN pip install --no-cache-dir .
|
| 67 |
|
| 68 |
# Make start script executable
|
| 69 |
-
RUN chmod +x scripts/start_space.sh
|
| 70 |
|
| 71 |
# Switch to non-root user
|
| 72 |
USER user
|
|
@@ -75,4 +75,4 @@ USER user
|
|
| 75 |
EXPOSE 7860
|
| 76 |
|
| 77 |
# Command to run the application
|
| 78 |
-
CMD ["./scripts/start_space.sh"]
|
|
|
|
| 59 |
RUN chown -R user:user /app
|
| 60 |
|
| 61 |
# Fix line endings and permissions for the start script
|
| 62 |
+
RUN dos2unix docker/scripts/start_space.sh && \
|
| 63 |
+
chmod +x docker/scripts/start_space.sh
|
| 64 |
|
| 65 |
# Install the project itself
|
| 66 |
RUN pip install --no-cache-dir .
|
| 67 |
|
| 68 |
# Make start script executable
|
| 69 |
+
RUN chmod +x docker/scripts/start_space.sh
|
| 70 |
|
| 71 |
# Switch to non-root user
|
| 72 |
USER user
|
|
|
|
| 75 |
EXPOSE 7860
|
| 76 |
|
| 77 |
# Command to run the application
|
| 78 |
+
CMD ["./docker/scripts/start_space.sh"]
|
README.md
CHANGED
|
@@ -249,7 +249,7 @@ make test-api-list # List predictions
|
|
| 249 |
#### Docker
|
| 250 |
Build and run the API in a container:
|
| 251 |
```bash
|
| 252 |
-
docker build -t hopcroft-api .
|
| 253 |
docker run --rm --name hopcroft-api -p 8080:8080 hopcroft-api
|
| 254 |
```
|
| 255 |
|
|
@@ -257,6 +257,80 @@ Endpoints:
|
|
| 257 |
- Swagger UI: [http://localhost:8080/docs](http://localhost:8080/docs)
|
| 258 |
- Health check: [http://localhost:8080/health](http://localhost:8080/health)
|
| 259 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 260 |
---
|
| 261 |
|
| 262 |
## Docker Compose Usage
|
|
@@ -283,7 +357,7 @@ Docker Compose orchestrates both the **API backend** and **Streamlit GUI** servi
|
|
| 283 |
#### 1. Build and Start All Services
|
| 284 |
Build both images and start the containers:
|
| 285 |
```bash
|
| 286 |
-
docker-compose up -d --build
|
| 287 |
```
|
| 288 |
|
| 289 |
| Flag | Description |
|
|
@@ -299,7 +373,7 @@ docker-compose up -d --build
|
|
| 299 |
#### 2. Stop All Services
|
| 300 |
Stop and remove containers and networks:
|
| 301 |
```bash
|
| 302 |
-
docker-compose down
|
| 303 |
```
|
| 304 |
|
| 305 |
| Flag | Description |
|
|
@@ -310,19 +384,19 @@ docker-compose down
|
|
| 310 |
#### 3. Restart Services
|
| 311 |
After updating `.env` or configuration files:
|
| 312 |
```bash
|
| 313 |
-
docker-compose restart
|
| 314 |
```
|
| 315 |
|
| 316 |
Or for a full restart with environment reload:
|
| 317 |
```bash
|
| 318 |
-
docker-compose down
|
| 319 |
-
docker-compose up -d
|
| 320 |
```
|
| 321 |
|
| 322 |
#### 4. Check Status
|
| 323 |
View the status of all running services:
|
| 324 |
```bash
|
| 325 |
-
docker-compose ps
|
| 326 |
```
|
| 327 |
|
| 328 |
Or use Docker commands:
|
|
@@ -333,13 +407,13 @@ docker ps
|
|
| 333 |
#### 5. View Logs
|
| 334 |
Tail logs from both services in real-time:
|
| 335 |
```bash
|
| 336 |
-
docker-compose logs -f
|
| 337 |
```
|
| 338 |
|
| 339 |
View logs from a specific service:
|
| 340 |
```bash
|
| 341 |
-
docker-compose logs -f hopcroft-api
|
| 342 |
-
docker-compose logs -f hopcroft-gui
|
| 343 |
```
|
| 344 |
|
| 345 |
| Flag | Description |
|
|
@@ -350,8 +424,8 @@ docker-compose logs -f hopcroft-gui
|
|
| 350 |
#### 6. Execute Commands in Container
|
| 351 |
Open an interactive shell inside a running container:
|
| 352 |
```bash
|
| 353 |
-
docker-compose exec hopcroft-api /bin/bash
|
| 354 |
-
docker-compose exec hopcroft-gui /bin/bash
|
| 355 |
```
|
| 356 |
|
| 357 |
Examples of useful commands inside the API container:
|
|
@@ -375,9 +449,9 @@ printenv | grep MLFLOW
|
|
| 375 |
**Docker Compose orchestrates two services:**
|
| 376 |
|
| 377 |
```
|
| 378 |
-
docker-compose.yml
|
| 379 |
├── hopcroft-api (FastAPI Backend)
|
| 380 |
-
│ ├── Build:
|
| 381 |
│ ├── Port: 8080:8080
|
| 382 |
│ ├── Network: hopcroft-net
|
| 383 |
│ ├── Environment: .env (MLflow credentials)
|
|
@@ -387,7 +461,7 @@ docker-compose.yml
|
|
| 387 |
│ └── Health Check: /health endpoint
|
| 388 |
│
|
| 389 |
├── hopcroft-gui (Streamlit Frontend)
|
| 390 |
-
│ ├── Build:
|
| 391 |
│ ├── Port: 8501:8501
|
| 392 |
│ ├── Network: hopcroft-net
|
| 393 |
│ ├── Environment: API_BASE_URL=http://hopcroft-api:8080
|
|
@@ -409,7 +483,7 @@ docker-compose.yml
|
|
| 409 |
|
| 410 |
**hopcroft-api (FastAPI Backend)**
|
| 411 |
- Purpose: FastAPI backend serving the ML model for skill classification
|
| 412 |
-
- Image: Built from `Dockerfile`
|
| 413 |
- Port: 8080 (maps to host 8080)
|
| 414 |
- Features:
|
| 415 |
- Random Forest model with embedding features
|
|
@@ -419,7 +493,7 @@ docker-compose.yml
|
|
| 419 |
|
| 420 |
**hopcroft-gui (Streamlit Frontend)**
|
| 421 |
- Purpose: Streamlit web interface for interactive predictions
|
| 422 |
-
- Image: Built from `Dockerfile.streamlit`
|
| 423 |
- Port: 8501 (maps to host 8501)
|
| 424 |
- Features:
|
| 425 |
- User-friendly interface for skill prediction
|
|
@@ -441,7 +515,7 @@ docker-compose.yml
|
|
| 441 |
- Use Dockerfile's CMD
|
| 442 |
- GUI → API via Docker network
|
| 443 |
|
| 444 |
-
For **production deployment**, modify `docker-compose.yml` to remove bind mounts and disable reload.
|
| 445 |
|
| 446 |
### Troubleshooting
|
| 447 |
|
|
@@ -450,19 +524,19 @@ For **production deployment**, modify `docker-compose.yml` to remove bind mounts
|
|
| 450 |
1. Wait 30-60 seconds for API to fully initialize and become healthy
|
| 451 |
2. Refresh the GUI page (F5)
|
| 452 |
3. Check API health: `curl http://localhost:8080/health`
|
| 453 |
-
4. Check logs: `docker-compose logs hopcroft-api`
|
| 454 |
|
| 455 |
#### Issue: "500 Internal Server Error" on predictions
|
| 456 |
**Solution:**
|
| 457 |
1. Verify MLflow credentials in `.env` are correct
|
| 458 |
-
2. Restart services: `docker-compose down && docker-compose up -d`
|
| 459 |
3. Check environment variables: `docker exec hopcroft-api printenv | grep MLFLOW`
|
| 460 |
|
| 461 |
#### Issue: Changes to code not reflected
|
| 462 |
**Solution:**
|
| 463 |
- For Python code changes: Auto-reload is enabled, wait a few seconds
|
| 464 |
-
- For Dockerfile changes: Rebuild with `docker-compose up -d --build`
|
| 465 |
-
- For `.env` changes: Restart with `docker-compose down && docker-compose up -d`
|
| 466 |
|
| 467 |
#### Issue: Port already in use
|
| 468 |
**Solution:**
|
|
@@ -472,9 +546,9 @@ netstat -ano | findstr :8080
|
|
| 472 |
netstat -ano | findstr :8501
|
| 473 |
|
| 474 |
# Stop existing containers
|
| 475 |
-
docker-compose down
|
| 476 |
|
| 477 |
-
# Or change ports in docker-compose.yml
|
| 478 |
```
|
| 479 |
|
| 480 |
|
|
@@ -498,12 +572,12 @@ To enable the application to pull models from DagsHub via DVC, you must configur
|
|
| 498 |
| `DAGSHUB_TOKEN` | Secret | Your DagsHub access token (Settings -> Tokens). |
|
| 499 |
|
| 500 |
> [!IMPORTANT]
|
| 501 |
-
> These secrets are injected into the container at runtime. The `scripts/start_space.sh` script uses them to authenticate DVC and pull the required model files (`.pkl`) before starting the API and GUI.
|
| 502 |
|
| 503 |
### 3. Automated Startup
|
| 504 |
The deployment follows this automated flow:
|
| 505 |
-
1. **Dockerfile**: Builds the environment, installs dependencies, and sets up Nginx.
|
| 506 |
-
2. **scripts/start_space.sh**:
|
| 507 |
- Configures DVC with your secrets.
|
| 508 |
- Pulls models from the DagsHub remote.
|
| 509 |
- Starts the **FastAPI** backend (port 8000).
|
|
@@ -531,7 +605,7 @@ The Streamlit GUI provides an interactive web interface for the skill classifica
|
|
| 531 |
- Responsive design
|
| 532 |
|
| 533 |
### Usage
|
| 534 |
-
1. Ensure both services are running: `docker-compose up -d`
|
| 535 |
2. Open the GUI in your browser: [http://localhost:8501](http://localhost:8501)
|
| 536 |
3. Enter a GitHub issue description in the text area
|
| 537 |
4. Click "Predict Skills" to get predictions
|
|
|
|
| 249 |
#### Docker
|
| 250 |
Build and run the API in a container:
|
| 251 |
```bash
|
| 252 |
+
docker build -f docker/Dockerfile -t hopcroft-api .
|
| 253 |
docker run --rm --name hopcroft-api -p 8080:8080 hopcroft-api
|
| 254 |
```
|
| 255 |
|
|
|
|
| 257 |
- Swagger UI: [http://localhost:8080/docs](http://localhost:8080/docs)
|
| 258 |
- Health check: [http://localhost:8080/health](http://localhost:8080/health)
|
| 259 |
|
| 260 |
+
### Milestone 5 (Deployment)
|
| 261 |
+
We implemented a complete containerized deployment pipeline for production-ready delivery:
|
| 262 |
+
|
| 263 |
+
1. **Docker Containerization**
|
| 264 |
+
- `docker/Dockerfile`: Multi-stage Python 3.10 slim image with non-root user, system dependencies (git, nginx, curl), DVC integration, and automated startup script.
|
| 265 |
+
- `docker/Dockerfile.streamlit`: Lightweight container for Streamlit GUI with minimal dependencies.
|
| 266 |
+
- `docker/.dockerignore`: Optimized build context excluding unnecessary files.
|
| 267 |
+
|
| 268 |
+
2. **Docker Compose Orchestration**
|
| 269 |
+
- Multi-service architecture: API backend (`hopcroft-api`), Streamlit frontend (`hopcroft-gui`), and monitoring stack.
|
| 270 |
+
- Bridge network (`hopcroft-net`) for inter-service communication.
|
| 271 |
+
- Health checks with automatic restart policies.
|
| 272 |
+
- Bind mounts for development hot-reload, named volumes for persistent storage (`hopcroft-logs`).
|
| 273 |
+
|
| 274 |
+
3. **Hugging Face Spaces Deployment**
|
| 275 |
+
- Docker SDK configuration with port 7860.
|
| 276 |
+
- `docker/scripts/start_space.sh`: Automated startup script that configures DVC credentials, pulls models from DagsHub, and starts FastAPI + Streamlit + Nginx.
|
| 277 |
+
- Secrets management via HF Spaces Variables (`DAGSHUB_USERNAME`, `DAGSHUB_TOKEN`).
|
| 278 |
+
- Live deployment: `https://huggingface.co/spaces/se4ai2526-uniba/Hopcroft`
|
| 279 |
+
|
| 280 |
+
4. **Nginx Reverse Proxy**
|
| 281 |
+
- `docker/nginx.conf`: Routes traffic to API (port 8000) and Streamlit (port 8501) on single port 7860.
|
| 282 |
+
- Path-based routing for API docs, metrics, and web interface.
|
| 283 |
+
|
| 284 |
+
5. **Environment Configuration**
|
| 285 |
+
- `.env.example` template with MLflow and DagsHub credentials.
|
| 286 |
+
- Automatic environment variable injection via `env_file` directive.
|
| 287 |
+
|
| 288 |
+
### Milestone 6 (Monitoring)
|
| 289 |
+
We implemented comprehensive observability and load testing infrastructure:
|
| 290 |
+
|
| 291 |
+
1. **Prometheus Metrics Collection**
|
| 292 |
+
- `prometheus.yml`: Scrape configuration for API metrics (10s interval), self-monitoring, and Pushgateway.
|
| 293 |
+
- Custom metrics: `hopcroft_requests_total`, `hopcroft_request_duration_seconds`, `hopcroft_in_progress_requests`, `hopcroft_prediction_processing_seconds`.
|
| 294 |
+
- PromQL queries for request rate, latency percentiles, and in-progress tracking.
|
| 295 |
+
|
| 296 |
+
2. **Grafana Dashboards**
|
| 297 |
+
- Auto-provisioned datasources and dashboards via `provisioning/` directory.
|
| 298 |
+
- `hopcroft_dashboard.json`: Real-time visualization of API request rate, latency, drift status, and p-value metrics.
|
| 299 |
+
- Credentials: `admin/admin` on port 3000.
|
| 300 |
+
|
| 301 |
+
3. **Alerting System**
|
| 302 |
+
- `alert_rules.yml`: Prometheus alert rules for `ServiceDown`, `HighErrorRate` (>10% 5xx), `SlowRequests` (p95 > 2s).
|
| 303 |
+
- Alertmanager configuration with severity-based routing and inhibition rules.
|
| 304 |
+
- Webhook integration for alert notifications.
|
| 305 |
+
|
| 306 |
+
4. **Data Drift Detection**
|
| 307 |
+
- `prepare_baseline.py`: Extracts 1000-sample reference dataset from training data.
|
| 308 |
+
- `run_drift_check.py`: Kolmogorov-Smirnov two-sample test with Bonferroni correction (p < 0.05).
|
| 309 |
+
- Metrics pushed to Pushgateway: `drift_detected`, `drift_p_value`, `drift_distance`, `drift_check_timestamp`.
|
| 310 |
+
- JSON reports saved to `monitoring/drift/reports/`.
|
| 311 |
+
|
| 312 |
+
5. **Locust Load Testing**
|
| 313 |
+
- `locustfile.py`: Simulated user behavior with weighted tasks (60% single prediction, 20% batch, 20% monitoring).
|
| 314 |
+
- Configurable wait times (1-5s) for realistic traffic simulation.
|
| 315 |
+
- Web UI on port 8089, headless mode support, CSV export for results.
|
| 316 |
+
- Pre-configured for HF Spaces and local Docker environments.
|
| 317 |
+
|
| 318 |
+
6. **Uptime Monitoring (Better Stack)**
|
| 319 |
+
- External monitoring of production endpoints (`/health`, `/openapi.json`, `/docs`).
|
| 320 |
+
- Multi-location checks with email notifications.
|
| 321 |
+
- Incident tracking and resolution screenshots in `monitoring/screenshots/`.
|
| 322 |
+
|
| 323 |
+
7. **CI/CD Pipeline**
|
| 324 |
+
- `.github/workflows/ci.yml`: GitHub Actions workflow triggered on push/PR to main and feature branches.
|
| 325 |
+
- Jobs: Ruff linting, pytest unit tests with HTML reports, DVC model pulling, Docker image build.
|
| 326 |
+
- Secrets: `DAGSHUB_USERNAME`, `DAGSHUB_TOKEN` for model access.
|
| 327 |
+
- Disk space optimization for CI runner.
|
| 328 |
+
|
| 329 |
+
8. **Pushgateway Integration**
|
| 330 |
+
- Collects metrics from short-lived jobs (drift detection scripts).
|
| 331 |
+
- Persistent storage with 5-minute intervals.
|
| 332 |
+
- Scraped by Prometheus for long-term storage and Grafana visualization.
|
| 333 |
+
|
| 334 |
---
|
| 335 |
|
| 336 |
## Docker Compose Usage
|
|
|
|
| 357 |
#### 1. Build and Start All Services
|
| 358 |
Build both images and start the containers:
|
| 359 |
```bash
|
| 360 |
+
docker compose -f docker/docker-compose.yml up -d --build
|
| 361 |
```
|
| 362 |
|
| 363 |
| Flag | Description |
|
|
|
|
| 373 |
#### 2. Stop All Services
|
| 374 |
Stop and remove containers and networks:
|
| 375 |
```bash
|
| 376 |
+
docker compose -f docker/docker-compose.yml down
|
| 377 |
```
|
| 378 |
|
| 379 |
| Flag | Description |
|
|
|
|
| 384 |
#### 3. Restart Services
|
| 385 |
After updating `.env` or configuration files:
|
| 386 |
```bash
|
| 387 |
+
docker compose -f docker/docker-compose.yml restart
|
| 388 |
```
|
| 389 |
|
| 390 |
Or for a full restart with environment reload:
|
| 391 |
```bash
|
| 392 |
+
docker compose -f docker/docker-compose.yml down
|
| 393 |
+
docker compose -f docker/docker-compose.yml up -d
|
| 394 |
```
|
| 395 |
|
| 396 |
#### 4. Check Status
|
| 397 |
View the status of all running services:
|
| 398 |
```bash
|
| 399 |
+
docker compose -f docker/docker-compose.yml ps
|
| 400 |
```
|
| 401 |
|
| 402 |
Or use Docker commands:
|
|
|
|
| 407 |
#### 5. View Logs
|
| 408 |
Tail logs from both services in real-time:
|
| 409 |
```bash
|
| 410 |
+
docker compose -f docker/docker-compose.yml logs -f
|
| 411 |
```
|
| 412 |
|
| 413 |
View logs from a specific service:
|
| 414 |
```bash
|
| 415 |
+
docker compose -f docker/docker-compose.yml logs -f hopcroft-api
|
| 416 |
+
docker compose -f docker/docker-compose.yml logs -f hopcroft-gui
|
| 417 |
```
|
| 418 |
|
| 419 |
| Flag | Description |
|
|
|
|
| 424 |
#### 6. Execute Commands in Container
|
| 425 |
Open an interactive shell inside a running container:
|
| 426 |
```bash
|
| 427 |
+
docker compose -f docker/docker-compose.yml exec hopcroft-api /bin/bash
|
| 428 |
+
docker compose -f docker/docker-compose.yml exec hopcroft-gui /bin/bash
|
| 429 |
```
|
| 430 |
|
| 431 |
Examples of useful commands inside the API container:
|
|
|
|
| 449 |
**Docker Compose orchestrates two services:**
|
| 450 |
|
| 451 |
```
|
| 452 |
+
docker/docker-compose.yml
|
| 453 |
├── hopcroft-api (FastAPI Backend)
|
| 454 |
+
│ ├── Build: docker/Dockerfile
|
| 455 |
│ ├── Port: 8080:8080
|
| 456 |
│ ├── Network: hopcroft-net
|
| 457 |
│ ├── Environment: .env (MLflow credentials)
|
|
|
|
| 461 |
│ └── Health Check: /health endpoint
|
| 462 |
│
|
| 463 |
├── hopcroft-gui (Streamlit Frontend)
|
| 464 |
+
│ ├── Build: docker/Dockerfile.streamlit
|
| 465 |
│ ├── Port: 8501:8501
|
| 466 |
│ ├── Network: hopcroft-net
|
| 467 |
│ ├── Environment: API_BASE_URL=http://hopcroft-api:8080
|
|
|
|
| 483 |
|
| 484 |
**hopcroft-api (FastAPI Backend)**
|
| 485 |
- Purpose: FastAPI backend serving the ML model for skill classification
|
| 486 |
+
- Image: Built from `docker/Dockerfile`
|
| 487 |
- Port: 8080 (maps to host 8080)
|
| 488 |
- Features:
|
| 489 |
- Random Forest model with embedding features
|
|
|
|
| 493 |
|
| 494 |
**hopcroft-gui (Streamlit Frontend)**
|
| 495 |
- Purpose: Streamlit web interface for interactive predictions
|
| 496 |
+
- Image: Built from `docker/Dockerfile.streamlit`
|
| 497 |
- Port: 8501 (maps to host 8501)
|
| 498 |
- Features:
|
| 499 |
- User-friendly interface for skill prediction
|
|
|
|
| 515 |
- Use Dockerfile's CMD
|
| 516 |
- GUI → API via Docker network
|
| 517 |
|
| 518 |
+
For **production deployment**, modify `docker/docker-compose.yml` to remove bind mounts and disable reload.
|
| 519 |
|
| 520 |
### Troubleshooting
|
| 521 |
|
|
|
|
| 524 |
1. Wait 30-60 seconds for API to fully initialize and become healthy
|
| 525 |
2. Refresh the GUI page (F5)
|
| 526 |
3. Check API health: `curl http://localhost:8080/health`
|
| 527 |
+
4. Check logs: `docker compose -f docker/docker-compose.yml logs hopcroft-api`
|
| 528 |
|
| 529 |
#### Issue: "500 Internal Server Error" on predictions
|
| 530 |
**Solution:**
|
| 531 |
1. Verify MLflow credentials in `.env` are correct
|
| 532 |
+
2. Restart services: `docker compose -f docker/docker-compose.yml down && docker compose -f docker/docker-compose.yml up -d`
|
| 533 |
3. Check environment variables: `docker exec hopcroft-api printenv | grep MLFLOW`
|
| 534 |
|
| 535 |
#### Issue: Changes to code not reflected
|
| 536 |
**Solution:**
|
| 537 |
- For Python code changes: Auto-reload is enabled, wait a few seconds
|
| 538 |
+
- For Dockerfile changes: Rebuild with `docker compose -f docker/docker-compose.yml up -d --build`
|
| 539 |
+
- For `.env` changes: Restart with `docker compose -f docker/docker-compose.yml down && docker compose -f docker/docker-compose.yml up -d`
|
| 540 |
|
| 541 |
#### Issue: Port already in use
|
| 542 |
**Solution:**
|
|
|
|
| 546 |
netstat -ano | findstr :8501
|
| 547 |
|
| 548 |
# Stop existing containers
|
| 549 |
+
docker compose -f docker/docker-compose.yml down
|
| 550 |
|
| 551 |
+
# Or change ports in docker/docker-compose.yml
|
| 552 |
```
|
| 553 |
|
| 554 |
|
|
|
|
| 572 |
| `DAGSHUB_TOKEN` | Secret | Your DagsHub access token (Settings -> Tokens). |
|
| 573 |
|
| 574 |
> [!IMPORTANT]
|
| 575 |
+
> These secrets are injected into the container at runtime. The `docker/scripts/start_space.sh` script uses them to authenticate DVC and pull the required model files (`.pkl`) before starting the API and GUI.
|
| 576 |
|
| 577 |
### 3. Automated Startup
|
| 578 |
The deployment follows this automated flow:
|
| 579 |
+
1. **docker/Dockerfile**: Builds the environment, installs dependencies, and sets up Nginx.
|
| 580 |
+
2. **docker/scripts/start_space.sh**:
|
| 581 |
- Configures DVC with your secrets.
|
| 582 |
- Pulls models from the DagsHub remote.
|
| 583 |
- Starts the **FastAPI** backend (port 8000).
|
|
|
|
| 605 |
- Responsive design
|
| 606 |
|
| 607 |
### Usage
|
| 608 |
+
1. Ensure both services are running: `docker compose -f docker/docker-compose.yml up -d`
|
| 609 |
2. Open the GUI in your browser: [http://localhost:8501](http://localhost:8501)
|
| 610 |
3. Enter a GitHub issue description in the text area
|
| 611 |
4. Click "Predict Skills" to get predictions
|
.dockerignore → docker/.dockerignore
RENAMED
|
@@ -27,5 +27,5 @@ notebooks/
|
|
| 27 |
reports/
|
| 28 |
docs/
|
| 29 |
tests/
|
| 30 |
-
scripts/
|
| 31 |
-
!scripts/start_space.sh
|
|
|
|
| 27 |
reports/
|
| 28 |
docs/
|
| 29 |
tests/
|
| 30 |
+
docker/scripts/
|
| 31 |
+
!docker/scripts/start_space.sh
|
Dockerfile.streamlit → docker/Dockerfile.streamlit
RENAMED
|
File without changes
|
docker/README.md
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Docker Configuration
|
| 2 |
+
|
| 3 |
+
This directory contains all Docker-related configuration files for the Hopcroft Skill Classification project.
|
| 4 |
+
|
| 5 |
+
## Directory Structure
|
| 6 |
+
|
| 7 |
+
```
|
| 8 |
+
docker/
|
| 9 |
+
├── Dockerfile # Main API container (Python 3.10, FastAPI, Nginx)
|
| 10 |
+
├── Dockerfile.streamlit # Streamlit GUI container
|
| 11 |
+
├── docker-compose.yml # Multi-service orchestration
|
| 12 |
+
├── nginx.conf # Reverse proxy configuration for HF Spaces
|
| 13 |
+
├── .dockerignore # Build context exclusions
|
| 14 |
+
├── scripts/
|
| 15 |
+
│ └── start_space.sh # Startup script for Hugging Face Spaces
|
| 16 |
+
└── README.md # This file
|
| 17 |
+
```
|
| 18 |
+
|
| 19 |
+
## File Descriptions
|
| 20 |
+
|
| 21 |
+
| File | Purpose |
|
| 22 |
+
|------|---------|
|
| 23 |
+
| `Dockerfile` | Main container with FastAPI API, DVC, Nginx. Used for HF Spaces deployment. |
|
| 24 |
+
| `Dockerfile.streamlit` | Lightweight Streamlit-only container for GUI service. |
|
| 25 |
+
| `docker-compose.yml` | Orchestrates API, GUI, Prometheus, Grafana, Alertmanager, Pushgateway. |
|
| 26 |
+
| `nginx.conf` | Reverse proxy routing traffic to API (8000) and Streamlit (8501) on port 7860. |
|
| 27 |
+
| `.dockerignore` | Excludes data/, tests/, docs/ etc. from Docker build context. |
|
| 28 |
+
| `start_space.sh` | Configures DVC, pulls models, starts FastAPI + Streamlit + Nginx. |
|
| 29 |
+
|
| 30 |
+
## Quick Start
|
| 31 |
+
|
| 32 |
+
### Local Development
|
| 33 |
+
|
| 34 |
+
```bash
|
| 35 |
+
# From the docker/ directory
|
| 36 |
+
docker compose up -d --build
|
| 37 |
+
|
| 38 |
+
# Or from repository root
|
| 39 |
+
docker compose -f docker/docker-compose.yml up -d --build
|
| 40 |
+
```
|
| 41 |
+
|
| 42 |
+
**Available Services:**
|
| 43 |
+
| Service | URL |
|
| 44 |
+
|---------|-----|
|
| 45 |
+
| API (FastAPI) | http://localhost:8080/docs |
|
| 46 |
+
| GUI (Streamlit) | http://localhost:8501 |
|
| 47 |
+
| Prometheus | http://localhost:9090 |
|
| 48 |
+
| Grafana | http://localhost:3000 (admin/admin) |
|
| 49 |
+
| Pushgateway | http://localhost:9091 |
|
| 50 |
+
| Alertmanager | http://localhost:9093 |
|
| 51 |
+
|
| 52 |
+
### Stop Services
|
| 53 |
+
|
| 54 |
+
```bash
|
| 55 |
+
docker compose -f docker/docker-compose.yml down
|
| 56 |
+
```
|
| 57 |
+
|
| 58 |
+
### View Logs
|
| 59 |
+
|
| 60 |
+
```bash
|
| 61 |
+
docker compose -f docker/docker-compose.yml logs -f
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
## Building Individual Images
|
| 65 |
+
|
| 66 |
+
```bash
|
| 67 |
+
# From repository root
|
| 68 |
+
docker build -f docker/Dockerfile -t hopcroft-api:latest .
|
| 69 |
+
docker build -f docker/Dockerfile.streamlit -t hopcroft-gui:latest .
|
| 70 |
+
```
|
| 71 |
+
|
| 72 |
+
## Environment Variables
|
| 73 |
+
|
| 74 |
+
The `.env` file in the repository root is automatically loaded. Required variables:
|
| 75 |
+
|
| 76 |
+
```env
|
| 77 |
+
MLFLOW_TRACKING_URI=https://dagshub.com/se4ai2526-uniba/Hopcroft.mlflow
|
| 78 |
+
MLFLOW_TRACKING_USERNAME=your_username
|
| 79 |
+
MLFLOW_TRACKING_PASSWORD=your_token
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
## Hugging Face Spaces
|
| 83 |
+
|
| 84 |
+
The `Dockerfile` and `start_space.sh` are configured for HF Spaces deployment:
|
| 85 |
+
- Exposes port 7860 (HF Spaces requirement)
|
| 86 |
+
- Uses Nginx as reverse proxy
|
| 87 |
+
- Pulls models from DagsHub via DVC on startup
|
| 88 |
+
|
| 89 |
+
**Secrets required on HF Spaces:**
|
| 90 |
+
- `DAGSHUB_USERNAME`
|
| 91 |
+
- `DAGSHUB_TOKEN`
|
docker-compose.yml → docker/docker-compose.yml
RENAMED
|
@@ -1,18 +1,18 @@
|
|
| 1 |
services:
|
| 2 |
hopcroft-api:
|
| 3 |
build:
|
| 4 |
-
context:
|
| 5 |
dockerfile: Dockerfile
|
| 6 |
container_name: hopcroft-api
|
| 7 |
ports:
|
| 8 |
- "8080:8080"
|
| 9 |
env_file:
|
| 10 |
-
-
|
| 11 |
environment:
|
| 12 |
- PROJECT_NAME=Hopcroft
|
| 13 |
volumes:
|
| 14 |
# Bind mount: enables live code reloading for development
|
| 15 |
-
-
|
| 16 |
# Named volume: persistent storage for application logs
|
| 17 |
- hopcroft-logs:/app/logs
|
| 18 |
networks:
|
|
@@ -30,8 +30,8 @@ services:
|
|
| 30 |
|
| 31 |
hopcroft-gui:
|
| 32 |
build:
|
| 33 |
-
context:
|
| 34 |
-
dockerfile: Dockerfile.streamlit
|
| 35 |
container_name: hopcroft-gui
|
| 36 |
ports:
|
| 37 |
- "8501:8501"
|
|
@@ -39,7 +39,7 @@ services:
|
|
| 39 |
- API_BASE_URL=http://hopcroft-api:8080
|
| 40 |
volumes:
|
| 41 |
# Bind mount for development hot-reload
|
| 42 |
-
-
|
| 43 |
networks:
|
| 44 |
- hopcroft-net
|
| 45 |
depends_on:
|
|
@@ -51,8 +51,8 @@ services:
|
|
| 51 |
image: prom/prometheus:latest
|
| 52 |
container_name: prometheus
|
| 53 |
volumes:
|
| 54 |
-
-
|
| 55 |
-
-
|
| 56 |
ports:
|
| 57 |
- "9090:9090"
|
| 58 |
networks:
|
|
@@ -65,7 +65,7 @@ services:
|
|
| 65 |
image: prom/alertmanager:latest
|
| 66 |
container_name: alertmanager
|
| 67 |
volumes:
|
| 68 |
-
-
|
| 69 |
ports:
|
| 70 |
- "9093:9093"
|
| 71 |
networks:
|
|
@@ -84,9 +84,9 @@ services:
|
|
| 84 |
- GF_SERVER_ROOT_URL=http://localhost:3000
|
| 85 |
volumes:
|
| 86 |
# Provisioning: auto-configure datasources and dashboards
|
| 87 |
-
-
|
| 88 |
-
-
|
| 89 |
-
-
|
| 90 |
# Persistent storage for Grafana data
|
| 91 |
- grafana-data:/var/lib/grafana
|
| 92 |
networks:
|
|
@@ -95,7 +95,7 @@ services:
|
|
| 95 |
- prometheus
|
| 96 |
restart: unless-stopped
|
| 97 |
healthcheck:
|
| 98 |
-
test: ["CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1"]
|
| 99 |
interval: 30s
|
| 100 |
timeout: 10s
|
| 101 |
retries: 3
|
|
@@ -115,7 +115,6 @@ services:
|
|
| 115 |
volumes:
|
| 116 |
- pushgateway-data:/data
|
| 117 |
|
| 118 |
-
|
| 119 |
networks:
|
| 120 |
hopcroft-net:
|
| 121 |
driver: bridge
|
|
|
|
| 1 |
services:
|
| 2 |
hopcroft-api:
|
| 3 |
build:
|
| 4 |
+
context: ..
|
| 5 |
dockerfile: Dockerfile
|
| 6 |
container_name: hopcroft-api
|
| 7 |
ports:
|
| 8 |
- "8080:8080"
|
| 9 |
env_file:
|
| 10 |
+
- ../.env
|
| 11 |
environment:
|
| 12 |
- PROJECT_NAME=Hopcroft
|
| 13 |
volumes:
|
| 14 |
# Bind mount: enables live code reloading for development
|
| 15 |
+
- ../hopcroft_skill_classification_tool_competition:/app/hopcroft_skill_classification_tool_competition
|
| 16 |
# Named volume: persistent storage for application logs
|
| 17 |
- hopcroft-logs:/app/logs
|
| 18 |
networks:
|
|
|
|
| 30 |
|
| 31 |
hopcroft-gui:
|
| 32 |
build:
|
| 33 |
+
context: ..
|
| 34 |
+
dockerfile: docker/Dockerfile.streamlit
|
| 35 |
container_name: hopcroft-gui
|
| 36 |
ports:
|
| 37 |
- "8501:8501"
|
|
|
|
| 39 |
- API_BASE_URL=http://hopcroft-api:8080
|
| 40 |
volumes:
|
| 41 |
# Bind mount for development hot-reload
|
| 42 |
+
- ../hopcroft_skill_classification_tool_competition/streamlit_app.py:/app/streamlit_app.py
|
| 43 |
networks:
|
| 44 |
- hopcroft-net
|
| 45 |
depends_on:
|
|
|
|
| 51 |
image: prom/prometheus:latest
|
| 52 |
container_name: prometheus
|
| 53 |
volumes:
|
| 54 |
+
- ../monitoring/prometheus/prometheus.yml:/etc/prometheus/prometheus.yml
|
| 55 |
+
- ../monitoring/prometheus/alert_rules.yml:/etc/prometheus/alert_rules.yml
|
| 56 |
ports:
|
| 57 |
- "9090:9090"
|
| 58 |
networks:
|
|
|
|
| 65 |
image: prom/alertmanager:latest
|
| 66 |
container_name: alertmanager
|
| 67 |
volumes:
|
| 68 |
+
- ../monitoring/alertmanager/config.yml:/etc/alertmanager/config.yml
|
| 69 |
ports:
|
| 70 |
- "9093:9093"
|
| 71 |
networks:
|
|
|
|
| 84 |
- GF_SERVER_ROOT_URL=http://localhost:3000
|
| 85 |
volumes:
|
| 86 |
# Provisioning: auto-configure datasources and dashboards
|
| 87 |
+
- ../monitoring/grafana/provisioning/datasources:/etc/grafana/provisioning/datasources
|
| 88 |
+
- ../monitoring/grafana/provisioning/dashboards:/etc/grafana/provisioning/dashboards
|
| 89 |
+
- ../monitoring/grafana/dashboards:/var/lib/grafana/dashboards
|
| 90 |
# Persistent storage for Grafana data
|
| 91 |
- grafana-data:/var/lib/grafana
|
| 92 |
networks:
|
|
|
|
| 95 |
- prometheus
|
| 96 |
restart: unless-stopped
|
| 97 |
healthcheck:
|
| 98 |
+
test: [ "CMD-SHELL", "curl -f http://localhost:3000/api/health || exit 1" ]
|
| 99 |
interval: 30s
|
| 100 |
timeout: 10s
|
| 101 |
retries: 3
|
|
|
|
| 115 |
volumes:
|
| 116 |
- pushgateway-data:/data
|
| 117 |
|
|
|
|
| 118 |
networks:
|
| 119 |
hopcroft-net:
|
| 120 |
driver: bridge
|
nginx.conf → docker/nginx.conf
RENAMED
|
File without changes
|
{scripts → docker/scripts}/start_space.sh
RENAMED
|
@@ -106,7 +106,7 @@ if ! command -v nginx &> /dev/null; then
|
|
| 106 |
echo "$(date) - ERROR: nginx not found in PATH"
|
| 107 |
exit 1
|
| 108 |
fi
|
| 109 |
-
nginx -c /app/nginx.conf -g "daemon off;" >> /tmp/nginx_startup.log 2>&1 &
|
| 110 |
|
| 111 |
echo "$(date) - Waiting for Nginx to initialize..."
|
| 112 |
sleep 5
|
|
|
|
| 106 |
echo "$(date) - ERROR: nginx not found in PATH"
|
| 107 |
exit 1
|
| 108 |
fi
|
| 109 |
+
nginx -c /app/docker/nginx.conf -g "daemon off;" >> /tmp/nginx_startup.log 2>&1 &
|
| 110 |
|
| 111 |
echo "$(date) - Waiting for Nginx to initialize..."
|
| 112 |
sleep 5
|
tests/integration/test_feature_pipeline.py
CHANGED
|
@@ -130,7 +130,8 @@ class TestDataFlowConsistency:
|
|
| 130 |
"Normal clean text",
|
| 131 |
]
|
| 132 |
|
| 133 |
-
|
|
|
|
| 134 |
|
| 135 |
# Clean version
|
| 136 |
clean_df = sample_dataframe.copy()
|
|
@@ -142,7 +143,8 @@ class TestDataFlowConsistency:
|
|
| 142 |
"Normal clean text",
|
| 143 |
]
|
| 144 |
|
| 145 |
-
|
|
|
|
| 146 |
|
| 147 |
# Features should be similar (cleaning is applied to both)
|
| 148 |
# But not necessarily identical due to stemming
|
|
@@ -283,19 +285,20 @@ class TestErrorHandlingInPipeline:
|
|
| 283 |
extract_tfidf_features(df)
|
| 284 |
|
| 285 |
def test_pipeline_with_all_nan_text(self):
|
| 286 |
-
"""Test pipeline with all NaN text values.
|
|
|
|
|
|
|
|
|
|
|
|
|
| 287 |
df = pd.DataFrame({
|
| 288 |
'issue text': [None, None, None],
|
| 289 |
'issue description': [None, None, None],
|
| 290 |
'Label1': [1, 0, 1],
|
| 291 |
})
|
| 292 |
|
| 293 |
-
#
|
| 294 |
-
|
| 295 |
-
|
| 296 |
-
# May result in zero features for all samples
|
| 297 |
-
assert features.shape[0] == 3
|
| 298 |
-
assert not np.any(np.isnan(features))
|
| 299 |
|
| 300 |
def test_pipeline_with_empty_labels(self):
|
| 301 |
"""Test pipeline when no labels are present."""
|
|
|
|
| 130 |
"Normal clean text",
|
| 131 |
]
|
| 132 |
|
| 133 |
+
# Use min_df=1 and max_df=1.0 for small test datasets to avoid empty vocabulary
|
| 134 |
+
features_dirty, _ = extract_tfidf_features(dirty_df, max_features=50, min_df=1, max_df=1.0)
|
| 135 |
|
| 136 |
# Clean version
|
| 137 |
clean_df = sample_dataframe.copy()
|
|
|
|
| 143 |
"Normal clean text",
|
| 144 |
]
|
| 145 |
|
| 146 |
+
# Use min_df=1 and max_df=1.0 for small test datasets
|
| 147 |
+
features_clean, _ = extract_tfidf_features(clean_df, max_features=50, min_df=1, max_df=1.0)
|
| 148 |
|
| 149 |
# Features should be similar (cleaning is applied to both)
|
| 150 |
# But not necessarily identical due to stemming
|
|
|
|
| 285 |
extract_tfidf_features(df)
|
| 286 |
|
| 287 |
def test_pipeline_with_all_nan_text(self):
|
| 288 |
+
"""Test pipeline with all NaN text values raises appropriate error.
|
| 289 |
+
|
| 290 |
+
TF-IDF cannot build a vocabulary from empty/NaN documents,
|
| 291 |
+
so it should raise a ValueError with a descriptive message.
|
| 292 |
+
"""
|
| 293 |
df = pd.DataFrame({
|
| 294 |
'issue text': [None, None, None],
|
| 295 |
'issue description': [None, None, None],
|
| 296 |
'Label1': [1, 0, 1],
|
| 297 |
})
|
| 298 |
|
| 299 |
+
# TF-IDF should raise ValueError for empty vocabulary
|
| 300 |
+
with pytest.raises(ValueError, match="empty vocabulary"):
|
| 301 |
+
extract_tfidf_features(df, max_features=50)
|
|
|
|
|
|
|
|
|
|
| 302 |
|
| 303 |
def test_pipeline_with_empty_labels(self):
|
| 304 |
"""Test pipeline when no labels are present."""
|