Restructure repository for final submission
فشلت بعض الفحوصات
CI/CD - Build & Deploy to Ghaymah Cloud / build-and-test (push) Has been cancelled
CI/CD - Build & Deploy to Ghaymah Cloud / deploy-staging (push) Has been cancelled
CI/CD - Build & Deploy to Ghaymah Cloud / deploy-production (push) Has been cancelled

هذا الالتزام موجود في:
Ubuntu
2026-07-28 13:22:05 +00:00
الأصل b43f9bf706
التزام 54af9f97ac
27 ملفات معدلة مع 1939 إضافات و0 حذوفات

عرض الملف

@@ -0,0 +1,20 @@
FROM python:3.12-slim
WORKDIR /app
# Install dependencies first (layer caching)
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
# Copy app code
COPY app.py .
EXPOSE 5000
# Basic container-level health check (Docker/most platforms respect this)
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \
CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:5000/health')" || exit 1
# gunicorn for production-grade serving instead of Flask dev server
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "2", "app:app"]

179
q1-deploy-monitor/README.md Normal file
عرض الملف

@@ -0,0 +1,179 @@
# Q1 - Deploy and Monitor an API on Ghaymah Cloud
## Overview
This project demonstrates deploying a Dockerized Python API to **Ghaymah Cloud** and implementing a simple monitoring solution.
The application exposes a `/health` endpoint that is continuously monitored using a Python script. A lightweight HTML dashboard displays the application's health status, response time, uptime, and request statistics.
---
# Project Structure
```
Q1-Deploy-and-Monitoring/
├── screenshots/
│ ├── dashboard-local.png
│ └── dashboard-ghaymah.png
├── Dockerfile
├── app.py
├── health-check.py
├── dashboard.html
├── monitor-log.csv
├── requirements.txt
├── install-docker.sh
└── README.md
```
---
# Task Requirements
This implementation satisfies all requirements of Question 1.
| Requirement | Status |
|------------|--------|
| Dockerize the API | ✅ |
| Deploy to Ghaymah Cloud | ✅ |
| Implement `/health` endpoint | ✅ |
| Monitoring script (every 30 seconds) | ✅ |
| Monitoring Dashboard | ✅ |
---
# Technologies
- Python
- Flask
- Docker
- HTML
- CSS
- JavaScript
- Ghaymah Cloud
- Ghaymah CLI
---
# API
## Health Endpoint
```
GET /health
```
Example response
```json
{
"status": "healthy"
}
```
The monitoring script periodically sends requests to this endpoint to verify application availability.
---
# Docker
Build the Docker image
```bash
docker build -t exam-api .
```
Run the container
```bash
docker run -d -p 5000:5000 exam-api
```
---
# Monitoring Script
The monitoring script (`health-check.py`) executes every **30 seconds** and performs the following operations:
- Sends an HTTP request to `/health`
- Measures response latency
- Detects application availability
- Records monitoring results
- Updates `monitor-log.csv`
Run the monitor
```bash
python3 health-check.py
```
---
# Monitoring Dashboard
The dashboard was implemented using HTML, CSS and JavaScript.
Displayed metrics include:
- Current application status
- Response time
- Total requests
- Application uptime
- Monitoring history
---
# Deployment using Ghaymah CLI
The application was deployed to **Ghaymah Cloud** using the official CLI.
Deployment workflow:
1. Install Ghaymah CLI
2. Authenticate using account credentials
3. Select deployment configuration
4. Launch the application
Example commands
```bash
gy auth login
cp .ghaymah.production.json .ghaymah.json
gy resource app launch
```
---
# Dashboard Preview
## Local Testing
The dashboard was first tested locally against the application running on the EC2 instance before deploying to Ghaymah Cloud.
![Local Dashboard](screenshots/dashboard-local.png)
---
## Ghaymah Cloud Deployment
After deployment, the dashboard successfully monitored the live application hosted on **Ghaymah Cloud**.
![Ghaymah Dashboard](screenshots/dashboard-ghaymah.png)
---
# Result
The project successfully demonstrates:
- Docker containerization
- Cloud deployment on Ghaymah
- Automated health monitoring
- Response time tracking
- Monitoring dashboard
- Continuous application health verification
This implementation satisfies all requirements of **Question 1 Deploy and Monitor an API on Ghaymah Cloud**.

