侧边栏壁纸
博主头像
ZDREAM - Thassarian 的个人博客

对抗变化的方法唯有拥抱变化。

  • 累计撰写 37 篇文章
  • 累计创建 3 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

打通基于GitLab的CI/CD(持续集成自动部署)

Thassarian
2024-05-05 / 0 评论 / 0 点赞 / 5 阅读 / 0 字

早在2023年,我就已经部署完了GitLab,用来做基础的代码管理,初步尝试CI/CD未果之后搁置。

如今通过 AI agent 来完成软件开发全流程已经最初从美好的畅想、混乱的实践,逐步变得越来越稳健、可落地了。

为了实现 code-agent 开发完成之后 test-agent 能够基于实际打包后的功能做黑盒测试 、完成图形页面的检查,自动化部署从可选优化项变成了刚需。

目前为止,我先跑通了简单的前端-本机自动部署,后续有精力再参照大厂最佳实践做一些改善。

逐步演进到 “全镜像化交付部署”,最终拥抱 “K8s 云原生编排”。

一、GitLab部署

官方提供了Docker部署的方式,不过有点笨重,它内置了Grafana和MySQL,好在可以通过配置环境变量让GitLab使用外部的通用版,节省内存,统一管理数据。

通过Portainer + docker-compose 的方式部署,个人配置过程中还是踩了非常多的坑,不过回顾了一下,很多经验没什么泛用性,还是略过了。

建议直接参考官方文档:

https://docs.gitlab.cn/docs/jh/install/docker/configuration/

二、架构概览

 GitLab Runner (Docker executor, privileged=false)
 │
 ├─ 同一宿主机 (ubuntu jammy)
 │   ├─ Nginx (:xxx 前端静态, :yyyy 后端代理)
 │   └─ jdk17-base 容器 (:zzzz, jar包更新到 jre容器中,触发脚本重启应用)
 │
 └─ GitLab (:xxx)
     ├─ 后端项目 → main 分支触发 → 自动部署生产
     └─ 前端项目 → main 分支触发 → 自动部署生产

二、准备打包专用镜像

GitLab runner在执行CI/CD的时候,会创建一个临时容器处理打包动作,会需要用到打包基础镜像。

我准备了一个包含 JDK 17 + Maven + Node.js 20 + pnpm、做了各类镜像源加速优化的镜像,前后端通用(对于我自己的项目)。

Dockerfile:

FROM ubuntu:jammy

LABEL version="1.0.0"
LABEL description="专用于zdream前后端CI/CD,包含阿里云maven镜像源、JDK 17 + Maven + Node.js 20 + pnpm"
LABEL maintainer="zc@zdream.cn"

# 设置环境变量
ENV DEBIAN_FRONTEND=noninteractive
ENV JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64
ENV MAVEN_HOME=/usr/share/maven

