هذا الالتزام موجود في:
ayashosha
2026-07-29 05:15:26 +03:00
الأصل 91e3e0f709
التزام 0a4a236f77
13 ملفات معدلة مع 756 إضافات و0 حذوفات

20
.ghaymah.json Normal file
عرض الملف

@@ -0,0 +1,20 @@
{
"id": "00000000-0000-0000-0000-000000000001",
"name": "nodeapp-production",
"projectId": "00000000-0000-0000-0000-000000000002",
"ports": [
{
"expose": true,
"number": 5000
}
],
"publicAccess": {
"enabled": true,
"domain": "auto"
},
"resourceTier": "t1",
"container": {
"image": "docker.io/your-username/nodeapp:production",
"pullSecretName": ""
}
}

15
metrics.json Normal file
عرض الملف

@@ -0,0 +1,15 @@
[
{
"timestamp": "2026-07-29T02:06:33.694428Z",
"latency_ms": 486.49,
"uptime": true,
"status_code": 200,
"dns_ms": 0.48,
"ssl_valid": false,
"ssl_expires": "Sep 15 13:10:47 2026 GMT",
"ssl_days_remaining": null,
"search_ms": 473.48,
"search_status_code": 200,
"overall": false
}
]

عرض الملف

@@ -0,0 +1,6 @@
FROM node:16.3.0-alpine
COPY . /app
RUN mkdir -p /logs && touch /logs/logs.txt
WORKDIR /app
RUN npm install
CMD ["node", "/app/app.js"]

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

@@ -0,0 +1,82 @@
const express = require('express')
const fs = require('fs')
const path = require('path')
const app = express()
const port = 5000
const logDir = '/logs'
const logFile = path.join(logDir, 'logs.txt')
if (!fs.existsSync(logDir)) {
fs.mkdirSync(logDir, { recursive: true })
}
function getStatsFromLogFile() {
if (!fs.existsSync(logFile)) {
return { requestCount: 0, averageResponseTime: 0 }
}
const content = fs.readFileSync(logFile, 'utf8')
const lines = content.split('\n').filter(Boolean)
const requestCount = lines.length
if (requestCount === 0) {
return { requestCount: 0, averageResponseTime: 0 }
}
const totalResponseTime = lines.reduce((total, line) => {
const parts = line.trim().split(' ')
const duration = parseFloat(parts[1])
return total + (Number.isNaN(duration) ? 0 : duration)
}, 0)
return {
requestCount,
averageResponseTime: totalResponseTime / requestCount
}
}
app.use('/dashboard', express.static(path.join(__dirname, 'dashboard'), { index: false }))
app.use((req, res, next) => {
const startTime = Date.now()
const requestTime = new Date(startTime).toISOString()
res.on('finish', () => {
const durationSeconds = (Date.now() - startTime) / 1000
const line = `${req.path} ${durationSeconds.toFixed(3)} ${requestTime} ${res.statusCode}\n`
fs.appendFile(logFile, line, (err) => {
if (err) console.error('Failed to write request log:', err)
})
})
next()
})
app.get('/dashboard', (req, res) => {
res.sendFile(path.join(__dirname, 'dashboard', 'index.html'))
})
app.get('/api/stats', (req, res) => {
const { requestCount, averageResponseTime } = getStatsFromLogFile()
res.json({
requestCount,
averageResponseTime,
status: 'up'
})
})
app.get('/', (req, res) => {
res.send('Hello World from GHAYMAH')
})
app.get('/health', (req, res) => {
res.send('The app up and runing')
})
app.listen(port, () => {
console.log(`Example app listening at http://localhost:${port}`)
console.log(`Dashboard available at http://localhost:${port}/dashboard`)
})

عرض الملف

