From c08211a99cedc6296565beea37f44616c2a37911 Mon Sep 17 00:00:00 2001 From: hotwa Date: Sun, 5 Oct 2025 19:02:44 +0800 Subject: [PATCH] update registrar.sh --- woodpecker/README.md | 156 +++++++++++++++++++++++++++++++++- woodpecker/docker-compose.yml | 12 ++- woodpecker/registrar.sh | 34 +++++++- 3 files changed, 195 insertions(+), 7 deletions(-) diff --git a/woodpecker/README.md b/woodpecker/README.md index ce0a291..e6784d6 100644 --- a/woodpecker/README.md +++ b/woodpecker/README.md @@ -1,4 +1,158 @@ -3) 外部 Agent 使用示例 +## gitea 中使用 woodpecker + +### 放哪里 + +Woodpecker 会按顺序找你的配置: + +.woodpecker/*.{yaml,yml}(文件夹里每个 yml 是一个独立 workflow) + +.woodpecker.yaml(仓库根的单文件) + +.woodpecker.yml(仓库根的单文件) +你也可以在项目设置里改成自定义路径(如果填成目录,必须以 / 结尾,如 .woodpecker/)。 + +### 启用仓库 + +在 Woodpecker Web 打开你的仓库,点 Enable 后它会自动在 Gitea 里装好 webhook。之后你只要往仓库推代码、开 PR、打 tag,就会触发 CI。最简单的是在根目录放 .woodpecker.yml。 + +### 通用模板(单文件 .woodpecker.yml) + +```bash +# 触发条件:main/dev 分支的 push 和 PR;打 tag 时发布 +when: + - event: push + branch: + - main + - dev + - event: pull_request + - event: tag + +# (推荐)完整 clone(需要时) +clone: + git: + image: woodpeckerci/plugin-git + settings: + depth: 0 + +steps: + - name: lint-and-test + image: node:20 + environment: + NODE_ENV: test + commands: + - node -v + - npm ci + - npm run lint + - npm test + + # 用 buildx 构建并推镜像(示例,需在 UI 里创建对应 secrets) + - name: docker-build + image: quay.io/thegeeklab/wp-docker-buildx + privileged: true + when: + event: + - push + - tag + branch: + - main + settings: + repo: registry.example.com/myorg/myapp + tags: latest + username: + from_secret: docker_username + password: + from_secret: docker_password +``` + +要点: + +secrets 现在统一通过 environment/settings -> from_secret 注入,不再用旧的 secrets: 块(3.x 迁移规则)。 +“你的第一个流水线”示例 & from_secret 的写法,官方文档也有。 +上面用的 wp-docker-buildx 是常见的多架构镜像构建插件(需要 privileged: true)。 + +多文件结构(文件夹 .woodpecker/) + +如果你想把 CI 拆开管理,就在仓库根新建目录: + +```bash +.woodpecker/ + ci.yaml # 普通 CI + release.yaml # 发布 + gpu.yaml # 需要 GPU 的任务(示例见下) +``` + +Woodpecker 会把每个 yml 当成一个独立 workflow 来跑。 + +适合 90% 项目:push/PR 都跑、用 secrets、再给个 Docker 构建示例。 + +### 增加agent + +外部/跨机器 agent 一律用 ci-agent.jmsu.top:443。 + +ci.jmsu.top:Web UI + REST API 的域名(人用浏览器访问、OAuth 回调等)。不是 gRPC 入口。 + +ci-agent.jmsu.top:gRPC(TCP) 的域名,你的 Traefik TCP 路由会把它转到 server 的 9000 端口——agent 该连这里。 + +在你现在的架构里: + +内网/同 Compose 里的 agent → WOODPECKER_SERVER=woodpecker-server:9000(容器内直连)。 + +跨机器/公网(或 tailnet)agent → WOODPECKER_SERVER=ci-agent.jmsu.top:443(走 Traefik TCP)。 + +只要机器能连通 ci-agent.jmsu.top:443,并且你给了正确的 WOODPECKER_AGENT_SECRET,它就能注册成为 agent。 + +(可选)如果 agent 在你的 tailnet 内,也可以直连 100.64.0.27:8419,那就把 WOODPECKER_GRPC_SECURE=false(因为这是你映射出来的明文 gRPC 端口)。 + +#### GPU agent + GPU 流水线 + +GPU Agent 的 docker-compose.yml(放在有 NVIDIA 显卡的那台机器上) + +```yaml +services: + woodpecker-agent-gpu: + image: woodpeckerci/woodpecker-agent:latest + container_name: woodpecker-agent-gpu + restart: unless-stopped + environment: + WOODPECKER_SERVER=ci-agent.jmsu.top:443 + WOODPECKER_GRPC_SECURE=true + WOODPECKER_GRPC_VERIFY=true + WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET} + # 给 agent 打标签,供 workflow 定向调度 + WOODPECKER_AGENT_LABELS=gpu=true,location=gpu-node-01,backend=docker + # 并发数(按需) + WOODPECKER_MAX_WORKFLOWS=2 + volumes: + - /var/run/docker.sock:/var/run/docker.sock +``` + +前置:这台 GPU 主机要装 NVIDIA Container Toolkit 并启用 Docker runtime: +sudo nvidia-ctk runtime configure --runtime=docker && sudo systemctl restart docker。官方指引推荐这样配置。 + +GPU 专用工作流(.woodpecker/gpu.yaml) + +```bash +# 只有带 gpu=true 标签的 agent 会接单 +labels: + gpu: "true" + +when: + - event: push + branch: + - main + - dev + - event: manual + +steps: + - name: cuda-smoke + image: nvidia/cuda:12.4.1-base-ubuntu22.04 + commands: + - nvidia-smi +``` + +要点:workflow 顶层的 labels 会作为调度选择器,只会派给拥有相同标签的 agent(你刚才在 GPU agent 用 WOODPECKER_AGENT_LABELS=gpu=true,... 打过标签)。 + +## 3) 外部 Agent 使用示例 在其他机器(非同一 docker 网络)的 agent: diff --git a/woodpecker/docker-compose.yml b/woodpecker/docker-compose.yml index c93f133..ab8c337 100644 --- a/woodpecker/docker-compose.yml +++ b/woodpecker/docker-compose.yml @@ -38,7 +38,10 @@ services: - woodpecker environment: # 内网 agent 仍然走容器网络直连 server:9000 - - "WOODPECKER_SERVER=woodpecker-server:9000" + # - "WOODPECKER_SERVER=woodpecker-server:9000" + - WOODPECKER_SERVER=ci-agent.jmsu.top:4443 + - WOODPECKER_GRPC_SECURE=true + - WOODPECKER_GRPC_VERIFY=true - "WOODPECKER_AGENT_SECRET=${WOODPECKER_AGENT_SECRET}" volumes: - "/var/run/docker.sock:/var/run/docker.sock" @@ -55,13 +58,16 @@ services: - SERVICE_NAME=woodpecker-grpc - SERVICE_ADDR=${LOCAL_TS_IP} - SERVICE_PORT=8419 # 对外注册用 8419 - - ROUTE_HOST=${WOODPECKER_GRPC_HOST} - SERVICE_PROTOCOL=tcp - CHECK_TYPE=tcp - CHECK_INTERVAL=${CHECK_INTERVAL} - CHECK_TIMEOUT=${CHECK_TIMEOUT} - DEREG_AFTER=${DEREG_AFTER} - - TRAEFIK_TCP_ENTRYPOINT=${TRAEFIK_TCP_ENTRYPOINT} + - TRAEFIK_TCP_ENTRYPOINT=${TRAEFIK_TCP_ENTRYPOINT} # 你在 traefik.yml 里把 :4443 命名为 tcp,.env 已经配置为 tcp + - SERVICE_PROTOCOL=tcp + - ROUTE_HOST=${WOODPECKER_GRPC_HOST} # ci-agent.jmsu.top + - TLS_MODE=terminating # ★ A 方案:Traefik 终止 TLS + - TRAEFIK_CERT_RESOLVER=alidns # ★ 用你已有的 alidns ACME volumes: - ./registrar.sh:/registrar.sh:ro entrypoint: ["/bin/sh","/registrar.sh"] diff --git a/woodpecker/registrar.sh b/woodpecker/registrar.sh index 5f3918a..f0b7b1c 100644 --- a/woodpecker/registrar.sh +++ b/woodpecker/registrar.sh @@ -15,6 +15,8 @@ CHECK_TIMEOUT="${CHECK_TIMEOUT:-2s}" DEREG_AFTER="${DEREG_AFTER:-1m}" TRAEFIK_HTTP_ENTRYPOINT="${TRAEFIK_HTTP_ENTRYPOINT:-websecure}" TRAEFIK_TCP_ENTRYPOINT="${TRAEFIK_TCP_ENTRYPOINT:-tcp}" +TRAEFIK_CERT_RESOLVER="${TRAEFIK_CERT_RESOLVER:-alidns}" +TLS_MODE="${TLS_MODE:-terminating}" # terminating | passthrough | plaintext # TRAEFIK_CERT_RESOLVER="${TRAEFIK_CERT_RESOLVER:-cf}" echo "[registrar] consul: $CONSUL, service: $SERVICE_NAME@$SERVICE_ADDR:$SERVICE_PORT" @@ -47,9 +49,35 @@ if [ "$SERVICE_PROTOCOL" = "http" ]; then # 如需 ACME 证书解析器可再加一行(取消注释) # TAGS="$TAGS${NL}traefik.http.routers.${SERVICE_NAME}.tls.certresolver=${TRAEFIK_CERT_RESOLVER}" elif [ "$SERVICE_PROTOCOL" = "tcp" ]; then - TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.rule=HostSNI(\`${ROUTE_HOST}\`)" - TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.entrypoints=${TRAEFIK_TCP_ENTRYPOINT}" - TAGS="$TAGS${NL}traefik.tcp.services.${SERVICE_NAME}.loadbalancer.server.port=${SERVICE_PORT}" + case "$TLS_MODE" in + # A:Traefik 终止 TLS(推荐公网) + terminating) + TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.rule=HostSNI(\`${ROUTE_HOST}\`)" + TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.entrypoints=${TRAEFIK_TCP_ENTRYPOINT}" + TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.tls=true" + TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.tls.certresolver=${TRAEFIK_CERT_RESOLVER}" + TAGS="$TAGS${NL}traefik.tcp.services.${SERVICE_NAME}.loadbalancer.server.port=${SERVICE_PORT}" + ;; + + # A-备用:后端自己终止 TLS(需要给 woodpecker-server 配 cert/key) + passthrough) + TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.rule=HostSNI(\`${ROUTE_HOST}\`)" + TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.entrypoints=${TRAEFIK_TCP_ENTRYPOINT}" + TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.tls.passthrough=true" + TAGS="$TAGS${NL}traefik.tcp.services.${SERVICE_NAME}.loadbalancer.server.port=${SERVICE_PORT}" + ;; + + # B:明文 TCP(仅内网/Tailscale,用 * 兜底) + plaintext) + TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.rule=HostSNI(\`*\`)" + TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.entrypoints=${TRAEFIK_TCP_ENTRYPOINT}" + TAGS="$TAGS${NL}traefik.tcp.routers.${SERVICE_NAME}.priority=1" + TAGS="$TAGS${NL}traefik.tcp.services.${SERVICE_NAME}.loadbalancer.server.port=${SERVICE_PORT}" + ;; + + *) + echo "unsupported TLS_MODE=$TLS_MODE" >&2; exit 2;; + esac else echo "unsupported SERVICE_PROTOCOL=$SERVICE_PROTOCOL" >&2; exit 2 fi