# 1. 替换 APT 源为阿里云,并安装基础依赖 (JDK、Maven、curl、xz-utils)
RUN sed -i 's/archive.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
    sed -i 's/security.ubuntu.com/mirrors.aliyun.com/g' /etc/apt/sources.list && \
    apt-get update && apt-get install -y \
    openjdk-17-jdk \
    maven \
    curl \
    xz-utils \
    # 清理 apt 缓存以减小镜像体积
    && apt-get clean && rm -rf /var/lib/apt/lists/*

# 2. 极速下载安装 Node.js 20 (使用阿里镜像二进制包) 与 pnpm
RUN curl -fsSL "https://npmmirror.com/mirrors/node/v20.11.1/node-v20.11.1-linux-x64.tar.xz" -o node.tar.xz \
    && tar -xJf node.tar.xz -C /usr/local --strip-components=1 \
    && rm node.tar.xz \
    # 使用阿里镜像安装 pnpm
    && npm install -g pnpm --registry=https://registry.npmmirror.com \
    # 配置 pnpm 默认使用阿里镜像源
    && pnpm config set registry https://registry.npmmirror.com \
    # 关闭 pnpm 更新提示,保持 CI 输出整洁
    && pnpm config set update-notifier false

# 3. 配置 Maven 阿里云镜像与本地仓库路径
# 将准备好的 aliyun.xml 直接覆盖系统的全局 settings.xml
COPY aliyun.xml /usr/share/maven/conf/settings.xml
# 预先创建你在 xml 中配置的仓库挂载目录
RUN mkdir -p /usr/share/maven/ref/repository

# 4. 验证安装版本 (构建成功时会在日志末尾打印版本号)
RUN echo "=== 环境版本验证 ===" \
    && java -version \
    && mvn -version \
    && echo "Node version: $(node -v)" \
    && echo "npm version: $(npm -v)" \
    && echo "pnpm version: $(pnpm -v)"

# 5. 设置工作目录
WORKDIR /workspace

附 aliyun.xml 文件

<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
  xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
                      https://maven.apache.org/xsd/settings-1.0.0.xsd">
  <localRepository>/usr/share/maven/ref/repository</localRepository>
  <mirrors>
    <mirror>
      <id>aliyunmaven</id>
      <mirrorOf>*</mirrorOf>
      <name>阿里云公共仓库</name>
      <url>https://maven.aliyun.com/repository/public</url>
    </mirror>
  </mirrors>
</settings>

构建完成之后,推送到自己的GitLab 镜像仓库

二、更新 Runner 默认镜像

无需修改 runner 配置.gitlab-ci.yml 中的 image: 会覆盖 runner 默认值。

当前 runner 配置(已存在,不需要改动):

  • executor: docker

  • privileged: false(安全,无 DinD)

  • concurrent: 1

  • builds_dir: /builds(映射到宿主机 /zdream/datas/gitlab-runner/builds

  • 挂载: /.m2(Maven 缓存)、/home/project/cache(npm 缓存)

三、前端.gitlab-ci.yml

# 定义 CI/CD 的阶段
stages:
  - build
  - deploy

# 全局变量配置
variables:
  # 将 pnpm 的全局 store 设置到我们挂载的缓存目录中,实现跨 Pipeline 的依赖缓存加速
  PNPM_STORE_PATH: '/home/project/cache'
  NODE_OPTIONS: '--max-old-space-size=4096'

# 全局默认配置
default:
  # 使用你提前准备好的包含了完整环境的构建镜像
  image: registry.我的域名端口/zdream/zdream-builder:latest

# ==========================================
# 1. 构建阶段
# ==========================================
build_frontend:
  stage: build
  script:
    - echo "开始安装依赖并构建..."
    # 配置 pnpm 使用缓存目录
    - pnpm config set store-dir $PNPM_STORE_PATH
    # 安装依赖
    - pnpm install
    # 执行打包 (Vite 默认产物是 dist 文件夹)
    - pnpm run build
  # 收集构建产物,传递给下一个阶段
  artifacts:
    name: 'dist-$CI_COMMIT_SHORT_SHA'
    paths:
      - dist/
    expire_in: 1 day # 产物在 GitLab 服务器上保留 1 天即可
  # 仅在指定分支发生变更时执行 
  only:
    - main
    - web-zdream

# ==========================================
# 2. 部署阶段
# ==========================================
deploy_frontend:
  stage: deploy
  # 依赖 build 阶段,确保先构建后部署
  dependencies:
    - build_frontend
  script:
    - echo "开始部署到宿主机目录..."
    # 确保宿主机的目录存在 (容器内执行时,如果宿主机没这个目录会自动创建空目录)
    - mkdir -p /zdream/web/dist
    # 清空旧的代码 (避免旧的 hash 文件残留)
    - rm -rf /zdream/web/dist/*
    # 将上一阶段 artifacts 传过来的 dist 目录内容复制到宿主机挂载目录下
    - cp -r dist/* /zdream/web/dist/
    - echo "部署完成!"
  only:
    - main
    - web-zdream
  1. 添加 .gitlab-ci.yml 到项目

  2. 推送更新到 main 分支

  3. 排错,或者 ☆[BINGO!]

四、后端 .gitlab-ci.yml

 stages:
   - build
   - deploy
 ​
 variables:
   MAVEN_OPTS: "-Dmaven.repo.local=/.m2/repository"
 ​
 cache:
   key: "${CI_COMMIT_REF_SLUG}"
   paths:
     - /.m2/repository
 ​
 build:backend:
   stage: build
   tags:
     - base-runner
   image: registry.gitlab.cn/YOUR_USERNAME/zdream/builder:latest
   script:
     - mvn clean package -DskipTests -q
     - ls -lh target/*.jar
   artifacts:
     paths:
       - target/*.jar
     expire_in: 1 hour
   only:
     - main
     - develop
 ​
 deploy:backend:
   stage: deploy
   tags:
     - base-runner
   image: registry.gitlab.cn/YOUR_USERNAME/zdream/builder:latest
   script:
     - |
       echo "=== [Backend] 部署 ==="
       
       # jar 路径
       JAR_PATH=$(find ${CI_PROJECT_DIR}/target -name "*.jar" | head -1)
       JAR_NAME=$(basename ${JAR_PATH})
       
       # 备份旧版本(保留最近 3 个)
       BACKUP_DIR="/zdream/datas/jdk-base/backups"
       mkdir -p ${BACKUP_DIR}
       if [ -f /zdream/datas/jdk-base/app/zdream/${JAR_NAME} ]; then
         cp /zdream/datas/jdk-base/app/zdream/${JAR_NAME} \
            ${BACKUP_DIR}/${JAR_NAME}.$(date +%Y%m%d%H%M%S)
         echo "备份完成"
         ls -t ${BACKUP_DIR} | tail -n +4 | xargs -I{} rm -f ${BACKUP_DIR}/{}
       fi
       
       # 复制新 jar(挂载卷自动同步到 jdk17-base 容器)
       cp ${JAR_PATH} /zdream/datas/jdk-base/app/zdream/${JAR_NAME}
       echo "JAR 已复制: ${JAR_NAME}"
       
       # 重启 jdk17-base 容器
       docker restart jdk17-base
       echo "jdk17-base 重启命令已发送"
       
       sleep 8
       docker logs jdk17-base --tail 15
   environment:
     name: ${CI_COMMIT_REF_NAME}
     url: https://www.hitagi.cn:18898
   dependencies:
     - build:backend
   only:
     - main
     - develop

五、进阶计划

AI 时代,学习新技能的门槛大大降低。

了解到大厂的CI/CD最佳实践,他们实现的是容器化交付,每次更新的产物是一个新的镜像,版本更新直接修改docker-compose中的镜像版本号update一下即可。

当单机 Docker-Compose 无法满足高可用需求时,架构将不可避免地走向 Kubernetes 集群编排。叠加了 K8s 之后,CI/CD 流程可以完成质的飞跃:

  • CD(持续交付)视角的转变: 部署不再是去操作具体的某一台服务器,而是向 K8s 的 API Server 提交一份 YAML 声明文件。告诉 K8s:“我期望后端应用运行 3 个副本,镜像版本是 v1.0.2”。

  • 零停机滚动更新(Rolling Update): 我们目前重启 Java 容器时,必然会存在几秒到几十秒的服务不可用。而在 K8s 中,它会先启动新版本的 Pod,等新 Pod 的健康检查(Readiness Probe)通过后,再把流量切过去,最后平滑销毁老 Pod。全程对用户无感。

  • 弹性伸缩与自愈: 如果某个 Node 节点宕机,K8s 会自动把上面的应用迁移到其他健康的节点上;如果遇到双十一等突发流量,可以通过 HPA(Horizontal Pod Autoscaling)根据 CPU 使用率自动横向扩容容器数量,流量过后再自动缩容。

0

评论区