文章目录
- 一、核心思路
-
- [1.1 项目的意义](#1.1 项目的意义)
- [1.2 流程梳理](#1.2 流程梳理)
-
- [1.2.1 配置阶段](#1.2.1 配置阶段)
- [1.2.2 Pipeline 打包与分发](#1.2.2 Pipeline 打包与分发)
- [1.2.3 Supervisor 服务管控](#1.2.3 Supervisor 服务管控)
- [1.3 本质](#1.3 本质)
- 二、Jenkins搭建
-
- [2.1 使用Docker搭建Jenkins](#2.1 使用Docker搭建Jenkins)
- [2.2 安装Jenkins必要插件](#2.2 安装Jenkins必要插件)
- 三、配置Jenkins
-
- [3.1 配置全局凭证](#3.1 配置全局凭证)
- [3.2 配置ssh密钥](#3.2 配置ssh密钥)
- 四、Pipeline脚本编写
-
- [4.1 脚本文件](#4.1 脚本文件)
- [4.2 脚本文件解读](#4.2 脚本文件解读)
- 五、Supervisor脚本
-
- [5.1 Supervisor执行脚本](#5.1 Supervisor执行脚本)
- [5.2 supervisor模版文件](#5.2 supervisor模版文件)
- [5.3 脚本与模版解读](#5.3 脚本与模版解读)
- 六、全部流程实践
-
- [6.1 选择服务](#6.1 选择服务)
- [6.2 拉取代码](#6.2 拉取代码)
- [6.3 构建服务jar包](#6.3 构建服务jar包)
- [6.4 sftp下发服务jar包](#6.4 sftp下发服务jar包)
- [6.5 配置启动文件并启动服务](#6.5 配置启动文件并启动服务)
- 七、小总结和感受
在当前数字化转型浪潮中,中小型企业往往面临着技术资源有限与业务快速迭代的双重挑战。对于非K8s部署场景,传统服务发布依赖人工完成代码拉取、编译打包、跨节点传输等一系列离散操作,不仅效率低下,更因人为失误潜藏着服务中断的风险。So,基于 Jenkins Pipeline与Supervisor构建的多节点服务发布方案,就是哥们儿最近研究出来的一种实现架构。嘻嘻:
对于小团队来说,一套 "够用、好用、用得起" 的 CICD 解决方案尤为关键。本方案正是瞄准这一需求,摒弃了大型企业常用的复杂 K8s 生态,转而通过 Jenkins 与 Supervisor 的轻量集成,实现服务发布全流程的半自动化管控。其设计理念贯穿高稳定(通过自动化脚本减少人为干预)、易维护(标准化配置模板与清晰的流程拆解)、低成本(基于现有服务器架构,无需额外硬件投入)三大原则,既解决了传统人工发布的混乱与低效,又避免了过度技术堆砌带来的维护负担,为中小企业提供了一条贴合自身规模与资源的数字化交付路径。

一、核心思路
1.1 项目的意义
针对中小型企业非K8s部署场景,本方案采用Jenkins Pipeline流水线与Supervisor自动化脚本相结合的方式,通过Jenkins的pipeline流水线结合supervisor自动化脚本,实现服务从代码->上线 运行的全流程实现,替代传统服务发布依赖人工完成代码拉取、编译打包、服务器上传、启动配置编写、服务启停等流程操作,以半自动化的形式进行完成服务发布。
其核心价值在于彻底替代传统依赖人工的发布模式,将代码拉取、编译打包、跨节点传输、启动配置生成、服务生命周期管控等离散操作整合为标准化半自动化流程,既降低了人为操作失误风险,又提升了服务发布效率,同时为后续分布式部署架构的演进奠定技术基础。
1.2 流程梳理
一阶段为准备工作,二三阶段为CICD实现自动发布的简要概括。
1.2.1 配置阶段
Jenkins 基础部署:搭建 Jenkins 服务(如容器化部署),安装必备插件(Git、Maven Integration、SSH、Pipeline 等)。
工具配置:配置 Maven 环境(指定路径、私服设置),关联到 Jenkins 全局工具。
通信配置:生成 Jenkins SSH 密钥对,配置目标主机免密登录,在 Jenkins 中登记主机 SSH 信息。
凭证管理:添加代码仓库(如 Git)访问凭证(用户名 + 令牌),供流水线拉取代码使用。
1.2.2 Pipeline 打包与分发
拉取代码:根据参数选择的服务和分支,从代码仓库拉取最新代码。
编译打包:通过 Maven 按配置编译代码(跳过测试),筛选需部署的 Jar 包(排除非服务类 Jar)。
上传分发:根据选择的目标主机,通过 SSH 将筛选后的 Jar 包批量上传到对应主机的指定目录。
1.2.3 Supervisor 服务管控
状态判断:根据上传的 Jar 包,判断服务是新增(无配置文件)还是更新(已有配置文件)。
新增服务处理:基于模板生成 Supervisor 配置文件,校验语法后加载配置并启动服务。
更新服务处理:通过 Supervisor 直接重启对应服务(使用新 Jar 包),完成更新上线。
1.3 本质
本质上是建立了一条从代码提交到服务上线的"软件生产流水线"。
- 对开发人员而言:它简化了发布过程,只需选择功能点和目标环境,即可一键完成部署,将重心更多地放在代码开发上。
- 对运维人员而言 :它将以人工操作为主、充满不确定性的发布日 ,变成了一个标准化、可预测的自动化流程,极大地降低了因操作失误导致的服务中断风险。
- 对业务而言 :它显著提升了软件交付的速度和可靠性 ,使得新功能、修复能更快、更稳地抵达用户,提升了业务的敏捷性。简而言之,这个流程的意义在于 "把混乱的、依赖人的发布,变成了有序的、由工具驱动的流水线"。
二、Jenkins搭建
2.1 使用Docker搭建Jenkins
拉取镜像并完成Jenkins的搭建工作:
bash
# 国内镜像源
docker pull swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/jenkins/jenkins:lts-jdk17
docker tag swr.cn-north-4.myhuaweicloud.com/ddn-k8s/docker.io/jenkins/jenkins:lts-jdk17 docker.io/jenkins/jenkins:lts-jdk17
mkdir /data/jenkins_home
chown -R 1000:1000 /data/jenkins_home
# 启动Jenkins
docker run -itd --restart=always \
-p 15105:8080 \
-p 50000:50000 \
-v /data/jenkins_home:/var/jenkins_home \
--name jenkins \
docker.io/jenkins/jenkins:lts-jdk17
(过于简单的流程直接跳过了,比如查/var的那个文件啥的哈,搭建过程中都有提示的)
2.2 安装Jenkins必要插件
添加相关插件:
| 插件名称 | 作用 | 必装与否 |
|---|---|---|
| Git Plugin | 拉取 Git 仓库代码 | 必装 |
| Maven Integration Plugin | 集成 Maven,自动打包 | 必装 |
| Publish Over SSH | SSH 传文件、远程执行命令 | 必装 |
| Pipeline | 流水线支持 | 推荐 |
三、配置Jenkins
3.1 配置全局凭证

访问 Jenkins → 点击左侧「凭证」→ 「系统」→ 「全局凭证」→ 「添加凭证」;
凭证类型选择:「用户名和密码」;
填写信息:
- 用户名:你的云效 Git 账号(通常是手机号 / 邮箱);
- 密码:你的云效 Git 密码(或云效个人访问令牌);
- ID:自定义(如
yunxiao,后续 Pipeline 中会用到); - 描述:可选(如 "云效 Git 拉取凭证");
点击「确定」,凭证添加完成。
3.2 配置ssh密钥
为了实现sftp进行jar包的分发,需要配置Jenkins的ssh密钥,并下发到需要运行服务的主机的ssh文件里。
bash
root@master:~# docker exec -it jenkins /bin/bash
jenkins@f4c93a31e108:/$ ssh-keygen -t rsa
# 一路回车
Generating public/private rsa key pair.
Enter file in which to save the key (/var/jenkins_home/.ssh/id_rsa):
Created directory '/var/jenkins_home/.ssh'.
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /var/jenkins_home/.ssh/id_rsa
Your public key has been saved in /var/jenkins_home/.ssh/id_rsa.pub
The key fingerprint is:
SHA256:TB0NN3lWQYeeXi8LrJqv1zvVQLtlL8nA1FGMNdok/po jenkins@f4c93a31e108
The key's randomart image is:
+---[RSA 3072]----+
| ooo+o&*|
| . o=.% +|
| . .o B + |
| o o * +|
| S . + Xo|
| o @ +|
| o E + |
| .o o . |
| +=. .o |
+----[SHA256]-----+
jenkins@f4c93a31e108:/$ exit
exit
root@master:~# cat /data/jenkins_home/.ssh/id_rsa.pub
ssh-rsa xxxxxxxxxxxxxxxxxxxxxxx
root@master:~# echo "ssh-rsa xxxxxxxxxxxxxxxxxxxxxxxxx" >> /root/.ssh/authorized_keys
配置ssh,在Jenkins上配置需要运行服务的主机:(可以使用test测试,查看是否生效)

配置maven
bash
# 将maven包解压放在jenkins_home目录下
jenkins@f4c93a31e108:/$ cat > /var/jenkins_home/.bashrc << EOF
export MAVEN_HOME=/var/jenkins_home/apache-maven
export PATH=\$PATH:\$MAVEN_HOME/bin
EOF
jenkins@f4c93a31e108:/$ cat /var/jenkins_home/.bashrc
export MAVEN_HOME=/var/jenkins_home/apache-maven
export PATH=$PATH:$MAVEN_HOME/bin
jenkins@f4c93a31e108:/$ source /var/jenkins_home/.bashrc
jenkins@f4c93a31e108:/$ mvn -v
Apache Maven 3.9.11 (3e54c93a704957b63ee3494413a2b544fd3d825b)
Maven home: /var/jenkins_home/apache-maven
Java version: 17.0.14, vendor: Eclipse Adoptium, runtime: /opt/java/openjdk
Default locale: en, platform encoding: UTF-8
OS name: "linux", version: "6.8.0-88-generic", arch: "amd64", family: "unix"
jenkins@f4c93a31e108:/$
jenkins@f4c93a31e108:/$ exit
exit

基本环境配置完成。
四、Pipeline脚本编写
4.1 脚本文件
yaml
pipeline {
agent any
tools {
maven 'maven'
}
parameters {
choice(
name: 'SERVICE_NAME',
choices: [
'全量打包',
'service1',
'service2',
'service3',
'service4',
'service5'
],
description: '选择要打包的服务(只需填服务名,不用加 .jar 后缀)'
)
choice(
name: 'TARGET_HOST',
choices: [
'全部主机',
'uat',
'master'
],
description: '选择要部署的目标主机(选「全部主机」则批量部署所有配置的主机)'
)
}
environment {
// git仓库地址
GIT_URL = 'https://service.git'
// 全局凭证名(git仓库密钥存放的名)
GIT_CRED = 'yunxiao'
// 代码分支
BRANCH = 'master'
// 服务jar包上传目录
REMOTE_DIR = '/data/projects/'
// 日志目录
LOG_DIR = '/data/logs'
EXCLUDE_PATTERN = '*/common/*|*-api.jar'
DEPLOYED_HOSTS_FILE = "${WORKSPACE}/deployed_hosts.txt"
DEPLOYED_IPS_FILE = "${WORKSPACE}/deployed_ips.txt"
// supervisor 执行脚本路径
SERVER_SCRIPT_PATH = '/etc/supervisor/supervisor_deploy.sh'
// supervisor 模版文件路径
SERVER_TEMPLATE_PATH = '/etc/supervisor/supervisor_template.conf'
// supervisor 启动文件目录
SUPERVISOR_CONF_DIR = '/etc/supervisor/conf.d'
}
stages {
stage('拉取Git代码') {
steps {
git url: env.GIT_URL, credentialsId: env.GIT_CRED, branch: env.BRANCH
sh "echo '' > ${DEPLOYED_HOSTS_FILE} && echo '' > ${DEPLOYED_IPS_FILE}"
}
}
stage('maven打包及智能筛选') {
steps {
script {
def targetService = params.SERVICE_NAME
echo "==== 执行${targetService == '全量打包' ? '全量' : '单个'}服务打包:${targetService} ===="
sh "mvn package -Dmaven.test.skip=true -Djdk.version=17"
// 核心修复:用单引号包裹Shell命令,变量通过字符串拼接插入,避免解析冲突
def filterCmd
if (targetService == '全量打包') {
filterCmd = 'find ' + WORKSPACE + ' -path \'*/target/*.jar\' ! -path \'*common/*\' ! -name \'*-api.jar\' | sort -u > ./deploy_jars.txt'
} else {
// 正则表达式部分用单引号隔离,变量单独拼接
filterCmd = 'find ' + WORKSPACE + ' -path \'*/target/*.jar\' ! -path \'*common/*\' ! -name \'*-api.jar\' | grep -E \'' + targetService + '(-biz)?\\.jar$\' | sort -u > ./deploy_jars.txt'
}
sh filterCmd
sh '''
if [ ! -s ./deploy_jars.txt ]; then
echo "ERROR:未找到对应Jar包!"
find ${WORKSPACE} -path '*/target/*.jar' ! -path '*common/*' ! -name '*-api.jar'
exit 1
fi
echo "筛选成功!共找到 $(wc -l < ./deploy_jars.txt) 个Jar包"
cat ./deploy_jars.txt
'''
}
}
}
stage('多主机SFTP批量上传') {
steps {
script {
def HOST_CONFIG = [
'master': [SSH_USER: 'root', REMOTE_HOST: '192.168.119.7', SSH_KEY: '/var/jenkins_home/.ssh/id_rsa'],
'uat': [SSH_USER: 'root', REMOTE_HOST: '192.168.119.3', SSH_KEY: '/var/jenkins_home/.ssh/id_rsa']
]
def targetHosts = params.TARGET_HOST == '全部主机' ? HOST_CONFIG.keySet().collect() : [params.TARGET_HOST]
for (def host : targetHosts) {
def hostInfo = HOST_CONFIG[host]
def sshUser = hostInfo.SSH_USER
def remoteHost = hostInfo.REMOTE_HOST
def sshKey = hostInfo.SSH_KEY
def remoteDir = env.REMOTE_DIR
echo "正在上传到主机:${host}(IP:${remoteHost})"
sh """
[ -f "${sshKey}" ] || { echo "ERROR:私钥文件不存在!"; exit 1; }
chmod 600 "${sshKey}"
while read -r jarPath; do
sftp -i "${sshKey}" -o StrictHostKeyChecking=no "${sshUser}@${remoteHost}" << EOF
put "\${jarPath}" "${remoteDir}/"
bye
EOF
done < ./deploy_jars.txt
"""
sh "echo -n '${host}, ' >> ${DEPLOYED_HOSTS_FILE}"
sh "echo -n '${remoteHost}, ' >> ${DEPLOYED_IPS_FILE}"
echo "主机${host}上传完成!"
}
}
}
}
stage('Supervisor自动发布服务') {
steps {
script {
def HOST_CONFIG = [
// 密钥的存放路径,为Jenkins路径
'master': [SSH_USER: 'root', REMOTE_HOST: '192.168.119.7', SSH_KEY: '/var/jenkins_home/.ssh/id_rsa'],
'uat': [SSH_USER: 'root', REMOTE_HOST: '192.168.119.3', SSH_KEY: '/var/jenkins_home/.ssh/id_rsa']
]
def targetHosts = params.TARGET_HOST == '全部主机' ? HOST_CONFIG.keySet().collect() : [params.TARGET_HOST]
def deployTimestamp = sh(script: "date +%s", returnStdout: true).trim()
for (def host : targetHosts) {
def hostInfo = HOST_CONFIG[host]
def sshUser = hostInfo.SSH_USER
def remoteHost = hostInfo.REMOTE_HOST
def sshKey = hostInfo.SSH_KEY
def remoteDir = env.REMOTE_DIR
def logDir = env.LOG_DIR
def serverScript = env.SERVER_SCRIPT_PATH
def serverTemplate = env.SERVER_TEMPLATE_PATH
def supervisorConfDir = env.SUPERVISOR_CONF_DIR
echo "正在${host}执行服务发布"
// 上传Jar包列表
sh """
sftp -i "${sshKey}" -o StrictHostKeyChecking=no "${sshUser}@${remoteHost}" << EOF
put "${WORKSPACE}/deploy_jars.txt" "${remoteDir}/"
bye
EOF
"""
sh "ssh -i '${sshKey}' -o StrictHostKeyChecking=no '${sshUser}@${remoteHost}' 'mkdir -p ${logDir} && chmod 777 ${logDir} && ${serverScript} --deploy-timestamp ${deployTimestamp} --jar-list-path ${remoteDir}/deploy_jars.txt --remote-dir ${remoteDir} --log-dir ${logDir} --supervisor-conf-dir ${supervisorConfDir} --supervisor-template ${serverTemplate}'"
}
}
}
}
}
post {
success {
echo "完整CICD流程执行成功"
echo "操作服务:${params.SERVICE_NAME}"
echo "部署主机:${params.TARGET_HOST}"
}
failure {
echo "CICD流程执行失败,请查看日志排查!"
}
}
}
4.2 脚本文件解读
第一部分:选项部分
这个部分可以自定义service的名字,也就是说可以自定义我们要上传哪个服务,也可以选择要部署的主机,脚本保存之后会有Build With Partments模块,在里面就可以选择。
bash
parameters {
choice(
name: 'SERVICE_NAME',
choices: [
'全量打包',
'service1',
'service2',
'service3',
'service4',
'service5'
],
description: '选择要打包的服务(只需填服务名,不用加 .jar 后缀),后续新增服务在此添加即可'
)
choice(
name: 'TARGET_HOST',
choices: [
'全部主机',
'uat',
'master'
],
description: '选择要部署的目标主机(选「全部主机」则批量部署所有配置的主机)'
)
}

SERVICE_NAME(服务选择)
- 功能定位:此选项定义了CI/CD流程的构建范围,决定了哪些微服务会被打包和部署
- 选项说明:
- 全量打包:构建和部署所有可用服务,适用于大规模发布或环境初始化。
- 单个服务:针对特定服务进行精准构建,实现快速迭代和热修复
- 使用场景:开发阶段可选择单个服务进行快速测试验证,生产发布可选择全量打包确保服务版本一致性
TARGET_HOST(目标环境)
- 功能定位:控制代码部署的目标环境,实现分级发布策略
- 选项说明:
- 全部主机:批量部署到所有配置环境,提高发布效率
- 特定环境:定向部署到指定环境,支持灰度发布策略
第二部分:核心配置部分,根据自己环境进行修改
bash
environment {
// git仓库地址
GIT_URL = 'https://service.git'
// 全局凭证名(git仓库密钥存放的名)
GIT_CRED = 'yunxiao'
// 代码分支
BRANCH = 'master'
// 服务jar包上传目录
REMOTE_DIR = '/data/projects/'
// 日志目录
LOG_DIR = '/data/logs'
// 需要排除的jar包
EXCLUDE_PATTERN = '*/common/*|*-api.jar'
// 部署主机,自动生成
DEPLOYED_HOSTS_FILE = "${WORKSPACE}/deployed_hosts.txt"
// 部署主机,自动生成
DEPLOYED_IPS_FILE = "${WORKSPACE}/deployed_ips.txt"
// supervisor 执行脚本路径
SERVER_SCRIPT_PATH = '/etc/supervisor/supervisor_deploy.sh'
// supervisor 模版文件路径
SERVER_TEMPLATE_PATH = '/etc/supervisor/supervisor_template.conf'
// supervisor 启动文件目录
SUPERVISOR_CONF_DIR = '/etc/supervisor/conf.d'
}
根据自己的环境进行配置,supervisor脚本文件和模版文件放在哪里无所谓,重点是。SUPERVISOR_CONF_DIR = '/etc/supervisor/conf.d'不允许更改,除非你的supervisor的配置文件不是默认配置文件。
完整阶段视图:

五、Supervisor脚本
5.1 Supervisor执行脚本
bash
#!/bin/bash
set -euo pipefail
# 解析传入参数(新增log-dir,适配日志目录)
while [[ $# -gt 0 ]]; do
case $1 in
--deploy-timestamp) DEPLOY_TIMESTAMP="$2"; shift 2 ;;
--jar-list-path) JAR_LIST_PATH="$2"; shift 2 ;;
--remote-dir) REMOTE_DIR="$2"; shift 2 ;;
--log-dir) LOG_DIR="$2"; shift 2 ;;
--supervisor-conf-dir) SUPERVISOR_CONF_DIR="$2"; shift 2 ;;
--supervisor-template) SUPERVISOR_TEMPLATE="$2"; shift 2 ;;
*) echo "未知参数:$1"; exit 1 ;;
esac
done
# 校验依赖和参数
check_dependency() {
local cmd=$1
if ! command -v ${cmd} &> /dev/null; then
echo "ERROR:未找到命令 ${cmd},请安装后重试!"
exit 1
fi
}
check_dependency "supervisorctl"
check_dependency "java"
[[ -z ${DEPLOY_TIMESTAMP:-} ]] && { echo "ERROR:缺少部署时间戳"; exit 1; }
[[ -f ${JAR_LIST_PATH} ]] || { echo "ERROR:Jar包列表文件不存在 ${JAR_LIST_PATH}"; exit 1; }
[[ -d ${SUPERVISOR_CONF_DIR} ]] || { echo "ERROR:Supervisor配置目录不存在"; exit 1; }
[[ -f ${SUPERVISOR_TEMPLATE} ]] || { echo "ERROR:Supervisor模板文件不存在"; exit 1; }
# 提取服务名 即Jar包名→服务名
extract_service_name() {
local jar_path=$1
local jar_filename=$(basename "${jar_path}")
echo "${jar_filename%.jar}"
}
# 判断是否是本次部署的新包(时间戳±300秒误差)
is_latest_jar() {
local jar_path=$1
local jar_mtime=$(stat -c %Y "${jar_path}")
if [[ ${jar_mtime} -ge $((DEPLOY_TIMESTAMP - 300)) && ${jar_mtime} -le $((DEPLOY_TIMESTAMP + 300)) ]]; then
return 0
else
return 1
fi
}
# 循环处理每个Jar包
echo "开始处理本次部署的Jar包(共 $(wc -l < ${JAR_LIST_PATH}) 个)"
while read -r jar_path; do
[[ -z ${jar_path} ]] && continue
# 提取服务名
service_name=$(extract_service_name "${jar_path}")
jar_filename=$(basename "${jar_path}") # 仅Jar包文件名(如easewise-register.jar)
echo -e "\n========================================"
echo "当前处理:服务名=${service_name} | Jar包=${jar_filename}"
# 校验Jar包存在且是本次新包
[[ -f "${REMOTE_DIR}/${jar_filename}" ]] || { echo "WARNING:Jar包不存在 ${REMOTE_DIR}/${jar_filename},跳过"; continue; }
if ! is_latest_jar "${REMOTE_DIR}/${jar_filename}"; then
local jar_time=$(date -d @$(stat -c %Y "${REMOTE_DIR}/${jar_filename}") +'%Y-%m-%d %H:%M:%S')
echo "WARNING:Jar包不是本次部署的新包(修改时间:${jar_time}),跳过";
continue;
fi
# 核心逻辑:新增/更新服务
supervisor_conf="${SUPERVISOR_CONF_DIR}/${service_name}.conf"
if [[ -f ${supervisor_conf} ]]; then
# 场景1:更新服务(配置已存在)
echo "识别为【更新服务】,配置文件:${supervisor_conf}"
echo "正在重启服务..."
supervisorctl stop "${service_name}" || true # 允许服务未启动
supervisorctl start "${service_name}"
if [[ $? -eq 0 ]]; then
echo "服务 ${service_name} 重启成功!"
else
echo "服务 ${service_name} 重启失败!"
echo "查看日志:tail -f ${LOG_DIR}/${service_name}.log"
exit 1
fi
else
# 场景2:新增服务(配置不存在)
echo "识别为【新增服务】,生成配置文件:${supervisor_conf}"
# 从模板生成配置
sed \
-e "s/{{SERVICE_NAME}}/${service_name}/g" \
-e "s/{{REMOTE_DIR}}/${REMOTE_DIR//\//\\/}/g" \
-e "s/{{JAR_FILENAME}}/${jar_filename}/g" \
-e "s/{{LOG_DIR}}/${LOG_DIR//\//\\/}/g" \
"${SUPERVISOR_TEMPLATE}" > "${supervisor_conf}"
# 校验配置语法
echo "正在校验配置文件..."
if ! supervisorctl reread > /dev/null 2>&1; then
echo "配置文件语法错误!"
rm -f "${supervisor_conf}"
exit 1
fi
# 加载配置并启动服务
echo "正在启动服务..."
supervisorctl update "${service_name}"
supervisorctl start "${service_name}"
if [[ $? -eq 0 ]]; then
echo "服务 ${service_name} 新增并启动成功!"
else
echo "服务 ${service_name} 启动失败!"
echo "查看日志:tail -f ${LOG_DIR}/${service_name}.log"
rm -f "${supervisor_conf}"
exit 1
fi
fi
done < "${JAR_LIST_PATH}"
echo -e "\n========================================"
echo "所有服务处理完成!"
5.2 supervisor模版文件
yaml
[program:{{SERVICE_NAME}}]
directory={{REMOTE_DIR}}
command=java -Dfile.encoding=utf-8 -jar {{JAR_FILENAME}}
autostart=true
autorestart=true
startsecs=5
startretries=3
user=root
redirect_stderr=false
stdout_logfile={{LOG_DIR}}/{{SERVICE_NAME}}.log
stdout_logfile_maxbytes=20MB
stdout_logfile_backups=3
stderr_logfile={{LOG_DIR}}/{{SERVICE_NAME}}.err
stderr_logfile_maxbytes=20MB
stderr_logfile_backups=3
5.3 脚本与模版解读
这个脚本的核心设计思想是实现服务部署的"智能自动化",它将传统依赖人工判断和操作的服务上线流程,抽象为一个能自主决策的标准化过程。其关键在于通过判断Supervisor配置文件是否存在,来自动识别当前服务是"新增"还是"更新",从而触发不同的标准化处理流程,最终目标是将繁琐的人工发布转变为一条可靠、高效的自动化流水线。
在实现上,脚本作为"大脑"负责逻辑判断和命令执行:它解析参数、检查环境、遍历Jar包列表,并根据服务状态决定是调用模板生成新配置并启动,还是直接重启现有服务。而模板文件则作为"蓝图",提供了标准化的服务运行配置;脚本通过填充模板中的变量(如服务名、路径)来动态生成每个服务专属的配置文件,从而确保所有服务都能以统一、规范的方式被管理和监控。
六、全部流程实践
6.1 选择服务
首先,我们要去选择要构建的服务和要部署服务的主机:

6.2 拉取代码
git开始从代码仓库拉取对应分支的代码:

6.3 构建服务jar包
拉取完成后,maven会进行自动构建服务jar包:

6.4 sftp下发服务jar包
代码构建完成后,sftp会将服务jar包上传至对应服务器的服务启动目录下

6.5 配置启动文件并启动服务
服务的jar包上传完成后,supervisor_deploy.sh脚本会被执行,相关参数Jenkins会完成补全,脚本会检查启动配置文件目录下是否存在我们本次构建的服务名对应的文件,如果有,直接复用,重启服务完成更新,如果没有,则会根据我们的模版文件和相关服务名等参数完成启动文件的创建,并且启动服务,全部流程结束。服务完成了更新/上线/启动。


七、小总结和感受
其实不想写这个的,但是还是写了。
总结就是,道阻且长。感觉运维这条路就是会的越多会的越少的一门,门门了解,门门不精,只能通过不断的实践和去实现创新想法,才能去一点点的完善,有的时候就是做的之前想的太多了,我啊,就是做之前想的想的太多,导致感觉不管做什么都有点唯唯诺诺哈哈哈,但是想的太多不去做,还是白费的嘛你说是不,所以我现在在改变,做之前不想太多,先做,再想,遇到啥解决啥,行吧。
其实这个,也是一次尝试,本来想玩Jenkins+K8s的形式,但是深刻考虑了一下整个项目实施起来的难度,服务从裸金属服务器部署的形式转变为容器化实施,向DevOps转型,这虽然说是迟早的事情,但是我还是觉得步子不能迈得太大,步子迈大了,咔,扯到蛋就有意思了,所以还是一步一个脚印往前走,况且等我们公司服务的规模变得越来越大的时候,有几十台服务器的时候,我们再玩K8s也不迟,我也会一直去学,所以这个也算是在中小型公司CICD的一个选择实施的一种思路吧。好啦,就这样,想交流的可以后台私信聊哦。
拜拜。