Drone CI/CD流水线实战:从Jenkins迁移到10秒构建
发布日期: 2026/07/27 阅读总量: 0
Drone CI/CD流水线实战:从Jenkins迁移到10秒构建

Drone CI/CD流水线实战:从Jenkins迁移到10秒构建

1. 真实场景:我被Jenkins困住了

2023年接手一个微服务项目,22个服务全部跑在Jenkins上。构建一次平均5分钟,并发只能开5个Agent,每天早上10点大家排队等构建。配置文件长达800行,全是freestyle job,维护一次要改20个地方。最痛苦的是每次修改Jenkinsfile必须重启Master。

老板说:「优化一下,构建时间压到1分钟内。」我翻了翻Jenkins的插件列表,决定换掉它。调研后选择了Drone —— 一个基于容器、轻量级、与GitLab原生集成的CI/CD系统。下面是我的迁移全记录。

2. 方案对比:Jenkins vs Drone

维度Jenkins (v2.387.1)Drone (v2.23.3)
安装方式WAR包或Docker,配置复杂Docker单容器启动,3条命令
流水线定义Jenkinsfile (Groovy) 或 Freestyle UI.drone.yml (YAML) 纯声明式
构建环境Agent节点,需预装JDK/Maven等每个step独立容器,镜像自包含
并发扩展通过增加Agent节点,资源隔离差通过增加Runner容器,天然隔离
缓存机制需插件或手动配置内置volume缓存,支持S3/MinIO
与GitLab集成需单独安装插件,webhook配置繁琐原生OAuth,一键挂接
资源占用Master 2C4G,每Agent 1C2GServer 0.5C1G,Runner 0.2C512M
构建耗时(示例Go项目)4分30秒(无缓存)1分10秒(含缓存)

结论:Drone在轻量、声明式、容器化方面完胜。但Drone的社区插件质量参差不齐,部分定制化需求不如Jenkins灵活。如果你的团队全是Groovy高手且已深度绑定Jenkins,迁移成本需要考虑。但对于新项目或想拥抱容器化的团队,Drone是更好的选择。

3. 完整实现:从零搭建Drone并配置流水线

3.1 安装Drone Server + Runner(Docker Compose版)

环境:Ubuntu 22.04,Docker 24.0.5,Docker Compose 2.20.0。

# docker-compose.yml
version: '3.8'

services:
  drone-server:
    image: drone/drone:2.23.3
    container_name: drone-server
    ports:
      - "8080:80"
      - "443:443"
    volumes:
      - ./data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - DRONE_GITLAB_SERVER=https://gitlab.yourcompany.com
      - DRONE_GITLAB_CLIENT_ID=your_client_id
      - DRONE_GITLAB_CLIENT_SECRET=your_client_secret
      - DRONE_RPC_SECRET=your_shared_secret   # 与runner通信密钥
      - DRONE_SERVER_HOST=drone.yourcompany.com
      - DRONE_SERVER_PROTO=https
      - DRONE_LOGS_TRACE=true   # 调试时开启,生产关掉
    restart: always

  drone-runner:
    image: drone/drone-runner-docker:1.8.5
    container_name: drone-runner
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    environment:
      - DRONE_RPC_PROTO=https
      - DRONE_RPC_HOST=drone.yourcompany.com
      - DRONE_RPC_SECRET=your_shared_secret
      - DRONE_RUNNER_NAME=runner-1
      - DRONE_RUNNER_CAPACITY=5      # 最大并发step数
      - DRONE_RUNNER_VOLUMES=/tmp/cache:/tmp/cache   # 共享缓存目录
      - DRONE_LIMIT_MEMORY=1024m     # 每个step最大内存
      - DRONE_LIMIT_CPUS=0.5         # 每个step最大CPU核数
    restart: always

启动:docker compose up -d。然后在GitLab中创建一个OAuth应用,回调地址填https://drone.yourcompany.com/login。第一次访问Drone用GitLab账号登录,自动同步仓库。

