拉取、编译、部署,无人机升空!

拉取、编译、部署,无人机升空!

技术 2970 字 / 6 分钟

编译型语言,就需要自动部署

我记得上学期刚注册助手 Gita 时,我还打趣地问过部长,助手什么时候上 CI / CD? 当时部长说快了,这学期就上。

现在助手可以说每个项目完成后,都会使用 Drone 实现持续交付 / 持续集成。 对于前端来说,因为助手主要是用 Vue 做单页应用,推送代码到 Gitea 上后,Drone 会使用 Vue CLI 对项目进行编译,之后将编译好的前端静态文件放进一个用 Go 写的小型服务端中。最后是一个可执行二进制文件的形式。在服务器上将这个二进制文件当作为一个service执行即可。 而后端部分,则是 Go 直接编译出二进制文件。之后有两种方法:一种是直接和前端一样,将二进制文件当做service执行;另一种是使用Dockfile打包成Docker镜像,推送到阿里云的镜像托管服务后,再以单个 Docker 容器的形式部署。这种方式的好处在于,阿里云的镜像托管是可以存储不同版本的镜像的,方便实现版本的回滚。

来搭个 Drone 吧~

我从这学期开始尝试学着写 Golang,确实挺为之着迷的。因为她没有 Python 那样的性能缓慢,速度之快地直逼 C 语言;同时又没有 C 语言那样的“语法残废”,该有的常用库都有,并且因为是官方的包,因此都是经过很细致的优化的,性能方面是有保障的。 好的,日常吹完 Go 了,开始聊正事了。 Golang 作为一个编译型语言,不像 PHP 那样有问题可以方便直接地修改文件内容即可。Golang 需要编译!!并且最后编译出来的还是一个小巧的二进制可执行文件。还得手动去执行它。因此,我也急需一个像助手一样的持续化自动部署平台——我在 GitHub 上的 Webhook 能触发她,她能自动拉取我的代码,自动编译,打包成 Docker 镜像,然后生成 Docker 容器。Drone 就是这样一个基于 Docker 的 CI / CD 平台。 昨天花了一天时间静下心来,终于配好了。还是照常踩了不少坑,借此文来记录一下。

建立 Drone Docker 容器

我是将 Drone 搭在 Docker 里,暴露出 80 端口,然后使用 Nginx 反代一个域名上去。 建立 Drone 镜像的过程可以参考官方的文档。这里提一下,我是想让 Drone 拉取我 GitHub 上的项目,因此需要与我的 GitHub 关联并使用 GitHub 登录。所以我们需要在自己的 GitHub 账号下创建一个 Oauth 应用。这里可以参照官方的文档。 https://docs.drone.io/installation/github/single-machine/

创建 Oauth 应用

Oauth 应用

之后创建 Drone 容器,将其中的参数替换成 GitHub Oauth 应用的信息即可:

docker run \
  --volume=/var/run/docker.sock:/var/run/docker.sock \
  --volume=/var/lib/drone:/data \
  --env=DRONE_GITHUB_SERVER=https://github.com \
 --env=DRONE_GITHUB_CLIENT_ID={% your-github-client-id %} \
  --env=DRONE_GITHUB_CLIENT_SECRET={% your-github-client-secret %} \
  --env=DRONE_RUNNER_CAPACITY=2 \
  --env=DRONE_SERVER_HOST=ci.github.red \
  --env=DRONE_SERVER_PROTO=http \
  --env=DRONE_TLS_AUTOCERT=false \
  --env=DRONE_USER_CREATE=username:wuhan005,admin:true \
  --publish=8001:80 \
  --publish=8002:443 \
  --restart=always \
  --detach=true \
  --name=drone \
  drone/drone:1