@@ -0,0 +1,49 @@
const requestCountEl = document.getElementById('requestCount')
const averageTimeEl = document.getElementById('averageTime')
const appStatusEl = document.getElementById('appStatus')
const statusBadgeEl = document.getElementById('statusBadge')
const statusTextEl = document.getElementById('statusText')
const lastUpdatedEl = document.getElementById('lastUpdated')
function setStatus(isUp) {
statusBadgeEl.classList.remove('up', 'down')
appStatusEl.classList.remove('up', 'down')
if (isUp) {
statusBadgeEl.classList.add('up')
appStatusEl.classList.add('up')
statusTextEl.textContent = 'Online'
appStatusEl.textContent = 'Up'
return
}
statusBadgeEl.classList.add('down')
appStatusEl.classList.add('down')
statusTextEl.textContent = 'Offline'
appStatusEl.textContent = 'Down'
}
async function fetchStats() {
try {
const response = await fetch('/api/stats')
if (!response.ok) {
throw new Error('Stats request failed')
}
const data = await response.json()
requestCountEl.textContent = data.requestCount.toLocaleString()
averageTimeEl.textContent = `${data.averageResponseTime.toFixed(3)}s`
setStatus(data.status === 'up')
lastUpdatedEl.textContent = `Last updated: ${new Date().toLocaleTimeString()}`
} catch (error) {
requestCountEl.textContent = '—'
averageTimeEl.textContent = '—'
setStatus(false)
lastUpdatedEl.textContent = 'Last updated: failed to connect'
}
}
fetchStats()
setInterval(fetchStats, 5000)

عرض الملف

@@ -0,0 +1,13 @@
from urllib.request import urlopen
from urllib.error import HTTPError
import time
while True:
try:
with urlopen('https://my-application-2f85feb054d8.hosted.ghaymah.systems/health') as response:
print(f"Status Code: {response.status}")
except HTTPError as error:
print(f"Error Status Code: {error.code}")
time.sleep(30)

عرض الملف

@@ -0,0 +1,50 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Server Dashboard</title>
<link rel="stylesheet" href="/dashboard/style.css">
</head>
<body>
<div class="dashboard">
<header class="header">
<div>
<h1>Server Dashboard</h1>
<p class="subtitle">Live monitoring for your Node.js application</p>
</div>
<div class="status-badge" id="statusBadge">
<span class="status-dot"></span>
<span id="statusText">Checking...</span>
</div>
</header>
<main class="cards">
<section class="card">
<p class="card-label">Total Requests</p>
<p class="card-value" id="requestCount"></p>
<p class="card-hint">All requests received by the server</p>
</section>
<section class="card">
<p class="card-label">Average Response Time</p>
<p class="card-value" id="averageTime"></p>
<p class="card-hint">Mean duration in seconds</p>
</section>
<section class="card">
<p class="card-label">Application Status</p>
<p class="card-value status-value" id="appStatus"></p>
<p class="card-hint">Based on health check</p>
</section>
</main>
<footer class="footer">
<span id="lastUpdated">Last updated: —</span>
<span>Auto-refresh every 5 seconds</span>
</footer>
</div>
<script src="/dashboard/dashboard.js"></script>
</body>
</html>

عرض الملف

@@ -0,0 +1,18 @@
{
"name": "nodeapp",
"version": "1.0.0",
"description": "",
"main": "app.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"nodemon": "^3.1.14"
}
}

158
q1-deploy-monitor/style.css Normal file
عرض الملف

@@ -0,0 +1,158 @@
* {
box-sizing: border-box;
margin: 0;
padding: 0;
}
body {
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
background: linear-gradient(135deg, #0f172a 0%, #1e293b 50%, #0f172a 100%);
min-height: 100vh;
color: #e2e8f0;
padding: 2rem;
}
.dashboard {
max-width: 1100px;
margin: 0 auto;
}
.header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 1.5rem;
margin-bottom: 2rem;
flex-wrap: wrap;
}
.header h1 {
font-size: 2rem;
font-weight: 700;
color: #f8fafc;
}
.subtitle {
margin-top: 0.35rem;
color: #94a3b8;
font-size: 0.95rem;
}
.status-badge {
display: inline-flex;
align-items: center;
gap: 0.6rem;
padding: 0.65rem 1.1rem;
border-radius: 999px;
background: rgba(15, 23, 42, 0.7);
border: 1px solid rgba(148, 163, 184, 0.25);
font-weight: 600;
}
.status-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: #64748b;
box-shadow: 0 0 0 0 rgba(100, 116, 139, 0.5);
}
.status-badge.up .status-dot {
background: #22c55e;
animation: pulse 2s infinite;
}
.status-badge.down .status-dot {
background: #ef4444;
}
.status-badge.up {
border-color: rgba(34, 197, 94, 0.35);
color: #bbf7d0;
}
.status-badge.down {
border-color: rgba(239, 68, 68, 0.35);
color: #fecaca;
}
.cards {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 1.25rem;
}
.card {
background: rgba(30, 41, 59, 0.85);
border: 1px solid rgba(148, 163, 184, 0.15);
border-radius: 16px;
padding: 1.5rem;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
transition: transform 0.2s ease, border-color 0.2s ease;
}
.card:hover {
transform: translateY(-2px);
border-color: rgba(96, 165, 250, 0.35);
}
.card-label {
color: #94a3b8;
font-size: 0.85rem;
text-transform: uppercase;
letter-spacing: 0.08em;
margin-bottom: 0.75rem;
}
.card-value {
font-size: 2.4rem;
font-weight: 700;
color: #f8fafc;
line-height: 1.1;
}
.status-value.up {
color: #4ade80;
}
.status-value.down {
color: #f87171;
}
.card-hint {
margin-top: 0.75rem;
color: #64748b;
font-size: 0.85rem;
}
.footer {
margin-top: 2rem;
display: flex;
justify-content: space-between;
gap: 1rem;
flex-wrap: wrap;
color: #64748b;
font-size: 0.85rem;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.5);
}
70% {
box-shadow: 0 0 0 8px rgba(34, 197, 94, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(34, 197, 94, 0);
}
}
@media (max-width: 600px) {
body {
padding: 1rem;
}
.card-value {
font-size: 2rem;
}
}

