☰
Docker 搭建 Drone CI/CD:轻量级持续集成实战指南
2026/10/14 1:04:10 网站建设 项目流程

1. 为什么我最终选择了 Drone 而不是 Jenkins

1.1 从一次痛苦的 Jenkins 维护经历说起

前两年我在一个中小型研发团队里负责基础设施维护,当时团队用的是 Jenkins 做持续集成。说实话,Jenkins 功能确实强大,插件生态也丰富,但维护成本真的让人头疼。每次升级插件都像拆盲盒,指不定哪个插件就跟核心版本不兼容了,构建节点偶尔莫名其妙掉线,Groovy 脚本写起来调试成本也高。最要命的是,团队里新来的同学想改个构建流程,光理解那一堆插件配置就得花大半天。

后来我接触到了 Drone CI,第一印象就是轻量。它整个架构非常清晰,一个 Server 负责调度和 Web 界面,一个 Runner 负责实际执行构建任务,两者都是独立的容器,用 Docker 就能跑起来。配置文件就是一个.drone.yml,放在项目根目录,语法简洁直观,Pipeline 的每一步都是一个 Docker 容器,环境隔离天然做好。对于我这种喜欢“基础设施即代码”思路的人来说,Drone 的设计哲学非常对胃口。

这篇文章我想把用 Docker 搭建 Drone CI/CD 的完整过程梳理一遍,包括架构原理、部署步骤、配置细节、Runner 选型、密钥管理,以及我在实际使用中踩过的坑。适合那些想给团队搭一套轻量持续集成系统、又不想被 Jenkins 复杂度折磨的开发者,也适合个人项目想要自动化构建部署的朋友。即使你之前没接触过 CI/CD,跟着走一遍也能理解整套流程是怎么运转的。

1.2 Drone 的核心架构到底长什么样

在动手之前,有必要先把 Drone 的架构讲清楚,不然部署的时候容易一头雾水。Drone 采用的是 Server 和 Runner 分离的架构,这个设计跟 Jenkins 的 Master/Agent 模式有点类似,但实现上更彻底地拥抱了容器化。

Drone Server是整个系统的大脑,它负责跟代码托管平台(比如 GitHub、GitLab、Gitea 等)通信,接收 Webhook 事件,解析.drone.yml配置文件,然后把具体的构建任务分发给 Runner。Server 本身不执行任何构建,它只做调度和状态管理,构建日志、构建状态这些数据都存在 Server 这边的数据库里。

Drone Runner是真正干活的角色,它从 Server 那里领取任务,然后根据 Pipeline 的定义,一步步拉起 Docker 容器来执行。Runner 有好几种类型,最常用的是 Docker Runner,它直接在宿主机上调用 Docker 守护进程来跑容器。还有 Kubernetes Runner,适合已经有 K8s 集群的团队。Runner 可以横向扩展,多个 Runner 同时从 Server 领取任务,天然支持并发构建。

两者之间通过一个共享的密钥(DRONE_RPC_SECRET)来认证通信,Server 和 Runner 必须配置相同的密钥才能正常握手。这个细节很关键,我第一次部署的时候就因为两边密钥不一致,Runner 一直连不上 Server,排查了半天。

理解了这套架构,后面的部署就顺理成章了:先起 Server,再起 Runner,两者通过 RPC 通信,构建任务在 Runner 侧的容器里执行。

2. 部署前的环境准备与关键决策

2.1 服务器和域名这些基础设施怎么定

部署 Drone 之前,有几样东西需要提前准备好。首先是一台能跑 Docker 的服务器,配置不用太高,2 核 4G 起步就够中小团队用了,因为真正的构建负载是在 Runner 拉起的容器里,Server 本身很轻。我实测下来,2 核 4G 的机器跑 Server 加一个 Docker Runner,同时处理两三个构建任务没什么压力。

其次是一个域名。Drone Server 需要跟代码托管平台做 OAuth 认证,回调地址必须是可访问的域名,用 IP 虽然理论上也行,但 OAuth 回调配置起来很别扭,而且后续 Webhook 也需要公网可达的地址。所以强烈建议准备一个域名,解析到服务器 IP。

