# 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 -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-` به‌جای `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 ```