之前已经用 Docker 和 Docker-compose 搭建了 Drone 的持续集成/部署环境。
这次我们将 Drone 部署到 Kubernetes 上。
Drone 介绍
在官方介绍中 Drone 介绍自己是一个现代化的持续集成和持续交付平台,是面向繁忙的开发团队的自助式持续交付平台。其实就是想表达 Drone 无需过多复杂的配置,就可以轻松部署和搭建测试环境。
但尝试使用 Drone 会发现,网络上大部分关于 Drone 的教程都是面向老版本的(v0.8),而现在的版本又与这个老版本有很大出入。所以我们就用截至现在最新版本的 drone 搭建集成测试环境,并部署到 k8s 上。
Drone 主要分为两个部分:
Drone Server
Drone Server 是一个 Drone 的控制管理平台,用于查看现在正在运行中的集成测试结果、终止测试或跳转源码。
Drone 支持大部分主流代码托管平台(Github, Bitbucket, Gitlab),同时也支持自建平台(Gogs, Gitea)。
Drone Runner
Drone Runner 负责执行实际测试,官方提供了适用于各种运行环境的 Runner。今天要用的就是其中的 Kubernetes Runner。
在 Kubernetes 上部署 Drone
首先你得有个可以被外网访问的 k8s 集群,至于怎么弄,请自行解决。
部署 Drone Server
这一步操作的最终目标是将 drone/drone:1.7.0
这个 Docker 镜像部署到 k8s 、暴露出接口并绑定域名(方便 webhook 发送请求)。
为了达成这个目的我们要创建一个运行 Drone 容器的 Deployment,暴露这个 Deployment 的 Service,以及用于反向代理的 Ingress。配置文件存在 ConfigMap 中。
所以我们首先需要一个 Ingress 做一个域名解析,内容如下:
apiVersion: networking.k8s.io/v1beta1
kind: Ingress
metadata:
name: drone-ingress
namespace: devops
spec:
rules:
- host: example.com
http:
paths:
- backend:
serviceName: drone-service
servicePort: 80
path: /
我们的所有操作将在 devops
这个命名空间里进行,替换自己的域名,代理指向 drone-service 这个服务。
接下来创建 drone-service 服务:
apiVersion: v1
kind: Service
metadata:
name: drone-service
namespace: devops
spec:
selector:
app: drone-server
ports:
- name: http
protocol: TCP
port: 8080
targetPort: 80
drone-service 这个服务将使用 drone-server 这个 Deployment,targetPort 将 80 端口暴露给 Ingress,port 的 8080 端口则是为连接 Drone Runner 准备的内部端口。
最后就是 drone-server,用于运行 Drone Server 容器。
apiVersion: apps/v1
kind: Deployment
metadata:
name: drone-server
namespace: devops
spec:
selector:
matchLabels:
app: drone-server
replicas: 1
template:
metadata:
labels:
app: drone-server
spec:
nodeSelector:
node-pool: devops
containers:
- image: drone/drone:1.7.0
imagePullPolicy: Always
name: drone-server
volumeMounts:
- name: drone-server-sqlite-db
mountPath: /var/lib/drone
envFrom:
- configMapRef:
name: drone-env
volumes:
- name: drone-server-sqlite-db
hostPath:
path: /rc/data/drone
nodeSelector 请根据使用的 k8s 情况自行修改,Drone Server 镜像使用最新的 1.7.0,volume 使用节点的本地卷(重启后会丢失所有数据,所以需要的话可以挂在到其他永久卷中)。
这里最关键的一点就是 envFrom 环境变量,这里面定义了 Drone Server 所需要一些重要参数。以下内容以 github 为例:
apiVersion: v1
kind: ConfigMap
metadata:
name: drone-env
namespace: devops
data:
DRONE_GITHUB_SERVER: https://github.com
DRONE_GITHUB_CLIENT_ID: <github-client-id>
DRONE_GITHUB_CLIENT_SECRET: <github-client-secret>
DRONE_SERVER_HOST: <example.com>
DRONE_SERVER_PROTO: http
DRONE_RPC_SECRET: <rpc-secret>
DRONE_LOGS_TRACE: 'true'
其中,DRONE_SERVER_HOST、DRONE_SERVER_PROTO、DRONE_RPC_SECRET 最为关键。
分别是你的 Drone Server 的域名,以及与 Drone Runner 通讯的 secret,其中 secret 可以存储在 k8s secret 里,再次为了方便演示直接写入配置文件中。
Kubernetes Runner
搭建好 Drone Server 后我们要创建 Drone Runner,并连接 Drone Server。
与上一个教程不同,这次我们要用到 drone/drone-runner-kube
这个官方镜像。Kubernetes Runner 不同于 Docker runner,他会创建 pod,在新建的 pod 上执行完 pipeline 后销毁掉它们。
因为 Kubernetes Runner 并不需要向外暴露自己的服务,所以我们只需要一个 Deployment。内容如下:
apiVersion: apps/v1
kind: Deployment
metadata:
name: kube-runner
namespace: devops
labels:
app.kubernetes.io/name: kube-runner
spec:
replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: kube-runner
template:
metadata:
labels:
app.kubernetes.io/name: kube-runner
spec:
nodeSelector:
node-pool: dev
containers:
- name: runner
image: drone/drone-runner-kube:1.0.0-beta.2
ports:
- containerPort: 3000
envFrom:
- configMapRef:
name: kube-runner-env
与 Drone Server 一样,我们要自行替换 nodeSelector ,尴尬的是截至今天 drone_runner_kube 最新版本是 _1.0.0-beta.2,我已我们直接使用它。
环境变量如下:
apiVersion: v1
kind: ConfigMap
metadata:
name: kube-runner-env
namespace: devops
data:
DRONE_RPC_PROTO: http
DRONE_RPC_HOST: drone-service.devops:8080
DRONE_RPC_SECRET: <rpc-secret>
DRONE_NAMESPACE_DEFAULT: devops
DRONE_RUNNER_CAPACITY: '2'
DRONE_RUNNER_NAME: kube-runner
其中, DRONE_RPC_HOST 要指向上面创建的 drone-service 地址,DRONE_RPC_SECRET 要与 drone-env 中的一致。
Kubernetes Runner 还有一个很重的东西就是 ServiceAccount,因为 Kubernetes Runner 需要创建新的 pod 执行 pipeline 然后销毁他们,所以它需要一个角色赋予他权限。
我们的 Drone 角色如下:
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
namespace: devops
name: kube-runner
rules:
- apiGroups:
- ""
resources:
- secrets
verbs:
- create
- delete
- apiGroups:
- ""
resources:
- pods
- pods/log
verbs:
- get
- create
- delete
- list
- watch
- update
创建好角色后就要将角色绑定到我们的 ServiceAcount:
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: kube-runner
namespace: devops
subjects:
- kind: ServiceAccount
name: default
namespace: devops
roleRef:
kind: Role
name: kube-runner
apiGroup: rbac.authorization.k8s.io
Monorepo
现在很多项目都会采用一种 Monorepo 的组织方式,就是一个库里面有好几个项目。这时候我们希望 Drone 只执行当前目录下的项目而不是其他的。官方为我们提供了一个解决方案: drone-tree-config。感兴趣可以看一下。
总结
Drone 给我们带来了简单快速的持续集成部署的方案,在繁忙的工作中可以不必盯着繁杂的配置项苦恼。如果你不想用 Java 系那些强大而笨重的解决方案,想去使用更符合 go 语言风格、更贴近容器化思维的解决方案,那么 Drone 是一个不错的选择。