第三是代码托管平台的 OAuth 应用。Drone 支持 GitHub、GitLab、Gitea、Bitbucket 等主流平台。你需要去对应平台创建一个 OAuth Application,拿到Client ID和Client Secret,这两个值在启动 Server 的时候要用。创建 OAuth 应用的时候,回调地址填https://你的域名/login,这个路径是 Drone 固定的。

最后是HTTPS 证书。Drone 的 OAuth 流程和 Webhook 都建议走 HTTPS,虽然 HTTP 也能跑,但很多代码托管平台对 Webhook 的 HTTPS 有要求,而且明文传输密钥不安全。证书可以用 Let's Encrypt 免费申请,也可以用云服务商提供的。我一般会在服务器前面放一个 Nginx 做反向代理,顺便处理证书和域名转发。

2.2 用 Docker Compose 还是纯 Docker 命令

部署方式上,我强烈推荐用Docker Compose,而不是一条条敲docker run。原因很简单:Drone 至少涉及 Server 和 Runner 两个容器,还有数据库(默认用内置的 SQLite,生产建议用 PostgreSQL 或 MySQL),用 Compose 可以把这些服务的依赖关系、网络、卷、环境变量都写在一个文件里,版本管理和迁移都方便。

纯 Docker 命令的问题在于,一旦容器多了,参数记不住,重启顺序也容易搞错,而且没法版本化。Compose 文件往 Git 仓库一放,换台机器直接docker compose up -d就起来了,这才是基础设施即代码的正确姿势。

数据库这块,小团队或者个人用内置 SQLite 完全够,数据文件挂载到宿主机就行。但如果团队规模大、构建频繁,建议单独起一个 PostgreSQL 容器,性能和并发能力都更好。我下面会以 PostgreSQL 为例来写,因为这是生产环境更稳妥的选择。

2.3 网络和端口规划要提前想清楚

端口规划上,Drone Server 默认监听 80 和 443,但因为我们前面有 Nginx 做反向代理,所以 Server 实际只需要暴露一个内部端口,比如 8000,让 Nginx 转发过去就行。Runner 不需要暴露任何端口,它是主动去连 Server 的,属于出站连接。

网络方面,Compose 会自动创建一个内部网络,Server、Runner、数据库都在这个网络里,通过服务名互相访问。这样数据库端口不用暴露到宿主机,安全性更好。Nginx 如果也跑在 Compose 里,可以直接加入这个网络;如果 Nginx 是宿主机上单独装的,那就把 Server 的端口映射到宿主机127.0.0.1:8000,只允许本机访问,再由 Nginx 转发。

这里有个细节要注意:Runner 需要访问宿主机的 Docker 守护进程,所以要把/var/run/docker.sock挂载进 Runner 容器。这是 Docker Runner 的工作机制决定的,它通过这个 socket 来创建和管理构建容器。挂载 socket 意味着 Runner 容器对宿主机 Docker 有完全控制权,所以 Runner 容器的安全边界要重视,不要在上面跑不可信的镜像。

3. 一步步搭建 Drone Server 和 Runner

3.1 编写 Docker Compose 编排文件

先创建一个工作目录,比如/opt/drone,然后在里面新建docker-compose.yml。下面是我实际在用的一个配置,包含 PostgreSQL、Drone Server 和 Docker Runner 三个服务。

