Jenkins+Ansible+Gitlab自动发布/回滚Spring项目
一、实现方法流程图
流程图如下:
代码托管在本地GitLab上(为了复现整套流水线,我直接使用了GitHub,懒得再搭建GitLab),开发完成后提交代码到代码仓库,【自动】触发Jenkins进行持续集成和部署,如果代码出现问题,根据版本进行回滚。
(1)、自动触发Jenkins可以使用hooks来实现,具体是否使用取决于自己;
(2)、Jenkins从GitLab上拉取代码进行编译打包,并将项目target目录备份到备份目录下,以便回滚使用;
(3)、Jenkins将打好得包通过ansible部署到对应得服务器上并重启服务;
二、规范标准
ansible:
备份路径:/data/backup/{{ENV}}/{{JOB_NAME}}
	ENV:SIT,PRE,UAT,SANDBOX,PRO
  JOB_NAME:Jenkins内建参数,项目名
Jenkins工作目录:/data/apps ,默认在/root/.jenkins下
应用服务器:
应用路径:/home/tomcat/apache-tomcat-9.0.16/webapps/
日志路径:/home/tomcat/logs/
系统名称:{数据中心}-{服务器区域}-{战队名}-{应用名}-{IP后两位}
	比如:DCA-APP-KFPT-WGFW01-11-85,其中DCA表示数据中心A,DCB表示数据中心B
