XII jenkins

简介:

jenkins是一个开源的CIcontinuous integration)服务器,它的扩展架构使它不仅是一个构建管理系统,也成为一个通用的开发生命周期管理系统;

jenkins是一个可扩展的持续集成引擎;

主要用于:持续、自动的构建/测试软件项目;监控一些定时执行的任务;

 

特性:

易安装,只要将jenkins.war部署到servlet容器,不需数据库支持;

易配置,所有配置都通过web界面实现;

集成RSS/email,通过rss发布构建结果,或构建完成时发email

支持分布式构建,能让多台计算机一起构建/测试;

生成Junit/TestNG测试报告;

文件识别,可跟踪某次构建生成哪些jar,哪次构建使用哪个版本的jar等;

支持扩展插件,可开发适合自己团队使用的工具;

支持环境配置矩阵;

追踪依赖关系;

 

优点:

一切配置都在web上完成,一些配置如MAVEN_HOME/email,只需配置一次,所有项目都能用,也可通过修改xml完成;

支持mavenmodulejenkinsmaven作了优化,它能自动识别module,每个module可配置成一个job,相当灵活;

测试报告聚合,所有模块的测试报告都被聚合在一起,结果一目了然,若使用其它ci这几乎是不可能完成的任务;

构建指纹artifact finterprint,每次build的结果构建都被很好的自动管理,无需任何配置就可方便的浏览下载;

 

CI持续集成已成为当前许多软件开发团队在整个软件开发生命周期内侧重于保证代码质量的常见做法,它是一种实践,旨在缓和和稳固软件的构建过程,并且能够帮助你的开发团队应对如下挑战:

软件构建自动化(配置完成后,ci会依照预先制定的时间表,或者针对某一特定事件,对目标软件进行构建);

构建可持续的自动化检查(ci能持续的获取新增或修改后签入的源代码,也就是说,当软件开发团队需要周期性的检查新增或修改后的代码时,ci会不断确认这些新代码是否破坏了原有软件的成功构建,这减少了开发者在检查彼此相互依存的代码中变化情况需要花费的时间和精力);

构建可持续的自动化测试(构建检查的扩展部分,构建后执行预先制定的一套测试规则,完成后触发通知给相关责任人);

生成后续过程的自动化(当自动化检查和测试成功完成,软件构建的周期中可能也需要一些额外的任务,如生成文档、打包软件、部署构建到一个运行环境或者软件仓库,这样才能更迅速的提供给用户使用);

 

jenkins的前身叫hudson,使用jenkins的理由:

是所有ci产品中,安装和配置最简单;

基于web访问,用户界面非常友好、直观、灵活,在许多情况下,还提供了ajax的即时反馈;

基于java,但不仅限于构建基于java的软件;

拥有大量的插件,且都开源,极大的扩展了jenkins的功能;

 

jenkins的目标:

监控软件开发流程,快速显示问题,为developer或相关人员省时省力提高开发效率;

ci系统在整个开发过程中的主要作用是控制,当系统在代码存储中探测到修改时,它将运行构建的任务委托给构建过程本身,如果构建失败将通知给相关人员,然后继续监视存储库,它的角色看起来是被动的,但的确能快速反映问题;

 

 

开发流程:

测试:

对项目进行测试以验证代码的运行情况,这样可预防忽略回归regressions,并在某些情况下它可作为一种文档格式,在其中阅读测试代码可告知其它人库中的api工作情况;

常用测试工具nosehttps://wiki.python.org/moin/PythonTestingToolsTaxonomy

TDD测试驱动开发,是敏捷开发中的一项核心实践和技术,也是一种设计方法论,TDD的原理是在开发功能代码之前先编写单元测试用例代码,测试代码确定需要编写什么产品代码;

文档:

与测试不同,可根据项目文档的质量和范围来判断一个项目的质量和工艺,编写和维护文档的方法与编写和维护代码的方法相似,文档写的出色并具有深度,可吸引更多的参与者加入,使用户更接近你的项目;

 

 

部署一个ci系统最低要求:一个可获取的源代码仓库;一个包含构建脚本的项目;

 

ci系统的基本结构:

wKiom1meOVjioGvvAABNF3hDX1k183.jpg

developer检入代码到源代码仓库;