3.2 Go微服务项目的流水线配置

项目结构:services/user-service,使用Go 1.21,依赖Redis、PostgreSQL(测试用testcontainers)。我们需要完成:lint → test → build → push镜像 → 部署到K8s。

# .drone.yml (位于 user-service 根目录)
kind: pipeline
type: docker
name: user-service

# 全局环境变量
environment:
  GO_VERSION: "1.21.5"
  DOCKER_REGISTRY: registry.yourcompany.com
  DOCKER_IMAGE: ${DOCKER_REGISTRY}/user-service
  DOCKER_TAG: ${DRONE_COMMIT_SHA:0:7}

# 缓存配置(共享volume)
volumes:
  - name: gomod
    host:
      path: /tmp/cache/gomod
  - name: build
    host:
      path: /tmp/cache/go-build

steps:
  - name: lint
    image: golang:${GO_VERSION}
    pull: if-not-exists
    volumes:
      - name: gomod
        path: /go/pkg/mod
      - name: build
        path: /root/.cache/go-build
    commands:
      - go vet ./...
      - go install honnef.co/go/tools/cmd/staticcheck@latest
      - staticcheck ./...
    when:
      event: [push, pull_request]

  - name: test
    image: golang:${GO_VERSION}
    pull: if-not-exists
    volumes:
      - name: gomod
        path: /go/pkg/mod
      - name: build
        path: /root/.cache/go-build
    environment:
      POSTGRES_URL: postgres://test:test@postgres:5432/test?sslmode=disable
      REDIS_URL: redis://redis:6379/0
    commands:
      # 启动测试容器(使用testcontainers-go)
      - go test -v -count=1 -race -timeout 120s ./...
    when:
      event: [push, pull_request]

  - name: build
    image: golang:${GO_VERSION}
    volumes:
      - name: gomod
        path: /go/pkg/mod
      - name: build
        path: /root/.cache/go-build
    commands:
      - CGO_ENABLED=0 go build -ldflags="-s -w" -o bin/user-service ./cmd/
      - ls -lh bin/
    when:
      event: [push, tag]

  - name: docker
    image: plugins/docker:20.13.0
    volumes:
      - name: docker-sock
        path: /var/run/docker.sock
    settings:
      registry: ${DOCKER_REGISTRY}
      repo: ${DOCKER_IMAGE}
      tags:
        - ${DOCKER_TAG}
        - latest
      dockerfile: Dockerfile
      build_args:
        - GO_VERSION=${GO_VERSION}
      cache_from: "${DOCKER_IMAGE}:latest"
      insecure: false
    when:
      event: [push, tag]

  - name: deploy
    image: alpine/helm:3.12.0
    commands:
      - apk add --no-cache kubectl curl
      - curl -LO "https://dl.k8s.io/release/v1.28.0/bin/linux/amd64/kubectl"
      - chmod +x kubectl && mv kubectl /usr/local/bin/
      - kubectl config use-context prod
      - helm upgrade --install user-service ./helm \
          --set image.repository=${DOCKER_IMAGE} \
          --set image.tag=${DOCKER_TAG} \
          --namespace production
    environment:
      KUBECONFIG: /drone/src/.kube/config    # 通过Secret注入
    when:
      event: [push, tag]
      branch: main

# 服务定义(用于测试步骤中的依赖)
services:
  - name: postgres
    image: postgres:16-alpine
    environment:
      POSTGRES_USER: test
      POSTGRES_PASSWORD: test
      POSTGRES_DB: test
  - name: redis
    image: redis:7-alpine