version: "3.8" services: drone-db: image: postgres:15-alpine container_name: drone-db restart: always environment: POSTGRES_USER: drone POSTGRES_PASSWORD: 换成你自己的强密码 POSTGRES_DB: drone volumes: - ./data/postgres:/var/lib/postgresql/data networks: - drone-net drone-server: image: drone/drone:2 container_name: drone-server restart: always depends_on: - drone-db ports: - "127.0.0.1:8000:80" volumes: - ./data/drone:/data environment: DRONE_GITEA_SERVER: https://你的代码托管平台地址 DRONE_GITEA_CLIENT_ID: 你的ClientID DRONE_GITEA_CLIENT_SECRET: 你的ClientSecret DRONE_RPC_SECRET: 换成一个随机长字符串 DRONE_SERVER_HOST: drone.你的域名.com DRONE_SERVER_PROTO: https DRONE_DATABASE_DRIVER: postgres DRONE_DATABASE_DATASOURCE: postgres://drone:你的数据库密码@drone-db:5432/drone?sslmode=disable DRONE_USER_CREATE: username:你的管理员用户名,admin:true networks: - drone-net drone-runner: image: drone/drone-runner-docker:1 container_name: drone-runner restart: always depends_on: - drone-server volumes: - /var/run/docker.sock:/var/run/docker.sock environment: DRONE_RPC_PROTO: http DRONE_RPC_HOST: drone-server DRONE_RPC_SECRET: 跟Server保持一致 DRONE_RUNNER_CAPACITY: 2 DRONE_RUNNER_NAME: docker-runner-1 networks: - drone-net networks: drone-net: driver: bridge

这份配置里有几个地方需要重点说明。DRONE_RPC_SECRET在 Server 和 Runner 里必须完全一致,这是两者握手的凭证,建议用openssl rand -hex 16生成一个随机字符串。DRONE_USER_CREATE这个环境变量用来在首次启动时创建一个管理员账户,格式是username:你的用户名,admin:true,这个用户名必须跟你代码托管平台上的用户名一致,否则管理员权限不会生效。

DRONE_RUNNER_CAPACITY控制这个 Runner 能同时跑几个构建任务,我设成 2,意思是同时最多两个 Pipeline 在跑,超出的会排队。这个值要根据服务器配置来定,构建任务吃 CPU 和内存,设太高容易把机器压垮。

3.2 配置 Nginx 反向代理和 HTTPS

Server 起来之后,还需要 Nginx 把外部 HTTPS 请求转发到127.0.0.1:8000。下面是一个典型的 Nginx 配置片段。

server { listen 443 ssl http2; server_name drone.你的域名.com; ssl_certificate /etc/letsencrypt/live/drone.你的域名.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/drone.你的域名.com/privkey.pem; 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:8000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_read_timeout 300s; } }

这里proxy_read_timeout我设成了 300 秒,因为构建日志是流式输出的,如果超时太短,长时间运行的构建任务日志会断掉。Upgrade和Connection这两个头是为了支持 WebSocket,Drone 的实时日志推送依赖它。

配置好之后nginx -t测试一下语法,没问题就systemctl reload nginx。然后访问https://drone.你的域名.com,应该能看到 Drone 的登录页面,点击登录会跳转到代码托管平台授权,授权完就进到 Dashboard 了。

3.3 验证 Server 和 Runner 是否正常握手

登录进去之后,第一件事是确认 Runner 有没有连上。在 Dashboard 左侧菜单找到 Runners 页面,如果配置正确,应该能看到一个名为docker-runner-1的 Runner,状态是绿色的,显示在线。

如果 Runner 显示离线,八成是DRONE_RPC_SECRET两边不一致,或者 Runner 连不上 Server。排查的时候先看 Runner 的日志:

docker logs drone-runner

正常的话会看到类似poller: request stage from remote server的日志,说明它在正常轮询任务。如果看到cannot connect to server或者invalid token,那就是网络或者密钥的问题。Server 的日志用docker logs drone-server看,重点看有没有 OAuth 相关的报错。

我第一次部署的时候,Runner 一直连不上,最后发现是 Compose 里 Server 的服务名写成了drone-server,但 Runner 的DRONE_RPC_HOST写的是drone,名字对不上,容器网络里解析不到。这种低级错误排查起来最费时间,所以配置里的服务名一定要仔细核对。

4. 编写第一个可用的 .drone.yml 流水线

4.1 Pipeline 的基本结构和语法要点

Drone 的流水线定义在项目根目录的.drone.yml文件里。它的核心概念是pipeline,一个 pipeline 里包含多个step,每个 step 本质上是一个 Docker 容器,按顺序执行。下面是一个最基础的例子,用来构建一个 Node.js 项目。

kind: pipeline type: docker name: default steps: - name: install image: node:18-alpine commands: - npm ci - name: test image: node:18-alpine commands: - npm run test - name: build image: node:18-alpine commands: - npm run build

