Spring Boot(十六):使用 Jenkins 部署 Spring Boot

Jenkins 是 Devops 神器,本篇文章介绍如何安装和使用 Jenkins 部署 Spring Boot 项目

Jenkins 搭建、部署分为四个步骤;

第一步,Jenkins 安装

第二步,插件安装和配置

第三步,Push SSH

第四步,部署项目

第一步 ,Jenkins 安装

准备环境:

JDK:1.8

Jenkins:2.83

Centos:7.3

maven 3.5

Jdk 默认已经安装完成

配置 Maven

版本要求 Maven3.5.0

软件下载

复制代码
wget http://mirror.bit.edu.cn/apache/maven/maven-3/3.5.0/binaries/apache-maven-3.5.0-bin.tar.gz

安装

修改环境变量,

在/etc/profile中添加以下几行

复制代码
## 解压
tar vxf apache-maven-3.5.0-bin.tar.gz
## 移动
mv apache-maven-3.5.0 /usr/local/maven3

MAVEN_HOME=/usr/local/maven3
export MAVEN_HOME
export PATH=${PATH}:${MAVEN_HOME}/bin

记得执行source /etc/profile使环境变量生效。

验证

最后运行mvn -v验证maven是否安装成功

配置防护墙

关闭防护墙

复制代码
#centos7
systemctl stop firewalld.service
==============================
#以下为:centOS 6.5关闭防火墙步骤
#关闭命令:  
service iptables stop 
#永久关闭防火墙:
chkconfig iptables off

两个命令同时运行,运行完成后查看防火墙关闭状态

复制代码
service iptables status

Jenkins 安装

下载

复制代码
cd /opt
wget http://mirrors.jenkins.io/war/2.83/jenkins.war

启动服务

复制代码
java -jar jenkins.war &

Jenkins 就启动成功了!它的war包自带Jetty服务器

第一次启动 Jenkins 时,出于安全考虑,Jenkins 会自动生成一个随机的按照口令。注意控制台输出的口令,复制下来,然后在浏览器输入密码:

复制代码
INFO: 

*************************************************************
*************************************************************
*************************************************************

Jenkins initial setup is required. An admin user has been created and a password generated.
Please use the following password to proceed to installation:

0cca37389e6540c08ce6e4c96f46da0f

This may also be found at: /root/.jenkins/secrets/initialAdminPassword

*************************************************************
*************************************************************
*************************************************************

访问

浏览器访问:http://localhost:8080/

输入:0cca37389e6540c08ce6e4c96f46da0f

进入用户自定义插件界面,建议选择安装官方推荐插件,因为安装后自己也得安装:

接下来是进入插件安装进度界面:

插件一次可能不会完全安装成功,可以点击Retry再次安装。直到全部安装成功

等待一段时间之后,插件安装完成,配置用户名密码:

输入:admin/admin

系统管理-》全局工具配置 jdk路径,

第二步,插件安装和配置

有很多插件都是选择的默认的安装的,所以现在需要我们安装的插件不多,Git plugin 和 Maven Integration plugin,publish over SSH。

插件安装:系统管理 > 插件管理 > 可选插件,勾选需要安装的插件,点击直接安装或者下载重启后安装

配置全局变量

系统管理 > 全局工具配置

JDK

配置本地 JDK 的路径,去掉勾选自动安装

Maven

配置本地maven的路径,去掉

复制代码
ssh-copy-id -i id_rsa.pub 192.168.0.xx
chmod 644 authorized_keys

勾选自动安装

其它内容可以根据自己的情况选择安装。

使用密钥方式登录目标发布服务器

ssh 的配置可使用密钥,也可以使用密码,这里我们使用密钥来配置,在配置之前先配置好jenkins服务器和应用服务器的密钥认证

Jenkins服务器上生成密钥对,使用ssh-keygen -t rsa命令

输入下面命令 一直回车,一个矩形图形出现就说明成功,在~/.ssh/下会有私钥id_rsa和公钥id_rsa.pub

复制代码
ssh-keygen -t rsa

将jenkins服务器的公钥id_rsa.pub中的内容复制到应用服务器 的~/.ssh/下的 authorized_keys文件

复制代码
ssh-copy-id -i id_rsa.pub 192.168.0.xx
chmod 644 authorized_keys

在应用服务器上重启 ssh 服务,service sshd restart现在 Jenkins 服务器可免密码直接登陆应用服务器

之后在用 ssh B尝试能否免密登录 B 服务器,如果还是提示需要输入密码,则有以下原因

a. 非 root 账户可能不支持 ssh 公钥认证(看服务器是否有限制)

b. 传过来的公钥文件权限不够,可以给这个文件授权下 chmod 644 authorized_keys

