因为默认版本的 Drone 包含构建次数限制,如果日常高频使用 Drone,不久之后,便会遇到需要“重新初始化”应用才能继续使用的问题,但其实,作为个人用户,我们其实可以不受此限制影响。

所以本篇文章,就来分享下如何使用容器方式构建无使用限制的 Drone CI。

写在前面

之所以会有这篇文章出现呢?因为之前折腾群里的同学“公子”曾提到过“Drone 默认镜像是企业版,有 5000 次构建限制,需要重新编译”。考虑到软件的长期稳定使用,对官方文档进行翻阅,看到情况确实如此,文档中是如此描述的:“存在两种版本的 Drone,分别是需要自行构建的社区开源版本,和官方提供的企业版本”,然而官方并没有更多对于编译构建相关的文档或者说明。

关于 Drone CI

对于个人开发者或者团队来说,我们最关心的几个问题,莫过于代码是否安全、软件授权方式、以及授权费用了,官方文档中有提及:

  • 软件全部开源,官方默认为所有人提供企业版的镜像试用,支持 5000 次构建调用。
  • 如果需要使用开源版本,需要自行进行构建。
  • 企业版对于个人使用是免费的。
  • 如果你的团队、公司(包含非盈利组织)年收入低于100万美元的企业,或者融资少于 500 万美元,可以免费使用。
  • 如果你的公司需要付费,最低门槛是每月 299 美元。

在官方企业服务页面,我们可以看到不同版本的功能区别,主要在于是否支持:分布式方式运行多个 Runner;使用 K8S Runner;支持组织密钥功能;支持搭配 Vault 使用;支持定时任务;支持使用 postgres、mysql、s3 进行数据存储;支持自动扩容以及“扩展功能”

如果你有上述需求,可以构建“企业版”、反之构建“开源版”即可。不过结合上面的使用限制,对于一般个人和团队来说,直接构建“企业版”会更省事一些,功能更加全面。

那么,就来看看如何采取类似“官方的方式”编译构建 Drone 的容器镜像吧。

收集 Drone 官方构建资料

翻阅文档,可以看到关于自行构建,只有两条(或者说一条)简单的命令:

# 构建开源版
$ go build -tags "oss nolimit" github.com/drone/drone/cmd/drone-server

# 构建企业版
$ go build -tags "nolimit" github.com/drone/drone/cmd/drone-server

为了构建出和官方基本一致的镜像,需要从官方仓库中梳理完整的“构建套路”。这里以 v1.10.1 代码为基础,进行构建方式梳理。

从仓库根目录的 BUILDINGBUILDING_OSS 文件,可以看到记录了两种发行版软件的安装和构建流程:

1. Clone the repository
2. Install go 1.11 or later with Go modules enabled
3. Install binaries to $GOPATH/bin

    go install -tags "oss nolimit" github.com/drone/drone/cmd/drone-server

4. Start the server at localhost:8080

    export DRONE_GITHUB_CLIENT_ID=...
    export DRONE_GITHUB_CLIENT_SECRET=...
    drone-server

继续翻阅项目的 .drone.yml CI 文件,可以看到官方是如何通过 CI 构建和发布软件的:

...
- name: build
  image: golang:1.14.4
  commands:
  - sh scripts/build.sh
  environment:
    GOARCH: amd64
    GOOS: linux

- name: publish
  image: plugins/docker:18
  settings:
    auto_tag: true
    auto_tag_suffix: linux-amd64
    dockerfile: docker/Dockerfile.server.linux.amd64
    repo: drone/drone
    username:
      from_secret: docker_username
    password:
      from_secret: docker_password
  when:
    event:
    - push
    - tag
...

按图索骥,翻阅 CI 文件中提到的“构建脚本”,内容如下:

#!/bin/sh

echo "building docker images for ${GOOS}/${GOARCH} ..."

REPO="github.com/drone/drone"

# compile the server using the cgo
go build -ldflags "-extldflags \"-static\"" -o release/linux/${GOARCH}/drone-server ${REPO}/cmd/drone-server

