概述
在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做各种各样的判断和后续操作,十分的方便!本章也到此结束,如果有什么需要改进或者建议,欢迎留言。
来源:oschina
链接:https://my.oschina.net/jack088/blog/4810694