Jenkins教程(自动化部署)
Jenkins教程(自动化部署)
1. Jenkins是什么?
Jenkins是一个开源的、提供友好操作界面的持续集成(CI)工具,广泛用于项目开发,具有自动化构建、测试和部署等功能。Jenkins用Java语言编写,可在Tomcat等流行的servlet容器中运行,也可独立运行。通常与版本管理工具(SCM)、构建工具结合使用。
2. 什么是持续集成(CICD)
因为开发部门同时维护多个版本,多个版本的发布,测试需要大量人力,所以要有一个专业的持续集成工具来管理持续重复的工作。
个人理解,说白了就是把代码测试、打包、发布等工作交给一些工具来自动完成。这样可以提高效率,减少失误,开发人员只需要关心开发和提交代码到Git就可以了。
3. Jenkins的安装
夸克网盘分享夸克网盘是夸克推出的一款云服务产品,功能包括云存储、高清看剧、文件在线解压、PDF一键转换等。通过夸克网盘可随时随地管理和使用照片、文档、手机资料,目前支持Android、iOS、PC、iPad。
https://pan.quark.cn/s/631fd86feeda
夸克网盘分享:https://pan.quark.cn/s/631fd86feeda
链接里包含jenkins.war maven jdk17 jenkins-start.sh简易shell命令
maven正常解压配置环境变量 ,记得修改maven目录里config的settings.xml配置
jdk如果系统jdk就为17 可以不用下载,不是的话就将jdk压缩包解压,将Jenkins-start.sh里面的java路径换成所解压的文件夹就行
(1)准备条件
安装JDK
下载JDK压缩包,并上传至Linux某个目录下解压。
①. 配置jdk的环境变量
#进入/etc/profile配置文件vim /etc/profile
②. 将以下代码填入到profile文件内底
export JAVA_HOME=/usr/wubin/jdk11export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jarexport PATH=$JAVA_HOME/bin:$PATH
③. 使配置生效
source /etc/profile
④. 检测java环境信息
javac
输入javac后出现以下信息则安装jdk成功!
ps:jenkins不同版本对jdk版本有不同要求,如果系统里面是jdk8,但是想在不升级jdk版本情况下使用更高版本的jenkins(比如要求jdk17),也可直接在jenkins启动时指定jdk路径,就不用修改系统里配置的jdk版本
(2)安装Jenkins
1. 下载jenkins
wget http://mirrors.jenkins.io/war-stable/latest/jenkins.war
2. 启动jenkins
# 使用nohup命令启动 nohup 当虚拟机黑屏时 也会运行 日志—>输出到jenkins.log & 后台运行
nohup java -jar /usr/wubin/jenkins.war --httpPort=8777 --httpsPort=8778 > /usr/wubin/jenkins.log 2>&1 &
3. 使用tail命令查看启动日志,日志中会输出jenkins密码
4. 通过浏览器访问jenkins(端口号必须为8777)
http://你的ip:8777
点击安装推荐的插件
(3)Jenkins中配置JDK路径
jenkins-》全局工具配置-》JDK-》新增JDK
(4)Jenkins忘记密码的解决方案
4. 集成Git
为了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
5. 凭证配置
凭据就是用来存储需要密文保护的数据库密码、Gitee密码信息、Docker私有仓库密码等,以便 Jenkins可以和这些第三方的应用进行交互。
1. 凭证插件安装Credentials Binding
该插件默认在一开始就会被安装,安装后在jenkins-》系统管理-》安全
栏目会出现Manage Credentials选项,若没有需要安装插件并重启。
6. Maven集成
在jenkins
上发布Java项目时需要使用Maven
来进行构建打包(Gradle项目则需要安装配置Gradle)
回到顶部
(1) 下载安装
# 找一个目录存放mavencd /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
回到顶部
(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/profilemvn -version
回到顶部
(4) Jenkins配置
Maven
(5) 安装Maven插件
(6) 在/data/software
目录下新建一个repository
文件夹,用来作为maven
的仓库
$ cd /usr/wubin$ mkdir repository
复制
(7) 使用root
账户修改Maven的settings.xml
文件(指定仓库目录和阿里云镜像)
/data/software/repository aliyun-maven central aliyun maven mirror http://maven.aliyun.com/nexus/content/groups/public/ ... ...
(8) Maven测试项目构建
接下来的步骤是将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
复制
(9) Jenkins添加Maven项目任务
①. 新建任务
②. 编写Maven编译命令
③. 构建并查看控制台日志
保存后,点击立即构建,然后进入日志控制台查看日志
从日志可以看到代码已经在拉取了,而且走的事阿里云仓库,第一次拉取过程会比较长。
通过查看/data/software/repository
可以看到有存放拉取的jar包,通过这2个证据可以证明settings.xml
文件配置成功且有效
ps:如果使用git账号密码拉取代码即使权限都已经给够了还是出现403等等一些问题,可以考虑使用ApiToken方式拉去代码,在git/gitlab创建apiToken,在凭证管理地方加上凭证
构建成功后查看jenkins
的workspace
目录下的jar包
到此Maven集成完毕
配置Post Steps,选中执行shell
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-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 root@192.168.223.129:/root/.ssh/authorized_keys
复制
在jenkins192.168.223.128服务器上进行免密连接测试
# 在jenkins服务器的/root/目录下创建filetest文件,并拷贝到应用服务器$ cd ~/$ touch filetest$ scp -p filetest root@192.168.223.129:/root/filetest# 进入到应用服务器(192.168.223.129),检查/root目录下是否出现filetest# 在jenkins服务器上使用ssh进行免密连接测试,成功后会出现Last Login的提示$ ssh root@192.168.223.129Last login: Sun Sep 20 21:53:03 2020$ exit
复制
到此免密登录和拷贝实现成功,为接下来jar包部署提供了快捷的帮助
8. 编写Jenkins发布脚本
注意下面代码第6行代码server_ips=”需要部署到的机器ip”
#!/bin/bashecho \"部署的目录和项目名称\"DIR=\"/data/app\"projectName=\"my-boot\"echo \"待部署的应用服务器,可多台\"server_ips=\"192.168.223.139\"for server_ip in ${server_ips[@]}doecho \"ssh连接进行备份操作\"ssh -Tq -oStrictHostKeyChecking=no root@${server_ip} <<EOFmkdir -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 fiEOFecho \"拷贝jar包到目标服务器的tmp目录\"scp -q -oStrictHostKeyChecking=no ${WORKSPACE}/target/*.jar root@${server_ip}:/tmp/${projectName}.jarecho \"ssh远程连接进行发布操作\"ssh -q -oStrictHostKeyChecking=no root@${server_ip} <<EOFmv /tmp/${projectName}.jar $DIR/${projectName}/${projectName}.jarEOFdoneecho \"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=8081MAX_ATTEMPTS=60INTERVAL=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 5done# 最终检查端口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 fifi# 启动服务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 15attempt=1while [ $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))doneecho \"Service failed to start within timeout. Last 50 lines of log:\"tail -n 50 $LOG_FILE[ -n \"$PID\" ] && kill -9 $PID 2>/dev/null || trueexit 1
9. 编写应用启动脚本
在/data/app/my-boot
目录下创建启动脚本start.sh
$ touch start.sh$ vi start.sh# 将下面代码粘贴到start.sh中
#!/bin/bashset -e #任何命令出错就退出set -o pipefailAPP_ID=my-bootAPP_DIR=\"/data/app\"nohup java -jar ${APP_DIR}/${APP_ID}/${APP_ID}.jar > release_out.log &start_ok=falseif [[ $? = 0 ]];then sleep 3 tail -n 10 release_out.log sleep 5 tail -n 50 release_out.logfiaaa=`grep \"Started\" release_out.log | awk \'{print $1}\'`if [[ -n \"${aaa}\" ]];then echo \"Application started ok\" exit 0else echo \"Application started error\" exit 1fi
在/data/app/my-boot
目录下创建停止脚本stop.sh
$ touch stop.sh$ vi stop.sh# 将下面代码粘贴到stop.sh中
#!/bin/bashAPP_ID=my-bootps aux | grep ${APP_ID} | grep -v \"grep\" | awk \'{print \"kill -9 \"$2}\' | sh
并进行启动和停止测试,查看日志输出是否正常
将下述启动代码配置jenkins
中
sh $DIR/${projectName}/stop.shsh $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/jobscp -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/