本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2021年02月25日 统计字数: 12912字 阅读时间: 26分钟阅读 本文链接: https://soulteary.com/2021/02/25/lightweight-code-warehouse-and-ci-usage-plan-in-docker-with-gitea-and-drone-part-1.html ----- # 容器方式下的轻量仓库与CI 使用方案:Gitea + Drone 基础篇 按照前篇文章所提,本篇将聊聊如何搭建使用 Gitea 和 Drone。因为内容过多,这个内容我计划拆为多篇来讲述,本篇先聊聊如何搭建使用。 ## 写在前面 为了方便配置域名、证书、以及后续潜在的动态扩容,我们可以搭配 Traefik 一起使用,让 Drone 和 Gitea 都只专注于 CI 和 代码存储相关功能,将“加密证书,流量转发相关”的事务交给 Traefik 处理。 相比较前篇内容中提到的[老版本的 GitLab](https://soulteary.com/2021/02/24/the-container-method-uses-a-lightweight-gitlab.html),这个方案对于资源的要求更低,让本地运行一套完整 CI 对于机器的负担降到了非常低的水平,日常运行资源占用几乎可以忽略不计(不算 CI 执行时的容器,即使算上 Traefik ,日常使用内存占用不到 200M): ```bash CONTAINER ID NAME CPU % MEM USAGE / LIMIT MEM % NET I/O BLOCK I/O PIDS 5295526d73f5 runner.nuc.com 0.00% 6.215MiB / 31.23GiB 0.02% 30.4kB / 24.3kB 11.8MB / 0B 17 9e810f12e2b4 drone.nuc.com 0.00% 10.56MiB / 31.23GiB 0.03% 36.5kB / 25.3kB 33.9MB / 0B 13 551b2e8683ba gitea.nuc.com 2.05% 152MiB / 31.23GiB 0.48% 104kB / 439kB 88.8MB / 459kB 18 f4606080ef23 traefik 2.40% 20.49MiB / 31.23GiB 0.06% 483kB / 282kB 58.1MB ``` 这套方案对于资源的要求低,本质是因为软件数量/功能相比较 GitLab 少了至少一个数量级,而且软件编写语言单一,相比较非编译优化执行的 Ruby ,编译执行的 Go 语言程序性能上有非常变态的提升,之前我在一篇[《重定向的九种方案及性能比较》](https://soulteary.com/2020/11/16/nine-redirection-schemes-and-performance-comparison.html)的文章中也有提过。 如果你好奇完整的 GitLab CI 相关的功能和发展历程,可以翻阅这篇文章[《聊聊 GitLab 的CI / CD 功能发展历程》](https://soulteary.com/2021/02/23/talk-about-the-development-history-of-gitlab-cicd-function.html)。 ## 搭建基础环境 接下来先聊聊如何搭建。 ### 系统环境准备 本文采用容器方式部署,简单来说,只要你的机器环境可以运行 Docker 就可以,所以笔记本也好、NUC也罢都是可以的、更何况是标准的 Linux 系统环境。 如果你对 Linux 不甚熟悉,我推荐使用容器友好的 Ubuntu 系统,如果你希望补充、了解一些基础操作,可以翻阅[以往的文章](https://soulteary.com/tags/ubuntu.html)。 当然,如果你使用 MacOS ,那么只需要安装 Docker Desktop 即可。 ### Traefik 前置相关安装配置 Traefik 的搭建和使用,我的老读者都熟悉了,这里不就过多赘述了,不熟悉的同学可以从[《更简单的 Traefik 2 使用方式》](https://soulteary.com/2020/12/02/easier-way-to-use-traefik-2.html)进行了解,如果你还想了解更多相关内容,可以翻阅这个[标签合集](https://soulteary.com/tags/traefik.html)。 ### 代码仓库 Gitea 安装配置 去年年初[《使用 Docker 和 Traefik v2 搭建轻量代码仓库(Gitea)》](https://soulteary.com/2020/02/04/gitea-git-server-with-docker-and-traefik-v2.html)一文中,我有提到过如何安装,当时选择了使用 Traefik 转发 Git Server 的 SSH 端口,本次我们换一种方式来进行端口暴露,减少应用之间的耦合,以及进一步提升效率。 为了方便后续维护,我们需要先定义一个 `.env` 文件,在里面配置好后续可能会有变化,以及需要我们自定义的内容: ```TeXT # 应用名称 SERVICE_NAME=Gitea # 服务域名 SERVICE_DOMAIN=gitea.nuc.com # 使用的应用镜像 DOCKER_IMAGE=gitea/gitea:1.13.2 # 允许公网,跨主机访问 Git SSH Server #SSH_PORT_EXPOSE=22 # 仅允许内部 CI ,本地机器使用 SSH 访问服务 SSH_PORT_EXPOSE=127.0.0.1:22 ``` 接着来定义服务编排配置文件,一般情况下你只需要复制粘贴即可,而不需要调整: ```yaml version: '3.6' services: gitea: image: ${DOCKER_IMAGE} container_name: ${SERVICE_DOMAIN} ports: - ${SSH_PORT_EXPOSE}:22 environment: - USER_UID=1000 - USER_GID=1000 - APP_NAME=${SERVICE_NAME} - RUN_MODE=prod - RUN_USER=git - SSH_DOMAIN=${SERVICE_DOMAIN} - SSH_PORT=22 - SSH_LISTEN_PORT=22 - HTTP_PORT=80 - ROOT_URL=https://${SERVICE_DOMAIN} - LFS_START_SERVER=true - REQUIRE_SIGNIN_VIEW=true - DB_TYPE=sqlite3 - INSTALL_LOCK=false - DISABLE_GRAVATAR=true networks: - traefik restart: unless-stopped labels: - "traefik.enable=true" - "traefik.docker.network=traefik" - "traefik.http.routers.giteaweb.middlewares=https-redirect@file" - "traefik.http.routers.giteaweb.entrypoints=http" - "traefik.http.routers.giteaweb.rule=Host(`${SERVICE_DOMAIN}`)" - "traefik.http.routers.giteassl.middlewares=content-compress@file" - "traefik.http.routers.giteassl.entrypoints=https" - "traefik.http.routers.giteassl.tls=true" - "traefik.http.routers.giteassl.rule=Host(`${SERVICE_DOMAIN}`)" - "traefik.http.services.giteabackend.loadbalancer.server.scheme=http" - "traefik.http.services.giteabackend.loadbalancer.server.port=80" volumes: # 标准 Linux 系统下使用 # - /etc/localtime:/etc/localtime:ro # - /etc/timezone:/etc/timezone:ro - ./repositories:/data/git/repositories - ./data:/data/gitea/ logging: driver: "json-file" options: max-size: "10m" extra_hosts: - "${SERVICE_DOMAIN}:127.0.0.1" healthcheck: test: ["CMD-SHELL", "wget -q --spider --proxy off localhost || exit 1"] interval: 5s networks: traefik: external: true ``` 将上面的内容保存为 `docker-compose.yml` 后,使用 `docker-compose up -d` 来启动服务。 使用 `docker-compose logs -f` 来观察日志: ```bash gitea.nuc.com | Generating /data/ssh/ssh_host_ed25519_key... gitea.nuc.com | Generating /data/ssh/ssh_host_rsa_key... gitea.nuc.com | Generating /data/ssh/ssh_host_dsa_key... gitea.nuc.com | Generating /data/ssh/ssh_host_ecdsa_key... gitea.nuc.com | Could not load host certificate "/data/ssh/ssh_host_ed25519_cert": No such file or directory gitea.nuc.com | Could not load host certificate "/data/ssh/ssh_host_rsa_cert": No such file or directory gitea.nuc.com | Could not load host certificate "/data/ssh/ssh_host_ecdsa_cert": No such file or directory gitea.nuc.com | Could not load host certificate "/data/ssh/ssh_host_dsa_cert": No such file or directory gitea.nuc.com | Server listening on :: port 22. gitea.nuc.com | Server listening on 0.0.0.0 port 22. gitea.nuc.com | 2021/02/25 16:31:51 cmd/web.go:108:runWeb() [I] Starting Gitea on PID: 15 gitea.nuc.com | 2021/02/25 16:31:51 ...dules/setting/git.go:91:newGit() [I] Git Version: 2.26.2, Wire Protocol Version 2 Enabled gitea.nuc.com | 2021/02/25 16:31:51 routers/init.go:132:GlobalInit() [T] AppPath: /app/gitea/gitea gitea.nuc.com | 2021/02/25 16:31:51 routers/init.go:133:GlobalInit() [T] AppWorkPath: /app/gitea gitea.nuc.com | 2021/02/25 16:31:51 routers/init.go:134:GlobalInit() [T] Custom path: /data/gitea gitea.nuc.com | 2021/02/25 16:31:51 routers/init.go:135:GlobalInit() [T] Log path: /data/gitea/log gitea.nuc.com | 2021/02/25 16:31:51 ...dules/setting/log.go:297:newLogService() [I] Gitea v1.13.2 built with GNU Make 4.3, go1.15.7 : bindata, timetzdata, sqlite, sqlite_unlock_notify gitea.nuc.com | 2021/02/25 16:31:51 ...dules/setting/log.go:343:newLogService() [I] Gitea Log Mode: Console(Console:info) gitea.nuc.com | 2021/02/25 16:31:51 ...dules/setting/log.go:249:generateNamedLogger() [I] Macaron Log: Console(console:info) gitea.nuc.com | 2021/02/25 16:31:51 ...dules/setting/log.go:249:generateNamedLogger() [I] Router Log: Console(console:info) gitea.nuc.com | 2021/02/25 16:31:51 ...les/setting/cache.go:70:newCacheService() [I] Cache Service Enabled gitea.nuc.com | 2021/02/25 16:31:51 ...les/setting/cache.go:81:newCacheService() [I] Last Commit Cache Service Enabled gitea.nuc.com | 2021/02/25 16:31:51 ...s/setting/session.go:63:newSessionService() [I] Session Service Enabled gitea.nuc.com | 2021/02/25 16:31:51 ...s/storage/storage.go:151:initAttachments() [I] Initialising Attachment storage with type: gitea.nuc.com | 2021/02/25 16:31:51 ...les/storage/local.go:43:NewLocalStorage() [I] Creating new Local Storage at /data/gitea/attachments gitea.nuc.com | 2021/02/25 16:31:51 ...s/storage/storage.go:145:initAvatars() [I] Initialising Avatar storage with type: gitea.nuc.com | 2021/02/25 16:31:51 ...les/storage/local.go:43:NewLocalStorage() [I] Creating new Local Storage at /data/gitea/avatars gitea.nuc.com | 2021/02/25 16:31:51 ...s/storage/storage.go:163:initRepoAvatars() [I] Initialising Repository Avatar storage with type: gitea.nuc.com | 2021/02/25 16:31:51 ...les/storage/local.go:43:NewLocalStorage() [I] Creating new Local Storage at /data/gitea/repo-avatars gitea.nuc.com | 2021/02/25 16:31:51 ...s/storage/storage.go:157:initLFS() [I] Initialising LFS storage with type: gitea.nuc.com | 2021/02/25 16:31:51 ...les/storage/local.go:43:NewLocalStorage() [I] Creating new Local Storage at /data/git/lfs gitea.nuc.com | 2021/02/25 16:31:51 routers/init.go:176:GlobalInit() [I] SQLite3 Supported gitea.nuc.com | 2021/02/25 16:31:51 routers/init.go:56:checkRunMode() [I] Run Mode: Production gitea.nuc.com | 2021/02/25 16:31:51 cmd/web.go:163:runWeb() [I] Listen: http://0.0.0.0:80 gitea.nuc.com | 2021/02/25 16:31:51 cmd/web.go:166:runWeb() [I] LFS server enabled gitea.nuc.com | 2021/02/25 16:31:51 ...s/graceful/server.go:55:NewServer() [I] Starting new server: tcp:0.0.0.0:80 on PID: 15 gitea.nuc.com | 2021/02/25 16:31:56 Started GET / for 127.0.0.1 gitea.nuc.com | 2021/02/25 16:31:56 Completed GET / 200 OK in 3.875698ms gitea.nuc.com | 2021/02/25 16:32:01 Started GET / for 127.0.0.1 gitea.nuc.com | 2021/02/25 16:32:01 Completed GET / 200 OK in 1.131553ms ``` 等待服务日志出现 **Starting new server: tcp:0.0.0.0:80** 后,打开浏览器访问我们绑定的域名 “gitea.nuc.com” 可以看到服务已经启动就绪了。 ![完成安装等待配置的 Gitea](https://attachment.soulteary.com/2021/02/25/gitea-started.png) 不过,应用目前还需要等待我们进一步配置,才能够正常提供服务,我等待 Drone CI 配置完毕,再进行下一步。 ### Drone 的服务端(Server)配置 同样的,先创建 `.env` 配置文件,这里有一部分内容,我们需要配置完 Gitea 后才能获取,所以你也可以选择在配置完 Gitea 后,再来完成下面的内容: ```TeXT # 服务域名 SERVICE_DOMAIN=drone.nuc.com # 使用的应用镜像 DOCKER_IMAGE=drone/drone:1.10.1 # Drone 服务端和 Runner 之间通讯秘钥 DRONE_RPC_SECRET=YOUR_RANDOM_KEY # Drone 超级管理员账号,根据自己需求修改 DRONE_ADMIN_USERNAME=soulteary # Gitea 域名配置 GITEA_DOMAIN=gitea.nuc.com # Gitea OAuth ClientID / Secret # 稍后配置 Gitea 后替换即可 DRONE_GITEA_CLIENT_ID=a0da8a47-e89e-48ea-8ea3-08f2554511b1 DRONE_GITEA_CLIENT_SECRET=nrdSbAX_4AXexpUG_ZDw9iF640M8uC79h1raJxnX74I= ``` 服务编排配置文件也比较简单,不需要做修改,直接复制粘贴到你的配置即可: ```yaml version: '3.6' services: drone: image: ${DOCKER_IMAGE} container_name: ${SERVICE_DOMAIN} environment: - DRONE_GITEA_SERVER=http://${GITEA_DOMAIN} - DRONE_GITEA_CLIENT_ID=${DRONE_GITEA_CLIENT_ID} - DRONE_GITEA_CLIENT_SECRET=${DRONE_GITEA_CLIENT_SECRET} - DRONE_LOGS_TRACE=true - DRONE_AGENTS_ENABLED=true - DRONE_RPC_SECRET=${DRONE_RPC_SECRET} - DRONE_SERVER_HOST=${SERVICE_DOMAIN} - DRONE_SERVER_PROTO=http - DRONE_CLEANUP_INTERVAL=60m - DRONE_CLEANUP_DISABLED=false - DRONE_CLEANUP_DEADLINE_RUNNING=1h - DRONE_CLEANUP_DEADLINE_PENDING=2h - DRONE_USER_CREATE=username:${DRONE_ADMIN_USERNAME},admin:true networks: - traefik restart: unless-stopped labels: - "traefik.enable=true" - "traefik.docker.network=traefik" - "traefik.http.routers.drone-web.middlewares=https-redirect@file" - "traefik.http.routers.drone-web.entrypoints=http" - "traefik.http.routers.drone-web.rule=Host(`${SERVICE_DOMAIN}`)" - "traefik.http.routers.drone-ssl.middlewares=content-compress@file" - "traefik.http.routers.drone-ssl.entrypoints=https" - "traefik.http.routers.drone-ssl.tls=true" - "traefik.http.routers.drone-ssl.rule=Host(`${SERVICE_DOMAIN}`)" - "traefik.http.services.drone-backend.loadbalancer.server.scheme=http" - "traefik.http.services.drone-backend.loadbalancer.server.port=80" volumes: # 标准 Linux 系统下使用 # - /etc/localtime:/etc/localtime:ro # - /etc/timezone:/etc/timezone:ro - ./data:/data logging: driver: "json-file" options: max-size: "10m" extra_hosts: - "${SERVICE_DOMAIN}:127.0.0.1" healthcheck: test: ["CMD-SHELL", "wget -q --spider --proxy off localhost:80/healthz || exit 1"] interval: 5s networks: traefik: external: true ``` 因为我们尚未配置好 Gitea ,所以先不着急启动服务。 ### Drone 的客户端(Runner)配置 我们接着来配置 Drone Runner ,还是先创建一套 `.env` 文件: ```TeXT # 服务域名 SERVICE_DOMAIN=runner.nuc.com # 使用的应用镜像 DOCKER_IMAGE=drone/drone-runner-docker:1.6.3 # Drone 服务端和 Runner 之间通讯秘钥 DRONE_RPC_SECRET=YOUR_RANDOM_KEY # Runner 最大并发数量,根据自己需求来 DRONE_RUNNER_CAPACITY=2 # Drone 服务配置域名 DRONE_SERVER_DOMAIN=drone.nuc.com ``` 然后是定义容器编排配置文件: ```yaml version: '3.6' services: drone-runner: image: drone/drone-runner-docker:1.6.3 container_name: ${SERVICE_DOMAIN} expose: - 3000 environment: - DRONE_RPC_PROTO=http - DRONE_RPC_HOST=${DRONE_SERVER_DOMAIN} - DRONE_RPC_SECRET=${DRONE_RPC_SECRET} - DRONE_RUNNER_CAPACITY=${DRONE_RUNNER_CAPACITY} - DRONE_RUNNER_NAME=${SERVICE_DOMAIN} - DRONE_RUNNER_NETWORKS=traefik networks: - traefik restart: always volumes: # 标准 Linux 系统下使用 # - /etc/localtime:/etc/localtime:ro # - /etc/timezone:/etc/timezone:ro - /var/run/docker.sock:/var/run/docker.sock - ./data:/data logging: driver: "json-file" options: max-size: "10m" extra_hosts: - "${SERVICE_DOMAIN}:127.0.0.1" healthcheck: test: ["CMD-SHELL", "wget -q --spider --proxy off localhost:3000/healthz || exit 1"] interval: 5s networks: traefik: external: true ``` 将上面的配置保存为 `docker-compose.yml`。和 Drone Server 一样,因为依赖 Gitea 配置,所以这里我们先不着急启动服务。 ## 代码仓库 Gitea 的进一步配置 访问安装后的 Gitea,不论是点击“登陆”亦或者“注册”,界面都将会来到“初始配置”页面:`https://gitea.nuc.com/install`。 ![配置 Gitea 应用](https://attachment.soulteary.com/2021/02/25/gitea-inital.png) 直到我们配置完毕后,应用才能够真正的开始提供服务。 ### 初始化应用配置 那么来简单讲讲如何进行配置,以及配置过程中的一些细节。 数据库可以根据自己实际情况切换为更为可靠的 `PostgreSQL`,如果你是个人或者小团队使用,使用 SQLite 问题也不大。 “一般设置”默认已经根据前文中的内容,进行了的自动化填充,这里如果还是想修改,仅建议修改 “站点名称”, 其余内容不建议进行修改。 “可选设置”包含三类配置项目: - “可选设置”中的“电子邮箱设置”可以根据你的实际情况完成配置,如果是个人使用,或者现在不想配置,可以先跳过,后续我们将配置更好用的推送通知,不依赖这个配置。 - “可选设置”中的“服务器和三方设置”,我个人建议勾选“启用本地模式”,其余内容根据自己喜好来勾选即可,如果是个人使用,可以将各种注册方式都去掉。 - “可选设置”中的“管理员账号设置”是**必须完成配置填写的**,填写方式可以参考下面的模式,建议全部使用小写英文,避免后续应用升级后出现预期之外的功能问题。 ![配置管理员账号示例](https://attachment.soulteary.com/2021/02/25/gitea-admin.png) 一切就绪后,点击安装按钮,完成安装,页面会自动跳转到新页面。 ![完成基础安装配置的 Gitea](https://attachment.soulteary.com/2021/02/25/gitea-inited.png) ### 配置 Drone 跨应用自动授权(OAuth授权) 还记得前文中我们迟迟没有启动的 Drone Server 和 Drone Runner 吗?前文中我们在 Drone Server 中设置了一套 OAuth ClientID / Secret 变量,当我们正确设置了 OAuth 变量后,Drone 便能够根据 Gitea 进行自动的仓库、用户的创建和管理,而无需我们再进行手动配置。 某种程度来看,Drone 可以看作一套无状态的服务,这方便了后续我们扩容或者同类服务切换的可能性。 下面来就来讲讲如何配置跨应用授权。 点击右上角的个人用户头像,选择下拉菜单中的“设置”,在新页面中选择“应用”选项。 ![配置 Gitea 的 OAuth 应用](https://attachment.soulteary.com/2021/02/25/gitea-oauth.png) 在名称处填写“DroneCI”,重定向 URI 填写之前的配置的域名,并带上 `/login` 路径: ```yaml http://drone.nuc.com/login ``` 点击提交,可以看到我们需要的 OAuth ClientID / Secret 信息已经生成完毕。 ![获取创建好的 OAuth Key](https://attachment.soulteary.com/2021/02/25/gitea-oauth-key.png) 将内容更新到我们上文中的 Drone Server 的配置中,我们开始对 Drone 进行配置。 ## 完成 Drone 的最后配置 对 Drone Server 的 `.env` 配置中的信息进行更新,将上面的 OAuth 信息填入配置中: ```TeXT DRONE_GITEA_CLIENT_ID=ed292553-9dca-4f76-856f-4172c8ee4186 DRONE_GITEA_CLIENT_SECRET=3FxbTuNomJ4fUiUnZuA2NXcX083v1oK76ntsOxIuy6U= ``` 然后使用 `docker-compose up -d` 启动服务,顺便进入 Drone Runner 目录,将 Runner 也使用 `docker-compose up -d` 一并启动,等待大概五秒钟,浏览器访问我们配置的 CI 服务域名:`drone.nuc.com`,会看到浏览器自动跳转到了 OAuth 配置授权页面: ![配置成功后,将能够看到 OAuth 授权提示](https://attachment.soulteary.com/2021/02/25/gitea-oauth-authorize.png) 点击授权按钮后,我们会以当前用户身份自动登录 Drone 。 至此,基础安装和配置部分便完成啦。 ![以 Gitea 身份自动登录 Drone](https://attachment.soulteary.com/2021/02/25/drone-logined.png) ## 最后 虽然安装配置结束,但是距离我们使用 Drone 进行 CI 来提升开发效率还早,关于 CI 过程的各种实践也还没有涉及到。 譬如“仓库和CI系统的进一步安全认证策略”、“细粒度的配置任务过程提醒”、“根据需求完成节点扩容”、“仓库构建内容持久化”、“CI和仓库之间更安全的数据交互”,以及“如何使用我们本地的机器来服务公网诸如 GitHub 等自动化过程”等话题都还没有聊到。 下一篇内容,我们将聊聊 Drone 的一些实战,将上述内容逐步涉及,以及针对本篇内容中的一些配置进行详细展开。 --EOF