15 KiB
🔧 راهنمای 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:
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.yamlenv 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 بدون مشکل استفاده میکنه.
🔄 تغییرات 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:
# 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
"