[TOC] ## 一、环境准备 本说明是针对使用需要Java 8的Jenkins发行版。还建议使用超过512MB RAM的系统,并且在安装jenkins的服务器先装好以下环境 1. Java 8(JRE或JDK) 2. git 3. gradle/maven 4. 1G可用内存 5. 40GB +可用磁盘空间 ## 二、下载Jenkins [http://mirrors.jenkins.io/war/latest/jenkins.war](http://mirrors.jenkins.io/war/latest/jenkins.war) ## 三、创建启动文件`start.sh` ~~~ nohup java -Dhudson.util.ProcessTree.disable=true -jar jenkins.war --httpPort=8080 >> nohup.out 2>&1 & ~~~ >[info] \-Dhudson.util.ProcessTree.disable=true 参数意思为:禁止Jenkins在Job构建过程结束后认为将kill掉未执行完的子进程 > [https://wiki.jenkins.io/display/JENKINS/ProcessTreeKiller](https://wiki.jenkins.io/display/JENKINS/ProcessTreeKiller) ## 四、启动和访问 * 启动 ~~~ sh start.sh ~~~ * 访问 [http://192.168.28.134:8080](http://192.168.28.134:8080/) ## 五、插件安装 ### 1\. 首次登录时候的插件安装 进入选择插件安装界面,选择第一个(Install suggested plugins) ![](https://img.kancloud.cn/d5/a0/d5a0dbc70ec27de32a0faed871d229c8_1565x770.png) ### 2\. 另外的推荐安装的插件 进入`系统管理 -> 插件管理 -> 可选插件` * [Blue Ocean](https://wiki.jenkins-ci.org/display/JENKINS/Blue+Ocean+Plugin):pipeline流水线的增强插件 * [Gitee Plugin](https://wiki.jenkins.io/display/JENKINS/Gitee+Plugin):支持码云的插件 * [Extended Choice Parameter Plug-In](http://wiki.jenkins-ci.org/display/JENKINS/Extended+Choice+Parameter+plugin):参数化构建参数扩展 ## 六、全局工具配置 * Maven 配置 配置`maven`的`settings.xml`路径 ![](https://img.kancloud.cn/86/ed/86ed3d2bfd7f85c3c4bc0c55d430440c_1890x493.png) * JDK 配置 ![](https://img.kancloud.cn/cf/a1/cfa1ff250fb871c0a2ad6cbaa566412c_675x351.png) * Git 配置 ![](https://img.kancloud.cn/f8/2a/f82a872d31292147d3e2520181df97a4_768x331.png) * Maven 配置 ![](https://img.kancloud.cn/3d/92/3d92086920b4461154ad6815b82638da_824x353.png) ## 七、安全配置 ### 1\. 配置匿名可读权限 ![](https://img.kancloud.cn/ed/83/ed83f0ca2ffbbe9c9b7402ff18d8a819_1822x879.png) ### 2\. 取消跨站请求伪造保护 ![](https://img.kancloud.cn/6d/93/6d93b75acda4d3d6c84e0dc08db44d6a_1368x832.png) ## 八、Jenkins中无法启动子进程的解决办法 * **场景如下**: 在Jenkins中新建了一个Job,假设你在一些列Build Step之前/之后,启动了一个进程,打个比方说启动一个**Jboss**进程。等到Build完成,你去Console Output中查看显示启动成功,甚至PID也有了。但是当你去后台查看的时候,发现其实这个进程根本不存在,并没有启动成功。因为它没有关闭继承的文件描述符,Jenkins在Job构建过程结束后认为Jboss进程未终止,因而将其kill掉了。 * **解决办法**: 启动时添加禁用参数 ~~~ nohup java -Dhudson.util.ProcessTree.disable=true -jar jenkins.war --httpPort=8080 >> nohup.out 2>&1 & ~~~ ## 九、ssh免密登录 ### 1\. 说明 在centos里配置免密登录之后,方便Jenkins通过ssh连接服务器操作 ### 2\. 获取本地客户端机器的公钥 2.1 先看看是不是已经有了,如果有内容就忽略第二步 ~~~ cat ~/.ssh/id_rsa.pub ~~~ 2.2 如果上一步没有这个文件我们就创建一个,运行下面命令生成(邮箱改成自己的),一路回车就好了 ~~~ ssh-keygen -t rsa -C "youremail@example.com" ~~~ ### 3\. 把生成的公钥发送到对方的主机上去 用ssh-copy-id命令,自动保存在对方主机的/root/.ssh/authorized\_keys文件中去,ip地址改成需要免密登录的服务器地址 ~~~ ssh-copy-id -i /root/.ssh/id_rsa.pub root@192.168.28.130 ~~~ ### 4\. 测试 ~~~ ssh 192.168.28.130 ~~~ ### 5\. 免密登录不生效问题解决 一般来讲失败的原因有两个:目录文件的权限和目录的属主。 5.1 目录文件的权限 .ssh父目录的权限是755(我的是/root),.ssh目录权限是700,authorized\_keys文件 600 ~~~ chmod 755 /root chmod 700 /root/.ssh chmod 600 /root/.ssh/authorized_keys ~~~ 5.2 目录的属主 如果上面方法还没有解决问题,那可能是.ssh父目录的属主存在问题。 ~~~ [root@node1 ~]# ls -al .ssh total 12 drwx------ 2 root root 80 Nov 3 10:07 . drwxr-xr-x. 11 hdfs users 4096 Nov 3 09:38 .. -rw------- 1 root root 0 Nov 3 10:06 authorized_keys -rw------- 1 root root 1679 Nov 3 09:38 id_rsa -rw-r--r-- 1 root root 390 Nov 3 09:38 id_rsa.pub -rw-r--r-- 1 root root 0 Nov 3 10:07 known_hosts ~~~ 这里发现.ssh父目录(..)的属主存在问题。 修改如下 ~~~ [root@node1 ~]# chown root:root /root [root@node1 ~]# ls -al .ssh total 16 drwx------ 2 root root 80 Nov 3 10:07 . drwxr-xr-x. 11 root root 4096 Nov 3 09:38 .. -rw------- 1 root root 1135 Nov 3 10:11 authorized_keys -rw------- 1 root root 1679 Nov 3 09:38 id_rsa -rw-r--r-- 1 root root 390 Nov 3 09:38 id_rsa.pub -rw-r--r-- 1 root root 0 Nov 3 10:07 known_hosts ~~~ ## 十、流水线简单例子 ### 1\. 介绍 Jenkins从根本上讲是一种支持多种自动化模式的自动化引擎。Pipeline在Jenkins上添加了一套强大的自动化工具,支持从简单的连续集成到全面的连续输送Pipeline的用例。通过建模一系列相关任务,用户可以利用Pipeline 的许多功能: * 代码:Pipeline以代码的形式实现,通常被检入源代码控制,使团队能够编辑,审查和迭代其传送流程。 * 耐用:Pipeline可以在计划和计划外重新启动Jenkins管理时同时存在。 * Pausable:Pipeline可以选择停止并等待人工输入或批准,然后再继续Pipeline运行。 * 多功能:Pipeline支持复杂的现实世界连续交付要求,包括并行分叉/连接,循环和执行工作的能力。 * 可扩展:Pipeline插件支持其DSL的自定义扩展 以及与其他插件集成的多个选项。 ### 2\. 新建任务 新建任务 -> 流水线 #### 2.1 General 通常情况下默认就行了 ![](https://img.kancloud.cn/24/82/2482e847d1570f31dc28abea5320b80c_1647x903.png) 如果需要参数化构建 ![](https://img.kancloud.cn/96/ac/96ace6927c3cfd9cb29a25f797282cb6_1656x5124.png) #### 2.2 构建触发器 如果需要通过git触发构建的话需要勾选`触发远程构建`和填写`身份验证令牌` 详细的git触发配置请看[webhook配置](./Gitlab代码仓库.md) ![](https://img.kancloud.cn/27/02/2702f976b229653f0b04eed1b33e75e6_1465x839.png) #### 2.3 高级项目选项 不需要填 #### 2.4 流水线 2.4.1 选择`Pipeline script` 2.4.2 本项目演示环境的pipeline脚本 > 下面的脚本仅供参考 > 是建立在目标服务器和git都已经做了**免密登录**的环境下 通常情况下 ~~~ pipeline { agent any environment { REPOSITORY="https://192.168.28.134/xxx/test.git" t_dir="/opt/application/test" t_ssh="root@10.80.98.111" api_url="10.80.98.111:9999" sonar_url="10.80.98.111:9000" } stages { stage('获取代码') { steps { echo "start fetch code from git:${REPOSITORY}" deleteDir() git "${REPOSITORY}" } } stage('代码静态检查') { steps { echo "start code check" sh "mvn clean compile sonar:sonar -Dsonar.host.url=http://${sonar_url}" } } stage('单元测试') { steps { echo "start test" sh "mvn test" junit '**/target/surefire-reports/TEST-*.xml' } } stage('打包') { steps { echo "start package" sh "sed -i 's/127.0.0.1:9999/${api_url}/g' frontend/src/main/resources/static/module/apiUrl.js" sh "mvn package -Dmaven.test.skip=true" } } stage('部署') { steps { echo "start deploy" sh "ssh ${t_ssh} rm -f ${t_dir}/*.jar" script { def apps = ['auth-server', 'user-center', 'gateway-zuul', 'job/job-admin', 'oss-server' , 'log-server', 'code-generator', 'frontend'] for (int i = 0; i < apps.size(); ++i) { sh "scp ${apps[i]}/target/*.jar ${t_ssh}:${t_dir}" } } } } stage('重启') { steps { echo "start restart" script { def apps = ['auth-server', 'user-center', 'gateway-zuul', 'job/job-admin', 'oss-server' , 'log-server', 'code-generator', 'frontend'] for (int i = 0; i < apps.size(); ++i) { sh "ssh ${t_ssh} 'cd ${t_dir};sh shutdown.sh ${apps[i]}'" if ("${apps[i]}" == 'sc-admin') { sleep 60 } else { sleep 30 } sh "ssh ${t_ssh} 'cd ${t_dir};sh start.sh ${apps[i]}'" } } } } } post { always { echo '构建结束...' } success { echo '恭喜您,构建成功!!!' } failure { echo '抱歉,构建失败!!!' } unstable { echo '该任务已经被标记为不稳定任务....' } changed { echo '' } } } ~~~ 如果需要参数化构建 >[info] 脚本里的参数`service_names`和`project_version`就是通过构建时传进去的 ~~~ pipeline { agent any environment { git_addr="https://192.168.28.134/xxx/test.git" target_dir="/opt/application/test" target_ssh="root@10.80.98.111" api_url="10.80.98.111:9999" sonar_url="10.80.98.111:9000" backup_dir="/opt/app/backup" } stages { stage('获取代码') { steps { echo "start fetch code from git:${git_addr}" deleteDir() git "${git_addr}" } } stage('代码静态检查') { when { environment name:'is_test', value:'是' } steps { echo "start code check" sh "mvn clean compile sonar:sonar -Dsonar.host.url=http://${sonar_url}" } } stage('单元测试') { when { environment name:'is_test', value:'是' } steps { echo "start test" sh "mvn test" junit '**/target/surefire-reports/TEST-*.xml' } } stage('打包') { steps { echo "start package" sh "sed -i 's/127.0.0.1:9999/${api_url}/g' frontend/src/main/resources/static/module/apiUrl.js" sh "mvn package -Dmaven.test.skip=true" } } stage('部署') { steps { echo "start deploy" sh ''' if [ ! -d "${backup_dir}/${project_version}" ];then mkdir -p ${backup_dir}/${project_version} fi ''' script { def apps = "${service_names}".split(",") for (int i = 0; i < apps.size(); ++i) { if ("${apps[i]}" == 'auth-server') { service_path = '' } else { service_path = '*/' } sh "rm -f ${backup_dir}/${project_version}/${apps[i]}.jar" sh "cp "+service_path+"${apps[i]}/target/*.jar ${backup_dir}/${project_version}/" sh "ssh ${target_ssh} rm -f ${target_dir}/${apps[i]}.jar" sh "scp "+service_path+"${apps[i]}/target/*.jar ${target_ssh}:${target_dir}" } } } } stage('重启') { steps { echo "start restart" script { def apps = "${service_names}".split(",") for (int i = 0; i < apps.size(); ++i) { sh "ssh ${target_ssh} 'cd ${target_dir};sh shutdown.sh ${apps[i]}'" if ("${apps[i]}" == 'sc-admin') { sleep 60 } else { sleep 30 } sh "ssh ${target_ssh} 'cd ${target_dir};sh start.sh ${apps[i]}'" } } } } } post { always { echo '构建结束...' } success { echo '恭喜您,构建成功!!!' } failure { echo '抱歉,构建失败!!!' } unstable { echo '该任务已经被标记为不稳定任务....' } changed { echo '' } } } ~~~ 2.4.3 目标服务器的`shutdown.sh`内容 ~~~ pid=`ps ax | grep -i $1 |grep java | grep -v grep | awk '{print $1}'` if [ -z "$pid" ] ; then echo "No $1 running." fi echo "The $1(${pid}) is running..." kill ${pid} echo "Send shutdown request to $1(${pid}) OK" ~~~ 2.4.4 目标服务器的`start.sh`内容 ~~~ nohup java -Dspring.profiles.active=test -Dpigframe.nacos.server-addr=10.80.98.111:8848 -jar -Xmx256M -Xms256M $1.jar >> /dev/null 2>&1 & ~~~