60
q1-deploy-monitor/app.py Normal file
عرض الملف

@@ -0,0 +1,60 @@
"""
Simple API app for Ghaymah SRE exam - Q1
Provides:
GET / -> basic info
GET /health -> health check endpoint (used by monitoring)
GET /metrics -> simple JSON metrics (request count, uptime)
"""
import time
from flask import Flask, jsonify
app = Flask(__name__)
@app.after_request
def add_cors_headers(response):
# Allows the dashboard (served from a different origin, e.g. file://
# or another host) to call this API from the browser.
response.headers["Access-Control-Allow-Origin"] = "*"
response.headers["Access-Control-Allow-Methods"] = "GET, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "Content-Type"
return response
START_TIME = time.time()
REQUEST_COUNT = 0
@app.before_request
def count_requests():
global REQUEST_COUNT
REQUEST_COUNT += 1
@app.route("/")
def index():
return jsonify({
"service": "ghaymah-exam-api",
"message": "API is running"
})
@app.route("/health")
def health():
"""Used by ghaymah.systems platform + our monitoring script."""
return jsonify({
"status": "healthy",
"uptime_seconds": round(time.time() - START_TIME, 2)
}), 200
@app.route("/metrics")
def metrics():
return jsonify({
"uptime_seconds": round(time.time() - START_TIME, 2),
"total_requests": REQUEST_COUNT
})
if __name__ == "__main__":
# 0.0.0.0 required so the container's port is reachable externally
app.run(host="0.0.0.0", port=5000)

عرض الملف

