Files
keyhan 6d9cd89cc5 docs: add portable from-zero deploy runbook and GitOps templates
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>
2026-07-03 12:17:55 +03:30

233 lines
15 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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‌ها سالم.