# 🚀 استقرار، 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)
---
## ۱. سرورها
| سرور | IP | نقش | منابع |
|------|-----|------|--------|
| **Staging** | 194.5.195.53 | توسعه + تست | 4 CPU, 8GB RAM |
| **Production** | 45.149.79.127 | محیط نهایی | 4 CPU, 16GB RAM |
| **Git** | git.se.kbs1.ir | Gitea (مخازن کد) | — |
| **Registry** | داخلی | Docker Registry / Nexus | — |
---
## ۲. Docker و Container
### ۲.۱ سرویسها
```yaml
# docker-compose.yml (production)
services:
cms:
image: foursat/cms:latest
ports: ["5001:5001"] # gRPC
environment:
- ConnectionStrings__Default=Server=db;Database=FourSatCMS
- ASPNETCORE_ENVIRONMENT=Production
depends_on: [db]
backoffice:
image: foursat/backoffice:latest
ports: ["5002:80"] # Static Blazor WASM
frontoffice:
image: foursat/frontoffice:latest
ports: ["5003:5003"] # Blazor Server
db:
image: mcr.microsoft.com/mssql/server:2022-CU16-ubuntu-22.04
ports: ["1433:1433"]
volumes: ["sqldata:/var/opt/mssql"]
nexus: # NuGet + Docker registry
image: sonatype/nexus3
ports: ["8081:8081"]
volumes:
sqldata:
```
### ۲.۲ Dockerfile (CMS)
```dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:9.0 AS base
WORKDIR /app
EXPOSE 5001
FROM mcr.microsoft.com/dotnet/sdk:9.0 AS build
WORKDIR /src
COPY ["CMSMicroservice/CMSMicroservice.csproj", "CMSMicroservice/"]
RUN dotnet restore
COPY . .
RUN dotnet publish -c Release -o /app/publish
FROM base AS final
COPY --from=build /app/publish .
ENTRYPOINT ["dotnet", "CMSMicroservice.dll"]
```
---
## ۳. Kubernetes
### ۳.۱ Manifests ساختار
مانیفستهای K8s **داخل ریپوی CMS** نگهداری میشن و توسط CI/CD اعمال میشن:
```
CMS/
k8s/
staging/
cms-deployment.yaml ← PVC + Deployment + Service + Ingress
production/
cms-deployment.yaml ← PVC + Deployment + Service + Ingress
```
> ⚠️ **هر دو محیط از namespace `default` استفاده میکنن.**
### ۳.۲ PersistentVolume برای آپلود فایل
فایلهای آپلودشده (عکس محصولات، بلاگ، آواتار و ...) در `/app/Uploads` ذخیره میشن.
برای جلوگیری از حذف فایلها با ریستارت Pod، یک **PersistentVolumeClaim** مونت شده:
```yaml
# PVC — 20Gi ذخیرهسازی دائمی
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: cms-uploads-pvc
namespace: default
spec:
accessModes: [ReadWriteOnce]
resources:
requests:
storage: 20Gi
```
```yaml
# Volume Mount در Deployment
volumeMounts:
- name: cms-uploads
mountPath: /app/Uploads
volumes:
- name: cms-uploads
persistentVolumeClaim:
claimName: cms-uploads-pvc
```
| تنظیم | مقدار |
|--------|-------|
| **PVC Name** | `cms-uploads-pvc` |
| **Mount Path** | `/app/Uploads` |
| **Access Mode** | `ReadWriteOnce` |
| **حجم** | `20Gi` |
| **StorageClass** | `local-path` (K3s default) |
| **Replicas** | `1` (محدودیت RWO) |
> 💡 **نکته مهم:** چون `ReadWriteOnce` هست، فقط **1 replica** میتونه بنویسه. برای 2+ replica نیاز به NFS/CephFS با `ReadWriteMany` هست.
### ۳.۳ تنظیمات محیطی (Environment Variables)
تنظیمات حساس (ConnectionString, Email, SMS, ZarinPal) **داخل `appsettings.{Environment}.json`** در ایمیج Docker قرار دارن.
**هیچ K8s Secret استفاده نمیشه** — .NET خودش فایل config مربوط به environment رو میخونه.
| محیط | `ASPNETCORE_ENVIRONMENT` | فایل Config |
|------|---------------------------|-------------|
| **Staging** | `Staging` | `appsettings.Staging.json` |
| **Production** | `Production` | `appsettings.Production.json` |
env varهای K8s manifest:
```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"
```
### ۳.۴ مثال Deployment (واقعی)
```yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: cms
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: cms
template:
spec:
containers:
- name: cms
image: 194.5.195.53:30080/admin/cms:latest
imagePullPolicy: Always
ports:
- containerPort: 8080
env:
- name: ASPNETCORE_ENVIRONMENT
value: "Staging"
- name: FileStorage__UploadPath
value: "/app/Uploads"
volumeMounts:
- name: cms-uploads
mountPath: /app/Uploads
resources:
requests: { memory: "512Mi", cpu: "500m" }
limits: { memory: "1Gi", cpu: "1000m" }
volumes:
- name: cms-uploads
persistentVolumeClaim:
claimName: cms-uploads-pvc
```
### ۳.۵ Ingress
**Staging:**
```yaml
spec:
ingressClassName: nginx
rules:
- host: cms.se.kbs1.ir
```
**Production:**
```yaml
spec:
ingressClassName: nginx
tls:
- hosts: [cms.kbs1.ir, cms.kbs2.ir]
secretName: cms-tls
rules:
- host: cms.kbs2.ir
- host: cms.kbs1.ir
```
> ⚠️ **هشدار:** از `spec.ingressClassName: nginx` استفاده کنید، نه `kubernetes.io/ingress.class` annotation (deprecated).
---
## ۴. CI/CD Pipeline
### ۴.۱ Gitea Actions Workflows (CMS)
فایلهای پایپلاین:
```
CMS/.gitea/workflows/
├── kub-deploy.yml ← Staging (branch: kub-stage)
├── prod-deploy.yml ← Production (branch: production)
└── cms-stage.yml ← قدیمی (IIS روی Windows — غیرفعال)
```
### ۴.۲ فلوی Staging (`kub-deploy.yml`)
```mermaid
flowchart TD
A["Push to kub-stage"] --> B["Start Docker daemon"]
B --> C["Clone repo"]
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"]
```
### ۴.۳ فلوی Production (`prod-deploy.yml`)
```mermaid
flowchart TD
A["Push to production"] --> B["Start Docker daemon"]
B --> C["Clone repo"]
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"]
```
### ۴.۴ شاخهها و محیطها
| شاخه | محیط | سرور | Image Tag | Deploy |
|------|------|------|-----------|--------|
| `kub-stage` | Staging | 194.5.195.53 | `:latest` | Auto |
| `production` | Production | 45.149.79.127 | `:sha` + `:prod` | Auto |
### ۴.۵ نکات مهم CI/CD
- **Proto NuGet:** هر deploy ابتدا proto packages رو build و به Nexus push میکنه
- **Manifest apply:** پایپلاین مانیفست K8s رو SCP به سرور و `kubectl apply` میزنه
→ 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 میشه
---
## ۵. استقرار آفلاین (Offline Deployment)
### ۵.۱ فلوی آمادهسازی
```mermaid
flowchart TD
subgraph ONLINE["🌐 سرور اینترنتدار"]
A1["pull-base-images.sh\nدانلود Docker images"] --> A2["cache-nuget-packages.sh\nدانلود NuGet packages"]
A2 --> A3["save-images.sh\nذخیره تصاویر به tar"]
A3 --> A4["بستهبندی"]
end
A4 -->|"💾 انتقال فیزیکی\nUSB / HDD"| B1
subgraph OFFLINE["🔒 سرور آفلاین"]
B1["load-images.sh\nبارگذاری تصاویر"] --> B2["setup-nexus-complete.sh\nراهاندازی Nexus"]
B2 --> B3["build-all-offline.sh\nبیلد با Nexus محلی"]
B3 --> B4["k8s-deploy.sh\nاستقرار در K8s"]
end
```
### ۵.۲ اسکریپتهای کلیدی
| اسکریپت | کاربرد |
|----------|--------|
| `pull-base-images.sh` | دانلود ۱۵+ Docker image پایه |
| `save-images.sh` | Export به tar (4-8 GB) |
| `load-images.sh` | Import از tar به Docker |
| `cache-nuget-packages.sh` | دانلود NuGet offline |
| `setup-nexus-complete.sh` | راهاندازی NuGet proxy |
| `build-all-offline.sh` | بیلد بدون اینترنت |
| `k8s-deploy.sh` | Deploy تمام سرویسها |
| `k8s-health-check.sh` | بررسی سلامت سرویسها |
---
## ۶. Nexus Repository Manager
### ۶.۱ نقش
```mermaid
graph TD
NEXUS["Nexus داخلی"] --> NP["NuGet proxy\ncache nuget.org"]
NEXUS --> NH["NuGet hosted\nبستههای proto داخلی"]
NEXUS --> DP["Docker proxy\ncache Docker Hub"]
NEXUS --> DH["Docker hosted\nتصاویر داخلی FourSat"]
```
### ۶.۲ NuGet.config
```xml
```
---
## ۷. Mirror و Cache
### ۷.۱ Docker Mirror
```json
// /etc/docker/daemon.json
{
"registry-mirrors": [
"https://mirror.gcr.io",
"https://docker.arvancloud.ir"
],
"insecure-registries": [
"localhost:8082"
]
}
```
### ۷.۲ NuGet Mirror
```
Primary: nuget.org
Fallback: Nexus local proxy
Proto packages: BaGet (internal) at http://localhost:5555
```
---
## ۸. Proto Packages (NuGet)
### ۸.۱ فلوی بستهبندی
```mermaid
flowchart TD
A["CMS/src/Protos/*.proto"] --> B["pack-protos.sh\ndotnet pack → .nupkg"]
B --> C["Push to BaGet / Nexus"]
C --> D["BackOffice + FrontOffice\ndotnet restore → مصرف proto"]
```
### ۸.۲ نام بسته
```xml
```
---
## ۹. مانیتورینگ و Health Check
### ۹.۱ مرج پروداکشن (اسفند ۱۴۰۴)
| ریپو | شاخه مبدأ | commit | نکات |
|------|------------|--------|------|
| **CMS** | `kub-stage` → `production` | `eb1b249` | حل conflict در `appsettings.Production.json` + حذف migration تکراری `u21` |
| **FrontOffice** | `kub-stage` → `production` | `f02d082` | 21 فایل، 400 insertion + فیکس GwUrl به `cms.kbs2.ir` |
| **BackOffice** | `kub-stage` → `production` | `bdea2e8` | 36 فایل، بدون conflict |
### ۹.۲ کامیتهای PVC و اصلاحات K8s (تیر ۱۴۰۴)
| commit | شرح |
|--------|------|
| `3153fd8` | feat: add PersistentVolume for CMS uploads + apply manifests in CI/CD |
| `68da3f4` | fix: staging uses namespace default, not foursat |
| `e41747a` | fix: production ingress — add cms.kbs2.ir, use ingressClassName |
| `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 |
> همه کامیتها به هر دو شاخه `kub-stage` و `production` push شدهاند.
**تنظیمات محیطی Production (`appsettings.Production.json`):**
| تنظیم | مقدار |
|--------|-------|
| `ZarinPal.MerchantId` | `4225d555-5fa9-4df0-9b61-1ce152cbbba8` |
| `ZarinPal.UseSandbox` | `false` |
| `SeedWorkers.MagicWalletCycleSeed.Enabled` | `true` |
| `Kestrel.Endpoints.Grpc.Protocols` | `Http2` |
| `Seq.ServerUrl` | `http://seq-svc:5341` |
| `ConnectionStrings.Default` | `Server=mssql-svc;Database=KBS` |
```bash
# k8s-health-check.sh (namespace = default)
kubectl get pods
kubectl top pods
kubectl logs deployment/cms --tail=50
# بررسی PVC
kubectl get pvc cms-uploads-pvc
kubectl exec deployment/cms -- ls /app/Uploads | wc -l
# تست سرویسها
grpcurl -plaintext localhost:5001 list # لیست سرویسها
grpcurl -plaintext localhost:5001 grpc.health.v1.Health/Check # Health
curl http://localhost:5002/index.html # BackOffice
curl http://localhost:5003/ # FrontOffice
```