Jenkins分布式构建(Jenkins Distributed builds)
前言:
当自动化测试用例需要在多个PC机或虚拟机中执行时,如果在每个虚拟机中均搭建类似tomcat+jenkins的环境,将会造成例如每台虚拟机资源占用大、对环境的配置维护成本大等弊端,此时,就可以采用Jenkins分布式构建方式了。
一、Jenkins节点配置
1.Master配置
1)进入Master的http://ip:8080/jenkins/网页界面
2)进入系统管理——节点管理界面
3)点击“新建节点”
远程工作目录:指定远程中的节点机器的工作目录,即Job中checkout出的代码所在的workspace目录
标签:该节点的唯一标识,当在Job中要指定只在该节点进行构建与测试时,通过该唯一标识进行指定
其中启动方法有四种:
Launch slave agents on Unix machines via SSH: 当节点为Unix
slaves时,可以选择此种方式
Launch
slave agents via Java Web Start: 使用JNLP方式来建立slave与master的连接
Launch
slave via execution of command on the Master: 使用命令行方式
Let
Jenkins control this Windows slave as a Windows service :Jenkins将把该Windows slave当做Windows
service进行控制
对于Windows操作系统的节点,推荐用第二种Launch
slave agents via Java Web Start
点击“保存”后,在Master中一个节点就配置好了
2.slave节点配置
当节点以Launch slaveagents via Java Web Start配置好后,在节点界面将看到如图所示,其slave有三种启动方法,本质上其实就一种,即将master中的slave-agetn.jnlp文件下载至slave所在的虚拟机,然后运行文件。
注:上图中Run from slave command line中的ip地址,如果在jenkins系统配置界面中没设置过ip的话,将为localhost,因此此时需要自己先查看下自己master机器的ip。
因此为方便,可以将连接方式写入bat批处理文件中。
1)进入节点虚拟机,先创建刚才配置的远程工作目录(D:\\jenkins)
2)创建bat文件,命令为例如:start_jenkins_agent.bat 内容:
javaws http://192.168.10.181:8080/jenkins/computer/Windows_181_to_4400/slave-agent.jnlp
3)双击运行文件即可看到slave节点与master连接
注:上图中,点击File——Installas a services 可以将该slave agent以Windows系统服务运行,理论上方便于开机自启动,但作为Windows服务,是无法与GUI进行交互的,因此没法启动火狐进行selenium的自动化测试,因此如果自动化测试与GUI相关,这里千万别Install as a services。
二、Job配置
在Master中新建一个job,其中在配置页面中勾选Restrict when this project can be run,Label选择要在哪个节点中运行,这样就可以指定该job在哪个slave节点中运行了
三、Jenkins slave开机自启动
1)若要对所用用户均进行开机自启动,则将.bat启动文件的快捷方式放入 ‘\Documents and Settings\All Users\“开始”菜单\程序\启动’ 目录下
2)若只要对指定用户进行开机自启动,则将.bat启动文件的快捷方式放入 ‘\Documents and Settings\ <用户名字>\“开始”菜单\程序\启动’ 目录下
四、Jenkins节点监控
jenkins节点由于系统运行、网络环境等各种因素,难免会出现系统挂机、节点掉线等情况,因此要想及时发现这些情况,就需要对节点进行监控,
可以采用这里推荐的监控并重连机制:https://wiki.jenkins-ci.org/display/JENKINS/Monitor+and+Restart+Offline+Slaves
1)由于jenkins节点监控使用的是groovy脚本调用的jenkins内部api,因此需要先安装groovy plugin插件
2)在Master中新建一个名字为如monitor的job,设置为例如每30分钟运行一次。
3)新增Excute system Groovy script构建步骤:
输入groovy脚本(由于wiki中的没有邮箱认证步骤,因此这里的脚本增加了邮箱认证):
import hudson.model.* import hudson.node_monitors.* import hudson.slaves.* import java.util.concurrent.* jenkins = Hudson.instance import javax.mail.internet.*; import javax.mail.* import javax.activation.* def sendMail (slave, cause) { //这里使用的是参数化构建中的变量,如果不使用此方式,可以注释掉,而使用下面的toAddress toAddress = build.buildVariableResolver.resolve("EMAIL_RECEIVERS") message = slave + " slave is down. Check http://192.168.10.181:8080/jenkins/computer/" + slave + "\nBecause " + cause subject = "【jenkins节点监控】" + slave + " slave is offline" //toAddress = "***@***.com;***@***.com" fromAddress = "***@***.com" host = "SMTP_SERVER" port = "SMTP_PORT" Properties props = new Properties(); // 发送邮件的服务器 props.setProperty("mail.smtp.host", "smtp.***.com"); // 发送邮件的协议 props.setProperty("mail.transport.protocol", "smtp"); // 在连接服务器的时候是否需要验证,发邮件是需要验证的 props.setProperty("mail.smtp.auth", "true"); // 当需要进行验证的时候,会自动从Session中去取该Authenticator对象 Authenticator authenticator = new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication("your user name", "your passwd"); //输入用户名、密码 } }; Session lSession = Session.getInstance(props,authenticator); MimeMessage msg = new MimeMessage(lSession); //tokenize out the recipients in case they came in as a list StringTokenizer tok = new StringTokenizer(toAddress,";"); ArrayList emailTos = new ArrayList(); while(tok.hasMoreElements()){ emailTos.add(new InternetAddress(tok.nextElement().toString())); } InternetAddress[] to = new InternetAddress[emailTos.size()]; to = (InternetAddress[]) emailTos.toArray(to); msg.setRecipients(MimeMessage.RecipientType.TO,to); InternetAddress fromAddr = new InternetAddress(fromAddress); msg.setFrom(fromAddr); msg.setFrom(new InternetAddress(fromAddress)); msg.setSubject(subject); msg.setText(message) Transport transporter = lSession.getTransport("smtp"); transporter.connect(); transporter.send(msg); } def getEnviron(computer) { def env def thread = Thread.start("Getting env from ${computer.name}", { env = computer.environment }) thread.join(2000) if (thread.isAlive()) thread.interrupt() env } def slaveAccessible(computer) { getEnviron(computer)?.get('PATH') != null } def numberOfflineNodes = 0 def numberNodes = 0 for (slave in jenkins.slaves) { def computer = slave.computer numberNodes ++ println "" println "Checking computer ${computer.name}:" def isOK = (slaveAccessible(computer) && !computer.offline) if (isOK) { println "\t\tOK, got PATH back from slave ${computer.name}." println('\tcomputer.isOffline: ' + slave.getComputer().isOffline()); println('\tcomputer.isTemporarilyOffline: ' + slave.getComputer().isTemporarilyOffline()); println('\tcomputer.getOfflineCause: ' + slave.getComputer().getOfflineCause()); println('\tcomputer.offline: ' + computer.offline); } else { numberOfflineNodes ++ println " ERROR: can't get PATH from slave ${computer.name}." println('\tcomputer.isOffline: ' + slave.getComputer().isOffline()); println('\tcomputer.isTemporarilyOffline: ' + slave.getComputer().isTemporarilyOffline()); println('\tcomputer.getOfflineCause: ' + slave.getComputer().getOfflineCause()); println('\tcomputer.offline: ' + computer.offline); sendMail(computer.name, slave.getComputer().getOfflineCause().toString()) if (slave.getComputer().isTemporarilyOffline()) { if (!slave.getComputer().getOfflineCause().toString().contains("Disconnected by")) { computer.setTemporarilyOffline(false, slave.getComputer().getOfflineCause()) } } else { computer.connect(true) } } } println ("Number of Offline Nodes: " + numberOfflineNodes) println ("Number of Nodes: " + numberNodes)
注:jenkins 在1.582版本时修复了一个slave会概率性出现掉线,且无法重连,只至重启才能再次连接的问题,CancelledKeyException can cause all JNLP slaves
to disconnect (and the problem remains until restart):https://issues.jenkins-ci.org/browse/JENKINS-24050
因此建议将jenkins升级至最新的或1.582以上的版本