☰
Docker 搭建轻量级 CI/CD:Drone 从部署到流水线实战
2026/10/14 2:49:44 网站建设 项目流程

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

1.1 从一次痛苦的流水线维护说起

前两年我接手了一个中小型研发团队的基础设施维护工作,当时他们用的是一套跑了两三年的 Jenkins。说实话,Jenkins 本身没什么问题,插件生态丰富、社区庞大,但真正让我头疼的是它的维护成本。那台 Jenkins 主节点上装了四十多个插件,每次升级都像拆炸弹,插件之间版本冲突、Groovy 脚本报错、构建节点莫名其妙掉线,几乎每个月都要花一两天时间处理这些破事。更麻烦的是,团队里新来的同学想改一条流水线,得先学一遍 Jenkinsfile 的语法,再理解一堆共享库的约定,学习曲线陡得吓人。

后来我开始认真评估替代方案,目标很明确:配置要声明式、部署要容器化、维护成本要低、和 Git 平台集成要顺滑。GitLab CI 我试过,功能确实强,但它和 GitLab 绑得太死,我们代码仓库分散在几个不同的平台上,用起来别扭。Tekton 太底层,写一条流水线要定义一堆 CRD,对中小团队来说属于杀鸡用牛刀。最后落到 Drone 上,原因很简单——它整个系统就是几个 Docker 容器,所有流水线配置就是一个放在代码根目录的.drone.yml文件,语法直观,和 Git 平台通过 Webhook 对接,几乎零学习成本。

Drone 的定位其实很清晰:它是一个轻量级的、容器原生的持续集成与持续交付系统。所谓容器原生,意思是它的每一步构建、测试、部署动作,默认都是在一个独立的 Docker 容器里跑的。这一点和 Jenkins 那种"在宿主机上跑脚本"的思路完全不同,带来的好处是环境隔离彻底、构建结果可复现、清理起来也干净。你不需要在构建机上预装 Node、Python、Go 这些乱七八糟的运行时,需要什么镜像,流水线里写一行image: node:18就行。

1.2 Drone 的架构到底长什么样

在动手搭之前,我建议先把 Drone 的组件关系搞清楚,不然配起来会一头雾水。Drone 的核心其实就两个服务:

  • Drone Server:负责和 Git 平台通信、接收 Webhook、解析.drone.yml、调度构建任务、提供 Web 界面。它本身不干活,只做指挥。
  • Drone Runner:真正执行构建的组件。它从 Server 那里领任务,然后拉起一个个 Docker 容器来跑流水线里的每一步。Runner 可以有多个,也可以按类型分(比如 Docker Runner、Kubernetes Runner、SSH Runner)。

除了这两个,还需要一个数据库给 Server 存元数据(构建记录、用户、仓库配置等),以及一个版本控制平台作为代码源和触发入口。数据库官方支持 SQLite、MySQL、PostgreSQL,生产环境我强烈建议用 PostgreSQL,SQLite 在并发构建多的时候容易锁表。

整个数据流是这样的:你在 Git 平台提交代码 → Git 平台通过 Webhook 通知 Drone Server → Server 拉取.drone.yml并解析 → Server 把任务派给 Runner → Runner 拉镜像、起容器、执行步骤 → 执行结果回传给 Server → Server 更新界面状态并通知 Git 平台。理解这条链路,后面排查问题的时候你就知道该看哪个环节的日志了。

1.3 用 Docker 搭建的三大实际好处

为什么标题里特意强调"docker 搭建"?因为用 Docker 部署 Drone 不是图省事,而是有实打实的收益。

第一,环境一致性。Server、Runner、数据库全部跑在容器里,版本、依赖、配置都固化在镜像和 compose 文件里。换一台机器,把 compose 文件拷过去docker compose up -d,几分钟就能拉起一套一模一样的环境。我试过在一台全新的云主机上从零到跑通第一条流水线,前后不到二十分钟。

第二,升级和回滚极其简单。Drone 发版挺勤的,用 Docker 的话升级就是改一下镜像 tag 然后重启容器,出问题把 tag 改回去再重启,几十秒的事。相比之下,二进制部署要处理 systemd、配置文件路径、权限一堆东西。

