Skip to content

基于 Docker + Portainer + Traefik + Gitea 搭建适用于个人的CI/CD持续构建系统

绝弹

之前写过一篇搭建CI/CD系统的文章,技术栈与标题所述差不多,但最近查看 Gitea 文档时发现更新一个名为 Gitea Actions 的功能,略作了解后发现与 Github Actions 是类似的东西,且可以直接使用复用 Github Actions 的配置,于是兴趣满满地实践了一番并有了这篇文章。

先说下为什么是这套技术栈,可能很多人会想到用 Gitlab/Jenkins 之类的工具来实现,对于企业级管理来说没问题,但上手配置却是不低。以 Gitlab 为例,动辄 4G 的起步运行内存,加上 CI/CD 等额外的运行内存,足以劝退很多人。

选择 Gitea 是因为运行内存很低(Go 语言写的),标题中所有工具+CI/CD任务运行在一个 2 核 4 G的腾讯云服务器完全没问题,对于个人实践来说是非常具有性价比的,且可以接触到 Github Actions 的相关内容,并反过来在 Github Actions 上实践和应用。

目标

一句话概括就是:提交代码自动部署。以前端为例,本地运行 git push 命令后,几分钟后可以访问到最新页面,免去手动构建/部署等繁琐的工作。主要流程如下:

  • 本地提交代码到 Gitea 仓库后,Gitea 自动调度 Runner 执行任务。
  • Runner(运行器)拉取代码,设置环境(例如NodeJS环境),安装依赖,构建产物
  • Runner(运行器)以产物构建docker镜像,推送到私有仓库,并远程到部署服务器执行更新脚本

技术栈

以容器形式部署,可视化管理。主要包含以下技术栈:

Docker

一个开源的容器平台,可以将程序及依赖打包到一个轻量级、可移植的容器中运行。内置有 Swarm 集群模式,可以理解为 K8S 的简单版(官方实现),可以同时管理和调度多台服务器上的资源。

Portainer

一个可视化的docker容器管理工具,手动管理 docker 是非常麻烦的,通过 portainer 可以很方便地管理容器/镜像/网络/数据卷等内容,且支持 docker swarm 模式。

Traefik

一个类似于 nginx 但更适用于容器的反向代理工具,支持域名和证书的动态配置,同时支持 docker swarm 模式。

Gitea

一个git 管理平台,有两个很关键地功能:Gitea Actions 可以让我们运行等价于 Github Actions地自动化任务,例如构建产物/构建镜像等;软件包可以让我们管理上传内容,例如npm包,docker镜像等。

准备工作

简单概括为:你需要一台服务器和一个域名。尽管可以在本地实现,但建议在云服务器上实践,主要是因为云服务有独立IP,可以配置域名区分访问和管理。

云服务器

推荐 2核4G 配置,理论上 1核2G 也能勉强跑得动。以我个人为例,我这里选择的是腾讯云、带容器镜像的 ubuntu 系统,后续将不再描述 docker 的描述。

域名解析

登陆你的域名管理平台,解析名为 *.dev 的 A 记录到你的云服务器IP上,以我个人为例如下。注意:如果域名解析的是国内服务器那么域名是要备案的,备案内容自行查阅这里不再描述。

安装Portainer

登陆你的服务器,可以使用云服务商提供的 web 面板登陆,或者配置SSH以及使用 VS Code 插件进行远程等,总而言之方法比较多此处不做过多描述具体查看相关文档。

启用集群模式

执行 docker swarm init 命令启用 swarm 模式,会输出加入令牌和加入命令,只有单台服务器的话可以忽略这些输出。

创建公共网络

执行 docker network create -d overlay public 命令创建一个名为 public 的公共网络,其中 overlay 类型可以让我们与其他服务器的容器在一个内部网络内通信。

创建数据卷

执行 docker volume create portainer 命令创建一个名为 portainer 的数据卷,保存你在运行 portainer 时产生的数据,避免容器重启后数据丢失的问题。

