Files
docs/deployment/CICD-PIPELINE-GUIDE.md
T

678 lines
26 KiB
Markdown
Raw 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.
# 🔧 راهنمای CI/CD Pipeline — Gitea Actions + K3s
> آخرین بروزرسانی: February 17, 2026
---
## 📐 معماری کلی
```
┌─────────────────────────────────────────────────────────┐
│ K3s Cluster (194.5.195.53) │
│ │
│ ┌─────────────────────────────────────────────────┐ │
│ │ gitea-runner Pod (2 containers) │ │
│ │ │ │
│ │ ┌──────────────────┐ ┌──────────────────────┐ │ │
│ │ │ docker (DinD) │ │ runner (act_runner) │ │ │
│ │ │ docker:dind │ │ gitea/act_runner │ │ │
│ │ │ privileged: true │ │ DOCKER_HOST= │ │ │
│ │ │ port: 2375 │ │ tcp://localhost:2375│ │ │
│ │ └──────────────────┘ └──────────────────────┘ │ │
│ │ ▲ shared volumes: docker-storage │ │
│ └─────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────┐ ┌───────────┐ ┌────────────────┐ │
│ │ Gitea │ │ Nexus │ │ Docker Reg. │ │
│ │ :3000 │ │ :32081 │ │ :32082 (pull) │ │
│ │ │ │ (NuGet) │ │ :30080 (push) │ │
│ └─────────────┘ └───────────┘ └────────────────┘ │
└─────────────────────────────────────────────────────────┘
```
### سه لایه Docker-in-Docker:
```
K3s containerd (لایه ۱)
└── gitea-runner Pod → docker container (DinD daemon) (لایه ۲)
└── workflow: docker run / docker build (لایه ۳)
```
---
## 📁 فایل‌های Workflow
### 🐳 K8s Pipelines (Docker + K3s) — آفلاین
| سرویس | فایل | Branch | Image | Deploy |
|--------|------|--------|-------|--------|
| CMS | `kub-deploy.yml` | `kub-stage` | `admin/cms` | SSH → kubectl |
| BackOffice | `kub-deploy.yml` | `kub-stage` | `admin/backoffice` | SSH → kubectl |
| FrontOffice | `kub-deploy.yml` | `kub-stage` | `admin/frontoffice` | SSH → kubectl |
| CMS | `prod-deploy.yml` | `production` | `admin/cms:prod` | SSH → kubectl |
| BackOffice | `prod-deploy.yml` | `production` | `admin/backoffice:prod` | SSH → kubectl |
| FrontOffice | `prod-deploy.yml` | `production` | `admin/frontoffice:prod` | SSH → kubectl |
### 🪟 Windows/IIS Pipelines (Legacy) — آنلاین
| سرویس | فایل | Branch | Target |
|--------|------|--------|--------|
| CMS | `cms-stage.yml` | `stage_new` | IIS → `cms.kbs1.ir` |
| BackOffice | `bo-stage.yml` | `stage-new` | IIS → `admin.kbs1.ir` |
| FrontOffice | `fo-stage.yml` | `stage-new` | IIS → `kbs1.ir` |
> ⚠️ Stage pipeline ها از Windows runner + IIS استفاده میکنن و Docker ندارن.
### ساختار مشترک Pipeline:
```
1. Start Docker daemon (DinD)
2. Checkout code (git clone)
3. Login to Docker registries (32082 + 30080)
4. [CMS only] Publish Protobuf packages
5. Build Docker Image
6. Push to Registry
7. Deploy to Kubernetes (SSH → kubectl rollout restart)
```
---
## 🐛 مشکلات حل‌شده و راه‌حل‌ها
### مشکل ۱: `iptables failed: Permission denied`
**خطا:**
```
iptables v1.8.10 (nf_tables): Could not fetch rule set generation id: Permission denied
```
**علت:** K3s containerd به Docker daemon اجازه تغییر iptables نمیده.
**راه‌حل:** غیرفعال کردن networking در dockerd:
```bash
dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs &
```
> ⚠️ با `--bridge=none` نیاز به شبکه‌سازی Docker نیست چون فقط build و push انجام میشه.
---
### مشکل ۲: `failed to unmount overlayfs: operation not permitted`
**خطا:**
```
failed to register layer: unshare: operation not permitted
```
**علت:** `overlay2` storage driver نیاز به mount namespace داره که داخل K3s مجاز نیست.
**راه‌حل:** استفاده از `vfs` storage driver:
```bash
dockerd --storage-driver=vfs &
```
> ⚠️ `vfs` کندتره ولی هیچ mount syscall خاصی نیاز نداره. برای CI/CD کافیه.
---
### مشکل ۳: `no basic auth credentials` هنگام pull ایمیج
**خطا:**
```
Error response from daemon: Head "https://194.5.195.53:32082/v2/dotnet/sdk/manifests/9.0":
no basic auth credentials
```
**علت:** `docker login` فقط قبل از push انجام میشد، ولی `docker build` (یا `docker run`) هم از `32082` ایمیج pull میکنه.
**راه‌حل:** اضافه کردن step "Login to Docker registries" بلافاصله بعد از Checkout:
```yaml
- name: Login to Docker registries
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 194.5.195.53:32082 -u admin --password-stdin
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin
```
---
### مشکل ۴: `unshare: operation not permitted` هنگام extract لایه‌ها
**خطا:**
```
docker: failed to register layer: unshare: operation not permitted
```
**علت اصلی (دو بخش):**
**بخش ۱:** Gitea act_runner دیفالت `container.privileged: false` داره. یعنی job container ها بدون privileged ساخته میشن — حتی اگه workflow بنویسه `options: --privileged`.
**بخش ۲:** env var `CONFIG_FILE` در runner container ست نبود → `run.sh` فلگ `--config` رو به `act_runner daemon` پاس نمیداد → config.yaml اصلاً لود نمیشد!
**راه‌حل (سمت سرور):**
۱. ساخت ConfigMap:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: runner-config
namespace: default
data:
config.yaml: |
log:
level: info
runner:
file: .runner
capacity: 1
timeout: 3h
container:
privileged: true
options: "--security-opt seccomp=unconfined --security-opt apparmor=unconfined"
valid_volumes:
- "**"
```
۲. Mount کردن در Deployment + env var:
```bash
kubectl patch deployment gitea-runner --type=json -p='[
{"op":"add","path":"/spec/template/spec/containers/1/env/-",
"value":{"name":"CONFIG_FILE","value":"/data/config.yaml"}},
{"op":"add","path":"/spec/template/spec/containers/1/volumeMounts/-",
"value":{"name":"runner-config","mountPath":"/data/config.yaml","subPath":"config.yaml"}},
{"op":"add","path":"/spec/template/spec/volumes/-",
"value":{"name":"runner-config","configMap":{"name":"runner-config"}}}
]'
```
> ⚠️ **نکته مهم:** بدون `CONFIG_FILE=/data/config.yaml` env var، فایل `run.sh` داخل act_runner image فلگ `--config` رو پاس نمیده!
---
### مشکل ۵: Protobuf restore از nuget.org بجای Nexus
**علت:** `dotnet restore` بدون `--configfile` از دیفالت NuGet sources استفاده میکنه.
**راه‌حل:**
```bash
dotnet restore "$proj" --configfile src/NuGet.config
```
---
### مشکل ۶: عدم دسترسی شبکه با `--bridge=none`
**علت:** `dockerd --bridge=none` شبکه Docker bridge رو غیرفعال میکنه. در نتیجه container هایی که با `docker run` یا `docker build` ساخته میشن، دسترسی شبکه ندارن (مثلاً `dotnet restore` نمیتونه به Nexus وصل بشه).
**راه‌حل:** استفاده از `--network host` در `docker run` و `docker build`:
```bash
# Protobuf step
docker run --rm --network host -v $(pwd):/src -w /src ...
# Build step
DOCKER_BUILDKIT=0 docker build --network host -t ... .
```
---
### مشکل ۷: `failed to prepare ... as ...: invalid argument` (BuildKit)
**خطا:**
```
ERROR: failed to build: failed to solve: failed to prepare xxx as yyy: invalid argument
```
**علت:** BuildKit (بیلدر پیش‌فرض Docker ≥23) از snapshotter overlay استفاده میکنه که با `--storage-driver=vfs` سازگاری نداره.
**راه‌حل:** غیرفعال کردن BuildKit:
```bash
DOCKER_BUILDKIT=0 docker build --network host -t ... .
```
> ⚠️ Legacy builder از vfs بدون مشکل استفاده میکنه.
---
### مشکل ۸: `COPY libs/` fails in Docker build (BackOffice)
**خطا:**
```
COPY failed: file not found in build context: stat libs/: file does not exist
```
**علت:** Dockerfile خط `COPY ["libs/", "libs/"]` داشت ولی `libs/` خارج از Docker build context (`src/`) بود. قبلاً BFF DLLها استفاده می‌شدن، ولی حالا از NuGet package مستقیم استفاده می‌شه.
**راه‌حل:**
1. حذف `COPY ["libs/", "libs/"]` از Dockerfile
2. تغییر `ProjectReference` به `PackageReference` در csproj:
```xml
<!-- قبل -->
<ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/CMSMicroservice.Protobuf.csproj" />
<!-- بعد -->
<PackageReference Include="Foursat.CMSMicroservice.Protobuf" Version="0.0.178" />
```
---
### مشکل ۹: ProjectReference خارج از Docker context (FrontOffice/BackOffice)
**خطا:**
```
error CS0246: The type or namespace name 'CustomerAddressModel' could not be found
```
**علت:** csproj از `ProjectReference Include="../../../CMS/src/CMSMicroservice.Protobuf/..."` استفاده می‌کرد. در Docker build context فقط `src/` موجوده → CMS قابل دسترسی نیست.
**راه‌حل:**
1. بامپ نسخه پروتوباف (`0.0.177``0.0.178`)
2. `dotnet pack -c Release` و push به Nexus
3. تغییر هر دو پروژه (FrontOffice + BackOffice) به `PackageReference`
```bash
# Pack & Push
cd CMS/src/CMSMicroservice.Protobuf
dotnet pack -c Release
dotnet nuget push bin/Release/Foursat.CMSMicroservice.Protobuf.0.0.178.nupkg \
--source http://194.5.195.53:32081/repository/foursat-nuget-hosted/index.json \
--api-key admin:87zH26nbqT --skip-duplicate
```
---
### مشکل ۱۰: `nginx:alpine` TLS handshake timeout
**خطا:**
```
Get "https://registry-1.docker.io/v2/": net/http: TLS handshake timeout
```
**علت:** Dockerfile خط `FROM nginx:alpine` مستقیم از Docker Hub پول می‌کرد ولی سرور به Docker Hub دسترسی نداره.
**راه‌حل:** تغییر به رجیستری لوکال:
```dockerfile
# قبل
FROM nginx:alpine AS final
# بعد
FROM 194.5.195.53:32082/nginx:alpine AS final
```
---
### مشکل ۱۱: SERVER_PASSWORD secret missing → Permission denied
**خطا:**
```
Permission denied, please try again.
```
**علت:** سکرت `SERVER_PASSWORD` در ریپو Gitea تنظیم نشده بود. Pipeline از `sshpass -e` با `${{ secrets.SERVER_PASSWORD }}` برای SSH استفاده می‌کنه.
**راه‌حل:** اضافه کردن سکرت از طریق Gitea API:
```bash
curl -sk -u "admin:87zH26nbqT" -X PUT \
"https://git.se.kbs1.ir/api/v1/repos/admin/BackOffice/actions/secrets/SERVER_PASSWORD" \
-H "Content-Type: application/json" -d '{"data":"87zH26nbqT"}'
```
---
### مشکل ۱۲: CMS ingress 502 — backend-protocol: GRPC
**خطا:** `https://cms.se.kbs1.ir/` → 502 Bad Gateway
**علت:** CMS ingress annotation `backend-protocol: GRPC` داشت + Kestrel فقط `Http2`. مرورگر HTTP/1.1 می‌فرسته → nginx نمی‌تونه به gRPC backend فوروارد کنه.
**راه‌حل (دو تغییر):**
1. Kestrel protocol → `Http1AndHttp2` (هم gRPC هم REST):
```bash
kubectl set env deployment/cms Kestrel__EndpointDefaults__Protocols=Http1AndHttp2
```
2. حذف GRPC annotations از ingress:
```bash
kubectl annotate ingress cms-ingress nginx.ingress.kubernetes.io/backend-protocol-
kubectl annotate ingress cms-ingress nginx.ingress.kubernetes.io/grpc-backend-
```
> ⚠️ FrontOffice از gRPC-Web استفاده می‌کنه که روی HTTP/1.1 هم کار می‌کنه.
---
## 🔄 تغییرات prod-deploy (قدیم → جدید)
| مورد | قدیم (prod-deploy) | جدید |
|------|-------------------|------|
| Container image | `docker:latest` | `docker-sshpass:latest` (شامل sshpass + git) |
| Proxy | `HTTP_PROXY` + `HTTPS_PROXY` | حذف شد (آفلاین) |
| Registry | `gitea-svc:3000` + external | فقط `194.5.195.53:30080` |
| kubectl | `apk add` + `curl` از اینترنت | SSH → `kubectl` مستقیم روی سرور |
| Auth | hardcoded password | `secrets.REGISTRY_PASSWORD` + `secrets.SERVER_PASSWORD` |
| BuildKit | فعال (دیفالت) | `DOCKER_BUILDKIT=0` |
| Network | Docker bridge (دیفالت) | `--network host` |
| dockerd | دیفالت | `--iptables=false --ip6tables=false --bridge=none --storage-driver=vfs` |
| Deploy | `KUBECONFIG_PROD` (base64) | SSH + sshpass (مثل kub-stage) |
> ✅ حالا همه ۶ K8s pipeline (۳ stage + ۳ prod) از **یک الگوی مشترک آفلاین** استفاده میکنن.
---
containers:
- name: docker # DinD sidecar
image: 194.5.195.53:32082/docker:dind
securityContext:
privileged: true
env:
- DOCKER_TLS_CERTDIR: ""
volumeMounts:
- /var/lib/docker → docker-storage
- /etc/docker/daemon.json → docker-config (ConfigMap)
- name: runner # Gitea act_runner
image: 194.5.195.53:32082/gitea/act_runner:latest
env:
- GITEA_INSTANCE_URL: http://gitea-svc:3000
- DOCKER_HOST: tcp://localhost:2375
- CONFIG_FILE: /data/config.yaml # ← حیاتی! بدون این runner config لود نمیشه
volumeMounts:
- /data → runner-data
- /data/config.yaml → runner-config (ConfigMap)
```
### ConfigMaps:
| نام | محتوا | Mount Path |
|-----|-------|------------|
| `docker-daemon-config` | `daemon.json` با insecure-registries | `/etc/docker/daemon.json` |
| `runner-config` | `config.yaml` با privileged + seccomp | `/data/config.yaml` |
### Labels (ثبت‌شده در Gitea):
```
ubuntu-latest → docker://docker.gitea.com/runner-images:ubuntu-latest
ubuntu-24.04 → docker://docker.gitea.com/runner-images:ubuntu-24.04
ubuntu-22.04 → docker://docker.gitea.com/runner-images:ubuntu-22.04
```
---
## 🔑 Secrets مورد نیاز (Gitea → Settings → Secrets)
| Secret | استفاده |
|--------|---------|
| `REGISTRY_PASSWORD` | پسورد Docker registry (admin) |
| `SERVER_PASSWORD` | پسورد SSH سرور (root) — ⚠️ باید در هر ۳ ریپو ست بشه |
> **نکته:** اگر `SERVER_PASSWORD` ست نباشه، مرحله Deploy با `Permission denied` فیل می‌شه.
> با API اضافه کنید:
> ```bash
> curl -sk -u "admin:PASSWORD" -X PUT \
> "https://git.se.kbs1.ir/api/v1/repos/admin/REPO/actions/secrets/SERVER_PASSWORD" \
> -H "Content-Type: application/json" -d '{"data":"PASSWORD"}'
> ```
---
## 🔧 dockerd فلگ‌های نهایی
```bash
dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs &
```
| Flag | دلیل |
|------|-------|
| `--iptables=false` | K3s اجازه تغییر iptables نمیده |
| `--ip6tables=false` | مشابه بالا برای IPv6 |
| `--bridge=none` | نیازی به Docker bridge network نیست |
| `--storage-driver=vfs` | overlay2 نمیتونه mount کنه داخل K3s |
---
## 🔍 عیب‌یابی Pipeline
### ۱. چک وضعیت Runner:
```bash
# SSH به سرور
ssh root@194.5.195.53
# آیا runner pod بالاست؟
kubectl get pods -l app=gitea-runner
# لاگ runner
kubectl logs <pod-name> -c runner --tail=30
# لاگ DinD
kubectl logs <pod-name> -c docker --tail=30
```
### ۲. تست Docker داخل Runner:
```bash
# exec به DinD container
kubectl exec <pod-name> -c docker -- docker info
# آیا registry قابل دسترسیه؟
kubectl exec <pod-name> -c docker -- docker pull 194.5.195.53:32082/dotnet/sdk:9.0
```
### ۳. چک config runner:
```bash
# آیا config.yaml mount شده؟
kubectl exec <pod-name> -c runner -- cat /data/config.yaml
# آیا privileged فعاله؟
kubectl exec <pod-name> -c docker -- docker inspect <job-container> \
--format '{{.HostConfig.Privileged}} {{.HostConfig.SecurityOpt}}'
```
### ۴. ری‌استارت runner:
```bash
kubectl rollout restart deployment/gitea-runner
kubectl rollout status deployment/gitea-runner --timeout=120s
```
---
## 📋 Workflow Template (کامل)
```yaml
name: Build and Deploy to Kubernetes
on:
push:
branches:
- kub-stage
env:
REGISTRY: 194.5.195.53:30080
IMAGE_NAME: admin/<service-name>
K8S_SERVER: 194.5.195.53
jobs:
build-and-deploy:
runs-on: ubuntu-latest
container:
image: 194.5.195.53:32082/docker-sshpass:latest
options: --privileged
steps:
- name: Start Docker daemon
run: |
mkdir -p /etc/docker
cat > /etc/docker/daemon.json << 'DAEMON'
{
"insecure-registries": ["194.5.195.53:30080", "194.5.195.53:32500", "194.5.195.53:32082"]
}
DAEMON
dockerd --iptables=false --ip6tables=false --bridge=none --storage-driver=vfs &
for i in $(seq 1 90); do
if docker info >/dev/null 2>&1; then
echo "✅ Docker ready"; break
fi
sleep 2
done
- name: Checkout code
run: |
git clone --depth 1 --branch kub-stage http://gitea-svc:3000/admin/<repo>.git .
- name: Login to Docker registries
run: |
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login 194.5.195.53:32082 -u admin --password-stdin
echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login ${{ env.REGISTRY }} -u admin --password-stdin
- name: Build Docker Image
run: |
DOCKER_BUILDKIT=0 docker build --network host -t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest .
- name: Push to Registry
run: |
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:latest
- name: Deploy to Kubernetes
run: |
export SSHPASS="${{ secrets.SERVER_PASSWORD }}"
sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} "
kubectl rollout restart deployment/<service>
kubectl rollout status deployment/<service> --timeout=180s
"
```
---
## 🐛 باگ بحرانی: Cross-Deployment — Push به Production ری‌دیپلوی Staging (اسفند ۱۴۰۴)
### علائم:
- Push به برنچ `production` → هم production و هم staging ری‌دیپلوی شدند
- CMS staging pod بعد از push ریستارت شد
- Runner log: **۲ تسک CMS** بجای ۱ تسک اجرا شد
### علت ریشه‌ای:
Gitea Act Runner **تمام فایل‌های workflow** داخل `.gitea/workflows/` برنچ push شده رو اجرا می‌کنه — حتی اگه `on.push.branches` برنچ دیگه‌ای باشه. وقتی production push شد، `kub-deploy.yml` (trigger: `kub-stage`) هم اجرا شد و ایمیج `admin/cms:latest` رو با کد production ساخت → staging از `latest` pull کرد → **staging با DB production بالا اومد!**
### راه‌حل:
حذف workflow‌های staging از برنچ production (هر ۳ ریپو):
```bash
git rm .gitea/workflows/kub-deploy.yml .gitea/workflows/cms-stage.yml # CMS
git rm .gitea/workflows/fo-stage.yml .gitea/workflows/kub-deploy.yml # FrontOffice
git rm .gitea/workflows/bo-stage.yml .gitea/workflows/kub-deploy.yml # BackOffice
```
> ⚠️ **قانون طلایی:** هر برنچ فقط workflow مربوط به خودش رو داشته باشه.
---
## 🐛 مشکل ۱۳: Production deploy ایمیج pull نمی‌شد
**علت:** Production K8s از `git.foursat.afrino.co/admin/cms:prod` pull می‌کرد، ولی CI ایمیج رو به `194.5.195.53:30080` push می‌کرد.
**راه‌حل:**
1. اضافه کردن `194.5.195.53:30080` به `/etc/rancher/k3s/registries.yaml` پروداکشن + ری‌استارت K3s
2. آپدیت deployment image: `kubectl set image deployment/cms cms=194.5.195.53:30080/admin/cms:prod`
3. فیکس `prod-deploy.yml`: `K8S_SSH_PASSWORD``SERVER_PASSWORD`, `rollout restart``set image :sha`
---
## 🔄 Production Workflow Template (فعلی)
```yaml
name: Build and Deploy to Production
on:
push:
branches: [production]
env:
REGISTRY: 194.5.195.53:30080
IMAGE_NAME: admin/<service>
K8S_SERVER: 45.149.79.127
jobs:
build-and-deploy:
runs-on: ubuntu-latest
container:
image: 194.5.195.53:32082/docker-sshpass:latest
options: --privileged
steps:
# ... (Start Docker, Checkout, Login — مشابه staging)
- name: Build Docker Image
run: |
DOCKER_BUILDKIT=0 docker build --network host \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }} \
-t ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod .
- name: Push to Registry
run: |
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:prod
- name: Deploy to Production
run: |
export SSHPASS="${{ secrets.SERVER_PASSWORD }}"
sshpass -e ssh -o StrictHostKeyChecking=no root@${{ env.K8S_SERVER }} "
kubectl set image deployment/<svc> <svc>=${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
kubectl rollout status deployment/<svc> --timeout=300s
"
```
> تفاوت staging vs production: staging = tag `latest` + `rollout restart` | production = tag `sha` + `set image`
---
## 🏗️ مشخصات دو محیط
| | Staging | Production |
|--|---------|------------|
| **سرور** | `194.5.195.53` | `45.149.79.127` |
| **DB** | `mssql-svc@Foursat` | `45.149.79.127,31433@KBS` |
| **Registry** | `194.5.195.53:30080` (local) | همان staging registry |
| **Image Tags** | `:latest` | `:prod` + `:sha` |
| **Branch** | `kub-stage` | `production` |
| **Domains** | `*.se.kbs1.ir` | `*.kbs1.ir` |
---
## 🐛 مشکل ۱۴: K8S_SERVER اشتباه در prod-deploy.yml (CMS + BackOffice)
**تاریخ:** February 17, 2026
**علت:** `K8S_SERVER` در `prod-deploy.yml` CMS و BackOffice هنوز `194.5.195.53` (staging) بود بجای `45.149.79.127` (production).
**عارضه:** `kubectl set image` به سرور staging ارسال می‌شد — deployment production آپدیت نمی‌شد.
**فایل‌های فیکس شده:**
- `CMS/.gitea/workflows/prod-deploy.yml``K8S_SERVER: 194.5.195.53``45.149.79.127`
- `BackOffice/.gitea/workflows/prod-deploy.yml``K8S_SERVER: 194.5.195.53``45.149.79.127`
- FrontOffice قبلاً درست بود ✅
**فیکس اضافی:** هر دو workflow از `kubectl rollout restart` به `kubectl set image` تغییر کردن تا ایمیج SHA-tagged واقعاً set بشه.
---
## 🐛 مشکل ۱۵: نبود appsettings.Production.json — URLهای staging روی production
**تاریخ:** February 17, 2026
**علت:** هیچکدوم از ۳ پروژه `appsettings.Production.json` نداشتن. از طرفی `ASPNETCORE_ENVIRONMENT=Production` ست بود → fallback به `appsettings.json` (که URLهای staging داشت).
**عارضه‌ها:**
- CMS: `CmsBaseUrl=cms.se.kbs1.ir` → ZarinPal callback به staging برمی‌گشت
- CMS: `FrontOfficeBaseUrl=foursat.se.kbs1.ir` → redirect بعد از پرداخت به staging می‌رفت
- FrontOffice: `GwUrl=localhost:32846` → gRPC به هیچ‌جا وصل نمی‌شد
- BackOffice: `GwUrl=localhost:32847` → gRPC به هیچ‌جا وصل نمی‌شد
**فایل‌های ساخته شده:**
| پروژه | فایل | محتوای کلیدی |
|--------|------|-------------|
| CMS | `src/CMSMicroservice.WebApi/appsettings.Production.json` | `CmsBaseUrl=cms.kbs1.ir`, `FrontOfficeBaseUrl=kbs1.ir`, `DB=KBS`, `ZarinPal.UseSandbox=false` |
| FrontOffice | `src/FrontOffice.Main/appsettings.Production.json` | `GwUrl=cms.kbs1.ir` |
| BackOffice | `src/BackOffice/wwwroot/appsettings.Production.json` | `GwUrl=cms.kbs1.ir` |
> ⚠️ **نکته:** CMS فایل `appsettings.Production.json` در `.gitignore` هست (`**/ appsettings.Production.json`). با `git add -f` ترک شد. بعد از هر تغییر باید دوباره force add بشه.
---
## 🐛 مشکل ۱۶: nginx image path اشتباه در BackOffice Dockerfile (production branch)
**تاریخ:** February 17, 2026
**علت:** Dockerfile روی برنچ `production` از `194.5.195.53:32082/library/nginx:alpine` استفاده می‌کرد که در رجیستری وجود نداشت. روی `kub-stage` قبلاً فیکس شده بود ولی merge به production این خط رو override کرده بود.
**ارور CI:**
```
Step 9/14 : FROM 194.5.195.53:32082/library/nginx:alpine AS final
manifest for 194.5.195.53:32082/library/nginx:alpine not found: manifest unknown
```
**رفع:**
```dockerfile
# قبل (اشتباه)
FROM 194.5.195.53:32082/library/nginx:alpine AS final
# بعد (صحیح)
FROM 194.5.195.53:32082/nginx:alpine AS final
```
**نکته:** این فیکس مستقیماً روی برنچ `production` انجام و push شد (کامیت `743403e`).