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,下面会 ...
随机推荐
- itoa、ltoa
#include <stdlib.h> /*整形转字符型*/ char * itoa(int value, char *string, int radix) { char tmp[33]; ...
- IKAnalyzer修改支持lucene8.0
源码已经上传GitHub. 如果直接使用jar:IKAnalyzer-5.0.2. 在这我只是在原有代码结构上做了微调,让代码支持lucene8.0.
- (转) 关于Windows CE和Windows Mobile
转发自http://www.cnblogs.com/chump/articles/1281955.aspx 一.Windows CE Windows CE是微软的嵌入式操作系统主要的一种,面世于199 ...
- stand up meeting 12/3/2015
part 组员 今日工作 工作耗时/h 明日计划 工作耗时/h UI 冯晓云 初始化弹窗的弹出位置并捕捉弹窗区域内的鼠标控制事件,初步解决弹窗的拖拽功能: 6 UWP对控件的支持各种看不懂,属性 ...
- 嘿嘿,我就知道面试官接下来要问我 ConcurrentHashMap 底层原理了,看我怎么秀他
前言 上篇文章介绍了 HashMap 源码后,在博客平台广受好评,让本来己经不打算更新这个系列的我,仿佛被打了一顿鸡血.真的,被读者认可的感觉,就是这么奇妙. 然后,有读者希望我能出一版 Concur ...
- UML(续)
活动图 活动图定义 活动图描述了在一个过程中,顺序的/并行的活动及其之间的关系 应用于商业过程.工作流(业务过程).复杂算法的建模 活动图是顶点和弧的集合 活动节点 动作 流 对象值 注解和约束等 建 ...
- @SessionAttributes 的使用
@SessionAttributes 注解只用作用在 类 上,作用是将指定的 Model 的键值对保存在 session 中.可以让其他请求共用 session 中的键值对. 指定保存的属性名 作用是 ...
- 基于 HTML5 WebGL 的高炉炼铁厂可视化系统
前言 在当今 工业4.0 新时代的推动下,不仅迎来了 工业互联网 的发展,还开启了 5G 时代的新次元.而伴随着带宽的提升,网络信息飞速发展,能源管控上与实时预警在工业互联网中也占着举足轻 ...
- ubuntu16.04-交叉编译opencv3.4.6
0.前言 在要移植opecv和SeetaFaceEngine-master到ARM板子上运行的所有步骤之前,有几点需要注意的: 查看板子运行的Kernel版本 交叉编译工具链的gcc版本,关键就是工具 ...
- 关于赋值的Java面试题
面试题:(1) short s = 1:s = s + 1;(2) short s = 1;s += 1;问:上面两个代码有没有问题,如果有,哪里有问题? 答:(1) 第一个是错的,会报错损失精度,因 ...