编写配置文件

新建 /docker/portainer.yaml 文件,编写 portainer 配置并保存。说明:包含一个主服务和若干个子服务,其中子服务会在每台服务器上都安装,并通过主服务管理。

yaml
version: '3.2'

services:
  agent:
    image: portainer/agent:2.19.0
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - /var/lib/docker/volumes:/var/lib/docker/volumes
    networks:
      - public
    deploy:
      mode: global
      placement:
        constraints: [node.platform.os == linux]

  core:
    image: portainer/portainer-ce:2.19.0
    command: -H tcp://tasks.agent:9001 --tlsskipverify
    volumes:
      - portainer:/data
    networks:
      - public
    ports:
      - 9000:9000
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.role == manager]
      labels:
        - traefik.enable=true
        - traefik.http.routers.server.entrypoints=web
        - traefik.http.routers.server.rule=Host(`docker.dev.juetan.cn`)
        - traefik.http.services.server1.loadbalancer.server.port=9000

networks:
  public:
    external: true

volumes:
  portainer:
    external: true

启动服务

执行 docker stack deploy -c /docker/portainer.yaml portainer 命令启动服务,其中使用 -c 指定配置文件的位置,最后的 portainer 为名字。

访问页面

在浏览器输入 http://ip:9000 并回车,页面中是以下安装页面即代表安装成功。注意:尽快安装完,否则2分钟后 Portainer 会出于安全考虑禁止安装,那时便要重启才能再操作。

安装Traefik和Gitea

安装完 Portainer 后,我们就可以在管理面板中进行操作而不用不停地敲命令执行。现在,登陆你的 Portainer 面板,接下来的操作都将在这里进行,你甚至不用登陆服务器敲命令。

创建数据卷

进入 Volumes 面板,分别创建 gitea 和 gitea_runner 数据卷,用于保存 gitea 和 gitea runner 在运行时产生的数据,创建时其他配置保持默认即可。

添加配置

进入 Stacks 面板,新建名为 core 的 Stack 并编写如下配置(具体请根据自身情况修改),配置包含 3 个部分:Traefik + Gitea + Gitea Runner。

配置如下,注意注释的内容,我们尚未登陆 Gitea 没有注册令牌所以暂时留空。

yaml
version: "3"

services:
  traefik:
    image: traefik:latest
    command:
      - --api
      - --api.debug
      - --api.dashboard
      - --providers.docker
      - --providers.docker.swarmmode
      - --providers.docker.exposedbydefault=false
      - --providers.docker.network=public
      - --entrypoints.web.address=:80
    networks:
      - public
    ports:
      - 80:80
      - 443:443
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.role == manager]
      labels:
        - traefik.enable=true
        - traefik.http.routers.dashboard.rule=Host(`router.dev.juetan.cn`)
        - traefik.http.routers.dashboard.entrypoints=web
        - traefik.http.routers.dashboard.service=api@internal
        - traefik.http.services.api@internal.loadbalancer.server.port=8080

  gitea:
    image: gitea/gitea:1.20.4
    restart: always
    networks:
      - public
    volumes:
      - gitea:/data
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.role == manager]
      labels:
        - traefik.enable=true
        - traefik.http.routers.gitea.rule=Host(`git.dev.juetan.cn`)
        - traefik.http.routers.gitea.entrypoints=web
        - traefik.http.services.gitea1.loadbalancer.server.port=3000

  runner:
    image: gitea/act_runner:nightly
    restart: always
    depends_on:
      - gitea
    # environment:
    #   - GITEA_INSTANCE_URL=http://git.dev.juetan.cn/
    #   - GITEA_RUNNER_REGISTRATION_TOKEN=MjSd8azK2819F8Wb4sNmZYp3FhuDBXUeVYkQb1Jy
    networks:
      - public
    volumes:
      - gitea_runner:/data
      - /var/run/docker.sock:/var/run//docker.sock
    deploy:
      mode: replicated
      replicas: 1
      placement:
        constraints: [node.role == manager]

