打造企业级自动化运维平台系列(五):Jenkis 基本使用介绍

这篇主要系统的介绍一下 jenkins 的使用,这里jenkins是搭建在 windows上。

常用功能介绍

常用插件介绍

除了最开始安装jenkins时他推荐让你下载的插件,还有一些插件是需要我们自行下载的,所以在这part我就简单汇总下其他我们可能要用到的一些插件。

  • 注意: jenkins上自行下载的一些插件,需要重启jenkins后才生效;
  • 另外像jdk,maven这些插件必须要自行在jenkins上配置好环境变量才可以运用。

因为jenkins下载了插件只代表你目前的jenkins能够支持使用这个插件,但是具体的插件位置其实是你本地的,而环境变量的配置就是去找你本地的目录。

另外常用的插件介绍
  • Rebuilder: 此插件可以直接重复上次构建
  • Pipeline: 持续交付插件,可以在新增 job时选择这一类型插件,然后通过写pipeline代码去运行job
  • Blue Ocean: 蓝海,可可视化看到任务的状态
  • Allure : 使用allure生成测试报告
  • robotframework: jenkins集成robotframework

创建一个自由风格的Job

我这里只是简单介绍一个简单任务的创建,大家可以按照各自需求配置自己的任务。

Jenkins新增节点

一般情况下,我们都不会在master节点上面去运行任务,通常会新增slave节点运行,由于我jenkins搭建在windows上,我这里就简单写下windows节点的新增。

新增windows节点
启动windows节点
在新节点上运行任务

Jenkins 配置报警机制

流程简单来说:

  • 就是在jenkins中配置好email后,
  • 运行任务时我们添加邮件触发器,当任务失败或者成功时,自动发送邮件
配置Email
下载Email相关插件
  • Email Extension
  • Email Extension Template Plugin

这两插件的作用即:帮助用户方便的设置格式化邮件。

配置管理员邮箱
配置邮件通知
配置邮件模板

Jenkins可以根据你配置的邮件模板格式发送结果邮件,通过Jenkins的参数定制自己的Email模板,常用的参数key值如下。

代码语言:javascript
复制
# 常用参数
$BUILD_STATUS -构建结果
$PROJECT_NAME -构建脚本名称
$BUILD_NUMBER -构建脚本编号
$JOB_DESCRIPTION -构建项目描述
$CAUSE - 脚本启动原因
$BUILD_URL - 脚本构建详情URL地址

模板内容,可以自行写

代码语言:javascript
复制
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>${ENV, var="JOB_NAME"}-第${BUILD_NUMBER}次构建日志</title>
</head>

<body leftmargin="8" marginwidth="0" topmargin="8" marginheight="4"
offset="0">
<table width="95%" cellpadding="0" cellspacing="0"
style="font-size: 11pt; font-family: Tahoma, Arial, Helvetica, sans-serif">
<tr>
<td>本邮件是Jenkins自动发送,请勿回复!</td>
</tr>
<tr>
<td><h3>
<font color="#e53935">&nbsp&nbsp&nbsp&nbsp构建结果 - ${BUILD_STATUS}!</font>
</h3></td>
</tr>
<tr>
<td><br />
<b><font color="#3f51b5">构建信息:</font></b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td>
<ul>
<li>项目名称&nbsp;:&nbsp;${PROJECT_NAME}</li>
<li>构建编号&nbsp;:&nbsp;第${BUILD_NUMBER}次构建</li>
<li>触发原因:&nbsp;${CAUSE}</li>
<li>构建日志:&nbsp;<a href="{BUILD_URL}console&#34;&gt;{BUILD_URL}console</a></li>
<li>构建&nbsp;&nbsp;Url&nbsp;:&nbsp;<a href="{BUILD_URL}&#34;&gt;{BUILD_URL}</a></li>
<li>工作目录&nbsp;:&nbsp;<a href="{PROJECT_URL}ws&#34;&gt;{PROJECT_URL}ws</a></li>
<li>项目&nbsp;&nbsp;Url&nbsp;:&nbsp;<a href="{PROJECT_URL}&#34;&gt;{PROJECT_URL}</a></li>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"></a></li>
</ul>
</td>
<tr>
<td><b><font color="#3f51b5">构建日志:</font></b>
<hr size="2" width="100%" align="center" /></td>
</tr>
<tr>
<td><textarea cols="160" rows="80" readonly="readonly"
style="font-family: Microsoft YaHei">${BUILD_LOG,maxLines=1000}</textarea>
</td>
</tr>
</html>

任务配置触发器
配置任务触发器

给你想要添加报警机制的任务添加即可

执行任务,查看邮件