# compile the runners with gcc disabled
export CGO_ENABLED=0
go build -o release/linux/${GOARCH}/drone-agent      ${REPO}/cmd/drone-agent
go build -o release/linux/${GOARCH}/drone-controller ${REPO}/cmd/drone-controller

继续查看容器 Dockerfile docker/Dockerfile.server.linux.amd64 ,可以看到容器结构:

# docker build --rm -f docker/Dockerfile -t drone/drone .

FROM alpine:3.11 as alpine
RUN apk add -U --no-cache ca-certificates

FROM alpine:3.11
EXPOSE 80 443
VOLUME /data

RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf

ENV GODEBUG netdns=go
ENV XDG_CACHE_HOME /data
ENV DRONE_DATABASE_DRIVER sqlite3
ENV DRONE_DATABASE_DATASOURCE /data/database.sqlite
ENV DRONE_RUNNER_OS=linux
ENV DRONE_RUNNER_ARCH=amd64
ENV DRONE_SERVER_PORT=:80
ENV DRONE_SERVER_HOST=localhost
ENV DRONE_DATADOG_ENABLED=true
ENV DRONE_DATADOG_ENDPOINT=https://stats.drone.ci/api/v1/series

COPY --from=alpine /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

ADD release/linux/amd64/drone-server /bin/
ENTRYPOINT ["/bin/drone-server"]

线索差不多齐了,我们开始编写基础的容器镜像。

编写构建脚本

上一小节中,我们可以看到容器文件中使用的核心的软件 drone-server 是从“本地”拷贝至镜像中的,猜测是为了更高的编译效率,使用更短时间编译多平台使用的二进制文件,所以采取了这样的策略。

对于我们来说,只需要使用适用于某一种 CPU 架构和系统的软件,可以考虑将编译环境直接用容器来创建。除了能够更好的保存编译环境外,还能够让编译使用的机器系统环境更加“纯粹和干净”:

FROM golang:1.16.0-alpine3.13 AS Builder

ENV DRONE_VERSION 1.10.1

ENV CGO_CFLAGS="-g -O2 -Wno-return-local-addr"

RUN apk add build-base && go env -w GO111MODULE=on && \
    mkdir /src && cd /src && \
    apk add curl && curl -L https://github.com/drone/drone/archive/refs/tags/v${DRONE_VERSION}.tar.gz -o v${DRONE_VERSION}.tar.gz && \
    tar zxvf v${DRONE_VERSION}.tar.gz && rm v${DRONE_VERSION}.tar.gz && \
    cd /src/drone-${DRONE_VERSION} && \
    go mod download && \
    go build -ldflags "-extldflags \"-static\"" -tags="nolimit" github.com/drone/drone/cmd/drone-server

为了让构建速度加快,我们可以适当调整 Dockerfile ,添加一些国内的软件源:

FROM golang:1.16.0-alpine3.13 AS Builder

RUN sed -i 's/https:\/\/dl-cdn.alpinelinux.org/http:\/\/mirrors.tuna.tsinghua.edu.cn/' /etc/apk/repositories && \
    echo "Asia/Shanghai" > /etc/timezone

RUN apk add build-base && \
    go env -w GO111MODULE=on && \
    go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct

ENV DRONE_VERSION 1.10.1

WORKDIR /src

# Build with online code
RUN apk add curl && curl -L https://github.com/drone/drone/archive/refs/tags/v${DRONE_VERSION}.tar.gz -o v${DRONE_VERSION}.tar.gz && \
    tar zxvf v${DRONE_VERSION}.tar.gz && rm v${DRONE_VERSION}.tar.gz
# OR with offline tarball
# ADD drone-1.10.1.tar.gz /src/

WORKDIR /src/drone-${DRONE_VERSION}

RUN go mod download

ENV CGO_CFLAGS="-g -O2 -Wno-return-local-addr"

RUN go build -ldflags "-extldflags \"-static\"" -tags="nolimit" github.com/drone/drone/cmd/drone-server

