Gitlab CI/CD实践

Gitlab支持通过配置CI/CD实现自动化部署我们的代码项目,主要核心就是配置gitlab-ci.yml文件以及在目标服务器上配置gitlab-runner(❣️为什么是在目标服务器?因为配置文件中的脚本就是在runner所在服务器执行的,所以把runner部署在目标服务器,就可以使用cp命令,是本地拷贝,不跨服务器,也省了配置SSH免密登录等步骤)

一,项目根目录中新增gitlab-ci.yml详细解释可查阅这里

示例1(复杂版)

default:
  before_script:
    - echo "start deploy"
    - echo $CI_COMMIT_REF_NAME

# 阶段
stages:
  - install
  - buildDev
  - buildTest
  - buildProd
  - deployDev
  - deployTest
  - deployProd

cache:
  paths:
    - node_modules/

# 安装依赖
install:
  stage: install
  # 此处的tags必须填入之前注册时自定的tag
  tags:
    - install
  # 规定仅在dev、test、prod分支提交时才触发此阶段
  only:
    - dev
    - test
    - prod
  # # 规定仅在package.json提交时才触发此阶段
  # changes:
  #   - package.json
  # 执行脚本
  script:
    - echo "start install"
    - rm -rf ./node_modules
    - npm install

# 打包dev分支
buildDev:
  stage: buildDev
  tags:
    - buildDev
  # 规定仅在dev分支提交时才触发此阶段
  only:
    - dev
  script:
    - echo "start dev build"
    - rm -rf ./dist
    - npm run build:dev
  # 将此阶段产物传递至下一阶段
  artifacts:
    paths:
      - dist/

# 打包test分支
buildTest:
  stage: buildTest
  tags:
    - buildTest
  # 规定仅在test分支提交时才触发此阶段
  only:
    - test
  script:
    - echo "start test build"
    - rm -rf ./dist
    - npm run build:test
  # 将此阶段产物传递至下一阶段
  artifacts:
    paths:
      - dist/

# 打包prod分支
buildProd:
  stage: buildProd
  tags:
    - buildProd
  # 规定仅在prod分支提交时才触发此阶段
  only:
    - prod
  script:
    - echo "start prod build"
    - rm -rf ./dist
    - npm run build:prod
  # 将此阶段产物传递至下一阶段
  artifacts:
    paths:
      - dist/

# 部署dev项目
deployDev:
  stage: deployDev
  tags:
    - deployDev
  only:
    # 规定仅在dev分支提交时才触发此阶段
    - dev
  script:
    - echo "start deploy Dev"
    # 删除远端服务器的dist目录下的所有文件
    - ssh -p 33953 root@17.215.219.70 'rm -rf /home/web/appointment_h5/dist'
    # 把当前dist目录下的所有文件传输到远端服务器的dist目录
    - scp -r -P 33953 ./dist root@17.215.219.70:/home/web/appointment_h5
    - echo "deploy Dev successfully"

# 部署test项目
deployTest:
  stage: deployTest
  tags:
    - deployTest
  only:
    # 规定仅在test分支提交时才触发此阶段
    - test
  script:
    - echo "start deploy Test"
    # 删除远端服务器的dist目录下的所有文件
    - ssh -p 33953 root@17.215.219.70 'rm -rf /home/web/appointment_h5/dist'
    # 把当前dist目录下的所有文件传输到远端服务器的dist目录
    - scp -r -P 33953 ./dist root@17.215.219.70:/home/web/appointment_h5
    - echo "deploy Test successfully"

# 部署prod项目
deployProd:
  stage: deployProd
  tags:
    - deployProd
  only:
    # 规定仅在prod分支提交时才触发此阶段
    - prod
  script:
    - echo "start deploy Prod"
    - pwd
    # 删除远端服务器的dist目录下的所有文件
    - ssh -p 33953 root@17.215.219.70 'rm -rf /home/web/appointment_h5/dist'
    # 把当前dist目录下的所有文件传输到远端服务器的dist目录
    - scp -r -P 33953 ./dist root@17.215.219.70:/home/web/appointment_h5
    - echo "deploy Prod successfully"