volumes:
  gitea:
    external: true
  gitea_runner:
    external: true

networks:
  public:
    external: true

部署服务

点击底部的 Deploy the action 按钮,稍等片刻便可以从列表中点进去查看运行状态。如果出现异常,可点击查看具体运行日志,或返回去检查下语法或配置有没有问题。

配置Gitea Actions

目前 Gitea Actions 还在开发中,默认情况下 Actions 是禁用的,使用上还是需要手动开启。官方文档上介绍有点杂,接下来以我们目前的状态快速配置以下。

进入容器

在 Portainer 面板中,提供有进入容器中执行命令的功能,对于日常操作非常方便。当然,对于复杂的修改还是通过直接操作数据卷更方便。在 Portainer 中进入容器步骤如下:

添加配置

修改 /data/gitea/conf/app.ini 文件,在文件末尾添加以下配置以启用 Actions 功能。保存并退出后,此时功能尚未生效,还需要到 Services 面板进行重启。

ini
[actions]
ENABLED=true

复制令牌

重启后,应该能在管理后台界面多出名为 actions 的选项卡,进入点击创建Runner按钮即可看到加入令牌(如下)。

更新配置

返回 Portainer 的 Stacks 管理页面,修改我们刚才创建的配置,还记得我们方才提到的注释吗?此时,修改为我们上一步得到的令牌,保存后重启,代码如下:

yaml
services:
  runner:
    # ...
    environment:
      - GITEA_INSTANCE_URL=http://git.dev.juetan.cn/
      - GITEA_RUNNER_REGISTRATION_TOKEN=7B3NLAz4aEDwIZDQx8dNEnLxToA8Fq81WSCjmSdI

更新 stack 后,你应该能在管理后台的 actions 面板看到新加入的 Runner,如下:

至此,我们的CI/CD环境已部署完成,其中 Portainer 用于管理 Docker 镜像和容器,Traefik 用于配置域名和代理,Gitea 用于管理 Git 代码,Gitea Actions 用来运行构建任务。

补充:HTTPS证书

Traefik 内置有证书集成的功能,对于日常我们可以使用 Traefik 自动签发 let's encrypt 的证书进行使用,需注意目前 let's encrypt 限制每周只能对一个主域名签发 50 个整数,对于日常使用是完全足够的。要进行使用需配置静态参数和动态参数。

  1. 使用命令行或Portainer创建数据卷,用于存放证书等文件,避免容器重启后数据丢失导致重新请求证书。
bash
docker volume create traefik
  1. 修改之前 traefik 的 stack 配置,添加如下配置后重新部署。
yaml
services:
  traefik:
    command:
      # - --entrypoints.web.http.redirections.entrypoint.to=websecure
      - --entrypoints.websecure.address=:443
      - --certificatesresolvers.myresolver.acme.httpchallenge=true
      - --certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web
      - --certificatesresolvers.myresolver.acme.email=contact@juetan.cn
      - --certificatesresolvers.myresolver.acme.storage=/data/acme.json
    volumes:
      - traefik:/data

volumes:
  traefik:
    external: true
  1. 在需要使用 HTTPS 的服务上,添加以下额外的配置
yaml
- traefik.http.routers.dashboard.entrypoints=websecure
- traefik.http.routers.dashboard.tls=true
- traefik.http.routers.dashboard.tls.certresolver=myresolver
  1. 贴一个访问后的效果(如下),注意:证书只有90天有效期(从图中10月9日到1月7日可以看出),到期前10天 Traefik 会自动续签。

结语

以上,相比于原有实践,本次配置更简洁且更易于理解。在实践时,我翻阅了不少相关的官方文档,对于以上构成也有了更深一步的的了解,特别是某些组件的参数说明和功能。如果有不懂的,有时候查阅官方文档比搜索来得更快。篇幅有限,有空再写写如何使用这套系统。