jenkins 自动化部署实战
jenkins 作为一个自动化的集成工具,已经是必不可少的了。它里面提供各种插件,以及完备的基础流程设施,为大家的自动化集成之路提供了很多的方便。所以,我们有必要完整的实践一回。以切身体会到它的好处!
一、 操作步骤说明陈列
1. 下载jenkins包,并安装;(两种方案,基于物理机和基于docker,视情况而定建议使用docker)
2. 安装后,初始化jenkins,基本都是下一步下一步搞定,保持默认是比较稳妥的选择;
3. 订立目标,需要完成什么的场景下的打包功能? git ? subversion? 或者其他 ?
4. 选择此场景需要的必要插件安装,并进行简单调试;
5. 新建一个任务,尝试使用工具完成一次简单的打包操作;
6. 完善一个任务,使其可以支持一些简单的参数定制,如分支的选择;
7. 打包完成后,编写部署脚本,使其可以一键部署;
8. 设置权限管理,使不同环境的包由不同级别的同学进行操作;
9. 进行反复功能验证,及权限验证;
10. 备注所有的操作流程,以便在进行下次重新安装时,能够随时搞定;
二、具体实践操作
简单的安装和初始化,咱们就不多说了,网上一大堆的信息,随便找一个就能搞定,不找也能搞定,请参考官网: https://wiki.jenkins.io/display/JENKINS/Use+Jenkins
打包场景一: 如何打包 maven 管理的java项目 ?
其实要打包这种项目比较简单,只要安装一个 maven integeration 的插件即可!如下:
安装好后,新建一个 maven 任务即可!
其实maven任务和其他任务不太一样的地方是,它会出现一个 pom.xml 的字样,其实就是自己去加载这个配置文件,然后自行调用 maven 进行打包。而如果想要更灵活,也是可以的,只需要使用 系统的命令进行自由构建即可!
goal 里可写: clean install | package
经过上面的操作后,你可能会发现,其实并不会打包成功,为什么呢?因为我们还没有安装 maven 的工具到系统上,或者说没有向 jenkins 解释 maven 在哪里,所以需要到全局工具配置一下!
另外,如果你是需要打包 git 类的代码的话,一般只需要进行简单授权就可以了!
到此,一个 简单的 maven java 项目打包就成功了!
但是,其实,打包到这个程度,其实对我们的环境部署一点更多的意义都没有,所以我们需要更深的定制化!咱们稍后再说!
先来说说,另一种项目的打包方式, h5 代码的打包。 更具体的讲,就是 基于 nodejs 的打包实现!
首先,我们当然需要安装 node js 的插件了!
同样,该插件安装好后,还要安装 nodejs 的软件!可以直接选择自动 安装 ! 注意要求的版本!
安装好后,新建一个自由风格的任务!
进入,把 node 的环境支持勾选上!然后定稿node 的打包命令!
同样,git 相关的操作一样!
至此,简单的 node 版本的代码打包功能也做好了。
同样,只是进行打包,无法定制化的东西,对我们毫无意义,所以,我们需要更高级的定制 jenkins !
例外:如果有依赖一些私有的包,那么,你可以选择安装私服,或者用如下方式解决:
# for local repository dependency... ln -sf /var/lib/jenkins/pkg-bak/testmodule/ $WORKSPACE/node_modules/testmodule npm install npm run prod
三、 定制你的 jenkins 功能!
场景一: 我需要为一个git 仓库的多个分支打包?
这个,简单, jenkins 中一添加定制化参数的功能,只需要添加一个文本参数就可以 了!
在 git 分支一栏,写入 $branch 替换即可!
如此,一个简单的支持 git 多分支打包的功能就做好了!
但是:每次都让我自己输入分支,好烦啊! 而且,我自己也不一定知道有什么样的分支,这可怎么办?
借助 groovy 脚本,可以动态拉取 分支列表,进行显示了!
简单来说就是,执行一些shell脚本 , 从 git 远端列举出分支,给到列表选项! groovy 脚本如下:
def git_url = "https://github.com/yougewe/elastic-job-lite" def git_cmd = "/usr/local/git/bin/git ls-remote -h $git_url | grep -oP '(?<=refs/heads/).*' " branch = ['bash', '-c', git_cmd].execute().text.readLines()
好了,分支的选择,看起来我们已经解决得差不多了!
但是,前端有个点,就是这分支的触发问题,其实我们可以更动态方式实现!
场景二: 我需要支持多个仓库进行切换打包,当然这里的仓库将必然影响到上面分支列表的选择!
可以使用 Active Choice 这个插件,可以让我们在需要的时候触发一次分支的选择,比如支持 分支快速过滤!达到数据联动的效果;
然后,也可以使分支依赖于动态的仓库进行变更分支列表等等!总之, Active Choice 插件真是很方便发挥!
最终的效果就是,我们可以选择,多仓库,动态列表多分支,进行打包!
总之,通过这个联动支持,以及简单groovy知识,你可以有很大的发挥空间;
场景三: 我需要将打好的包部署到指定服务器上?
这是个关键的问题,就是我们打包来的包,应该处理的问题!
这里有两个核心问题: 1. 我如何找到打包后的东西? 2. 如何将找到的东西部署到线上?
如果不能很好的处理这些包,我们将很无奈!所以 shell 就出场了!
jenkins 支持在打包后进行一些自定义的操作! 比如 上传包到另外的服务器,或者部署到另外的地方等等!总之,就是任你发挥!
一般地说,查找打包后的东西,要依赖于你的代码实现,所以具体问题具体分析;当然了,你可以通过mvn等命令直接订制打包的各种配置,从而活动查找的麻烦;
另外,对于部署到服务器,至少有两个解决方案:1. 直接通过scp等服务器拷贝技术,将包放到目标服务器即可; 2. 将包打包成可执行的安装包,上传yum源服务器,在使用端直接拉取镜像即可;
比如如下的脚本 ,我们可以将它部署到我们指定服务器的 docker 容器中!
参考脚本如下:
echo "hello, build over ??? would you scp or deploy apps ??? work dir: $WORKSPACE , repository: $spec_repository;" # specify dev environment or product environment deploy_env_server="root@172.10.11.16" deploy_path_prefix="/opt/docker/webapps/test" docker_app_startup_script="/etc/init.d/startup restart"; case "$spec_repository" in richCash) docker_container_name="test_container"; docker_app_startup_script="/etc/init.d/startup restart"; deploy_path_prefix="/opt/docker/webapps/"; deploy_sub_path="$deploy_environment" case "$deploy_environment" in "test-p8081") docker_container_name="test_p8081_container" ;; docker_container_name="test_p8083_container" ;; *) # default container ;; esac deploy_path_prefix="/opt/docker/webapps/$deploy_sub_path"; ;; test2-01) deploy_path_prefix="/opt/docker/webapps/test2-01"; docker_container_name="test2_01_container"; docker_app_startup_script="/etc/init.d/startup restart"; ;; *) echo "not find special configures in $spec_repository, do the default thing"; ;; esac # deploy script deploy_script_path="ssh $deploy_env_server \"docker exec $docker_container_name bash -c '$docker_app_startup_script'\""; pom_file="$WORKSPACE/pom.xml"; pom_file_tmp="$pom_file\.tmp"; cd $WORKSPACE; cat $pom_file | awk '{if($0 ~ /<parent>/){parent_begin++; print "no dump parent"} else if(parent_begin > 0){ if($0 ~ /<\/parent>/){parent_begin = 0; print "match the parent end.";} else {print "hit parent dump"; }} else { print $0;}}' > $pom_file_tmp package_artifactId=`grep "<artifactId>" $pom_file_tmp | head -n 1 | sed -e 's/\(\s*<artifactId>\|<\/artifactId>\s*\)//g'`; package_version=`grep "<version>" $pom_file_tmp | head -n 1 | sed -e 's/\(\s*<version>\|<\/version>\s*\)//g'`; package_full_name="$package_artifactId-$package_version.jar" # 定义为jar包写死 package_full_path="$WORKSPACE/target/$package_full_name"; echo "packaged file is: $package_full_name"; scp $package_full_path $deploy_env_server:"$deploy_path_prefix/$package_full_name"; # restart the server... eval $deploy_script_path;
如上,是对 jar 包的部署方式,我们再来看下 h5 包的部署方式:
echo "hello, build over ??? work dir: $WORKSPACE , repository: $spec_repository; branch: $branch ;" # specify dev environment or product environment deploy_env_server="root@172.11.1.12" docker_container_name="web_container"; deploy_path_prefix="/opt/docker/webapps/"; deploy_sub_path="$deploy_environment" deploy_path_prefix="$deploy_path_prefix""$deploy_sub_path" deploy_package_bak_path="/opt/docker/webapps/bak/" docker_app_startup_script="ls " case "$deploy_environment" in "web-p81") docker_container_name="web_81_container" ;; *) # default container ;; esac datetime=`date '+%Y%m%d%H%M'`; package_full_name="$spec_repository-$branch-$datetime.tar.gz" package_full_origin_path_prefix="/var/lib/jenkins/tmppackages/h5" package_full_origin_path="$package_full_origin_path_prefix/$package_full_name"; deploy_package_bak_path="$deploy_package_bak_path""$package_full_name" # deploy script deploy_script_path="ssh $deploy_env_server \"rm -rf $deploy_path_prefix/*; if [ ! -d $deploy_path_prefix ]; then mkdir -p $deploy_path_prefix; fi; tar -xzf $deploy_package_bak_path -C $deploy_path_prefix\""; package_dist_origin_path="$WORKSPACE/dist"; cd $package_dist_origin_path; tar -czf $package_full_origin_path -C $package_dist_origin_path .; echo "packaged file is: $package_full_origin_path"; scp $package_full_origin_path $deploy_env_server:"$deploy_package_bak_path"; # restart the server... eval $deploy_script_path;
如上,h5 的打包方式,区别在于需要手动打包一为 gz 包,然后到目标机器进行解压缩即是安装!
如上,就可以进行一键部署测试环境了!
剩下的,就是自己自由发挥了!
同理于 h5 的部署方式!
四、重难点提示
其实,准确来说,上面的操作都比较常规,因为都是最终的结果步骤展示!但是其实这其中有很多的坑,期望提示对你有帮助!
1. 使用groovy 进行分支获取时, 需要对 git 命令进行免密授权操作,否则将永远拉不到分支;
2. 使用scp, scp 这些命令调用远程,需要免密登录,否则授权失败;
3. 使用 ssh xx@1.2.2.4 "ls ." 运行远程命令如同本地命令;
4. 真正的部署脚本可以远程服务器上,也可以写在部署机器上;
5. 出于安全考虑,默认情况下运行jenkins的用户都不是root,所以一般不要以为在terminal操作正常的命令,在jenkins上也能正常;
五、jenkins带来的切实好处
为什么一定要用jenkins?难道就因为其名声大燥吗?其实是是因为它真的能带来这些好处:
1. 部署速度指数级提升,以前按照个人的操作速度,要10分钟左右的操作且要记忆多种复杂关联命令,现在可能只要2分钟一键搞定,爽不爽?
2. 部署信息有迹可查,如果按照之前的部署,谁部署出了问题,完全不知道,但是jenkins上有操作记录,更易发现问题;
3. 解决了系统兼容性问题引发的不适,之前各自部署来源不一致,可能导致各种难查问题,而用jenkins则保证完全一致;
4. 拒绝不可靠错误代码的上线,借助于jenkins的单元测试功能,可以发现在一些简单代码的问题,避免之前的误操作上线;
5. 因为收口部署方式,对于后续新的部署方案,有了更加有利的支持,比如想加入sonar代码扫描,只需修改下部署部署脚本即可;
附:
Q: jenkins 使用插件进行部署,调用运行shell脚本不成功?
nohup 运行 java 程序,被关掉后无法重启。可能原因是 该进程尚未被挂到当前会话,就被kill或者退出。解决办法是加上适当延迟。
# 该进程可能启动缓慢 nohup java -jar a.jar & #sleep 延迟可以处理异步问题 sleep 3 echo " start over, new pid: $! !!!"
Q: 有哪些方便的插件推荐?
Publish On SSH & SSH Slaves, 方便进行远程部署。
Rebuilder, 方便进行相同参数的部署重新执行, rebuild 即可,避免反复选择参数信息。
Active Choice, 下拉框,可以触发脚本执行,方便进行动态响应,而不是全静态的参数。
Timestamper, 可以获取时间参数,以便进行日志类记录;
user build vars plugin, 获取各种系统变量;
Workspace Cleanup Plugin, 空间清理插件,避免jenkins占用太多空间;
一句话总结:有时知道问题比解决问题更重要!