Jenkins搭建经验

这是开箱即用的搭建Jenkins的手册,本文内所有操作都是我实践过后的记录。

安装

  • Jenkins是一款流行的开源持续集成(Continuous Integration)工具,广泛用于项目开发,具有自动化构建、测试和部署等功能。可以解决测试环境不可控,开发测试环境不一致等问题。正好可以弥补我司在这块的工作空白,有助于提升效率,提高容错、减少冲突。
  • 注意,我在服务器上,都是以root权限在活动的,所以可能有一些操作要root权限,但我没有发现。
  • 我在本机测试过一次Jenkins部署,所以下面很多时候是通过localhost访问;部署到服务器上后,就改为服务器ip(或者域名)来访问。

配置环境

  • Jenkins本身是Java开发的,所以一个Java环境是必须的(也就是JDK的安装)

    1. 如果服务器可以直接连外网,就可以直接在服务器下载,否则先下载到本地再用scp上传服务器。相关命令:wget [url]就可以将对应的资源下载到当前目录下。
    2. 解压rpm -ivh jdk-8u172-linux-x64.rpm
    3. 测试java -version注意连字符只有一个。
    4. 如果上一步不通过,就添加一下PATH变量,在/etc/profile中:

      1
      2
      3
      4
      5
      6
      # set java environment
      # jdk的路径根据自己的来写,版本是本文当时选择的
      JAVA_HOME=/usr/java/jdk1.8.0_111
      CLASSPATH=.:$JAVA_HOME/lib.tools.jar
      PATH=$JAVA_HOME/bin:$PATH
      export JAVA_HOME CLASSPATH PATH
    5. source /etc/profile让配置生效,并且再次通过java -version命令检查。

  • maven环境(如果需要构建Java项目)

    1. 下载
    2. 解压tar -zxvf apache-maven-3.5.3-bin.tar.gz
    3. 环境变量的配置,在/etc/profile中:

      1
      2
      3
      4
      5
      # set maven environment
      # 路径根据自己的来写,版本是本文当时选择的
      MAVEN_HOME=/usr/local/apache-maven-3.5.3
      export MAVEN_HOME
      export PATH=${PATH}:${MAVEN_HOME}/bin
    4. source /etc/profile后通过mvn -v来检测是否生效

安装Jenkins

  1. 官网上有丰富的安装文档
  2. 最简单的运行.war file的方式:
    1. 下载war版本,到官网地址下面去找“Generic Java package(.war)”下载
    2. 在下载之后的目录里,直接通过命令java -jar jenkins.war运行起来,然后就可以通过localhost:8080看到了,还可以通过参数--httpPort=[port]来选择非默认端口(8080)而在其他端口号上跑起来。
    3. 如果使用nohub [shell order] &的话,可以让这个命令脱离终端,Jenkins在后台保持运行。终端的输出会被保存在当前目录下的pso.file,可供日后查看。
  3. 把war文件部署到servlet容器里的方式:

    1. 区别于war自己起服务,就是多一个jenkins/的路径而已,比如localhost:8080/jenkins这样子。比如我们可以使用Tomcat。
    2. 先去下载Tomcat,在官网左边的导航栏去找“download”,我下载的是最新的9.0的.tar.gz包
    3. 一样,通过tar命令解压,之后需要修改一些Tomcat的配置
    4. 进入tomcat所在文件夹(就是上面解压出来的),然后去conf/server.xml文件里确认一下端口号(有待补充,我这次看了一下,感觉没啥问题就放着不管了),比如你是这样的:

      1
      2
      3
      <Connector port="8080" protocol="HTTP/1.1"
      connectionTimeout="20000"
      redirectPort="8443" />

      这里的port就是你将要起的服务的端口了,也可以随意修改为别的空闲端口。

    5. 接下来要增加tomcat用户,同样进入tomcat文件夹,在conf/tomcat-users.xml文件里,在<tomcat-users></tomcat-users>之间增加:

      1
      2
      3
      4
      5
      6
      7
      <role rolename="admin-gui"></role>
      <role rolename="admin-script"></role>
      <role rolename="manager-gui"></role>
      <role rolename="manager-script"></role>
      <role rolename="manager-jmx"></role>
      <role rolename="manager-status"></role>
      <user username="admin" password="admin" roles="manager-gui,manager-script,manager-jmx,manager-status,admin-script,admin-gui"/>
    6. 进入tomcat文件夹,然后在webapps/文件夹下,把下载的jenkins.war文件丢进去

    7. 进入tomcat文件夹,在bin/目录下,可以看到tomcat的运行命令startup.sh和停止命令shutdown.sh。在Windows系统下,就不是 .sh 而应该是 .bat 文件了。
    8. 启动服务后,可以通过localhost:8080/jenkins来访问到服务。

