feat: Add documentation for Admin/Customer separation fix and CI/CD pipeline guide
This commit is contained in:
@@ -25,6 +25,7 @@
|
|||||||
| [REMAINING-TASKS.md](cms/REMAINING-TASKS.md) | وضعیت ۴۹/۴۹ متد — همه انجام شده ✅ |
|
| [REMAINING-TASKS.md](cms/REMAINING-TASKS.md) | وضعیت ۴۹/۴۹ متد — همه انجام شده ✅ |
|
||||||
| [FRONTOFFICE-CMS-API-COMPATIBILITY.md](cms/FRONTOFFICE-CMS-API-COMPATIBILITY.md) | ماتریس سازگاری API بین FrontOffice و CMS |
|
| [FRONTOFFICE-CMS-API-COMPATIBILITY.md](cms/FRONTOFFICE-CMS-API-COMPATIBILITY.md) | ماتریس سازگاری API بین FrontOffice و CMS |
|
||||||
| [ICURRENTUSERSERVICE-IMPLEMENTATION.md](cms/ICURRENTUSERSERVICE-IMPLEMENTATION.md) | پترن JWT + ICurrentUserService در endpointهای Customer |
|
| [ICURRENTUSERSERVICE-IMPLEMENTATION.md](cms/ICURRENTUSERSERVICE-IMPLEMENTATION.md) | پترن JWT + ICurrentUserService در endpointهای Customer |
|
||||||
|
| [ADMIN-CUSTOMER-SEPARATION-FIX.md](cms/ADMIN-CUSTOMER-SEPARATION-FIX.md) | 🆕 فیکس جداسازی Admin/Customer: حذف JWT fallback از ۸ handler + resolve صریح در ۴ endpoint |
|
||||||
| [payment-gateway.md](cms/payment-gateway.md) | IPaymentGatewayService: Mock vs Daya، پیادهسازی payout |
|
| [payment-gateway.md](cms/payment-gateway.md) | IPaymentGatewayService: Mock vs Daya، پیادهسازی payout |
|
||||||
| [payment-architecture-pyms.md](cms/payment-architecture-pyms.md) | معماری PYMS: جریان پرداخت BFF→PYMS→Gateway→CMS |
|
| [payment-architecture-pyms.md](cms/payment-architecture-pyms.md) | معماری PYMS: جریان پرداخت BFF→PYMS→Gateway→CMS |
|
||||||
| [chatika-integration.md](cms/chatika-integration.md) | یکپارچهسازی Chatika AI: Hangfire worker، retry logic |
|
| [chatika-integration.md](cms/chatika-integration.md) | یکپارچهسازی Chatika AI: Hangfire worker، retry logic |
|
||||||
@@ -40,6 +41,7 @@
|
|||||||
| فایل | توضیح |
|
| فایل | توضیح |
|
||||||
|------|-------|
|
|------|-------|
|
||||||
| [OFFLINE-DEPLOYMENT-GUIDE.md](deployment/OFFLINE-DEPLOYMENT-GUIDE.md) | راهنمای جامع استقرار آفلاین + تنظیمات Nexus |
|
| [OFFLINE-DEPLOYMENT-GUIDE.md](deployment/OFFLINE-DEPLOYMENT-GUIDE.md) | راهنمای جامع استقرار آفلاین + تنظیمات Nexus |
|
||||||
|
| [CICD-PIPELINE-GUIDE.md](deployment/CICD-PIPELINE-GUIDE.md) | 🆕 راهنمای CI/CD Pipeline: معماری DinD، فیکسهای dockerd، Runner ConfigMap، عیبیابی |
|
||||||
| [INFRASTRUCTURE-GUIDE.md](deployment/INFRASTRUCTURE-GUIDE.md) | مشخصات سرور، DB credentials، Gitea، وضعیت استقرار |
|
| [INFRASTRUCTURE-GUIDE.md](deployment/INFRASTRUCTURE-GUIDE.md) | مشخصات سرور، DB credentials، Gitea، وضعیت استقرار |
|
||||||
| [SERVER-MIRRORS-CONFIG.md](deployment/SERVER-MIRRORS-CONFIG.md) | تنظیمات mirror: K3s registries.yaml، containerd |
|
| [SERVER-MIRRORS-CONFIG.md](deployment/SERVER-MIRRORS-CONFIG.md) | تنظیمات mirror: K3s registries.yaml، containerd |
|
||||||
| [INGRESS-NGINX-WARNING.md](deployment/INGRESS-NGINX-WARNING.md) | ⚠️ هشدار K3s: مشکل hostNetwork در ingress-nginx |
|
| [INGRESS-NGINX-WARNING.md](deployment/INGRESS-NGINX-WARNING.md) | ⚠️ هشدار K3s: مشکل hostNetwork در ingress-nginx |
|
||||||
@@ -66,4 +68,5 @@
|
|||||||
| فاز ۲ (حذف obsolete عمیق) | 41 | ۹۴ |
|
| فاز ۲ (حذف obsolete عمیق) | 41 | ۹۴ |
|
||||||
| فاز ۳ (ساختاردهی + merge) | **28** | ۱۳ |
|
| فاز ۳ (ساختاردهی + merge) | **28** | ۱۳ |
|
||||||
| cleanup نهایی (+2 فایل جدید) | **30** | — |
|
| cleanup نهایی (+2 فایل جدید) | **30** | — |
|
||||||
| **نهایی** | **30 + INDEX** | **۱۹۱ فایل حذف/ادغام** |
|
| session CI/CD + Admin fix (+2) | **32** | — |
|
||||||
|
| **نهایی** | **32 + INDEX** | **۱۹۱ فایل حذف/ادغام** |
|
||||||
|
|||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# 🔀 جداسازی سرویسهای Admin و Customer
|
||||||
|
|
||||||
|
> آخرین بروزرسانی: February 10, 2026
|
||||||
|
> مرتبط با: [ICURRENTUSERSERVICE-IMPLEMENTATION.md](ICURRENTUSERSERVICE-IMPLEMENTATION.md)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🐛 مشکل
|
||||||
|
|
||||||
|
پنل ادمین BackOffice بجای نمایش اطلاعات **همه کاربران**، فقط اطلاعات **خود ادمین** رو نشان میداد.
|
||||||
|
|
||||||
|
### علت ریشهای:
|
||||||
|
Query Handler ها وقتی `UserId = 0` دریافت میکردند، بجای اینکه "همه کاربران" رو برگردانند، به JWT fallback میکردند و UserId ادمین رو از توکن استخراج میکردند:
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
// ❌ الگوی قدیمی (مشکلدار)
|
||||||
|
var userId = request.UserId == 0
|
||||||
|
? (long.TryParse(_currentUser.UserId, out var uid) ? uid : 0) // ← fallback به JWT
|
||||||
|
: request.UserId;
|
||||||
|
```
|
||||||
|
|
||||||
|
### مشکل:
|
||||||
|
- **BackOffice (Admin)** → `UserId = 0` ارسال میکنه → Handler از JWT ادمین میخونه → فقط اطلاعات ادمین برمیگرده
|
||||||
|
- **FrontOffice (Customer)** → `UserId = 0` ارسال میکنه → Handler از JWT مشتری میخونه → اتفاقاً درسته، ولی دلیلش اشتباهه
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ✅ الگوی جدید
|
||||||
|
|
||||||
|
### اصل طراحی:
|
||||||
|
> **Handler ها بیخبر از JWT هستند.** وظیفه resolve کردن کاربر، به عهده **Service Layer (gRPC endpoint)** است.
|
||||||
|
|
||||||
|
### الگوی Handler:
|
||||||
|
```csharp
|
||||||
|
// ✅ الگوی جدید
|
||||||
|
// UserId = 0 → بدون فیلتر (نمایش همه) — مناسب Admin
|
||||||
|
// UserId > 0 → فیلتر بر اساس کاربر خاص — مناسب Customer یا Admin
|
||||||
|
|
||||||
|
public async Task<Result> Handle(SomeQuery request, CancellationToken ct)
|
||||||
|
{
|
||||||
|
var userId = request.UserId;
|
||||||
|
|
||||||
|
var query = _context.SomeEntity.AsNoTracking();
|
||||||
|
|
||||||
|
if (userId > 0)
|
||||||
|
query = query.Where(x => x.UserId == userId);
|
||||||
|
|
||||||
|
// userId == 0 → no filter → return all
|
||||||
|
return await query.ToListAsync(ct);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### الگوی Customer Service (JWT رو خودش resolve میکنه):
|
||||||
|
```csharp
|
||||||
|
// ✅ Customer endpoint → حتماً JWT resolve میکنه
|
||||||
|
public override async Task<Response> GetMyData(Request request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
if (!long.TryParse(_currentUserService.UserId, out var userId) || userId == 0)
|
||||||
|
throw new RpcException(new Status(StatusCode.Unauthenticated, "User not authenticated"));
|
||||||
|
|
||||||
|
var query = new GetDataQuery { UserId = userId }; // ← userId صریح
|
||||||
|
var result = await _sender.Send(query, context.CancellationToken);
|
||||||
|
return MapToResponse(result);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### الگوی Admin Service (UserId رو از request میگیره):
|
||||||
|
```csharp
|
||||||
|
// ✅ Admin endpoint → UserId از request (0 = همه)
|
||||||
|
public override async Task<Response> GetAllData(Request request, ServerCallContext context)
|
||||||
|
{
|
||||||
|
// request.UserId = 0 → handler همه رو برمیگردونه
|
||||||
|
// request.UserId > 0 → handler فیلتر میکنه
|
||||||
|
var result = await _dispatcher.Send(request, context);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📝 لیست تغییرات
|
||||||
|
|
||||||
|
### 🔧 ۸ Query Handler اصلاحشده:
|
||||||
|
|
||||||
|
| # | Handler | تغییر | رفتار `UserId = 0` |
|
||||||
|
|---|---------|-------|---------------------|
|
||||||
|
| 1 | `GetCustomerOrdersQueryHandler` | حذف `ICurrentUserService` + JWT fallback | بدون فیلتر → همه سفارشات |
|
||||||
|
| 2 | `GetCustomerOrderQueryHandler` | حذف `ICurrentUserService` + JWT fallback | هر سفارشی با OrderId |
|
||||||
|
| 3 | `GetUserWeeklyBalancesQueryHandler` | حذف `ICurrentUserService` + JWT fallback | بدون فیلتر → همه تعادلها |
|
||||||
|
| 4 | `GetUserCommissionPayoutsQueryHandler` | حذف `ICurrentUserService` + JWT fallback | بدون فیلتر → همه پرداختها |
|
||||||
|
| 5 | `GetNetworkStatisticsQueryHandler` | حذف `ICurrentUserService` + JWT fallback | آمار root user (کل شبکه) |
|
||||||
|
| 6 | `GetNetworkTreeQueryHandler` | حذف JWT fallback + خطا اگر UserId نباشد | `ArgumentException` (الزامی) |
|
||||||
|
| 7 | `GetUserQueryHandler` | حذف JWT fallback + خطا اگر UserId نباشد | `ArgumentException` (الزامی) |
|
||||||
|
| 8 | `GetUserWalletQueryHandler` | حذف JWT fallback + خطا اگر UserId نباشد | `ArgumentException` (الزامی) |
|
||||||
|
|
||||||
|
### 🌐 ۴ Customer Service Endpoint اصلاحشده:
|
||||||
|
|
||||||
|
| # | Service / Method | تغییر |
|
||||||
|
|---|-----------------|-------|
|
||||||
|
| 1 | `UserOrderService.GetCustomerOrders` | JWT resolve → ارسال `customerUserId` به handler |
|
||||||
|
| 2 | `UserOrderService.GetCustomerOrder` | JWT resolve → ارسال `customerUserId` به handler |
|
||||||
|
| 3 | `NetworkMembershipService.GetMyNetworkStatistics` | افزودن `ICurrentUserService` + JWT resolve |
|
||||||
|
| 4 | `UserWalletService.GetCustomerWallet` | تغییر از `Id = 0` به `Id = userId` (از JWT) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📐 دیاگرام جریان
|
||||||
|
|
||||||
|
### درخواست Admin (BackOffice):
|
||||||
|
```
|
||||||
|
BackOffice Panel → gRPC (UserId=0) → Admin Service → Handler (UserId=0 → no filter → ALL users) ✅
|
||||||
|
BackOffice Panel → gRPC (UserId=42) → Admin Service → Handler (UserId=42 → filter → one user) ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
### درخواست Customer (FrontOffice):
|
||||||
|
```
|
||||||
|
FrontOffice App → gRPC → Customer Service → JWT resolve (UserId=42) → Handler (UserId=42 → filter) ✅
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ نکات مهم
|
||||||
|
|
||||||
|
1. **Handler ها هرگز `ICurrentUserService` رو inject نمیکنند** (بعد از این فیکس)
|
||||||
|
2. فقط **Customer Service endpoints** مسئول JWT resolve هستند
|
||||||
|
3. **Admin endpoints** از `IDispatchRequestToCQRS` استفاده میکنند و UserId مستقیم از proto request میاد
|
||||||
|
4. Handler هایی که UserId **الزامی** دارند (مثل GetUser, GetUserWallet, GetNetworkTree) → `ArgumentException` پرتاب میکنند
|
||||||
|
5. Handler هایی که لیست برمیگردونند (مثل GetCustomerOrders, GetWeeklyBalances) → `UserId = 0` یعنی "بدون فیلتر"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔗 فایلهای تغییریافته
|
||||||
|
|
||||||
|
### Application Layer:
|
||||||
|
```
|
||||||
|
CMS/src/CMSMicroservice.Application/
|
||||||
|
├── OrdersCQ/Queries/GetCustomerOrders/GetCustomerOrdersQueryHandler.cs
|
||||||
|
├── OrdersCQ/Queries/GetCustomerOrder/GetCustomerOrderQueryHandler.cs
|
||||||
|
├── UserWeeklyBalanceCQ/Queries/GetUserWeeklyBalances/GetUserWeeklyBalancesQueryHandler.cs
|
||||||
|
├── CommissionPayoutCQ/Queries/GetUserCommissionPayouts/GetUserCommissionPayoutsQueryHandler.cs
|
||||||
|
├── NetworkStatisticsCQ/Queries/GetNetworkStatistics/GetNetworkStatisticsQueryHandler.cs
|
||||||
|
├── NetworkTreeCQ/Queries/GetNetworkTree/GetNetworkTreeQueryHandler.cs
|
||||||
|
├── UserCQ/Queries/GetUser/GetUserQueryHandler.cs
|
||||||
|
└── UserWalletCQ/Queries/GetUserWallet/GetUserWalletQueryHandler.cs
|
||||||
|
```
|
||||||
|
|
||||||
|
### WebApi Layer:
|
||||||
|
```
|
||||||
|
CMS/src/CMSMicroservice.WebApi/Services/
|
||||||
|
├── UserOrderService.cs (GetCustomerOrders + GetCustomerOrder)
|
||||||
|
├── NetworkMembershipService.cs (GetMyNetworkStatistics)
|
||||||
|
└── UserWalletService.cs (GetCustomerWallet)
|
||||||
|
```
|
||||||
@@ -0,0 +1,418 @@
|
|||||||
|
# 🔧 راهنمای CI/CD Pipeline — Gitea Actions + K3s
|
||||||
|
|
||||||
|
> آخرین بروزرسانی: February 11, 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 بدون مشکل استفاده میکنه.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔄 تغییرات 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) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 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
|
||||||
|
"
|
||||||
|
```
|
||||||
Reference in New Issue
Block a user