持续集成-gitea

持续集成-gitea

简述

官方文档 https://docs.gitea.cn/

源码仓库 https://github.com/go-gitea/gitea

这个项目最开始是 fork gogs 的,看资讯说是 gogs 的作者控制欲太强, 很多功能和社区期望功能都不愿意加入进来,于是才有的分支。

目前看起来 gitea 的发展得更好,因为大家确实需要一个私有的 github, 而 gitlib 又太重量级了。

功能

核心的代码管理方面相关功能都有,如工单、软件包注册中心,Wiki、钩子等。

硬件需求很低, 1G 内存的主机就够了。

安装方式

支持 二进制部署, docker 部署, k8s 部署, 基本各平台都支持。
详细的这里就不介绍了,看官方文档就行,下面只是简单的介绍一下

基本过程如下

  1. 提前一个数据库和用户帐号, 如 mysql, pg
  2. 使用 docker 或 k8s yml 文件进行部署

docker-compose 方式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
version: "3"

services:

  gitea:
    # 镜像提前从 docker hub 下载好了, 上传到的私有仓库
    image: registry.services.wait/cwx/gitea:1.18.5
    restart: always

    environment:
      # 自定义运行用户id, 如果是数据卷和新装,就没有必要指定;
      # 因为以前是 bind 挂载所以文件已经有了属主, 所以这里指定只是为了兼容
      - USER_UID=1000
      - USER_GID=1000

      # 指定数据库类型和帐号密码
      - DB_TYPE=mysql
      - DB_HOST=10.2.1.5:3306
      - DB_NAME=gitea
      - DB_USER=gitea
      - DB_PASSWD=passw0rd

      # 随便取一个应用名字
      - APP_NAME=WaitCode
      - RUN_MODE=prod

      # 项目主域,和后续页面上看到的 http 项目地址有关系
      - DOMAIN=git.services.wait

      # 和 ssh clone 时页面看到的地址有关系
      - SSH_DOMAIN=git.services.wait

      # 如果 ssh 使用 22 端口的话,需要宿主机让出22端口或配置 ssh透传
      # 这里简单使用就避开端口就行
      - SSH_PORT=10022

      # 禁止用户注册
      - DISABLE_REGISTRATION=true

      # 需要登陆才能查看项目
      - REQUIRE_SIGNIN_VIEW=true

      # 最终实际的 http 服务地址
      - ROOT_URL=https://git.services.wait/

      # 离线模式, 也就不会用到一些 cdn 或外部的 js 文件等
      - OFFLINE_MODE=true
    volumes:
      - gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro

      # 挂了一个私有CA证书, 因为有向外的 webhook https 请求
      - ./config/cwxCA.pem:/etc/ssl/certs/cwxCA.pem:ro
    ports:
      - "3000:3000"
      # - "10022:10022"
    depends_on:
      - mysql
    labels:
      - "service_name=gitea"

      # 我之前采用的 traefik 做的统一网关, 如果不需要则直接将这一段删除, 并修改一下 ports
      - "traefik.enable=true"
      - "traefik.http.routers.giteaHttps.entrypoints=https"
      - "traefik.http.routers.giteaHttps.service=giteaWeb"
      - "traefik.http.routers.giteaHttps.rule=Host(`git.services.wait`)"
      - "traefik.http.routers.giteaHttps.tls=true"
      - "traefik.http.services.giteaWeb.loadbalancer.server.port=3000"

      - "traefik.tcp.routers.giteaSSH.entrypoints=port10022"
      - "traefik.tcp.routers.giteaSSH.service=giteaSSH"
      - "traefik.tcp.routers.giteaSSH.rule=HostSNI(`*`)"
      - "traefik.tcp.services.giteaSSH.loadbalancer.server.port=10022"

    logging:
      options:
        labels: "service_name"

volumes:
  gitea:
    external: true

说明,因为数据持久化用的外部命名卷, 所以这里提前创建一下

docker volume create gitea

docker-compose up -d

k8s helm

1
2
helm repo add gitea <https://dl.gitea.io/charts/> \
helm pull gitea/gitea --untar

下载下来后修改一下 values 文件的相关配置在安装

方法二: 资源文件方式安装

  1. 准备 pv 和 pvc 文件, 持久化存储, 我的 pvc 名字是 pvc-nfs-gitea

  2. statefulset 方式部署

  3. 初始配置, 因为需要前置的几个配置也比较少, 所以没有准备 configmap, 直接用的环境变量;
    还有个原因, gitea 会将中途修改的内容持久化到 配置文件中, 而配置文件又在卷路径下;
    所以最好还是不使用 configmap 来管理初始化配置

  4. services 采用的 traefik 的 IngressRoute IngressRouteTCP 方式进行的暴露