# 触发条件
trigger:
  branch:
    - main
    - develop
    - feature/*

关键点说明:

  • 使用volumes挂载宿主目录作为Go module和build缓存,第二次构建时无需重新下载依赖,大幅提速。
  • 服务postgresredis仅用于test步骤,Drone会自动在Runner网络上启动它们,测试代码连接服务名即可。
  • docker步骤使用官方plugins/docker插件,支持缓存层,但要注意Dockerfile中必须显式使用--cache-from参数,这里通过settings.cache_from指定。
  • deploy步骤中直接使用kubectl和helm,Kubeconfig文件通过Drone Secret注入(在UI中创建),路径固定为/drone/src/.kube/config

3.3 Node.js前端项目的流水线配置

另一个项目是React前端(Node 18,yarn 3)。核心步骤:install → lint → test → build → 上传CDN。

kind: pipeline
type: docker
name: frontend-app

environment:
  NODE_VERSION: "18.18.0"
  CDN_BUCKET: my-bucket
  CDN_REGION: cn-north-1

volumes:
  - name: yarn-cache
    host:
      path: /tmp/cache/yarn-cache
  - name: node-modules
    host:
      path: /tmp/cache/node-modules
  - name: build-output
    temp: {}   # 临时空卷,每个步运行一次

steps:
  - name: install
    image: node:${NODE_VERSION}
    commands:
      - yarn install --frozen-lockfile
    volumes:
      - name: yarn-cache
        path: /home/node/.yarn/berry/cache
      - name: node-modules
        path: /app/node_modules
    when:
      event: [push, pull_request]

  - name: lint
    image: node:${NODE_VERSION}
    commands:
      - yarn lint
    volumes:
      - name: node-modules
        path: /app/node_modules
    depends_on: [install]

  - name: test
    image: node:${NODE_VERSION}
    commands:
      - yarn test --coverage
    volumes:
      - name: node-modules
        path: /app/node_modules
    depends_on: [install]

  - name: build
    image: node:${NODE_VERSION}
    commands:
      - yarn build
    volumes:
      - name: node-modules
        path: /app/node_modules
      - name: build-output
        path: /app/build
    depends_on: [install]

  - name: upload
    image: amazon/aws-cli:2.13.0
    commands:
      - aws s3 sync /app/build s3://${CDN_BUCKET}/frontend/${DRONE_COMMIT_SHA:0:7}/ --region ${CDN_REGION} --delete
    volumes:
      - name: build-output
        path: /app/build
    environment:
      AWS_ACCESS_KEY_ID:
        from_secret: aws_access_key_id
      AWS_SECRET_ACCESS_KEY:
        from_secret: aws_secret_access_key
    depends_on: [build]
    when:
      event: [push, tag]
      branch: main

注意:这里使用了temp: {}创建了一个临时卷,只在当前pipeline内有效,用于在build和upload之间传递产物。yarn缓存和node_modules目录挂载宿主目录,避免每次重装。

4. 效果数据:迁移前后对比

我们选取了三个典型项目进行压测,分别在Jenkins(5个Agent,每个2C4G)和Drone(单Runner,并发5个step,每个step分配1C2G)上运行相同的流水线。测试环境:同机房内网,GitLab为同一实例,包含Redis缓存。结果取10次平均值。

项目类型Jenkins平均耗时Drone平均耗时加速比备注
Go微服务 (user-service)4分30秒1分10秒3.8xDrone缓存了go module和build cache
Node前端 (frontend-app)3分20秒1分02秒3.2xyarn cache命中,跳过install
Python爬虫 (spider, pip + pytest)2分50秒45秒3.8xpip cache+多阶段构建

此外,Drone的并发能力明显更强。Jenkins在5个Agent上最多同时跑5个job,而Drone单Runner配置DRONE_RUNNER_CAPACITY=5可以同时运行5个step,但不同pipeline之间无上限(取决于机器资源)。实际生产中发现,一个4C8G的节点跑Drone Runner可以同时处理10-15个step(大部分step是轻量命令),而Jenkins的Agent容易被大型构建任务打满。

5. 避坑指南(亲测踩过的)

5.1 Runner内存限制导致OOM被杀死

踩坑:某次前端项目yarn build占用内存过多,Runner容器被Docker OOM Killer杀死。查看日志发现exit code 137。解决方案:在Runner环境变量中设置DRONE_LIMIT_MEMORY=2048m(默认512m)。同时可以在.drone.yml中为特定step指定资源限制:

- name: build
  image: node:18
  resources:
    memory: 2048
    cpu: 1
  commands:
    - yarn build

注意:resources字段在Drone 2.0+才支持,旧版本需要全局限制。

5.2 分支触发条件写错,生产环境被PR构建覆盖

踩坑:有个同事在feature分支推送后,触发了deploy步骤(因为trigger里写了event: [push])。结果feature分支的镜像覆盖了生产环境的latest标签。修复方法:在deploy步骤增加when.branch: main,同时在全局trigger中限制主要分支。另外,不要使用latest标签作为生产版本,改用Git commit SHA。

5.3 Docker构建缓存不生效,每次都全量构建

踩坑:使用了plugins/docker但每次构建都从头开始,耗时3分钟。查文档发现cache_from需要配合Dockerfile的多阶段构建和--cache-from参数。正确的写法:在Dockerfile中这样写:

## Dockerfile (Go项目)
FROM golang:1.21.5 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download                  # 这一层会被缓存
COPY . .
RUN CGO_ENABLED=0 go build -o bin/user-service ./cmd/

FROM alpine:3.19
COPY --from=builder /app/bin/user-service /user-service
EXPOSE 8080
CMD ["/user-service"]

同时在.drone.yml的docker步骤中设置cache_from: "${DOCKER_IMAGE}:latest"。但要注意:如果latest镜像已经不存在或者版本相差太大,缓存会miss。更稳妥的做法:同时缓存上一次的构建层,使用--cache-from指定多个镜像:

settings:
  cache_from:
    - "${DOCKER_IMAGE}:${DOCKER_TAG}"
    - "${DOCKER_IMAGE}:latest"
  build_args:
    - BUILDKIT_INLINE_CACHE=1

5.4 Secret管理:不要明文写在YAML中

踩坑:有人直接把数据库密码写在.drone.yml的环境变量里,然后上传到GitLab。后果是所有人都能看到。解决方案:在Drone UI中创建Secret(Setting → Secrets),然后在YAML中引用:

environment:
  DB_PASSWORD:
    from_secret: db_password

注意:Secret的值在UI中设置后不可再查看,可以更新。另外,Secret仅对指定的仓库和步骤可见,根步骤(如deploy)可以使用全部secret。

5.5 中文路径/文件导致构建失败

踩坑:项目中有个中文名字的目录资源文件,在Drone容器内挂载时路径乱码。解决方案:所有文件和目录名必须使用ASCII字符。如果必须用中文,确保宿主机的locale为en_US.UTF-8且Docker版本支持。

5.6 网络问题:克隆超时

踩坑:公司内网GitLab SSL证书自签,Drone克隆仓库报certificate signed by unknown authority。解决:在Server环境变量中添加DRONE_GITLAB_SKIP_VERIFY=false(不要跳过验证),或者将CA证书放到容器内。更推荐做法:在Runner中挂载自定义CA:

# drone-runner volumes
volumes:
  - /etc/ssl/certs/ca-certificates.crt:/etc/ssl/certs/ca-certificates.crt:ro

6. 总结

Drone用得好,构建效率翻倍。本文给出了适用于Go和Node项目的完整流水线配置,覆盖了缓存、测试、多阶段构建、部署等环节。核心要点:

  • 合理使用volume挂载缓存,加速依赖安装和Go build。
  • 精准控制触发条件,避免误部署。
  • 利用Docker多阶段构建和缓存层,缩小镜像体积。
  • 注意Runner资源限制,避免OOM。

如果你还在用Jenkins抱怨构建慢,不妨花一天时间试试Drone。按照本文的配置,你也能在10分钟内跑通第一个流水线。