有坑注意 注意,这里还需要加入`DRONE_USER_CREATE`这样一个环境变量,这是官方文档里所没有提到的。这个环境变量将我们的 GitHub 设置成 Drone 的管理员,从而可以使用 CLI 进行操作并可以对项目进行更多的设置。若忘了设置就麻烦了,只能删除容器重建。(因为 Docker 更改已创建容器的配置很麻烦
建立好后,我们即可访问域名进行登录。进入后可以看到我们 GitHub 上的所有 repos。

开通阿里云镜像托管服务

我所想要的 Golang 部署是编译项目后,将项目放在一个 Docker 容器内运行。因此我们是需要将项目打包成一个 Docker 镜像,再依靠镜像进行部署。而镜像是要上传到阿里云做镜像托管的。 找到阿里云的容器镜像服务,在访问凭证里面设置我们的访问的密码,访问的账号是和阿里云的用户名一样的。

有坑注意 这里不要根据提示去开通什么`授权管理`,阿里云的权限管理很迷,开个子账户配权限十分麻烦。直接用当前账户即可。
然后创建一个命名空间,我们的镜像就在这个命名空间下。之后就可以创建单个项目的镜像仓库了。 阿里云 镜像仓库 点击进入后,可以查看当前镜像仓库Registry的地址。

编写.drone.yml文件

Drone 默认是将项目根目录下的.drone.yml文件当做配置文件进行相关部署任务的。 直接上我的配置,之后再解释:

workspace:
  base: /go
  path: src

pipeline:
  build:
    commands:
      - export GOPROXY=https://go-proxy.github.red
      - export GO111MODULE=on
      - go mod tidy
      - CGO_ENABLED=0 go build

  publish:
    image: plugins/docker
    secrets: [docker_username, docker_password, docker_registry]
    repo: registry.cn-hongkong.aliyuncs.com/eggplant/drone-test
    dockerfile: ./Dockerfile
    tags: latest

  deploy:
    image: docker:latest
    volumes: [/var/run/docker.sock:/var/run/docker.sock]
    secrets: [docker_username, docker_password, docker_registry]
    commands:
      - docker login --username=$DOCKER_USERNAME --password=$DOCKER_PASSWORD $DOCKER_REGISTRY
      - docker rm -f drone-test || true
      - docker pull $DOCKER_REGISTRY/eggplant/drone-test
      - docker run -dt --name drone-test -p 8003:12306 $DOCKER_REGISTRY/eggplant/drone-test:latest

volumes: [/var/run/docker.sock:/var/run/docker.sock]

最上面的workspace指定了当前部署任务的路径。pipeline里是整套流程的几个步骤。

build 构建

首先是构建(编译)我们的 Golang 程序。Drone 的操作都是基于 Docker 容器进行的,首先它会先创建一个 git 容器用于拉取脚本,然后我们这里指定了image: golang:1.12.4,即在 Golang 1.12.4 版本的镜像下构建。在该容器下,执行了 4 条命令:

export GOPROXY=https://go-proxy.github.red		# 更换 vgo 镜像
export GO111MODULE=on		# 开启 vgo 包管理
go mod tidy			# 拉取项目所需要的包
CGO_ENABLED=0 go build		# 编译项目
有坑注意 注意最后一行的编译语句中的`CGO_ENABLED=0`,这是因为我们最后是部署到一个十分精简的`alpine`中,其不自带的动态链接库,因此我们需要把动态库`CGO`关掉。不然在`alpine`中运行不起来。

publish 推送镜像

下一步是将我们编译好的二进制可执行文件,放入到一个 Docker 镜像中上传至阿里云镜像仓库。使用到了 Drone 的 Docker 插件:plugins/docker。这里是使用 Dockerfile 生成镜像。 Dockerfile 如下:

FROM alpine:latest

ADD . /home/app/
ENTRYPOINT ["/home/app/drone_test"]

EXPOSE 12306

这里使用了alpine镜像当做基础镜像进行构建。使用ADD将我们编译好的二进制可执行文件添加进去。使用ENTRYPOINT来指定镜像启动时即运行的程序。最后暴露出12306,即 Golang 服务的端口。

注意这里上传到阿里云的镜像仓库是需要账号密码的,这些敏感信息不能直接暴露在.drone.yml配置文件中。应该是要在 Drone 面板的项目SETTINGSecrets中,设定相关敏感账号密钥信息。在.drone.yml配置文件中使用secrets: [docker_username, docker_password, docker_registry]的形式来加入。变量会以大写环境变量的信息注入到容器中。因此这里的 Docker 仓库用户名密码字段请严格使用docker_usernamedocker_password;如需要将 Docker 源地址也不显示,可以使用docker_registry

deploy 部署镜像

之后需要将镜像从阿里云镜像仓库拉下来部署为容器。 这里 Drone 会拉取一个 Docker 镜像来实现对宿主机 Docker 的操作。可以看到这里有一个文件卷的映射:volumes: [/var/run/docker.sock:/var/run/docker.sock],就是将宿主机中的 Docker daemon 挂载进来。然后就可以直接在宿主机的 Docker 上来创建容器了。

有坑注意 但是直接对宿主机上 Docker 的操作属于危险行为。Drone 不会信任我们随便拉取一个镜像部署。因此需要在 Drone 面板中把当前项目的`Project settings`设置为`Trusted`。这样 Drone 就会信任我们这个项目,允许我们拉取部署容器了。注意这个`Project settings`选项,如果你在安装 Drone 时,没有把你的账号设置成为管理员,则这个选项是不会显示的!!
之后同样使用了secrets来处理敏感信息。使用环境变量的形式放进需要执行的命令中。

docker login --username=$DOCKER_USERNAME --password=$DOCKER_PASSWORD $DOCKER_REGISTRY
docker rm -f drone-test || true
docker pull $DOCKER_REGISTRY/eggplant/drone-test
docker run -dt --name drone-test -p 8003:12306 $DOCKER_REGISTRY/eggplant/drone-test:latest

首先是登录到阿里云的镜像仓库。然后删除原来的部署容器。

有坑注意 如果这是第一次部署,原来的 Docker 里不存在该容器,则删除的时候会报错。然后 Drone 部署任务会失败停止。因此我们需要在`docker rm`后加上`|| true`来使这段命令变成无论如何都是正常。
后面就是常规的 Docker 命令行操作啦。

加入项目

在 Drone 中加入项目后,Drone 会自动在 GitHub 的项目中加入一个 WebHook。之后我们的每一次提交都会触发自动部署啦~ 你还可以在.drone.yml中设置,只部署 master 分支的代码。就可以实现开发 dev 分支,部署 master 主分支啦。

完成啦

至此,整个 Drone 的 Golang 自动部署就完成啦~ 花费了我一天的时间研究。以后就可以舒舒服服地部署 Golang 项目啦。 Drone 其实还可以支持部署 PHP 项目的,但是我个人不是很推荐吧。毕竟有 Deployer 了,强行配上个 Drone 也不需要啦。