عرض الملف

175
q3-cicd/workflow.yml Normal file
عرض الملف

@@ -0,0 +1,175 @@
name: CI/CD - Ghaymah Cloud
on:
push:
branches: [main, staging]
pull_request:
branches: [main, staging]
env:
DOCKERHUB_REPOSITORY: ${{ secrets.DOCKERHUB_REPOSITORY }}
jobs:
build-and-push:
name: Build and Push to Docker Hub
runs-on: ubuntu-latest
if: github.event_name == 'push'
outputs:
environment: ${{ steps.env.outputs.environment }}
image_uri: ${{ steps.image.outputs.image_uri }}
steps:
- name: Checkout code
uses: actions/checkout@v5.0.0
- name: Set deployment environment
id: env
run: |
if [ "${{ github.ref_name }}" = "main" ]; then
echo "environment=production" >> $GITHUB_OUTPUT
else
echo "environment=staging" >> $GITHUB_OUTPUT
fi
- name: Login to Docker Hub
uses: docker/login-action@v3.6.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v6
with:
context: ./nodeapp
push: true
tags: |
docker.io/${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKERHUB_REPOSITORY }}:production
docker.io/${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKERHUB_REPOSITORY }}:staging
docker.io/${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKERHUB_REPOSITORY }}:${{ github.sha }}
- name: Set deployment image
id: image
run: |
IMAGE_BASE="docker.io/${{ secrets.DOCKERHUB_USERNAME }}/${{ env.DOCKERHUB_REPOSITORY }}"
if [ "${{ steps.env.outputs.environment }}" = "production" ]; then
echo "image_uri=${IMAGE_BASE}:production" >> $GITHUB_OUTPUT
else
echo "image_uri=${IMAGE_BASE}:staging" >> $GITHUB_OUTPUT
fi
build:
name: Build Docker Image
runs-on: ubuntu-latest
if: github.event_name == 'pull_request'
steps:
- name: Checkout code
uses: actions/checkout@v5.0.0
- name: Build Docker image
run: docker build ./nodeapp
deploy-staging:
name: Deploy to Staging
runs-on: ubuntu-latest
needs: build-and-push
if: github.event_name == 'push' && github.ref_name == 'staging'
environment:
name: staging
steps:
- name: Checkout code
uses: actions/checkout@v5.0.0
- 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 }}"
- name: Create Ghaymah configuration
working-directory: nodeapp
env:
IMAGE_URI: ${{ needs.build-and-push.outputs.image_uri }}
PROJECT_ID: ${{ secrets.GHAYMAH_PROJECT_ID }}
APP_ID: ${{ secrets.GHAYMAH_APP_ID_STAGING }}
PULL_SECRET: ${{ secrets.DOCKERHUB_PULL_SECRET_NAME }}
run: |
jq -n \
--arg id "${APP_ID}" \
--arg name "nodeapp-staging" \
--arg projectId "${PROJECT_ID}" \
--arg image "${IMAGE_URI}" \
--arg pullSecret "${PULL_SECRET}" \
'{
id: $id,
name: $name,
projectId: $projectId,
ports: [{ expose: true, number: 5000 }],
publicAccess: {
enabled: true,
domain: "auto"
},
resourceTier: "t1",
container: {
image: $image,
pullSecretName: $pullSecret
}
}' > .ghaymah.json
cat .ghaymah.json
- name: Deploy to Ghaymah
working-directory: nodeapp
run: $HOME/ghaymah/bin/gy resource app launch --env staging
deploy-production:
name: Deploy to Production
runs-on: ubuntu-latest
needs: build-and-push
if: github.event_name == 'push' && github.ref_name == 'main'
environment:
name: production
steps:
- name: Checkout code
uses: actions/checkout@v5.0.0
- 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 }}"
- name: Create Ghaymah configuration
working-directory: nodeapp
env:
IMAGE_URI: ${{ needs.build-and-push.outputs.image_uri }}
PROJECT_ID: ${{ secrets.GHAYMAH_PROJECT_ID }}
APP_ID: ${{ secrets.GHAYMAH_APP_ID_PRODUCTION }}
PULL_SECRET: ${{ secrets.DOCKERHUB_PULL_SECRET_NAME }}
run: |
jq -n \
--arg id "${APP_ID}" \
--arg name "nodeapp-production" \
--arg projectId "${PROJECT_ID}" \
--arg image "${IMAGE_URI}" \
--arg pullSecret "${PULL_SECRET}" \
'{
id: $id,
name: $name,
projectId: $projectId,
ports: [{ expose: true, number: 5000 }],
publicAccess: {
enabled: true,
domain: "auto"
},
resourceTier: "t1",
container: {
image: $image,
pullSecretName: $pullSecret
}
}' > .ghaymah.json
cat .ghaymah.json
- name: Deploy to Ghaymah
working-directory: nodeapp
run: $HOME/ghaymah/bin/gy resource app launch --env production

