Files
cloud-host/RUNBOOK-DEPLOY.fa.md
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

427 lines
18 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.
# RUNBOOK — استقرار پلتفرم CloudHost از صفر
این سند **کارهایی را که روی سرور/کلاستر باید انجام دهید** مرحله‌به‌مرحله توضیح می‌دهد — از bootstrap زیرساخت تا اولین deploy موفق پس از hardening.
> **برای چه کسی است:** هر کسی که می‌خواهد CloudHost را روی یک کلاستر Kubernetes تازه (یا کلاستر دیگری غیر از abrban) بالا بیاورد.
>
> **چه چیزی اینجا نیست:** جزئیات معماری اپ → [`RUNBOOK.fa.md`](RUNBOOK.fa.md)؛ جزئیات pipeline CI → [`RUNBOOK-CICD.fa.md`](RUNBOOK-CICD.fa.md).
---
## قبل از شروع — جدول متغیرها
همهٔ دستورات زیر از این متغیرها استفاده می‌کنند. **یک‌بار** آن‌ها را برای محیط خودتان پر کنید:
| متغیر | توضیح | مثال abrban | مثال محیط جدید |
|-------|--------|-------------|----------------|
| `PLATFORM_NS` | namespace پلتفرم | `cloudhost` | `cloudhost` |
| `BUILD_NS` | namespace بیلد Kaniko | `cloudhost-builds` | `cloudhost-builds` |
| `LOGGING_NS` | namespace Elasticsearch | `logging` | `logging` |
| `REGISTRY_HOST` | آدرس pull ایمیج (Ingress/registry عمومی) | `registry.abrban.com` | `registry.example.com` |
| `REGISTRY_PROJECT` | پروژه Harbor برای ایمیج‌های platform | `abrban` | `cloudhost` |
| `REGISTRY_PUSH` | endpoint داخلی push (بدون TLS) | `harbor-registry.cloudhost.svc.cluster.local:5000` | `registry.registry.svc:5000` |
| `GIT_HOST` | URL گیت (Gitea/GitHub) | `git.abrban.com` | `git.example.com` |
| `APP_REPO` | ریپوی کد + چارت | `abrban/cloud-host` | `org/cloud-host` |
| `GITOPS_REPO` | ریپوی state (values + sealed secrets) | `abrban/cloud-host-gitops` | `org/cloud-host-gitops` |
| `VALUES_FILE` | فایل values در gitops | `platform/values-abrban.yaml` | `platform/values-production.yaml` |
| `PLATFORM_SECRET` | Secret پلتفرم (JWT, DB, Redis, …) | `abrban-platform-secrets` | `cloudhost-platform-secrets` |
| `ARGO_APP` | نام Application در Argo CD | `abrban-platform` | `cloudhost-platform` |
| `DOMAIN_LANDING` | لندینگ | `abrban.com` | `example.com` |
| `DOMAIN_PANEL` | پنل | `panel.abrban.com` | `panel.example.com` |
| `DOMAIN_API` | API | `api.abrban.com` | `api.example.com` |
| `DOMAIN_APPS` | دامنهٔ اپ‌های کاربر | `apps.abrban.com` | `apps.example.com` |
| `STORAGE_CLASS` | StorageClass PVCها | `local-path` | `standard` |
| `INGRESS_CLASS` | Ingress controller | `traefik` | `nginx` |
```bash
# نمونه — قبل از اجرای دستورات export کنید:
export PLATFORM_NS=cloudhost
export BUILD_NS=cloudhost-builds
export LOGGING_NS=logging
export REGISTRY_HOST=registry.example.com
export REGISTRY_PROJECT=cloudhost
export REGISTRY_PUSH=harbor-registry.cloudhost.svc.cluster.local:5000
export GIT_HOST=git.example.com
export APP_REPO=org/cloud-host
export GITOPS_REPO=org/cloud-host-gitops
export VALUES_FILE=platform/values-production.yaml
export PLATFORM_SECRET=cloudhost-platform-secrets
export ARGO_APP=cloudhost-platform
export DOMAIN_LANDING=example.com
export DOMAIN_PANEL=panel.example.com
export DOMAIN_API=api.example.com
export DOMAIN_APPS=apps.example.com
export STORAGE_CLASS=standard
export INGRESS_CLASS=nginx
```
---
## دو مسیر استقرار
| | **مسیر A — GitOps (توصیه Production)** | **مسیر B — Helm مستقیم** |
|---|--------------------------------------|---------------------------|
| CI/CD | Gitea Actions → Kaniko → Argo CD | build/push دستی + `helm upgrade` |
| Values | ریپوی جدا `GITOPS_REPO` | فایل محلی `my-values.yaml` |
| Secretها | Sealed Secrets در gitops | inline در values یا Secret دستی |
| مستند | **همین سند +** [`RUNBOOK-CICD.fa.md`](RUNBOOK-CICD.fa.md) | [`README.md`](README.md) بخش Deploy |
بقیهٔ این سند **مسیر A** را پوشش می‌دهد. برای مسیر B به انتهای سند بروید.
---
## مسیر A — GitOps: فاز ۰ تا ۷
### فاز ۰ — پیش‌نیازهای سخت‌افزاری و شبکه
- [ ] کلاستر Kubernetes (k3s یا دیگر) با kubectl از ماشین admin
- [ ] DNS: رکوردهای A/CNAME برای `$DOMAIN_LANDING`, `$DOMAIN_PANEL`, `$DOMAIN_API`, `$REGISTRY_HOST`, `$GIT_HOST`, Argo CD
- [ ] گواهی TLS (wildcard یا cert-manager + `clusterIssuer`)
- [ ] دسترسی `kubectl` به کلاستر
- [ ] `helm`, `kubeseal` (برای Sealed Secrets) روی ماشین admin
- [ ] دو ریپوی Git: `$APP_REPO` (کد) و `$GITOPS_REPO` (خالی یا با skeleton)
---
### فاز ۱ — Bootstrap زیرساخت (یک‌بار per cluster)
این مراحل در [`gitops/README.md`](gitops/README.md) هم هست؛ خلاصه:
```bash
cd cloud-host # ریپوی اپلیکیشن
# 1) mirror رجیستری k3s → Harbor (یا registry خودتان)
./scripts/apply-k3s-registries.sh # در صورت k3s؛ برای کلاستر دیگر mirror معادل تنظیم کنید
# 2) Argo CD
helm upgrade --install argocd argo/argo-cd -n argocd --create-namespace \
-f gitops/argocd/values-bootstrap.yaml --timeout 15m --wait
# 3) Gitea (یا GitHub/GitLab — workflow را متناسب تنظیم کنید)
helm upgrade --install gitea gitea-charts/gitea -n gitea --create-namespace \
-f gitops/gitea/values.yaml --timeout 15m --wait
# 4) Secretهای TLS + registry-pull + egress در nsهای لازم
# (wildcard TLS و registry-pull-secret را یک‌بار در $PLATFORM_NS بسازید، سپس کپی)
for ns in argocd gitea $BUILD_NS; do
kubectl -n $PLATFORM_NS get secret <wildcard-tls-secret> -o yaml \
| sed "s/namespace: ${PLATFORM_NS}/namespace: ${ns}/" | kubectl apply -f -
kubectl -n $PLATFORM_NS get secret registry-pull-secret -o yaml \
| sed "s/namespace: ${PLATFORM_NS}/namespace: ${ns}/" | kubectl apply -f -
done
# 5) Seed ایمیج‌های CI (act-runner, alpine-git, node, kaniko) — فایل را برای REGISTRY_* خودتان ویرایش کنید
kubectl apply -f gitops/jobs/seed-ci-images.yaml
kubectl -n $PLATFORM_NS wait --for=condition=complete job/seed-ci-images --timeout=15m
# 6) Sealed Secrets controller
helm repo add sealed-secrets https://bitnami.github.io/sealed-secrets
helm upgrade --install sealed-secrets sealed-secrets/sealed-secrets \
-n kube-system -f gitops/sealed-secrets/values.yaml --timeout 10m --wait
# 7) Gitea Actions runner + Secret CI_TOKEN در ریپوی app
kubectl apply -f gitops/gitea/act-runner.yaml
# در Gitea: Settings → Actions → Secrets → CI_TOKEN = PAT کاربر ci
# 8) Argo CD Application (chart از app repo، values از gitops repo)
# قبل از apply: repoURLها در gitops/argocd/application-platform.yaml را با GIT_HOST/APP_REPO/GITOPS_REPO هم‌خوان کنید
kubectl apply -f gitops/argocd/application-platform.yaml
```
**بررسی فاز ۱:**
```bash
kubectl get nodes
kubectl -n argocd get pods
kubectl -n gitea get pods
kubectl -n kube-system get pods -l app.kubernetes.io/name=sealed-secrets
```
---
### فاز ۲ — آماده‌سازی ریپوی GitOps (values)
```bash
# کلون ریپوی gitops (کنار ریپوی app یا هر مسیر دلخواه)
git clone "https://${GIT_HOST}/${GITOPS_REPO}.git" cloud-host-gitops
cd cloud-host-gitops
# کپی template values از ریپوی app
cp ../cloud-host/gitops/platform/values-abrban.example.yaml "${VALUES_FILE}"
```
**فایل values را برای محیط خودتان ویرایش کنید** — حداقل این فیلدها:
| بخش | چه چیزی عوض شود |
|-----|------------------|
| `images.postgres/redis/busybox` | مسیر mirror در `$REGISTRY_HOST` (مثلاً `proxy-dockerhub/library/postgres:16-alpine`) |
| `images.backend/frontend.repository` | `$REGISTRY_HOST/$REGISTRY_PROJECT/cloudhost-backend` |
| `secrets.existingSecret` | `$PLATFORM_SECRET` |
| `ingress.*.host` | `$DOMAIN_LANDING`, `$DOMAIN_PANEL`, `$DOMAIN_API` |
| `ingress.className` | `$INGRESS_CLASS` |
| `global.storageClass` | `$STORAGE_CLASS` |
| `backend.env.PLATFORM_DOMAIN` | `$DOMAIN_APPS` |
| `backend.env.FRONTEND_URL` | `https://${DOMAIN_PANEL},https://${DOMAIN_LANDING}` |
| `backend.env.REGISTRY_URL` | push داخلی: `$REGISTRY_PUSH/$REGISTRY_PROJECT` |
| `backend.env.REGISTRY_PULL_URL` | `$REGISTRY_HOST/$REGISTRY_PROJECT` |
| `backend.env.BASE_IMAGE_REGISTRY` | prefix mirror برای Dockerfileهای کاربر |
| `backend.env.ELASTIC_*` | بعد از فاز ۴ پر می‌شود |
| `postgres/redis.imagePullSecrets` | `[{ name: registry-pull-secret }]` |
```bash
git add "${VALUES_FILE}"
git commit -m "chore: initial platform values for $(hostname -s 2>/dev/null || echo production)"
git push origin main
```
> **نکته:** CI فقط `images.backend.tag` و `images.frontend.tag` را عوض می‌کند — بقیهٔ فایل دست شماست.
---
### فاز ۳ — Secretهای پلتفرم (Sealed Secrets)
Secret پلتفرم **نباید** در values به‌صورت plaintext commit شود. از SealedSecret استفاده کنید.
**کلیدهای الزامی** در `$PLATFORM_SECRET`:
| کلید | کاربرد |
|------|--------|
| `postgres-password` | Postgres پلتفرم + migration Job |
| `jwt-secret` | JWT access (حداقل ۳۲ کاراکتر تصادفی) |
| `jwt-refresh-secret` | JWT refresh |
| `cluster-kubeconfig-key` | رمزگذاری kubeconfig کلاسترها (۶۴ hex یا passphrase قوی) |
| `redis-password` | Redis پلتفرم + backend (Bull queues) |
```bash
cd cloud-host-gitops
# تولید رمزهای تصادفی (یا خودتان مقدار قوی بگذارید)
PG_PASS="$(openssl rand -base64 24)"
JWT="$(openssl rand -base64 32)"
JWT_REFRESH="$(openssl rand -base64 32)"
KUBE_KEY="$(openssl rand -hex 32)"
REDIS_PASS="$(openssl rand -base64 24)"
kubectl -n $PLATFORM_NS create secret generic "$PLATFORM_SECRET" \
--from-literal=postgres-password="$PG_PASS" \
--from-literal=jwt-secret="$JWT" \
--from-literal=jwt-refresh-secret="$JWT_REFRESH" \
--from-literal=cluster-kubeconfig-key="$KUBE_KEY" \
--from-literal=redis-password="$REDIS_PASS" \
--dry-run=client -o json \
| kubeseal \
--controller-name=sealed-secrets-controller \
--controller-namespace=kube-system \
--format yaml \
> "sealed-secrets/${PLATFORM_SECRET}.yaml"
kubectl apply -f "sealed-secrets/${PLATFORM_SECRET}.yaml"
git add "sealed-secrets/${PLATFORM_SECRET}.yaml"
git commit -m "chore: seal platform secrets"
git push origin main
```
**بررسی:**
```bash
kubectl -n $PLATFORM_NS get secret "$PLATFORM_SECRET"
kubectl get sealedsecrets -A | grep "$PLATFORM_SECRET"
```
SealedSecretهای CI دیگر (kaniko، runner، repo creds) را طبق [`RUNBOOK-CICD.fa.md`](RUNBOOK-CICD.fa.md) بسازید.
---
### فاز ۴ — Logging stack + Secret Elasticsearch
```bash
cd cloud-host
# 1) namespace logging (اگر در manifest نیست)
kubectl create namespace $LOGGING_NS --dry-run=client -o yaml | kubectl apply -f -
# 2) Secret elasticsearch — خارج از git (plaintext commit ممنوع)
ELASTIC_PASS="$(openssl rand -base64 24)"
FLUENT_PASS="$(openssl rand -base64 24)"
kubectl -n $LOGGING_NS create secret generic elasticsearch-credentials \
--from-literal=ELASTIC_PASSWORD="$ELASTIC_PASS" \
--from-literal=FLUENTBIT_PASSWORD="$FLUENT_PASS"
# یا seal کنید:
kubectl -n $LOGGING_NS create secret generic elasticsearch-credentials \
--from-literal=ELASTIC_PASSWORD="$ELASTIC_PASS" \
--from-literal=FLUENTBIT_PASSWORD="$FLUENT_PASS" \
--dry-run=client -o json \
| kubeseal --controller-name=sealed-secrets-controller \
--controller-namespace=kube-system --format yaml \
> ../cloud-host-gitops/sealed-secrets/elasticsearch-credentials.yaml
# 3) deploy stack (بدون Secret inline — manifest فقط ConfigMap/Deployment دارد)
kubectl apply -f backend/k8s/logging/elasticsearch-stack.yaml
# 4) همان مقادیر را در values پلتفرم بگذارید (backend.env)
# ELASTIC_PASSWORD, FLUENTBIT_PASSWORD, KIBANA_SYSTEM_PASSWORD
# سپس commit/push در gitops repo
```
> backend در production بدون `ELASTIC_PASSWORD` معتبر **بالا نمی‌آید** (`validate-production-config`).
---
### فاز ۵ — اولین Deploy
**روش ۱ — CI (توصیه):** push به `main` در `$APP_REPO` → workflow تست + Kaniko + آپدیت tag در gitops → Argo sync.
```bash
cd cloud-host
git push origin main # یا push به Gitea remote
# پیگیری: Gitea Actions UI یا kubectl -n $BUILD_NS get jobs -w
```
**روش ۲ — دستی (bootstrap / بدون CI):**
```bash
# build ایمیج‌ها (روی ماشینی که به registry دسترسی دارد) یا trigger-platform-build.sh
TAG="$(date +%Y%m%d-%H%M)-manual"
VALUES="../cloud-host-gitops/${VALUES_FILE}"
./scripts/gitops-deploy.sh TAG="$TAG" VALUES="$VALUES"
```
**بررسی Argo:**
```bash
kubectl -n argocd get app "$ARGO_APP"
argocd app sync "$ARGO_APP" # در صورت sync خودکار غیرفعال
kubectl -n $PLATFORM_NS get pods
kubectl -n $PLATFORM_NS rollout status deploy/cloudhost-backend --timeout=300s
kubectl -n $PLATFORM_NS rollout status deploy/cloudhost-frontend --timeout=300s
```
---
### فاز ۶ — Greenfield / ارتقا از نسخهٔ قدیم
اگر کلاستر **قبلاً** با نسخهٔ قدیمی CloudHost بالا آمده (namespace کوتاه UUID، migration بدون `schema_migrations`**قبل از deploy جدید** دیتابیس را reset کنید.
> ⚠️ **فقط greenfield / بدون دادهٔ واقعی.** در production با داده، اول backup بگیرید.
```bash
# 1) backend را متوقف کنید
kubectl -n $PLATFORM_NS scale deploy/cloudhost-backend --replicas=0
# 2) schema را از نو بسازید
kubectl -n $PLATFORM_NS exec deploy/cloudhost-postgres -- \
psql -U cloudhost -c 'DROP SCHEMA public CASCADE; CREATE SCHEMA public;'
# 3) Argo sync — migration Job (pre-upgrade hook) base schema + migrations را اجرا می‌کند
argocd app sync "$ARGO_APP"
# 4) backend را بالا بیاورید
kubectl -n $PLATFORM_NS scale deploy/cloudhost-backend --replicas=1
```
**تغییرات breaking که reset می‌خواهند:**
| تغییر | اثر |
|-------|-----|
| namespace کاربر `user-<uuid-32>` به‌جای `user-<8char>` | namespaceهای قدیمی دیگر استفاده نمی‌شوند — اپ‌ها redeploy |
| `000_base_schema.sql` + `schema_migrations` | DB باید از نو migrate شود |
| `redis-password` جدید | Secret + restart Redis و backend |
---
### فاز ۷ — چک‌لیست تأیید سلامت
```bash
# Podها
kubectl -n $PLATFORM_NS get deploy,pods
kubectl -n $LOGGING_NS get pods
# API
curl -sf "https://${DOMAIN_API}/api/v1/health" && echo OK
curl -sf "https://${DOMAIN_API}/api/v1/ready" && echo OK
# Frontend
curl -sf -o /dev/null -w '%{http_code}\n' "https://${DOMAIN_LANDING}"
curl -sf -o /dev/null -w '%{http_code}\n' "https://${DOMAIN_PANEL}"
# Migration
kubectl -n $PLATFORM_NS logs job/$(kubectl -n $PLATFORM_NS get jobs -o name | grep migration | tail -1 | cut -d/ -f2) 2>/dev/null || true
# Redis auth
kubectl -n $PLATFORM_NS exec deploy/cloudhost-redis -- redis-cli ping
# Backup CronJob (اگر enabled)
kubectl -n $PLATFORM_NS get cronjobs
```
| علامت | اقدام |
|-------|-------|
| Backend CrashLoop — JWT/DB/Redis | Secret `$PLATFORM_SECRET` و keys — [`RUNBOOK-CICD.fa.md`](RUNBOOK-CICD.fa.md) عیب‌یابی |
| Backend CrashLoop — ELASTIC_PASSWORD | env در values + Secret logging |
| Migration fail | `kubectl logs` روی migration Job؛ `schema_migrations` و فایل‌های `backend/migrations/` |
| Argo OutOfSync | `argocd app diff $ARGO_APP` |
---
## مسیر B — Helm مستقیم (بدون GitOps)
برای lab، staging، یا کلاستری **بدون** Gitea/Argo:
```bash
cp backend/helm/cloudhost-platform/values-production.example.yaml my-values.yaml
# ویرایش: hosts, registry, secrets (jwtSecret, postgres.password, redis.password), ingress
docker build -t $REG/cloudhost-backend:1.0.0 ./backend
docker build -t $REG/cloudhost-frontend:1.0.0 \
--build-arg NEXT_PUBLIC_API_URL=https://${DOMAIN_API} ./frontend
docker push $REG/cloudhost-backend:1.0.0
docker push $REG/cloudhost-frontend:1.0.0
helm upgrade --install cloudhost ./backend/helm/cloudhost-platform \
-n $PLATFORM_NS --create-namespace \
-f my-values.yaml \
--set images.backend.repository=$REG/cloudhost-backend \
--set images.frontend.repository=$REG/cloudhost-frontend \
--set images.backend.tag=1.0.0 \
--set images.frontend.tag=1.0.0 \
--set global.storageClass=$STORAGE_CLASS
```
> در این مسیر `secrets.existingSecret` خالی بماند تا Helm Secret بسازد — **برای production با Argo CD توصیه نمی‌شود** (lookup در `helm template` خالی است).
---
## فایل‌های مرجع در ریپو
| فایل | نقش |
|------|-----|
| [`gitops/platform/values-abrban.example.yaml`](gitops/platform/values-abrban.example.yaml) | Template values — کپی و rename برای محیط جدید |
| [`gitops/sealed-secrets/abrban-platform-secrets.example.yaml`](gitops/sealed-secrets/abrban-platform-secrets.example.yaml) | دستور seal Secret پلتفرم |
| [`gitops/sealed-secrets/elasticsearch-credentials.example.yaml`](gitops/sealed-secrets/elasticsearch-credentials.example.yaml) | دستور seal Secret logging |
| [`backend/helm/cloudhost-platform/values-production.example.yaml`](backend/helm/cloudhost-platform/values-production.example.yaml) | Template برای مسیر B |
| [`scripts/gitops-deploy.sh`](scripts/gitops-deploy.sh) | deploy دستی با Helm + values از gitops |
---
## خلاصهٔ ترتیب (Quick reference)
```
فاز ۰ DNS + kubectl + helm + kubeseal + دو ریپو
فاز ۱ Argo + Gitea + registry + sealed-secrets + runner + Application
فاز ۲ کپی values template → ویرایش → push gitops
فاز ۳ seal platform secrets → push gitops
فاز ۴ elasticsearch stack + secret + env در values
فاز ۵ push main (CI) یا gitops-deploy.sh (دستی)
فاز ۶ (در صورت upgrade) reset DB
فاز ۷ health check
```