c. 使用 root 账户执行 ssh-copy-id -i ~/.ssh/id_rsa.pub 这个指令的时候如果需要输入密码则要配置sshd_config

复制代码
vi /etc/ssh/sshd_config
#内容
PermitRootLogin no

修改完后要重启 sshd 服务

复制代码
service sshd restart

最后,如果可以 SSH IP 免密登录成功说明 SSH 公钥认证成功。

上面这种方式比较复杂,其实在 Jenk

复制代码
clean install -Dmaven.test.skip=true -Ptest

ins 后台直接添加操作即可,参考下面方式

使用用户名+密码方式登录目标发布服务器

(1)点击"高级"展开配置

(2)配置SSH的登陆密码

配置完成后可点击"Test Configuration"测试到目标主机的连接,出现"success"则成功连接,如果有多台应用服务器,可以点击"增加",配置多个"SSH Servers" 点击"保存"以保存配置。

第三步,Push SSH

系统管理 > 系统设置

选择 Publish over SSH

Passphrase 不用设置

Path to key 写上生成的ssh路径:/root/.ssh/id_rsa

下面的 SSH Servers 是重点

Name 随意起名代表这个服务,待会要根据它来选择

Hostname 配置应用服务器的地址

Username 配置 linux 登陆用户名

Remote Directory 不填

点击下方增加可以添加多个应用服务器的地址

第四步,部署项目

首页点击新建:输入项目名称

下方选择构建一个 Maven 项目,点击确定。

勾选丢弃旧的构建,选择是否备份被替换的旧包。我这里选择备份最近的10个

源码管理,选择 SVN,配置 SVN 相关信息,点击 add 可以输入 SVN 的账户和密码

SVN 地址:http://192.168.0.xx/svn/xxx@HEAD,@HEAD意思取最新版本

构建环境中勾选"Add timestamps to the Console Output",代码构建的过程中会将日志打印出来

在 Build 中输入打包前的 mvn 命令,如:

复制代码
clean install -Dmaven.test.skip=true -Ptest

意思是:排除测试的包内容,使用后缀为 test 的配置文件。

Post Steps 选择 Run only if build succeeds

点击Add post-build step,选择 Send files or execute commands over SSH

Name 选择上面配置的 Push SSH

Source files配置:target/xxx-0.0.1-SNAPSHOT.jar 项目jar包名

Remove prefix:target/

Remote directory:Jenkins-in/ 代码应用服务器的目录地址,

Exec command:Jenkins-in/xxx.sh 应用服务器对应的脚本。

需要在应用服务器创建文件夹:Jenkins-in,在文件夹中复制一下脚本内容:xxx.sh

复制代码
DATE=$(date +%Y%m%d)
export JAVA_HOME PATH CLASSPATH
JAVA_HOME=/usr/java/jdk1.8.0_131
PATH=$JAVA_HOME/bin:$JAVA_HOME/jre/bin:$PATH
CLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/jre/lib:$CLASSPATH
DIR=/root/xxx
JARFILE=xxx-0.0.1-SNAPSHOT.jar

if [ ! -d $DIR/backup ];then
   mkdir -p $DIR/backup
fi
cd $DIR

ps -ef | grep $JARFILE | grep -v grep | awk '{print $2}' | xargs kill -9
mv $JARFILE backup/$JARFILE$DATE
mv -f /root/Jenkins-in/$JARFILE .

java -jar $JARFILE > out.log &
if [ $? = 0 ];then
        sleep 30
        tail -n 50 out.log
fi

cd backup/
ls -lt|awk 'NR>5{print $NF}'|xargs rm -rf

这段脚本的意思,就是 kill 旧项目,删除旧项目,启动新项目,备份老项目。

全文完。

参考:https://blog.csdn.net/LLQ_200/article/details/76921487

资料推荐

最近又赶上跳槽的高峰期(招聘旺季),好多读者都问我要有没有最新面试题,找华为朋友整理一份内部资料《第6版:互联网大厂面试题》并分类 4 份 PDF,累计 926 页!

整个资料包,包括 Spring、Spring Boot/Cloud、Dubbo、JVM、集合、多线程、JPA、MyBatis、MySQL、大数据、Nginx、Git、Docker、GitHub、Servlet、JavaWeb、IDEA、Redis、算法、面试题等几乎覆盖了 Java 基础和阿里巴巴等大厂面试题等、等技术栈!

据说已经有小伙伴通过这套资料,成功的入职了蚂蚁金服、字节跳动等大厂。

而且,这些资料不是扫描版的,里面的文字都可以直接复制,非常便于我们学习:

2.1 基本名词解释

名词 概念

PlatformTransactionManager 事务管理器,管理事务的各生命周期方法,下文简称TxMgr

