概述
笔者在工作过程中,遇到了一个Web应用程序的部署和运维的需求。
业务应用程序是一个Java语言和平台开发的Web应用,程序通过War包提供。运行环境所使用的应用服务器是Tomcat,数据库系统使用MySQL。系统部署后,需要修改相关的应用程序配置,来连接应用系统和数据库系统。此外,在前端还使用了nginx系统作为Web应用的入口和反向代理程序,来提供更好的系统安全性和可扩展性。这也是一种典型的Web应用和部署的模式。
原有的部署方案中,为了更加充分的利用当前的硬件系统资源,在应用程序所在主机环境中,还使用了多实例的部署方式。基本方法就是复制多个Tomcat文件夹,在每个文件夹中,修改使用不同的服务器配置(主要的外部提现在不同的侦听端口),并且在每个Tomcat实例文件夹中的应用文件夹中部署应用程序。最后,修改Nginx配置,来使用这些多个tomcat的实例的端口,作为后端应用程序的端口,来达到负载均衡的目的。
但是,这一方案的主要问题是应用程序的维护不是很方便。除了需要维护多个Tomcat实例之外,我们还需要管理和维护多个在不同实例中运行的应用程序实例。但其实在绝大多数场景中,这些应用程序的执行方式和配置信息都是一致的。在理想的情况下,我们认为应该可以"共享"这个应用程序在多个Tomcat实例环境中运行,这样就只需要维护一份应用程序代码就可以了,更加简单方便,并且能够提高程序的一致性,避免由于代码的差异,造成用户在使用时可能遇到的不一致的问题。
本文想要研究和探讨的重点,就是基于这个思路,在实际应用环境中的实现和操作过程,以及在此过程中可能遇到和需要注意的问题。作为一个比较高阶的讨论,本文假设读者已经具备一定的基础,比如熟悉和了解一般的Java Web应用程序的运行部署方式、相关的软件和相关操作,不会在这些基础内容方面展开说明,而是重点讨论多实例的配置和部署方面的内容。
优化多实例部署
根据前面部署工作优化的基本思路,本文提出具体实施方案如下:
将整个程序系统分为主运行环境和配置环境,其中主运行环境包括了Tomcat主程序和业务应用程序文件,配置环境包括了各个实例的配置相关信息和工作环境,然后通过设置不同的启动方式,来让不同的Tomcat实例在启动的时候,使用不同的配置和运行环境。
下面是这一思路的具体操作:
1. 准备Tomcat主环境
我们假设操作系统中,已经安装好了JDK和Java环境。我们这里用的是JDK1.8。Tomcat版本使用8.5.99。
和普通的Tomcat安装方式一样,我们从Tomcat的官方网站下载对应操作系统的二进制压缩版本。可以将其解压到一个文件夹中,并将其命名为 "tomcat_main" 作为Tomcat主执行环境。
在这一阶段的操作,和普通的Tomcat环境没有太大的区别。配置完成后的基本情况如下:
js
yanjh@d10-tw:/webapp/tomcat_main$ java -version
openjdk version "1.8.0_442"
OpenJDK Runtime Environment (Temurin)(build 1.8.0_442-b06)
OpenJDK 64-Bit Server VM (Temurin)(build 25.442-b06, mixed mode)
yanjh@d10-tw:/webapp/tomcat_main$ tree -L 1
.
├── bin
├── BUILDING.txt
├── conf
├── CONTRIBUTING.md
├── lib
├── LICENSE
├── logs
├── NOTICE
├── README.md
├── RELEASE-NOTES
├── RUNNING.txt
├── temp
├── webapps
└── work
在实际工作时,基于这个主执行环境,通过启动时不同的环境变量,来区分启动时,使用何种配置方式和相关的工作文件夹,达到单主机多配置多实例的目的。这是下一阶段的主要工作。
2. Tomcat配置环境
在配置阶段,笔者创建了一个"tomcat_conf"文件夹,作为实例的主配置文件夹。在这个文件夹中,为各个实例创建对应的配置信息和工作子文件夹。为了方便区分,我们可以使用的服务端口号来区分各个实例配置。这些实例配置文件夹的示例如下:
js
yanjh@d10-tw:/webapp$ tree tomcat_conf -L 2
tomcat_conf
├── 6080
│ ├── conf
│ ├── logs
│ ├── temp
│ ├── webapps -> /webapp/tomcat_main/webapps/
│ └── work
└── 6090
├── conf
├── logs
├── temp
├── webapps -> /webapp/tomcat_main/webapps/
└── work
12 directories, 4 files
在笔者的项目中,初步规划了两个Tomcat实例,分别侦听6080和6090两个端口。所以创建了两个端口命名的子文件夹。在这些子文件夹中,conf是复制的Tomcat程序文件夹中的内容,后面我们需要修改这其中的配置文件和信息。其他的如logs、temp和work都是创建的空的用于应用程序的工作文件夹。
3 服务器配置
在配置信息文件夹中,需要为每个tomcat的示例,进行适配性的修改。默认情况下,修改的内容,就是服务器的应用监听端口和管理端口。这其中最重要的内容,就是server.xml配置文件,在其中可以配置服务端口和连接器端口。这些配置信息的示例如下:
js
## server.xml (in 6080 folder)
<Server port="6085" shutdown="SHUTDOWN">
<Connector port="6080" protocol="HTTP/1.1"
...
其他的配置信息,可以根据需要进行配置,只需要注意对于程序运行需要保持一致就可以了。
4 业务程序目录
为了共享业务程序,需要将业务程序,都按照在业务程序文件夹中,这里使用的Tomcat的原生程序文件夹:webapps,其实可以也可以配置成为任意文件夹,但由于需要额外的设置,笔者就没有修改。而且为了方便起见,在配置信息文件夹中,创建了一个软连接,指向了原生程序文件夹。对于这些Tomcat实例而言,它们都使用同一份应用程序文件夹。
创建这个链接的命令示例如下:
js
cd tomcat_conf/6080
ln -s /webapp/tomcat_main/webapps/ webapps
5 实例启动脚本
这一步是多实例应用实现的关键。
对于Tomcat实例的启动,需要在启动时,设置启动的环境参数,这里使用一个参数化的启动脚本来实现,当执行脚本程序时,脚本程序根据调用的参数,来决定如何配置Tomcat启动的环境变量,达到无冲突的启动多个实例副本的目的。
我们编写的start.sh启动程序脚本的具体内容如下:
js
## 脚本内容: tomcat_conf/start.sh
#!/bin/sh
if [ -z "$1"]; then
echo "Foler Not Set"
exit 1
fi
dir="/webapp/tomcat_conf/$1"
echo "Base Folder: $dir"
if [ ! -d "$dir" ]; then
echo "Folder Not Exist"
exit
fi
export CATALINA_HOME=/webapp/tomcat_main
export CATALINA_BASE=$dir
$CATALINA_HOME/bin/startup.sh
ss -lpnt | grep $1
## 调用方式
./start.sh 6080
脚本的核心比较简单直接,就是根据参数,来选择和配置CATALINA_HOME和CATALINA_BASE两个文件夹,作为Tomcat实例工作的环境,然后调用Tomcat的启动脚本来启动实例。这里没有修改任何Tomcat的原本的配置和脚本,同时为了程序的健壮性适应性,在启动前需要检查参数的可用性。
6 实例的关闭
在技术上,准确的关闭Tomcat实例进程,应当是找到这个运行实例的pid,然后通过kill指令关闭。但考虑到作为网络服务程序,这样操作过于生硬,所以Tomcat一般提供了服务端口(在server.xml中设置)来实现比较平滑的关闭,包括处理完当前的请求等等。
为了简化配置和维护工作,我们这里做了一个简单的约定,就是程序端口号+5,就是这个服务端口。比如这个实例的工作端口是6080,那么其对应的服务端口就是6085。这样我们就可以方便的编写脚本,找到实例对应的关闭端口来实现平滑的关闭操作了。
根据上述的构想,结合tomcat的标准关闭脚本,我们编写的实例关闭脚本的示例代码如下:
js
## 实例关闭程序 tomcat_conf/stop.sh
#!/bin/sh
if [ -z "$1"]; then
echo "Foler Not Set"
exit 1
fi
dir="/webapp/tomcat_conf/$1"
echo "BaseFolder:$dir"
if [ ! -d "$dir" ]; then
echo "Folder Not Exist"
exit
fi
export CATALINA_HOME=/webapp/tomcat_main
export CATALINA_BASE=$dir
$CATALINA_HOME/bin/shutdown.sh -port $(($1+5))
ss -lpnt | grep $1
## 执行调用方式
./stop.sh 6080
7 部署检查
实例启动后,可以简单的使用ps和ss检查当前运行的实例和侦听的端口。
js
yanjh@d10-tw:/webapp$ ps aux | grep tomcat
yanjh 19362 0.2 12.8 6605288 1038632 pts/1 Sl Mar18 3:04 /usr/bin/java -Djava.util.logging.config.file=/webapp/tomcat_conf/6080/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dsun.io.useCanonCaches=false -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -classpath /webapp/tomcat_main/bin/bootstrap.jar:/webapp/tomcat_main/bin/tomcat-juli.jar -Dcatalina.base=/webapp/tomcat_conf/6080 -Dcatalina.home=/webapp/tomcat_main -Djava.io.tmpdir=/webapp/tomcat_conf/6080/temp org.apache.catalina.startup.Bootstrap start
yanjh 19385 0.2 12.3 6605288 998444 pts/1 Sl Mar18 3:06 /usr/bin/java -Djava.util.logging.config.file=/webapp/tomcat_conf/6090/conf/logging.properties -Djava.util.logging.manager=org.apache.juli.ClassLoaderLogManager -Djdk.tls.ephemeralDHKeySize=2048 -Djava.protocol.handler.pkgs=org.apache.catalina.webresources -Dsun.io.useCanonCaches=false -Dorg.apache.catalina.security.SecurityListener.UMASK=0027 -Dignore.endorsed.dirs= -classpath /webapp/tomcat_main/bin/bootstrap.jar:/webapp/tomcat_main/bin/tomcat-juli.jar -Dcatalina.base=/webapp/tomcat_conf/6090 -Dcatalina.home=/webapp/tomcat_main -Djava.io.tmpdir=/webapp/tomcat_conf/6090/temp org.apache.catalina.startup.Bootstrap start
yanjh 21567 0.0 0.0 6072 816 pts/1 S+ 10:29 0:00 grep tomcat
yanjh@d10-tw:/webapp$ ss -lpnt
State Recv-Q Send-Q Local Address:Port Peer Address:Port
LISTEN 0 80 0.0.0.0:3306 0.0.0.0:*
LISTEN 0 128 127.0.0.1:6379 0.0.0.0:*
LISTEN 0 128 0.0.0.0:80 0.0.0.0:*
LISTEN 0 128 0.0.0.0:22 0.0.0.0:*
LISTEN 0 100 *:6080 *:* users:(("java",pid=19362,fd=57))
LISTEN 0 1 [::ffff:127.0.0.1]:6085 *:* users:(("java",pid=19362,fd=65))
LISTEN 0 100 *:6090 *:* users:(("java",pid=19385,fd=57))
LISTEN 0 128 [::1]:6379 [::]:*
LISTEN 0 1 [::ffff:127.0.0.1]:6095 *:* users:(("java",pid=19385,fd=65))
LISTEN 0 128 [::]:80 [::]:*
LISTEN 0 128 [::]:22
8 部署扩展
使用上述的部署模式,再在主机中进行扩展就是非常方便的。只需要再复制一遍实例配置文件夹,修改新的配置信息即可。甚至不用关心启动关闭方式和应用程序部署的问题。
应用程序部署
前面,我们已经看到了如何配置、启动和关闭多个Tomcat实例的方法。剩下的问题,就是应用程序本身的部署和配置了。这里笔者不确定在多实例的环境中,Tomcat还支持应用程序的自动热部署方式,而是采用了常规基于war包的手动部署方式。
当然,为了配合这个多实例的执行方式,我们还可以对执行过程进行简单的优化,比如编写一个部署脚本,来更方便的执行部署过程,示例如下:
js
yanjh@d10-tw:/webapp$ cat deploy2.sh
#!/bin/bash
## 1 Check Deploay File
[[ -f backup/demo.war ]] ||exit 1
## 2 Stop Tomcat Instance
tomcat_conf/stop.sh 6080
tomcat_conf/stop.sh 6090
## 3 Backup Old Application
mv -f tomcat_main/webapps/demo backup/demo_$(date +%Y%m%d)
## 4 Make App Folder
mkdir tomcat_main/webapps/demo
## 5. Deploy App
unzip backup/demo.war -d tomcat_main/webapps/demo
## 6. Copy Config File
cp -av backup/*.properties tomcat_main/webapps/demo/WEB-INF/classes/config/
## 7 Start Tomcat Instance
tomcat_conf/start.sh 6080
tomcat_conf/start.sh 6090
在部署之前,将新的应用程序的war包和相关的配置信息(通常不变),复制到backup文件夹中,然后执行这个部署脚本,就可以将新版本的应用程序部署到tomcat执行环境当中了。
Nginx配置
作为一个完整的部署和应用方案,在前端,需要使用一个反向代理负载均衡的机制,才能够真正的实现多实例应用的方式。这通常通过安装配置Nginx来实现。
我们不讨论常规的Nginx安装配置的过程,主要专注于负载均衡相关的配置操作。下面是典型的配置方式:
nginx.conf
// http 区段
upstream appserver {
ip_hash;
health_check;
server 192.168.10.106:6070;
server 192.168.10.106:6080;
server 192.168.10.106:6090;
}
// server 区段
location /demo {
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Scheme $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass http://appserver;
}
这里的要点如下:
- 首先在http区段,定义一个 upstream 代理服务器,内容包括了后端提供真实服务的端点地址
- 此处选择iphas的负载均衡逻辑
- 设置了主动监控检查,自动管理下线的服务器
- 在server区段,配置某一个端点和路径,使用前面配置的代理服务器
小结
本文作为一个工作笔记,讨论了一种tomcat和其应用的多实例部署的技术方案。包括基本原理,技术特点,操作步骤和需要注意的问题等。还进一步讨论了相关问题如应用程序的脚本化部署和nginx配置等内容。