kind: pipeline和type: docker是固定写法,表示这是一个 Docker 类型的流水线。name是这个 pipeline 的名字,一个.drone.yml里可以有多个 pipeline,通过depends_on来定义依赖关系。每个 step 的image指定用哪个 Docker 镜像,commands是要执行的命令列表。

这里有个很重要的机制:同一个 pipeline 里的所有 step 共享一个工作目录,也就是代码仓库被克隆下来的那个目录。所以install步骤装的依赖,test和build步骤能直接用。但要注意,每个 step 是独立的容器,容器里的环境变量、系统级安装的软件是不共享的,只有工作目录里的文件共享。这个特性决定了你在写 Pipeline 的时候,要么把依赖装在项目目录里(比如node_modules),要么在每个 step 里重新装系统依赖。

4.2 用缓存加速构建的实操配置

每次构建都重新装依赖,时间长了很浪费。Drone 提供了缓存机制,可以把node_modules、Maven 的.m2、Go 的模块缓存这些目录缓存起来,下次构建直接复用。下面是一个带缓存的配置示例。

steps: - name: restore-cache image: drillster/drone-volume-cache settings: restore: true mount: - ./node_modules volumes: - name: cache path: /cache - name: install image: node:18-alpine commands: - npm ci - name: rebuild-cache image: drillster/drone-volume-cache settings: rebuild: true mount: - ./node_modules volumes: - name: cache path: /cache volumes: - name: cache host: path: /tmp/drone-cache

这个配置的思路是:构建开始时先把缓存的node_modules恢复到工作目录,然后npm ci只装缺失的包,构建结束后再把更新后的node_modules存回缓存。drillster/drone-volume-cache这个镜像专门干这个事,它把宿主机的/tmp/drone-cache目录挂载到容器里做持久化。

这里有个坑要注意:缓存目录的路径是相对于工作目录的,./node_modules前面的./不能省。另外,如果多个项目共用同一个 Runner,缓存目录最好按项目区分,否则不同项目的node_modules会互相覆盖。我一般会在宿主机上给每个项目建一个独立的缓存目录。

4.3 构建镜像并推送到私有仓库

CI 的常见需求之一是构建 Docker 镜像并推送到镜像仓库。Drone 有个官方插件drone/docker,用起来很方便。下面是一个构建并推送镜像的 step。

- name: build-image image: plugins/docker settings: registry: registry.你的域名.com repo: registry.你的域名.com/你的项目名 tags: - latest - ${DRONE_COMMIT_SHA:0:8} username: from_secret: docker_username password: from_secret: docker_password when: branch: main event: push

tags里用了 Drone 内置的变量${DRONE_COMMIT_SHA:0:8},表示取 commit SHA 的前 8 位作为镜像 tag,这样每个提交都有唯一对应的镜像,方便回滚。when条件限制了只有 main 分支的 push 事件才触发这个 step,避免每个分支都推镜像。

from_secret是 Drone 的密钥引用语法,docker_username和docker_password这两个密钥需要在 Drone 的 Web 界面里配置,或者用命令行工具drone secret add添加。密钥不会出现在日志里,Drone 会自动做脱敏处理,这点比在配置文件里写明文安全得多。

5. 密钥管理、通知机制与多环境部署

5.1 密钥的三种管理方式对比

Drone 里管理敏感信息有几种方式,各有适用场景,我整理了一个对比表。

方式配置位置适用场景安全性
Repository Secret仓库级别单个项目专用的密钥高,仅该仓库可见
Organization Secret组织级别多个项目共用的密钥中,组织内所有仓库可见
环境变量直接写.drone.yml非敏感配置低,明文暴露

Repository Secret 是最常用的,在 Drone 的仓库设置页面里添加,或者用命令行drone secret add --repository 你的仓库 --name 密钥名 --value 密钥值。添加之后,在.drone.yml里用from_secret引用。Organization Secret 适合那种多个项目都要用的密钥,比如公司统一的镜像仓库账号,配置一次所有项目都能用。

