Jenkins 自动化部署教程
1. Jenkins 是什么?
Jenkins 是一个开源的、提供友好操作界面的持续集成 (CI) 工具,广泛用于项目开发,具有自动化构建、测试和部署等功能。Jenkins 用 Java 语言编写,可在 Tomcat 等流行的 servlet 容器中运行,也可独立运行。通常与版本管理工具 (SCM)、构建工具结合使用。
Jenkins 自动化部署涉及环境搭建、版本控制集成、项目构建及服务发布全流程。首先安装 JDK 与 Jenkins 服务,配置全局工具及凭证管理。接着集成 Git 仓库与 Maven 构建工具,实现代码拉取与编译打包。通过 SSH 免密登录配置,编写 Shell 脚本实现应用服务的远程拷贝、停止旧进程、启动新服务及日志监控,最终达成持续集成与自动化部署目标。

Jenkins 是一个开源的、提供友好操作界面的持续集成 (CI) 工具,广泛用于项目开发,具有自动化构建、测试和部署等功能。Jenkins 用 Java 语言编写,可在 Tomcat 等流行的 servlet 容器中运行,也可独立运行。通常与版本管理工具 (SCM)、构建工具结合使用。
因为开发部门同时维护多个版本,多个版本的发布,测试需要大量人力,所以要有一个专业的持续集成工具来管理持续重复的工作。
个人理解,说白了就是把代码测试、打包、发布等工作交给一些工具来自动完成。这样可以提高效率,减少失误,开发人员只需要关心开发和提交代码到 Git 就可以了。
下载 JDK 压缩包,并上传至 Linux 某个目录下解压。
①. 配置 jdk 的环境变量
#进入/etc/profile配置文件 vim /etc/profile
②. 将以下代码填入到 profile 文件内底
export JAVA_HOME=/usr/wubin/jdk11 export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar export PATH=$JAVA_HOME/bin:$PATH
③. 使配置生效
source /etc/profile
④. 检测 java 环境信息
javac
输入 javac 后出现以下信息则安装 jdk 成功!

**ps:**jenkins 不同版本对 jdk 版本有不同要求,如果系统里面是 jdk8,但是想在不升级 jdk 版本情况下使用更高版本的 jenkins(比如要求 jdk17),也可直接在 jenkins 启动时指定 jdk 路径,就不用修改系统里配置的 jdk 版本
wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war
nohup java -jar /usr/wubin/jenkins.war --httpPort=8777 --httpsPort=8778 > /usr/wubin/jenkins.log 2>&1 &

http://你的 ip:8777

点击安装推荐的插件



jenkins-》全局工具配置-》JDK-》新增 JDK



为了 Jenkins 能够拉取代码,需要安装 Git 环境和 jenkins 对应的 Git 插件
(1) CentOS 7 上安装 Git 环境
# 安装 $ yum install git -y # 查看版本 $ git --version
(2) Jenkins 安装 Git 插件

(3) Jenkins 配置 Git 环境
此处无需在 jenkins 中配置 Git 环境,采用默认生成的即可

(4) Gitee 上任意建一个仓库


(5) 测试凭据是否能够使用
jenkins-》新建任务-》自由风格项目






进入 jenkins 的工作空间查看文件是否拉取下来,所有拉取的文件都会存放在 jenkins 工作空间中

到这一步用户名和密码方式的凭证已经打通 Git
凭据就是用来存储需要密文保护的数据库密码、Gitee 密码信息、Docker 私有仓库密码等,以便 Jenkins 可以和这些第三方的应用进行交互。

该插件默认在一开始就会被安装,安装后在 jenkins-》系统管理-》安全 栏目会出现 Manage Credentials 选项,若没有需要安装插件并重启。










在 jenkins 上发布 Java 项目时需要使用 Maven 来进行构建打包(Gradle 项目则需要安装配置 Gradle)
# 找一个目录存放 maven cd /usr/wubin/ # 从阿里云上下载 maven 安装包 wget https://mirrors.aliyun.com/apache/maven/maven-3/3.6.3/binaries/apache-maven-3.6.3-bin.tar.gz # 解压 tar -zxvf apache-maven-3.6.3-bin.tar.gz # 当前 maven 的安装目录为:
/usr/wubin/ apache-maven-3.6.3
vi /etc/profile 在最后面 JDK 配置上作出一些更改 export MAVEN_HOME= /usr/wubin/ apache-maven-3.6.3
export PATH=$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH
source /etc/profile mvn -version