第三,Runner 的隔离性天然契合。Drone Runner 本身就需要访问宿主机的 Docker daemon 来创建构建容器,把它自己也放在容器里跑,通过挂载/var/run/docker.sock就能实现,架构上非常自然。当然这里有个安全注意点,后面我会专门讲。

2. 搭建前的准备工作与关键选型

2.1 服务器与域名的基础要求

先说硬件。Drone Server 本身很轻,1 核 1G 就能跑起来,但真正吃资源的是 Runner 执行的构建任务。如果你要构建前端项目、编译 Go 或者打 Docker 镜像,建议至少 2 核 4G 起步,磁盘留 40G 以上,因为构建过程中会拉大量镜像,Docker 的镜像层很占空间。我踩过一次坑,一台 20G 磁盘的机器跑了两个月,镜像和构建缓存把盘塞满了,导致所有构建失败,排查了半天才发现是磁盘问题。

域名方面,Drone Server 需要一个能被 Git 平台和浏览器访问到的地址。因为 Webhook 回调、OAuth 授权都依赖这个地址,所以不能用 localhost 或者内网 IP(除非你的 Git 平台也在同一内网)。生产环境建议配一个独立子域名,比如drone.yourdomain.com,然后上 HTTPS。Drone 对 HTTPS 的支持很直接,它自己不做证书管理,通常的做法是在前面放一个反向代理(Nginx、Caddy、Traefik 都行)来终结 TLS。

这里有个细节很多人会忽略:DRONE_SERVER_HOST这个环境变量必须填外部可访问的域名,而不是容器名或者内网地址。因为 Drone 生成 Webhook 回调地址、OAuth 重定向地址时用的就是这个值,填错了会导致授权失败或者 Webhook 收不到。

2.2 Git 平台 OAuth 应用怎么创建

Drone 不自己管理用户密码,它完全依赖 Git 平台的 OAuth 来做身份认证。所以搭建的第一步,是在你的 Git 平台上创建一个 OAuth 应用。不同平台入口不一样,但需要填的信息大同小异:

配置项填写内容说明
应用名称Drone CI随意,能识别就行
主页地址https://drone.yourdomain.comDrone 的访问地址
回调地址https://drone.yourdomain.com/login必须是这个路径
权限范围仓库读写、用户信息、Webhook按平台要求勾选

创建完之后你会拿到一对Client ID和Client Secret,这两个值后面配置 Server 的时候要用。Secret 只显示一次,记得当场保存好,丢了就得重新生成。

注意:回调地址的路径是固定的/login,不要自作主张改成/oauth/callback之类的,Drone 只认这个路径。我第一次配的时候想当然改了,结果授权一直报 redirect_uri 不匹配,查了半天文档才发现。

2.3 共享密钥的生成与作用

Drone Server 需要一个DRONE_RPC_SECRET,这是 Server 和 Runner 之间通信的凭证。Runner 拿着这个 secret 去连 Server,Server 验证通过才派任务。这个值必须足够随机,生成方式很简单:

openssl rand -hex 16

输出一串 32 位的十六进制字符串,复制下来备用。Server 和 Runner 两边都要配成一样的值,不一致的话 Runner 会一直连不上 Server,日志里会刷invalid rpc secret之类的错误。这个 secret 一旦泄露,别人就能伪造 Runner 接入你的 Server,所以别提交到代码仓库里。

3. 用 Docker Compose 编排整套系统

3.1 目录结构与 compose 文件设计

我习惯把 Drone 相关的文件放在一个独立目录里,结构清晰,备份和迁移都方便:

drone/ ├── docker-compose.yml ├── .env └── data/ └── postgres/

.env文件放敏感配置,docker-compose.yml放服务定义,data/挂载数据库数据做持久化。这种分离的好处是 compose 文件可以进版本库,而.env加进.gitignore不进库。

下面是完整的 compose 文件,我加了详细注释:

