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>
23 KiB
RUNBOOK — خط CI/CD (Gitea Actions → Kaniko → Harbor → Argo CD)
این مستند جریان کامل Build و Deploy پلتفرم را توضیح میدهد: از Push شدن کد روی main تا استقرار خودکار روی Kubernetes.
استقرار از صفر روی سرور جدید:
RUNBOOK-DEPLOY.fa.md— شامل جدول متغیرها، seal کردن Secretها، logging stack، greenfield reset، و چکلیست سلامت.
معماری و جریان کلی
flowchart TD
Dev[Developer] -->|git push main| AppRepo["Gitea: abrban/cloud-host (کد + چارت)"]
AppRepo -->|trigger workflow| Runner["Act Runner (namespace: gitea)"]
Runner -->|"checkout با CI_TOKEN"| AppRepo
Runner -->|kubectl apply Job| Kaniko["Kaniko Job (namespace: cloudhost-builds)"]
Kaniko -->|"push با harbor_registry_user"| Harbor["Harbor (harbor-registry:5000)"]
Runner -->|"آپدیت image.tag + commit/push"| GitOpsRepo["Gitea: abrban/cloud-host-gitops (state)"]
GitOpsRepo -->|"poll (پیشفرض هر ۳ دقیقه)"| Argo["Argo CD (automated sync)"]
AppRepo -->|"Helm Chart (source دوم)"| Argo
Argo -->|"helm render + apply"| K8s["Kubernetes (namespace: cloudhost)"]
Harbor -->|"pull از طریق mirror در k3s"| K8s
Rollback["Rollback: git revert در cloud-host-gitops"] -.-> GitOpsRepo
مراحل به ترتیب:
- Developer روی شاخهٔ
mainدر ریپوی اپلیکیشن (git.abrban.com/abrban/cloud-host) push میکند. - Workflow در
.gitea/workflows/build-deploy.yamlروی Runner با لیبلabrban-builderاجرا میشود. - Runner کد را با توکن CI کلون میکند و تگ ایمیج (
YYYYMMDD-HHMM-<sha>) را میسازد. - Job تست بکاند در namespace
cloudhost-buildsاجرا میشود (npm ci+jest --ci) — در صورت fail، بیلد ایمیج شروع نمیشود. - برای هر ایمیج (backend و frontend) یک Kaniko Job در namespace
cloudhost-buildsساخته میشود که کد را کلون، ایمیج را build و به Harbor push میکند. - بعد از موفقیت هر دو Build، همان Runner ریپوی
cloud-host-gitopsرا کلون میکند، مقدارimages.backend.tagوimages.frontend.tagرا درplatform/values-abrban.yamlعوض و commit/push میکند (با retry وgit pull --rebaseدر صورت race). - Argo CD (Application به نام
abrban-platformبا sync خودکار) تغییر را تشخیص میدهد و نسخهٔ جدید را در namespacecloudhostمستقر میکند.
جلوگیری از حلقهٔ CI: کامیتِ Pipeline به ریپوی جدا (
cloud-host-gitops) میرود که هیچ Workflowای ندارد؛ بنابراین Build دوباره trigger نمیشود.
زمان تقریبی یک Pipeline کامل: ۱۵–۲۵ دقیقه (backend سنگینتر است؛ شامل دانلود npm، helm و kubectl داخل Dockerfile).
Bootstrap — پیشنیازهای یکبار (کلاستر تازه)
قبل از اولین push به main، این موارد باید در کلاستر آماده باشند:
| # | کار | دستور / فایل |
|---|---|---|
| 1 | Mirror k3s → Harbor | ./scripts/apply-k3s-registries.sh |
| 2 | Secretهای TLS و registry در nsهای gitea, cloudhost-builds, argocd |
gitops/README.md گام ۴ |
| 3 | پروکسی egress در gitea و cloudhost-builds |
همان گام ۴ — برای npm/helm/kubectl داخل build و دانلود kubectl توسط Runner |
| 4 | Seed ایمیجهای CI در Harbor abrban/ |
gitops/jobs/seed-ci-images.yaml |
| 5 | Sealed Secrets controller | helm upgrade --install sealed-secrets ... -f gitops/sealed-secrets/values.yaml |
| 6 | SealedSecretها از ریپوی gitops | kubectl apply -f روی cloud-host-gitops/sealed-secrets/ |
| 7 | Gitea Runner + Secret CI_TOKEN در ریپو |
gitops/gitea/act-runner.yaml |
| 8 | ریپوی cloud-host-gitops + Argo Application |
gitops/argocd/application-platform.yaml |
Seed ایمیجهای CI (الزامی)
kubelet و Kaniko نمیتوانند reliably از proxy-cache هاربر برای همهٔ ایمیجها استفاده کنند. این ایمیجها باید یکبار با skopeo در پروژهٔ abrban/ کپی شوند:
| ایمیج در Harbor | منبع upstream | مصرف |
|---|---|---|
abrban/act-runner:0.2.11 |
docker.io/gitea/act_runner | Gitea Actions runner |
abrban/alpine-git:2.43.0 |
docker.io/alpine/git | initContainer کلون در Kaniko Job |
abrban/node:24-alpine |
docker.io/library/node | BASE_IMAGE در Dockerfile (هر stage) |
abrban/kaniko-executor:v1.27.6-debug |
gcr.io/kaniko-project/executor | Kaniko Job |
# پیشنیاز: secret registry-egress-proxy و registry-pull-secret در ns cloudhost
kubectl apply -f gitops/jobs/seed-ci-images.yaml
kubectl -n cloudhost wait --for=condition=complete job/seed-ci-images --timeout=15m
kubectl -n cloudhost logs job/seed-ci-images --tail=5
# انتظار: SEED_OK
بررسی:
kubectl -n cloudhost run tags --rm -i --restart=Never \
--image=registry.abrban.com/abrban/alpine:3 \
--overrides='{"spec":{"imagePullSecrets":[{"name":"registry-pull-secret"}]}}' \
-- sh -c 'H="harbor_registry_user:$(kubectl -n cloudhost get secret harbor-core -o jsonpath="{.data.REGISTRY_CREDENTIAL_PASSWORD}" | base64 -d)@harbor-registry.cloudhost.svc.cluster.local:5000"; for r in act-runner kaniko-executor alpine-git node; do wget -qO- "http://${H}/v2/abrban/${r}/tags/list"; echo; done'
بعد از bootstrap، Pipeline با push به
mainخودکار اجرا میشود؛ نیازی به./scripts/trigger-platform-build.shبرای جریان عادی نیست (فقط برای دیباگ دستی).
ساختار Repository (دو ریپو)
abrban/cloud-host — Application Repo
| مسیر | نقش |
|---|---|
backend/, frontend/ |
کد اپلیکیشن + Dockerfile |
backend/helm/cloudhost-platform/ |
Helm Chart پلتفرم |
.gitea/workflows/build-deploy.yaml |
Pipeline (Build + آپدیت GitOps) |
gitops/ |
نصب زیرساخت (Argo CD، Gitea، Sealed Secrets، k3s و…) |
abrban/cloud-host-gitops — GitOps Repo (منبع حقیقت Argo CD)
| مسیر | نقش |
|---|---|
platform/values-abrban.yaml |
مقادیر Production — تنها فایلی که CI آپدیت میکند |
argocd/application-platform.yaml |
تعریف Application (نسخهٔ mirror آن در gitops/argocd/ ریپوی اپ هم هست) |
sealed-secrets/*.yaml |
SealedSecretهای CI — رمزشده و قابل کامیت |
Application در Argo CD بهصورت multi-source تعریف شده: چارت از cloud-host و values از cloud-host-gitops:
sources:
- repoURL: https://git.abrban.com/abrban/cloud-host.git
path: backend/helm/cloudhost-platform
helm:
valueFiles:
- $values/platform/values-abrban.yaml
- repoURL: https://git.abrban.com/abrban/cloud-host-gitops.git
ref: values
مزیت این جداسازی: history تمیز، دسترسی نوشتن CI محدود به ریپوی state، و امکان دیدن کل تاریخچهٔ Deployها با git log یک ریپوی کوچک.
احراز هویتها (چه کسی با چه چیزی به کجا وصل میشود)
| مسیر | مکانیزم | محل نگهداری |
|---|---|---|
| Runner → Gitea (ثبت) | Registration Token | SealedSecret gitea-act-runner-token (ns gitea) در ریپوی gitops |
| Workflow → Gitea (clone/push هر دو ریپو) | PAT کاربر ci |
Secret ریپوی cloud-host در Gitea با نام CI_TOKEN (نامهای GITEA_* رزرو هستند) |
| Kaniko → Harbor (push) | harbor_registry_user |
SealedSecret kaniko-harbor-auth (ns cloudhost-builds) در ریپوی gitops |
| kubelet → Harbor (pull) | user cloudhost |
Secret registry-pull-secret + mirror در gitops/k3s/registries.yaml |
Argo CD → cloud-host (read) |
repo credential | Secret gitea-repo-creds (ns argocd) |
Argo CD → cloud-host-gitops (read) |
PAT کاربر ci |
SealedSecret gitea-gitops-repo-creds (ns argocd) در ریپوی gitops |
توکن CI برای Gitea (CI_TOKEN)
کاربر ci در Gitea ساخته شده و روی هر دو ریپو دسترسی write دارد. PAT آن با scope read:repository, write:repository بهعنوان Secret با نام CI_TOKEN در Settings → Actions → Secrets ریپوی cloud-host ثبت شده است.
برای rotate: در Gitea با کاربر ci توکن جدید بسازید (یا از API ادمین: POST /api/v1/users/ci/tokens)، مقدار Secret را در تنظیمات ریپو آپدیت کنید و SealedSecret gitea-gitops-repo-creds را هم دوباره seal کنید.
احراز هویت Kaniko به Harbor
Kaniko به endpoint داخلی harbor-registry.cloudhost.svc.cluster.local:5000 push میکند که مستقیم به کامپوننت registry میرود و harbor-core را دور میزند. نکتهٔ مهم:
- Robot Accountهای Harbor اینجا کار نمیکنند — توکن آنها را harbor-core صادر میکند و endpoint داخلی به سرویس توکن دسترسی ندارد.
- credential درست، کاربر داخلی
harbor_registry_userاست با پسوردREGISTRY_CREDENTIAL_PASSWORDاز Secretharbor-core:
REG_PASS="$(kubectl -n cloudhost get secret harbor-core \
-o jsonpath='{.data.REGISTRY_CREDENTIAL_PASSWORD}' | base64 -d)"
kubectl -n cloudhost-builds create secret docker-registry kaniko-harbor-auth \
--docker-server=harbor-registry.cloudhost.svc.cluster.local:5000 \
--docker-username=harbor_registry_user \
--docker-password="${REG_PASS}"
نمونهٔ manifest: gitops/jobs/kaniko-harbor-auth.example.yaml — نسخهٔ واقعی بهصورت SealedSecret در ریپوی gitops است.
ورکفلو این Secret را در مسیر /kaniko/.docker/config.json هر دو Kaniko Job مانت میکند. چون push/pull داخلی و بدون TLS است، این فلگها لازماند:
--insecure/--skip-tls-verify— push--insecure-pull/--insecure-registry=${PUSH_REGISTRY}— pull ایمیج پایه ازharbor-registry:5000
ایمیج پایه (node:24-alpine) از endpoint داخلی کشیده میشود، نه از registry.abrban.com:
# در .gitea/workflows/build-deploy.yaml
--build-arg=BASE_IMAGE=harbor-registry.cloudhost.svc.cluster.local:5000/abrban/node:24-alpine
Dockerfileها از ARG BASE_IMAGE=node:24-alpine استفاده میکنند (build محلی بدون تغییر).
پروکسی egress (
registry-egress-proxyدر nscloudhost-builds) هنوز لازم است برایnpm ciو دانلود helm/kubectl داخل مراحل RUN در Dockerfile — فقط pull ایمیج پایه از docker.io حذف شده است.
عارضهٔ جانبی push مستقیم به :5000 — Harbor DB از این ایمیجها بیخبر میماند؛ در UI هاربر دیده نمیشوند ولی pull بهدرستی کار میکند. برای دیدن تگها از registry API استفاده کنید (بخش عیبیابی).
ارتباط Runner با Harbor
Runner خودش با Harbor حرف نمیزند؛ فقط Job میسازد. دو مسیر Harbor:
- Push (داخلی):
harbor-registry.cloudhost.svc.cluster.local:5000— بدون عبور از Traefik. - Pull (kubelet):
registry.abrban.com— از طریق mirror در k3s (scripts/apply-k3s-registries.sh) به harbor-core route میشود.
Versioning ایمیجها
استاندارد فعلی: YYYYMMDD-HHMM-<git-sha-short> (مثلاً 20260702-1230-a1b2c3d)
- Immutable است — هیچوقت یک تگ بازنویسی نمیشود (برخلاف
latest). - قابل ردیابی است — از روی تگ ایمیجِ در حال اجرا مستقیماً به کامیت میرسید.
- مرتبشونده است — بهترتیب زمانی دیده میشود.
از latest هرگز برای Deploy استفاده نکنید؛ هم قابلیت Rollback را از بین میبرد و هم Argo CD تغییری برای sync نمیبیند.
SemVer برای Releaseها (اختیاری): روی کامیت release یک Git Tag مثل v1.4.0 بزنید و همان ایمیج را با skopeo copy تگ اضافه بزنید (rebuild لازم نیست). تگ SemVer برای انسانهاست؛ منبع حقیقتِ Deploy همان تگ SHA-دار در values است.
آپدیت خودکار Helm Values
مرحلهٔ آخر Workflow ریپوی cloud-host-gitops را کلون میکند و فقط دو مقدار را در platform/values-abrban.yaml عوض میکند:
images:
backend:
repository: registry.abrban.com/abrban/cloudhost-backend
tag: "20260702-1230-a1b2c3d" # ← CI این را آپدیت میکند
frontend:
repository: registry.abrban.com/abrban/cloudhost-frontend
tag: "20260702-1230-a1b2c3d" # ← CI این را آپدیت میکند
اگر yq روی Runner موجود باشد از آن استفاده میشود، وگرنه sed هدفمند (فقط خطِ tag: بلافاصله بعد از repository: ...cloudhost-*) اجرا میشود.
جایگزین بررسیشده و کنارگذاشتهشده: Argo CD Image Updater — با روش فعلی همپوشانی دارد و شفافیت کامیتِ صریح از CI را ندارد.
Rollback
چون Deploy فقط از Git انجام میشود، Rollback هم یک عملیات Git است — این بار در ریپوی cloud-host-gitops:
git clone https://git.abrban.com/abrban/cloud-host-gitops.git && cd cloud-host-gitops
# 1. پیدا کردن کامیت deploy مشکلدار
git log --oneline -- platform/values-abrban.yaml
# 2. برگرداندن آن (تگ ایمیج به نسخهٔ قبلی برمیگردد)
git revert <commit-sha>
git push origin main
# 3. Argo CD بهصورت خودکار به نسخهٔ قبلی sync میکند (ایمیج قبلی هنوز در Harbor هست)
نکتهها:
git revert(نهreset --force) — history حفظ میشود و مشخص است چه چیزی چرا برگشت.- Rollback اضطراری (وقتی Git در دسترس نیست):
argocd app rollback abrban-platformیا Sync به revision قبلی در UI. هشدار: چونselfHeal: trueفعال است، Argo در sync بعدی دوباره به HEAD گیت برمیگردد — rollback اضطراری موقتی است و باید بلافاصله باgit revertدائمی شود. - اگر Deployment جدید خراب باشد (CrashLoopBackOff)، بهخاطر
RollingUpdateنسخهٔ قبلی تا آمادهشدن نسخهٔ جدید بالا میماند.
مدیریت Secretها (Sealed Secrets)
کنترلر Sealed Secrets در kube-system نصب است (values در gitops/sealed-secrets/values.yaml؛ ایمیج آن از ghcr.io/bitnami به پروژهٔ abrban/ هاربر seed شده). Secretهای CI بهصورت SealedSecret در ریپوی cloud-host-gitops (پوشهٔ sealed-secrets/) نگهداری میشوند — رمزشده با کلید عمومی کلاستر؛ فقط کنترلرِ داخل کلاستر میتواند رمزگشایی کند، پس کامیتکردنشان امن است.
| SealedSecret | Namespace | محتوا |
|---|---|---|
gitea-act-runner-token |
gitea |
توکن ثبت Runner |
kaniko-harbor-auth |
cloudhost-builds |
dockerconfig کاربر harbor_registry_user |
gitea-gitops-repo-creds |
argocd |
repo credential ریپوی gitops (کاربر ci) |
abrban-platform-secrets |
cloudhost |
postgres-password، jwt-secret، jwt-refresh-secret، cluster-kubeconfig-key، redis-password |
elasticsearch-credentials |
logging |
ELASTIC_PASSWORD، FLUENTBIT_PASSWORD (خارج از چارت پلتفرم — elasticsearch-credentials.example.yaml) |
چارت Helm با secrets.existingSecret: abrban-platform-secrets در platform/values-abrban.yaml (ریپوی gitops) از Secret ازپیشساخته استفاده میکند — Argo CD با helm template نمیتواند Secret تصادفی بسازد (lookup خالی است و هر sync مقادیر JWT/Redis را عوض میکند).
نمونهٔ کامل values: gitops/platform/values-abrban.example.yaml — شامل mirror ایمیج postgres/redis، BASE_IMAGE_REGISTRY، و envهای Elastic.
Greenfield / ارتقا از نسخهٔ قدیم
اگر کلاستر قبلاً با schema یا namespace قدیمی بالا آمده، قبل از deploy جدید reset دیتابیس لازم است. مراحل کامل (با متغیرهای قابلتنظیم برای هر محیط) در RUNBOOK-DEPLOY.fa.md — فاز ۶.
ساخت/بهروزرسانی یک SealedSecret
brew install kubeseal # فقط بار اول
kubectl -n <ns> create secret generic <name> --from-literal=key=value --dry-run=client -o json \
| kubeseal --controller-name=sealed-secrets-controller --controller-namespace=kube-system --format yaml \
> sealed-secrets/<name>.yaml
# سپس commit/push در ریپوی cloud-host-gitops و kubectl apply (یا sync توسط Argo در آینده)
اگر Secret از قبل در کلاستر وجود دارد و میخواهید کنترلر آن را تصاحب کند، اول annotate کنید:
kubectl -n <ns> annotate secret <name> sealedsecrets.bitnami.com/managed="true"
Secretهایی که هنوز دستیاند (خارج از چرخهٔ CI): abrban-wildcard-tls، registry-pull-secret، registry-egress-proxy، harbor-core (ساختهٔ Helm) — میتوانند بهتدریج seal شوند.
نکتهٔ امنیتی: توکن ثبت Runner و پسورد پروکسی که قبلاً در history گیت افشا شده بودند rotate شدهاند (توکن Runner جدید صادر و Runner دوباره ثبت شد). پسورد کاربر پروکسی (
builder) روی سرور پروکسی هنوز باید توسط ادمین عوض شود؛ بعد از تغییر، Secretregistry-egress-proxyرا در namespaceهایcloudhostوgiteaآپدیت کنید.
Best Practiceهای GitOps در این استک (چکلیست)
- Git تنها منبع حقیقت — Argo CD با
automated + prune + selfHeal؛ تغییر دستی باkubectl editبرگردانده میشود. - جداسازی App Repo از GitOps Repo — history تمیز و دسترسی حداقلی CI.
- تگ Immutable بهجای
latest— هر Build تگ یکتا دارد. - جلوگیری از CI Loop — کامیت CI به ریپوی جدا میرود که Workflow ندارد.
- Build بدون Docker Daemon — Kaniko داخل Job، بدون
docker.sockو بدون privileged. - جداسازی push/pull هاربر — push داخلی بدون عبور از Ingress؛ pull از طریق mirror k3s.
- Concurrency در Workflow — دو push پشتسرهم روی آپدیت values با هم race نمیکنند.
- Secretهای GitOps-شده — Sealed Secrets نصب و secretهای CI رمزشده در Git.
- محیط Staging — با
platform/values-staging.yamlو Application دوم قابل اضافهشدن است. - Notification — Argo CD Notifications برای اطلاع از Sync موفق/ناموفق.
عیبیابی سریع
| علامت | بررسی |
|---|---|
| Workflow اجرا نمیشود | kubectl -n gitea logs deploy/gitea-act-runner — ثبت Runner و لیبل abrban-builder |
| Build fail — clone | معتبربودن Secret CI_TOKEN در تنظیمات ریپوی cloud-host |
| Build fail — pull ایمیج پایه | node:24-alpine باید seed شده باشد؛ Workflow باید BASE_IMAGE=harbor-registry.../abrban/node:24-alpine + --insecure-pull داشته باشد؛ نه pull مستقیم از docker.io |
| Build fail — UNAUTHORIZED روی registry.abrban.com | BASE_IMAGE نباید registry.abrban.com/... باشد — credential کانیکو فقط برای harbor-registry:5000 است |
| Build fail — HTTP response to HTTPS client | --insecure-pull و --insecure-registry=harbor-registry.cloudhost.svc.cluster.local:5000 در Kaniko args |
| Build fail — timeout npm/helm/kubectl | registry-egress-proxy در ns cloudhost-builds و سلامت پروکسی egress |
| Build fail — push به Harbor | kubectl -n cloudhost-builds get secret kaniko-harbor-auth؛ پسورد باید با REGISTRY_CREDENTIAL_PASSWORD هاربر یکی باشد |
| کامیت values push نمیشود | دسترسی write کاربر ci روی cloud-host-gitops |
| Argo sync نمیکند | kubectl -n argocd get app abrban-platform؛ هر دو repo credential (gitea-repo-creds و gitea-gitops-repo-creds) |
| Pod ایمیج را pull نمیکند | registry-pull-secret در ns cloudhost و mirror k3s (scripts/apply-k3s-registries.sh) |
| دیدن تگهای موجود در registry | از داخل کلاستر: wget -qO- "http://harbor_registry_user:<REG_PASS>@harbor-registry.cloudhost.svc.cluster.local:5000/v2/abrban/cloudhost-backend/tags/list" |
| Backend CrashLoop — CLUSTER_KUBECONFIG_KEY | Secret abrban-platform-secrets باید کلید cluster-kubeconfig-key داشته باشد و در values: secrets.existingSecret: abrban-platform-secrets |
| Backend CrashLoop — DB auth | پسورد postgres در Secret با DB واقعی همخوان باشد (ALTER USER ... WITH PASSWORD در صورت rotate شدن Secret) |
| Backend CrashLoop — Redis auth | Secret abrban-platform-secrets باید کلید redis-password داشته باشد؛ backend و Redis پلتفرم هر دو از آن استفاده میکنند |
| Backend CrashLoop — ELASTIC_PASSWORD | در production مقدار پیشفرض رد میشود — env در values-abrban.yaml باید رمز rotateشده داشته باشد |
| Workflow fail — tests | Job test-be-* در ns cloudhost-builds — kubectl logs job/... -c test |
| SealedSecret باز نمیشود | kubectl get sealedsecrets -A (ستون SYNCED) و لاگ kubectl -n kube-system logs deploy/sealed-secrets-controller |