Files
docs/deployment/CICD-PIPELINE-GUIDE.md
T
masoodafar-web 4ef4bfbeef docs: بروزرسانی کامل مستندات — باگ‌ها، فیکس‌ها، دیپلوی Production، CI/CD cross-deploy
- payment-gateway.md: سکشن ۸-۱۳ (ZarinPal callback, تخفیف ۱۰۰٪, VAT, ExpirePendingOrders, DeliveryStatus mapping, Production deploy)
- CICD-PIPELINE-GUIDE.md: باگ cross-deploy, قالب workflow Production, جدول مقایسه دو محیط
- INFRASTRUCTURE-GUIDE.md: سرور Production (45.149.79.127), DB KBS, Proto v0.0.179
- DISCOUNT-STORE-STATUS.md: وضعیت Production Deploy, فلوی پرداخت جدید
- SERVER-MIRRORS-CONFIG.md: registries.yaml سرور Production
- INDEX.md: تاریخ, توضیحات بروز, لینک‌های سریع جدید
2026-02-17 01:44:05 +03:30

22 KiB
Raw Blame History

🔧 راهنمای 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:

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:

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:

- 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:

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:

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 استفاده میکنه.

راه‌حل:

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:

# 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:

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:
<!-- قبل -->
<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.1770.0.178)
  2. dotnet pack -c Release و push به Nexus
  3. تغییر هر دو پروژه (FrontOffice + BackOffice) به PackageReference
# 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 دسترسی نداره.

راه‌حل: تغییر به رجیستری لوکال:

# قبل
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:

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):
kubectl set env deployment/cms Kestrel__EndpointDefaults__Protocols=Http1AndHttp2
  1. حذف GRPC annotations از ingress:
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:

# 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:

# 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:

# آیا 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:

kubectl rollout restart deployment/gitea-runner
kubectl rollout status deployment/gitea-runner --timeout=120s

📋 Workflow Template (کامل)

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 (هر ۳ ریپو):

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_PASSWORDSERVER_PASSWORD, rollout restartset image :sha

🔄 Production Workflow Template (فعلی)

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