简单介绍了一下Jenkins自动集成/部署工具的安装和使用. ppt见

功能概览/Overview

Jenkins是一款由Java编写的开源持续集成(continuous integration), 持续交付(continuous delivery), 自动化工具.

像下图中:

Jenkins处于软件发布过程的中间角色,

  • 其本身主要负责自动化的 代码编译, 打包, 测试, 组装,
  • 向上要与代码管理工具(如gitlab)/配置管理工具进行交互,
  • 向下要与服务器管理工具/容器(docker)/服务器(物理机, aws)进行交互.

那么Jenkins要与这些其他工具配合, 靠的是他插件化的特性, 社区贡献的1000+插件, 一些有代表性的例子如: gitlab/maven/junit/docker/ecs/jira/dingding

有了这些插件, 老管家(Jenkins的图标)变身变形金刚了.

系统管理请看安装-运维/Setup权限-安全/Safety两节, 使用者请继续向下.

安装-运维/Setup

Jenkins是一个Java Web项目, 运行于servlet容器中. 最低Java7, 不过最好还是用Java8及以上.

下载安装

在其官网下载页面, 可以看到提供了各种系统的包, 如RPM,deb等, 然而最简单的方式是下载一个 Generic Java package (.war), 熟悉java的同学可能知道了, 拿到这个war包, 放在Tomcat下就可以运行, 不过这个war包内嵌了jetty服务, 所以使用诸如 ` nohup java -jar jenkins.war –httpPort=8001 &` 的命令运行即可, 不需要依赖其他的服务. 可用的启动参数.

支持https方式启动, 不过这种事还是前面放一个nginx简单些.

yum安装参考, yum安装后包在/usr/lib/jenkins/jenkins.war, 配置在/etc/sysconfig/jenkins, service维护脚本/etc/init.d/jenkins, 日志在/var/log/jenkins

数据备份和日志

Jenkins默认会使用~/.jenkins/作为工作目录, 这个目录就是Jenkins存放所有数据的地方了. 可以通过JENKINS_HOME这个环境变量或者system property来更改这个位置. 其结构类似:

可以定时打包备份这个目录作为恢复用.

通过java -jar启动时, Jenkins会将日志输出到标准输出中, 最好将他重定向到自己指定的位置. 通过其他方式启动会默认打到别的目录.

Jenkins使用java.util.logging管理日志, 可以通过系统管理->System Log来查看日志和配置jul的logger.

初始化Jenkins的步骤.todo

初次启动, Jenkins会随机生成一个字符串作为密钥

打开页面, 设置管理员账号

接下来根据需要选择plugins, 选择完成之后安装即可. (后面随时可以在设置页面继续安装plugins).

接下来就进入了Jenkins系统, 先进入系统设置页进行配置.

插件列表

列一些特殊的默认不安装的插件:

  • Config File Provider Plugin
  • Credentials Binding Plugin
  • Folders Plugin
  • Role-based Authorization Strategy
  • SSH Agent Plugin
  • SSH Credentials Plugin

其他.

还有一些其他的诸如CLI, 负载统计等等管理内容, 可在系统管理页面查看.

本节内容参看wiki页前三节

权限-安全/Safety

首先一些通用的安全策略要被实施:

  • 通过防火墙等, 限制Jenkins服务的非公网访问和端口等.
  • 启动Jenkins的用户, 以及Jenkins登录别的服务器的用户, 要是受限的用户, 只给最小的权限.
  • Jenkins服务启用HTTPS.

Jenkins内部的权限/安全分两部分讲: 1是Jenkins本身的用户/权限/安全问题. 2是Jenkins去访问gitlab, 访问其他服务器时的安全问题.

Jenkins权限/安全

在系统管理->Configure Global Security页面中可以看到一些安全配置. 其中访问控制分两步:

  • 安全域, 即如何控制用户认证, authentication. 默认有如下选项
    • Jenkins专有用户数据库. 即Jenkins自己存储用户, 用户需要在系统中手动创建
    • LDAP
    • Servlet容器代理
    • Unix用户/用户组

    通过名字就可以知道这几项的大概功能, 通过安装插件, 可以添加别的安全域, 比如安装gitlab插件通过gitlab的用户进行认证. 管理员根据需要选择认证方式即可.

  • 授权策略, 即认证后的用户能做什么, authorization. 默认有如下选项
    • 任何用户可以做任何事(没有任何限制).
    • 登录用户可以做任何事.
    • 安全矩阵. 通过一个矩阵(类似confluence权限配置的表), 来配置用户可以操作哪些项, 这些操作项是全局的.
    • 项目矩阵授权策略. 扩展了上面的安全矩阵模式. 不光是全局的安全矩阵, 可以在每个项目的Job配置页面, 配置项目的安全矩阵.
    • Role-Based Strategy