ci系统会为每一个项目创建一个单独的工作区workspace,当预设或请求一次新的构建时,它将把源代码仓库的源码存放到对应的workspace

ci系统会在对应的工作区内执行构建过程;

配置存在的情况下,构建完成后,ci系统会在一个新的构建中执行定义的一套测试,完成后触发通知emailrss给相关责任人;

配置存在的情况下,构建成功后,这个构建会被打包并转移到一个部署目标,如appserver,或存储为软件仓库中的一个新版本;软件仓库可以是ci系统的一部分,也可以是一个外部的仓库,如fileserversourceForgejava.net之类的网站;

ci系统通常会根据请求发起相应的操作,如即时构建、生成报告、或检索一些构建好的构建;

 

 

脚本的好处:

降低开发门槛,提高开发效率;

增强开发的灵活性,更新的便捷性;

减小与主程序的耦合度;

增强代码多用性;

阻碍用户做逆向工程(不用反编译);

 

自动化部署:

纯手工scp

纯手工(git pull;svn update);

纯手工(xftp;rz);

 

缺陷:

全程运维参与,占用大量时间;

上线速度慢;

人为失误多(如rz -y会覆盖,管理混乱);

回滚慢,不及时;

 

运维标准化itil

自动化安装cobbler

监控体系zabbix

配置管理saltstack

自动化代码部署;

日志平台ELK Stack

 

环境规划:

开发环境(开发本地有自己的环境,大家共用的服务(如DBredismemcached));

测试环境(功能测试;性能测试);

预生产环境(生产环境集群中的某一个节点担任,产生的原因(数据库不一致,测试环境和生产环境的数据肯定是不一样的,导致生产部署失败;使用生产环境的联调接口,如支付接口));

生产环境(直接对用户提供服务);

 

预生产环境-->生产环境-->灰度发布;

 

1、规划;

2、实现;

3、总结和扩展(PDCA);

4、在生产环境中应用;

 

shell脚本实现自动化部署:

一个集群有10node,实现一键部署这10node

一键回滚到任意版本;

一键回滚到上个版本;

 

部署:

代码在哪里(svn;git);

获取什么版本代码(直接拉取某个分支(svn;git);指定版本号(svn);指定taggit));

配置文件未必一样(如应用内部的定时任务quartz.xml);

如何更新(tomcat需重启);

测试;

串行,并行(分组部署);

如何执行(bash script.sh;web界面);

 

差异解决:

各节点间差异;

代码仓库和实际的差异,配置文件是否放在代码中;

 

1、获取代码(直接拉取);

2、编译(可选);

3、配置文件;

4、打包;

5scp到目标server

6、将目标server移除集群;

7、解压;

8、放到webapps/下;

9scp差异文件;

10、重启(可选);

11、测试;

12、加入集群;

 

注:

所有的web服务都应该使用普通用户,所有的web服务都不应该监听在80port(普通用户不能启用80port),除LB

 

 

devops是过程、方法、系统的统称,让开发、运维、测试之间沟通的一种文化;

CIcontinuous integration,让我们的软件、构建、测试、发布更敏捷、频繁、可靠,BUILD-->TEST-->RESULT

continuous delivery,持续交付,在CI基础上,将集成后的代码部署到更贴近真实运行环境中(production-like environment),如,在完成单元测试后,可把代码部署到连接数据库的staging环境中,进行更多的测试,若代码没问题,可继续手动部署到生产环境中,(BUILD-->TEST-->RESULT-->TEST-->STAGING-->PRODUCTIONmanual);

continuous deployment,持续部署,在CD的基础上,把部署到生产环境的过程自动化;

 

continuous delivery&continuous deployment区别:最终部署到生产环境是自动化的,BUILD-->TEST-->RESULT-->TEST-->STAGING-->PRODUCTIONauto);

 

java构建(antgradle);

OWASPopen web application security project

 

 

sonar,是一个用于代码质量管理的开放平台,通过插件机制,sonar可集成不同的测试工具、代码分析工具、持续集成工具;

CI工具(jenkins)不同,sonar并不是简单的把不同的代码检查工具结果(findbugsPMD)直接显示在web页面上,而是通过不同的插件对这些结果进行再加工处理,通过量化的方式度量代码质量的变化,从而方便的对不同规模和种类的工程进行代码质量管理;

