跳到主要内容Jenkins 自动化部署教程 | 极客日志Javajava
Jenkins 自动化部署教程
Jenkins 自动化部署涉及环境搭建、版本控制集成、项目构建及服务发布全流程。首先安装 JDK 与 Jenkins 服务,配置全局工具及凭证管理。接着集成 Git 仓库与 Maven 构建工具,实现代码拉取与编译打包。通过 SSH 免密登录配置,编写 Shell 脚本实现应用服务的远程拷贝、停止旧进程、启动新服务及日志监控,最终达成持续集成与自动化部署目标。
随缘20 浏览 Jenkins 自动化部署教程
1. Jenkins 是什么?
Jenkins 是一个开源的、提供友好操作界面的持续集成 (CI) 工具,广泛用于项目开发,具有自动化构建、测试和部署等功能。Jenkins 用 Java 语言编写,可在 Tomcat 等流行的 servlet 容器中运行,也可独立运行。通常与版本管理工具 (SCM)、构建工具结合使用。
2. 什么是持续集成(CICD)
因为开发部门同时维护多个版本,多个版本的发布,测试需要大量人力,所以要有一个专业的持续集成工具来管理持续重复的工作。
个人理解,说白了就是把代码测试、打包、发布等工作交给一些工具来自动完成。这样可以提高效率,减少失误,开发人员只需要关心开发和提交代码到 Git 就可以了。
3. Jenkins 的安装
(1)准备条件
安装 JDK
下载 JDK 压缩包,并上传至 Linux 某个目录下解压。
①. 配置 jdk 的环境变量
②. 将以下代码填入到 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 版本
(2)安装 Jenkins
- 下载 jenkins
wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war
- 启动 jenkins
使用 nohup 命令启动 nohup 当虚拟机黑屏时 也会运行 日志—>输出到 jenkins.log & 后台运行
nohup java -jar /usr/wubin/jenkins.war --httpPort=8777 --httpsPort=8778 > /usr/wubin/jenkins.log 2>&1 &
- 使用 tail 命令查看启动日志,日志中会输出 jenkins 密码
- 通过浏览器访问 jenkins(端口号必须为 8777)
(3)Jenkins 中配置 JDK 路径
jenkins-》全局工具配置-》JDK-》新增 JDK
(4)Jenkins 忘记密码的解决方案
4. 集成 Git
为了 Jenkins 能够拉取代码,需要安装 Git 环境和 jenkins 对应的 Git 插件
此处无需在 jenkins 中配置 Git 环境,采用默认生成的即可
进入 jenkins 的工作空间查看文件是否拉取下来,所有拉取的文件都会存放在 jenkins 工作空间中
5. 凭证配置
凭据就是用来存储需要密文保护的数据库密码、Gitee 密码信息、Docker 私有仓库密码等,以便 Jenkins 可以和这些第三方的应用进行交互。
- 凭证插件安装 Credentials Binding
该插件默认在一开始就会被安装,安装后在 jenkins-》系统管理-》安全 栏目会出现 Manage Credentials 选项,若没有需要安装插件并重启。
6. Maven 集成
在 jenkins 上发布 Java 项目时需要使用 Maven 来进行构建打包(Gradle 项目则需要安装配置 Gradle)
(1) 下载安装
/usr/wubin/ apache-maven-3.6.3
(2) 环境配置
vi /etc/profile 在最后面 JDK 配置上作出一些更改 export MAVEN_HOME= /usr/wubin/ apache-maven-3.6.3
export PATH=$JAVA_HOME/bin:$MAVEN_HOME/bin:$PATH
(3) 使配置生效并查看安装情况
source /etc/profile mvn -version
(4) Jenkins 配置 Maven
(5) 安装 Maven 插件
(6) 在 /data/software 目录下新建一个 repository 文件夹,用来作为 maven 的仓库
$ cd /usr/wubin $ mkdir repository
(7) 使用 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>
(8) Maven 测试项目构建
接下来的步骤是将 java 项目传到 Gitee 上供 jenkins 拉取打包,如果运维同学不懂 Java 代码,可以直接将我的 Git 项目 fork 或采用 gitlab 等其他方式进行拉取。
使用 git bash 命令将项目初始化,无论是传到 Gitee、GitHub、GitLab、Codeup 步骤一样,如果对 Git 的安装部署不熟悉
(9) Jenkins 添加 Maven 项目任务
从日志可以看到代码已经在拉取了,而且走的事阿里云仓库,第一次拉取过程会比较长。
通过查看 /data/software/repository 可以看到有存放拉取的 jar 包,通过这 2 个证据可以证明 settings.xml 文件配置成功且有效
ps:如果使用 git 账号密码拉取代码即使权限都已经给够了还是出现 403 等等一些问题,可以考虑使用 ApiToken 方式拉去代码,在 git/gitlab 创建 apiToken,在凭证管理地方加上凭证
构建成功后查看 jenkins 的 workspace 目录下的 jar 包
7. 配置 SSH 免密登录
由于 jenkins 构建消耗内存极大,一般 jenkins 是一台单独的工具机器,Java 项目一般在其他的机器上,这里我重新安装一台虚拟机
- IP:192.168.223.129
- JDK:1.8
- user:root
- 部署路径:/data/app/my-boot
- 端口:9010
免密登录主要是方便 jenkins 服务器 192.168.223.128 的 root 用户—》应用服务器 192.168.223.129 的 root 用户上的 jar 包拷贝,部署本就是 jar 包拷贝的过程
在 192.168.223.128 机器上使用 root 用户生成秘钥注意此处是 root 用户
运行后会在当前用户的根目录生成一个.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 目录下
在 jenkins192.168.223.128 服务器上进行免密连接测试
到此免密登录和拷贝实现成功,为接下来 jar 包部署提供了快捷的帮助
8. 编写 Jenkins 发布脚本
注意下面代码第 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"
最新脚本,以端口 kill 进程
#!/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
9. 编写应用启动脚本
在 /data/app/my-boot 目录下创建启动脚本 start.sh
$ touch start.sh $ vi 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
#!/bin/bash APP_ID=my-boot ps aux | grep ${APP_ID} | grep -v "grep" | awk '{print "kill -9 "$2}' | sh
sh $DIR/${projectName}/stop.sh sh $DIR/${projectName}/start.sh
修改代码返回值,提交至 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/
相关免费在线工具
- Keycode 信息
查找任何按下的键的javascript键代码、代码、位置和修饰符。 在线工具,Keycode 信息在线工具,online
- Escape 与 Native 编解码
JavaScript 字符串转义/反转义;Java 风格 \uXXXX(Native2Ascii)编码与解码。 在线工具,Escape 与 Native 编解码在线工具,online
- JavaScript / HTML 格式化
使用 Prettier 在浏览器内格式化 JavaScript 或 HTML 片段。 在线工具,JavaScript / HTML 格式化在线工具,online
- JavaScript 压缩与混淆
Terser 压缩、变量名混淆,或 javascript-obfuscator 高强度混淆(体积会增大)。 在线工具,JavaScript 压缩与混淆在线工具,online
- Base64 字符串编码/解码
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
- Base64 文件转换器
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online