/data/software 目录下新建一个 repository 文件夹,用来作为 maven 的仓库$ cd /usr/wubin $ mkdir repository
root 账户修改 Maven 的 settings.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 http://maven.apache.org/xsd/settings-1.0.0.xsd">
<!--本地仓库-->
<localRepository>/data/software/repository</localRepository>
<mirrors>
<!--阿里云镜像-->
<mirror>
<id>aliyun-maven</id>
<mirrorOf>central</mirrorOf>
<name>aliyun maven mirror</name>
<url>http://maven.aliyun.com/nexus/content/groups/public/</url>
</mirror>
</mirrors>
... ...
</settings>
接下来的步骤是将 java 项目传到 Gitee 上供 jenkins 拉取打包,如果运维同学不懂 Java 代码,可以直接将我的 Git 项目 fork 或采用 gitlab 等其他方式进行拉取。
新建 Maven 项目

在码云上建一个同名的 git 项目

使用 Git 上传到码云
使用 git bash 命令将项目初始化,无论是传到 Gitee、GitHub、GitLab、Codeup 步骤一样,如果对 Git 的安装部署不熟悉
# 进入到本地的项目文件夹 $ cd existing_folder # 初始化仓库 $ git init # 添加文件至工作区并提交 $ git add . $ git commit -m "first commit" # 关联 Gitee 远程仓库 $ git remote add origin https://gitee.com/nobug8/it235-jenkins-jar.git # 将本地仓库推送到远程仓库的 master 分支,此处会弹出用户名密码交互 $ git push -u origin master ## 如果 push 报错可以先拉取一下,会有新的 gitee 生成的文件拉下来,然后重新添加提交并 push $ git pull origin master --allow-unrelated-histories $ git add . $ git commit -m "拉取下来合并后再次提交" $ git push -u origin master

①. 新建任务

②. 编写 Maven 编译命令

③. 构建并查看控制台日志
保存后,点击立即构建,然后进入日志控制台查看日志

从日志可以看到代码已经在拉取了,而且走的事阿里云仓库,第一次拉取过程会比较长。
通过查看 /data/software/repository 可以看到有存放拉取的 jar 包,通过这 2 个证据可以证明 settings.xml 文件配置成功且有效
ps:如果使用 git 账号密码拉取代码即使权限都已经给够了还是出现 403 等等一些问题,可以考虑使用 ApiToken 方式拉去代码,在 git/gitlab 创建 apiToken,在凭证管理地方加上凭证

构建成功后查看 jenkins 的 workspace 目录下的 jar 包


到此 Maven 集成完毕
配置 Post Steps,选中执行 shell


由于 jenkins 构建消耗内存极大,一般 jenkins 是一台单独的工具机器,Java 项目一般在其他的机器上,这里我重新安装一台虚拟机
应用服务器信息
免密登录主要是方便 jenkins 服务器 192.168.223.128 的 root 用户—》应用服务器 192.168.223.129 的 root 用户上的 jar 包拷贝,部署本就是 jar 包拷贝的过程
在 192.168.223.128 机器上使用 root 用户生成秘钥注意此处是 root 用户
$ ssh-keygen -t rsa # 3 次回车
运行后会在当前用户的根目录生成一个.ssh 文件夹

ssh 文件夹中的文件描述
id_rsa : 生成的私钥文件id_rsa.pub:生成的公钥文件接下来需要将公钥导入到认证文件中
$ cat ~/.ssh/id_rsa.pub >> ~/.ssh/authorized_keys
如果希望 ssh 公钥生效需满足至少下面两个条件:
.ssh 目录的权限必须是 700.ssh/authorized_keys 文件权限必须是 600给对应文件授权
$ chmod 700 ~/.ssh $ chmod 600 ~/.ssh/authorized_keys
将 authorized_keys 文件拷贝到另一台应用服务器的 root 用户.ssh 目录下
# 在应用服务器(192.168.223.129)上用 root 用户创建/root/.ssh 文件夹 mkdir -p /root/.ssh # 在 jenkins 服务器(192.168.223.128)上将 pub 公钥文件拷贝到应用服务器的.ssh 目录下 scp -p ~/.ssh/id_rsa.pub [email protected]:/root/.ssh/authorized_keys
在 jenkins192.168.223.128 服务器上进行免密连接测试
# 在 jenkins 服务器的/root/目录下创建 filetest 文件,并拷贝到应用服务器 $ cd ~/ $ touch filetest $ scp -p filetest [email protected]:/root/filetest # 进入到应用服务器(192.168.223.129),检查/root 目录下是否出现 filetest # 在 jenkins 服务器上使用 ssh 进行免密连接测试,成功后会出现 Last Login 的提示 $ ssh [email protected] Last login: Sun Sep 20 21:53:03 2020 $ exit
到此免密登录和拷贝实现成功,为接下来 jar 包部署提供了快捷的帮助

