feat: Add documentation for Admin/Customer separation fix and CI/CD pipeline guide

This commit is contained in:
masoodafar-web
2026-02-11 00:41:43 +03:30
parent 5149b9a89c
commit 956a9ff6d6
3 changed files with 575 additions and 1 deletions
+418
View File
@@ -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
"
```