坦白讲, 这几个授权策略都很鸡肋. 我们的目标是实现不同组的不同用户登录之后能看到不同的东西有不同的权限, 这里唯一能用的就是项目矩阵授权策略, 但是操作起来很麻烦, 需要每加一个项目, 就要去项目里给各个用户配置权限. 新加用户也要操作一波.

解决这个问题, 我发现了一个叫做Role Strategy的插件, 基于角色的授权策略.安装这个插件, 选择这个策略后, 在系统管理->Manage and Assign Roles页面.

  • Creating global roles, such as admin, job creator, anonymous, etc., allowing to set Overall, Slave, Job, Run, View and SCM permissions on a global basis.
  • Creating project roles, allowing to set only Job and Run permissions on a project basis.
  • Creating slave roles, allowing to set node-related permissions.
  • Assigning these roles to users.

完美解决不同组的不同用户登录之后能看到不同的东西有不同的权限的问题.

Jenkins登录外部的权限/安全

由于前面提到Jenkins要和上下游很多组件交互, 那么和外部组件交互的安全也是个问题. 这里只考虑两方面的问题

  • 拉取代码的权限和安全.
  • 登录服务器的权限和安全.

通过Credentials Plugin插件, 我们可以在全局或项目文件夹上, 存储我们的认证资质, 比如账号密码, username+私钥等等.

我们尽量不将credentials存放在系统级别下, 而存放在各个项目目录下. 在一个目录下的credentials分domain存放, 可以通过domain来判别何时使用哪个credentials(没试过), 也可以在使用处, 指定使用哪个credentials.

比如拉取代码, 在项目设置中设置了Repository URL后, 接着就可以选择使用哪个credentials去访问这个代码库. 这样我们可以配合gitlab的deploy key功能, 做到拉取代码的权限问题.

在部署的时候想要登录服务器, 那么可以:

配合Role插件, 就可以做到, 不同的人, 不同项目, 区分存放和使用不同的credentials.

credentials管理通过页面左侧菜单项进入.

登录服务器是否安全? TODO IMPORTANT

既然credentials已经可以被分开存储和使用了,

  • 那么credentials在Jenkins内部存储是否安全呢?
  • 部署步骤中, Jenkins使用credentials登录各个被部署的服务器执行各种命令安全么?
  • 上一条如果被认为不安全, 那么将部署脚本运行在被部署服务器, Jenkins只负责把打好的程序包scp过去? 这种方式真的好么?
  • 通过在被部署节点上启动Jenkins的agent, 由agent进行操作, Jenkins的master向agent发起指令, 这样的方式是否规避了Jenkins登录各个服务器的问题?

这些问题还待解决.

使用/Usage

项目类型/Job Type

Jenkins里管理的项目或者也叫Job. 在新建页面可以看到有这样几种类型(有的通过插件实现)

这些类型中有的是单个Job, 有几个是相当于文件夹, 里面再创建Job. 为了配合我们的Role Strategy的授权策略, 我们最外层最好都用Folder, 里面再创建各个组自己的项目.

构建

关于构建的重要提示: 构建相当于在jenkins服务器给这个项目一个专门的目录, 构建步骤都相对这个目录下执行: 在这个地方checkout代码, 执行打包编译等.

虽然上面看到的项目类型有很多种, 但对于真正的构建操作其实只分为两类:

  • 通过在界面上操作, 配置构建过程
  • 通过编写Pipeline, 描述构建过程

界面配置构建

比如我创建了一个自由风格的项目, 在配置页面:

可以看到有这几项功能(这些功能都可以通过安装各种插件来扩展):

  • 源码管理: git和svn插件来支持这两种方式.
  • 构建触发器: 何时进行构建:
    • 远程触发 (发一个请求)
    • Build periodically (cron表达式)
    • Build after other projects are built
    • Poll SCM (cron + poll changes)
    • gitlab事件发生 (push,merge等)
    • 手动触发构建
  • 构建环境
  • 构建步骤: 按顺序添加的可用的构建操作, 有很多插件增加构建功能.
  • 构建后操作: 增加一些清理, 通知, 报告之类的操作.

通过在这个页面上点点点, 就可以配置好这个项目的整个构建流程.

Pipeline配置构建

Pipeline是一个文件, 随着项目代码保存在一起(gitlab上), 这个文件里面就描述了构建的整个过程. 这个用法就好比makefile,travis.yml等等文件的意思一样, 这个文件我习惯叫作Jenkinsfile.

Jenkins引入这个Pipeline as Code的好处就是, 部署步骤随着代码一起存放了, 不像页面配置构建那种, 点着麻烦还容易丢失.

Pipeline是Jenkins 2之后官方化的功能, 之前是社区开发的一个插件. (所以使用的时候要注意, 不要使用Jenkins2以前的Pipeline插件了, 使用Jenkins2之后的)

创建一个Pipeline类型的项目, 在配置中, 没有上面那个五项功能了, 只剩下源码管理和构建触发器, 构建环境/构建步骤/构建后操作 都移到Pipeline文件中描述了.