在对其它工具的支持方面,sonar不仅提供了对IDE的支持,可在eclipseintelliJIDE这些工具里联机查看结果,同时sonar还对大量的CI工具提供了接口支持,可方便的在CI中使用sonarsonar的插件还可对java以外的其它编程语言提供支持,对国际化及报告文档化有良好的支持;

 

sonarqube-5.6.zip

sonar-scanner-2.6.1.zip

sonar-examples-master.zip

 

jenkins上安装SonarQube plugin

 

 

安装示例(2.32.1 version2.70以上版本需jdk1.8以上;tomcat中):

[root@test7 ~]# uname -rm

2.6.32-431.el6.x86_64 x86_64

[root@test7 ~]# cat /etc/redhat-release

Red Hat Enterprise Linux Server release 6.5(Santiago)

 

[root@test7 ~]# java -version

java version "1.5.0"

gij (GNU libgcj) version 4.4.7 20120313(Red Hat 4.4.7-17)

[root@test7 ~]# rpm -qa | grep java

java-1.5.0-gcj-1.5.0.0-29.1.el6.x86_64

libvirt-java-0.4.9-1.el6.noarch

libvirt-java-devel-0.4.9-1.el6.noarch

java_cup-0.10k-5.el6.x86_64

[root@test7 ~]# rpm -e --nodeps java-1.5.0-gcj-1.5.0.0-29.1.el6.x86_64

 

[root@test7 ~]# mkdir /ane

[root@test7 ~]# cd /ane

[root@test7 ane]# tar xf jdk-7u79-linux-x64.tar.gz

[root@test7 ane]# vim /etc/profile.d/java.sh

export JAVA_HOME=/ane/jdk1.7.0_79

export PATH=$PATH:$JAVA_HOME/bin

exportCLASSPATH=.:$JAVA_HOME/lib:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar

exportCATALINA_HOME=/ane/apache-tomcat-7.0.67

export PATH=$PATH:$CATALINA_HOME/bin

[root@test7 ane]# . !$

. /etc/profile.d/java.sh

[root@test7 ane]# java -version

java version "1.7.0_79"

Java(TM) SE Runtime Environment (build1.7.0_79-b15)

Java HotSpot(TM) 64-Bit Server VM (build24.79-b02, mixed mode)

 

[root@test7 ane]# tar xf apache-tomcat-7.0.67.tar.gz

[root@test7 ane]# ln -sv apache-tomcat-7.0.67 tomcat

`tomcat' -> `apache-tomcat-7.0.67'

[root@test7 ane]# cd tomcat/webapps/

[root@test7 webapps]# rm -rf ./*

[root@test7 webapps]# rz   #(上传jenkins.war到该目录下)

 

[root@test7 webapps]# cd /ane

[root@test7 ane]# vim startup.sh

[root@test7 ane]# chmod +x startup.sh

 

[root@test7 ane]# vim tomcat/bin/catalina.sh

export JAVA_OPTS="

……

"

[root@test7 ane]# touch tomcat/logs/catalina.out

[root@test7 ane]# bash startup.sh

……

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

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

……

Apr 20, 2017 12:04:07 AMhudson.model.UpdateSite updateData

INFO: Obtained the latest update centerdata file for UpdateSource default

Apr 20, 2017 12:04:07 AMhudson.WebAppMain$3 run

INFO: Jenkins is fully up and running

 

[root@test7 ane]# cat/root/.jenkins/secrets/initialAdminPassword  #默认安装在运行程序用户的家目录下,如/root/.jenkins/

de00bab6d66a42deafda838e6a9c4d0d

wKiom1meO8PTJl_-AAB5qmitqLk355.jpg

Continue-->选建议配置的插件

wKiom1meO9HAUoDfAACQzhC--8o463.jpg

admin/admin

wKioL1meO9WygghfAABeQKL7gR4984.jpg

wKiom1mePCPRepZiAABYtcVTxec500.jpg

“系统管理”

wKiom1mePDiwlpJkAACA6DvQbkQ082.jpg

注:

解决:Your container doesn't use UTF-8 to decode URLs. If you usenon-ASCII characters as a job name etc, this will cause problems.

[root@test7 ane]# vimtomcat/conf/server.xml

   <Connector port="8080" protocol="HTTP/1.1"

              connectionTimeout="20000"

               redirectPort="8443"

               URIEncoding="UTF-8"/>

 

 