示例2(简单版)

  build:
    stage: build
    script:
      - echo "start build"
  
  test:
    stage: test
    script:
      - echo "start test"
  
  deploy:
    stage: deploy
    script: 
      - echo "start deploy"

二,在gitlab上查看gitlab-runner配置方法

打开安装教程,菜单路径:Admin / CI/CD / Runners,Url: https://你的gitlab域名/admin/runners

以Linux操作系统为例,安装脚本如下

# Download the binary for your system
sudo curl -L --output /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64

# Give it permission to execute
sudo chmod +x /usr/local/bin/gitlab-runner

# Create a GitLab Runner user
sudo useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash

# Install and run as a service(备注:执行本行命令时报错 gitlab-runner: command not found,需要加上路径)
sudo gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
sudo gitlab-runner start

#Command to register runner
#备注:这一步需要你输入项目描述、tags、以及部署的执行方式,tags就是.gitlab-ci.yml中指定的,需要一一对应,也可以定义一个通用的,复杂版中示例的tags是buildDev, buildProd, buildTest, deployDev, deployProd, deployTest, install,执行方式是shell
sudo gitlab-runner register --url https://gitlab.yourdomain.com/ --registration-token pP4NhXS9A7HQC1Fxi9Yk

三,注册完成后, 就会在Runners列表中看到

四,推送代码,触发runner,实现自动构建和部署

向gitlab-ci.yml指定的分支(或不区分分支,对应上述gitlab-ci.yml简单版配置)推送代码,gitlab就会监听仓库指定分支的变化,从而触发runner,执行gitlab-ci.yml上的script脚本,这样就会把项目自动部署到服务器上

五,查看CI/CD过程日志

在项目Build中找到Pipelines,可以看到全部过程,点击Passed可以看到详细流程(简单版)

六,配置gitlab服务器免密登录应用服务器,方便脚本拷贝文件(该章节适用于runner为独立服务器,不和应用服务器在一起的情况)

#在GitLab服务器上执行以下命令,会在/root/.shh/目录下生成两个文件,它们就是私钥 (id_rsa) 与公钥 (id_rsa.pub)文件
ssh-keygen -t rsa

#在GitLab服务器上执行以下命令将公钥拷贝到应用服务器上
scp /root/.ssh/id_rsa.pub root@172.24.142.64:/root/.ssh/id_rsa_gitlab.pub

#在应用服务器上执行以下命令,将GitLab服务器的公钥内容写入authorized_keys文件
cat /root/.ssh/id_rsa_gitlab.pub >> /root/.ssh/authorized_keys

#在GitLab服务器上执行以下命令,可以发现不用输入用户名密码就可以登录到应用服务器了
ssh 172.24.142.64

七,调整gitlab-runner权限,以完成对目标服务器的操作(该章节适用于runner安装在目标服务器,属于本地操作)

默认安装的gitlab-runner,用户是gitlab-runner,组是gitlab-runner,而常规情况下应用目录都是在www组www用户下(Nginx),所以就会出现拷贝文件失败的情况,提示没有权限,如下

Running with gitlab-runner 17.5.3 (12030cf4)
  on wonderbyte_cicd t1_tCTjo, system ID: s_9ec25d1764c9