k8s 自定义资源文件

statefulset.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: gitea
  namespace: cwx
spec:
  selector:
    matchLabels:
      app: gitea
  serviceName: gitea
  replicas: 1
  template:
    metadata:
      labels:
        app: gitea
    spec:
      containers:
      - name: gitea
        image: registry.services.wait/cwx/gitea:1.20.2
        env:
          - name: USER_UID
            value: "1000"
          - name: USER_GID
            value: "1000"
          - name: DB_TYPE
            value: mysql
          - name: DB_HOST
            value: wcn7:3306
          - name: DB_NAME
            value: gitea
          - name: DB_USER
            value: gitea
          - name: DB_PASSWD
            value: passw0rd
          - name: APP_NAME
            value: WaitCode
          - name: RUN_MODE
            value: prod
          - name: DOMAIN
            value: git.services.wait
          - name: SSH_DOMAIN
            value: git.services.wait
          - name: SSH_PORT
            value: "10022"
          - name: SSH_LISTEN_PORT
            value: "10022"
          - name: DISABLE_REGISTRATION
            value: "true"
          - name: REQUIRE_SIGNIN_VIEW
            value: "true"
          - name: ROOT_URL
            value: https://git.services.wait/
          - name: OFFLINE_MODE
            value: "true"
        volumeMounts:
          - name: data
            mountPath: /data
          - name: timezone
            mountPath: /etc/timezone
            readOnly: true
          - name: localtime
            mountPath: /etc/localtime
            readOnly: true
          - name: ca
            mountPath: /etc/ssl/certs/cwxCA.pem
            subPath: cwxCA.pem
            readOnly: true
        resources:
          limits:
            cpu: "200m"
            memory: "300Mi"
        ports:
        - containerPort: 3000
          name: web
        - containerPort: 10022
          name: ssh
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: pvc-nfs-gitea
        - name: timezone
          hostPath:
            path: /etc/timezone
        - name: localtime
          hostPath:
            path: /etc/localtime
        - name: ca
          secret:
            secretName: cwx-ca-pem

service.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
apiVersion: v1
kind: Service
metadata:
  name: gitea
  namespace: cwx
spec:
  selector:
    app: gitea
  clusterIP: None
  ports:
  - port: 80
    name: web
    targetPort: 3000
  - port: 22
    name: ssh
    targetPort: 10022

IngressRoute.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
apiVersion: traefik.io/v1alpha1
kind: IngressRoute
metadata:
  name: giteaweb
  namespace: cwx
  annotations:
    kubernetes.io/ingress.class: "traefik-class"
spec:
  entryPoints:
    - https
  routes:
    - match: Host(`git.services.wait`)
      kind: Rule
      services:
        - name: gitea
          namespace: cwx
          port: 80
  tls:
    secretName: key-services-wait

ingressRouteTcp.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
apiVersion: traefik.io/v1alpha1
kind: IngressRouteTCP
metadata:
  name: giteassh
  namespace: cwx
  annotations:
    kubernetes.io/ingress.class: "traefik-class"
spec:
  entryPoints:
    - giteassh
  routes:
    - match: HostSNI(`*`)
      services:
        - name: gitea
          port: 22
          namespace: cwx

部署完成后事项

  1. 使用自己配置的域名地址进行访问
    https://git.services.wait/

  2. 第一个用户是超级管理员

  3. 然后就是正常的使用了

gitea-runner

优点:

  1. 一套系统完成开发过程中的主要功能, 不需要在多个系统之间切换
  2. 基本兼容 github actions, 生态友好
  3. 支持docker和NodeJs 类型的 actions 插件, 上手很容易
  4. 基本是中小型私有化场景部署的首选了

缺点:

  1. 太新, 今年初才发布;
  2. 更新慢, 感觉开发团队进展得很慢,团队资源不足的样子;
  3. 因为是兼容 github,所以使用过程中需要注意很多地方默认都是 github, 需要手动指定。
  4. 最大的缺点: k8s 不友好, 目前只支持 docker

文档(只能看github的):
https://docs.github.com/zh/actions/guides

原理

  1. runner 启动后注册到 gitea 等待 webhook
  2. 触发事件后拉取代码进行打包发布等操作

runner 自身

只负责和 gitea 通信和 actions 的调度
可以是二进制启动, 也可以是 docker 容器启动 runner

基础的运行环境

因为大部分或者说是官方主要 actions 的插件都是 nodeJs 开发的,
所以需要一个 nodejs 的运行环境,这个和 runner 是独立开的;

runner 只负责以容器的方式启动 一个 nodejs 环境, 然后所有的 js 类型的插件都在这个环境里面运行

如果插件类型是 docker, 则这个 docker action 是在 nodejs 环境运行的;