Jenkins配置父子job

父子Job简要介绍

正常情况下,我们通常会有很多个任务,然后这些任务之间是有衔接的,比如先要执行一个任务,且这个任务是成功的状态下再去执行另一个任务

  • 适用场景:有先后次序关系的任务
  • 举个简单的例子:当我们要实现UI自动化时,首先我们需要先将最新的APP包给下载下来,下载成功后我们再去运行UI自动化用例
任务启动的触发条件:其他任务的运行结果

那下面我分别来详细讲下这三种情况的具体操作

  • 1、前驱任务成功的条件下被触发
  • 2、前驱任务失败的条件下被触发
  • 3、前驱任务不稳定的条件下被触发
父Job成功的条件下被触发
父Job失败的条件下被触发
  • 这里不重新再创建新任务,只在原任务上稍微调整一下
父Job不稳定的条件下被触发
  • 一样的,我这里也还是在原有任务上面进行简单修改,实现这种场景
  • 这里用到了一个新的插件Text Find,之前统一让大家下载过,如果没有下载过的话自行先下载下这个插件

JenkinsAPI 接口调用

Jenkins_API简要介绍

Jenkins_API:即Jenkins对外暴露的动作交互入口,为外部程序提供入口,可以控制Jenkins。

  • 支持协议:Http
  • 常用功能:运行Job,查看任务状态,返回任务编号
request调用JenkinsAPI

这里简单介绍下request进行调用JenkinsAPI,详细看下注释,其实很简单。

代码语言:javascript
复制
"""
该类调用jenkins_api接口
1、获取任务的最新编号
2、获取任务的详细信息
"""
import json

import requests

注意:这个地址前面部分

mikasa:yy1998123 是你的jenkins用户名和密码

127.0.0.1:8080 是本地jenkins域名+端口号

url = "http://mikasa:yy1998123@127.0.0.1:8080/jenkins/job/"

def get_jenkins_url(job_name):
"""
拼接url+任务job
:return:
"""
# print("拼接url为:", url + job_name)
return url + job_name

def send_api(req, tools="requests"):
"""
对发送接口测试的工具进行封装(可以使用urlib3/requests)
:param tools:
:param req:
:return:
"""
if tools == "requests":
return requests.request(**req)

def get_latest_job_number():
"""
1、获取最新任务编号
:return:
"""
req = {
"method": "GET",
"url": get_jenkins_url("mikasa_demo001") + "/lastBuild/buildNumber",
}
res = send_api(req)
print("最新任务编号:", res.json())

def get_job_info():
"""
2、获取job详细信息
:return:
"""
req = {
"method": "GET",
"url": get_jenkins_url("mikasa_demo001") + "/api/json",
}
res = send_api(req)
res_json = json.dumps(res.json(), indent=2)
print("返回结果:", res_json)

get_latest_job_number()
get_job_info()

使用jenkins api库调用

-这里介绍下利用jenkins api库去调用,上面是我们自己去写request请求去调用,而其实目前已经存在了轮子,我们直接使用Jenkins api即可。

下载jenkinsapi库
封装jenkins调用
  • jenkins_api.py
代码语言:javascript
复制
"""
封装jenkins调用类
"""
import configparser
import datetime
import logging
import os
import re
from jenkinsapi.jenkins import Jenkins

logging.basicConfig(level=logging.INFO, format='[%(asctime)s] - [%(name)s] - [%(levelname)s] - [%(message)s]')
log = logging.getLogger(__name__)

def get_jenkins_config(chose):
"""
读取Jenkins配置:从配置文件中jenkins_server.ini
:param chose:
:return:
"""
config = configparser.ConfigParser()
# 读取配置
config.read(os.path.join(os.getcwd(), 'jenkins_server.ini'))
username = config.get(chose, 'username')
password = config.get(chose, 'password')
host = config.get(chose, 'host')
port = config.get(chose, 'port')
url = "http://" + host + ":" + port
return url, username, password

class JenkinsDemo:
def init(self, job_name, chose='jenkins'):
"""
初始化,拿到jenkins配置
:param job_name:
:param chose:
"""
self.job_name = job_name
config = get_jenkins_config(chose)
print("config:", config)
# 解包元祖
self.jk = Jenkins(*config, useCrumb=True)

def __get_job_from_keys(self):
    &#34;&#34;&#34;
    拿到所有的job名称
    :return: 返回一个列表
    &#34;&#34;&#34;
    choose_list = []
    print(self.jk.keys())
    for my_job_name in self.jk.keys():
        # 遍历拿到所有的job,判断当前job是否在job列表里面,在的话添加到自定义列表
        if self.job_name in my_job_name:
            choose_list.append(my_job_name)
    return choose_list