version: "3.8" services: drone-server: image: drone/drone:2 container_name: drone-server ports: - "8000:80" # 映射到宿主机 8000,前面用 Nginx 反代 volumes: - ./data/drone:/data # 持久化 Drone 自身数据 environment: - DRONE_GITEA_SERVER=https://git.yourdomain.com # 换成你的 Git 平台地址 - DRONE_GITEA_CLIENT_ID=${GITEA_CLIENT_ID} - DRONE_GITEA_CLIENT_SECRET=${GITEA_CLIENT_SECRET} - DRONE_RPC_SECRET=${DRONE_RPC_SECRET} - DRONE_SERVER_HOST=drone.yourdomain.com - DRONE_SERVER_PROTO=https - DRONE_DATABASE_DRIVER=postgres - DRONE_DATABASE_DATASOURCE=postgres://drone:${DB_PASSWORD}@drone-db:5432/drone?sslmode=disable - DRONE_USER_CREATE=username:yourname,admin:true # 首个管理员 depends_on: - drone-db restart: always drone-runner: image: drone/drone-runner-docker:1 container_name: drone-runner volumes: - /var/run/docker.sock:/var/run/docker.sock # 关键:让 Runner 能操作宿主机 Docker environment: - DRONE_RPC_PROTO=http - DRONE_RPC_HOST=drone-server - DRONE_RPC_SECRET=${DRONE_RPC_SECRET} - DRONE_RUNNER_CAPACITY=2 # 同时最多跑 2 个构建 - DRONE_RUNNER_NAME=docker-runner-01 depends_on: - drone-server restart: always drone-db: image: postgres:15-alpine container_name: drone-db volumes: - ./data/postgres:/var/lib/postgresql/data environment: - POSTGRES_USER=drone - POSTGRES_PASSWORD=${DB_PASSWORD} - POSTGRES_DB=drone restart: always

对应的.env文件:

GITEA_CLIENT_ID=你的client_id GITEA_CLIENT_SECRET=你的client_secret DRONE_RPC_SECRET=刚才生成的随机串 DB_PASSWORD=给数据库设一个强密码

3.2 关键环境变量逐个拆解

上面这些环境变量不是随便填的,每一个都有讲究,我挑几个容易出错的详细说。

DRONE_RUNNER_CAPACITY控制 Runner 的并发构建数。设成 2 意味着同时最多跑两条流水线,超出的任务会排队。这个值不是越大越好,它取决于你服务器的 CPU 和内存。我的经验是每 2G 内存对应 1 个并发比较稳妥,因为一个构建容器跑起来可能吃掉几百兆到 1G 内存。设太大容易把机器跑挂,设太小又会让构建排队等太久。

DRONE_USER_CREATE用来指定第一个管理员。格式是username:你的用户名,admin:true。这个用户名必须和 Git 平台上的用户名完全一致,否则管理员权限给不上去。我第一次配的时候用户名大小写搞错了,结果登录进去发现没有管理员权限,改配置重启才解决。

DRONE_DATABASE_DATASOURCE里的sslmode=disable是因为数据库和 Server 在同一个 Docker 网络里,走的是内网通信,不需要 TLS。如果你把数据库放在外部,记得改成require并配好证书。

DRONE_RPC_HOST=drone-server这里填的是容器名,因为 Runner 和 Server 在同一个 compose 网络里,Docker 的内置 DNS 会把容器名解析成对应 IP。这是 Docker Compose 的一个便利之处,不用去记 IP。

3.3 反向代理与 HTTPS 配置

Drone Server 自己监听 80 端口,前面需要一个反向代理来处理 HTTPS。我用 Nginx 举例,配置如下:

