Gitlab CICD 实践,思考与记录
2024-7-2
·
hexer

本文更多的是对之前gitlab部署的补充,单机环境不会涉及k8s滚动部署之流

环境

测试环境:局域网测试服务器(1台)

生产环境:阿里云生产服务器(1台)

在局域网的服务器中用gitlab存储代码,以及运行了gitlab runner用来执行ci脚本

持续交付

版本控制

局域网中部署gitlab,在上面提交代码

分支策略

主分支策略:仓库只保留主干,其他非核心开发者只能分出新分支,当新功能完成后,上传代码请提出合并请求(PR)

多分支策略:在主分支策略的基础上开几个新的分支,可能是专门用于测试环境的分支(测试完成后合并到master),可能是release分支(大版本更新的分支),可能是对一些大型功能进行重构的分支,可能是专门用来解决bug的分支…

代码提交规范

参考: 语义化版本约定式提交

可以使用 Checkstyle,ESLint 等规范代码风格

用 Husky 等工具使用 git hook

如果是规范 git commit 信息,可以直接在 .git/hooks/commit-msg中写入

#!/bin/sh

# 提交信息格式正则表达式
commit_regex='^(feat|fix|docs|style|refactor|perf|test|build|ci|chore|revert)(\([a-z0-9_-]+\))?: .{1,50}$'

# 读取提交信息
commit_message=$(cat "$1")

# 检查提交信息是否符合正则表达式
if ! echo "$commit_message" | grep -Eq "$commit_regex"; then
  echo "Error: Invalid commit message format."
  echo "Proper commit message format is required for automated changelog generation."
  echo "Examples: "
  echo "  feat(module): add a new feature"
  echo "  fix(module): fix a bug"
  exit 1
fi

这样,如果 git commit 信息不符合规范就会无法提交

部署自动化

根据gitlab ci脚本可以选择不同的策略:

  • 指定master/main分支,有新提交就自动执行ci脚本
  • 指定分支,当新提交有指定tag(例如test-0.2.1,prod-0.2.2)时执行ci脚本
  • 当有新的PR也可以执行ci脚本

单机部署方式探讨

单服务器,以何种方式进行部署更合适?

  • 线上一套环境(当项目出现bug时需要停机回滚)
  • 线上两套环境相互切换,即蓝绿部署(用nginx切换两套环境,当更新后出问题了就切换回原环境)
  • 其他…

蓝绿部署还有一种,是直接切软链接,但我觉得不太靠谱(强制停机软链接可能会丢失,但其他的问题我就不清楚了)

复杂的项目限制,我在实践中选择了蓝绿部署

  • 缺点:占空间,需要维护两套环境
  • 优点:回滚方便,回滚的速度也快

其他的滚动部署,金丝雀部署等方式不适用单机环境

.gitlab-ci.yml的不同场景

16.11 gitlab-ci

# Executors用docker还是ssh

docker的开启和关闭是需要时间的,缓存的上传和下载也是需要时间的,这时候为了减少时间可以专门开一个构建用的docker,把环境弄好,构建的时候用ssh执行器直接连接,这样依赖无需下载也无需docker开启关闭的时间

我之前写的就是ci太慢了,部署springboot项目,maven构建好了,那么又把构件移到rsync的docker里发往远程服务器,这不是浪费了相当多时间吗

专门的docker构建环境

可能需要安装的:

  • rsync
  • git
  • maven
  • node
  • java jdk/jre(如果是构建jar包就需要jdk,不然jre就行)

可以考虑用nvmmise这种版本切换工具来切换不同环境,且这种工具一般是无需root权限的,是用户级别的命令

大部分此类工具在非交互式shell里和一般的操作是不一样的,例如非交互式shell里直接使用nvm是不行的,需要先source ~/.bashrc一下

当然,也可以自行编写dockerfile

ssh 执行器取消git clone操作

单个作业脚本中添加:

  variables:
    GIT_STRATEGY: none

指定分支/标签

  only:
    refs:
      - /^prod-.*$/
    variables:
      - $CI_COMMIT_REF_NAME == "master"

Git回滚时指定SHA

  stage: build
  variables: {} # 可以用来跳过单个作业中的全局变量
  script:
    - |
      git checkout $COMMIT_SHA
      echo "COMMIT_SHA: $COMMIT_SHA"

点入作业,可以手动修改变量(覆盖变量)后提交

rsync传输文件

  script:
    rsync -av ~/A/ devops@$PROD_SERVER:/home/devops/cache/

rsync

  • 排除文件夹 : rsync -av --chmod=755 --exclude '.git' ~/A/ devops@$PROD_SERVER:/home/devops/cache/
  • 修改文件权限 : rsync -av --chmod=755 ~/A/ devops@$PROD_SERVER:/home/devops/cache/
  • 默认会把...的权限也同步,所以需要加上 --no-implied-dirs : rsync -av --no-implied-dirs ~/A/ devops@$PROD_SERVER:/home/devops/cache/

减少依赖安装次数

可以考虑,当依赖文件发生变化才安装依赖

可以使用 git diff 查询本次提交和上次的提交有什么区别,如果以下文件不一样就构建

  • ‘pom.xml’
  • ‘package.json’
  • ‘package-lock.json’
  • ‘yarn.lock’

并行策略

有时可以考虑并行执行任务

build_source m-source m &
build_source n-source n &
wait

跨pipelines缓存

node依赖,maven依赖等

  cache:
    key: "XXX_CACHE"
    paths:
      - node_modules

普通不加key的缓存不能跨pipelines

部分执行器是不支持缓存的,例如ssh执行器(但是ssh执行器本身就不需要缓存)

dockerhub访问问题

可以考虑用docker_image_pusher转成国内的镜像

单独开一个构建专用的容器的利弊

单独开一个容器进行:

  • 构建(maven的打包,npm的build等)
  • 推送(rsync等)

优点:

  • 省时间(好处是构建的依赖不会丢失,比每次用缓存上传下载依赖省时间,且无需找到rsync或者node,maven容器再启动)
  • 环境一致
  • 与主机环境隔离

缺点:

  • 需要单独创建一个容器,并在里面安装好对应的依赖
  • 容器管理麻烦(需要将容器的22端口映射到外部,使用ssh执行器连接)
  • 需要占用一定资源

其实在测试环境开一个新用户,在里面直接安装需要的工具并构建,也是可行的,但就是没有与主机隔离就是了

无需root启动容器

可以使用docker的替代品podman代替docker操作,命令几乎完全一样

docker反复构建镜像造成空间不够

解决办法:

  • 在构建完成后直接删除多余的容器
  • 直接用脚本定期清理多余的构建容器

重复构建相同名称相同标签相同的容器,会造成悬空镜像(可以用时间戳随机生成来打标签,防止生成悬空镜像)

修改dockerfile时旧的镜像层次可能会变成悬空镜像

手动清理空间步骤:

查看命令:

# 查看docker网络
docker network ls
# 查看容器卷
docker volume ls
# 查看容器镜像
docker image ls
## 或者
docker images
# 查看悬空镜像
docker image ls -f dangling=true
# 查看所有悬空卷:
docker volume ls -f dangling=true
# 查看未使用镜像
docker image prune -a --filter "until=24h"

删除命令:

# 删除镜像
docker rmi mh-go
# 删除所有悬空镜像
docker image prune
# 删除所有悬空卷
docker volume prune
# 删除所有未使用的 Docker 对象(包括停止的容器,未使用的网络,悬空镜像和未使用的镜像,构建缓存,不包括悬空卷)
docker system prune -a