intial commit
هذا الالتزام موجود في:
1
common-qabilah/qabilah-profile.md
Normal file
1
common-qabilah/qabilah-profile.md
Normal file
@@ -0,0 +1 @@
|
|||||||
|
https://qabilah.com/profile/mostafa-bebars/posts
|
||||||
18
q1-deploy-monitor/Dockerfile
Normal file
18
q1-deploy-monitor/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Use a lightweight Node base image
|
||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
# Set the working directory inside the container
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Copy dependency definitions and install them
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install --production
|
||||||
|
|
||||||
|
# Copy the application code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose the API port
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
CMD ["npm", "start"]
|
||||||
105
q1-deploy-monitor/dashboard.html
Normal file
105
q1-deploy-monitor/dashboard.html
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>API Metrics Dashboard</title>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: system-ui, -apple-system, sans-serif;
|
||||||
|
background: #f4f4f9;
|
||||||
|
padding: 2rem;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
.grid {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||||
|
gap: 1.5rem;
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
padding: 2rem;
|
||||||
|
border-radius: 12px;
|
||||||
|
box-shadow: 0 4px 6px rgba(0,0,0,0.05);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin: 0 0 0.5rem;
|
||||||
|
font-size: 1rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
color: #666;
|
||||||
|
}
|
||||||
|
.value {
|
||||||
|
font-size: 2.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: #111;
|
||||||
|
}
|
||||||
|
.status-up { color: #10b981; }
|
||||||
|
.status-down { color: #ef4444; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="grid">
|
||||||
|
<div class="card">
|
||||||
|
<h2>System Status</h2>
|
||||||
|
<div id="status" class="value">Loading...</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h2>Avg Response Time</h2>
|
||||||
|
<div class="value"><span id="responseTime">0</span> ms</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h2>Dashboard Pings</h2>
|
||||||
|
<div class="value" id="reqCount">0</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
// Ensure this matches your running API (local or deployed)
|
||||||
|
const API_URL = 'https://bebars-64f19cdda81b.hosted.ghaymah.systems/health';
|
||||||
|
|
||||||
|
// Variables stored in the browser's memory
|
||||||
|
let requestCount = 0;
|
||||||
|
let totalResponseTimeMs = 0;
|
||||||
|
|
||||||
|
async function fetchMetrics() {
|
||||||
|
const startTime = Date.now(); // 1. Start the timer
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(API_URL);
|
||||||
|
if (!res.ok) throw new Error('API unreachable');
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
const endTime = Date.now(); // 2. Stop the timer
|
||||||
|
const duration = endTime - startTime; // 3. Calculate how long it took
|
||||||
|
|
||||||
|
//Do the math
|
||||||
|
requestCount++;
|
||||||
|
totalResponseTimeMs += duration;
|
||||||
|
const avgResponseTime = Math.round(totalResponseTimeMs / requestCount);
|
||||||
|
|
||||||
|
// Update the UI
|
||||||
|
const statusEl = document.getElementById('status');
|
||||||
|
statusEl.textContent = data.status;
|
||||||
|
statusEl.className = data.status === 'UP' ? 'value status-up' : 'value status-down';
|
||||||
|
|
||||||
|
document.getElementById('responseTime').textContent = avgResponseTime;
|
||||||
|
document.getElementById('reqCount').textContent = requestCount;
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
const statusEl = document.getElementById('status');
|
||||||
|
statusEl.textContent = 'DOWN';
|
||||||
|
statusEl.className = 'value status-down';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fetch immediately, then poll every 5 seconds
|
||||||
|
fetchMetrics();
|
||||||
|
setInterval(fetchMetrics, 5000);
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
22
q1-deploy-monitor/monitor.sh
Normal file
22
q1-deploy-monitor/monitor.sh
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Replace this with your actual deployed Ghaymah URL
|
||||||
|
API_URL="https://bebars-64f19cdda81b.hosted.ghaymah.systems/health"
|
||||||
|
|
||||||
|
echo "Starting monitoring for $API_URL..."
|
||||||
|
echo "Press [CTRL+C] to stop."
|
||||||
|
|
||||||
|
while true; do
|
||||||
|
TIMESTAMP=$(date +"%Y-%m-%d %H:%M:%S")
|
||||||
|
|
||||||
|
# Fetch only the HTTP status code
|
||||||
|
HTTP_STATUS=$(curl -o /dev/null -s -w "%{http_code}" "$API_URL")
|
||||||
|
|
||||||
|
if [ "$HTTP_STATUS" -eq 200 ]; then
|
||||||
|
echo "[$TIMESTAMP] ✅ Status: $HTTP_STATUS - App is HEALTHY"
|
||||||
|
else
|
||||||
|
echo "[$TIMESTAMP] ❌ Status: $HTTP_STATUS - App is DOWN or UNREACHABLE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
sleep 30
|
||||||
|
done
|
||||||
18
q1-deploy-monitor/package.json
Normal file
18
q1-deploy-monitor/package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "q1-deploy-monitor",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"start": "node server.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"dependencies": {
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"express": "^4.18.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
23
q1-deploy-monitor/server.js
Normal file
23
q1-deploy-monitor/server.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const cors = require('cors');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
let requestCount = 0;
|
||||||
|
let totalResponseTime = 0;
|
||||||
|
|
||||||
|
// cors
|
||||||
|
app.use(cors());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// The requested health endpoint
|
||||||
|
app.get('/health', (req, res) => {
|
||||||
|
res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`API running on port ${port}`);
|
||||||
|
});
|
||||||
51
q2-postmortem/postmortem-report.md
Normal file
51
q2-postmortem/postmortem-report.md
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
# Incident Postmortem Analysis: Application Outage (OOMKilled)
|
||||||
|
|
||||||
|
## 1. Summary
|
||||||
|
The application deployed on the Ghaymah platform experienced a complete outage lasting 45 minutes. The service continuously crashed and failed to start due to recurring `OOMKilled` (Out of Memory) errors. This resulted in a 100% failure rate for incoming requests during the incident window until mitigation measures were applied.
|
||||||
|
|
||||||
|
## 2. Timeline of Events
|
||||||
|
|
||||||
|
* **T-00:00 - Traffic Spike / Memory Leak Initiated:** Application memory consumption begins to spike uncontrollably due to an anomalous load or internal memory leak.
|
||||||
|
* **T+00:05 - Initial OOMKilled Event:** Container RAM usage hits the hard limit. The Ghaymah orchestrator forcefully terminates the process to protect the node, logging an `OOMKilled` event.
|
||||||
|
* **T+00:06 to T+00:44 - CrashLoopBackOff State:** Ghaymah automatically attempts to restart the container, but it repeatedly exceeds memory limits shortly after booting. Service remains entirely unavailable.
|
||||||
|
* **T+00:45 - Incident Mitigated:** Engineers manually intervene by vertically scaling the container's memory limits or deploying a hotfix. The application stabilizes and resumes serving traffic.
|
||||||
|
|
||||||
|
## 3. Root Cause
|
||||||
|
An `OOMKilled` error occurs when a container attempts to consume more memory than its configured limits allow. The underlying causes typically involve:
|
||||||
|
* **Memory Leak:** A flaw in the application code where unused memory is not released (e.g., inefficient garbage collection, unclosed connections).
|
||||||
|
* **Unoptimized Queries/Payloads:** The application attempted to process an unusually large dataset in memory all at once rather than using streams or pagination.
|
||||||
|
* **Under-provisioning:** The application lacked the baseline memory required to handle a legitimate surge in concurrent user traffic.
|
||||||
|
|
||||||
|
## 4. Action Items & Recommendations
|
||||||
|
* **Code Profiling:** Conduct memory profiling on the application to identify and patch memory leaks.
|
||||||
|
* **Pagination/Streaming:** Ensure large data processing tasks use streams rather than loading entire datasets into RAM.
|
||||||
|
* **Capacity Tuning:** Re-evaluate and adjust the baseline memory limits and requests configured for the container.
|
||||||
|
* **Implement Auto-scaling:** Configure dynamic scaling policies to absorb sudden spikes automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Auto-Scaling Policy Design for Ghaymah
|
||||||
|
|
||||||
|
Ghaymah natively supports both horizontal and vertical auto-scaling based on CPU and memory usage. To prevent this issue from recurring, a proactive scaling policy should be implemented:
|
||||||
|
|
||||||
|
* **Target Metric:** Average Container Memory Utilization.
|
||||||
|
* **Scale-Out (Increase Capacity):**
|
||||||
|
* **Condition:** If Memory Utilization is greater than 75% for a sustained period of 2 minutes.
|
||||||
|
* **Action:** Add 1 additional container replica (Horizontal Scaling) or dynamically increase the RAM allocation (Vertical Scaling).
|
||||||
|
* **Scale-In (Decrease Capacity):**
|
||||||
|
* **Condition:** If Memory Utilization falls below 40% for 5 minutes.
|
||||||
|
* **Action:** Remove 1 replica to optimize resource consumption.
|
||||||
|
* **Limits:** Minimum of 2 replicas (for high availability) and a Maximum of 10 replicas (to control cloud spend).
|
||||||
|
|
||||||
|
**Why this works:** The 75% threshold leaves a 25% safety buffer. This gives the Ghaymah platform enough time to spin up new instances and distribute the load before any single container hits the 100% `OOMKilled` threshold.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Early Detection Using Ghaymah Monitoring Tools
|
||||||
|
|
||||||
|
Relying on user complaints or complete downtime is an anti-pattern. You can detect memory exhaustion early using Ghaymah's built-in monitoring:
|
||||||
|
|
||||||
|
* **Threshold Alerts:** Configure Ghaymah to send automated alerts (via Slack, email, or webhook) when memory usage reaches 70% and 80%. This provides a critical window for intervention before the container crashes.
|
||||||
|
* **Restart Rate Monitoring:** Set an alert if the container restart count exceeds 1 within a 15-minute window. Frequent restarts are the earliest indicator of a `CrashLoopBackOff` state.
|
||||||
|
* **Application Logs Analysis:** Monitor logs for warning signs such as garbage collection (GC) taking excessively long, or application-level out-of-memory warnings that often precede the infrastructure-level kill signal.
|
||||||
|
* **Endpoint Health Checks:** Ensure proper Liveness and Readiness probes are configured. If high memory pressure degrades application performance, the Readiness probe should fail, instructing Ghaymah's load balancer to stop routing traffic to the struggling instance before it dies.
|
||||||
17
q3-cicd/.ghaymah.json
Normal file
17
q3-cicd/.ghaymah.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"id": "9299b6b6-ff2e-4984-8d85-5bdb0b809533",
|
||||||
|
"name": "q3",
|
||||||
|
"projectId": "27aacb50-4d56-474c-baae-b52a853b4d57",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"expose": true,
|
||||||
|
"number": 3000
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"publicAccess": {
|
||||||
|
"enabled": true,
|
||||||
|
"domain": "auto"
|
||||||
|
},
|
||||||
|
"resourceTier": "t1",
|
||||||
|
"dockerFileName": "Dockerfile"
|
||||||
|
}
|
||||||
55
q3-cicd/.github/workflows/ci.yml
مباع
Normal file
55
q3-cicd/.github/workflows/ci.yml
مباع
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
|
||||||
|
name: Ghaymah CI/CD Pipeline
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
deploy-staging:
|
||||||
|
name: Deploy to Staging
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Staging is used for pre-production validation before a live release.
|
||||||
|
environment:
|
||||||
|
name: staging
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Ghaymah CLI
|
||||||
|
run: curl -sSL https://cli.ghaymah.systems/install.sh | bash
|
||||||
|
|
||||||
|
- name: Login to Ghaymah
|
||||||
|
run: $HOME/ghaymah/bin/gy auth login --email "${{ secrets.GHAYMAH_EMAIL }}" --password "${{ secrets.GHAYMAH_PW }}" --debug
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
deploy-production:
|
||||||
|
name: Deploy to Production
|
||||||
|
needs: deploy-staging
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
# Production is the live environment and should be protected by manual approval.
|
||||||
|
environment:
|
||||||
|
name: production
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Ghaymah CLI
|
||||||
|
run: curl -sSL https://cli.ghaymah.systems/install.sh | bash
|
||||||
|
|
||||||
|
- name: Login to Ghaymah
|
||||||
|
run: $HOME/ghaymah/bin/gy auth login --email "${{secrets.GHAYMAH_EMAIL}}" --password "${{secrets.GHAYMAH_PW}}" --debug
|
||||||
|
|
||||||
|
- name: Deploy to Ghaymah (Production)
|
||||||
|
run: $HOME/ghaymah/bin/gy resource app launch --debug
|
||||||
18
q3-cicd/Dockerfile
Normal file
18
q3-cicd/Dockerfile
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Use a lightweight Node base image
|
||||||
|
FROM node:18-alpine
|
||||||
|
|
||||||
|
# Set the working directory inside the container
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
|
||||||
|
# Copy dependency definitions and install them
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install --production
|
||||||
|
|
||||||
|
# Copy the application code
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
# Expose the API port
|
||||||
|
EXPOSE 3000
|
||||||
|
|
||||||
|
# Start the application
|
||||||
|
CMD ["npm", "start"]
|
||||||
50
q3-cicd/README.md
Normal file
50
q3-cicd/README.md
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
# Ghaymah CI/CD Guide
|
||||||
|
|
||||||
|
## Staging vs. Production
|
||||||
|
|
||||||
|
- Staging is a pre-production environment used to test changes safely before they reach real users. It is typically isolated from production data and traffic, so teams can validate deployments, configuration, and application behavior without affecting live services.
|
||||||
|
- Production is the live environment that serves real users and business-critical traffic. Deployments here should be controlled carefully, usually with approval gates, protected environments, and rollback plans.
|
||||||
|
- A common release flow is:
|
||||||
|
1. Build and test the application.
|
||||||
|
2. Deploy to staging.
|
||||||
|
3. Validate the release.
|
||||||
|
4. Request approval for production.
|
||||||
|
5. Deploy to production.
|
||||||
|
|
||||||
|
## Connecting the GitHub Actions workflow to Ghaymah CLI
|
||||||
|
|
||||||
|
The workflow uses the Ghaymah CLI to authenticate and deploy the app.
|
||||||
|
|
||||||
|
### 1. Add GitHub secrets
|
||||||
|
Create these repository or environment secrets in GitHub:
|
||||||
|
|
||||||
|
- GHAYMAH_EMAIL
|
||||||
|
- GHAYMAH_PW
|
||||||
|
|
||||||
|
### 2. Install the CLI
|
||||||
|
The workflow installs the CLI during the deployment job:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sSL https://cli.ghaymah.systems/install.sh | bash
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Authenticate with Ghaymah
|
||||||
|
After installation, the workflow logs in with the stored credentials:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$HOME/ghaymah/bin/gy auth login --email "${{ secrets.GHAYMAH_EMAIL }}" --password "${{ secrets.GHAYMAH_PW }}" --debug
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Deploy the application
|
||||||
|
The deployment step then launches the app through Ghaymah:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$HOME/ghaymah/bin/gy resource app launch --debug
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Recommended GitHub environment setup
|
||||||
|
Use GitHub Environments for deployment control:
|
||||||
|
|
||||||
|
- Create a staging environment for regular validation.
|
||||||
|
- Create a production environment and require manual approval before deployment.
|
||||||
|
- Attach the same secrets to the relevant environment when needed.
|
||||||
18
q3-cicd/package.json
Normal file
18
q3-cicd/package.json
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "q1-deploy-monitor",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"start": "node server.js"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"dependencies": {
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"express": "^4.18.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
23
q3-cicd/server.js
Normal file
23
q3-cicd/server.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const cors = require('cors');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
|
||||||
|
let requestCount = 0;
|
||||||
|
let totalResponseTime = 0;
|
||||||
|
|
||||||
|
// cors
|
||||||
|
app.use(cors());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
// The requested health endpoint
|
||||||
|
app.get('/health', (req, res) => {
|
||||||
|
res.status(200).json({ status: 'UP', timestamp: new Date().toISOString() });
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
app.listen(port, () => {
|
||||||
|
console.log(`API running on port ${port}`);
|
||||||
|
});
|
||||||
ثنائية
q4-scalability/architecture.png
Normal file
ثنائية
q4-scalability/architecture.png
Normal file
ملف ثنائي غير معروض.
|
بعد العرض: | الارتفاع: | الحجم: 111 KiB |
20
q4-scalability/calculations.md
Normal file
20
q4-scalability/calculations.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
Container Calculation
|
||||||
|
|
||||||
|
|
||||||
|
### Container Capacity Calculation
|
||||||
|
To handle 15,000 requests per second with a base container capacity of 500 requests per second and a 30% safety margin:
|
||||||
|
|
||||||
|
1. **Base Containers Needed:** 15,000 / 500 = 30 containers
|
||||||
|
2. **Safety Buffer (30%):** 30 * 0.30 = 9 containers
|
||||||
|
3. **Total Required Containers:** 30 + 9 = **39 containers**
|
||||||
|
|
||||||
|
### Cold Start Mitigation Strategy
|
||||||
|
To prevent dropped requests or high latency when spinning up new containers:
|
||||||
|
1. **Pre-warming (Provisioned Concurrency):** Maintain a buffer of extra "warm" containers running just above the current traffic demands.
|
||||||
|
2. **Health & Readiness Probes:** Ensure the load balancer only routes traffic to containers that have passed their readiness checks and are fully booted.
|
||||||
|
3. **Image Optimization:** Keep Docker images small (e.g., using Alpine Linux) so Ghaymah can pull and extract the image rapidly.
|
||||||
|
|
||||||
|
### Using Ghaymah Block Storage for Stateful Workloads
|
||||||
|
Because containers are ephemeral, stateful workloads (like databases) lose data if a container crashes. Ghaymah Block Storage solves this:
|
||||||
|
1. **Provision & Mount:** A persistent block storage volume is provisioned and mounted to a specific directory path inside the container
|
||||||
|
2. **Persistence:** The application writes data directly to this external volume. If the container is destroyed or rescheduled, the volume easily reattaches to the new container, ensuring zero data loss.
|
||||||
17
q5-mithal-monitor/.ghaymah.json
Normal file
17
q5-mithal-monitor/.ghaymah.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"id": "159ccedb-4f26-447c-99e7-bcad1de22120",
|
||||||
|
"name": "q5-mithal-monitor",
|
||||||
|
"projectId": "27aacb50-4d56-474c-baae-b52a853b4d57",
|
||||||
|
"ports": [
|
||||||
|
{
|
||||||
|
"expose": true,
|
||||||
|
"number": 80
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"publicAccess": {
|
||||||
|
"enabled": true,
|
||||||
|
"domain": "auto"
|
||||||
|
},
|
||||||
|
"resourceTier": "t1",
|
||||||
|
"dockerFileName": "Dockerfile"
|
||||||
|
}
|
||||||
34
q5-mithal-monitor/.github/workflows/ci.yml
مباع
Normal file
34
q5-mithal-monitor/.github/workflows/ci.yml
مباع
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
|
||||||
|
name: Ghaymah CI/CD Pipeline
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ "master" ]
|
||||||
|
|
||||||
|
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
deploy-production:
|
||||||
|
name: Deploy to Production
|
||||||
|
needs: build
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout Code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Install Ghaymah CLI
|
||||||
|
run: curl -sSL https://cli.ghaymah.systems/install.sh | bash
|
||||||
|
|
||||||
|
- name: Login to Ghaymah
|
||||||
|
run: $HOME/ghaymah/bin/gy auth login --email "${{ secrets.GHAYMAH_EMAIL }}" --password "${{ secrets.GHAYMAH_PW }}" --debug
|
||||||
|
|
||||||
|
- name: Deploy to Ghaymah (Production)
|
||||||
|
run: $HOME/ghaymah/bin/gy resource app launch --debug
|
||||||
|
|
||||||
28
q5-mithal-monitor/Dockerfile
Normal file
28
q5-mithal-monitor/Dockerfile
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
FROM nginx:alpine
|
||||||
|
|
||||||
|
# Install bash, curl, openssl, coreutils (for date parsing), and cron
|
||||||
|
RUN apk update && apk add bash curl openssl coreutils dcron
|
||||||
|
|
||||||
|
# Clear the default Nginx welcome page first
|
||||||
|
RUN rm -rf /usr/share/nginx/html/*
|
||||||
|
|
||||||
|
# Setup workspace
|
||||||
|
COPY dashboard.html /usr/share/nginx/html/index.html
|
||||||
|
COPY monitor.sh /usr/local/bin/monitor.sh
|
||||||
|
RUN chmod +x /usr/local/bin/monitor.sh
|
||||||
|
|
||||||
|
RUN touch /usr/share/nginx/html/data.csv && \
|
||||||
|
chown -R nginx:nginx /usr/share/nginx/html && \
|
||||||
|
chmod -R 755 /usr/share/nginx/html
|
||||||
|
|
||||||
|
# Add cron job (runs every minute)
|
||||||
|
RUN echo "* * * * * /usr/local/bin/monitor.sh" | crontab -
|
||||||
|
|
||||||
|
# Custom entrypoint to start cron in background, then nginx in foreground
|
||||||
|
RUN echo '#!/bin/sh' > /entrypoint.sh && \
|
||||||
|
echo 'crond -b -l 8' >> /entrypoint.sh && \
|
||||||
|
echo 'nginx -g "daemon off;"' >> /entrypoint.sh && \
|
||||||
|
chmod +x /entrypoint.sh
|
||||||
|
|
||||||
|
EXPOSE 80
|
||||||
|
CMD ["/entrypoint.sh"]
|
||||||
115
q5-mithal-monitor/dashboard.html
Normal file
115
q5-mithal-monitor/dashboard.html
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Mithal Engine Status</title>
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||||
|
<style>
|
||||||
|
body { font-family: system-ui, sans-serif; background: #f4f4f5; margin: 0; padding: 20px; color: #18181b; }
|
||||||
|
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 20px; margin-bottom: 20px; }
|
||||||
|
.card { background: white; padding: 20px; border-radius: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.1); }
|
||||||
|
.metric { font-size: 2rem; font-weight: bold; color: #2563eb; }
|
||||||
|
table { width: 100%; border-collapse: collapse; margin-top: 10px; }
|
||||||
|
th, td { text-align: left; padding: 12px; border-bottom: 1px solid #e4e4e7; }
|
||||||
|
.status-up { color: #16a34a; font-weight: bold; }
|
||||||
|
.status-down { color: #dc2626; font-weight: bold; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<h1>Mithal Engine Monitoring Dashboard</h1>
|
||||||
|
|
||||||
|
<div class="grid">
|
||||||
|
<div class="card">
|
||||||
|
<h3>Uptime (Last 24h)</h3>
|
||||||
|
<div class="metric" id="uptime-indicator">--%</div>
|
||||||
|
</div>
|
||||||
|
<div class="card">
|
||||||
|
<h3>SSL Certificate</h3>
|
||||||
|
<div class="metric" id="ssl-status">-- Days</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card" style="margin-bottom: 20px;">
|
||||||
|
<h3>Response Time (Last Hour)</h3>
|
||||||
|
<canvas id="latencyChart" height="80"></canvas>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="card">
|
||||||
|
<h3>Recent Logs (Last 10 Checks)</h3>
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Timestamp</th>
|
||||||
|
<th>Status</th>
|
||||||
|
<th>Latency (s)</th>
|
||||||
|
<th>DNS (s)</th>
|
||||||
|
<th>Search (s)</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="log-table"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
async function loadData() {
|
||||||
|
const response = await fetch('data.csv');
|
||||||
|
const text = await response.text();
|
||||||
|
|
||||||
|
// Parse CSV
|
||||||
|
const rows = text.trim().split('\n').slice(1).map(row => {
|
||||||
|
const [timestamp, status, latency, ssl_days, dns, search] = row.split(',');
|
||||||
|
return { timestamp, status: parseInt(status), latency: parseFloat(latency), ssl_days, dns, search };
|
||||||
|
});
|
||||||
|
|
||||||
|
if (rows.length === 0) return;
|
||||||
|
|
||||||
|
// Uptime Calculation
|
||||||
|
const upChecks = rows.filter(r => r.status >= 200 && r.status < 400).length;
|
||||||
|
const uptimePct = ((upChecks / rows.length) * 100).toFixed(2);
|
||||||
|
document.getElementById('uptime-indicator').textContent = `${uptimePct}%`;
|
||||||
|
|
||||||
|
// SSL Status
|
||||||
|
const latest = rows[rows.length - 1];
|
||||||
|
document.getElementById('ssl-status').textContent = `${latest.ssl_days} Days`;
|
||||||
|
|
||||||
|
// Chart Data (Last 60 entries = 1 hour)
|
||||||
|
const recentHour = rows.slice(-60);
|
||||||
|
const labels = recentHour.map(r => new Date(r.timestamp).toLocaleTimeString());
|
||||||
|
const data = recentHour.map(r => r.latency);
|
||||||
|
|
||||||
|
new Chart(document.getElementById('latencyChart'), {
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
labels: labels,
|
||||||
|
datasets: [{
|
||||||
|
label: 'Latency (s)',
|
||||||
|
data: data,
|
||||||
|
borderColor: '#2563eb',
|
||||||
|
tension: 0.1,
|
||||||
|
fill: false
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: { animation: false }
|
||||||
|
});
|
||||||
|
|
||||||
|
// Logs Table (Last 10)
|
||||||
|
const last10 = rows.slice(-10).reverse();
|
||||||
|
const tbody = document.getElementById('log-table');
|
||||||
|
tbody.innerHTML = last10.map(r => `
|
||||||
|
<tr>
|
||||||
|
<td>${new Date(r.timestamp).toLocaleString()}</td>
|
||||||
|
<td class="${r.status === 200 ? 'status-up' : 'status-down'}">${r.status}</td>
|
||||||
|
<td>${r.latency.toFixed(3)}</td>
|
||||||
|
<td>${parseFloat(r.dns).toFixed(3)}</td>
|
||||||
|
<td>${parseFloat(r.search).toFixed(3)}</td>
|
||||||
|
</tr>
|
||||||
|
`).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
loadData();
|
||||||
|
setInterval(loadData, 60000); // Refresh every minute
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
41
q5-mithal-monitor/monitor.sh
Normal file
41
q5-mithal-monitor/monitor.sh
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# monitor.sh
|
||||||
|
|
||||||
|
TARGET="mithal.space"
|
||||||
|
URL="https://$TARGET"
|
||||||
|
SEARCH_URL="$URL/?q=test" # Adjust query parameter based on the actual search endpoint
|
||||||
|
if [ -d "/usr/share/nginx/html" ]; then
|
||||||
|
DATA_FILE="/usr/share/nginx/html/data.csv"
|
||||||
|
else
|
||||||
|
DATA_FILE="./data.csv"
|
||||||
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Initialize CSV with headers if it doesn't exist
|
||||||
|
if [ ! -f "$DATA_FILE" ]; then
|
||||||
|
echo "timestamp,status,latency,ssl_days,dns_time,search_time" > "$DATA_FILE"
|
||||||
|
fi
|
||||||
|
|
||||||
|
TIMESTAMP=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
|
||||||
|
|
||||||
|
# 1. Uptime, Latency, and DNS Resolution
|
||||||
|
HTTP_RESPONSE=$(curl -o /dev/null -s -w "%{http_code},%{time_total},%{time_namelookup}" "$URL")
|
||||||
|
STATUS=$(echo "$HTTP_RESPONSE" | cut -d',' -f1)
|
||||||
|
LATENCY=$(echo "$HTTP_RESPONSE" | cut -d',' -f2)
|
||||||
|
DNS_TIME=$(echo "$HTTP_RESPONSE" | cut -d',' -f3)
|
||||||
|
|
||||||
|
# 2. Search Response Time
|
||||||
|
SEARCH_TIME=$(curl -o /dev/null -s -w "%{time_total}" "$SEARCH_URL")
|
||||||
|
|
||||||
|
# 3. SSL Expiration
|
||||||
|
EXP_DATE=$(echo | openssl s_client -servername "$TARGET" -connect "$TARGET:443" 2>/dev/null | openssl x509 -noout -enddate | cut -d= -f2)
|
||||||
|
if [ -n "$EXP_DATE" ]; then
|
||||||
|
EXP_EPOCH=$(date -d "$EXP_DATE" +%s)
|
||||||
|
CURRENT_EPOCH=$(date +%s)
|
||||||
|
SSL_DAYS=$(( (EXP_EPOCH - CURRENT_EPOCH) / 86400 ))
|
||||||
|
else
|
||||||
|
SSL_DAYS=0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Append to CSV
|
||||||
|
echo "$TIMESTAMP,$STATUS,$LATENCY,$SSL_DAYS,$DNS_TIME,$SEARCH_TIME" >> "$DATA_FILE"
|
||||||
المرجع في مشكلة جديدة
حظر مستخدم