将上面的内容保存为 Dockerfile,然后执行 docker build -t drone:1.10.1 . ,稍等片刻“全功能”的 Drone 就在镜像内构建完毕了,但是镜像尺寸非常大,足足有 1.28GB 之大,所以我们要继续编写一个多阶段构建的镜像,来减少容器尺寸。

多阶段镜像构建

在上面的容器声明文件下方继续添加一些内容,结合前文找到的官方构建脚本,我们可以对构建脚本进行一些调整:

FROM golang:1.16.0-alpine3.13 AS Builder

RUN sed -i 's/https:\/\/dl-cdn.alpinelinux.org/http:\/\/mirrors.tuna.tsinghua.edu.cn/' /etc/apk/repositories && \
    echo "Asia/Shanghai" > /etc/timezone

RUN apk add build-base && \
    go env -w GO111MODULE=on && \
    go env -w GOPROXY=https://mirrors.aliyun.com/goproxy/,direct

ENV DRONE_VERSION 1.10.1

WORKDIR /src

# Build with online code
RUN apk add curl && curl -L https://github.com/drone/drone/archive/refs/tags/v${DRONE_VERSION}.tar.gz -o v${DRONE_VERSION}.tar.gz && \
    tar zxvf v${DRONE_VERSION}.tar.gz && rm v${DRONE_VERSION}.tar.gz
# OR with offline tarball
# ADD drone-1.10.1.tar.gz /src/

WORKDIR /src/drone-${DRONE_VERSION}

RUN go mod download

ENV CGO_CFLAGS="-g -O2 -Wno-return-local-addr"

RUN go build -ldflags "-extldflags \"-static\"" -tags="nolimit" github.com/drone/drone/cmd/drone-server



FROM alpine:3.13 AS Certs
RUN sed -i 's/https:\/\/dl-cdn.alpinelinux.org/http:\/\/mirrors.tuna.tsinghua.edu.cn/' /etc/apk/repositories && \
    echo "Asia/Shanghai" > /etc/timezone
RUN apk add -U --no-cache ca-certificates



FROM alpine:3.13
EXPOSE 80 443
VOLUME /data

RUN [ ! -e /etc/nsswitch.conf ] && echo 'hosts: files dns' > /etc/nsswitch.conf

ENV GODEBUG netdns=go
ENV XDG_CACHE_HOME /data
ENV DRONE_DATABASE_DRIVER sqlite3
ENV DRONE_DATABASE_DATASOURCE /data/database.sqlite
ENV DRONE_RUNNER_OS=linux
ENV DRONE_RUNNER_ARCH=amd64
ENV DRONE_SERVER_PORT=:80
ENV DRONE_SERVER_HOST=localhost
ENV DRONE_DATADOG_ENABLED=true
ENV DRONE_DATADOG_ENDPOINT=https://stats.drone.ci/api/v1/series

COPY --from=Certs /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
COPY --from=Builder /src/drone-1.10.1/drone-server /bin/drone-server
ENTRYPOINT ["/bin/drone-server"]

再次执行 docker build -t drone:1.10.1 .,能够看到镜像尺寸减少到了 61.7MB ,和官方提供的 67.3MB 镜像差不多大了。

其他

今年早些时候,曾写过一篇关于 Drone 的内容:《容器方式下的轻量仓库与CI 使用方案:Gitea + Drone 基础篇》,前些天在《站点优化日志(2021.04.12)》 中,也曾提到过我在尝试使用 Gitea + Drone 替换之前个人使用的 GitLab,所以如果你有类似轻量化运行的需求,可以翻阅之前的文章,或许能节约一些折腾过程的时间。

当然,如果你对 GitLab Runner 的编译构建感兴趣,可以翻阅两年前的一篇内容:《源码编译 GitLab Runner》,同样是使用 Golang 编写,但是相比之下,比 Drone 复杂不少。

最后

希望这篇文章能够帮到使用 Drone 的你。

–EOF