有个细节值得注意:Drone 默认对 Pull Request 事件是不注入密钥的,这是为了防止有人通过 PR 恶意读取密钥。如果你的 Pipeline 需要在 PR 里用到密钥,得在密钥配置里勾选允许 PR 访问,但要谨慎,因为这意味着任何能提 PR 的人都能拿到密钥。我一般不建议开这个选项,PR 阶段只跑测试和 lint,不碰密钥。

5.2 构建结果通知怎么配才不烦人

构建成功失败的通知,配得好能提升效率,配得不好就是骚扰。Drone 支持多种通知方式,最常用的是邮件、Slack、钉钉、企业微信这些。我个人的经验是,只在失败时通知,成功时静默,这样能大幅减少噪音。

下面是一个钉钉通知的配置示例,用的是社区维护的插件。

- name: notify image: appleboy/drone-dingtalk settings: token: from_secret: dingtalk_token type: markdown message_color: true message_pic: true sha_link: true when: status: - failure

when.status设成failure,意思是只有构建失败才发通知。如果想成功失败都通知,就写成[ success, failure ]。token从密钥里读,不要写明文。

这里有个实操心得:通知内容里最好带上构建的 commit 信息、分支名、构建链接,这样收到通知的人能直接点进去看日志,不用再去 Drone 界面里翻。很多通知插件都支持模板变量,配置的时候花几分钟调一下,长期用下来体验差别很大。

5.3 多环境部署的 Pipeline 拆分思路

实际项目里,通常有测试环境、预发布环境、生产环境,不同环境的部署策略不一样。Drone 里可以用多个 pipeline 加depends_on来实现这种编排。下面是一个典型的三段式结构。

kind: pipeline type: docker name: build steps: - name: compile image: node:18-alpine commands: - npm ci - npm run build --- kind: pipeline type: docker name: deploy-staging steps: - name: deploy image: appleboy/drone-ssh settings: host: 测试环境服务器 username: deploy key: from_secret: ssh_key script: - cd /app && docker compose pull && docker compose up -d depends_on: - build trigger: branch: - main

---用来分隔多个 pipeline。deploy-staging通过depends_on: [build]依赖前面的构建 pipeline,只有构建成功了才会执行部署。trigger限制了触发条件,只有 main 分支才部署到测试环境。

生产环境的部署我一般会加一个when条件,或者用 Drone 的 promotion 功能,需要手动在界面上点一下才触发,避免误部署。这种“构建自动、部署手动”的模式在团队里很受欢迎,既保证了构建的及时性,又给部署留了一道人工确认的关口。

6. 常见问题排查与实战避坑经验

6.1 Runner 连不上 Server 的排查清单

这是新手最容易遇到的问题,我整理了一个排查顺序,按这个顺序走基本能定位到原因。

排查项检查方法常见问题
RPC 密钥对比 Server 和 Runner 的 DRONE_RPC_SECRET两边不一致或含特殊字符
服务名解析在 Runner 容器里 ping Server 服务名Compose 服务名写错
网络连通检查是否在同一 Docker 网络网络配置遗漏
Server 状态看 Server 日志有无报错数据库连接失败导致 Server 没起来

我遇到最多的是密钥不一致,尤其是密钥里带了特殊字符,在 YAML 里没加引号导致解析出错。建议密钥用纯十六进制字符串,避免转义问题。另外,如果 Server 因为数据库连不上没起来,Runner 自然连不上,所以排查时先确认 Server 本身是健康的。

6.2 构建容器权限不足的典型场景

Docker Runner 拉起的构建容器,默认是以 root 用户运行的,但有些操作还是会受限。比如在容器里执行docker命令,就需要把宿主机的 Docker socket 挂载到构建容器里。这个需求在“在 CI 里构建镜像”的场景很常见。

配置方式是在 step 里加volumes:

- name: build-image image: docker:24-cli volumes: - name: docker-socket path: /var/run/docker.sock commands: - docker build -t myapp . volumes: - name: docker-socket host: path: /var/run/docker.sock