@@ -0,0 +1,211 @@
<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Monitoring Dashboard - Ghaymah Exam Q1</title>
<style>
:root {
--up: #22c55e;
--down: #ef4444;
--bg: #0f172a;
--card: #1e293b;
--text: #e2e8f0;
--muted: #94a3b8;
}
* { box-sizing: border-box; }
body {
margin: 0;
font-family: 'Segoe UI', Tahoma, sans-serif;
background: var(--bg);
color: var(--text);
padding: 24px;
}
h1 { font-size: 22px; margin-bottom: 4px; }
.subtitle { color: var(--muted); margin-bottom: 24px; font-size: 14px; }
.config {
display: flex;
gap: 8px;
margin-bottom: 24px;
}
.config input {
flex: 1;
padding: 10px 12px;
border-radius: 8px;
border: 1px solid #334155;
background: var(--card);
color: var(--text);
font-size: 14px;
}
.config button {
padding: 10px 20px;
border-radius: 8px;
border: none;
background: #3b82f6;
color: white;
cursor: pointer;
font-size: 14px;
}
.config button:hover { background: #2563eb; }
.grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 16px;
margin-bottom: 24px;
}
.card {
background: var(--card);
border-radius: 12px;
padding: 20px;
border: 1px solid #334155;
}
.card .label {
color: var(--muted);
font-size: 13px;
margin-bottom: 8px;
}
.card .value {
font-size: 28px;
font-weight: 700;
}
.status-up { color: var(--up); }
.status-down { color: var(--down); }
.dot {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-left: 8px;
}
.dot-up { background: var(--up); box-shadow: 0 0 8px var(--up); }
.dot-down { background: var(--down); box-shadow: 0 0 8px var(--down); }
.log {
background: var(--card);
border-radius: 12px;
border: 1px solid #334155;
padding: 16px;
max-height: 260px;
overflow-y: auto;
}
.log table { width: 100%; border-collapse: collapse; font-size: 13px; }
.log th, .log td { text-align: right; padding: 6px 8px; border-bottom: 1px solid #334155; }
.log th { color: var(--muted); font-weight: 500; }
</style>
</head>
<body>
<h1>لوحة مراقبة التطبيق</h1>
<div class="subtitle">Ghaymah SRE Exam — Q1 Monitoring Dashboard</div>
<div class="config">
<input id="appUrl" type="text" placeholder="ضع رابط التطبيق المنشور على ghaymah.systems (مثال: https://myapp.ghaymah.systems)">
<button onclick="startMonitoring()">ابدأ المراقبة</button>
</div>
<div class="grid">
<div class="card">
<div class="label">الحالة (Status)</div>
<div class="value" id="statusValue"></div>
</div>
<div class="card">
<div class="label">زمن الاستجابة (Response Time)</div>
<div class="value" id="latencyValue">— ms</div>
</div>
<div class="card">
<div class="label">عدد الطلبات (Total Requests)</div>
<div class="value" id="requestsValue"></div>
</div>
<div class="card">
<div class="label">وقت التشغيل (Uptime)</div>
<div class="value" id="uptimeValue"></div>
</div>
</div>
<div class="log">
<table>
<thead>
<tr><th>الوقت</th><th>الحالة</th><th>زمن الاستجابة</th></tr>
</thead>
<tbody id="logBody"></tbody>
</table>
</div>
<script>
let intervalId = null;
const MAX_LOG_ROWS = 15;
function startMonitoring() {
const url = document.getElementById('appUrl').value.trim();
if (!url) { alert('من فضلك ضع رابط التطبيق'); return; }
if (intervalId) clearInterval(intervalId);
checkNow(url);
intervalId = setInterval(() => checkNow(url), 30000);
}
async function checkNow(baseUrl) {
const cleanUrl = baseUrl.replace(/\/$/, '');
const start = performance.now();
let status = 'DOWN';
let statusCode = null;
try {
const res = await fetch(cleanUrl + '/health', { cache: 'no-store' });
statusCode = res.status;
status = res.ok ? 'UP' : 'DEGRADED';
} catch (e) {
status = 'DOWN';
}
const latency = Math.round(performance.now() - start);
updateStatusCard(status, latency);
addLogRow(status, latency);
// metrics endpoint (requests count + uptime) - best effort
try {
const mRes = await fetch(cleanUrl + '/metrics', { cache: 'no-store' });
if (mRes.ok) {
const data = await mRes.json();
document.getElementById('requestsValue').textContent = data.total_requests ?? '—';
document.getElementById('uptimeValue').textContent = formatUptime(data.uptime_seconds);
}
} catch (e) { /* metrics optional */ }
}
function updateStatusCard(status, latency) {
const statusEl = document.getElementById('statusValue');
statusEl.textContent = status;
statusEl.className = 'value ' + (status === 'UP' ? 'status-up' : 'status-down');
document.getElementById('latencyValue').textContent = latency + ' ms';
}
function addLogRow(status, latency) {
const tbody = document.getElementById('logBody');
const row = document.createElement('tr');
const dotClass = status === 'UP' ? 'dot-up' : 'dot-down';
row.innerHTML = `
<td>${new Date().toLocaleTimeString('ar-EG')}</td>
<td><span class="dot ${dotClass}"></span>${status}</td>
<td>${latency} ms</td>
`;
tbody.prepend(row);
while (tbody.rows.length > MAX_LOG_ROWS) {
tbody.deleteRow(tbody.rows.length - 1);
}
}
function formatUptime(seconds) {
if (seconds == null) return '—';
const h = Math.floor(seconds / 3600);
const m = Math.floor((seconds % 3600) / 60);
return `${h}h ${m}m`;
}
</script>
</body>
</html>
<!-- staging test -->

عرض الملف

@@ -0,0 +1,79 @@
#!/usr/bin/env python3
"""
Monitoring script for Q1 - Ghaymah SRE exam.
Checks the deployed app's /health endpoint every 30 seconds,
logs status + response time to a CSV file, and prints live status
to the console.
Usage:
python3 health-check.py https://your-app-url.ghaymah.systems
"""
import sys
import time
import csv
import os
from datetime import datetime, timezone
import urllib.request
import urllib.error
CHECK_INTERVAL_SECONDS = 30
LOG_FILE = "monitor-log.csv"
def check_health(url: str) -> dict:
endpoint = url.rstrip("/") + "/health"
start = time.time()
try:
with urllib.request.urlopen(endpoint, timeout=10) as response:
elapsed_ms = round((time.time() - start) * 1000, 2)
status_code = response.getcode()
return {
"timestamp": datetime.now(timezone.utc).isoformat(),
"status": "UP" if status_code == 200 else "DEGRADED",
"status_code": status_code,
"response_time_ms": elapsed_ms,
}
except (urllib.error.URLError, urllib.error.HTTPError, TimeoutError) as e:
elapsed_ms = round((time.time() - start) * 1000, 2)
return {
"timestamp": datetime.now(timezone.utc).isoformat(),
"status": "DOWN",
"status_code": None,
"response_time_ms": elapsed_ms,
"error": str(e),
}
def log_result(result: dict):
file_exists = os.path.isfile(LOG_FILE)
with open(LOG_FILE, "a", newline="") as f:
fieldnames = ["timestamp", "status", "status_code", "response_time_ms", "error"]
writer = csv.DictWriter(f, fieldnames=fieldnames)
if not file_exists:
writer.writeheader()
writer.writerow({**{"error": ""}, **result})
def main():
if len(sys.argv) < 2:
print("Usage: python3 health-check.py <app_url>")
sys.exit(1)
url = sys.argv[1]
print(f"Monitoring {url}/health every {CHECK_INTERVAL_SECONDS}s. Logging to {LOG_FILE}. Ctrl+C to stop.")
try:
while True:
result = check_health(url)
log_result(result)
print(f"[{result['timestamp']}] {result['status']} "
f"({result['response_time_ms']}ms)"
+ (f" - {result.get('error')}" if result.get("error") else ""))
time.sleep(CHECK_INTERVAL_SECONDS)
except KeyboardInterrupt:
print("\nMonitoring stopped.")
if __name__ == "__main__":
main()

عرض الملف

@@ -0,0 +1,36 @@
#!/bin/bash
set -e
echo ">>> Updating packages..."
sudo apt update
echo ">>> Installing prerequisites..."
sudo apt install -y ca-certificates curl gnupg
echo ">>> Setting up Docker keyring..."
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo ">>> Adding Docker repository..."
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(. /etc/os-release && echo $VERSION_CODENAME) stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
echo ">>> Installing Docker..."
sudo apt update
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
echo ">>> Starting Docker service..."
sudo systemctl start docker
sudo systemctl enable docker
echo ">>> Adding current user to docker group..."
sudo usermod -aG docker $USER
echo ""
echo "=========================================="
echo "Docker installed successfully."
echo "IMPORTANT: run 'newgrp docker' OR log out/in"
echo "then run: docker --version"
echo "=========================================="

عرض الملف

@@ -0,0 +1,35 @@
timestamp,status,status_code,response_time_ms,error
2026-07-27T10:17:38.379006+00:00,UP,200,14.79,
2026-07-27T10:18:08.381609+00:00,UP,200,2.08,
2026-07-27T10:18:38.383866+00:00,UP,200,1.82,
2026-07-27T10:19:08.386140+00:00,UP,200,1.77,
2026-07-27T10:19:38.388582+00:00,UP,200,2.01,
2026-07-27T10:20:08.390836+00:00,UP,200,1.85,
2026-07-27T10:20:38.394181+00:00,UP,200,2.97,
2026-07-27T10:21:08.396674+00:00,UP,200,1.98,
2026-07-27T10:21:38.398784+00:00,UP,200,1.7,
2026-07-27T10:22:08.401664+00:00,UP,200,1.84,
2026-07-27T10:22:38.403790+00:00,UP,200,1.71,
2026-07-27T10:23:08.406096+00:00,UP,200,1.78,
2026-07-27T10:23:38.407503+00:00,DOWN,,0.43,<urlopen error [Errno 111] Connection refused>
2026-07-27T10:24:08.408384+00:00,DOWN,,0.54,<urlopen error [Errno 111] Connection refused>
2026-07-27T10:24:38.409097+00:00,DOWN,,0.38,<urlopen error [Errno 111] Connection refused>
2026-07-27T10:25:08.409873+00:00,DOWN,,0.4,<urlopen error [Errno 111] Connection refused>
2026-07-27T10:25:38.412335+00:00,UP,200,2.12,
2026-07-27T10:26:08.414479+00:00,UP,200,1.78,
2026-07-27T10:26:38.416810+00:00,UP,200,1.92,
2026-07-27T10:27:08.418967+00:00,UP,200,1.74,
2026-07-27T10:27:38.421130+00:00,UP,200,1.68,
2026-07-27T10:28:08.423401+00:00,UP,200,1.76,
2026-07-27T10:28:38.425814+00:00,UP,200,1.99,
2026-07-27T10:29:08.428129+00:00,UP,200,1.76,
2026-07-27T10:29:38.430424+00:00,UP,200,1.85,
2026-07-27T10:30:08.432756+00:00,UP,200,1.87,
2026-07-27T10:30:38.435351+00:00,UP,200,1.77,
2026-07-27T10:31:08.437531+00:00,UP,200,1.78,
2026-07-27T10:31:38.439587+00:00,UP,200,1.69,
2026-07-27T10:32:08.441908+00:00,UP,200,1.94,
2026-07-27T10:32:38.443987+00:00,UP,200,1.68,
2026-07-27T10:33:08.446193+00:00,UP,200,1.75,
2026-07-27T10:33:38.448817+00:00,UP,200,1.85,
2026-07-27T10:34:08.451690+00:00,UP,200,1.73,
1 timestamp status status_code response_time_ms error
2 2026-07-27T10:17:38.379006+00:00 UP 200 14.79
3 2026-07-27T10:18:08.381609+00:00 UP 200 2.08
4 2026-07-27T10:18:38.383866+00:00 UP 200 1.82
5 2026-07-27T10:19:08.386140+00:00 UP 200 1.77
6 2026-07-27T10:19:38.388582+00:00 UP 200 2.01
7 2026-07-27T10:20:08.390836+00:00 UP 200 1.85
8 2026-07-27T10:20:38.394181+00:00 UP 200 2.97
9 2026-07-27T10:21:08.396674+00:00 UP 200 1.98
10 2026-07-27T10:21:38.398784+00:00 UP 200 1.7
11 2026-07-27T10:22:08.401664+00:00 UP 200 1.84
12 2026-07-27T10:22:38.403790+00:00 UP 200 1.71
13 2026-07-27T10:23:08.406096+00:00 UP 200 1.78
14 2026-07-27T10:23:38.407503+00:00 DOWN 0.43 <urlopen error [Errno 111] Connection refused>
15 2026-07-27T10:24:08.408384+00:00 DOWN 0.54 <urlopen error [Errno 111] Connection refused>
16 2026-07-27T10:24:38.409097+00:00 DOWN 0.38 <urlopen error [Errno 111] Connection refused>
17 2026-07-27T10:25:08.409873+00:00 DOWN 0.4 <urlopen error [Errno 111] Connection refused>
18 2026-07-27T10:25:38.412335+00:00 UP 200 2.12
19 2026-07-27T10:26:08.414479+00:00 UP 200 1.78
20 2026-07-27T10:26:38.416810+00:00 UP 200 1.92
21 2026-07-27T10:27:08.418967+00:00 UP 200 1.74
22 2026-07-27T10:27:38.421130+00:00 UP 200 1.68
23 2026-07-27T10:28:08.423401+00:00 UP 200 1.76
24 2026-07-27T10:28:38.425814+00:00 UP 200 1.99
25 2026-07-27T10:29:08.428129+00:00 UP 200 1.76
26 2026-07-27T10:29:38.430424+00:00 UP 200 1.85
27 2026-07-27T10:30:08.432756+00:00 UP 200 1.87
28 2026-07-27T10:30:38.435351+00:00 UP 200 1.77
29 2026-07-27T10:31:08.437531+00:00 UP 200 1.78
30 2026-07-27T10:31:38.439587+00:00 UP 200 1.69
31 2026-07-27T10:32:08.441908+00:00 UP 200 1.94
32 2026-07-27T10:32:38.443987+00:00 UP 200 1.68
33 2026-07-27T10:33:08.446193+00:00 UP 200 1.75
34 2026-07-27T10:33:38.448817+00:00 UP 200 1.85
35 2026-07-27T10:34:08.451690+00:00 UP 200 1.73

عرض الملف

@@ -0,0 +1,3 @@
flask==3.0.3
gunicorn==22.0.0

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 135 KiB

ملف ثنائي غير معروض.

بعد

العرض:  |  الارتفاع:  |  الحجم: 134 KiB