docs: update TECH-03 — K8s Secret for persistent config, branch/appsettings separation, updated CI/CD flow

This commit is contained in:
masoodafar-web
2026-02-23 22:13:15 +03:30
parent 52e6e1530c
commit 0115142faf
+94 -26
View File
@@ -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<br/>cms-appsettings"] -->|volumeMount| F["/app/appsettings.*.json"]
F --> D[".NET reads config"]
I["Docker Image<br/>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`):**