CI/CD 的核心概念是持续集成、持续交付和持续部署
- CI 持续集成(Continuous Integration)
- CD 持续交付(Continuous Delivery)
- CD 持续部署(Continuous Deployment)
GitLab 是一个用于仓库管理系统的开源项目,使用Git作为代码管理工具,并在此基础上搭建起来的Web服务。安装方法是参考GitLab在GitHub上的Wiki页面。
GitLab 是支持CI/CD的,当前项目使用的gitlab管理的代码,故自己尝试一下 jar的CI/CD。
参考
主要参考:GitLab CI/CD 自动化部署入门 ,手把手教你搭建 —— 从安装 Linux 到 GitLab 自动化部署(非常详细)
Gitlab CI 配置文件 .gitlab-ci.yaml 详解(上)
GitLab Runner的安装与使用
部署-gitlab克隆地址踩坑
Gitlab 安装gitlab-runner踩坑记录
什么是 CI/CD ?
操作
Linux服务器是使用的 公司内网测试服务 centos 7
gitlab的server端是由同事前不久部署的,版本 14.0.5
下载gitlab-runner,版本15.1.0
wget https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
ll
部署、授权、增加用户、安装、运行
cp gitlab-runner-linux-amd64 /usr/local/bin/gitlab-runner
# 分配运行权限
chmod +x /usr/local/bin/gitlab-runner
ll /usr/local/bin/
# 获取版本
gitlab-runner -v
# 创建用户
useradd --comment 'GitLab Runner' --create-home gitlab-runner --shell /bin/bash
# 安装 指定工作目录
gitlab-runner install --user=gitlab-runner --working-directory=/home/gitlab-runner
# 运行
gitlab-runner start
# 注册 runner
gitlab-runner register
####
# 输入 gitlab 的访问地址
http://192.168.x.x:x
# 输入 runner token,把开 http://192.168.x.x:x/admin/runners 页面查看
xxxxxxx
# runner 描述,随便填
构建 runner-001 项目
# runner tag
runner-001
# 输入(选择) shell
shell
####
注册完成后,在页面 http://192.168.x.x:x/admin/runners 中查看
创建测试项目
结构如下
gitlab-ci.yml
# gitlab 持续CI配置
## 用于docker镜像
#image: ruby:2.1
## 用于docker服务
#services:
# - postgres
## 定义在每个job之前运行的命令
before_script:
- echo "----> start"
## 定义在每个job之后运行的命令
after_script:
- echo "----> end"
## 定义构建变量-全局
variables:
MVN_ECHO_1: "开始mvn编译打包"
RUN_DIR_1: /home/gitlab-runner/server
JAR_NAME_1: runner-spring-demo-1.0-SNAPSHOT.jar
## 定义构建阶段, 默认是(build、test、deploy),可以自定义名称 无限量(不能用 gitlab-ci的保留字)
stages:
- build
- deploy
cache: # 缓存-全局,job内有就覆盖全局的
untracked: true # 缓存git中没有被跟踪的文件
# $CI_JOB_NAME 缓存每个job、$CI_COMMIT_REF_NAME 缓存每个分支、
# $CI_JOB_NAME/$CI_COMMIT_REF_NAME 缓存每个job且每个分支、$CI_JOB_STAGE/$CI_COMMIT_REF_NAME 缓存每个分支且每个stage
#key: "$CI_JOB_NAME"
paths: # 缓存 target 目录,mvn会清理。如果是前端项目 就可以缓存 依赖目录 如 node_modules 可以减少打包时间
- target/
# 单独job,名字唯一-拉取项目
build:
#cache: # job级别缓存,内容和全局配置一样
#variables: [] # job级别变量,内容和全局配置一样,[] 是关闭全局变量
#before_script: # job级别 job之前运行的命令,内容和全局配置一样
#before_script: # job级别 job之后运行的命令,内容和全局配置一样
#allow_failure: true # 设置一个job失败的之后并不影响后续的CI组件 默认false
# 定义何时开始job。可以是on_success(前面stages的所有工作成功时才执行 默认),on_failure(前面stages中任意一个jobs失败后执行),
# always(无论前面stages中jobs状态如何都执行)或者manual(手动执行)
#when: on_success
stage: build # 阶段名称 对应,stages
tags: # runner 标签(注册runner时设置的,可在 admin->runner中查看)
- runner_001
script: # 脚本(执行的命令行)
- cd ${CI_PROJECT_DIR} # 拉取项目的根目录
- pwd
- echo ${MVN_ECHO_1}
- mvn clean validate package -Dmaven.test.skip=true
only: # 定义一列git分支,并为其创建job
- master # 拉取分支
#except: # 定义一列git分支,不创建job,和only对应
artifacts: # 把 dist 的内容传递给下一个阶
paths:
- target/
- archive/
deploy:
stage: deploy
tags:
- runner_001
script:
- echo "开始部署>>jar"
- mkdir -p ${RUN_DIR_1} # 创建目录,如果存在 不报错
- /bin/cp -rf ${CI_PROJECT_DIR}/target/${JAR_NAME_1} ${RUN_DIR_1}/${JAR_NAME_1} # 使用系统原生cp强制覆盖 避免交互提示
- /bin/cp -rf ${CI_PROJECT_DIR}/archive/start_server.sh ${RUN_DIR_1}/start_server.sh # 复制 启动脚本
- cd ${RUN_DIR_1} # 跳转到 服务目录
- chmod +x ${RUN_DIR_1}/start_server.sh # 授权运行权限
- sh ${RUN_DIR_1}/start_server.sh restart # 重启
- ps -ef | grep ${JAR_NAME_1} # 查看运行状态
- tail ${RUN_DIR_1}/logs/runner-demo.log # 查看服务运行日志,可以看指定时间日志 info.`date +"%Y-%m-%d"`.0.log
start_server.sh
#!/bin/bash
#jar包路径
APP_PATH="/home/gitlab-runner/server/"
JAR_FILE_PATH="${APP_PATH}runner-spring-demo-1.0-SNAPSHOT.jar"
#环境变量 如果没有可空,dev test prod
# ACTIVE="-Dspring.profiles.active=test"
ACTIVE=""
#web项目检查,是否启动 此路径需要自己创建直接返回 ok,不填写则经过默认时间后认为服务启动
WEB_CHECK_URL=""
#console path 数据输出路径,即项目print的内容以及日志sdpt的内容会输出到此文件 /dev/null 为 消失,其他具体文件为输出到指定文件
CONSOLE_PATH="${APP_PATH}hup.out"
#预计项目多久启动
WEB_START_TIME=10
#jvm配置 值依次如下
#元空间初始大小
#元空间最大
#新生代初始空间
#堆栈初始大小
#堆栈最大空间
#垃圾回收器
#启动模式 使用server
#设置时区
#配置文件路径 -Dspring.config.location=${APP_PATH}etc/
JAVA_OPTS="-XX:MetaspaceSize=256M
-XX:MaxMetaspaceSize=512M
-Xms256M
-Xmx4G
-XX:+UseG1GC
-Xss512k
-server
-Duser.timezone=GMT+08
"
#检查服务是否在运行
is_running(){
pid=$(ps -ef |grep ${JAR_FILE_PATH}|grep -v grep|awk '{print $2}')
#如果不存在返回1,存在返回0
if [ -z "${pid}" ]; then
return 1
else
return 0
fi
}
#运行服务,如果服务运行中则进行提示
start_service(){
echo `date +"%Y-%m-%d %H:%M:%S"` "开始启动服务..."
is_running
if [ $? -eq "0" ]; then
echo `date +"%Y-%m-%d %H:%M:%S"` "${JAR_FILE_PATH} 已经在运行,线程id ${pid}"
else
#启动jar包,并且将输出扔入黑洞(即不保留输出)
nohup java $JAVA_OPTS -jar $ACTIVE $JAR_FILE_PATH > $CONSOLE_PATH 2>&1 &
if [ -n "$WEB_CHECK_URL" ]; then
i=1
start_ok=0
while [ $start_ok -eq 0 ]
do
if [ $WEB_START_TIME -lt $i ]; then
start_ok=1
break
fi
back=$(curl -s ${WEB_CHECK_URL})
if [ "$back" == "ok" ]; then
start_ok=2
break
fi
let i++
sleep 1
done
if [ $start_ok -eq 2 ]; then
echo "服务已启动"
else
echo `date +"%Y-%m-%d %H:%M:%S"` "服务未在规定时间内正确运行(${WEB_START_TIME}),请检查日志"
fi
else
echo `date +"%Y-%m-%d %H:%M:%S"` "等待${WEB_START_TIME}秒..."
sleep $WEB_START_TIME
echo `date +"%Y-%m-%d %H:%M:%S"` "服务已启动"
fi
fi
}
stop_service(){
echo `date +"%Y-%m-%d %H:%M:%S"` "即将关闭程序..."
is_running
if [ $? -eq "1" ]; then
echo "${JAR_FILE_PATH} 未运行,无需停止"
else
echo `date +"%Y-%m-%d %H:%M:%S"` "杀死进程(kill不带-9) ${pid}"
kill $pid
echo `date +"%Y-%m-%d %H:%M:%S"` "程序已关闭"
fi
}
status(){
is_running
if [ $? -eq "0" ]; then
echo "${JAR_FILE_PATH} 运行中,线程id ${pid}"
else
echo "${JAR_FILE_PATH} 未运行"
fi
}
#重启服务
restart_service(){
echo `date +"%Y-%m-%d %H:%M:%S"` "等待1秒,在执行停止..."
sleep 1
stop_service
echo `date +"%Y-%m-%d %H:%M:%S"` "等待10秒,在执行重启..."
sleep 10
start_service
echo `date +"%Y-%m-%d %H:%M:%S"` "服务重启结束..."
}
case "$1" in
"start")
echo `date +"%Y-%m-%d %H:%M:%S"` "开始执行服务启动..."
start_service
;;
"stop")
echo `date +"%Y-%m-%d %H:%M:%S"` "开始执行服务停止..."
stop_service
;;
"restart")
echo `date +"%Y-%m-%d %H:%M:%S"` "开始执行服务重启..."
restart_service
;;
"status")
status
;;
*)
echo "命令为 start|stop|restart|status"
exit 0
;;
esac
pom
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>cn.zy.demo.runner</groupId>
<artifactId>runner-spring-demo</artifactId>
<version>1.0-SNAPSHOT</version>
<!--基于Springboot-->
<parent>
<artifactId>spring-boot-starter-parent</artifactId>
<groupId>org.springframework.boot</groupId>
<version>2.3.1.RELEASE</version>
<relativePath/>
</parent>
<properties>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<skipTests>true</skipTests>
<spring-boot.version>2.3.1.RELEASE</spring-boot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<finalName>${project.artifactId}-${project.version}</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<executions>
<execution>
<id>default-jar</id>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-deploy-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
</build>
</project>
yml
server:
port: 9980
version: 2022072001
servlet:
context-path: /runner-demo/
spring:
jackson:
time-zone: GMT+8
date-format: yyyy-MM-dd HH:mm:ss
logging:
file:
name: ./logs/runner-demo.log
pattern:
console: '%clr(%d{${LOG_DATEFORMAT_PATTERN:-yyyy-MM-dd HH:mm:ss.SSS}}){faint} %clr(${LOG_LEVEL_PATTERN:-%5p}) %clr(${PID:- }){magenta} %clr(---){faint} %clr([%15.15t]){faint} %clr(%-40.40logger{39}){cyan} %clr(:){faint} %m%n${LOG_EXCEPTION_CONVERSION_WORD:-%wEx}'
java
启动类
import cn.zy.demo.runner.util.EnvUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
* 启动类
*
* @author z.y.l
* @version v1.0
* @date 2022/7/20
*/
@SpringBootApplication
public class RunnerSpringDemoApplication {
private static final Logger log = LoggerFactory.getLogger(RunnerSpringDemoApplication.class);
public static void main(String[] args) {
SpringApplication.run(RunnerSpringDemoApplication.class,args);
String port = EnvUtil.getStr("server.port"),
path = EnvUtil.getStr("server.servlet.context-path");
log.info("http://127.0.0.1:{}{}",port,path);
}
}
工具类
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import javax.validation.constraints.NotNull;
/**
* EnvUtil 类说明:
*
* @author z.y.l
* @version v1.0
* @date 2022/7/20
*/
@Component
public class EnvUtil implements EnvironmentAware {
private static Environment env;
@Override
public void setEnvironment(@NotNull Environment environment) {
env = environment;
}
public static String getStr(String key){
return env.getProperty(key);
}
}
测试接口
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Date;
import java.util.Map;
import java.util.TreeMap;
/**
* RootController 类说明:
*
* @author z.y.l
* @version v1.0
* @date 2022/7/20
*/
@RestController
@RequestMapping("/")
public class RootController {
private static final Logger logger = LoggerFactory.getLogger(RootController.class);
private static final Date RUN_TIME = new Date();
@Value("${server.version}")
private String version;
@RequestMapping
public Map<String, Object> index(){
Map<String, Object> map = new TreeMap<>();
map.put("now-time", new Date());
map.put("run-time", RUN_TIME);
map.put("version", version);
logger.info("请求,{}",map);
return map;
}
}
测试CI CD
提交代码,登录 页面 http://192.168.x.x:x/admin/jobs,就可以看到触发的自动job。
可以看到定义的两个阶段job串行执行了,status 状态大概是:等待、执行中、已失败、已跳过等。点击 status状态,就可以看到日志了
大概分为几个运行阶段
mvn编译的日志
第二个job
部署的日志
失败的情况
跳过的没有日志
因为依赖的上个job执行失败,就会跳过,也和yml中的配置有关系
提交项目,自动构建,查看发现失败
构建失败的原因是gitlab的克隆路径不对,因为gitlab是公司同事维护的,并且是在docker部署。联系同事一起解决问题
修复后
问题-部署失败-git版本低问题
报错
Running with gitlab-runner 15.1.0 (76984217)
on 构建 runner001 项目 xymGSfhp
Preparing the "shell" executor
00:00
Using Shell executor...
Preparing environment
00:00
Running on localhost.localdomain...
Getting source from Git repository
00:01
Fetching changes with git depth set to 50...
重新初始化现存的 Git 版本库于 /home/gitlab-runner/builds/xymGSfhp/0/****/runner-spring-demo/.git/
fatal: git fetch-pack: expected shallow list
fatal: The remote end hung up unexpectedly
ERROR: Job failed: exit status 1
解决方式
# git版本太低 要升级
git --version
#git version 1.8.3.1
#安装源
yum install http://opensource.wandisco.com/centos/7/git/x86_64/wandisco-git-release-7-2.noarch.rpm
#安装git
#yum install git
#更新git
yum update git
#...
git --version
#git version 2.31.1
END
这只是后端的部署,前端部署 在cp文件到nginx下面就可以了,命令更少点。这只是个人的操作记录,请根据实际情况自行判断。