server { listen 443 ssl http2; server_name drone.yourdomain.com; ssl_certificate /etc/nginx/certs/drone.crt; ssl_certificate_key /etc/nginx/certs/drone.key; location / { proxy_pass http://127.0.0.1:8000; 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; # WebSocket 支持,Drone 的实时日志推送依赖它 proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; } }

这里最容易漏的是WebSocket 相关的三行配置。Drone 的构建日志是实时推送的,走的是 WebSocket。如果没配Upgrade和Connection头,日志页面会一直转圈或者只显示最后几行,非常影响排查问题。我一开始就漏了这几行,看着构建在跑但日志刷不出来,还以为是 Runner 的问题,折腾了好久。

证书可以用 Let's Encrypt 免费申请,也可以用云服务商提供的。配好之后访问https://drone.yourdomain.com,应该能看到 Drone 的登录页。

4. 启动、验证与第一条流水线

4.1 启动顺序与健康检查

配置齐了之后,启动命令很简单:

docker compose up -d

但启动之后别急着访问,先确认三个容器都正常起来了:

docker compose ps

正常情况下三个服务都应该是Up状态。如果drone-server反复重启,八成是数据库连不上或者环境变量有问题,用这个命令看日志:

docker compose logs -f drone-server

数据库初始化需要几秒钟,Server 第一次启动时如果报连接失败,等几秒再重启一下 Server 容器通常就好了。Runner 启动后会主动去连 Server,连上之后 Server 的日志里会出现类似runner registered的记录,看到这个就说明 Runner 接入成功了。

4.2 登录与仓库激活

浏览器打开 Drone 地址,点登录会跳转到 Git 平台授权,授权完跳回来就进主界面了。第一次进去仓库列表是空的,需要点右上角的Sync按钮同步仓库列表。同步出来之后,找到你要做 CI 的仓库,点进去,点Activate Repository激活。

激活的时候 Drone 会自动往你的仓库里加一个 Webhook,指向 Drone Server。这个 Webhook 是后续自动触发的关键,如果激活后提交代码没反应,第一件事就是去 Git 平台的仓库设置里检查 Webhook 是否存在、最近一次投递是否成功。

4.3 写一个能跑通的最小 .drone.yml

激活之后,在仓库根目录建一个.drone.yml,先写个最简单的验证一下链路:

kind: pipeline type: docker name: default steps: - name: greet image: alpine:3.18 commands: - echo "hello drone" - uname -a

提交这个文件,Drone 应该立刻触发一次构建。点进构建详情,能看到greet这一步在跑,日志里输出hello drone和系统信息。看到这个,说明从 Git 提交到 Webhook 触发、Server 调度、Runner 执行、日志回传的整条链路全通了。

提示:kind: pipeline和type: docker是 Drone 2.x 的写法,1.x 的语法不一样。网上很多老教程还是 1.x 的写法,照抄会报错,注意甄别。

4.4 一个真实项目的完整流水线示例

跑通最小示例后,来看一个贴近实战的 Node 项目流水线,包含安装依赖、跑测试、构建、打 Docker 镜像四个阶段:

kind: pipeline type: docker name: build-and-test steps: - name: install image: node:18-alpine commands: - npm ci volumes: - name: cache path: /root/.npm - name: test image: node:18-alpine commands: - npm run test depends_on: - install - name: build image: node:18-alpine commands: - npm run build depends_on: - test - name: docker-build image: plugins/docker settings: repo: registry.yourdomain.com/myapp tags: - ${DRONE_COMMIT_SHA:0:8} - latest username: from_secret: registry_user password: from_secret: registry_password depends_on: - build volumes: - name: cache host: path: /tmp/drone-cache trigger: branch: - main event: - push

这条流水线里有几个值得说的点。depends_on显式声明了步骤依赖关系,Drone 默认是并行执行没有依赖的步骤,加了depends_on才会串行。volumes挂了一个宿主机目录做 npm 缓存,这样每次构建不用重新下载所有依赖,速度能快好几倍。from_secret是从 Drone 的密钥管理里读敏感信息,不要在 yml 里写明文密码。

${DRONE_COMMIT_SHA:0:8}是 Drone 内置的变量语法,取 commit SHA 的前 8 位作为镜像 tag,这样每个提交对应一个唯一镜像,方便回滚。Drone 内置了一大批这样的变量,比如DRONE_BRANCH、DRONE_TAG、DRONE_BUILD_NUMBER,用好了能省很多事。

5. 踩坑记录与常见问题排查

5.1 Runner 连不上 Server 的几种情况

这是新手最容易遇到的问题,表现是构建一直处于pending状态,或者 Server 日志里刷 Runner 掉线的记录。排查思路按这个顺序来:

先确认 Runner 容器活着,docker compose ps看状态。然后看 Runner 日志docker compose logs drone-runner,如果报cannot connect to server,检查DRONE_RPC_HOST填的是不是 Server 的容器名,以及两个容器是不是在同一个 Docker 网络里。如果报invalid rpc secret,那就是两边的DRONE_RPC_SECRET不一致,回去核对.env文件。

还有一种隐蔽的情况:Runner 能连上 Server,但构建任务派不下来。这通常是DRONE_RUNNER_CAPACITY被占满了,或者 Runner 的标签和流水线要求的标签不匹配。Drone 支持给 Runner 打标签,流水线里可以指定node来选择特定 Runner,标签对不上任务就会一直排队。

5.2 构建容器拉镜像慢或失败

构建时拉镜像慢,多半是网络问题。可以在 Runner 的环境变量里配镜像加速,或者提前把常用镜像拉到本地。如果报pull access denied,检查镜像名拼写,以及私有镜像的认证有没有配好。

我遇到过一次很诡异的情况:某个构建步骤一直卡在Waiting for image,日志也不报错。后来发现是那个镜像的 tag 在仓库里被删了,Docker 拉取时一直重试。这种问题没有明确报错,只能靠经验判断,遇到长时间卡住先怀疑镜像本身。

5.3 挂载 docker.sock 的安全权衡

前面 compose 文件里 Runner 挂载了/var/run/docker.sock,这是让 Runner 能创建构建容器的必要条件。但这个挂载意味着Runner 容器实际上拥有了宿主机的 root 权限,因为能操作 Docker daemon 就等于能操作宿主机。这是个安全权衡,不是 bug。

在受信任的内部环境里,这个风险可以接受。但如果你的 Drone 允许外部人员提交代码触发构建,那就危险了——别人可以在.drone.yml里写恶意命令,通过 docker.sock 逃逸到宿主机。缓解办法有几个:一是限制谁能激活仓库和提交代码;二是用 rootless Docker 或者专门的构建节点隔离;三是考虑用 Kubernetes Runner 替代 Docker Runner,把隔离交给 K8s 来做。

5.4 常见问题速查表

现象可能原因排查方向
构建一直 pendingRunner 未连接或容量满看 Runner 日志、检查 capacity
日志页面不刷新反向代理缺 WebSocket 配置检查 Nginx 的 Upgrade 头
提交代码不触发Webhook 未配置或投递失败查 Git 平台 Webhook 记录
授权报 redirect 错误回调地址填错必须是/login路径
数据库连接失败密码错或数据库没起来看 Server 日志、确认 DB 状态
构建中途 OOM内存不足或并发过高降 capacity、加内存
磁盘写满构建失败镜像和缓存堆积定期docker system prune

5.5 几个我踩过的实操心得

第一个心得:定期清理构建缓存和悬空镜像。Drone 每次构建都会产生新的镜像层,时间长了磁盘会被吃光。我写了个定时任务,每周跑一次docker system prune -af --filter "until=168h",清理一周前的无用镜像和缓存。注意加until过滤,别把正在用的镜像删了。

第二个心得:密钥管理用 Drone 的 Secrets 而不是环境变量。敏感信息比如镜像仓库密码、部署密钥,配在 Drone 的仓库设置里的 Secrets 中,流水线里用from_secret引用。这样密钥不会出现在 yml 文件里,也不会在日志里被打印出来。Drone 还会自动对日志里的密钥值做脱敏。

第三个心得:给流水线加超时。默认情况下一个步骤卡住会一直等,加个timeout能避免资源被长期占用:

steps: - name: test image: node:18-alpine commands: - npm run test timeout: 600 # 单位秒,10 分钟没跑完就杀掉

第四个心得:善用when条件控制触发。不是每次提交都需要跑完整流水线,比如只在打 tag 时才构建发布镜像,只在 main 分支才部署。用when可以精细控制:

steps: - name: deploy image: alpine:3.18 commands: - ./deploy.sh when: branch: - main event: - push

这套 Drone 环境我从搭建到现在跑了快一年,中间升级过几次版本,除了改镜像 tag 重启之外没出过什么大问题。它最大的价值不是功能有多强,而是简单到团队里任何人都能看懂和修改流水线。一个新人入职,看一下.drone.yml就知道项目是怎么构建、怎么测试、怎么部署的,不需要额外的文档。这种透明和低门槛,对中小团队来说比什么高级特性都实在。

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

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

立即咨询