三、环境配置
1、软件版本
| 软件 | 版本 | 
|---|---|
| ansible | 2.7.10 | 
| python | 2.7.5 | 
| centos | 7.4.1708 | 
| java | 1.8.0_171 | 
| maven | 3.6.1 | 
| jenkins | 2.164.1 | 
2、软件部署
略.....(运维必备技能,不需要多说)
四、代码发布
1、脚本介绍
脚本名:deploy.yml
---
- hosts: "{{ TARGET }}"
  remote_user: "{{ REMOTE_USER }}"
  any_errors_fatal: true
  vars:
    BACKUP_DIR: /data/backup/{{ ENV }}/{{ JOB_NAME }}
  tasks:
  - name: 获取时间节点
    set_fact: BACKUP_TIME={{ '%Y%m%d_%H%M' | strftime  }}
  - name: 获取进程PID,并把它赋值给变量java_pid
    shell: ps -ef | grep /home/tomcat |grep -v grep|awk '{print $2}'
    register: java_pid
  - name: 创建备份目录
    file: path={{ BACKUP_DIR }}/{{ BACKUP_TIME  }} state=directory
  - name: 备份构建产物
    shell: cp -ra {{ WORKSPACE }}/* {{ BACKUP_DIR }}/{{ BACKUP_TIME  }}/
  - name: 停止服务
    shell: ps -ef | grep /home/tomcat |grep -v grep|awk '{print $2}'|xargs kill -9
    when: java_pid.stdout != ''
  - name: 删除原有war包,其实也可以不用删除
    file: path=/home/tomcat/apache-tomcat-9.0.16/webapps/*.war state=absent
  - name: 复制war包到应用路径
    shell: cp -ra {{ WORKSPACE }}/target/*.war /home/tomcat/apache-tomcat-9.0.16/webapps/
  - name: 启动服务
    shell: source /etc/profile && nohup sh /home/tomcat/apache-tomcat-9.0.16/bin/catalina.sh start &>/dev/null &
2、Jenkins配置
2.1、插件
- Ansible plugin: 执行Ansible所需插件。
 - AnsiColor:彩色输出,非必须
 - Build With Parameters:参数化构建需要
 - Git plugin:git需要
 - JDK Parameter Plugin:自己按需吧
 - Mask Passwords Plugin:参数化构建,加密密码类参数,非必须
 - Readonly Param链接eter plugin:参数化构建里的,只读参数选项
 - Active Choices Plug-in: 动态参数插件,发布不会用到,后面会介绍,有奇用
 - Run Selector Plugin:参数化构建,选择插件
 - Git Parameter Plug-In:git分支选择插件,非必要
 - Maven Release Plug-in Plug-in:Maven插件,创建Maven项目必须
 
2.2、配置
2.2.1 Jenkins项目命名规范
{环境}-{战队}-{项目}
2.2.2 创建一个maven项目

设置构建保存日期:
描述业务环境:
GIT配置:
构建环境配置:
构建配置:
ansible配置:
点击高级,配置Extra Variable,配置需要传递得参数:

然后配置完成,保存项目。
点击构建可以看到具体得输出:
五、代码回滚
由于各种各样的原因,发布的代码可能会出现异常,这时候可能需要紧急回滚代码,庆幸的是,我们前面有做备份策略,我们可以手动去回滚备份的代码,但是缺点也很明显,当主机实例过多时,手动回滚明显是不再明智的,所以我们想办法结合Jenkins+Ansible这两者来做到一个通用的服务回滚策略,首先我们先分析下我们回滚代码需要用到什么?
- 代码的历史备份
 - 回滚应用名称
 - 需要回滚的主机
 
首先看下第1点,我们发布过程是是有对代码做备份的。再看第2,3点,应用名称跟回滚的主机哪里可以获取到呢?答案是jenkins job里。
我们进JENKINS_HOME得jobs查看下面得文件如下:
我们将PRE-DSJ-ORDER进行拆分可以得到{业务环境}-{战队}-{业务名}等信息,而主机信息就在config.xml得content字段,如下:
1、创建业务回滚JOB

下拉选择参数化构建,选择动态参数,新建一个Avctive Choices Parameter ==>ENV,选择Groovy script,里面用groovy套了一个shell去执行(当然,你groovy熟悉不套shell能获取一个列表也行)。
我们可以在服务器上执行一下这条命令,看看输出:
[root@hjkj jobs]# cd /data/apps/jobs && ls |grep -Po '^(?!PRD|APP)[A-Z]+(?=-)'  | sort -n | uniq
PRE
从这一步可以获取业务环境。
接下来,我们添加第二个参数Active Choices Reactive Parameter(要引用其他参数)==> GROUP,Groovy Script里多了一个env=ENV是为了引入前面获取的ENV,Choice Type里继续勾选单选框,不同的是下面多了一个Referenced parameter,这里填写ENV(这个是因为要关联上面的ENV)

看看这条命令的输出:
[root@hjkj jobs]# cd /data/apps/jobs && ls -d $env* | grep -Po '(?<=-)[A-Z0-9]+(?=-)' | sort -n | uniq
DSJ
ZT
接下来,我们添加第三个动态参数Active Choices Reactive Parameter(要引用其他参数)==>SERVICE,Groovy Script里多了一个env=ENV,group=GROUP是为了引入前面获取的ENV跟GROUP,Choice Type里继续勾选单选框,
不同的是下面多了一个Referenced parameter,这里填写ENV,GROUP(这个是因为要关联上面的ENV,GROUOP)

看看这条命令的输出:
[root@hjkj jobs]# cd /data/apps/jobs/ && ls -d ${env}-${group}-* | grep -Po "(?<=-)[a-zA-Z0-9-].*" | grep -Po "(?<=-)[a-zA-Z0-9-].*"| sort
config
ORDER
最后,我们再配置一个Active Choices Reactive Parameter==>HISTORY,用于获取历史版本,选择单选框,关联ENV,GROUP,SERVICE参数。

看看这条命令的输出结果:
[root@hjkj jobs]# env=PRE
[root@hjkj jobs]# group=DSJ
[root@hjkj jobs]# service=ORDER
[root@hjkj jobs]# cd /data/backup/${env}/${env}-${group}-${service}/ && ls  | sort -nr | head -n10
20190506_140
作为可选项,我们还可以加个Active Choices Reactive Parameter==>INFO的动态参数构建,用于获取config.xml里的job描述,这里要关联三个参数ENV,GROUP,SERVICE.

看看命令的执行结果:
[root@hjkj PRE-DSJ-ORDER]# grep -Po '(?<=<description>).*(?=<)' /data/apps/jobs/${env}-${group}-${service}/config.xml | head -n 1
PRE-DSJ-ORDER
我们正确获取到了{环境},{项目分组},{应用名}以及{历史备份点}等信息,但是还没有关联的inventory,既然inventory信息在config.xml中已有,我们可以通过声明ENV,GROUP,SERVICE环境变量,再通过脚本获取这几个值来拼接出config.xml所在位置,再通过解析xml来获取主机host,得到一个ansible动态inventory,(不单单是host,我们可以在xml里获取各种我们定义的值来作为inventory vars变量为我们所用!)
在构建选项添加Executor shell,我们将ENV,GROUP,SERVICE声明到了执行的环境变量中:
获取信息的inventory.py脚本如下:
#!/usr/bin/python
# -- encoding: utf-8 --
## pip install xmltodict ##
import xmltodict
import json
import re
import os
# 从环境变量获取参数
# 账号密码你做了信任就不需要,自己看着办
options = {
    'ENV': os.getenv('ENV'),
    'GROUP': os.getenv('GROUP'),
    'SERVICE': os.getenv('SERVICE'),
    'ACTION': os.getenv('ACTION'),
    'ansible_user': 'stguser',
    'ansible_password': 'abc',
    'ansible_become_pass': '123',
    'ansible_become_method': 'su',
    'ansible_become_user': 'root',
    'ansible_become': True
}
def getXml(env,group,service):
    ''' 拼接对应项目的jenkinx config.xml路径'''
    file = '/data/apps/jobs/{}-{}-{}/config.xml'.format(env,group,service)
    return file
def getData(file):
    data = dict()
    xml = open(file)
    try:
        xmldata = xml.read()
    finally:
        xml.close()
    convertedDict = xmltodict.parse(xmldata)
    # maven2 项目模板数据提取
    if convertedDict.has_key('maven2-moduleset'):
        name = convertedDict['maven2-moduleset']['rootModule']['artifactId']
        _ansi_obj = convertedDict['maven2-moduleset']['postbuilders']['org.jenkinsci.plugins.ansible.AnsiblePlaybookBuilder']
        # 可能有多个playbbok,只要是inventory一样就无所谓取哪一个(这里取第一个,如果多个不一样,自己想办法合并)
        if isinstance(_ansi_obj,list):
            host_obj = _ansi_obj[0]['inventory']['content']
        else:
            host_obj = _ansi_obj['inventory']['content']
        data['hosts'] = re.findall('[\d+\.]{3,}\d+',host_obj)
        # 如果设置了参数化构建,把只读参数作为ansible参数
        if convertedDict['maven2-moduleset']['properties'].has_key('hudson.model.ParametersDefinitionProperty'):
            parameter_data = convertedDict['maven2-moduleset']['properties']['hudson.model.ParametersDefinitionProperty']['parameterDefinitions']['com.wangyin.ams.cms.abs.ParaReadOnly.WReadonlyStringParameterDefinition']
    # 这里使用的自由风格模板模板,数据结构与maven2不一样,需要拆开判断
    if convertedDict.has_key('project'):
        host_obj = convertedDict['project']['builders']['org.jenkinsci.plugins.ansible.AnsiblePlaybookBuilder']['inventory']['content']
        data['hosts'] = re.findall('[\d+\.]{3,}\d+',host_obj)
        # 如果设置了参数化构建,把只读参数作为ansible参数
        if convertedDict['project']['properties'].has_key('hudson.model.ParametersDefinitionProperty'):
            parameter_data = convertedDict['project']['properties']['hudson.model.ParametersDefinitionProperty']['parameterDefinitions']['com.wangyin.ams.cms.abs.ParaReadOnly.WReadonlyStringParameterDefinition']
    # 插入参数化构建参数(我这里是只读字符串参数)
    try:
        for parameter in parameter_data:
            data[parameter['name']] = parameter['defaultValue']
    except:
        pass
    #print(json.dumps(convertedDict,indent=4))
    return data
def returnInventory(xmldata,**options):
    ''' 合并提取的数据,返回inventory的json'''
    inventory = dict()
    inventory['_meta'] = dict()
    inventory['_meta']['hostvars'] = dict()
    inventory[options['SERVICE']] = dict()
    inventory[options['SERVICE']]['vars'] = dict()
    # 合并xmldata提取的数据
    for para_key,para_value in xmldata.items():
        # 单独把hosts列表提取出来,其他的都丢vars里
        if para_key == 'hosts':
            inventory[options['SERVICE']][para_key] = para_value
        else:
            inventory[options['SERVICE']]['vars'][para_key] = para_value
    # 合并options里的所有东西到vars里
    for opt_key,opt_value in options.items():
        inventory[options['SERVICE']]['vars'][opt_key] = opt_value
    return inventory
if __name__ == "__main__":
    xmldata = getData(getXml(options['ENV'],options['GROUP'],options['SERVICE']))
    print(json.dumps(returnInventory(xmldata,**options),indent=4))
执行结果如下:
[root@hjkj python]# export ENV=PRE GROUP=DSJ SERVICE=ORDER
[root@hjkj python]# ./inventory.py
{
    "_meta": {
        "hostvars": {}
    },
    "ORDER": {
        "hosts": [
            "172.16.0.33"
        ],
        "vars": {
            "ansible_become_method": "su",
            "GROUP": "DSJ",
            "SERVER_PORT": "8080",
            "SERVICE": "ORDER",
            "ansible_become_user": "root",
            "ansible_become": true,
            "ansible_user": "stguser",
            "ENV": "PRE",
            "ansible_become_pass": "123",
            "ACTION": null,
            "ansible_password": "abc"
        }
    }
}
最后我们看下jenkins里的ansible配置,inventory执行了python脚本,并传入了一个extra vars 的HISTORY参数:

2、ansible roles介绍
目录结构:
.
├── roles
│   └── spring_rollback
│       ├── defaults
│       │   └── main.yml
│       └── tasks
│           ├── common.yml
│           ├── main.yml
│           └── rollback.yml
├── rollback.yml
rollback.yml
---
- hosts: all
  pre_tasks:
    - assert:
        that:
        - "HISTORY != ''"
        fail_msg: "请选择一个正确的历史版本。"
  roles:
    - spring_rollback
defaults/main.yml
---
BACKUP: "/data/backup/{{ ENV }}/{{ENV}}-{{GROUP}}-{{ SERVICE }}/{{ HISTORY }}"
OWNER: stguser
tasks/main.yml
---
- include_tasks: common.yml
- include_tasks: rollback.yml
  loop: "{{ play_hosts }}"
  run_once: true
  become: yes
tasks/common.yml
---
- shell: "ls -d /home/tomcat/apache-tomcat-9.0.16/webapps"
  register: result
- set_fact:
    src_package: "{{ BACKUP }}"
    dest_package: "{{ result.stdout }}"
tasks/rollback.yml
---
- block:
  - name: get pid
    shell: ps -ef | grep /home/tomcat |grep -v grep|awk '{print $2}'
    register: java_pid
  - name: stop tomcat
    shell: ps -ef | grep /home/tomcat |grep -v grep|awk '{print $2}'|xargs kill -9
    when: java_pid.stdout != ''
  - name: 回滚{{ SERVICE }}至{{ HISTORY }}历史版本
    shell: |
      [[ -f {{ dest_package }}/*.war ]] && rm -rf {{ dest_package }}/*
      \cp -ra {{ src_package }}/target/*.war {{ dest_package }}/
  - name: start tomcat
    shell: source /etc/profile && nohup sh /home/tomcat/apache-tomcat-9.0.16/bin/catalina.sh start &>/dev/null &
3、执行效果


Jenkins+Ansible+Gitlab自动发布/回滚Spring项目的更多相关文章
- jenkinsfile or pipline 实现微服务自动发布回滚流程
		
1 #!/usr/bin/env groovy Jenkinsfile node { //服务名称 def service_name = "**" //包名 def service ...
 - Jenkins Ansible GitLab 自动化部署
		
Jenkins Ansible GitLab 自动化部署 DevOps https://www.cnblogs.com/yangjianbo/articles/10393765.html https: ...
 - Jenkins实用发布与回滚PHP项目生产实践
		
目录 1.概述 2.项目实践 2.1.环境说明 2.2.Jenkins配置 2.2.1.修改Jenkins的运行用户 2.2.2.配置Jenkins用户和Gitlab的ssh-key 2.2.3.Je ...
 - Jenkins发布回滚方案
		
Jenkins回滚可以通过每次发布从主干打tag,然后发布的时候发tag,比如tag, v1, v2,v3 如果我发布了v3,想要回滚回v2,直接在Jenkins中选择v2的tag地址重新构建就可以回 ...
 - 【06】Jenkins:Gitlab 自动触发构建以及钉钉通知
		
写在前面的话 在某些时候,我们希望能够实现这样一个功能,当用户提交东西到 gitlab 上的时候,希望它能够自动触发构建,发布到我们需要的环境. 目前我们内部有做类似的需求:产品提交原型到 gitla ...
 - Gitlab之版本回滚
		
gitlab提交错误需要回滚版本 首先查看log找到需要回滚的head git log 回滚 git reset --hard 297ff2dcf20605297684f296a4b4ccaa1cf4 ...
 - Jenkins + Ansible + Gitlab之gitlab篇
		
前言 持续交付 版本控制器:Gitlab.GitHub 持续集成工具:jenkins 部署工具:ansible 课程安排 Gitlab搭建与流程使用 Ansible环境配置与Playbook编写规范 ...
 - 在docker中运行jenkins实现代码自动发布到测试服务器
		
在docker中运行jenkins 用的镜像是apline版:lts-alpine,并设置正确的时区. docker run --name jenkins_master -d \ -p 8081:80 ...
 - Centos 7.2 Jenkins+Ansible+Gitlab 基础配置
		
注意:首先准备jenkins服务器 如何搭建jenkins 由于上篇文章中jenkins是采用war并部署在tomcat中来完成的安装,所以这里隆重介绍下启动tomcat的用户:tomcat,下面会 ...
 
随机推荐
- MAC中PHP7.3安装mysql扩展
			
1.下载mysql扩展http://git.php.net/?p=pecl/database/mysql.git;a=summary 2.解压tar xzvf mysql-d7643af.tar.gz ...
 - Struts2-学习笔记系列(4)-访问servlet api
			
5.1通过actioncontext: public String execute() throws Exception { ActionContext ctx = ActionContext.get ...
 - Centos7 编译安装 Libmcrypt 库
			
0x00 先下载 libmcrypt 库源码 libmcrypt-2.5.8.tar.gz 或者去这里 libmcrypt 下载你需要的版本. 0x01 将下载的源码解压到文件夹 tar -zxvf ...
 - AJ学IOS(52)多线程网络之GCD下单例设计模式
			
AJ分享,必须精品 单例模式 1:单例模式的作用 可以保证在程序运行过程,一个类只有一个实例,而且该实例易于供外界访问 从而方便地控制了实例个数,并节约系统资源 单例模式的使用场合 在整个应用程序中, ...
 - 你的网购价格监督利器——python+爬虫+微信机器人
			
前言 文的文字及图片来源于网络,仅供学习.交流使用,不具有任何商业用途,版权归原作者所有,如有问题请及时联系我们以作处理. 作者:风,又奈何 PS:如有需要Python学习资料的小伙伴可以加点击下方链 ...
 - 常见DL网络模型参数
 - G - GCD and LCM   杭电
			
Given two positive integers G and L, could you tell me how many solutions of (x, y, z) there are, sa ...
 - [javascript]各种页面定时跳转(倒计时跳转)代码总结
			
(1)使用setTimeout函数实现定时跳转(如下代码要写在body区域内) <script type="text/javascript"> //3秒钟之后跳转到指定 ...
 - 如何用TensorFlow实现线性回归
			
环境Anaconda 废话不多说,关键看代码 import tensorflow as tf import os os.environ['TF_CPP_MIN_LOG_LEVEL']='2' tf.a ...
 - Android-网页解析-gson的使用
			
相对于较为传统的Json解析来说,google共享的开源Gson在解析速度和所使用的内存在有着明显的优势,虽然说阿里巴巴也提供了fastgson包,但是它跟Gson的处理速度大同小异,只是底层实现的原 ...