有几种层级关系

  1. runner
  2. runner – nodejsEnv(docker) – js actions
  3. runner – nodejsEnv(docker) – docker actions(docker)
  4. runner(docker) – nodejsEnv(docker) – docker actions(docker)

涉及到docker镜像

gitea/act_runner:0.2.5
也就是一个docker 的 runner 运行器, 内部除了附加一个 git 外没有别的工具

node:16-bullseye
一个默认的 nodejs 的运行环境, 内部有一些少量的工具支持
如 node npm python gcc 等, 但主要是为了运行 js 类型的 actions
也支持 docker 命令, 目的是运行 docker 类型的 actions

ghcr.io/catthehacker/ubuntu:act-22.04
一个第三方的 actions 运行环境, 参考 github 的
优点是命令和工具带得非常多, 很多时候不需要外部 action 而直接使用 run 执行 shell 命令就可以了

关于自定义action

使用CICD绕不过去的内容就是自定义插件;

github 支持2种插件形式

  1. js 类型的插件, 由运行环境上的 nodejs 直接运行
  2. docker 类型的插件, 由运行环境上的 docker 命令运行,会创建一个新的容器

个人 对 js 不大熟悉, 所以还是推荐 docker actions

这里的要点

1
2
3
4
5
- name: actions 的使用格式
    uses: actions/xxxx1:v1
    uses: actions/xxxx1@master
    uses: https://xxxxx/actions/xxxx1@master
    uses: docker://registry.services.wait/cwx/xxxx1:v1

uses 默认会去 github 上拉取仓库代码, 然后根据里面的 action.yml 文件判断这是一个 js 还是 docker 类型的插件。

如果是 js 类型的, 一般就需要执行 npm 先编译插件自身, 然后再执行插件;
如果是 docker 类型的, 就会按照 提供的 Dockerfile 来创建一个新的 docker 镜像, 并且执行这个镜像;
因为编译镜像是有缓存的, 所以后续再使用到这个docker镜像, 是不需要重新打包镜像的;

如果是不想去仓库拉取插件的代码, 而是使用已有镜像可以采用这种格式;
uses: docker://registry.services.wait/cwx/xxxx1:v1

镜像插件的参数传递

当需要在 yml 文件内把一个参数传递到 运行的 action 镜像内时, 可以如下

1
2
3
4
5
- name: actions 的使用格式
  uses: actions/xxxx1:v1
  with:
    cmd1: zzzz1
    cmd2: zzzz2

with 下面的 key, 会被以环境变量的方式传递到镜像内;
镜像内直接获取对应的环境变量即可获取到值, 但是需要加 INPUT 前缀,如

cmd1=${INPUT_CMD1}
cmd2=${INPUT_CMD2}

输出也是相同道理, 只不过需要定义 OUTPUT 来使用

状态截图

workflows

编译成功

离线actions使用

github-actions 在国内环境不太好用;

私有化环境一般使用 gitea, 搭配 act_runner 来使用, 兼容 github-actions;

离线环境如何使用呢?

尝试过的方案

AI 给出过一些错误的案例, 经验证没有成功

  1. uses: /local/actions/checkout@v6 本地路径方式,实际是错误的
  2. 各自 env 方式映射到本地路径,验证无效
  3. 拷贝 $HOME/.cache/act 缓存的方式, 验证了一半没有成功,理论上应该是可以的;
  4. 将 github 的相关actiions镜像到本地仓库, 可行;但是不能这样做
    • 要镜像太多库,麻烦
    • 如果放在本地, 这个库必须是要能免认证就可见的, 企业场景不允许
    • 如果单独在拉取时附带一个token, 则语法上就不好看, 就不兼容外网版本的workflows;

gitea使用 actions 的事项

  1. 默认会去 github 下载
  2. 可以使用 https://xxx.com/actions/checkout@v6 的方式指定库
  3. 如果开启 DEFAULT_ACTIONS_URL = self 配置, 则在无域名路径时, 默认去当前的git仓库下载actions
  4. 除非使用 uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd 哈希的方式, 否则每次都需要联网确认tag或分支的具体的hash

最终方案

还是在底层解决,网络代理的方式

1
2
3
4
5
6
7
# 允许 act_runner 时附加代理
# 可以连接局域网内的可访问github的socket5相关端口
# 注意一定要使用 NO_PROXY 排除掉自身域名
export HTTPS_PROXY=http://127.0.0.1:1081
export HTTP_PROXY=http://127.0.0.1:1081
export ALL_PROXY=socks5://127.0.0.1:1080
export NO_PROXY=localhost,192.168.5.0/24,127.0.0.0/8,services.wait