常用插件:

Git plugin

Git Parameter Plug-in

Gitlab Plugin

SSH plugin

AnsiColor  #jenkins console output上显示颜色;

Publish Over SSH   #构建后操作Send build artifacts over SSH

SaltStack plugin

 

注:

使用tomcat自身热部署的插件:

Deploy to container Plugin  #构建后操作Deploy war/ear to a container

WAR/EAR files   #相对于当前项目的工作目录,/root/.jenkins/jobs/dj/

Context path   #是部署到tomcat/webapps下的目录名字,此处写mypro,则会将war包部署到tomcat/webapps/mypro.war

 

注:

#yum -y install salt-api

#vim /etc/salt/master.d/api.conf

#vim /etc/salt/master.d/eauth.conf

 

 

配置SVN

]# mkdir -pv svn/{svndata,svnpasswd}

mkdir: created directory `svn'

mkdir: created directory `svn/svndata'

mkdir: created directory `svn/svnpasswd'

 

]# svnserve -r /ane/svn/svndata/ -d

]# netstat -tnulp | grep :3690

tcp       0      0 0.0.0.0:3690                0.0.0.0:*                   LISTEN      22717/svnserve

 

]# svnadmin create /ane/svn/svndata/dj

]# tree /ane/svn

 

]# cp authz passwd /ane/svn/svnpasswd/

]# vim /ane/svn/svndata/dj/conf/svnserve.conf

[general]

anon-access = none

auth-access = write

password-db = /ane/svn/svnpasswd/passwd

authz-db = /ane/svn/svnpasswd/authz

 

]# vim /ane/svn/svnpasswd/passwd

[users]

root = jowin

dj = dj123

]# vim /ane/svn/svnpasswd/authz

[groups]

djgroup = root,dj

[dj:/]

@djgroup = rw

 

win主机上上传文件;

 

在另一linxu主机checkout:

]# mkdir /ane/dj

]# svn checkout svn://172.168.101.229/dj/ane/dj/ --username=dj --password=dj123

]# ll -h /ane/dj/

total 54M

-rw-r--r-- 1 root root 54M Aug 10 15:36 ROOT(8).war

 

 

 

jenkins操作:

2.70 version

/ane/tomcat/webapps/ROOT.war方式   #已将jenkins.war更名为ROOT.war

额外安装插件:Ansible pluginAnsiColor

wKiom1mePNfiCV59AACfxO_JmTU023.jpg

 新建-->yt,构建一个自由风格的软件项目-->OK

wKiom1mePKHy-v7cAABLI8bTbsI690.jpg

General

wKiom1mePPfh_xONAABS6d6b-50496.jpg

 

源码管理:

wKioL1mePP-wG_Q4AABB5bvzPJw209.jpg

 

构建触发器:

Build periodicallyH 12,20 * * *,每天12点和20点进行周期性的项目构建,无论代码有没有变化;

Poll SCMH/10 * * * *,每隔10分钟根据SCM的版本号进行一次代码检查,如果有更新则checkout新代码,并进行自动构建;

wKiom1mePTfTqDFvAAA-1RcdR54451.jpg 

构建环境:

wKiom1mePUfSa2UaAAAwdFPjd7w877.jpg

 

构建:

wKiom1mePVSTk_z2AABBHA9mxZI692.jpg

 

 

ansible serve上操作:

ansible server到被管理主机作rootanessh认证;

]# mkdir -pv /ane/ansible/{yt,dj}/

]# cd /ane/ansible/yt

]# mkdir -pv roles/{ini,ytcd,ytpda,ytquartz}/{tasks,files,templates,handlers,vars,meta}

]# cd /ane/ansible/dj/

]# mkdir -pv roles/dj/{tasks,files,templates,handlers,vars,meta}

 

]# vim /etc/ansible/hosts

[ytcd]

10.113.128.42

10.113.128.43

[ytpda]

10.113.128.37

10.113.128.38

[ytquartz]

10.113.128.41

[yt:children]

ytcd

ytpda

ytquartz

[dj]

10.113.128.114

 

]# pwd

/ane/ansible/dj

]# vim dj.yml

- hosts: dj

  remote_user: ane

  vars:

    tomcat_root: /ane/tomcat/webapps

    war_file: ROOT.war

    bak_path: /ane/bak

    war_path: /root/.jenkins/workspace/dj

  tasks:

   - name: backup war file

     shell: cp ` tomcat_root `/` war_file ` ` bak_path `/` war_file`_`date +%F_%T`

     ignore_errors: True

   - name: stop tomcat

     shell: pkill -9 -f java

     ignore_errors: True

     tags:

       - stop

   - name: scp war

     copy: src=` war_path `/` war_file ` dest=` tomcat_root `/

   - name: start tocmat

     shell: /bin/bash /ane/startup.sh

     ignore_errors: True

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

补充:

[root@test6 ~]# docker search jenkins

[root@test6 ~]# docker pull jenkins

#docker run -d -p 49001:8080 -v $PWD/jenkins:/var/jenkins_home-t jenkins

$docker ps

$docker attach jenkins_ci

...#vim /etc/default/jenkins

HTTP_PORT=8080

...#/etc/init.d/jenkins start

...#su - jenkins

...$cd ; ls ; rm -rf mypython

...$virtualenv -p /usr/bin/python3.4--no-site-packages python3.4

...$cd python3.4/bin

...$pwd

 

jenkinsweb界面:

系统管理-->管理插件-->可选插件,搜索并安装pythongit

调整用gitproject sentry-->配置(源码管理用Git

调整python环境:

系统管理-->系统设置-->设一个全局变量(键PYTHON,值/var/lib/jenkins/python3.4/bin

 

注:不要用系统自带的python环境,安装的包容易把系统的python环境污染,用pip3 install virtualenv工具把系统的python环境复制出来;

几个项目不要使用同一个python环境,要给每一个项目建一个python环境,这样层次更加分明,包也不易冲突;

 

$pip3 install virtualenv

$mkdir myproject ; cd !$

$virtualenv --no-site-packages venv   #(新python环境放到当前目录的venv下)

$source venv/bin/activate   #(进入python新环境)

...$pip install jinja2

...$deactivate   #(退出当前python环境)

 

 

 

 

 

 

注:

在每个函数中写入echo "code_各自函数的名称",测试该script流程是否有问题;

测试锁时在code_get()函数中加入sleep 60,同时在其它终端执行该script

$CODE_DIR此路径只用于放仓库文件,不能用于其它用途;

包名要重命名,规则:时间+版本号;

 

git pull  #(拉取最新的代码;更新频繁,没有特别严格的项目管理);

git tag  #(获取指定的标签版本;更新不频繁,有一定的项目管理的团队);

获取指定的commit id

 

API_VERL=`git show | grep commit | cut -d '' -f2`

API_VER=`echo ${API_VERL:0:6}`   #(截取前6位)

 

回滚:

方一:

列出回滚版本-->目标服务器移除集群-->执行回滚-->重启&测试-->加入集群;

方二:

列出回滚版本-->执行回滚(重启);

 

 

 

 

#vim deploy.sh

#!/bin/bash

#

 

#shell env

SHELL_DIR="/home/www"

SHELL_NAME="deploy.sh"

SHELL_LOG="{$SHELL_DIR}/${SHELL_NAME}.log"

 

#code var

PROJECT_NAME="web-demo"

CODE_DIR="/deploy/code/$PROJECT_NAME"

CONFIG_DIR="/deploy/config/$PROJECT_NAME"

TMP_DIR="/deploy/tmp"

TAR_DIR="/deploy/tar"

 

LOCK_FILE="/tmp/deploy.lock"

 

CDATE=`date "+%Y-%m-%d"`

CTIME=`date "+%H-%M-%S"`

 

#node list

NODE_LIST="172.17.101.1 171.17.101.2172.17.101.3"

 

usage() {

         echo$"Usage: $0 [deploy|rollback [list|VERSION]]"

}

 

writelog() {

         LOGINFO=$1

         echo"${CDATE} ${CTIME}:${SHELL_NAME}:${LOGINFO}" >> ${SHELL_LOG}

}

 

shell_lock() {

         touch$LOCK_FILE

}

 

shell_unlock() {

         rm-f $LOCK_FILE

}

 

urltest() {

         url=$1

         curl-s --head $url | grep "200 OK"

         if[ $? -ne 0 ];then

           writelog "test error" &&exit;

         fi

}

 

code_get() {

         echo"code_get"

         #sleep60

         writelog"code_get"

         cd$CODE_DIR && git pull

         cp-r $CODE_DIR $TMP_DIR

         API_VERL=`gitshow | grep commit | cut -d ' ' -f2`

         API_VER=`echo${API_VERL:0:6}`

 

}

 

code_build() {

         echo"code_build"

}

 

code_config() {

         echo"code_config"

         writelog"code_config"

         /bin/cp-r ${CONFIG_DIR}/* ${TMP_DIR}/"${PROJECT_NAME}"

         PKG_NAME="${PROJECT_NAME}_"$API_VER"_"${CDATE}_"${CTIME}"

         cd${TMP_DIR} && mv ${PROJECT_NAME} $PKG_NAME

}

 

code_tar() {

         echo"code_tar"

         writelog"code_tar"

         cd${TMP_DIR} && tar zcf ${PKG_NAME}.tar.gz ${PKG_NAME}

         write_log"${PKG_NAME}.tar.gz"

}

 

code_scp() {

         echo"code_scp"

         fornode in $NODE_LIST;do

                   scp${TMP_DIR}/${PKG_NAME}.tar.gz $node:/opt/webroot/

         done

}

 

cluster_node_remove() {

         echo"cluster_node_remove"

         wirtelog"cluster_node_remove"

          

}

 

code_deploy() {

         echo"code_deploy"

         writelog"code_deploy"

         fornode in $NODE_LIST;do

           ssh $node "cd /opt/webroot && rm-f /webroot/* && tar zxf ${PKG_NAME}.tar.gz"

         done

         scp${CONFIG_DIR}/other/crontab.xml172.17.101.1:/opt/webroot/${PKG_NAME}/crontab.xml

         ln-s /opt/webroot/${PKG_NAME} /webroot/web-demo

}

 

config_diff() {

         echo"config_diff"

}

 

code_test() {

         echo"code_test"

         urltest"http://172.17.101.1/index.html"

}

 

cluster_node_in() {

         echo"cluster_node_in"

}

 

rollback_fun() {

         fornode in $ROLLBACK_LIST;do

           if [ -d /opt/webroot/$1 ];then

             ssh $node "rm -f /webroot/web-demo&& ln -s /opt/webroot/$1 /webroot/web-demo"

           fi

         done

}

 

rollback() {

         echo"rollback"

         if[ -z $1 ];then

           shell_unlock

           echo "hehe" && exit

         fi

         case$1  in

                   list)

                     ls -l /opt/webroot/*.tar.gz

                   ;;

                   *)

                     rollback_fun $1

         esac

}

 

main() {

  if[ -f $LOCK_FILE ];then

   echo "Deploy is running" && exit;

  fi

 DEPLOY_METHOD=$1

 ROLLBACK_VER=$2

         case$DEPLOY_METHOD in

                   deploy)

                            shell_lock;

                            code_get;

                            code_build;

                            code_config;

                            code_tar;

                            code_scp;

                            cluster_node_remove;

                            code_deploy;

                            config_diff;

                            code_test;

                            cluster_node_in;

                            shell_unlock;

                   ;;

                   rollback)

                            shell_lock;

                            rollback$ROLLBACK_VER;

                            shell_unlock;

                   ;;

                   *)

                            usage;

         esac

}

 

main $1 $2

 

 


本文转自 chaijowin 51CTO博客,原文链接:http://blog.51cto.com/jowin/1958920,如需转载请自行联系原作者

相关文章
|
2月前
|
监控 数据可视化 jenkins
Jenkins是什么
【10月更文挑战第18天】Jenkins是什么
38 2
|
2月前
|
运维 安全 jenkins
Jenkins适合哪些场景
【10月更文挑战第18天】Jenkins适合哪些场景
|
3月前
|
jenkins 持续交付
|
JSON jenkins 持续交付
jenkins安装
jenkins安装
153 0
|
存储 jenkins 持续交付
jenkins-1
jenkins-1
80 1
|
jenkins 应用服务中间件 持续交付
jenkins-2
jenkins-2
75 1
|
存储 jenkins 持续交付
Jenkins-1
Jenkins-1
57 0
|
jenkins 应用服务中间件 持续交付
|
jenkins Java Shell
Jenkins ——你很神气
Jenkins ——你很神气
88 0
|
jenkins Java 持续交付
Jenkins 使用总结
Jenkins 使用总结
145 0