def __job_build(self, my_job_name):
    &#34;&#34;&#34;
    构建job
    :param my_job_name:
    :return:
    &#34;&#34;&#34;
    if self.jk.has_job(my_job_name):
        # 如果有这个job拿到他里面的job对象
        my_job = self.jk.get_job(my_job_name)
        if not my_job.is_queued_or_running():
            # 如果job当前没有在运行的话,就运行
            try:
                # 若当前没有在跑的话,拿到最后一次构建数
                last_build = my_job.get_last_buildnumber()
            except:
                # 若没有获取到最后一次构建数的话,默认置为0
                last_build = 0
            # 最新构建数+1
            build_num = last_build + 1
            try:
                # 开始打包
                self.jk.build_job(my_job_name)
            except Exception as e:
                log.error(str(e))

            # 循环判断Jenkins是否打包完成
            while True:
                # 若当前任务没有运行才获取信息
                if not my_job.is_queued_or_running():
                    # 拿到最新一次的大奥信息
                    count_build = my_job.get_build(build_num)
                    # 获取打包开始时间
                    start_time = count_build.get_timestamp() + datetime.timedelta(hours=8)
                    # 获取打包日志
                    console_out = count_build.get_console()
                    # 获取打包状态
                    status = count_build.get_status()
                    # 获取变更内容
                    change = count_build.get_changeset_items()
                    log.info(&#34; &#34; + str(start_time) + &#34; 发起的&#34; + my_job_name + &#34;构建已经完成,构建的状态为:&#34; + status)
                    p2 = re.compile(r&#34;.*ERROR.*&#34;)
                    err_list = p2.findall(console_out)
                    log.info(&#34;打包日志为:&#34; + str(console_out))
                    if status == &#34;SUCCESS&#34;:
                        if len(change) &gt; 0:
                            for data in change:
                                for file_list in data[&#34;affectedPaths&#34;]:
                                    log.info(&#34;发起的&#34; + my_job_name + &#34;变更的类:&#34; + file_list)
                                log.info(&#34;发起的&#34; + my_job_name + &#34;变更的备注:&#34; + data[&#34;msg&#34;])
                                log.info(&#34;发起的&#34; + my_job_name + &#34;变更的提交人:&#34; + data[&#34;author&#34;][&#34;fullName&#34;])
                        else:
                            log.info(&#34;发起的&#34; + my_job_name + &#34;构建没有变更内容&#34;)

                        if len(err_list) &gt; 0:
                            log.warning(&#34;构建的&#34; + my_job_name + &#34;构建状态为成功,但包含了以下错误:&#34;)
                            for error in err_list:
                                log.error(error)
                    else:
                        if len(err_list) &gt; 0:
                            log.warning(&#34;构建的&#34; + my_job_name + &#34;包含了以下错误:&#34;)
                            for error in err_list:
                                log.error(error)
                    break
        else:
            log.warning(&#34;发起的&#34; + my_job_name + &#34;Jenkins is running&#34;)
    else:
        log.warning(&#34;发起的&#34; + my_job_name + &#34;没有该服务&#34;)

def run(self):
    my_job_name = self.__get_job_from_keys()
    if len(my_job_name) == 1:
        self.__job_build(my_job_name[0])
    elif len(my_job_name) == 0:
        log.error(&#34;输入的job名称不正确!&#34;)

if name == 'main':
jk = JenkinsDemo("mikasa_demo001")
jk.run()

  • jenkins_server.ini
代码语言:javascript
复制
[jenkins]
username=mikasa
password=yy1998123
host=127.0.0.1
port=8080

Jenkins多线程任务执行

正常一个项目部署中,为了节省时间,我们通常都可以将一些没有依赖关系的任务同步执行。

  • 比如说:在进行UI自动化中,下载app包的同时,我们可以把git源码同步更新给拉下来

那本章就简单讲一个例子,多线程的情况下我们如何去写jenkinsfile。

任务示例
mikasa_parallel_demo
  • 并行任务即在外面加个**parallel{}**,里面包含stage{}即可。
代码语言:javascript
复制
pipeline {
agent none
stages {
stage('run parallel Stage') {
parallel {
stage('mikasa_Stage_1') {
agent { label "slave" }
steps {
echo "at agent slave run task 1."
bat "ipconfig"
sleep 10
}
}
stage('mikasa_Stage_2') {
agent { label "master" }
steps {
echo "at agent master run task 2."
bat "ipconfig"
sleep 10
}
}
}
}
}
}

参考文章:https://blog.csdn.net/makasa/category

_10722865.html