Jenkins+Ansible 持续集成与自动发布

Stella981
• 阅读 1012

概述

在CI/CD流程中,项目的可持续迭代,版本回档都是十分重要的环节。而网络资源中看着似乎基于jenkins+ansible发布spring boot/cloud类的jar包程序,或者tomcat下的war包的需求挺多的,闲来无事,也说说自己做过的jenkins基于ansible的发布 。

发布流程

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依赖插件

环境配置

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配置

Jenkins+Ansible 持续集成与自动发布

SVN拉取配置

Jenkins+Ansible 持续集成与自动发布

maven构建配置

Jenkins+Ansible 持续集成与自动发布

Ansible插件配置

Jenkins+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是远程账户_

发布预览:

Jenkins+Ansible 持续集成与自动发布

查看服务:

Jenkins+Ansible 持续集成与自动发布

查看备份日志

Jenkins+Ansible 持续集成与自动发布

查看备份文件:

Jenkins+Ansible 持续集成与自动发布

回滚概述

基于前面发布时做了备份处理,那么这里说说怎么做回滚。我们知道,备份是很简单的,但是怎么去根据策略动态去回滚到我们备份的历史版本呢?

在这之前,我们先考虑一个问题,回滚需要哪些东西?

  1. 备份文件:备份的代码是基础,备份都没有怎么谈回滚?
  2. 备份的路径:需要获取的备份代码的所在位置
  3. 备份的还原点:即这是哪一次的备份,归档应该至少有一份
  4. 代码的所在路径:备份的代码需要还原到哪里去
  5. 应用的所在服务器地址:我们需要在哪台机器上做回滚的操作
  6. 应用名称:我们需要回滚哪个应用,一台服务器也可能有多个应用
  7. 还原的环境:我们所还原的应用是哪个环境的
  8. 应用的类型:我们回滚后可能会需要做重启服务的操作,不同类型的服务启动方式千差万别,所以还可能需要描述一个类型去区分启动方式(方便的是我们服务都写入了s版权声明:本文遵循 CC 4.0 BY-SA 版权协议,若要转载请务必附上原文出处链接及本声明,谢谢合作!ystemd)
  9. 启动账户:通过什么用户去启动应用

我们在备份时候写入了一个备份日志,日志的内容大概如下:

_# 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+Ansible 持续集成与自动发布

回滚任务配置

Jenkins Job的配置

这里我们使用Active choice来读取$JENKINS_HOME/jobs下的名称,拆分出相应的内容来拼接出环境分组名称,以及应用名称,并到/data/deploy_release_log下动态读取出备份日志传递给Ansible.

    1.获取环境名称

Jenkins+Ansible 持续集成与自动发布

    2.获取分组名称

Jenkins+Ansible 持续集成与自动发布

 3.获取应用名称

Jenkins+Ansible 持续集成与自动发布

   4.从归档目录里获取备份日志列表

Jenkins+Ansible 持续集成与自动发布

  5.获取应用名称的描述信息(可选)

Jenkins+Ansible 持续集成与自动发布

 6.Ansible Plugin配置

Jenkins+Ansible 持续集成与自动发布

    点开高级,附加参数里,添加指向到备份日志的extra vars(通过拼接变量名)

Jenkins+Ansible 持续集成与自动发布

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下,修改一个文件,我们插入了一段注释

Jenkins+Ansible 持续集成与自动发布

接下来,我们回滚到昨天的版本

Jenkins+Ansible 持续集成与自动发布

我们看下文件是否还原

Jenkins+Ansible 持续集成与自动发布

可以看到,文件正确的还原了!而且服务也正常的在启动中

说在最后的话

    其实我们不管是tomcat还是其他类型的应用,备份回滚策略都大同小异,另外我们在填充备份日志的时候可以添加任何自己需要的字段,然后传递给Ansible做各种各样的判断和后续操作,十分的方便!本章也到此结束,如果有什么需要改进或者建议,欢迎留言。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
3个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Easter79 Easter79
3年前
Twitter的分布式自增ID算法snowflake (Java版)
概述分布式系统中,有一些需要使用全局唯一ID的场景,这种时候为了防止ID冲突可以使用36位的UUID,但是UUID有一些缺点,首先他相对比较长,另外UUID一般是无序的。有些时候我们希望能使用一种简单一些的ID,并且希望ID能够按照时间有序生成。而twitter的snowflake解决了这种需求,最初Twitter把存储系统从MySQL迁移
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Stella981 Stella981
3年前
Django中Admin中的一些参数配置
设置在列表中显示的字段,id为django模型默认的主键list_display('id','name','sex','profession','email','qq','phone','status','create_time')设置在列表可编辑字段list_editable
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
9个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这