但这里有个安全隐患:挂载了 Docker socket 的容器,等于拥有了宿主机的 root 权限,可以操作宿主机上的任何容器。所以这种 step 只应该在可信的代码上运行,绝对不能让外部 PR 触发。我一般会把这类 step 的when条件限制得很死,只允许特定分支和特定事件触发。

6.3 构建超时和资源限制的调优

构建任务跑太久被中断,或者把服务器资源吃满,也是常见问题。Drone 的每个 step 可以设置超时时间,Runner 层面也可以限制资源。

在 step 里加timeout字段,单位是秒:

- name: long-task image: node:18-alpine commands: - npm run build timeout: 1800

timeout: 1800表示这个 step 最多跑 30 分钟,超时会被强制终止。默认超时是 1 小时,对于大多数任务够用,但有些编译型项目可能需要调大。

资源限制方面,Docker Runner 支持在 step 里设置 CPU 和内存限制:

- name: build image: node:18-alpine commands: - npm run build resources: limits: cpu: 2000 memory: 2048MiB

cpu: 2000表示限制 2 个 CPU 核心,memory: 2048MiB表示限制 2GB 内存。这个配置能防止某个构建任务把整台机器拖垮。我建议给每个 step 都设上合理的资源限制,尤其是内存,因为内存超了会触发 OOM,影响其他任务。

6.4 我踩过的几个印象深刻的坑

第一个坑是工作目录的权限问题。有一次构建任务里生成的文件,在后续 step 里读不了,报权限错误。原因是不同 step 的容器虽然共享工作目录,但容器里的用户 UID 可能不一样,导致文件属主混乱。解决办法是在 step 里统一指定user,或者用chmod调整权限。我后来养成的习惯是,在 Pipeline 开头加一个 step 统一设置工作目录权限。

第二个坑是缓存目录把磁盘撑爆。缓存机制虽然好用,但如果不清理,日积月累会把宿主机磁盘占满。我现在的做法是定期清理缓存目录,或者用带过期策略的缓存方案。另外,缓存目录最好单独挂一块盘,避免影响系统盘。

第三个坑是Webhook 重复触发。有时候一次 push 会触发多次构建,排查下来是代码托管平台的 Webhook 配置里勾选了多个事件类型,或者 Drone 的 Webhook 地址配了两遍。这个问题的排查方法是看 Drone 的构建记录,如果短时间内有多个相同 commit 的构建,基本就是 Webhook 重复了。

第四个坑是日志脱敏不彻底。Drone 会对密钥做脱敏,但如果密钥被 base64 编码或者拼接后输出,脱敏就可能失效。我有一次在脚本里echo了一个包含密钥的 URL,结果密钥明文出现在日志里。从那以后,我在脚本里处理密钥都格外小心,尽量用环境变量传递,不打印任何可能包含密钥的内容。

7. 关于 Drone 这套方案我的一些真实体会

用 Drone 搭 CI/CD 这套方案,我最大的感受是“够用且省心”。它不像 Jenkins 那样需要维护一堆插件,也不像某些商业 CI 那样绑定特定平台。整个系统就是几个容器,配置文件就是一个 YAML,迁移和备份都很简单。对于中小团队和个人项目来说,这套方案的性价比很高。

当然它也有局限。比如 Pipeline 的调试体验不如本地跑脚本直观,出错了得看日志慢慢排查;再比如它的插件生态没有 Jenkins 那么庞大,有些特殊需求得自己写脚本或者找社区镜像。但这些在我看来都是可以接受的取舍,毕竟它换来了极低的维护成本。

如果你打算上手,我的建议是先用一台测试服务器把整套流程跑通,包括 OAuth 配置、Runner 握手、一个简单的构建任务,确认没问题了再往生产环境迁。配置过程中把每个环境变量的作用搞清楚,别照抄了事,因为一旦出问题,理解原理能帮你快速定位。密钥管理从一开始就规范起来,用 Secret 而不是明文,这个习惯越早养成越好。

最后分享一个小技巧:Drone 的 CLI 工具drone很好用,可以在命令行里管理密钥、触发构建、查看日志,配合脚本能实现不少自动化操作。装好之后drone info看一下连接状态,日常维护会方便很多。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询