Pipeline与构建插件的关系. 没有Pipeline之前, 编写一个扩展构建步骤的插件, 都是定义自己的功能, 在页面上配置使用. 使用Pipeline方式构建的话, 这些插件怎么办用起来呢? 答案是很多都用不起来了.

扩展构建步骤的插件, 要想支持Pipeline, 需要配合Pipeline, 将自己的构建按照Pipeline要求编写成一个step, 就类似提供了一个函数, 这样才能在Pipeline里调用. 所有可用的steps, 参看最后的step reference.

Pipeline其实就是一个groovy语言的脚本. 里面预定义好了一些类/函数(groovy语言), 按照格式写就好了.

Jenkins 2.x版本后, 定义了DSL, 为Pipeline加了另一种写法, 叫Declarative Pipeline, 原来的groovy脚本叫Scripted Pipeline. dsl语法简单, 功能简单, 脚本方式功能更全.

talk is cheap: the example code :

#!groovy
node {
    stage('checkout') {
        checkout scm
    }

    def user = 'user'
    def server = 'xx.xx.cn'

    def instancePortMap = ['01': '7002', '02': '6002']
    def activeIdList = [:]
    def keyId = "xx-ssh-myproject-key"

    withCredentials([sshUserPrivateKey(credentialsId: "${keyId}", keyFileVariable: 'keyfile')]) {

        stage('get active tomcat instance') {
            echo "trying to get activeId on ${server}"

            def activeId = sh([returnStdout: true,
                               script      : """
                ssh -i ${keyfile} ${user}@${server} 'ps aux|grep tomcat'
                """]).trim()
            if (!instanceList.contains(activeId)) {
                echo "wrong activeId ${activeId} on ${server}, please check"
                currentBuild.result = 'FAILURE'
                error "wrong activeId ${activeId} on ${server}, please check"
            }
            activeIdList[server] = activeId
            echo "${activeId} is running on ${server}"
        }

        stage('build') {
            def mvnHome = tool name: 'mvn3', type: 'maven'
            env.PATH = "${mvnHome}/bin:${env.PATH}"
            sh 'mvn -f myproject-web/pom.xml clean package -Ptest'
            echo 'building project!'

        }

        stage('scp&unzip backup') {
            def warName = sh([returnStdout: true,
                              script      : 'ls myproject-web/target/*.war']).trim().split('/')[-1]

            def instances = []
            instances.addAll(instanceList)
            instances.remove(activeIdList[server])
            def backupInstance = instances[0];
            sh "scp -i ${keyfile} myproject-web/target/*.war ${user}@${server}:/home/data/apath"
            echo "scp to ${server} backup tomcat ${backupInstance}"
            sh "ssh -i ${keyfile} ${user}@${server} 'cd /home/data/apath;rm -r myprojectweb${backupInstance}; unzip -oq ${warName} -d myprojectweb${backupInstance}'"
        }
    }
}

一些通用的格式:

  • node: 定义使用哪个节点来构建, 上面的写法就是任意node的意思.最外层都这么写.
  • stage: 阶段, 将构建分阶段, 只是为了清晰好看. 页面上可以看:

  • post: 构建之后的操作.
  • withEnv: 设置/使用环境变量
  • tool: 寻找系统中定义好的工具, 比如mvn3.
  • checkout scm: 从代码库checkout代码. scm是一个简写, 直接使用项目配置的地址, checkout也可以传参数指定代码库url等.
  • sh: 执行shell命令.
  • timeout: 设定超时.
  • sleep
  • echo / error
  • input 接收输入
  • configFileProvider
  • withCredentials

可用的step太多了, 想用什么功能去step references里找一下.

这里要说明的是, 具体的构建/部署应该怎么做, Jenkins是管不了的, 它只是在外围提供了一些工具, 具体步骤由人根据项目情况来定的.

参数化构建过程

为构建传入参数todo

项目配置文件管理

我们知道, 像数据库密码这类东西, 不应该出现在gitlab, 而是在部署的时候在这里加上去. Jenkins有一个config files的插件可以简单实现这个功能.

在项目左侧菜单, 添加一些config文件. 在部署Pipeline里, 就可以使用了:

其他

Jenkins页面中可以看到, 提供了 构建历史/工作空间/修改记录/构建输出 等等的默认功能. 更加可以通过安装各种插件, 实现许多意想不到的功能.

TODO

  • 上面提到的登录服务器是否安全 问题
  • Jenkins的主从分布式部署
  • 配合gitlab
  • 配合jira
  • docker?

参考/Reference

##UPDATE0: 测试报告

finally {
    junit "target/surefire-reports/*.xml"
    jacoco classPattern: "target/classes", execPattern: "target/**.exec", sourcePattern: "src/main/java"
    findbugs canComputeNew: false, defaultEncoding: "", excludePattern: "", healthy: "", includePattern: "",
            pattern: "target/findbugsXml.xml", unHealthy: ""
}