ثنائية
q4-scalability/architecture.png Normal file

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

بعد

العرض:  |  الارتفاع:  |  الحجم: 1.4 MiB

عرض الملف

@@ -0,0 +1,170 @@
# Q4 — قابلية التوسع وتوزيع الأحمال
## 1. Architecture Diagram
راجع الملف `architecture.png` في هذا المجلد.
يوضّح المخطط تدفّق **15,000 req/s** عبر:
| الطبقة | المكوّن | الدور |
|--------|---------|-------|
| Edge | Ghaymah Load Balancer (L7) | توزيع الطلبات، SSL، Health Checks |
| Compute | Auto Scaling Group — 43 حاوية | معالجة الطلبات (500 req/s لكل حاوية) |
| Stateful | Ghaymah Block Storage | تخزين دائم للـ stateful workloads |
| Cache/CDN | Object Storage / CDN | الملفات الثابتة والوسائط |
| Ops | Monitoring & Alerts | مقاييس، تنبيهات، قرارات التوسع |
| Warm Pool | Cold Start Pool | حاويات مُسخَّنة مسبقاً لتقليل زمن الإقلاع |
---
## 2. حساب عدد الحاويات
### المعطيات
| المتغير | القيمة |
|---------|--------|
| إجمالي الحمل | **15,000 req/s** |
| سعة الحاوية الواحدة | **500 req/s** |
| هامش الأمان | **30%** |
### المنطق
الهامش 30% يعني **عدم تشغيل الحاويات فوق 70%** من سعتها القصوى، لترك مساحة للذروات والتقلبات:
```
السعة الفعلية القابلة للتخطيط = 500 × (1 0.30) = 500 × 0.70 = 350 req/s لكل حاوية
```
### الحساب
```
عدد الحاويات = ⌈ 15,000 ÷ 350 ⌉
= ⌈ 42.857 ⌉
= 43 حاوية
```
### التحقق
```
43 × 350 = 15,050 req/s ✅ (يغطي 15,000 req/s مع هامش 30%)
```
### ملخص
| البند | القيمة |
|-------|--------|
| الحد الأدنى نظرياً (بدون هامش) | 30 حاوية |
| **العدد الموصى به (مع هامش 30%)** | **43 حاوية** |
| السعة الإجمالية الفعلية | 15,050 req/s |
| نسبة الاستخدام عند 15K req/s | ≈ 99.7% من السعة المُخطَّطة |
> **ملاحظة:** إذا قصدت إضافة 30% فوق العدد الأساسي: `30 × 1.30 = 39 حاوية`.
> في سياق SRE، هامش السعة (capacity headroom) هو التفسير الأدق — أي 43 حاوية.
---
## 3. استراتيجية Cold Start للحاويات الجديدة
عند التوسع الأفقي (scale-out)، الحاوية الجديدة تحتاج وقتاً قبل أن تستقبل حركة. الاستراتيجية المقترحة:
### أ) Warm Pool (مجموعة تسخين)
- الإبقاء على **35 حاويات جاهزة** في حالة `standby` (مُشغَّلة ومُسخَّنة، لكن خارج rotation الـ Load Balancer).
- عند ارتفاع الحمل، تُضاف فوراً إلى الـ LB **بدون** انتظار pull للصورة أو boot.
### ب) Pre-pull الصور
- تخزين صورة Docker في **Ghaymah Internal Registry** داخل نفس المنطقة.
- تقليل زمن `image pull` من دقائق إلى ثوانٍ.
### ج) Readiness Probe تدريجي
```
Startup Probe → Readiness Probe → إضافة للـ LB
(30s) (HTTP /health)
```
- لا تُوجَّه الطلبات للحاوية حتى تمر `/health` بنجاح.
- يمنع إرسال حركة لحاوية لم تكتمل تهيئتها.
### د) Graceful Scale-In
- عند التقليص: إرسال `SIGTERM` → انتظار إنهاء الطلبات الجارية (drain) → إزالة من LB → إيقاف.
- يمنع قطع الطلبات أثناء التوسع العكسي.
### هـ) Predictive Scaling
- مراقبة CPU / RPS / latency.
- بدء التوسع **قبل** الوصول للحد (مثلاً عند 60% utilization) وليس عند 90%.
### ف) Application Warm-up
- عند الإقلاع: تحميل cache محلي، pre-connect لقاعدة البيانات، JIT warm-up.
- endpoint `/warmup` يُستدعى تلقائياً بعد readiness.
---
## 4. Ghaymah Block Storage للـ Stateful Workloads
### ما هو Block Storage؟
تخزين **كتلة (block-level)** يُ attach كقرص افتراضي (volume) لحاوية أو خدمة — مثل `/dev/vdb` — ويحتفظ بالبيانات **بعد** إعادة تشغيل أو استبدال الحاوية.
### متى نستخدمه؟
| Stateful | Stateless (لا يحتاج Block Storage) |
|----------|-------------------------------------|
| PostgreSQL / MySQL | API servers (43 حاوية أعلاه) |
| Redis persistence (AOF/RDB) | Static assets → Object Storage |
| File uploads / media processing | Session في Redis مشترك |
| Logs طويلة الأمد | |
### كيف يُستخدم على غيمة؟
1. **إنشاء Volume**
- حجم مناسب (مثلاً 100 GB SSD) في نفس منطقة التطبيق.
2. **Mount على الحاوية Stateful**
```text
/data/postgresql ← Ghaymah Block Volume (persistent)
```
3. **فصل Compute عن Storage**
- حاويات الـ API **stateless** — تتوسع أفقياً بحرية.
- قاعدة البيانات **stateful** — volume واحد (أو cluster مع replication).
4. **Snapshots & Backup**
- Ghaymah Backup: snapshots دورية للـ volume.
- استرداد Point-in-Time عند الفشل.
5. **High Availability**
- Primary DB على volume + Replica على volume منفصل.
- Failover تلقائي عند سقوط Primary.
### في مخططنا (architecture.png)
```
Load Balancer → 43 App Containers (stateless)
Stateful DB Pod ← Ghaymah Block Storage (persistent volume)
```
- الـ **43 حاوية** لا تخزّن state محلياً.
- كل البيانات الدائمة على **Block Storage** م attached لطبقة DB/Cache الم persistent.
### الفوائد
| الفائدة | الشرح |
|---------|--------|
| Persistence | البيانات تبقى بعد restart/redeploy |
| Performance | IOPS مضمون لقواعد البيانات |
| Scalability | فصل التوسع الأفقي (API) عن التخزين (DB) |
| DR | Snapshots + restore سريع |
---
## المراجع
- [Ghaymah Cloud](https://ghaymah.systems/)
- [Ghaymah CLI — `gy resource app launch`](https://cli.ghaymah.systems/)
- Health & Retry: HTTP/TCP probes — down-nodes تخرج فوراً من rotation