From 7e66d1edf3ce7c7b7389c741efe4a84b61d57b1d Mon Sep 17 00:00:00 2001 From: keyhan Date: Thu, 2 Jul 2026 14:10:46 +0330 Subject: [PATCH] ci: split GitOps state into cloud-host-gitops repo, add Sealed Secrets, fix pipeline auth - Workflow now pushes image tags to the separate cloud-host-gitops repo (no more CI loop risk) and authenticates via CI_TOKEN secret - Fix undefined ${REGISTRY} in Kaniko jobs, add concurrency group, targeted tag update, and mounted kaniko-harbor-auth docker config - Argo CD Application is now multi-source (chart from cloud-host, values from cloud-host-gitops) - Remove plaintext runner token and proxy credentials from manifests; secrets are now SealedSecrets in the gitops repo Co-authored-by: Cursor --- .gitea/workflows/build-deploy.yaml | 67 ++++-- RUNBOOK-CICD.fa.md | 236 ++++++++++++++++++++ gitops/README.md | 11 +- gitops/argocd/application-platform.yaml | 22 +- gitops/gitea/act-runner.yaml | 25 ++- gitops/jobs/kaniko-harbor-auth.example.yaml | 36 +++ gitops/platform/values-abrban.yaml | 56 ----- gitops/sealed-secrets/values.yaml | 15 ++ scripts/gitops-deploy.sh | 10 +- scripts/trigger-platform-build.sh | 9 +- 10 files changed, 390 insertions(+), 97 deletions(-) create mode 100644 RUNBOOK-CICD.fa.md create mode 100644 gitops/jobs/kaniko-harbor-auth.example.yaml delete mode 100644 gitops/platform/values-abrban.yaml create mode 100644 gitops/sealed-secrets/values.yaml diff --git a/.gitea/workflows/build-deploy.yaml b/.gitea/workflows/build-deploy.yaml index 137ebed..8c75440 100644 --- a/.gitea/workflows/build-deploy.yaml +++ b/.gitea/workflows/build-deploy.yaml @@ -3,15 +3,26 @@ on: push: branches: [main] paths-ignore: - - "gitops/platform/values-abrban.yaml" - "**.md" workflow_dispatch: +# Serialize builds so parallel pushes don't race on the GitOps values update. +concurrency: + group: build-deploy-platform + cancel-in-progress: false + env: - REGISTRY: harbor-registry.cloudhost.svc.cluster.local:5000 + # PULL_REGISTRY: kubelet pulls via k3s mirror → harbor-core (matches registry-pull-secret) + PULL_REGISTRY: registry.abrban.com + # PUSH_REGISTRY: kaniko pushes directly to harbor-registry (internal, no TLS) + PUSH_REGISTRY: harbor-registry.cloudhost.svc.cluster.local:5000 PROJECT: abrban BUILD_NS: cloudhost-builds - GITEA_URL: http://gitea-http.gitea.svc.cluster.local:3000 + GITEA_HOST: gitea-http.gitea.svc.cluster.local:3000 + # PAT of the "ci" user, stored as repo secret CI_TOKEN (names starting with GITEA_ are reserved) + GITEA_TOKEN: ${{ secrets.CI_TOKEN }} + REPO_PATH: abrban/cloud-host.git + GITOPS_REPO_PATH: abrban/cloud-host-gitops.git jobs: build-and-deploy: @@ -20,7 +31,7 @@ jobs: - name: Checkout shell: sh run: | - git clone --depth=1 --branch main "${GITEA_URL}/abrban/cloud-host.git" workspace + git clone --depth=1 --branch main "http://oauth2:${GITEA_TOKEN}@${GITEA_HOST}/${REPO_PATH}" workspace cd workspace echo "Checked out $(git rev-parse --short HEAD)" @@ -53,29 +64,37 @@ jobs: - name: registry-pull-secret initContainers: - name: clone - image: ${REGISTRY}/${PROJECT}/alpine:3 + image: ${PULL_REGISTRY}/${PROJECT}/alpine:3 command: - sh - -c - - apk add --no-cache git && git clone --depth=1 --branch main ${GITEA_URL}/abrban/cloud-host.git /workspace + - apk add --no-cache git && git clone --depth=1 --branch main http://oauth2:${GITEA_TOKEN}@${GITEA_HOST}/${REPO_PATH} /workspace volumeMounts: - name: ws mountPath: /workspace containers: - name: kaniko - image: ${REGISTRY}/${PROJECT}/kaniko-executor:v1.27.6-debug + image: ${PULL_REGISTRY}/${PROJECT}/kaniko-executor:v1.27.6-debug args: - --dockerfile=/workspace/backend/Dockerfile - --context=dir:///workspace/backend - - --destination=${REGISTRY}/${PROJECT}/cloudhost-backend:${IMAGE_TAG} + - --destination=${PUSH_REGISTRY}/${PROJECT}/cloudhost-backend:${IMAGE_TAG} - --insecure - --skip-tls-verify volumeMounts: - name: ws mountPath: /workspace + - name: docker-config + mountPath: /kaniko/.docker volumes: - name: ws emptyDir: {} + - name: docker-config + secret: + secretName: kaniko-harbor-auth + items: + - key: .dockerconfigjson + path: config.json ENDJOB echo "Waiting for backend build job: ${JOB_NAME}" kubectl -n ${BUILD_NS} wait --for=condition=complete job/${JOB_NAME} --timeout=20m @@ -101,43 +120,59 @@ jobs: - name: registry-pull-secret initContainers: - name: clone - image: ${REGISTRY}/${PROJECT}/alpine:3 + image: ${PULL_REGISTRY}/${PROJECT}/alpine:3 command: - sh - -c - - apk add --no-cache git && git clone --depth=1 --branch main ${GITEA_URL}/abrban/cloud-host.git /workspace + - apk add --no-cache git && git clone --depth=1 --branch main http://oauth2:${GITEA_TOKEN}@${GITEA_HOST}/${REPO_PATH} /workspace volumeMounts: - name: ws mountPath: /workspace containers: - name: kaniko - image: ${REGISTRY}/${PROJECT}/kaniko-executor:v1.27.6-debug + image: ${PULL_REGISTRY}/${PROJECT}/kaniko-executor:v1.27.6-debug args: - --dockerfile=/workspace/frontend/Dockerfile - --context=dir:///workspace/frontend - --build-arg=NEXT_PUBLIC_API_URL=https://api.abrban.com - - --destination=${REGISTRY}/${PROJECT}/cloudhost-frontend:${IMAGE_TAG} + - --destination=${PUSH_REGISTRY}/${PROJECT}/cloudhost-frontend:${IMAGE_TAG} - --insecure - --skip-tls-verify volumeMounts: - name: ws mountPath: /workspace + - name: docker-config + mountPath: /kaniko/.docker volumes: - name: ws emptyDir: {} + - name: docker-config + secret: + secretName: kaniko-harbor-auth + items: + - key: .dockerconfigjson + path: config.json ENDJOB echo "Waiting for frontend build job: ${JOB_NAME}" kubectl -n ${BUILD_NS} wait --for=condition=complete job/${JOB_NAME} --timeout=20m echo "Frontend build done" - - name: Update GitOps values and push + - name: Update GitOps repo and push shell: sh run: | - cd workspace - sed -i "s|tag: \"[^\"]*\"|tag: \"${IMAGE_TAG}\"|g" gitops/platform/values-abrban.yaml + git clone --depth=1 --branch main "http://oauth2:${GITEA_TOKEN}@${GITEA_HOST}/${GITOPS_REPO_PATH}" gitops-repo + cd gitops-repo + VALUES=platform/values-abrban.yaml + if command -v yq >/dev/null 2>&1; then + IMAGE_TAG="${IMAGE_TAG}" yq -i '.images.backend.tag = strenv(IMAGE_TAG) | .images.frontend.tag = strenv(IMAGE_TAG)' "${VALUES}" + else + # Only touch the tag line directly below each cloudhost-* repository line. + sed -i "/repository: .*cloudhost-backend/{n;s|tag: \".*\"|tag: \"${IMAGE_TAG}\"|;}" "${VALUES}" + sed -i "/repository: .*cloudhost-frontend/{n;s|tag: \".*\"|tag: \"${IMAGE_TAG}\"|;}" "${VALUES}" + fi git config user.email "ci@abrban.com" git config user.name "Gitea Actions" - git add gitops/platform/values-abrban.yaml + git add "${VALUES}" if ! git diff --cached --quiet; then git commit -m "ci: deploy platform ${IMAGE_TAG}" git push origin HEAD:main diff --git a/RUNBOOK-CICD.fa.md b/RUNBOOK-CICD.fa.md new file mode 100644 index 0000000..212bcef --- /dev/null +++ b/RUNBOOK-CICD.fa.md @@ -0,0 +1,236 @@ +# RUNBOOK — خط CI/CD (Gitea Actions → Kaniko → Harbor → Argo CD) + +این مستند جریان کامل Build و Deploy پلتفرم را توضیح می‌دهد: از Push شدن کد روی `main` تا استقرار خودکار روی Kubernetes. + +--- + +## معماری و جریان کلی + +```mermaid +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 +``` + +مراحل به ترتیب: + +1. Developer روی شاخهٔ `main` در ریپوی اپلیکیشن (`git.abrban.com/abrban/cloud-host`) push می‌کند. +2. Workflow در [`.gitea/workflows/build-deploy.yaml`](.gitea/workflows/build-deploy.yaml) روی Runner با لیبل `abrban-builder` اجرا می‌شود. +3. Runner کد را با توکن CI کلون می‌کند و تگ ایمیج (`YYYYMMDD-HHMM-`) را می‌سازد. +4. برای هر ایمیج (backend و frontend) یک Kaniko Job در namespace `cloudhost-builds` ساخته می‌شود که کد را کلون، ایمیج را build و به Harbor push می‌کند. +5. بعد از موفقیت هر دو Build، همان Runner ریپوی **`cloud-host-gitops`** را کلون می‌کند، مقدار `images.backend.tag` و `images.frontend.tag` را در `platform/values-abrban.yaml` عوض و commit/push می‌کند. +6. Argo CD (Application به نام `abrban-platform` با sync خودکار) تغییر را تشخیص می‌دهد و نسخهٔ جدید را در namespace `cloudhost` مستقر می‌کند. + +> **جلوگیری از حلقهٔ CI:** کامیتِ Pipeline به ریپوی جدا (`cloud-host-gitops`) می‌رود که هیچ Workflowای ندارد؛ بنابراین Build دوباره trigger نمی‌شود. + +--- + +## ساختار 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`: + +```yaml +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` از Secret `harbor-core`: + +```bash +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`](gitops/jobs/kaniko-harbor-auth.example.yaml) — نسخهٔ واقعی به‌صورت SealedSecret در ریپوی gitops است. + +ورک‌فلو این Secret را در مسیر `/kaniko/.docker/config.json` هر دو Kaniko Job مانت می‌کند. چون push داخلی و بدون TLS است، فلگ‌های `--insecure --skip-tls-verify` لازم‌اند — این ترافیک از کلاستر خارج نمی‌شود. + +> **عارضهٔ جانبی 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-` (مثلاً `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` عوض می‌کند: + +```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`: + +```bash +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 +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`](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`) | + +### ساخت/به‌روزرسانی یک SealedSecret + +```bash +brew install kubeseal # فقط بار اول + +kubectl -n create secret generic --from-literal=key=value --dry-run=client -o json \ + | kubeseal --controller-name=sealed-secrets-controller --controller-namespace=kube-system --format yaml \ + > sealed-secrets/.yaml +# سپس commit/push در ریپوی cloud-host-gitops و kubectl apply (یا sync توسط Argo در آینده) +``` + +> اگر Secret از قبل در کلاستر وجود دارد و می‌خواهید کنترلر آن را تصاحب کند، اول annotate کنید: +> `kubectl -n annotate secret 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`) روی سرور پروکسی هنوز باید توسط ادمین عوض شود؛ بعد از تغییر، Secret `registry-egress-proxy` را در namespaceهای `cloudhost` و `gitea` آپدیت کنید. + +--- + +## Best Practiceهای GitOps در این استک (چک‌لیست) + +- [x] **Git تنها منبع حقیقت** — Argo CD با `automated + prune + selfHeal`؛ تغییر دستی با `kubectl edit` برگردانده می‌شود. +- [x] **جداسازی App Repo از GitOps Repo** — history تمیز و دسترسی حداقلی CI. +- [x] **تگ Immutable به‌جای `latest`** — هر Build تگ یکتا دارد. +- [x] **جلوگیری از CI Loop** — کامیت CI به ریپوی جدا می‌رود که Workflow ندارد. +- [x] **Build بدون Docker Daemon** — Kaniko داخل Job، بدون `docker.sock` و بدون privileged. +- [x] **جداسازی push/pull هاربر** — push داخلی بدون عبور از Ingress؛ pull از طریق mirror k3s. +- [x] **Concurrency در Workflow** — دو push پشت‌سرهم روی آپدیت values با هم race نمی‌کنند. +- [x] **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 — 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:@harbor-registry.cloudhost.svc.cluster.local:5000/v2/abrban/cloudhost-backend/tags/list"` | +| SealedSecret باز نمی‌شود | `kubectl get sealedsecrets -A` (ستون SYNCED) و لاگ `kubectl -n kube-system logs deploy/sealed-secrets-controller` | diff --git a/gitops/README.md b/gitops/README.md index 5b015c0..eb529e0 100644 --- a/gitops/README.md +++ b/gitops/README.md @@ -52,6 +52,9 @@ for ns in argocd gitea cloudhost-builds; do kubectl -n cloudhost get secret registry-pull-secret -o yaml | sed "s/namespace: cloudhost/namespace: ${ns}/" | kubectl apply -f - done +# پروکسی egress برای act-runner (دانلود kubectl در initContainer) +kubectl -n cloudhost get secret registry-egress-proxy -o yaml | sed 's/namespace: cloudhost/namespace: gitea/' | kubectl apply -f - + # 5. git push سورس به Gitea (نه docker push) # git remote add gitea https://git.abrban.com/abrban/cloud-host.git # git push gitea main @@ -63,6 +66,8 @@ done ./scripts/gitops-deploy.sh # 8. Gitea Actions runner +# توکن ثبت: SealedSecret در ریپوی cloud-host-gitops (sealed-secrets/gitea-act-runner-token.yaml) +# یا دستی: kubectl -n gitea create secret generic gitea-act-runner-token --from-literal=token='' kubectl apply -f gitops/gitea/act-runner.yaml # 9. Argo CD Application @@ -73,4 +78,8 @@ kubectl apply -f gitops/argocd/application-platform.yaml Gitea Actions: [.gitea/workflows/build-deploy.yaml](../.gitea/workflows/build-deploy.yaml) -Push به `main` → Kaniko (از Harbor proxy) → push به `abrban/` → ArgoCD sync. +Push به `main` → Kaniko → push به `abrban/` → کامیت tag در ریپوی [cloud-host-gitops](https://git.abrban.com/abrban/cloud-host-gitops) → ArgoCD sync. + +- مقادیر Production در ریپوی جدا `abrban/cloud-host-gitops` است (`platform/values-abrban.yaml`)؛ Application به‌صورت multi-source تعریف شده. +- Secretهای CI به‌صورت SealedSecret در همان ریپو هستند (کنترلر در `kube-system`، values در `gitops/sealed-secrets/values.yaml`). +- مستند کامل: [RUNBOOK-CICD.fa.md](../RUNBOOK-CICD.fa.md) diff --git a/gitops/argocd/application-platform.yaml b/gitops/argocd/application-platform.yaml index 7ee049d..a391486 100644 --- a/gitops/argocd/application-platform.yaml +++ b/gitops/argocd/application-platform.yaml @@ -1,19 +1,23 @@ +# Source of truth for this file: cloud-host-gitops repo (argocd/application-platform.yaml). +# Kept here as a mirror for bootstrap convenience. apiVersion: argoproj.io/v1alpha1 kind: Application metadata: name: abrban-platform namespace: argocd - finalizers: - - resources-finalizer.argocd.argoproj.io spec: project: default - source: - repoURL: https://git.abrban.com/abrban/cloud-host.git - targetRevision: main - path: backend/helm/cloudhost-platform - helm: - valueFiles: - - ../../../gitops/platform/values-abrban.yaml + # Multi-source: chart lives in the app repo, values (GitOps state) live in cloud-host-gitops. + sources: + - repoURL: https://git.abrban.com/abrban/cloud-host.git + targetRevision: main + path: backend/helm/cloudhost-platform + helm: + valueFiles: + - $values/platform/values-abrban.yaml + - repoURL: https://git.abrban.com/abrban/cloud-host-gitops.git + targetRevision: main + ref: values destination: server: https://kubernetes.default.svc namespace: cloudhost diff --git a/gitops/gitea/act-runner.yaml b/gitops/gitea/act-runner.yaml index 4400678..61790c0 100644 --- a/gitops/gitea/act-runner.yaml +++ b/gitops/gitea/act-runner.yaml @@ -1,13 +1,17 @@ # Gitea Actions runner — host mode (no docker.sock; k3s uses containerd) # Runner creates Kaniko Jobs in cloudhost-builds for image builds. -apiVersion: v1 -kind: Secret -metadata: - name: gitea-act-runner-token - namespace: gitea -type: Opaque -stringData: - token: "nL63VkZEyqpCNFdF3AMM9wzQLdLlATUvXSe5Tj0R" +# +# Secrets (never commit real values): +# +# # Registration token: managed as SealedSecret in the cloud-host-gitops repo +# # (sealed-secrets/gitea-act-runner-token.yaml). To rotate manually: +# # token from Gitea UI → Site Administration → Actions → Runners, then: +# kubectl -n gitea create secret generic gitea-act-runner-token \ +# --from-literal=token='' +# +# # Egress proxy (copied from cloudhost namespace, needed by the kubectl download): +# kubectl -n cloudhost get secret registry-egress-proxy -o yaml \ +# | sed 's/namespace: cloudhost/namespace: gitea/' | kubectl apply -f - --- apiVersion: v1 kind: ServiceAccount @@ -64,7 +68,10 @@ spec: imagePullPolicy: IfNotPresent env: - name: HTTPS_PROXY - value: "http://builder:BeeldiOr!12009@45.129.38.203:9911" + valueFrom: + secretKeyRef: + name: registry-egress-proxy + key: HTTPS_PROXY command: - sh - -c diff --git a/gitops/jobs/kaniko-harbor-auth.example.yaml b/gitops/jobs/kaniko-harbor-auth.example.yaml new file mode 100644 index 0000000..3c5cfac --- /dev/null +++ b/gitops/jobs/kaniko-harbor-auth.example.yaml @@ -0,0 +1,36 @@ +# Example only — DO NOT commit real credentials. +# The real secret is managed as a SealedSecret in the cloud-host-gitops repo +# (sealed-secrets/kaniko-harbor-auth.yaml). +# +# Kaniko pushes directly to the internal registry endpoint +# (harbor-registry.cloudhost.svc.cluster.local:5000), which bypasses harbor-core. +# That endpoint only accepts the internal registry credential — Harbor robot +# accounts do NOT work there (their tokens are issued by harbor-core's token +# service). Use the harbor_registry_user credential from the harbor-core secret: +# +# 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}" +# +# The build-deploy workflow mounts this secret at /kaniko/.docker/config.json +# inside every Kaniko Job. See RUNBOOK-CICD.fa.md for the full procedure. +apiVersion: v1 +kind: Secret +metadata: + name: kaniko-harbor-auth + namespace: cloudhost-builds +type: kubernetes.io/dockerconfigjson +stringData: + .dockerconfigjson: | + { + "auths": { + "harbor-registry.cloudhost.svc.cluster.local:5000": { + "username": "harbor_registry_user", + "password": "", + "auth": "" + } + } + } diff --git a/gitops/platform/values-abrban.yaml b/gitops/platform/values-abrban.yaml deleted file mode 100644 index fe06d01..0000000 --- a/gitops/platform/values-abrban.yaml +++ /dev/null @@ -1,56 +0,0 @@ -# Production values for abrban.com — used by ArgoCD / Gitea Actions GitOps -namespace: cloudhost -createNamespace: false - -global: - storageClass: local-path - -images: - backend: - repository: registry.abrban.com/abrban/cloudhost-backend - tag: "1.0.0" - pullPolicy: Always - frontend: - repository: registry.abrban.com/abrban/cloudhost-frontend - tag: "1.0.0" - pullPolicy: Always - -backend: - imagePullSecrets: - - name: registry-pull-secret - sourceStorage: - enabled: true - existingSecret: ceph-app-sources-credentials - env: - NODE_ENV: production - PORT: "4000" - PLATFORM_DOMAIN: apps.abrban.com - REGISTRY_URL: registry.abrban.com - REGISTRY_PULL_URL: registry.abrban.com - BUILD_NAMESPACE: cloudhost-builds - BUILD_SERVICE_ACCOUNT: kaniko-builder - UPLOAD_DIR: /app/uploads - PLATFORM_CREATE_STORAGE_CLASS: "false" - PLATFORM_STORAGE_CLASS: rook-ceph-block - PLATFORM_STORAGE_PROVISIONER: rook-ceph.rbd.csi.ceph.com - ELASTICSEARCH_HOST: elasticsearch.logging.svc.cluster.local - ELASTICSEARCH_AUTO_PORT_FORWARD: "false" - -frontend: - imagePullSecrets: - - name: registry-pull-secret - -ingress: - enabled: true - className: traefik - annotations: - traefik.ingress.kubernetes.io/router.entrypoints: websecure - frontend: - host: abrban.com - panel: - host: panel.abrban.com - api: - host: api.abrban.com - tls: - enabled: true - secretName: abrban-wildcard-tls diff --git a/gitops/sealed-secrets/values.yaml b/gitops/sealed-secrets/values.yaml new file mode 100644 index 0000000..d6a2023 --- /dev/null +++ b/gitops/sealed-secrets/values.yaml @@ -0,0 +1,15 @@ +# Sealed Secrets controller — encrypts secrets so they can live in Git. +# Install: +# 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 +# +# fullnameOverride matches the default the kubeseal CLI expects +# (sealed-secrets-controller in kube-system), so kubeseal works without flags. +fullnameOverride: sealed-secrets-controller + +image: + # Seeded into Harbor abrban/ project from ghcr.io/bitnami (proxy-ghcr returned 401 + # for kubelet pulls; see seed job pattern in gitops/jobs/seed-ci-images.yaml). + registry: registry.abrban.com + repository: abrban/sealed-secrets-controller diff --git a/scripts/gitops-deploy.sh b/scripts/gitops-deploy.sh index fed08a9..eb9ecd1 100755 --- a/scripts/gitops-deploy.sh +++ b/scripts/gitops-deploy.sh @@ -5,9 +5,17 @@ set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" NAMESPACE="${NAMESPACE:-cloudhost}" RELEASE="${RELEASE:-cloudhost}" -VALUES="${VALUES:-${ROOT}/gitops/platform/values-abrban.yaml}" +# Production values now live in the cloud-host-gitops repo (platform/values-abrban.yaml). +VALUES="${VALUES:-${ROOT}/../cloud-host-gitops/platform/values-abrban.yaml}" TAG="${TAG:-}" +if [[ ! -f "${VALUES}" ]]; then + echo "ERROR: values file not found: ${VALUES}" >&2 + echo "Clone the GitOps repo next to this one, or pass VALUES=/path/to/values-abrban.yaml:" >&2 + echo " git clone https://git.abrban.com/abrban/cloud-host-gitops.git" >&2 + exit 1 +fi + if [[ -z "${TAG}" ]]; then TAG="$(grep -E '^\s+tag:' "${VALUES}" | head -1 | sed 's/.*tag: *"\?\([^"]*\)"\?.*/\1/')" fi diff --git a/scripts/trigger-platform-build.sh b/scripts/trigger-platform-build.sh index fb25e57..a958f85 100755 --- a/scripts/trigger-platform-build.sh +++ b/scripts/trigger-platform-build.sh @@ -70,9 +70,8 @@ EOF echo "==> Waiting for build job..." kubectl -n "${NAMESPACE}" wait --for=condition=complete "job/${JOB_NAME}" --timeout=45m -echo "==> Updating values-abrban.yaml tag to ${IMAGE_TAG}" -sed -i.bak "s|tag: \".*\"|tag: \"${IMAGE_TAG}\"|g" "${ROOT}/gitops/platform/values-abrban.yaml" -rm -f "${ROOT}/gitops/platform/values-abrban.yaml.bak" - -echo "==> Build complete. Deploy with:" +echo "==> Build complete (tag=${IMAGE_TAG})." +echo "To deploy via GitOps, commit the tag in the cloud-host-gitops repo:" +echo " platform/values-abrban.yaml → images.backend.tag / images.frontend.tag = \"${IMAGE_TAG}\"" +echo "Or for a direct Helm deploy (bootstrap only):" echo " TAG=${IMAGE_TAG} ./scripts/gitops-deploy.sh"