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>
18 KiB
RUNBOOK — استقرار پلتفرم CloudHost از صفر
این سند کارهایی را که روی سرور/کلاستر باید انجام دهید مرحلهبهمرحله توضیح میدهد — از bootstrap زیرساخت تا اولین deploy موفق پس از hardening.
برای چه کسی است: هر کسی که میخواهد CloudHost را روی یک کلاستر Kubernetes تازه (یا کلاستر دیگری غیر از abrban) بالا بیاورد.
چه چیزی اینجا نیست: جزئیات معماری اپ →
RUNBOOK.fa.md؛ جزئیات pipeline CI →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 |
# نمونه — قبل از اجرای دستورات 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 |
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 هم هست؛ خلاصه:
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
بررسی فاز ۱:
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)
# کلون ریپوی 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 }] |
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) |
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
بررسی:
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 بسازید.
فاز ۴ — Logging stack + Secret Elasticsearch
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.
cd cloud-host
git push origin main # یا push به Gitea remote
# پیگیری: Gitea Actions UI یا kubectl -n $BUILD_NS get jobs -w
روش ۲ — دستی (bootstrap / بدون CI):
# 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:
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 بگیرید.
# 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 |
فاز ۷ — چکلیست تأیید سلامت
# 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 عیبیابی |
| 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:
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 |
Template values — کپی و rename برای محیط جدید |
gitops/sealed-secrets/abrban-platform-secrets.example.yaml |
دستور seal Secret پلتفرم |
gitops/sealed-secrets/elasticsearch-credentials.example.yaml |
دستور seal Secret logging |
backend/helm/cloudhost-platform/values-production.example.yaml |
Template برای مسیر B |
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