注意下面代码第 6 行代码 server_ips="需要部署到的机器 ip"
#!/bin/bash echo "部署的目录和项目名称" DIR="/data/app" projectName="my-boot" echo "待部署的应用服务器,可多台" server_ips="192.168.223.139" for server_ip in ${server_ips[@]} do echo "ssh 连接进行备份操作" ssh -Tq -oStrictHostKeyChecking=no root@${server_ip} <<EOF mkdir -p $DIR/backup/${projectName} mkdir -p $DIR/${projectName} if [ -f "$DIR/${projectName}/${projectName}.jar" ];then mv $DIR/${projectName}/${projectName}.jar $DIR/backup/${projectName}/${projectName}-`date "+%Y%m%d_%H%M%S"`.jar fi EOF echo "拷贝 jar 包到目标服务器的 tmp 目录" scp -q -oStrictHostKeyChecking=no ${WORKSPACE}/target/*.jar root@${server_ip}:/tmp/${projectName}.jar echo "ssh 远程连接进行发布操作" ssh -q -oStrictHostKeyChecking=no root@${server_ip} <<EOF mv /tmp/${projectName}.jar $DIR/${projectName}/${projectName}.jar EOF done echo "success"
#!/bin/bash # 配置变量 LOG_FILE="/home/xxx/.jenkins/workspace/whale-mgnt-service/startup.log" JAR_PATH="/home/xxx/.jenkins/workspace/whale-mgnt-service/whale-mgnt-service/target/whale-mgnt-service.jar" APP_PORT=8081 MAX_ATTEMPTS=60 INTERVAL=5 # 清空之前的日志 echo "" > $LOG_FILE # 使用多种方式查找和终止进程 kill_process_by_port() { local port=$1 echo "Checking for processes using port $port..." # 方法 1: 使用 netstat 查找 local pid=$(sudo netstat -tlnp | grep ":${port}" | awk '{print $7}' | cut -d'/' -f1) # 方法 2: 如果方法 1 失败,使用另一种方式 if [ -z "$pid" ]; then pid=$(sudo ss -tlnp | grep ":${port}" | awk '{print $6}' | cut -d',' -f2 | cut -d'=' -f2) fi # 方法 3: 如果还是失败,尝试使用 ps 和 grep if [ -z "$pid" ]; then pid=$(ps aux | grep "whale-mgnt-service" | grep -v grep | awk '{print $2}') fi if [ ! -z "$pid" ]; then echo "Found process(es) with PID: $pid" for single_pid in $pid; do echo "Stopping process $single_pid" sudo kill -15 $single_pid 2>/dev/null || true sleep 3 if ps -p $single_pid > /dev/null 2>&1; then echo "Force killing process $single_pid" sudo kill -9 $single_pid 2>/dev/null || true sleep 2 fi done else echo "No process ID found, trying alternative methods..." # 尝试直接通过应用名称终止 sudo pkill -f "whale-service" || true fi # 等待端口释放 sleep 5 # 检查端口是否已释放 if sudo netstat -tlnp | grep ":${port}" > /dev/null; then echo "Warning: Port $port is still in use" return 1 fi echo "Port $port is now available" return 0 } # 确保端口可用 echo "Ensuring port $APP_PORT is available..." for i in {1..3}; do echo "Attempt $i to free port $APP_PORT" kill_process_by_port $APP_PORT if ! sudo netstat -tlnp | grep ":${APP_PORT}" > /dev/null; then echo "Port successfully freed" break fi echo "Current processes using port $APP_PORT:" sudo netstat -tlnp | grep ":${APP_PORT}" sleep 5 done # 最终检查端口 if sudo netstat -tlnp | grep ":${APP_PORT}" > /dev/null; then echo "Failed to free port $APP_PORT after multiple attempts" echo "Current processes using port $APP_PORT:" sudo netstat -tlnp | grep ":${APP_PORT}" # 最后的紧急措施 echo "Attempting emergency measures..." sudo fuser -k ${APP_PORT}/tcp 2>/dev/null || true sudo pkill -9 -f "whale-mgnt-service" 2>/dev/null || true sleep 5 if sudo netstat -tlnp | grep ":${APP_PORT}" > /dev/null; then echo "All attempts to free port failed. Please check manually." exit 1 fi fi # 启动服务 echo "Starting service..." sudo nohup java -jar \ -Xms256m \ -Xmx512m \ -XX:MaxMetaspaceSize=256m \ -XX:+HeapDumpOnOutOfMemoryError \ -XX:HeapDumpPath=/tmp/ \ -XX:+UseG1GC \ -XX:MaxGCPauseMillis=200 \ $JAR_PATH \ --spring.profiles.active=test \ --server.port=$APP_PORT \ --spring.jpa.open-in-view=false \ --server.tomcat.max-threads=50 \ --server.tomcat.min-spare-threads=20 \ --spring.task.execution.pool.core-size=5 \ --spring.task.execution.pool.max-size=10 \ --spring.task.execution.pool.queue-capacity=100 \ > $LOG_FILE 2>&1 & PID=$! echo "Starting service with PID: $PID" echo "Waiting for service to start..." # 给予初始化时间 sleep 15 attempt=1 while [ $attempt -le $MAX_ATTEMPTS ]; do # 检查进程是否存在 if ! ps -p $PID > /dev/null; then echo "Process died unexpectedly. Check logs:" tail -n 50 $LOG_FILE exit 1 fi # 检查端口和应用状态 if netstat -tunlp 2>/dev/null | grep ":$APP_PORT" > /dev/null; then if grep -q "Started WhaleManagementApplication" $LOG_FILE; then echo "Application started successfully" sleep 5 if ps -p $PID > /dev/null; then echo "Service is running stably" echo "Final memory usage:" free -m echo "Process memory usage:" ps -o pid,ppid,%mem,rss,command -p $PID exit 0 else echo "Process died after startup" tail -n 50 $LOG_FILE exit 1 fi fi fi echo "Attempt $attempt of $MAX_ATTEMPTS - Service still starting..." # 检查启动日志中的错误 if grep -q "Port $APP_PORT was already in use" $LOG_FILE; then echo "Port conflict detected!" echo "Current port status:" sudo netstat -tunlp | grep ":$APP_PORT" [ -n "$PID" ] && kill -9 $PID 2>/dev/null || true exit 1 fi echo "Recent log entries:" tail -n 10 $LOG_FILE sleep $INTERVAL attempt=$((attempt+1)) done echo "Service failed to start within timeout. Last 50 lines of log:" tail -n 50 $LOG_FILE [ -n "$PID" ] && kill -9 $PID 2>/dev/null || true exit 1
在 /data/app/my-boot 目录下创建启动脚本 start.sh
$ touch start.sh $ vi start.sh # 将下面代码粘贴到 start.sh 中
#!/bin/bash set -e #任何命令出错就退出 set -o pipefail APP_ID=my-boot APP_DIR="/data/app" nohup java -jar ${APP_DIR}/${APP_ID}/${APP_ID}.jar > release_out.log & start_ok=false if [[ $? = 0 ]];then sleep 3 tail -n 10 release_out.log sleep 5 tail -n 50 release_out.log fi aaa=`grep "Started" release_out.log | awk '{print $1}'` if [[ -n "${aaa}" ]];then echo "Application started ok" exit 0 else echo "Application started error" exit 1 fi
在 /data/app/my-boot 目录下创建停止脚本 stop.sh
$ touch stop.sh $ vi stop.sh # 将下面代码粘贴到 stop.sh 中
#!/bin/bash APP_ID=my-boot ps aux | grep ${APP_ID} | grep -v "grep" | awk '{print "kill -9 "$2}' | sh
并进行启动和停止测试,查看日志输出是否正常
将下述启动代码配置 jenkins 中
sh $DIR/${projectName}/stop.sh sh $DIR/${projectName}/start.sh
访问并测试代码是否生效
如果是虚拟机则需要给防火墙添加 9010 端口
$ su root # 开启防火墙 9010 端口 $ firewall-cmd --zone=public --add-port=9010/tcp --permanent # 使配置生效 $ firewall-cmd --reload
修改代码返回值,提交至 Gitee,并再次进行构建发布,访问 http://192.168.223.129:9010 查看结果是否更新