TransactionAttribute 事务属性, 包含隔离级别,传播行为,是否只读等信息,下文简称TxAttr

TransactionStatus 事务状态,下文简称TxStatus

TransactionInfo 事务信息,内含TxMgr, TxAttr, TxStatus等信息,下文简称TxInfo

TransactionSynchronization 事务同步回调,内含多个钩子方法,下文简称TxSync / transaction synchronization

TransactionSynchronizationManager 事务同步管理器,维护当前线程事务资源,信息以及TxSync集合

2.2 七种事务传播行为

Spring中的Propagation枚举和TransactionDefinition接口定义了7种事务传播行为:

REQUIRED

如果当前无事务则开启一个事务,否则加入当前事务。

SUPPORTS

如果当前有事务则加入当前事务。

MANDATORY

如果当前无事务则抛出异常,否则加入当前事务。

REQUIRES_NEW

如果当前无事务则开启一个事务,否则挂起当前事务并开启新事务。

NOT_SUPPORTED

如果当前有事务,则挂起当前事务以无事务状态执行方法。

NEVER

如果当前有事务,则抛出异常。

NESTED

创建一个嵌套事务,如果当前无事务则创建一个事务。

2.3 套路讲解

这里介绍以下Spring声明式事务的套路。如果能知晓大致套路会对接下来看源码有很大的帮助。文笔不是太好,下面的描述如有不清,还请指出。

2.3.1 事务拦截器

我们给一个bean的方法加上@Transactional注解后,Spring容器给我们的是一个代理的bean。当我们对事务方法调用时,会进入Spring的ReflectiveMethodInvocation#proceed方法。这是AOP的主要实现,在进入业务方法前会调用各种方法拦截器,我们需要关注的拦截器是org.springframework.transaction.interceptor.TransactionInterceptor。

TransactionInterceptor的职责类似于一个"环绕切面",在业务方法调用前根据情况开启事务,在业务方法调用完回到拦截器后进行善后清理。

事务切面在源码中具体的实现方法是TransactionAspectSupport#invokeWithinTransaction,相信平时大家debug的时候在调用栈中经常能看到此方法。事务切面关注的是TransactionInfo(TxInfo),TxInfo是一个"非常大局观"的东西(里面啥都有:TxMgr, TxAttr, TxStatus还有前一次进入事务切面的TransactionInfo)。

因此事务切面会调用createTransactionIfNecessary方法来创建事务并拿到一个TxInfo(无论是否真的物理创建了一个事务)。如果事务块内的代码发生了异常,则会根据TxInfo里面的TxAttr配置的rollback规则看看这个异常是不是需要回滚,不需要回滚就尝试提交,否则就尝试回滚。如果未发生异常,则尝试提交。

2.3.2 提交与回滚

事务切面对于尝试提交会判断是否到了最外层事务(某个事务边界)。举个例子:有四个事务方法依次调用,传播行为分别是 方法1:REQUIRED, 方法2:REQUIRED, 方法3: REQUIRES_NEW, 方法4: REQUIRED。很显然这其中包含了两个独立的物理事务,当退栈到方法4的事务切面时,会发现没有到事务最外层,所以不会有真正的物理提交。而在退栈到了方法3对应的事务切面时会发现是外层事务,此时会发生物理提交。同理,退栈到方法1的事务切面时也会触发物理提交。

那么问题来了,Spring是怎么判断这所谓"最外层事务"的呢。

答案是TxStatus中有个属性叫newTransaction用于标记是否是新建事务(根据事务传播行为得出,比如加入已有事务则会是false),以及一个名为transaction的Object用于表示物理事务对象(由具体TxMgr子类负责给出)。Spring会根据每一层事务切面创建的TxStatus内部是否持有transaction对象以及newTransaction标志位判断是否属于外层事务。

类似的,Spring对于回滚事务也是会在最外层事务方法对应的切面中进行物理回滚。而在非最外层事务的时候会由具体txMgr子类给对应的事务打个的标记用于标识这个事务该回滚,这样的话在所有同一物理事务方法退栈过程中在事务切面中都能读取到事务被打了应该回滚的标记。可以说这是同一物理事务方法之间进行通信的机制。

2.3.3 ThreadLocal的使用

Spring事务代码中用ThreadLocal来进行资源与事务的生命周期的同步管理。

在事务切面层面,TransactionAspectSupport里面有个transactionInfoHolder的ThreadLocal对象,用于把TxInfo绑定到线程。那么这样在我们的业务代码或者其他切面中,我们可以拿到TxInfo,也能拿到TxStatus。拿到TxStatus我们就可以调用setRollbackOnly来打标以手动控制事务必须回滚。

