# 🔧 راهنمای 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 -c runner --tail=30 # لاگ DinD kubectl logs -c docker --tail=30 ``` ### ۲. تست Docker داخل Runner: ```bash # exec به DinD container kubectl exec -c docker -- docker info # آیا registry قابل دسترسیه؟ kubectl exec -c docker -- docker pull 194.5.195.53:32082/dotnet/sdk:9.0 ``` ### ۳. چک config runner: ```bash # آیا config.yaml mount شده؟ kubectl exec -c runner -- cat /data/config.yaml # آیا privileged فعاله؟ kubectl exec -c docker -- docker inspect \ --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/ 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/.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/ kubectl rollout status deployment/ --timeout=180s " ```