PS:异常修复 如出现 jenkins 内存占用异常,重启等方法都不能解决时 创建一个新的 jenkins_home
mkdir -p /mnt/data/jenkins_home_bak/secrets /mnt/data/jenkins_home_bak/users /mnt/data/jenkins_home_bak/jobs cp -a jobs users credentials.xml config.xml plugins tools /mnt/data/jenkins_home_bak/ cp -a secrets/hudson.util.Secret /mnt/data/jenkins_home_bak/secrets/ cp -a jenkins.model.*.xml hudson.tasks.*.xml /mnt/data/jenkins_home_bak/ cp -a hudson.plugins.git.GitTool.xml /mnt/data/jenkins_home_bak/ cp -a io.jenkins.plugins.gitlabserverconfig.servers.GitLabServers.xml /mnt/data/jenkins_home_bak/ cp -a jenkins.plugins.nodejs.tools.NodeJSInstallation.xml /mnt/data/jenkins_home_bak/ cp -a hudson.plugins.gradle.Gradle.xml /mnt/data/jenkins_home_bak/ cp /mnt/data/jenkins_home/jenkins.install.InstallUtil.lastExecVersion /mnt/data/jenkins_home_bak/ cp /mnt/data/jenkins_home/jenkins.install.UpgradeWizard.state /mnt/data/jenkins_home_bak/

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online