ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

基于Gitea、Drone与Harbor搭建自托管CI/CD平台实战指南

基于Gitea、Drone与Harbor搭建自托管CI/CD平台实战指南 在实际的工程落地场景里Gitea、Harbor、Drone、Docker、Nginx 是一套很常见的自托管 CI/CD 组合。Gitea 负责代码托管Harbor 负责镜像存储和权限控制Drone 负责流水线调度Docker 提供容器运行时Nginx 承担统一入口和 HTTPS 反向代理。把这几者串成一条链路后开发人员只需要推送代码剩下的构建、镜像推送和部署都可以自动完成。对于不想把代码和镜像放在第三方平台的团队这套方案既能保留数据自主权又能获得接近商业平台的自动化体验。整套平台的关键不是单个工具怎么用而是它们之间如何协作。文章会从组件职责讲起然后依次完成服务器准备、服务编排、Nginx 配置、Gitea OAuth 对接、Drone 流水线打通最后给出常见的排错表和加固建议。整个流程可以在一台 4 核 8G 的 Linux 服务器上完成适合作为团队内部 CI/CD 平台或个人的一站式实验环境。1. 先理解这套 CI/CD 技术栈为什么这样组合1.1 每个组件解决什么问题这套组合里没有一个组件是多余的。Gitea 是一套极轻量的 Git 服务端它比 GitLab 占用的资源少很多但在仓库管理、Webhook、OAuth、Issue、Pull Request 等核心能力上已经足够。Harbor 是专门为容器镜像设计的仓库平台支持镜像签名、漏洞扫描、复制、项目级权限和 Robot Account比直接使用 Docker Registry 适合团队协作。Drone 是 CI/CD 引擎它用.drone.yml文件描述流水线风格接近 GitHub Actions学习成本比 Jenkins 低。Docker 是所有这些服务的运行基础也是流水线构建镜像和启动测试容器的运行时。Nginx 在最外层充当统一网关负责 HTTPS 证书、域名转发和端口收敛。各组件在整条链路中的位置可以这样理解组件职责对外端口示例替代方案Gitea代码仓库、Pull Request、Webhook、OAuth 授权3000GitLab、Gogs、BitbucketDrone Server接收 Webhook、调度流水线、提供管理界面80 映射为 8080Jenkins、GitLab CIDrone Runner拉取仓库、执行.drone.yml中的步骤无固定端口Drone 的 Kubernetes RunnerHarbor镜像仓库、项目权限、漏洞扫描、镜像复制18080Docker Registry、NexusDocker容器运行时、镜像构建、服务隔离2375 不建议暴露containerd、PodmanNginxHTTPS 终止、反向代理、统一入口80 / 443Caddy、Apache端口规划要在部署前就确定好否则后面改域名、改证书、改反向代理会牵一发动全身。上面表格里的端口是示例实际项目可以根据服务器情况调整。1.2 一次代码提交的完整链路这条链路从开发人员执行git push开始到镜像出现在 Harbor 中结束中间经过四个关键节点git push - Gitea 收到代码 - Gitea 触发 Webhook - Drone Server 创建构建任务 - Drone Runner 拉取仓库 - 执行 .drone.yml 中的步骤 - 构建镜像并推送 Harbor理解这个顺序对排查问题非常重要。很多 CI/CD 出问题的场景并不复杂而是链路中某个环节没有接通。例如 Webhook 没有发送成功Drone 里就看不到构建Runner 和 Server 的 RPC 密钥不一致构建就会一直 pendingHarbor 项目没有创建流水线即使构建成功也会在 push 阶段失败。1.3 为什么选择 Drone 而不是 Jenkins如果是第一次搭建自托管 CI/CD很容易在 Jenkins、GitLab CI 和 Drone 之间犹豫。Jenkins 生态庞大、插件多但它的配置通常不集中在代码仓库里流水线要维护在 Jenkins 服务端迁移和审计不如 Drone 方便。GitLab CI 和 Gitea 集成得也很好但 GitLab 本身对资源要求高如果只是为了代码托管和 CI整套 GitLab 会让轻量服务器吃不消。Drone 的设计思路恰好和这套组合匹配流水线定义在代码仓库里天然支持分支级和事件级的触发控制Runner 以 Docker 容器方式运行每一步也都是容器配合 Docker 构建镜像非常顺滑和 Gitea 的集成通过 OAuth 完成Webhook 注册基本是自动的。它的缺点是插件生态不如 Jenkins 庞大但常见构建、推送镜像、部署、通知场景已经足够。选型时只需要做一个判断团队是否愿意把流水线定义作为代码的一部分来管理。如果愿意Drone 的体验会远好于 Jenkins 的传统 Job 模式。如果团队已经有大量 Jenkins 脚本沉淀迁移 Drone 的成本会高一些这时需要评估是否值得重写。1.4 学习环境与生产环境的差异本地或测试环境可以用 Docker Compose 一键拉起整条链路很多配置可以用默认值。生产环境至少要额外考虑数据备份、密钥保护、TLS 证书、访问控制、日志收集和 Drone Runner 的隔离。文中后面的示例以快速跑通为主每个环节都会注明生产环境需要补充什么避免直接把学习配置搬到线上。2. 环境准备与前置检查2.1 服务器和域名规划建议使用一台 Linux 服务器常见发行版为 Ubuntu 22.04 或 Debian 12。配置方面代码托管、CI、镜像仓库放在同一台机器时4 核 8G 起步比较合适如果团队规模较大或镜像很多建议把 Harbor 拆到独立机器并给 Docker 数据卷使用单独的磁盘。域名是整个部署里最容易改错的地方。推荐在部署前画一张域名表用途示例域名说明Gitea 页面和仓库地址git.example.com所有 Git 仓库 URL 的根地址Drone 管理界面ci.example.comOAuth 回调也指向这个域名Harbor 镜像地址registry.example.comDocker 镜像名中包含这个域名Nginx 入口git / ci / registry所有请求都通过 443 进入域名确定后需要把三个域名解析到服务器公网 IP或在本地/etc/hosts中配置测试解析。公网环境下证书可以使用 Lets Encrypt 自动签发纯内网环境则需要自建 CA 或使用自签名证书并让所有拉取镜像的机器信任该 CA。2.2 安装 Docker 与 Docker ComposeDocker 是这套平台的基础。在 Debian/Ubuntu 上可以通过官方脚本安装也可以使用 apt 安装docker.io。执行前先确认安装源可用sudo apt update sudo apt install -y ca-certificates curl gnupg sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg echo deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin安装完成后将当前用户加入docker组避免每次执行 Docker 命令都加sudosudo usermod -aG docker $USER newgrp docker docker version docker compose version这里要注意docker compose是插件命令和老版本的docker-compose是不同命令。文中统一使用docker compose。如果系统里装的是旧版docker-compose大部分命令语法也相同但 Compose 文件版本和插件参数可能略有差异。2.3 生成 RPC 密钥并准备目录结构Drone Server 和 Runner 之间通过 RPC 通信通信密钥由DRONE_RPC_SECRET控制。这个密钥不能写成固定的弱密码需要单独生成openssl rand -hex 32将输出保存到本地环境文件或密码管理器中。目录统一规划在/opt/cicd下Gitea 数据、Drone 数据、Nginx 配置都放在这里方便备份和迁移sudo mkdir -p /opt/cicd/data/gitea sudo mkdir -p /opt/cicd/data/drone sudo mkdir -p /opt/cicd/certs sudo mkdir -p /opt/cicd/nginx2.4 在 Gitea 中注册 Drone OAuth 应用整套平台第一次启动时Gitea 的首次页面会引导创建管理员账号。创建完成后需要先到 Gitea 中注册一个 OAuth 应用把生成的 Client ID 和 Client Secret 填给 Drone Server。这是 Gitea 和 Drone 建立信任关系的关键步骤。路径为“Gitea 右上角用户头像 - 设置 - 应用 - 管理 OAuth2 应用程序”创建时填写配置项示例值应用名称Drone CI重定向 URIhttps://ci.example.com/login之后的顺序会有些绕Gitea 还没起来Drone 还没起来Nginx 也没配置好但 OAuth 应用又需要先注册。实际操作时可以分两步先把 Gitea 容器启动起来完成初始化注册好 OAuth 应用再启动 Drone Server 和 Runner。3. 用 Docker Compose 编排 Gitea 与 Drone 基础服务3.1 编写 Compose 文件在/opt/cicd下创建docker-compose.yml。这个文件只编排 Gitea、Drone Server 和 Drone RunnerHarbor 单独使用官方离线安装包Nginx 使用宿主机安装原因在后续章节说明。version: 3.8 services: gitea: image: gitea/gitea:1.21 container_name: gitea restart: unless-stopped environment: - USER_UID1000 - USER_GID1000 - GITEA__server__DOMAINgit.example.com - GITEA__server__ROOT_URLhttps://git.example.com/ - GITEA__server__HTTP_PORT3000 - GITEA__server__SSH_PORT2222 - GITEA__service__DISABLE_REGISTRATIONtrue volumes: - /opt/cicd/data/gitea:/data ports: - 127.0.0.1:3000:3000 - 127.0.0.1:2222:22 drone-server: image: drone/drone:2.24 container_name: drone-server restart: unless-stopped environment: - DRONE_GITEA_SERVERhttps://git.example.com - DRONE_GITEA_CLIENT_ID替换为Gitea生成的ClientID - DRONE_GITEA_CLIENT_SECRET替换为Gitea生成的ClientSecret - DRONE_RPC_SECRET替换为openssl生成的32字节十六进制字符串 - DRONE_SERVER_HOSTci.example.com - DRONE_SERVER_PROTOhttps - DRONE_USER_CREATEusername:管理员用户名,admin:true - DRONE_LOGS_DEBUGfalse volumes: - /opt/cicd/data/drone:/data ports: - 127.0.0.1:8080:80 drone-runner: image: drone/drone-runner-docker:1.8 container_name: drone-runner restart: unless-stopped environment: - DRONE_RPC_HOSTdrone-server - DRONE_RPC_PROTOhttp - DRONE_RPC_SECRET替换为与drone-server相同的RPC密钥 - DRONE_RUNNER_NAMEdrone-runner-docker - DRONE_RUNNER_CAPACITY2 volumes: - /var/run/docker.sock:/var/run/docker.sock代码里有几个点需要解释。GITEA__server__ROOT_URL控制 Gitea 页面生成的所有绝对链接如果填成http://localhost:3000后续从 Drone 跳回 Gitea 时会跳到服务器本机导致浏览器访问失败。必须使用外部域名。DRONE_GITEA_SERVER是 Gitea 对外的完整地址Drone 在 OAuth 跳转和 Webhook 回调时依赖它。DRONE_SERVER_HOST是 Drone 自身的对外域名DRONE_SERVER_PROTO必须和 Nginx 的 TLS 策略一致这里填https。DRONE_RPC_HOSTdrone-server是 Compose 网络内的服务名适用于 Runner 和 Server 在同一台机器、同一个 Compose 网络的情况。生产环境跨主机部署时这个值要改成 CI 域名比如ci.example.com并把DRONE_RPC_PROTO改为https。DRONE_RPC_SECRET在 Server 和 Runner 中必须完全一致否则 Runner 无法认证构建会一直处于等待状态。DRONE_LOGS_DEBUGfalse是为了避免生产环境日志量过大排查阶段可以先设为true。3.2 使用环境变量文件管理敏感信息把 Client ID、Client Secret 和 RPC 密钥直接写进 Compose 文件不适合作为最佳实践因为 Compose 文件会被提交到配置仓库。更稳妥的做法是使用.env文件并让 Compose 通过变量引用cd /opt/cicd cat .env EOF DRONE_GITEA_CLIENT_ID替换为实际值 DRONE_GITEA_CLIENT_SECRET替换为实际值 DRONE_RPC_SECRET替换为实际值 EOF然后把docker-compose.yml中的对应值改成${DRONE_GITEA_CLIENT_ID}、${DRONE_GITEA_CLIENT_SECRET}、${DRONE_RPC_SECRET}。.env文件不要提交到 Git也不要放在 Web 服务可访问的目录下。3.3 单独安装 HarborHarbor 不像普通应用一样只有一个容器它安装后会生成一组服务包括 Harbor 自身的 Nginx、Core、Jobservice、Registry、数据库和 Portal。使用官方离线安装包是最稳妥的方式。cd /opt/cicd wget https://github.com/goharbor/harbor/releases/download/v2.10.3/harbor-offline-installer-v2.10.3.tgz tar xzvf harbor-offline-installer-v2.10.3.tgz cd harbor cp harbor.yml.tmpl harbor.yml版本号以 Harbor 官方 Release 页面为准示例中的 2.10.3 只代表写作时的常见版本。修改harbor.yml时重点确认几个配置hostname: registry.example.com http: port: 18080 # 因为统一用外部 Nginx 终止 TLS这里先不启用 https # https: # port: 18443 # certificate: /your/certificate.pem # private_key: /your/private_key.pem harbor_admin_password: 请替换为强密码 database: password: 请替换为数据库密码 data_volume: /opt/cicd/data/harbor在这个方案里Harbor 自带的 HTTPS 被关闭由最外层 Nginx 统一终止 TLS。这样做的好处是三种服务共用一套证书和入口不容易出现证书过期只坏一个服务的情况。代价是 Harbor 和外部 Nginx 之间是明文 HTTP在可信内网是没问题的跨公网部署时需要评估是否接受。确认配置后执行安装sudo ./install.sh安装脚本会通过 Docker Compose 启动整套 Harbor 服务。启动后检查进程docker compose ls docker ps | grep harborHarbor 启动较慢尤其是第一次初始化数据库时可能需要一分钟以上。看到所有容器处于 running 或 healthy 状态后再用浏览器访问http://127.0.0.1:18080验证。3.4 启动 Gitea 和 Drone环境变量和 Compose 文件准备好后先启动 Gitea 和 Drone 服务cd /opt/cicd docker compose up -d docker compose ps这一步终端不会返回太多日志观察容器状态即可。Gitea 首次启动后访问http://127.0.0.1:3000或通过 Nginx 访问 https 地址完成初始安装。注意 Gitea 初始安装页面上的“数据库类型”选择 SQLite 即可跑通流程生产环境建议安装后把数据迁移到 PostgreSQL或者直接使用GITEA__database__DB_TYPE等环境变量连接外部数据库。4. 配置 Nginx 统一反向代理与 HTTPS4.1 为什么使用宿主机 NginxHarbor 安装脚本内部带了 NginxGitea 和 Drone 又各自提供服务如果每个服务都自己绑定 80/443端口会冲突。把 Nginx 放到宿主机对外只暴露 80 和 443内部服务全部监听127.0.0.1端口端口管理会非常清晰也方便统一控制 TLS 证书和数据备份。安装 Nginxsudo apt install -y nginx sudo systemctl enable nginx sudo systemctl start nginx4.2 编写反向代理配置在/etc/nginx/conf.d/cicd.conf中写入三个域名对应的 Server 配置。证书文件放在/opt/cicd/certs下假设已经通过 Lets Encrypt 或内部 CA 生成ssl_certificate /opt/cicd/certs/example.com.pem; ssl_certificate_key /opt/cicd/certs/example.com.key; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; upstream gitea_backend { server 127.0.0.1:3000; } upstream drone_backend { server 127.0.0.1:8080; } upstream harbor_backend { server 127.0.0.1:18080; } server { listen 80; server_name git.example.com ci.example.com registry.example.com; return 301 https://$host$request_uri; } server { listen 443 ssl; http2 on; server_name git.example.com; client_max_body_size 500m; location / { proxy_pass http://gitea_backend; 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; } } server { listen 443 ssl; http2 on; server_name ci.example.com; location / { proxy_pass http://drone_backend; 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_read_timeout 300s; } } server { listen 443 ssl; http2 on; server_name registry.example.com; client_max_body_size 0; proxy_request_buffering off; location / { proxy_pass http://harbor_backend; 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; } }X-Forwarded-Proto头非常关键。Gitea 和 Harbor 会通过这个头判断当前请求是 HTTPS 还是 HTTP如果缺失页面可能陷入重定向循环或生成错误的资源链接。Harbor 域名下设置client_max_body_size 0是因为镜像层文件很大Nginx 默认的 1m 限制会导致镜像推送失败。检查配置并重载sudo nginx -t sudo systemctl reload nginx然后用浏览器分别访问三个域名。Gitea 能正常打开并完成初始安装Drone 能打开登录页并被重定向到 Gitea 授权Harbor 能打开登录页说明统一入口已经打通。4.3 端口和防火墙检查Nginx 接管外部流量后内部服务就不应该再暴露到公网。检查防火墙和云安全组时只放行 80、443以及后面需要的 SSH 端口。可以使用ss确认端口监听范围ss -tlnp | grep -E :3000|:8080|:18080正常情况下127.0.0.1前缀说明服务只监听本机回环地址外部无法直接访问。如果看到0.0.0.0:3000说明 Gitea 端口被直接暴露到了所有网卡应修改 Compose 文件中的端口映射。5. 打通 Gitea 到 Harbor 的第一条流水线5.1 在 Gitea 创建测试仓库Gitea 初始安装完成后用管理员账号登录创建一个名为demo-app的仓库。创建时可以初始化 README方便立即推送。创建完成后进入 Gitea 的 OAuth 应用管理页确认刚才填写的重定向 URI 是https://ci.example.com/login并把 Client ID 和 Secret 与 Compose 中的环境变量逐一核对。接着打开 Drone 管理界面即https://ci.example.com。第一次访问会跳到 Gitea 做 OAuth 授权授权成功后回到 Drone 页面右侧会出现 Gitea 中的仓库列表。点击仓库旁边的 Activate 按钮Drone 会自动在 Gitea 中注册 Webhook。这个步骤经常被遗漏没有激活的仓库即使推送了代码Drone 也不会收到构建请求。5.2 编写.drone.yml在demo-app仓库中创建一个最简单的 Dockerfile用来验证“构建镜像并推送 Harbor”的完整链路FROM nginx:1.25-alpine COPY index.html /usr/share/nginx/html/index.html!DOCTYPE html html headmeta charsetutf-8titledemo/title/head bodyh1demo app/h1/body /html然后在仓库根目录创建.drone.ymlkind: pipeline type: docker name: build-push volumes: - name: docker_sock host: path: /var/run/docker.sock trigger: event: - push branch: - main steps: - name: build image: docker:24 volumes: - name: docker_sock path: /var/run/docker.sock commands: - docker build -t registry.example.com/demo/demo-app:$DRONE_COMMIT_SHA . - name: push image: docker:24 volumes: - name: docker_sock path: /var/run/docker.sock environment: HARBOR_USERNAME: from_secret: harbor_username HARBOR_PASSWORD: from_secret: harbor_password commands: - echo $HARBOR_PASSWORD | docker login registry.example.com -u $HARBOR_USERNAME --password-stdin - docker push registry.example.com/demo/demo-app:$DRONE_COMMIT_SHA这个文件展示了 Drone 流水线的三个常见机制。第一个是volumes。Drone Runner 通过 Docker socket 来创建流水线容器如果想在流水线里执行docker build需要把宿主机的/var/run/docker.sock挂载到对应步骤容器中。这里的docker_sock卷就是为此服务的。第二个是内置变量$DRONE_COMMIT_SHA。Drone 在执行每个步骤时会把提交 SHA、分支、构建编号等作为环境变量注入容器镜像 tag 使用提交 SHA 可以避免镜像被latest覆盖后无法回溯。第三个是from_secret。明文密码写进.drone.yml会随着仓库历史一起泄露正确的做法是在 Drone 管理页面中配置 Secret。路径为“仓库 - Settings - Secrets”添加harbor_username和harbor_password两个 Secret流水线中通过from_secret引用。5.3 推送代码并观察构建把代码推送到 Gitea 的 main 分支后打开 Drone 中的demo-app仓库应该能看到一条新的构建记录。点击构建可以查看每个步骤的日志正常流程会依次显示 build 和 push 两个步骤都成功Step build : docker build -t registry.example.com/demo/demo-app:xxxxxxx . Step push : docker push registry.example.com/demo/demo-app:xxxxxxx构建日志里出现Pushed或Digest字样说明镜像已经进入 Harbor。5.4 验证 Harbor 中的镜像登录 Harbor 管理界面进入demo项目可以看到刚刚推送的镜像和 tag。如果之前没有创建项目需要在 Harbor 中先创建一个名为demo的公开或私有项目否则docker push registry.example.com/demo/demo-app会因为项目不存在而失败。也可以在服务器上用命令行验证curl -u 用户名:密码 https://registry.example.com/v2/_catalog输出 JSON 中如果包含demo-app说明镜像仓库接口正常。生产环境建议为 CI 创建 Harbor Robot Account而不是直接使用管理员账号。Robot Account 的权限可以限定在某个项目即使泄漏也不会影响整个 Harbor。6. 生产环境加固、常见问题排查与扩展方向6.1 这套部署里最容易踩的坑第一坑是域名配置不一致。Gitea 的ROOT_URL、Drone 的DRONE_GITEA_SERVER、Harbor 的hostname、Nginx 的server_name必须全部指向同一个外部域名只要有一个地方填了localhost或内网 IP页面跳转和 Webhook 回调就会断掉。第二坑是 RPC 密钥不一致。Drone Server 和 Runner 的DRONE_RPC_SECRET必须相同不一致时 Runner 日志会出现认证失败构建任务卡
返回列表