6d9cd89cc5
Document server-side rollout (values, Sealed Secrets, logging, greenfield reset) with environment variables so any cluster can follow the same steps. Co-authored-by: Cursor <cursoragent@cursor.com>
233 lines
15 KiB
Markdown
233 lines
15 KiB
Markdown
# CloudHost — راهنمای معماری و اجرای وبسایت (Runbook)
|
||
|
||
این سند دو بخش دارد:
|
||
1. **اپلیکیشن چطور کار میکند** — معماری و جریانها.
|
||
2. **اجرای وبسایت، مرحلهبهمرحله** — هم برای توسعهی محلی، هم برای استقرار (deploy) روی کلاستر k3s سرور (abrban) بههمراه راهحلهای مخصوص شبکهی ایران.
|
||
|
||
> اصطلاحها: «پنل» = اپ احرازشده (`panel.abrban.com`)، «لندینگ» = صفحهی معرفی (`abrban.com`)، «اپ کاربر» = اپلیکیشنی که مشتری روی CloudHost دیپلوی میکند.
|
||
|
||
> **بهروزرسانی ۲۰۲۶:** pipeline بیلد فعلی از **Kaniko** + Dockerfileهای نگهداریشده توسط پلتفرم استفاده میکند (نه Nixpacks/MinIO). آرشیو سورس روی دیسک/PVC آپلود میشود. manifest بوتاسترپ namespace بیلد: [`backend/k8s/builds/cloudhost-builds-bootstrap.yaml`](backend/k8s/builds/cloudhost-builds-bootstrap.yaml).
|
||
|
||
> **استقرار از صفر روی سرور:** [`RUNBOOK-DEPLOY.fa.md`](RUNBOOK-DEPLOY.fa.md) — مراحل values، Secretها، deploy، greenfield reset.
|
||
|
||
---
|
||
|
||
### ۱.۱ CloudHost چیست
|
||
یک **PaaS خودسرویس** برای بازار ایران: کاربر کد/ریپوی خودش را میدهد و CloudHost آن را build و روی Kubernetes اجرا میکند، با مدیریت دامنه، دیتابیس، لاگ، فاکتور و کیف پول.
|
||
|
||
### ۱.۲ اجزای اصلی
|
||
|
||
| جزء | تکنولوژی | نقش |
|
||
|---|---|---|
|
||
| **Frontend** | Next.js (App Router, SSR) | لندینگ + پنل کاربری/ادمین |
|
||
| **Backend** | NestJS (REST `/api/v1`) | منطق کسبوکار، ساخت اپ، احراز هویت |
|
||
| **Postgres** | postgres:16 | دیتابیس اصلی (کاربر، اپ، فاکتور، …) |
|
||
| **Redis** | redis:7 | کش، صف Bull (مهاجرت اپ، دسترسی موقت)، پیشرفت بیلد |
|
||
| **Registry داخلی** | Harbor + registry:2 (legacy) | ایمیجهای build و platform؛ جزئیات: [`RUNBOOK-HARBOR.fa.md`](RUNBOOK-HARBOR.fa.md) |
|
||
| **Storage (Ceph)** | Rook-Ceph | PVC (`rook-ceph-block`) + bucket zip (`rook-ceph-bucket`)؛ جزئیات: [`RUNBOOK-CEPH.fa.md`](RUNBOOK-CEPH.fa.md) |
|
||
| **Build pipeline** | Kaniko | تبدیل سورس به ایمیج Docker داخل کلاستر (بدون Docker daemon) |
|
||
| **Kubernetes** | k3s (تکنود) | اجرای همهی موارد بالا + اپهای کاربر |
|
||
|
||
### ۱.۳ دامنهها (همه روی `78.157.39.52`، HTTPS با wildcard cert)
|
||
- `abrban.com` → لندینگ
|
||
- `panel.abrban.com` → پنل احرازشده
|
||
- `api.abrban.com` → بکاند
|
||
- `registry.abrban.com` → Harbor (UI + proxy-cache) + registry قدیمی برای ایمیجهای platform — [`RUNBOOK-HARBOR.fa.md`](RUNBOOK-HARBOR.fa.md)
|
||
- `apps.abrban.com` → دامنهی پیشفرض اپهای کاربر
|
||
|
||
### ۱.۴ جریان احراز هویت
|
||
- ورود مبتنی بر **موبایل + OTP** (پیامک از طریق MizbanSMS) یا رمز عبور.
|
||
- توکن **JWT** (access ~15m، refresh ~7d).
|
||
- `JwtStrategy` در هر درخواست نقش و فعالبودن کاربر را **از دیتابیس** میخواند (نه از توکن) تا تغییر نقش/غیرفعالسازی بلافاصله اثر کند.
|
||
|
||
### ۱.۵ جریان دیپلویِ «اپ کاربر» (مهمترین بخش)
|
||
وقتی کاربر یک اپ را build/redeploy میکند:
|
||
|
||
```
|
||
کاربر (پنل)
|
||
│ آپلود zip ──────────────► MinIO (bucket: app-sources) ┐
|
||
│ یا git URL │ منبع سورس
|
||
▼ │
|
||
Backend: یک job در صف Bull («app-deploy») میگذارد │
|
||
▼ │
|
||
ساخت یک Kubernetes Job در namespace «cloudhost-builds»: │
|
||
1) init: fetch-source (دانلود از MinIO) یا git-clone ◄───────┘
|
||
2) init: nixpacks-prepare
|
||
• اگر سورس Dockerfile دارد → همان (BYO)
|
||
• وگرنه → با Nixpacks یک Dockerfile میسازد
|
||
3) container: Kaniko → build ایمیج → push به registry داخلی
|
||
▼
|
||
(غیرمسدودکننده) اسکن Trivy → خلاصهی آسیبپذیری
|
||
▼
|
||
Backend با Helm، اپ را روی کلاستر بالا میآورد (Deployment + Service + Ingress)
|
||
▼
|
||
GC رجیستری: روزانه فقط N نسخهی آخر هر اپ را نگه میدارد
|
||
```
|
||
|
||
- پیشرفت بیلد در Redis نگهداری میشود؛ فرانت هر ۱.۵ ثانیه `build-progress`/`build-logs` را poll میکند.
|
||
- توکن git در یک Secret موقتِ هر بیلد مینشیند و در `finally` پاک میشود (در manifest درج نمیشود).
|
||
|
||
---
|
||
|
||
## بخش ۲ — اجرای محلی (Local Dev)
|
||
|
||
پیشنیاز: Node 20، Docker، Docker Compose.
|
||
|
||
```bash
|
||
# ۱) دیتابیس و Redis
|
||
docker compose up -d # از docker-compose.yml ریشهی پروژه
|
||
|
||
# ۲) بکاند
|
||
cd backend
|
||
cp .env.example .env # مقادیر را پر کن (DB، JWT، SMS، …)
|
||
npm install
|
||
npm run start:dev # روی http://localhost:4000 (پیشوند /api/v1)
|
||
|
||
# ۳) فرانت
|
||
cd ../frontend
|
||
npm install
|
||
npm run dev # روی http://localhost:3000
|
||
```
|
||
|
||
- در حالت dev، TypeORM `synchronize` روشن است و جدولها خودکار ساخته میشوند.
|
||
- `NEXT_PUBLIC_API_URL` فرانت باید به آدرس بکاند اشاره کند.
|
||
|
||
---
|
||
|
||
## بخش ۳ — استقرار روی کلاستر (abrban / k3s)
|
||
|
||
> این بخش فرض میکند کلاستر k3s و wildcard cert از قبل آمادهاند. context کوبه: `default`.
|
||
|
||
### ۳.۰ ثابتهای محیط
|
||
| | مقدار |
|
||
|---|---|
|
||
| namespace اپ | `cloudhost` |
|
||
| namespace بیلد | `cloudhost-builds` |
|
||
| Helm release | `cloudhost` |
|
||
| چارت | `backend/helm/cloudhost-platform` |
|
||
| values | `/tmp/abrban/values-abrban.yaml` |
|
||
| رجیستری (push داخلی) | `registry.cloudhost.svc.cluster.local:5000` (HTTP, insecure) |
|
||
| رجیستری (pull توسط kubelet) | `registry.abrban.com` (HTTPS, wildcard cert) — همان storage |
|
||
| پروکسی build | `http://builder:<pw>@45.129.38.203:9911` |
|
||
|
||
### ۳.۱ نکات شبکهی ایران (چرا کارها اینشکلیاند)
|
||
- کلاستر به **Let's Encrypt، github، docker.io، ghcr.io، gcr.io** مستقیم نمیرسد (یا خیلی کند).
|
||
- **gTLS/cert**: دستی، secret `abrban-wildcard-tls` (نه cert-manager).
|
||
- **npm**: از `registry.npmmirror.com` مستقیم (نه پروکسی).
|
||
- **ایمیجهای پایه**: اول داخل رجیستری داخلی **mirror** میشوند، بعد استفاده.
|
||
- **دانلودهای build (apk/nix/pip/…)**: از طریق پروکسی بالا.
|
||
|
||
### ۳.۲ گامهای استقرار
|
||
|
||
**گام ۱ — بررسی دسترسی کلاستر**
|
||
```bash
|
||
kubectl config current-context # باید default باشد
|
||
kubectl get nodes
|
||
```
|
||
|
||
**گام ۲ — mirror کردن ایمیجهای پایه به رجیستری داخلی**
|
||
ایمیجهایی که کلاستر مستقیم نمیتواند pull کند را با یک Job داخل کلاستر کپی کن.
|
||
- برای ایمیجهای **کوچک**: `crane copy <src> registry.cloudhost.svc.cluster.local:5000/<dst> --insecure`
|
||
- برای ایمیجهای **بزرگ** (مثل nixpacks): از **skopeo** استفاده کن — چون بلاب را با PUT یکجا آپلود میکند و گیر `PROTOCOL_ERROR` آپلود تکهای crane را ندارد:
|
||
```
|
||
skopeo copy --override-os linux --override-arch amd64 --dest-tls-verify=false \
|
||
docker://<src> docker://registry.cloudhost.svc.cluster.local:5000/<dst>
|
||
```
|
||
(با `REGISTRY_AUTH_FILE` از secret `kaniko-docker-config` و env پروکسی.)
|
||
|
||
ایمیجهای لازم: `minio/minio`, `railwayapp/nixpacks`, `library/alpine:3.19`, `library/postgres`, `library/redis`, و base ای که Nixpacks تولید میکند.
|
||
|
||
**گام ۳ — MinIO (ذخیرهی سورس اپها)**
|
||
بهطور خودکار فقط هنگام **ثبت کلاستر جدید** ساخته میشود؛ روی کلاستر موجود دستی بساز: secret `minio-credentials` (`accesskey`/`secretkey`) + PVC ۲۰Gi + Deployment (`registry.abrban.com/minio/minio:latest`) + Service، همه در `cloudhost-builds`. کردنشال پیشفرض با config بکاند میخواند.
|
||
|
||
**گام ۴ — pull-secret برای namespace بیلد**
|
||
```bash
|
||
# کپی pull-secret رجیستری به ns بیلد
|
||
kubectl get secret registry-pull-secret -n cloudhost -o json \
|
||
| jq '.metadata.namespace="cloudhost-builds" | del(.metadata.uid,.metadata.resourceVersion,.metadata.creationTimestamp)' \
|
||
| kubectl apply -f -
|
||
# وصل به هر دو ServiceAccount که pod بیلد ممکن است از آنها استفاده کند
|
||
kubectl patch sa default -n cloudhost-builds -p '{"imagePullSecrets":[{"name":"registry-pull-secret"}]}'
|
||
kubectl patch sa kaniko-builder -n cloudhost-builds -p '{"imagePullSecrets":[{"name":"registry-pull-secret"}]}'
|
||
```
|
||
> ⚠️ `kaniko-builder` حتماً لازم است: pod بیلد با همین SA اجرا میشود و init container نیکسپکس ایمیجش را از `registry.abrban.com` میکشد.
|
||
|
||
**گام ۵ — build ایمیجهای frontend/backend (Kaniko)**
|
||
سورس را در PVC بیلد (`build-src`) از طریق pod `srcsync` قرار بده، سپس Jobهای Kaniko را اجرا کن.
|
||
```bash
|
||
# سینک سورس (از working tree؛ tsbuildinfo و dist را حذف کن!)
|
||
cd <repo>
|
||
tar czf - --exclude=node_modules --exclude=.next --exclude=.git frontend \
|
||
| kubectl exec -i srcsync -n cloudhost -- sh -c 'rm -rf /workspace/frontend && tar xzf - -C /workspace'
|
||
rm -f backend/tsconfig.tsbuildinfo # ← مهم
|
||
tar czf - --exclude=node_modules --exclude=dist --exclude=.git backend \
|
||
| kubectl exec -i srcsync -n cloudhost -- sh -c 'rm -rf /workspace/backend && tar xzf - -C /workspace'
|
||
|
||
# build (manifestهای kaniko-*.yaml: registry-mirror + proxy + npmmirror)
|
||
kubectl apply -f /tmp/abrban/kaniko-frontend-<tag>.yaml
|
||
kubectl apply -f /tmp/abrban/kaniko-backend-<tag>.yaml
|
||
```
|
||
|
||
**گام ۶ — استقرار با Helm**
|
||
```bash
|
||
helm upgrade cloudhost backend/helm/cloudhost-platform \
|
||
-n cloudhost -f /tmp/abrban/values-abrban.yaml \
|
||
--set images.backend.tag=<tag> \
|
||
--set images.frontend.tag=<tag> \
|
||
--set migrations.enabled=false # ← مهاجرتها روی DB زنده تداخل دارند
|
||
```
|
||
> بدون `--wait` اجرا کن (وگرنه بهخاطر کندیِ pull، status اشتباهاً `failed` میشود درحالیکه rollout موفق است).
|
||
|
||
**گام ۷ — bootstrap اسکیمای دیتابیس (فقط روی DB تازه)**
|
||
در پروداکشن `synchronize` خاموش است. روی DB کاملاً تازه: موقتاً `NODE_ENV=development` کن تا synchronize جدولها را بسازد و pricing خودش seed شود، بعد به `production` برگردان. روی DB موجود، فقط مهاجرتهای idempotent جدید را با یک Job جدا اعمال کن (نه helm hook).
|
||
|
||
**گام ۸ — تأیید**
|
||
```bash
|
||
kubectl get deploy -n cloudhost # backend/frontend 1/1
|
||
helm status cloudhost -n cloudhost # STATUS: deployed
|
||
curl -s -o /dev/null -w '%{http_code}\n' https://panel.abrban.com # 307
|
||
curl -s -X POST https://api.abrban.com/api/v1/auth/otp/request \
|
||
-H 'Content-Type: application/json' -d '{"phone":"09xxxxxxxxx"}' # {"sent":true}
|
||
```
|
||
|
||
---
|
||
|
||
## بخش ۴ — envهای کلیدی build pipeline (روی بکاند)
|
||
اینها در `backend.env` فایل values ست میشوند:
|
||
|
||
| env | مقدار/توضیح |
|
||
|---|---|
|
||
| `MINIO_SECRET_KEY` | کلید MinIO (هماهنگ با secret) |
|
||
| `NIXPACKS_IMAGE` | `registry.abrban.com/railwayapp/nixpacks:latest` (mirror) |
|
||
| `NIXPACKS_BUILD_ENV` | `NPM_CONFIG_REGISTRY=https://registry.npmmirror.com` |
|
||
| `BUILD_HTTP_PROXY` | پروکسی build (تزریق به Kaniko + init containerها) |
|
||
| `BUILD_REGISTRY_MIRROR` | `registry.cloudhost.svc.cluster.local:5000` (pull پایه از mirror) |
|
||
| `BUILD_SCAN_ENABLED` | `false` تا وقتی ایمیج Trivy mirror شود |
|
||
| `SMS_PROVIDER` + `MIZBANSMS_*` | بدون اینها ارسال OTP خطای 503 میدهد |
|
||
|
||
---
|
||
|
||
## بخش ۵ — عیبیابی رایج
|
||
|
||
| نشانه | علت | راهحل |
|
||
|---|---|---|
|
||
| backend کرش: `Cannot find module '/app/dist/main.js'` | `tsconfig.tsbuildinfo` کهنه در سورس → tsc فایلها را دوباره emit نمیکند | قبل از build، `tsconfig.tsbuildinfo` را حذف کن |
|
||
| init container بیلد: `no basic auth credentials` | SA `kaniko-builder` بدون pull-secret | گام ۴ را اجرا کن |
|
||
| pull ایمیج: `not found` با اینکه push شده | crane در آپلود تکهایِ بلاب بزرگ شکست خورده (tag ناقص) | با **skopeo** دوباره mirror کن |
|
||
| pull از docker.io: `TLS handshake timeout` | docker.io از کلاستر بسته است | ایمیج را mirror کن |
|
||
| `nixpacks: not found` در init | باینری نیکسپکس روی PATH پیشفرض نیست | command را با مسیر/ENTRYPOINT درست صدا بزن |
|
||
| helm: `no template "...namespace"` یا `nil pointer redis.enabled` | فایلهای چارت (`_helpers.tpl`/`values.yaml`) از `/tmp` پاک شدهاند | از سورس اصلی بازیابی + ادیتهای deploy را دوباره اعمال کن |
|
||
| OTP خطای 503 | env پیامک ست نیست | `SMS_PROVIDER`/`MIZBANSMS_*` را ست کن |
|
||
|
||
> ⚠️ پوشهی `/tmp` در macOS فایلهای قدیمیتر از ~۳ روز را پاک میکند؛ درخت کاری deploy در `/tmp` ممکن است فایل از دست بدهد — قبل از build بررسی کن.
|
||
|
||
---
|
||
|
||
## بخش ۶ — بهروزرسانی نسخه (خلاصه)
|
||
1. تغییرات کد را در سورس اعمال کن (`tsc --noEmit` بگیر).
|
||
2. tag جدید انتخاب کن.
|
||
3. سورس را در `srcsync` سینک کن (با حذف `tsbuildinfo`).
|
||
4. Job Kaniko را با tag جدید بساز.
|
||
5. `helm upgrade ... --set images.*.tag=<tag> --set migrations.enabled=false` (بدون `--wait`).
|
||
6. تأیید کن: podها 1/1، helm `deployed`، endpointها سالم.
|