Preparing the "shell" executor
00:00
Using Shell (bash) executor...
Preparing environment
00:00
Running on iZp0wf6tdgetcihaqjpu4nZ...
Getting source from Git repository
00:00
Fetching changes with git depth set to 20...
Reinitialized existing Git repository in /home/gitlab-runner/builds/t1_tCTjo/0/root/test-cicd/.git/
Checking out 91c3dbdd as detached HEAD (ref is main)...
Skipping Git submodules setup
Executing "step_script" stage of the job script
00:01
$ echo "start test deploy"
start test deploy
$ cp -r ./* /home/gitlab-test
cp: cannot create regular file ‘/home/gitlab-test/README.md’: Permission denied
Cleaning up project directory and file based variables
00:00
ERROR: Job failed: exit status 1

方案一:更改目录所有者

#将 /home/gitlab-test 目录的所有者更改为 gitlab-runner 用户:
sudo chown -R gitlab-runner:gitlab-runner /home/gitlab-test

方案二:更改目录权限

#如果不想更改所有者,可以调整目录权限以允许 gitlab-runner 用户写入:
sudo chmod -R 755 /home/gitlab-test

#或者,为了更宽松的权限:
sudo chmod -R 775 /home/gitlab-test

方案三:添加 gitlab-runner 用户到目标目录的组

#假设 /home/gitlab-test 目录属于某个特定组(例如 www-data),您可以将 gitlab-runner 用户添加到该组:
sudo usermod -aG www-data gitlab-runner

最终我选择了方案三,变更下runner的用户组,这样就可以正常执行cp命令了

Running with gitlab-runner 17.5.3 (12030cf4)
  on wonderbyte_cicd t1_tCTjo, system ID: s_9ec25d1764c9
Preparing the "shell" executor
00:00
Using Shell (bash) executor...
Preparing environment
00:00
Running on iZp0wf6tdgetcihaqjpu4nZ...
Getting source from Git repository
00:01
Fetching changes with git depth set to 20...
Reinitialized existing Git repository in /home/gitlab-runner/builds/t1_tCTjo/0/root/test-cicd/.git/
Checking out 4e3030bd as detached HEAD (ref is main)...
Skipping Git submodules setup
Executing "step_script" stage of the job script
00:00
$ echo "start test deploy"
start test deploy
$ cp -rf ./* /home/gitlab-test
$ echo "deploy test successfully"
deploy test successfully
Cleaning up project directory and file based variables
00:00
Job succeeded

八,使用 systemd 管理 Gunicorn,实现完全自动化(基于Python,Flask, Flask-SocketIO的项目实践)

systemd 是现代Linux系统中的初始化系统和服务管理器。通过创建一个systemd服务单元文件,可以管理Gunicorn进程的启动、停止和重启。

a.在目标服务器上创建一个systemd服务文件,例如 /etc/systemd/system/my_flask_app.service:

[Unit]
Description=Gunicorn instance to serve my_flask_app
After=network.target

[Service]
User=your_username
Group=www-data
WorkingDirectory=/path/to/your_project
Environment="PATH=/path/to/your_project/venv/bin"
ExecStart=/path/to/your_project/venv/bin/gunicorn -b 0.0.0.0:6060 --worker-class eventlet -w 1 app:app

[Install]
WantedBy=multi-user.target

#注意:
#User 和 Group:设置为运行应用程序的用户和组,确保有权限访问项目目录和文件。
#WorkingDirectory:你的项目目录路径。
#Environment:指定Python虚拟环境的路径。
#ExecStart:Gunicorn启动命令,指向正确的应用入口(根据你的wsgi.py,可能是 wsgi:app 或 wsgi:socketio_server)。

b.重新加载 systemd 配置并启动服务

sudo systemctl daemon-reload
sudo systemctl start my_flask_app
sudo systemctl enable my_flask_app  # 开机自启

c.验证服务状态

sudo systemctl status my_flask_app

d.修改gitlab-runner用户的sudo免密码交互权限

#使用visudo命令编辑sudoers文件,以避免语法错误:
sudo visudo

#在sudoers文件末尾,添加以下行:
gitlab-runner ALL=NOPASSWD: /bin/systemctl restart openai-test.service, /bin/chown www -R /home/wwwroot/openai-test


#gitlab-runner:替换为实际运行GitLab Runner的用户。
#ALL:表示从任何主机都可以执行。
#NOPASSWD::表示不需要密码。
#/bin/systemctl restart openai-test.service:具体允许执行的命令,多个命令用逗号隔开。

e.修改gitlab-ci.yml脚本命令

# 示例一,未考虑环境搭建及依赖安装,只复制代码及重启服务,已验证可执行
# 阶段
stages:
  - deployDev

# 部署dev项目
deployDev:
  stage: deployDev
  tags:
    # 规定使用哪个Runner
    - centos_runner
  only:
    # 规定仅在dev分支提交时才触发此阶段
    - dev
  script:
    - echo "start deploy Dev"
    # 把当前目录下的所有文件传输到本地项目目录(Runner在本地)
    - cp -rf ./* /home/wwwroot/openai-test
    - sudo systemctl restart openai-test.service
    - sudo chown www -R /home/wwwroot/openai-test
    - echo "deploy Dev successfully"
# 示例二,首先判断环境情况,再复制代码和重启服务,未验证
# 定义阶段
stages:
  - deployDev

# 部署dev项目
deployDev:
  stage: deployDev
  tags:
    - centos_runner  # 确保Runner标签正确
  only:
    - dev  # 仅在dev分支触发
  script:
    - echo "Start deploying Dev"
    
    # 1. 复制代码到目标目录
    - cp -rf ./* /home/wwwroot/openai-test/
    
    # 2. 检查是否为首次部署
    - |
      if [ ! -f /home/wwwroot/openai-test/.deploy_done ]; then
        echo "First deployment: installing dependencies and starting service."
        cd /home/wwwroot/openai-test
        
        # 创建虚拟环境(如果不存在)
        if [ ! -d "venv" ]; then
          python3 -m venv venv
        fi
        
        # 激活虚拟环境
        source venv/bin/activate
        
        # 升级pip
        pip install --upgrade pip
        
        # 安装依赖
        pip install -r requirements.txt
        
        # 重启systemd服务
        sudo systemctl restart openai-test.service
        
        # 创建标志文件,标记首次部署完成
        sudo touch .deploy_done
      else
        echo "Subsequent deployment: restarting service."
        # 重启systemd服务
        sudo systemctl restart openai-test.service
      fi
    
    - echo "Deploy Dev successfully"

通过以上配置,就完成了对项目的自动部署及自动重启,整个过程无需人工干预,只需提交代码,后面就全自动了

九,题外:本地项目开发早于仓库的创建,仓库创建后,并没有将代码提交到主干main(不太会,也没查),就直接创建了个分支dev,将代码提交到分支dev上,然后又从分支dev合并到主干main,第一次主干是空的,合并很顺利,但后续再更新合并时,总会提示存在冲突,但想来主干的代码完全是从分支合并过来的,不应该有冲突,遂查之,是由于主干和分支的基线不一致导致的,通过以下命令强制将main指向dev

a. 强制将 main 分支指向 dev 分支

# 切换到 dev 分支并确保是最新的
git checkout dev
git pull origin dev

# 切换到 main 分支
git checkout main

# 将 main 分支重置为与 dev 分支一致
git reset --hard dev

# 强制推送到远程 main 分支
git push origin main --force


这样,main 分支将完全与 dev 分支同步,后续 dev 分支的更新应该可以顺利合并到 main,而不会产生冲突。

b.确保未来的合并策略

1). 从 main 分支创建 dev 分支:

确保 dev 分支是从 main 分支正确派生的,这样可以保持两者的提交历史一致。

git checkout main
git pull origin main
git checkout -b dev

2). 在 dev 分支上进行开发:

所有的开发工作都在 dev 分支上进行,避免在 main 分支上直接进行修改。

3). 定期同步 main 到 dev(如果有必要):

虽然在目前的情况下 main 仅通过 dev 更新,但如果有其他原因需要同步,可以定期将 main 分支的更改合并回 dev。

git checkout dev
git pull origin main

4). 使用合并请求(Merge Request)进行合并:

在 GitLab 上,通过创建合并请求将 dev 分支的更改合并到 main,这样可以在合并前检查潜在的冲突,并在合并过程中更好地管理代码。

Leave a Comment

Your email address will not be published.

*

此站点使用Akismet来减少垃圾评论。了解我们如何处理您的评论数据