概述
在CI/CD流程中,项目的可持续迭代,版本回档都是十分重要的环节。而网络资源中看着似乎基于jenkins+ansible发布spring boot/cloud类的jar包程序,或者tomcat下的war包的需求挺多的,闲来无事,也说说自己做过的jenkins基于ansible的发布 。
发布流程
规范与标准
无规矩不成方圆,要做好后期的自动化,标准化是少不了的,下面是我们这边规划的一些标准(非强制,根据自己实际情况调整)
- 应用名称:
{应用类型}-{端口}-{应用名称}
or{类型}-{应用名称}
, 例如:web-8080-gateway
,app-jobs-platform
- 主机名称:
{地区}-{机房}-{应用类型}-{代码语言}-{项目分组}-{ip结尾}
, 例如:sz-rjy-service-java-foo-14-81
- 日志路径:
/log/web,
例如:/log/web/web-8080-gateway
- 代码路径:
/data/,
例如:/data/web-8080-gateway
- Tomcat安装路径:
/usr/local/tomcat
- 实例路径:
/usr/local/tomcat/{应用名称}
- Jenkins job命名: {
环境
}_{项目分组
}_{应用名称
},如:TEST_GROUP1_web-8080-nc
应用配置:存放于git中,建立了各个环境相应的目录,环境目录下对应应用名称的同名文件,如:
git@192.168.1.24:devops-conf/conf.git
|
|__UAT
| |__web-8080-nc
| |__web-8081-wechat
|
|__STG
| |__web-8080-nc
| |__web-8081-wechat
... ...
jenkins依赖插件:
- A版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!nsible plugin: 执行Ansible所需插件>必须
- AnsiColor:彩色输出>非必须
- Build With Parameters:参数化构建>必须
- Git plugin:git>必须
- JDK Parameter Plugin:自己>必须
- Mask Passwords Plugin:参数化构建,加密密码类参数>非必须
- Readonly Parameter plugin:参数化构建里的,只读参数选项>必须
- Active Choices Plug-in: 动态参数插件,发布不会用到,后面会介绍,有奇用>必须
- Run Selector Plugin:参数化构建,选择插件>必须
- Git Parameter Plug-In:git分支选择插件>非必须
- PostBuildScript Plugin : 可在构建后根据构建结果运行多个可配置的操作>非必须
环境配置
1). 软件版本
- Ansible: 2.7.1
- Python: 2.7.5
- CentOS: 7.2
- Java: 1.8.0_73
- Jenkins: 2.121.1
2).环境/软件安装
略…自己玩去
发布任务配置
1. Jenkins Job配置
SVN拉取配置
maven构建配置
Ansible插件配置
2. Ansible Role配置
Role结构
1)代码结构
playbooks
└──test-tomcat-deploy.yml # 入口文件
roles
└──tomcat-deploy-test
├── defaults
│ └── main.yml # 默认变量
├── files
│ ├── conf # 实例配置文件
│ │ ├── catalina.policy
│ │ ├── catalina.properties
│ │ ├── context.xml
│ │ ├── logging.properties
│ │ ├── tomcat-users.xml
│ │ ├── tomcat-users.xsd
│ │ └── web.xml
│ └── tomcat.tar.gz # tomcat安装文件(没做任何调整,就是改名成tomcat后压缩了一下)
├── handlers
│ └── main.yml # handlers任务
├── tasks
│ ├── backup.yml # 备份任务
│ ├── commons.yml # 一般任务
│ ├── deploy.yml # 部署任务
│ ├── init.yml # 初始化任务
│ └── main.yml # 主文件
└── templates
├── catalina.sh.j2 # 启动脚本
├── env.j2 # 对于实例的描述文件,非必要
├── server.xml.j2 # 实例的server.xml配置
└── systemd.service.j2 # systemd服务脚本
2)Role配置文件说明
① .playbooks/test-tomcat-deploy.yml
---
- hosts: target
roles:
- { role: tomcat-deploy-test, tags: deploy }
② .defaults/main.yml版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!
---
_# base info_
_# JOB_NAME等信息直接从环境变量读取,而不是之前那样通过exra vars导入,少了很多不必要的参数填写_
JOB_NAME: "{{ lookup('env','JOB_NAME') }}"
TOMCAT_HOME: "/usr/share/tomcat"
_# 将JOB_NAME切割出各个信息_
PROJECT: "{{ JOB_NAME.split('_')[-1] }}"
CODE_DEPTH_PATH: "{{ code_depth_path | default('') }}"
PROJECT_BASE: "/data/{{ PROJECT }}"
ENV: "{{ JOB_NAME.split('_')[0] }}"
# project info
project:
public:
JAVA_HOME: "{{ java_home | default('') }}"
GIT_CONF_REPO: "git@192.168.1.24:devops/conf.git"
JAVA_OPTS: "{{ lookup('env','JAVA_OPTS') | default('-server -Xms2048M -Xmx2048M') }}"
local:
_# 备份信息日志路径_
deploy_release_log: "/data/deploy_release_log/{{ ENV }}/{{ PROJECT }}"
_# 构建后的代码路径_
CODE_PATH: "{{ local_code_path | default('') }}"
target:
_# 实例相关配置_
CATALINA_HOME: "{{ TOMCAT_HOME }}"
CATALINA_BASE: "/usr/local/tomcat/{{ PROJECT }}"
CATALINA_LOGBASE: "/usr/local/tomcat/{{ PROJECT }}/logs"
_#tomcat虚拟路径,默认为空_
TOMCAT_VIRTUAL_PATH: "{{ lookup('env','tomcat_virtual_path') }}"
CODE_PATH: "{{ PROJECT_BASE }}/{{ CODE_DEPTH_PATH }}"
TOMCAT_USER: "{{ tomcat_user | default('tomcat') }}"
TOMCAT_PORT: "{{ PROJECT.split('-')[1] }}"
_# 备份目录_
HISTORY_ARCHIVR_DIR: "/data/deploy_release_library/{{ PROJECT }}"
_# 应用日志(不是必须的)_
LOGPATH: "/log/{{ PROJECT.split('-')[0] }}/{{ PROJECT }}"
③ .files/conf(直接从tomcat二进制包里的conf原样复制过来即可)
④ .tasks/main.yml
_---_
_# 打印变量信息_
- name: show project info
debug: var=project
failed_when: (project is not defined)
_# 初始化实例_
- include_tasks: "init.yml"
_# 一般任务_
- include_tasks: "commons.yml"
_# 备份任务,存在代码才备份_
- include_tasks: "backup.yml"
when: (code_status.rc == 0)
_# 发布任务_
- include_tasks: "deploy.yml"
⑤ .tasks/init.yml
_---_
_# 创建运行账户_
- name: Create {{ project.target.TOMCAT_USER }} user
user: name={{ project.target.TOMCAT_USER }}
_# 检查tomcat是否安装_
- name: Verify that tomcat installed
command: "{{ TOMCAT_HOME }}/bin/catalina.sh version"
environment:
JAVA_HOME: "{{ project.public.JAVA_HOME }}"
register: check_install
failed_when: false
_# 没装就直接解压过去_
- name: Install tomcat
unarchive:
src: tomcat.tar.gz
dest: /usr/share
owner: "{{ project.target.TOMCAT_USER }}"
group: "{{ project.target.TOMCAT_USER }}"
when: check_install.rc != 0
_# 创建实例目录接口(file循环我嫌慢,shell来的快)_
- name: Create {{ PROJECT }} directory
shell: |
mkdir -p {{ project.target.CATALINA_BASE }}/{conf,work,bin,webapps,temp,logs,lib} {{ project.target.LOGPATH }} {{ project.target.CODE_PATH }} &>/dev/null
chown {{ project.target.TOMCAT_USER }}. -R {{ project.target.CATALINA_BASE }} {{ project.target.LOGPATH }} {{ project.target.CODE_PATH }}
_# 推送实例模板文件_
- name: Push {{ PROJECT }} templates
template:
src: "{{ item.src }}"
dest: "{{ item.dest }}"
mode: "{{ item.mode }}"
owner: "{{ project.target.TOMCAT_USER }}"
group: "{{ project.target.TOMCAT_USER }}"
loop:
- { src: server.xml.j2, dest: "{{ project.target.CATALINA_BASE }}/conf/server.xml", mode: 644 }
- { src: catalina.sh.j2, dest: "{{ project.target.CATALINA_BASE }}/bin/catalina.sh", mode: 'u=rwx,g=rx,o=r' }
- { src: env.j2, dest: "{{ project.target.CATALINA_BASE }}/env", mode: 644 }
_# 推送实例一般配置文件_
- name: Push Tomcat config
synchronize:
src: conf/
dest: "{{ project.target.CATALINA_BASE }}/conf"
_# CentOS7使用systemd管理服务_
- name: Copy Systemd script
template:
src: systemd.service.j2
dest: /etc/systemd/system/{{ PROJECT }}.service
mode: 0644
when: ansible_distribution_major_version|int >= 7
_# CentOS6使用SysV脚本_
- block:
- name: Link to Sysvinit
file:
src: "{{ project.target.CATALINA_BASE }}/bin/catalina.sh"
dest: "/etc/init.d/{{ PROJECT }}"
state: link
force: yes
- name: Add to chkconfig
shell: chkconfig _--add {{ PROJECT }}_
when: ansible_distribution_major_version|int < 7
⑥ .tasks/commons.yml
---
_# 设置变量,time相关的不要放入defaults里,因为include是动态取的,会有变化_
- set_fact:
deploy_time: "{{ lookup('pipe','date +%Y%m%d%H') }}"
_# 这里使用lookup获取本地时间,而不是strftime去取,因为客户端时间可能不一致_
git_conf_tmp_path: "/tmp/.config/{{ lookup('pipe', 'date +%s') }}"
_# 拉取git里存放的配置到临时目录_
- name: Git conf
git:
repo: "{{ project.public.GIT_CONF_REPO }}"
dest: "{{ git_conf_tmp_path }}"
ssh_opts: '-o StrictHostKeyChecking=no'
delegate_to: localhost
run_once: true
_# 检查应用代码是否为空_
- name: Verify remote code exists
shell: ls {{ project.target.CODE_PATH }}/* &> /dev/null
register: code_status
failed_when: false
⑦ .tasks/backup.yml
_---_
_# 创建备份目录_
- name: Create Histroy Archive Directory
file:
dest: "{{ project.target.HISTORY_ARCHIVR_DIR }}/{{ deploy_time }}"
state: directory
owner: "{{ project.target.TOMCAT_USER }}"
group: "{{ project.target.TOMCAT_USER }}"
recurse: yes
_# 压缩文件到备份目录下_
- block:
- name: Compress and backup files
archive:
path:
- "{{ PROJECT_BASE }}/*"
dest: "{{ HISTORY_ARCHIVR_DIR }}/{{ deploy_time }}/{{ PROJECT }}.zip"
exclude_path:
- "{{ PROJECT_BASE }}/log"
- "{{ PROJECT_BASE }}/logs"
format: zip
owner: "{{ RUN_USER }}"
group: "{{ RUN_USER }}"
- name: Write Deploy release log
blockinfile:
path: "{{ project.local.deploy_release_log }}/{{ deploy_time }}"
block: |-
{{ release_log | to_nice_json(indent=2) }}
create: yes
delegate_to: localhost
run_once: true
vars:
release_log:
target: "{{ ansible_play_hosts | join(':') }}"
project: "{{ PROJECT }}"
run_user: "{{ RUN_USER }}"
backup_dir: "{{ HISTORY_ARCHIVR_DIR }}/{{ deploy_time }}"
backup_file: "{{ HISTORY_ARCHIVR_DIR }}/{{ deploy_time }}/{{ PROJECT }}.zip"
project_dir: "{{ PROJECT_BASE }}"
backup_time: "{{ deploy_time }}"
env: "{{ ENV }}"
_# 备份该时间段的第一次,一小时内的不重复备份,防止破坏备份文件稳定性_
when: not lookup('pipe','ls ' + project.local.deploy_release_log + '/' + deploy_time + ' &> /dev/null', errors='ignore')
⑧ .tasks/deploy.yml
---
_# 同步构建后的代码_
- name: "Sync {{ project.local.CODE_PATH }} >> {{ project.target.CODE_PATH }}"
synchronize:
src: "{{ project.local.CODE_PATH }}/"
dest: "{{ project.target.CODE_PATH }}"
delete: yes
# 同步git拉取到临时目录的配置文件,同事触发handler,不同系统触发不同的hander任务重启
- name: "Sync {{ git_conf_tmp_path }}/{{ ENV }}/{{ PROJECT }}/ >> {{ PROJECT_BASE }}"
synchronize:
src: "{{ git_conf_tmp_path }}/{{ ENV }}/{{ PROJECT }}/"
dest: "{{ PROJECT_BASE }}"
notify:
- "{{ ansible_distribution_major_version }} Restart Tomcat"
# 设置属主
- name: Set owner >> "{{ project.target.TOMCAT_USER }}"
file:
path: "{{ PROJECT_BASE }}"
recurse: yes
state: directory
owner: "{{ project.target.TOMCAT_USER }}"
group: "{{ project.target.TOMCAT_USER }}"
# 删除临时文件
- name: Remove local config
file:
path: "{{ git_conf_tmp_path }}"
state: absent
delegate_to: localhost
run_once: true
⑨ .handlers/main.yml
---
- name: 7 Restart Tomcat
systemd: name={{ PROJECT }} state=restarted enabled=yes daemon_reload=yes
_# 因为我们是root过去的,6指定下become到运行账户启动_
- name: 6 Restart Tomcat
service: name={{ PROJECT }} pattern=/etc/init.d/{{ PROJECT }} state=restarted sleep=5 enabled=yes
become_user: "{{ project.target.TOMCAT_USER }}"
become: yes
⑩ .templates/server.xml.j2
<?xml version='1.0' encoding='utf-8'?>
__<!--__
_Licensed to the Apache Software Foundation (ASF) under one or more_
_contributor license agreements. See the NOTICE file distributed with_
_this work for additional information regarding copyright ownership._
_The ASF licenses this file to You under the Apache License, Version 2.0_
_(the "License"); you may not use this file except in compliance with_
_the License. You may obtain a copy of the License at_
_http://www.apache.org/licenses/LICENSE-2.0_
_Unless required by applicable law or agreed to in writing, software_
_distributed under the License is distributed on an "AS IS" BASIS,_
_WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied._
_See the License for the specific language governing permissions and_
_limitations under the License._
_-->_
__<!-- Note: A "Server" is not itself a "Container", so you may not__
_define subcomponents such as "Valves" at this level._
_Documentation at /docs/config/server.html_
_-->_
<Server port="{{ project.target.TOMCAT_PORT|int + 1000 }}" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.startup.VersionLoggerListener" />
__<!-- Security listener. Documentation at /docs/config/listeners.html__
_<Listener className="org.apache.catalina.security.SecurityListener" />_
_-->_
_<!--APR library loader. Documentation at /docs/apr.html -->_
<Listener className="org.apache.catalina.core.AprLifecycleListener" SSLEngine="off" />
_<!-- Prevent memory leaks due to use of particular java/javax APIs-->_
<Listener className="org.apache.catalina.core.JreMemoryLeakPreventionListener" />
<Listener className="org.apache.catalina.mbeans.GlobalResourcesLifecycleListener" />
<Listener className="org.apache.catalina.core.ThreadLocalLeakPreventionListener" />
__<!-- Global JNDI resources__
_Documentation at /docs/jndi-resources-howto.html_
_-->_
<GlobalNamingResources>
__<!-- Editable user database that can also be used by__
_UserDatabaseRealm to authenticate users_
_-->_
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml" />
</GlobalNamingResources>
__<!-- A "Service" is a collection of one or more "Connectors" that share__
_a single "Container" Note: A "Service" is not itself a "Container",_
_so you may not define subcomponents such as "Valves" at this level._
_Documentation at /docs/config/service.html_
_-->_
<Service name="Catalina">
_<!--The connectors can use a shared executor, you can define one or more named thread pools-->_
__<!--__
_<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"_
_maxThreads="150" minSpareThreads="4"/>_
_-->_
__<!-- A "Connector" represents an endpoint by which requests are received__
_and responses are returned. Documentation at :_
_Java HTTP Connector: /docs/config/http.html (blocking & non-blocking)_
_Java AJP Connector: /docs/config/ajp.html_
_APR (HTTP/AJP) Connector: /docs/apr.html_
_Define a non-SSL/TLS HTTP/1.1 Connector on port 8080_
_-->_
<Connector port="{{ project.target.TOMCAT_PORT }}"
protocol="org.apache.coyote.http11.Http11NioProtocol"
maxThreads="3000" minSpareThreads="500"
acceptCount="1000" maxKeepAliveRequests="1"
connectionTimeout="20000" enableLookups="false"
URIEncoding="UTF-8" redirectPort="8443" />
_<!-- A "Connector" using the shared thread pool-->_
__<!--__
_<Connector executor="tomcatThreadPool"_
_port="8080" protocol="HTTP/1.1"_
_connectionTimeout="20000"_
_redirectPort="8443" />_
_-->_
__<!-- Define a SSL/TLS HTTP/1.1 Connector on port 8443__
_This connector uses the NIO implementation that requires the JSSE_
_style configuration. When using the APR/native implementation, the_
_OpenSSL style configuration is required as described in the APR/native_
_documentation -->_
__<!--__
_<Connector port="8443" protocol="org.apache.coyote.http11.Http11NioProtocol"_
_maxThreads="150" SSLEnabled="true" scheme="https" secure="true"_
_clientAuth="false" sslProtocol="TLS" />_
_-->_
__<!-- Define an AJP 1.3 Connector on port 8009__
_<Connector port="8009" protocol="AJP/1.3" redirectPort="8443" /> -->_
__<!-- An Engine represents the entry point (within Catalina) that processes__
_every request. The Engine implementation for Tomcat stand alone_
_analyzes the HTTP headers included with the request, and passes them_
_on to the appropriate Host (virtual host)._
_Documentation at /docs/config/engine.html -->_
__<!-- You should set jvmRoute to support load-balancing via AJP ie :__
_<Engine name="Catalina" defaultHost="localhost" jvmRoute="jvm1">_
_-->_
<Engine name="Catalina" defaultHost="localhost">
__<!--For clustering, please take a look at documentation at:__
_/docs/cluster-howto.html (simple how to)_
_/docs/config/cluster.html (reference documentation) -->_
__<!--__
_<Cluster className="org.apache.catalina.ha.tcp.SimpleTcpCluster"/>_
_-->_
__<!-- Use the LockOutRealm to prevent attempts to guess user passwords__
_via a brute-force attack -->_
<Realm className="org.apache.catalina.realm.LockOutRealm">
__<!-- This Realm uses the UserDatabase configured in the global JNDI__
_resources under the key "UserDatabase". Any edits_
_that are performed against this UserDatabase are immediately_
_available for use by the Realm. -->_
<Realm className="org.apache.catalina.realm.UserDatabaseRealm"
resourceName="UserDatabase"/>
</Realm>
<Host name="localhost" appBase="{{ PROJECT_BASE }}"
unpackWARs="true" autoDeploy="true">
__<!-- SingleSignOn valve, share authentication between web applications__
_Documentation at: /docs/config/valve.html -->_
__<!--__
_<Valve className="org.apache.catalina.authenticator.SingleSignOn" />_
_-->_
__<!-- Access log processes all example.__
_Documentation at: /docs/config/valve.html_
_Note: The pattern used is equivalent to using pattern="common" -->_
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
{#-
这是是虚拟路径的配置,只有指定了tomcat_virtual_path才生效,
默认TOMCAT_VIRTUAL_PATH为空,tomcat_vittual_path做为jenkins参数化构建传递,
格式为: /path:/docBase,比如,
项目名称为web-8080-static,代码路径为/data/web-8080-nc/static,那么格式如下:
/static:/static
多个路径格式为:
/static:/static,/nc:/nc
-#}
{% if project.target.TOMCAT_VIRTUAL_PATH != "" %}
{% for vpath in project.target.TOMCAT_VIRTUAL_PATH.split(',') %}
<Context path="{{ vpath.split(':')[0] }}" docBase="{{ PROJECT_BASE }}{{ vpath.split(':')[1] }}" reloadable="true" crossContext="true"></Context>
{% endfor %}
{% endif %}
_<!--<Context path="" docBase="{{ PROJECT_BASE }}" reloadable="true" crossContext="true"></Context>-->_
</Host>
</Engine>
</Service>
</Server>
&nbs版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!p;⑪ .templates/catalina.sh.j版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!2
#!/bin/bash
_# Provides: {{ PROJECT }}_
_# chkconfig: - 55 25_
_# Default-Start: 2 3 4 5_
_# Default-Stop: 0 1 6_
_# Short-Description: Start Tomcat._
_# Description: Start the Tomcat servlet engine._
_### END INIT INFO_
_# Source function library._
. /etc/rc.d/init.d/functions
program="{{ PROJECT }}"
export TOMCAT_USER="{{ project.target.TOMCAT_USER }}"
export CATALINA_PID="{{ project.target.CATALINA_BASE }}/bin/$program.pid"
export TOMCAT_HOME={{ TOMCAT_HOME }}
export CATALINA_HOME=$TOMCAT_HOME
export CATALINA_BASE={{ project.target.CATALINA_BASE }}
export CATALINA_LOGBASE={{ project.target.CATALINA_LOGBASE }}
export JAVA_OPTS="{{ project.public.JAVA_OPTS }} -Dcatalina.logbase=$CATALINA_LOGBASE -Djava.security.egd=file:/dev/./urandom"
{% if project.public.JAVA_HOME != '' %}
export JAVA_HOME={{ project.public.JAVA_HOME }}
{% else %}
source /etc/profile
{% endif %}
_# Set Tomcat environment._
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$CATALINA_HOME
export CATALINA_OUT=$CATALINA_LOGBASE/catalina.out
export PATH=$PATH:$JAVA_HOME/bin:/usr/bin:/usr/lib/bin
_# locale环境,防止中文写日志乱码问题_
export LANG=en_US.UTF-8
export LC_CTYPE="en_US.UTF-8"
export LC_NUMERIC="en_US.UTF-8"
export LC_TIME="en_US.UTF-8"
export LC_COLLATE="en_US.UTF-8"
export LC_MONETARY="en_US.UTF-8"
export LC_MESSAGES="en_US.UTF-8"
export LC_PAPER="en_US.UTF-8"
export LC_NAME="en_US.UTF-8"
export LC_ADDRESS="en_US.UTF-8"
export LC_TELEPHONE="en_US.UTF-8"
export LC_MEASUREMENT="en_US.UTF-8"
export LC_IDENTIFICATION="en_US.UTF-8"
export LC_ALL=
_# cache_
cache_dir="${CATALINA_BASE}/work"
_# check --no-daemonize option_
args=$2
_# sctipt_
catalinaScript="${TOMCAT_HOME}/bin/catalina.sh"
get_pid() {
ps -eo pid,cmd | grep java | grep "${CATALINA_BASE}" | grep -Pv "grep|python" | awk '{print $1}'
}
run_program() {
if [[ "${args}"x == "--no-daemonize"x ]];then
${catalinaScript} start 2>&1 | tee -a $CATALINA_OUT
tailf $CATALINA_OUT
else
nohup ${catalinaScript} start &>> $CATALINA_OUT
return $?
fi
}
run_or_not() {
pid=$(get_pid)
if [[ ! ${pid} ]];then
return 1
else
return 0
fi
}
start() {
run_or_not
if [[ $? -eq 0 ]];then
pid=$(get_pid)
success;echo -e "[\e[0;32m${pid}\e[0m] Tomcat ${program} is running..."
else
echo -n 'Start Tomcat.'
run_program
if [[ $? -eq 0 ]];then
sleep 5
pid=$(get_pid)
if [[ "$pid"x != x ]];then
flag=success
else
flag=failure
$flag;echo -e "Success Start Tomcat ${program}, but it exists.!"
return 1
fi
else
flag=failure
fi
$flag;echo -e "[\e[0;32m$pid\e[0m] Start Tomcat ${program}"
fi
}
stop() {
run_or_not
if [[ $? -eq 0 ]];then
${catalinaScript} stop |& tee -a $CATALINA_OUT
sleep 5
run_or_not
if [[ $? -eq 0 ]];then
pid=$(get_pid)
kill -9 $pid
echo -e "Stop Failed...Killing Process [\e[0;32m$pid\e[0m]..."
fi
success;echo -e "Stop Tomcat $program"
else
failure;echo -e "Tomcat $program is not running."
fi
}
status() {
run_or_not
if [[ $? -eq 0 ]];then
pid=$(get_pid)
success;echo -e "[\e[0;32m${pid}\e[0m] Tomcat $program is running..."
return 0
else
failure;echo -e "Tomcat $program not running."
return 1
fi
}
case $1 in
start)
start
;;
stop)
stop
;;
status)
status
;;
restart)
stop
start
;;
*)
echo "Usage: $0 {start [--no-daemonize]|stop|status|restart}"
return 1
esac
⑫ .templates/systemd.service.j2
[Unit]
Description={{ PROJECT }}
After=network.target
[Service]
ExecStart={{ project.target.CATALINA_BASE }}/bin/catalina.sh start --no-daemonize
ExecStop={{ project.target.CATALINA_BASE }}/bin/catalina.sh stop
_#WorkingDirectory={{ project.target.CATALINA_BASE }}_
_#Restart=on-failure_
_#RestartSec=30_
User={{ project.target.TOMCAT_USER }}
Group={{ project.target.TOMCAT_USER }}
[Install]
WantedBy=multi-user.target
⑬ .templates/env.j2(描述信息而已)
project_name="{{ PROJECT }}"
tomcat_port="{{ project.target.TOMCAT_PORT }}"
control_port="{{ project.target.TOMCAT_PORT|int + 1000 }}"
project_base="{{ PROJECT_BASE }}"
log_base="{{ project.target.CATALINA_LOGBASE }}"
last_update="{{ '%Y-%m-%d %H:%M' | strftime }}"
发布演示
大部分的工作都OK了,由于我们没有沿用之前jenins中账号密码的方式去验证,所以我们需要先做ssh key的信任,这里提供一个playbooks
ssh_keys.yml
---
- hosts: "{{ target }}"
gather_facts: false
tasks:
- name: Create user if not exists
user: name={{ luser }} shell=/bin/bash generate_ssh_key=yes
register: local
delegate_to: localhost
- name: Ensure remote user exists
user: name={{ ruser }} shell=/bin/bash
- name: Write key to authorized_keys
authorized_key:
user: "{{ ruser }}"
state: present
key: "{{ local.ssh_public_key }}"
manage_dir: yes
运行方式:
ansible-playbook ssh_keys.yml -e 'target=你远程的服务器 luser=jenkins ruser=root' -u root -k
_# 其中target是你远程服务器的地址,luser是你jenkins运行账户,ruser是远程账户_
发布预览:
查看服务:
查看备份日志
查看备份文件:
回滚概述
基于前面发布时做了备份处理,那么这里说说怎么做回滚。我们知道,备份是很简单的,但是怎么去根据策略动态去回滚到我们备份的历史版本呢?
在这之前,我们先考虑一个问题,回滚需要哪些东西?
- 备份文件:备份的代码是基础,备份都没有怎么谈回滚?
- 备份的路径:需要获取的备份代码的所在位置
- 备份的还原点:即这是哪一次的备份,归档应该至少有一份
- 代码的所在路径:备份的代码需要还原到哪里去
- 应用的所在服务器地址:我们需要在哪台机器上做回滚的操作
- 应用名称:我们需要回滚哪个应用,一台服务器也可能有多个应用
- 还原的环境:我们所还原的应用是哪个环境的
- 应用的类型:我们回滚后可能会需要做重启服务的操作,不同类型的服务启动方式千差万别,所以还可能需要描述一个类型去区分启动方式(方便的是我们服务都写入了s版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!ystemd)
- 启动账户:通过什么用户去启动应用
我们在备份时候写入了一个备份日志,日志的内容大概如下:
_# cat 2019053018_
_# BEGIN ANSIBLE MANAGED BLOCK_
{
"backup_dir": "/data/deploy_release_library/web-8080-nc/2019053018",
"backup_file": "/data/deploy_release_library/web-8080-nc/2019053018/web-8080-nc.zip",
"backup_time": "2019053018",
"env": "UAT",
"project": "web-8080-nc",
"project_dir": "/data/web-8080-nc",
"run_user": "devops",
"target": "10.18.4.50"
}
_# END ANSIBLE MANAGED BLOC_
可以看到,这个日志里有我们需要的大部分内容,接下来就好办了,只要我们通过Ansible动态读取到这个文件,就能根据这个JSON的东西来做应用回滚。
回滚流程
回滚任务配置
Jenkins Job的配置
这里我们使用Active choice
来读取$JENKINS_HOME/jobs
下的名称,拆分出相应的内容来拼接出环境
,分组名称
,以及应用名称
,并到/data/deploy_release_log
下动态读取出备份日志传递给Ansible.
1.获取环境名称
2.获取分组名称
3.获取应用名称
4.从归档目录里获取备份日志列表
5.获取应用名称的描述信息(可选)
6.Ansible Plugin配置
点开高级,附加参数里,添加指向到备份日志的extra vars
(通过拼接变量名)
Role配置文件说明
playbooks
└──manager-rollback.yml _# 入口文件_
roles
└──manager-rollback
├── defaults
│ └── main.yml _# 默认变量_
├── README.md
└── tasks
├── other-server.yml _# 其他服务的启动方式(之前说的,可能有些服务启动方式一样)_
├── general.yml _# 一般程序的启动(我这里是指注册到systemd里服务)_
└── main.yml _# 主task文件_
① .manager-rollback.yml
---
_# target通过备份日志里的json获取_
- hosts: "{{ target }}"
_# 直接root去操作_
remote_user: root
roles:
- manager-rollbac
② .defaults/main.yml
---
_# defaults file for rollback_
_# 备份日志(那个json)里如果指定了app_type,否则全部当一般应用_
APP_TYPE: "{{ app_type | default('general') }}"
③ .tasks/main.yml
_---_
_# 有些系统可能没装解压软件_
- name: Install unzip
yum: name=unzip
_# 将备份文件解压到项目路径下覆盖_
- name: Rollback {{ project }} to {{ backup_time }}
unarchive:
src: "{{ backup_file }}"
dest: "{{ project_dir }}"
owner: "{{ run_user }}"
group: "{{ run_user }}"
remote_src: yes
_# 判断应用类型,默认调用general_
- include: "{{ APP_TYPE }}.yml"
④ .tasks/general.yml
---
_# 注册到systemd的一般服务启动_
_# CentOS7通过systemd启动服务_
- name: 7 Restart service
systemd: name={{ project }} state=restarted
when: ansible_distribution_major_version|int >= 7
_# CentOS6通过service启动服务_
- name: 6 Restart service
service: name={{ project }} pattern=/etc/init.d/{{ project }} state=restarted sleep=3
when: ansible_distribution_major_version|int < 7
become_user: "{{ run_user }}"
become: yes
⑤ .tasks/other-server.yml(这里是个示例,可以自己拓展)
---
_# 这里是一个示例,备份日志里还写入了该程序的启动脚本在哪_
_# manager_script也是插入到了备份日志里。_
- name: Restart GciTask
shell: bash {{ manager_script }} -t all -a restart
args:
chdir: "{{ manager_script | dirname}}"
become_user: "{{ run_user }}"
become: yes
回滚演示
首先,我们进入应用路径/data/web-8080-nc/nc下,修改一个文件,我们插入了一段注释
接下来,我们回滚到昨天的版本
我们看下文件是否还原
可以看到,文件正确的还原了!而且服务也正常的在启动中
说在最后的话
其实我们不管是tomcat还是其他类型的应用,备份回滚策略都大同小异,另外我们在填充备份日志的时候可以添加任何自己需要的字段,然后传递给Ansible做各种各样的判断和后续操作,十分的方便!本章也到此结束,如果有什么需要改进或者建议,欢迎留言。