Gitea+Harbor+Drone+Docker+Nginx 搭建内网持续集成流水线 这次我们看一个比较有意思的开源项目组合Gitea Harbor Drone Docker Nginx。名字里有 drone但别往机场那则新闻上联想这里是 DevOps 里的 Drone CI/CD。它解决的问题很具体代码推到 Gitea 之后自动触发构建、跑测试、打镜像再推到 Harbor 私有仓库最后通过 Nginx 对外提供访问入口。整套流程下来就是一个能落地的内网持续集成方案。先说结论Drone 的核心优势不在功能堆砌而是“管道即代码”。你只需要在仓库里放一个.drone.yml它就能按你定义的步骤去执行构建任务。配合 Gitea 做代码托管、Harbor 做镜像仓库、Docker 跑运行环境、Nginx 做反向代理这套架构非常适合中小团队或内网环境快速搭建。本文不会只讲概念会按“环境准备 - 安装部署 - 流水线测试 - API 调用 - 性能观察 - 问题排查”的顺序给你一套可以直接复制的落地流程。1. 核心能力速览能力项说明项目类型开源 CI/CD 持续集成工具Drone 分为 Server 与 Runner 两部分代码托管原生支持 Gitea也可对接 GitHub、GitLab、Bitbucket 等管道定义通过仓库内.drone.yml声明式配置支持 step、service、pipeline 概念镜像仓库可对接 Harbor、Docker Registry、阿里云 ACR 等通过 Docker 插件推送反向代理推荐 Nginx 终结 HTTPS再转发到 Drone Server 与 Runner API启动方式Docker Compose 编排适合单机或内网服务器部署接口能力提供 REST API 与 Drone CLI可用于查询仓库、触发构建、查看日志批量任务支持多仓库、多分支自动触发也可通过 API 外部批量调度并发能力Runner 默认按容器并发执行可配置并发数与资源限制适合场景内网代码托管、私有镜像仓库、自动构建发布、多环境交付从材料看这套技术栈的典型使用路径是开发者在 Gitea 提交代码Webhook 通知 Drone ServerServer 将任务分发给 RunnerRunner 启动 Docker 容器执行构建命令最后把产物推送到 Harbor。整个过程无需人工登录服务器敲命令CI 的“持续”二字在这里体现得最明显。实际部署时你不需要关心 Gitea、Harbor、Drone 三者之间的深层依赖关系它们各自是独立服务通过 Webhook、API、Docker Socket 互相通信。重点要理解的是网络路径Gitea 需要能访问到 Drone Server 的 Webhook 地址Drone Runner 需要能访问到 Harbor 或镜像仓库Nginx 需要把对应域名或路径转发到正确容器。只要这条链路通了整套系统就能跑起来。2. 适用场景与使用边界这套组合最适合三类团队。第一类是内网开发团队。代码不想放到公网 Git 平台镜像也不想推到公共仓库但又要享受自动构建的便利。Gitea 加上 Harbor 正好覆盖代码和镜像两端的私有化需求。第二类是中小项目或创业团队。没有专职 DevOps 工程师但又需要一套能自动构建、自动打标签、自动推送的流水线。Drone 的.drone.yml比 Jenkins 的界面配置更容易用 Git 管理也比 GitLab CI 少了很多组件。第三类是已经在用 Docker 和 Nginx 做部署的团队。因为 Drone 的 Runner 本身就是以容器方式运行构建环境也通过容器隔离整个技术栈的一致性很高不需要额外学习 K8s 或复杂编排工具。需要强调使用边界。第一不要把 Drone Server 暴露到公网而且不做访问控制Gitea 和 Drone 之间的 Webhook 必须用密钥保护。第二Runner 在执行管道时会挂载 Docker Socket这意味着管道内容器拥有较高的宿主权限不能随便让不可信的人往 Gitea 仓库提交代码。第三Harbor 只存放你拥有合法授权或可以合法使用的镜像项目中涉及第三方基础镜像、商业软件镜像时要注意许可证和分发限制。第四如果你把 Drone 集成到生产环境所有流水线步骤必须有日志留存镜像 tag 要可追溯不能只图方便而跳过验证环节。这套方案不是一个完整的发布系统。你可以把它理解为“构建 推送”的自动化工具但部署到哪台服务器、如何平滑升级、如何回滚这些还需要 Jenkins、Ansible、K8s 或其他工具配合。不要在文章里过度拔高它的能力边界。3. 环境准备与前置条件部署前先确认基础设施。以下清单适用于大多数单机内网部署场景具体版本和路径以实际环境为准。3.1 操作系统与 Docker推荐使用 Ubuntu 22.04 LTS 或 Debian 12 这类长期支持系统。Docker 安装方式不固定但建议使用官方源安装 Docker Engine 和 Docker Compose 插件。安装完成后执行以下命令验证docker version docker compose version只要这两个命令都能输出版本号说明 Docker 环境可用了。3.2 网络与域名规划建议为每个服务规划独立的域名或子路径避免端口混淆。常见规划如下服务推荐域名后端端口Giteagit.example.com3000Harborharbor.example.com80 / 443Drone Serverci.example.com80 / 443Drone Runner不需要对外暴露3000仅内网如果暂时没有域名也可以直接用 IP 端口访问但后续配置 Drone 的DRONE_SERVER_HOST时必须把外部访问地址写对否则 Webhook 回调会失败。3.3 Gitea 与 Harbor 准备Gitea 和 Harbor 可以先通过各自官方文档部署好。这里不展开完整安装过程但有一个关键点Gitea 需要提前创建好一个 OAuth2 应用用于和 Drone 做单点登录集成。在 Gitea 的用户设置 - 应用 - 管理 OAuth2 应用中创建一个新的 OAuth2 应用回调地址填http://ci.example.com/login或https://ci.example.com/login注意和后续 Drone 配置中的协议保持一致。Harbor 需要确认两点一是当前仓库策略是否开放匿名拉取如果私有项目需要认证管道中要配置 Docker 登录凭据二是如果 Drone Runner 和 Harbor 在同一台机器建议在/etc/docker/daemon.json中把 Harbor 地址加入insecure-registries否则推送 https 证书不受信任的 Harbor 时会报错。4. 安装部署与启动方式部署方式推荐 Docker Compose运维成本最低。这里给出一套单机部署示例参数需要根据实际环境替换。4.1 创建 Drone Server 与 Runner 的 Compose 文件新建/opt/drone/docker-compose.yml内容如下version: 3 services: drone-server: image: drone/drone:2 container_name: drone-server ports: - 8080:80 environment: - DRONE_GITEA_SERVERhttp://git.example.com - DRONE_GITEA_CLIENT_ID${DRONE_GITEA_CLIENT_ID} - DRONE_GITEA_CLIENT_SECRET${DRONE_GITEA_CLIENT_SECRET} - DRONE_RPC_SECRET${DRONE_RPC_SECRET} - DRONE_SERVER_HOSTci.example.com - DRONE_SERVER_PROTOhttps - DRONE_USER_CREATEusername:your_admin_username,admin:true volumes: - /var/lib/drone:/data restart: always drone-runner: image: drone/drone-runner-docker:1 container_name: drone-runner ports: - 3000:3000 environment: - DRONE_RPC_PROTOhttp - DRONE_RPC_HOSTdrone-server - DRONE_RPC_SECRET${DRONE_RPC_SECRET} - DRONE_RUNNER_CAPACITY2 - DRONE_RUNNER_NAMEdocker-runner volumes: - /var/run/docker.sock:/var/run/docker.sock depends_on: - drone-server restart: always需要创建.env文件存放密钥DRONE_GITEA_CLIENT_IDyour_client_id DRONE_GITEA_CLIENT_SECRETyour_client_secret DRONE_RPC_SECRET$(openssl rand -hex 32)启动命令cd /opt/drone docker compose up -d启动后观察日志docker compose logs -f drone-server docker compose logs -f drone-runner如果看到 Runner 成功注册到 Server说明通信正常。这里要注意DRONE_RPC_SECRET必须一致否则 Runner 无法连接 Server。4.2 Nginx 反向代理配置Nginx 配置中需要将ci.example.com转发到 Drone Server 的 8080 端口。一个最小可用的配置如下server { listen 80; server_name ci.example.com; client_max_body_size 100m; location / { proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_pass http://127.0.0.1:8080; } }实际生产环境建议再加上 HTTPS 证书。注意 Drone 的DRONE_SERVER_PROTO必须和 Nginx 对外协议一致如果 Nginx 只做 HTTP那DRONE_SERVER_PROTO填http如果做了 HTTPS就填https。这个参数影响 Gitea Webhook 的回调地址和登录跳转地址写错会导致登录后回调异常。5. 功能测试与效果验证部署完成后需要验证整套链路是否真的能自动构建。下面给出一套可执行的验证流程。5.1 验证 Drone 与 Gitea 集成访问http://ci.example.com使用 Gitea 账号登录。登录后进入 Drone 界面选择需要激活的仓库点击“Activate Repository”。这一步会触发以下几件事Drone 在 Gitea 中创建 WebhookGitea 后续的 push、tag、merge 事件都会通知 Drone。如果点击激活后没反应先确认 Gitea 的 OAuth2 回调地址是否填了http://ci.example.com/login再确认 Nginx 能正常转发到 Drone Server 的 8080 端口。5.2 编写.drone.yml流水线激活仓库后在仓库根目录创建.drone.yml。下面是一个完整的示例包含构建和推送镜像到 Harbor 两个步骤kind: pipeline type: docker name: build-and-push steps: - name: build image: golang:1.21 commands: - go build -o myapp . - go test ./... - name: docker-build-push image: plugins/docker settings: registry: harbor.example.com repo: harbor.example.com/library/myapp tags: ${DRONE_COMMIT_SHA} username: from_secret: harbor_username password: from_secret: harbor_password将文件提交到 Giteagit add .drone.yml git commit -m add drone pipeline git push origin main推送后Gitea 会触发 WebhookDrone Server 会创建一次构建记录。你可以在 Drone 界面看到流水线状态点击构建步骤可以查看实时日志。5.3 判断构建是否成功成功标准有三条第一Drone 界面中流水线状态为绿色第二构建日志里面能看到go test通过第三Harbor 仓库中出现 tag 为 commit SHA 的镜像例如harbor.example.com/library/myapp:abc123...。如果镜像没有出现在 Harbor 中先查看 Docker 推送步骤的日志重点排查以下问题dial tcp: lookup harbor.example.com失败Runner 容器无法解析 Harbor 域名检查 Docker DNS 配置。x509: certificate signed by unknown authorityHarbor 的 https 证书未受信任把 Harbor 地址加入 Docker daemon 的insecure-registries。denied: requested access to the resource is deniedHarbor 用户名或密码错误检查 Drone secrets 是否配置正确。5.4 验证 Tag 触发与多分支团队实践中主干分支和 tag 往往需要不同处理。可以在.drone.yml中增加条件触发trigger: event: - push - tag也可以按分支区分构建参数。例如只在main分支上推送镜像其他分支只执行测试steps: - name: test image: golang:1.21 commands: - go test ./... - name: build image: golang:1.21 commands: - go build -o myapp . when: branch: - main通过when条件可以控制步骤在哪些分支、哪些事件下执行。这是 Drone 流水线非常实用的功能。6. 接口 API 与批量任务6.1 Drone REST API 概览Drone 提供 REST API可以通过 token 访问。常用接口包括接口方法说明/api/reposGET列出当前用户所有仓库/api/repos/{owner}/{repo}GET获取仓库详情/api/repos/{owner}/{repo}/buildsGET获取构建列表/api/repos/{owner}/{repo}/builds/{build}POST重启某个构建/api/repos/{owner}/{repo}/buildsPOST手动触发构建调用 API 需要先获取 token。登录 Drone Web 界面后在用户设置页面可以复制 Personal Access Token。拿到 token 后在请求头中带上Authorization: Bearer token。6.2 使用 curl 触发构建手动触发某个仓库的构建可以使用如下命令curl -X POST \ -H Authorization: Bearer ${DRONE_TOKEN} \ http://ci.example.com/api/repos/{owner}/{repo}/builds查询构建列表curl -H Authorization: Bearer ${DRONE_TOKEN} \ http://ci.example.com/api/repos/{owner}/{repo}/builds6.3 使用 Drone CLI 管理多仓库Drone 官方提供了命令行工具适合在脚本中批量操作。首先安装 CLIcurl -L https://github.com/drone/drone-cli/releases/latest/download/drone_linux_amd64.tar.gz | tar -xz sudo install drone /usr/local/bin/配置环境变量export DRONE_SERVERhttp://ci.example.com export DRONE_TOKENyour_personal_access_token查看仓库列表drone repo ls查看构建日志drone build logs {owner}/{repo} {build_number}6.4 批量触发仓库构建如果需要在一批仓库统一的版本上重新构建可以写一个 shell 脚本读取仓库列表逐个触发#!/bin/bash DRONE_SERVERhttp://ci.example.com DRONE_TOKENyour_personal_access_token REPOS$(curl -s -H Authorization: Bearer ${DRONE_TOKEN} ${DRONE_SERVER}/api/repos | jq -r .[] | .slug) for repo in ${REPOS}; do echo trigger build for ${repo} curl -X POST -H Authorization: Bearer ${DRONE_TOKEN} ${DRONE_SERVER}/api/repos/${repo}/builds sleep 2 done这里用jq解析 JSON实际使用前需要确认是否安装。批量触发后建议在脚本中增加检查机制每 30 秒查询一次构建状态避免某个仓库构建失败后无感知。这里要说明的是Drone 自身有并发队列批量触发大量任务时Runner 的DRONE_RUNNER_CAPACITY决定了同时执行的构建数。如果一次触发 50 个仓库而 Runner 并发数只有 2那么任务会排队这不算故障只是吞吐量受限于 Runner 资源。7. 资源占用与性能观察7.1 容器资源监控Drone Server、Runner、Gitea、Harbor、Nginx 都是容器运行监控资源占用的方式是docker statsdocker stats drone-server drone-runner从实际部署经验看Drone Server 本身很轻单机运行通常几百 MB 内存以内。真正吃资源的是 Runner 启动的构建容器以及 Harbor 中镜像的存储和推送过程。如果 Runner 并发数过高会导致单机 CPU 和内存飙升甚至影响其他服务。更稳妥的做法是在 Compose 文件中为 Runner 设置资源限制services: drone-runner: deploy: resources: limits: cpus: 4 memory: 4G7.2 显存与性能的类比理解如果你之前玩过 AI 相关的一键包会习惯看显存占用。Drone 这类 CI 工具不涉及 GPU关注点是 CPU、内存、磁盘 I/O 和网络带宽。每一次构建启动一个容器会拉取基础镜像并创建多个层所以磁盘和网络会成为主要瓶颈。建议把/var/lib/docker放到 SSD 上避免构建速度被机械盘拖慢。7.3 如何提升构建吞吐量如果团队内构建任务很多可以考虑两个方向第一增加 Runner 数量。一台机器跑多个 Runner 实例或多台机器各跑一个 Runner通过同一个 RPC Secret 连接到 Drone Server。这样任务会被分发到不同机器执行。第二使用构建缓存。Drone 社区有缓存插件可以将依赖目录打包上传到对象存储或本机缓存。比如 Go 项目的GOPATH、Node 项目的node_modules都可以缓存避免每次构建重新下载全部依赖。配置示例如下steps: - name: restore-cache image: meltwater/drone-cache settings: backend: filesystem cache_key: volume archive_format: gzip mount: - /go/pkg/mod当然缓存插件会依赖外部存储或共享目录使用前需要确认版本兼容性。8. 常见问题与排查方法以下排查表来自常见部署场景不同版本可能有个别参数差异但排查思路通用。问题现象可能原因排查方式解决方案Gitea 登录 Drone 后提示回调失败OAuth2 回调地址与DRONE_SERVER_HOST不一致对比 Gitea 应用配置和 Drone 环境变量将回调地址改为当前外部访问地址协议保持一致推送代码后 Drone 没有自动构建Webhook 未创建或 Gitea 无法访问 Drone在 Gitea 仓库 Webhook 设置中检查投递记录重新激活仓库确认 Drone 地址可从 Gitea 访问构建卡在 pending 状态Runner 未启动或 RPC Secret 不一致查看 Runner 日志检查DRONE_RPC_HOST、DRONE_RPC_PROTO、DRONE_RPC_SECRET构建容器无法访问代码仓库仓库为私有未配置凭据查看构建日志中 git clone 的报错Drone 通过 Gitea 集成自动 clone确认仓库已激活且账号有权限推送镜像到 Harbor 失败认证失败Harbor 用户名或密码不正确检查 Drone secrets重新在 Drone 仓库设置中添加harbor_username和harbor_passwordHarbor 证书不受信任使用自签 https 证书查看推送日志是否出现 x509 错误将 Harbor 地址加入insecure-registries或配置可信证书Nginx 访问 Drone 返回 502Drone Server 容器没有启动或端口错误检查docker compose ps和 Nginx 日志确认 proxy_pass 指向正确端口多个 Runner 同时挂载同一个 Docker Socket并发构建导致资源竞争观察构建卡顿和失败时间点降低DRONE_RUNNER_CAPACITY或横向扩展 Runner 机器.drone.yml语法错误导致构建不启动YAML 缩进或字段错误在 Drone 界面查看构建状态和错误信息使用 Drone 文档示例对比注意kind、type、steps层级9. 最佳实践与使用建议这套组合的工程化落地建议按以下几条来约束。第一第一次跑通时永远使用最小配置。不要一上来就写复杂的多阶段构建先用一条echo hello drone命令的.drone.yml验证链路确认自动触发和日志显示正常后再逐步加入编译、测试、镜像推送步骤。这样可以快速定位问题是出在链路还是出在流水线本身。第二密钥统一走 Drone Secrets。不要在.drone.yml中明文写 Harbor 密码、云平台 AccessKey、SSH 私钥。Drone 的 Secrets 机制可以在仓库或全局级别配置然后在管道中通过from_secret引用。生产环境更应该限制 Secrets 的可见范围避免所有仓库共享全部密钥。第三镜像 tag 规范必须提前定好。建议使用 commit SHA 作为唯一 tag例如myapp:abc123def同时通过latest、v1.2.3这样的标签辅助发布。不要把 tag 只写成latest否则无法定位线上跑的是哪一个版本。第四日志和状态要有留存。Drone Server 的日志默认在容器内如果构建失败需要回溯建议把/var/lib/drone挂载到持久化磁盘并定期备份。更大的团队可以接 Prometheus 和 Loki把构建指标和日志集中管理但这不是首次部署的必选项。第五批量任务要设计重试和告警。通过 API 批量触发构建时不要一股脑全部发过去控制并发数增加失败重试和状态检查。Gitea 和 Drone 的 Webhook 传递是有时间窗口的网络抖动可能导致事件丢失关键发布场景最好保留手动触发入口。第六涉及外部依赖时先确认授权边界。Harbor 中如果有从第三方拉取再转存的基础镜像或商用组件要确认许可证允许分发和内部使用。构建过程中从公网拉取依赖包时也要留意依赖的许可证变化避免合规风险。10. 总结与下一步把 Gitea、Harbor、Drone、Docker、Nginx 组合在一起得到的是一套轻量但完整的持续集成交付链路。最值得尝试的点是 Drone 的“管道即代码”模式仓库里提交一个.drone.yml构建流程就跟着代码走不需要在某个中心化界面里拖拽配置。最先应该验证的功能是“提交代码自动触发构建”。只要这一步通了后面的镜像推送、多分支条件、API 批量调度都可以在这个基础上叠加。最容易踩的坑有两个一是 Gitea OAuth2 回调地址和 Drone 外部访问地址不一致导致登录失败二是 Runner 的 RPC Secret 配置不一致导致构建全部卡在 pending。部署时先把这两个点检查一遍能省下大量排查时间。后续可以扩展的方向包括在流水线中加入单元测试和静态检查把构建结果通知到企业微信或钉钉通过 Drone 的 API 接入自己的发布平台或者把 Runner 横向扩展到多台机器提升构建吞吐。对于还想继续深入的同学建议先去读 Drone 官方文档中关于kind: pipeline的完整定义把trigger、when、depends_on这些条件组合用熟你会发现这套系统的表达能力比想象中强很多。