如果后续要完全离线使用, 则将相关的 workflows 都运行一次,当 actions 下载过一次后,后续就不需要再连github, 也不需要再配置代理了,当然actions版本的位置必须使用 hash 才行;

如果跨网络目标主机不能访问代理时,可以通过 ssh 反向代理到本地

1
2
# socket5 反向隧道
ssh -R 1080 -N -f ubuntu@192.168.5.110 -i id_ed25519

完全离线场景, 且不能使用代理时

从已经有的主机上拷贝 $HOME/.cache/act 缓存, 必须配合 uses: actions/checkout@de0facxxxxx 的方式使用。

gitea-runner 部署当前站点

当前站点正在使用的一套的集成流程

本站点历史发布模式

早期的脚本模式

  1. 本地编写好 MD 文件
  2. 执行 hugo 命令, 构建 WEB静态文件
  3. scp 传输到目标主机上

将上述流程脚本化

前期-drone

前期采用 drone 进行的自动化编译和部署,参考另一篇单独介绍的文章。
drone无人机-当前站点的发布实践

当前-gitea-runner

我又又又又决定再学一学 ts 了,没办法,搞这个 action 始终绕不过去 js 这一套东西, 虽然单纯的只用 docker 也不是不可以。但是很多既有的 action 也就没法用了。会点 js 至少还能拿来改一改吧。

实际工作流文件

真私有化部署, 不涉及 github 的访问;

流程中存在的问题

  1. 没有使用 js 类型的 action,太可惜了,后面得补一补
  2. 拉取代码,拉取镜像,配置环境实现得都不太优雅

.gitea/workflows/work.yml

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

# 工作流的名字
name: myblog-dev

on:
  push:
    branches: [ main ]

jobs:
  # 任务名
  hugo-task:
    # 选择所运行的平台, 也就是哪个类型的 runner
    runs-on: ubuntu-latest

    # 运行 action 的环境
    container:
      # 不使用默认的 node:16 的基础容器镜像, 使用支持docker命令的act镜像
      image: registry.services.wait/cwx/catthehacker/ubuntu:act-22.04
      # volumes:

    steps:
      # 配置私有 CA 证书
      - name: set ca
        # 注意这个位置单独拉出来配置, 是因为多行字符串会被解析为多行命令导致, 算是个 bug
        run: echo -e "${{ secrets.CWX_CA_PEM }}" >> /usr/local/share/ca-certificates/cwx.crt

      # 配置一下后续运行的基础环境
      - name: Set run env
        run: |
          update-ca-certificates

          # git config --global http.sslVerify false
          git config --global http.sslCAInfo /usr/local/share/ca-certificates/cwx.crt
          git --version

      # 克隆代码, 包括一份主题模板
      - name: clone code
        run: |
          git clone https://chenwx:${{secrets.CHENWX_TOKEN}}@git.services.wait/chenwx/myblog.git .
          git clone https://chenwx:${{secrets.CHENWX_TOKEN}}@git.services.wait/chenwx/hugo-theme-stack.git themes/hugo-theme-stack

      # 登陆仓库和拉取后面需要的 docker 镜像, 其实应该合并的 set env 里面去; 调试暂留
      # 目前存在个问题, 这个 镜像应该是不需要单独拉取一次的, 但报了认证错误
      # 应该是这里配置 login 时所发生的层和 实际 pull 时的层不一样导致, 后面找时间调试一下再修改。
      - name: login registry
        run: |
          echo "${{ secrets.HARBOR_PW }}" | docker login -u "${{ secrets.HARBOR_USER }}" --password-stdin registry.services.wait
          docker pull registry.services.wait/cwx/actions-hugo:ubuntu-v0.118
          docker pull registry.services.wait/cwx/ssh-scp-action:v1

      # 这个镜像只是单纯的将 hugo 命令打包了,并配置 ENTRYPOINT ["hugo"]
      - name: 编译-build
        uses: docker://registry.services.wait/cwx/actions-hugo:ubuntu-v0.118

      # 占个位置,看看有没有生成文件
      - name: 测试-test
        run: |
          ls public/

      # 也就是 scp 推送到目标路径
      - name: 发布-deploy
        uses: docker://registry.services.wait/cwx/ssh-scp-action:v1
        with:
          key: ${{ secrets.ED25519_KEY }}
          host: 10.2.1.5
          port: 22
          user: wait
          ssh_before: |
            rm -rf /data/nfs_private/blog-web-data/blog/*
          scp: |
            public/* wait@10.2.1.5:/data/nfs_private/blog-web-data/blog/

构建效果图

Licensed under CC BY-NC-SA 4.0
转载或引用本文时请遵守许可协议,知会作者并注明出处
不得用于商业用途!
最后更新于 2023-03-17 00:00 UTC