配置并使用Jenkins

  1. 访问Jenkins并开始使用
    1. 安装后之后,访问服务,输入密码(之前会给出),Jenkins会询问你怎么安装插件,因为是第一次使用,我就使用社区推荐的插件了。Jenkins不管在哪里启动,它都会在~/.jenkins下去安装它需要的东西,比如在这里的plugins文件夹里就存放了选择的插件
    2. 之后是设置第一个admin。另外,通过修改访问Jenkins的URL,简单的增加restart的path就可以很重启Jenkins,特别方便。比如localhost:8080/jenkins/restart
    3. 设置Git权限:在个人电脑上我们都是配置SSH Key,在服务器上也可以给Jenkins做同样的配置,这里推荐下面的方法,可视化更易操作。另外,关联的Git库最好能提供一个对全部项目有READ权限没WRITE权限的帐号
    4. 设置Credentials - System - Add domain,添加ssh,或者username和password。之后在job里可以选择设置好的Credentials下的某个domain。
  2. 按需选择类型,新建任务开始构建。(我司其他部门现有的Jenkins做的略烂,一个分支一个环境一个job,这完全无法体现Jenkins的优势,而且浪费workspace的存储空间。我一开始还以为都是poll SCM模式的,结果还是手动的。这算一个反例,引以为戒。)
  3. 设置丢弃构建的规则:为了服务器的健康还是推荐加上,Jenkins在每一次的执行构建后,都会对该构建的项目生成一个历史构建记录以及生成一份历史构建的项目发布包,如果不管,最终的结果就是磁盘被占用的空间越来越大,直至磁盘空间被占用完为止,如果没有再可以被写入的空间,其它软件也就无法正常运行了。配置的时候记得点开“高级”选项。
  4. 配置参数化构建选项:Jenkins本身支持参数被传递进入构建脚本,在脚本里通过$param的形式来引用。另外,还有一些默认的全局变量,比如$JOB_NAME。更可以增加插件,比如git parameters。注意,在Jenkins里参数不要写成字符串模版的${param}了,这是不对的。
  5. 配置插件(系统设置 - 管理插件。插件可以用关键字搜索)
    1. 作为前端,我们需要安装Node.js环境。在“全局工具配置”里去安装想要的node版本,命名可以写成可读性比较强的,比如node_v8.10.0这样。
    2. 插件git parameters可以自动获取远程仓库的分支信息并提供参数传入脚本。但不需要在插件设置里关联Git库,只要在我们新建的job里的源码管理选项里设置好就行了。当然最好点开高级设置一些默认的选项之类的的。Jenkins的源码管理那边,分支写上参数上的分支对应的变量名即可。(当然是$param的样子)
    3. 插件Publish over SSH,然后在“系统管理 - 系统设置”里,拉到下面去设置,建议设置可读性较强的名字,方便选择。这里的remote directory和在job里用插件的时候的是相互衔接的。注意一个坑,我一开始前者放空,后者写/root/soft结果服务器上成了/root/root/soft
    4. 用户角色管理插件Role-based Authorization Strategy,安装之后直接出现在“系统设置”的下级菜单,而不是其他菜单里面。插件官方文档要求安全域设置为Servlet容器代理,实际操作发现Jenkins专有用户数据库也是可以的。然后就可以在“管理用户”和“Manage and Assign Roles”上施展拳脚了(插件增加的是后者)
  6. 构建触发器
    1. Build periodicallyPoll SCM的区别在于,前者会严格遵循时间去构建;后者会检查版本库有没有更新,有更新才继续。
    2. 设置里如*/3 * * * *,代表三分钟检查一次。
  7. 构建环境:
    1. 前端的话,勾选Provide Node & npm bin/ folder to PATH
    2. 推荐插件“user build vars plugin”,安装之后,在构建环境这里,勾选“Set jenkins user build variables”,可以获得更多环境变量,比如“BUILD_USER”
    3. 另外,写脚本的时候,稍微注意下,如果最外侧是单引号,那么内部是没法引用变量的。这不是说不能用单引号和双引号嵌套,而是shell的语法限制,字符串最外层不可以用,否则内部没法通过$var来引用变量。
  8. 构建过程的配置

    1. 这个命令npm install --registry=https://registry.npm.taobao.org总是很方便的。
    2. 编写Jenkins运行的Shell脚本。参数化的构建可以让我们的工作事半功倍,所以,项目的打包也应该做成分环境的参数化打包,这样一切就会顺利很多,比如,npm run bundle:$env这种在Jenkins里用起来就太开心了。
    3. 构建的设计是一个仁者见仁的过程,比如可以全部个性定制,也可以设计一套“约定大于配置”的规范。写一个接收参数的shell脚本,然后,在若干项目,都设置一样的部署姿势,这样,一般来说,就只要给Shell脚本传入几个参数:ip地址、项目目录、远程发布目录、远程执行命令,类似这样。这里放一个典型的脚本例子:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      #!/bin/bash
      . /etc/profile

      #脚本变量

      #Nginx IP
      NGINX_SERVER=("$1")
      USER=jenkins
      USER_KEY="/home/keys/id_rsa"

      #打包临时存放目录
      DEPLOY_TMP_DIR="/home/jenkins"

      #构建war包服务器路径
      DEPLOY_SOURCE_DIR="/home/tomcat_jenkins/jenkins_project/$2"

      #远端发布目录
      DEPLO Y_TARGET_DIR="$3"

      TAR_FILE="$JOB_NAME.tar.gz"

      #执行远程命令
      EXE_CMD="$4"

      cd ${DEPLOY_SOURCE_DIR}
      tar -czf ${DEPLOY_TMP_DIR}/${TAR_FILE} ./*

      DATE=`date '+%Y-%m-%d %H:%M:%S'`

      #上传
      for IP_ADDR in ${NGINX_SERVER[@]}
      do

      echo "`date '+%Y-%m-%d %H:%M:%S'` 拷贝 ${DEPLOY_TMP_DIR}/${TAR_FILE} 至 $IP_ADDR 的 ${DEPLOY_TMP_DIR}"
      scp -q -o StrictHostKeyChecking=no -i ${USER_KEY} ${DEPLOY_TMP_DIR}/${TAR_FILE} $USER@$IP_ADDR:${DEPLOY_TMP_DIR}

      echo "`date '+%Y-%m-%d %H:%M:%S'` 创建 $IP_ADDR 的目录 ${DEPLOY_TARGET_DIR}"
      ssh -i ${USER_KEY} ${USER}@${IP_ADDR} "mkdir -p ${DEPLOY_TARGET_DIR}"

      #echo "`date '+%Y-%m-%d %H:%M:%S'` 删除 $IP_ADDR 的目录 ${DEPLOY_TARGET_DIR} 下内容"
      #ssh -i ${USER_KEY} ${USER}@${IP_ADDR} "rm -rf ${DEPLOY_TARGET_DIR}"

      echo "`date '+%Y-%m-%d %H:%M:%S'` 解压 $IP_ADDR 的 ${EPLOY_TMP_DIR}/${TAR_FILE} 至 ${DEPLOY_TARGET_DIR}"
      ssh -i ${USER_KEY} ${USER}@${IP_ADDR} "tar -zxf ${DEPLOY_TMP_DIR}/${TAR_FILE} -C ${DEPLOY_TARGET_DIR}"

      echo "`date '+%Y-%m-%d %H:%M:%S'` 删除 $IP_ADDR 的 ${DEPLOY_TMP_DIR}/${TAR_FILE} 文件"
      ssh -i ${USER_KEY} ${USER}@${IP_ADDR} "rm -rf ${DEPLOY_TARGET_DIR}/${TAR_FILE}"

      if [ ! -n "${EXE_CMD}" ] ;then
      echo "no exe_cmd, ignore."
      else
      echo "`date '+%Y-%m-%d %H:%M:%S'` 在${IP_ADDR} 执行脚本 ${EXE_CMD}"
      ssh -i ${USER_KEY} ${USER}@${IP_ADDR} "${EXE_CMD}"
      fi
      done
    4. 打包完成后,自然需要部署。如果直接利用ssh publisher,没办法用参数来决定走哪一个。因此上面提到的,编写一个约定大于配置的Shell脚本是有优势的。我的做法是在构建脚本里,根据参数打出名字不同的包,不同的publisher只上传对应的名字的包——当然,是和参数挂钩的!并且根据参数在服务器端执行的脚本里加一句判断,选择性地exit 0这样,顶多也就消耗一次连接的性能。

  9. 构建后操作,按个人需要设置吧。

踩坑

环境需求问题

  1. 在Jenkins里可以灵活配置你构建过程里需要的一切环境
  2. 当然也可以在服务器上全局配置这些环境。比如gcc环境,如果你不在乎。你随时可以配一个全局的。

环境变量问题

我操作的时候获取不到BRANCH_NAME:原因尚不详,折衷方案是使用参数化构建来传递参数,代替它

密码问题

  1. 第一次启动Jenkins时,出于安全考虑,Jenkins会自动生成一个随机的按照口令。
  2. 注意控制台输出的口令,复制下来。在打开网页的时候会遇到需要输入的地方。
  3. 但事实上,即使你忘记了,Jenkins也会在首页提醒你去哪里找,它在文件里有保存了,一般是在/root/.jenkins/secrets/initialAdminPassword

中文乱码问题

  1. 为了方便阅读,在脚本里可能会让console输出中文字符,但是这样会带来乱码
  2. 一开始尝试在“系统管理 - 系统设置”里,对“环境变量”添加了键值对:LANGzh_CN.UTF-8,但是并没有解决console的乱码。
  3. 想到我的Jenkins其实是跑在Tomcat这个servlet里的,自然想到去修改Tomcat的server.xml,就在connector里增加了一行URIEncoding="utf-8"全部的代码块如下:

    1
    2
    3
    4
    5
    <Connector port="8080"
    URIEncoding="utf-8"
    protocol="HTTP/1.1"
    connectionTimeout="20000"
    redirectPort="8443" />

服务器hostname没有配置

  1. 通过.war直接跑服务的时候就能看到warning:

    1
    2
    WARING:Could not intialize the host network interface on nullbecause of an error: hz-p2ptest02 : hz-p2ptest02 : Temporary failure in same resolution
    java.net.UnknownHostException : hz-p2ptest02 : hz-p2ptest02: Temporary failure in same resolution
  2. 这其实就是因为服务器本机的hostname没有好好添加到它自己的hosts里。(发服务器的管理员你出来挨打)

  3. 编辑服务器的/etc/hosts文件,形如:

    1
    2
    127.0.0.1   localhost localhost.localdomain localhost4 localhost4.localdomain4 hz-p2ptest02
    ::1 localhost localhost.localdomain localhost6 localhost6.localdomain6 hz-p2ptest02
    1. hosts文件的作用相当如DNS,提供IP地址到hostname的对应。

服务器防火墙

  1. Linux是有自带防火墙的,比如你看到了jenkins is fully up and running但是就是访问不到Jenkins,只有404。
  2. 最粗暴的解决方式,到服务器上运行:iptables -F
  3. 当然我们是文明人,让防火墙放行端口就行了

    1
    2
    3
    ### sudo vim /etc/sysconfig/iptables
    -A INPUT -m state --state NEW -m tcp -p tcp --dport 8080 -j ACCEPT
    ### 重启生效: sudo systemctl restart iptables
  4. 在我司,据说内网可以不开防火墙,清规则或者直接service iptables stop。依赖外网的硬件防火墙做配置,这样可以省去内网每台服务器去做一次配置。这块我不懂就不乱说了。

Git库获取问题

  1. 在Jenkins上,试图获取公司的Git库,但是通过ssh的方式无限失败。
  2. 不在Jenkins目录里,而是直接在它所在的服务器上,直接安装git环境,在我的git帐号下添加ssh public key,可以以我的帐号通过SSH方式获取
  3. 注意到公司其他部门的搭建,全部都是通过http方式连接,遂申请了一个拥有部门全部一级仓库READ权限的帐号来专供Jenkins使用
  4. 问题涉及ssh的机制,暂无深入了解。TODO:

打包失败

文件大小写问题

  1. linux系统下是对大小写敏感的。
  2. 遇到的问题是在jenkins服务器上打包的时候,某个页面的import出了问题,module not found,仔细看,其实就是这个文件是小写开头,但是import的路径里却是大写开头,直接导致报错。
  3. 还有另外一个悬而未决的问题:也是import的模块找不到,后来试了试,发现路径上,也就是某个文件夹是字母数字混合,如果数字后面有英文字母就不行……比如“a、C1、b1、activity518”都可以,但是“1d、518activity”就都不行。暂时还没有总结出问题的规律。FIXME:

文件权限问题

  1. 问题根源来自我把Jenkins放在tomcat这servlet里。
  2. 问题表现是Jenkins生成的文件,当然,包括重要的打包出来的文件,出现了类似750这样的权限,发布到测试服务器上,Nginx没权限访问发布的内容
  3. 问题的定位:如果直接用root用户,通过java -jar jenkins.war跑服务,没有以上问题。
  4. 问题的原理:tomcat对于umask的复写。tomcat在bin文件夹下的catalina.sh里,会重新设置umask,和操作系统的0022不一样,它是0027。相关的代码段是:

    1
    2
    3
    4
    5
    # Set UMASK unless it has been overridden
    if [ -z "$UMASK" ]; then
    UMASK="0027"
    fi
    umask=$UMASK
  5. 一般会把umask设置成当前用户的umask。这样,tomcat下新生成的文件的umask就能正确被设置了,之前是0027,所以导致出现了各种如750这样的文件权限,就很尴尬。

shell脚本的一些坑

  1. 判断语句,分号不要少,条件方括号里的两边的空格不要少。
  2. 直接上一个例子:

    1
    2
    3
    if [ $deploy != "sst" ];then
    exit 0
    fi
  3. unzip和zip命令的坑:一句话,想zip哪个,老老实实的cd进目录去做,不然解压开了就是带路径的。-j参数别乱用,用了,所有文件都失去路径。你想要的目录结构也会消失。

参考文档