TransactionSynchronizationManager是Spring事务代码中对ThreadLocal使用最多的类,目前它内部含有6个ThreadLocal,分别是:

resources

类型为Map<Object, Object>用于保存事务相关资源,比如我们常用的DataSourceTransactionManager会在开启物理事务的时候把<DataSource, ConnectionHolder>绑定到线程。

这样在事务作用的业务代码中可以通过Spring的DataSourceUtils拿到绑定到线程的ConnectionHolder中的Connection。事实上对于MyBatis来说与Spring集成时就是这样拿的。

synchronizations

类型为Set用于保存transaction synchronization,这个可以理解为是回调钩子对象,内部含有beforeCommit, afterCommit, beforeCompletion等钩子方法。

我们自己如果需要的话也可以在业务方法或者切面中注册一些transaction synchronization对象用于追踪事务生命周期做一些自定义的事情。

currentTransactionName

当前事务名

currentTransactionReadOnly

当前事务是否只读

currentTransactionIsolationLevel

当前事务隔离级别

actualTransactionActive

是否存在物理事务,比如传播行为为NOT_SUPPORTED时就会是false。

2.4 几幅草图

下面是我做的几张图,比较丑陋。举了三个不同的事务传播情况,列一下TxInfo的信息(TxInfo中主要列了TxStatus的一些关键字段以及oldTransactionInfo字段)

复制代码
protected Object invokeWithinTransaction(Method method, Class<?> targetClass, final InvocationCallback invocation) throws Throwable {

    // 获取TransactionAttribute、PlatformTransactionManager、以及连接点方法信息。
    final TransactionAttribute txAttr = getTransactionAttributeSource().getTransactionAttribute(method, targetClass);
    final PlatformTransactionManager tm = determineTransactionManager(txAttr);
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
        // Standard transaction demarcation with getTransaction and commit/rollback calls.
        // 根据上面抓取出来的txAttribute, tm, 连接点方法等信息判断是否需要开启事务。
        TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
        Object retVal = null;
        try {
            // 执行回调,如果没有后续拦截器的话,就进入事务方法了。
            retVal = invocation.proceedWithInvocation();
        }
        catch (Throwable ex) {
            // 事务发生异常。
            completeTransactionAfterThrowing(txInfo, ex);
            throw ex;
        }
        finally {
            // 把上一层事务的TxInfo重新绑到ThreadLocal中。
            cleanupTransactionInfo(txInfo);
        }
        // 事务未发生异常。
        commitTransactionAfterReturning(txInfo);
        return retVal;
    }

    else {
        try {
            Object result = ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr,
                    new TransactionCallback<Object>() {
                        @Override
                        public Object doInTransaction(TransactionStatus status) {
                            TransactionInfo txInfo = prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
                            try {
                                return invocation.proceedWithInvocation();
                            }
                            catch (Throwable ex) {
                                if (txAttr.rollbackOn(ex)) {
                                    if (ex instanceof RuntimeException) {
                                        throw (RuntimeException) ex;
                                    }
                                    else {
                                        throw new ThrowableHolderException(ex);
                                    }
                                }
                                else {
                                    return new ThrowableHolder(ex);
                                }
                            }
                            finally {
                                cleanupTransactionInfo(txInfo);
                            }
                        }
                    });

            if (result instanceof ThrowableHolder) {
                throw ((ThrowableHolder) result).getThrowable();
            }
            else {
                return result;
            }
        }
        catch (ThrowableHolderException ex) {
            throw ex.getCause();
        }
    }
}
相关推荐
晨非辰1 天前
算法闯关日记 Episode :解锁链表「环形」迷局与「相交」奥秘
数据结构·c++·人工智能·后端·python·深度学习·神经网络
小周在成长1 天前
Java 权限修饰符(Access Modifiers)指南
后端
00后程序员1 天前
iOS 上架 4.3,重复 App 审核条款的真实逻辑与团队应对策略研究
后端
00后程序员1 天前
专业的 IPA 处理工具 构建可维护、可回滚的 iOS 成品加工与加固流水线
后端
百度Geek说1 天前
项目级效能提升一站式交付最佳实践
后端
今天你TLE了吗1 天前
通过RocketMQ延时消息实现优惠券等业务MySQL当中定时自动过期
java·spring boot·后端·学习·rocketmq
Gundy1 天前
构建一个真正好用的简单搜索引擎
后端
疯狂的程序猴1 天前
构建面向复杂场景的 iOS 应用测试体系 多工具协同下的高质量交付实践
后端
大巨头1 天前
C# 中如何理解泛型
后端
用户992441031561 天前
TRAE SOLO实战录:AI应用可观测性与风险管控的破局之道
后端