diff --git a/technical/TECH-03-DEPLOYMENT-INFRA.md b/technical/TECH-03-DEPLOYMENT-INFRA.md
index a0f3d90..887fea8 100644
--- a/technical/TECH-03-DEPLOYMENT-INFRA.md
+++ b/technical/TECH-03-DEPLOYMENT-INFRA.md
@@ -1,7 +1,7 @@
# 🚀 استقرار، CI/CD و زیرساخت
> **منابع ادغامشده:** `CICD-PIPELINE-GUIDE.md`, `DEPLOYMENT-README.md`, `INFRASTRUCTURE-GUIDE.md`, `INGRESS-NGINX-WARNING.md`, `OFFLINE-DEPLOYMENT-GUIDE.md`, `SERVER-MIRRORS-CONFIG.md`
-> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: PersistentVolume برای آپلود فایل + اصلاح namespace + حذف secretRef)
+> **آخرین بروزرسانی:** اسفند ۱۴۰۴ (بروزرسانی: PVC آپلود + K8s Secret برای config دائمی + جدا کردن appsettings هر برنچ)
---
@@ -83,9 +83,11 @@ ENTRYPOINT ["dotnet", "CMSMicroservice.dll"]
CMS/
k8s/
staging/
- cms-deployment.yaml ← PVC + Deployment + Service + Ingress
+ cms-config.yaml ← K8s Secret (appsettings.Staging.json)
+ cms-deployment.yaml ← PVC + Deployment + Service + Ingress
production/
- cms-deployment.yaml ← PVC + Deployment + Service + Ingress
+ cms-config.yaml ← K8s Secret (appsettings.Production.json)
+ cms-deployment.yaml ← PVC + Deployment + Service + Ingress
```
> ⚠️ **هر دو محیط از namespace `default` استفاده میکنن.**
@@ -131,30 +133,60 @@ volumes:
> 💡 **نکته مهم:** چون `ReadWriteOnce` هست، فقط **1 replica** میتونه بنویسه. برای 2+ replica نیاز به NFS/CephFS با `ReadWriteMany` هست.
-### ۳.۳ تنظیمات محیطی (Environment Variables)
+### ۳.۳ تنظیمات محیطی (K8s Secret)
-تنظیمات حساس (ConnectionString, Email, SMS, ZarinPal) **داخل `appsettings.{Environment}.json`** در ایمیج Docker قرار دارن.
-**هیچ K8s Secret استفاده نمیشه** — .NET خودش فایل config مربوط به environment رو میخونه.
+تنظیمات حساس (ConnectionString, Email, SMS, ZarinPal) **در K8s Secret** نگهداری میشن — نه داخل Docker image.
+فایل `appsettings.{Environment}.json` از Secret به `/app/` مونت میشه و .NET اون رو override میخونه.
-| محیط | `ASPNETCORE_ENVIRONMENT` | فایل Config |
+```mermaid
+flowchart LR
+ S["K8s Secret
cms-appsettings"] -->|volumeMount| F["/app/appsettings.*.json"]
+ F --> D[".NET reads config"]
+ I["Docker Image
appsettings.json (base)"] --> D
+```
+
+| محیط | `ASPNETCORE_ENVIRONMENT` | فایل Config (از Secret) |
|------|---------------------------|-------------|
| **Staging** | `Staging` | `appsettings.Staging.json` |
| **Production** | `Production` | `appsettings.Production.json` |
-env varهای K8s manifest:
+**Secret manifest** (`cms-config.yaml`):
+```yaml
+apiVersion: v1
+kind: Secret
+metadata:
+ name: cms-appsettings
+ namespace: default
+type: Opaque
+stringData:
+ appsettings.Staging.json: | # یا appsettings.Production.json
+ { "ConnectionStrings": { ... }, "ZarinPal": { ... }, ... }
+```
+**Volume mount در Deployment:**
+```yaml
+volumeMounts:
+- name: cms-config
+ mountPath: /app/appsettings.Staging.json
+ subPath: appsettings.Staging.json
+ readOnly: true
+volumes:
+- name: cms-config
+ secret:
+ secretName: cms-appsettings
+```
+
+env varهای K8s manifest (فقط environment و URL):
```yaml
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Staging" # یا "Production"
- name: ASPNETCORE_URLS
value: "http://+:8080"
-- name: Kestrel__EndpointDefaults__Protocols
- value: "Http1AndHttp2"
-- name: FileStorage__UploadPath
- value: "/app/Uploads"
```
+> 💡 **تغییر config بدون deploy:** `kubectl edit secret cms-appsettings && kubectl rollout restart deployment/cms`
+
### ۳.۴ مثال Deployment (واقعی)
```yaml
@@ -179,11 +211,15 @@ spec:
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Staging"
- - name: FileStorage__UploadPath
- value: "/app/Uploads"
+ - name: ASPNETCORE_URLS
+ value: "http://+:8080"
volumeMounts:
- name: cms-uploads
mountPath: /app/Uploads
+ - name: cms-config
+ mountPath: /app/appsettings.Staging.json
+ subPath: appsettings.Staging.json
+ readOnly: true
resources:
requests: { memory: "512Mi", cpu: "500m" }
limits: { memory: "1Gi", cpu: "1000m" }
@@ -191,6 +227,9 @@ spec:
- name: cms-uploads
persistentVolumeClaim:
claimName: cms-uploads-pvc
+ - name: cms-config
+ secret:
+ secretName: cms-appsettings
```
### ۳.۵ Ingress
@@ -217,6 +256,28 @@ spec:
> ⚠️ **هشدار:** از `spec.ingressClassName: nginx` استفاده کنید، نه `kubernetes.io/ingress.class` annotation (deprecated).
+### ۳.۶ جداسازی appsettings در Git
+
+هر برنچ فقط فایل config مربوط به محیط خودش رو داره:
+
+| برنچ | `appsettings.json` | `appsettings.Staging.json` | `appsettings.Production.json` |
+|------|---|---|---|
+| `kub-stage` | ✅ | ✅ | ❌ حذف شده |
+| `production` | ✅ | ❌ حذف شده | ✅ |
+
+**چرا؟** چون config اصلی از K8s Secret میاد (`cms-config.yaml`)، فایلهای محیط دیگه داخل ایمیج اضافی و گمراهکنندهان.
+همچنین وقتی merge/cherry-pick میکنید، فایل config محیط دیگه دیگه conflict ایجاد نمیکنه.
+
+> ⚠️ **کامیتهای حذف فایل config رو هرگز cherry-pick نکنید به برنچ دیگه!**
+> `e72673c` (حذف Production از staging) و `3ebe0f9` (حذف Staging از production)
+
+### ۳.۷ خلاصه: چه چیزهایی دائمی هستند (مستقل از ایمیج)
+
+| چه چیزی | مکانیزم K8s | محل Mount |
+|---------|-------------|------------|
+| **فایلهای آپلود** (عکس، آواتار، ...) | `PersistentVolumeClaim` | `/app/Uploads` |
+| **تنظیمات اپلیکیشن** (DB, SMS, IPG, ...) | `Secret` (`cms-appsettings`) | `/app/appsettings.{Env}.json` |
+
---
## ۴. CI/CD Pipeline
@@ -240,10 +301,11 @@ flowchart TD
C --> D["Pack & Push Proto NuGet"]
D --> E["Docker build → tag :latest"]
E --> F["Push to 194.5.195.53:30080"]
- F --> G["SCP manifest to server"]
- G --> H["kubectl apply -f cms-deployment.yaml"]
- H --> I["kubectl rollout restart"]
- I --> J["✅ Deployed to Staging"]
+ F --> G["SCP cms-config.yaml + cms-deployment.yaml"]
+ G --> H["kubectl apply -f cms-config.yaml (Secret)"]
+ H --> I["kubectl apply -f cms-deployment.yaml"]
+ I --> J["kubectl rollout restart"]
+ J --> K["✅ Deployed to Staging"]
```
### ۴.۳ فلوی Production (`prod-deploy.yml`)
@@ -255,10 +317,11 @@ flowchart TD
C --> D["Pack & Push Proto NuGet"]
D --> E["Docker build → tag :sha + :prod"]
E --> F["Push to 194.5.195.53:30080"]
- F --> G["SCP manifest to server"]
- G --> H["kubectl apply -f cms-deployment.yaml"]
- H --> I["kubectl set image → sha"]
- I --> J["✅ Deployed to Production"]
+ F --> G["SCP cms-config.yaml + cms-deployment.yaml"]
+ G --> H["kubectl apply -f cms-config.yaml (Secret)"]
+ H --> I["kubectl apply -f cms-deployment.yaml"]
+ I --> J["kubectl set image → sha"]
+ J --> K["✅ Deployed to Production"]
```
### ۴.۴ شاخهها و محیطها
@@ -271,10 +334,11 @@ flowchart TD
### ۴.۵ نکات مهم CI/CD
- **Proto NuGet:** هر deploy ابتدا proto packages رو build و به Nexus push میکنه
-- **Manifest apply:** پایپلاین مانیفست K8s رو SCP به سرور و `kubectl apply` میزنه
- → PVC، Deployment، Service و Ingress هر بار اعمال میشه
+- **Manifest apply:** پایپلاین ابتدا `cms-config.yaml` (Secret) رو apply میکنه، بعد `cms-deployment.yaml`
+ → Secret + PVC + Deployment + Service + Ingress هر بار اعمال میشه
- **Image registry:** `194.5.195.53:30080` (داخلی Nexus) — نه `git.se.kbs1.ir`
-- **appsettings حفاظت:** `.gitattributes` با `merge=ours` مانع overwrite شدن `appsettings.Production.json` موقع merge میشه
+- **Config دائمی:** تنظیمات در K8s Secret هست، نه داخل Docker image — تغییر config بدون rebuild ایمیج ممکنه
+- **جداسازی برنچ:** هر برنچ فقط appsettings محیط خودش رو داره (بخش ۳.۶)
---
@@ -406,8 +470,12 @@ flowchart TD
| `2d6c95e` | fix: use local registry 194.5.195.53:30080 instead of git.se.kbs1.ir |
| `f8dc4ab` | fix: staging ASPNETCORE_ENVIRONMENT=Staging, remove secretKeyRef |
| `de83c31` | fix: production uses namespace default + remove foursat namespace references |
+| `9288d06` | feat: externalize appsettings to K8s Secret — config persists independently |
+| `e72673c` | chore(staging): remove appsettings.Production.json (فقط kub-stage) |
+| `3ebe0f9` | chore(production): remove appsettings.Staging.json (فقط production) |
-> همه کامیتها به هر دو شاخه `kub-stage` و `production` push شدهاند.
+> کامیتهای PVC و Secret به هر دو شاخه push شدهاند.
+> ⚠️ کامیتهای حذف appsettings فقط به برنچ مربوطه push شده — cherry-pick نکنید!
**تنظیمات محیطی Production (`appsettings.Production.json`):**