Websphere Application Server 环境配置与应用部署最佳实践
在发布一个运行于 WebSphere Application Server 的 J2EE 应用之前,对服务器进行配置和部署应用是必不可少的一个过程,这个过程是非常复杂的。WAS 为用户提供了可视化的管理控制台(Web Admin Console)来完成这一任务。即便如此,开发或部署人员仍需要接受培训,并花费一定的时间和精力来完成这些配置和部署工作。对于一个开发团队来说,如果每个团队成员都要手工的完成环境配置和应用部署,其代价是比较高的。
本文介绍使用 wsadmin 工具配合 Jython 脚本来完成服务器的环境配置和应用部署。配置和部署脚本一经编写,就可以多次重复使用。以自动化的脚本代替手工操作,是提高工作效率的重要一环。在此基础上,本文介绍一种使用 XML 文件对 wsadmin 命令行工具和 Jython 脚本进行扩展,从而实现服务器的环境配置和应用部署的方法。我们为该方法提供了 Jython 实现,相关的脚本已经在 WAS 6.0 和 7.0 版本上验证通过。在文章中,我们将详细阐述这种方法并简要描述 XML 文件格式。此外,本文将说明 XML 文件解析与运行模块的实现原理,以便读者能够添加所需的功能模块。
wsadmin 脚本工具引入于 IBM WebSphere Application Server V5。它是一个接受脚本语言输入的非图形化管理工具,用户可以使用 wsadmin 工具执行那些可以用管理控制台执行的相同任务。
wsadmin 目前只支持 Jython 和 Jacl 两种脚本语言。本文选取 Jython 脚本作为示例代码,对使用自动化脚本进行服务器配置、管理进行说明。我们将会应用部署为例编写 Jython 脚本,即便是第一次接触 Jython 脚本的开发人员也能很快的了解并使用它们。
开发人员可以通过 wsadmin.bat(windows)或 wsadmin.sh(Linux 或 AIX)来启动 wsadmin 工具。这两类文件既可以在 WAS 概要文件的 bin 目录(${profile_root}/bin)中找到,也可以 WAS 的 bin 目录(${app_server_root}/bin)中找到。在 WAS 的 bin 目录下的启动命令在运行时需要指定 -profileName 参数,而在特定概要文件的 bin 目录下的启动命令则不用指定。
wsadmin 工具可以以交互式或批处理式两种方式运行。以交互式运行时,开发人员可以在 wsadmin 工具中运行单条命令。在 windows 系统中启动交互式 wsadmin 工具的命令如下:
- 启动支持 Jacl 命令的 wsadmin 工具:wsadmin.bat
- 启动支持 Jython 命令的 wsadmin 工具:wsadmin.bat –lang jython
此外,开发人员还可以通过 wsamin 工具运行批处理脚本。批处理脚本需要通过 -profile 参数指定:
- 运行 Jacl 脚本:wsadmin.bat –profile sample.jacl
- 运行 Jython 脚本:wsadmin.bat –lang jython –profile sample.py
Jython 语言是 wsadmin 目前所支持的两种脚本语言之一。Jython 解释器为脚本提供了控制流的支持以及各种辅助命令,此外,通过对 wsadmin 工具提供的脚本对象的访问,Jython 扩展了自身功能,从而使开发人员可以利用编写脚本来实现应用部署、服务器的管理和配置。
Jython 语言是 Python 的一个 Java 实现,wsadmin 工具使用的是 Jython V2.1。Jython 是动态类型语言,开发人员不用像 Java 那样声明变量类型,因为变量的类型是运行时决定的。
Jython 的基本数据类型包括数字类型、布尔类型和字符串类型。Jython 的数字类型包括整型和浮点型:
wsadmin>a=1.33
wsadmin>print a
1.33
以上的语句将浮点数 1.33 赋给变量 a,并通过 print 命令在控制台输出变量 a 的值。Jython 中的字符串类型也与 Java 类似,不同的地方在于单引号和双引号在 Jython 中的功能是相同的:"My String" 和 'My String' 在 Jython 中被认为是一样的。Jython 为这些基本类型提供了各种运算符:布尔类型的 or 和 and 运算符、数字类型的+、-、*、/ 和%运算符以及字符串类型的+运算符。
wsadmin>print 'My '+'String'
My String
以上的语句将两个字符做连接并打印在控制台上。除了运算符,Jython 还提供了一系列字符串处理方法,其中常用的有:
- str1.find(str2):返回 str2 在 str1 中第一次出现的位置;
- str1.lower():将 str1 中的字符变成小写并返回;
- str1.replace(str2, str3):将 str1 中的所有 str2 子串替换成 str3;
- str1.split(str2):以 str2 为分隔符将 str1 分隔成列表;
- len(str1):返回 str1 中包含的字符个数;
- cmp(str1, str2):比较 str1 和 str2,如果相同则返回0。
在数字类型和字符串的基础上,Jython 提供了列表类型,列表中的每个元素都可以是数字类型、布尔类型、字符串或是一个子列表。开发人员可以通过中括号来创建列表:
wsadmin>list=['a','b',['c','d'],'e']
wsadmin>print list[0]
a
wsadmin>print list[2][1]
d
以上的语句创建了两个列表,其中一个包含['c','d']两个元素,同时,以该列表为第3个元素(下标为2)创建另一个列表并赋值给变量 list。Jython 也为列表类型提供了各种处理方法,其中常用的有:
- len(list1):返回 list1 中包含的元素个数;
- cmp(list1, list2):如果 list1 和 list2 中包含的元素相同,返回0;
- max(list1):返回 list1 中值最大的元素;
- min(list1):返回 list1 中值最小的元素。
除了提供基础的数据类型,Jython 还对控制流提供支持。Jython 语言支持的控制流语句包括以下几类:
- 条件命令:if-else;
- 循环命令:while、for;
- 错误处理命令:try。
此外,开发人员还可以使用 break、continue 和 pass 语句来调整控制流。我们以 if-else 语句来说明 Jython 控制流命令的使用:
if (len(list) == 0): print 'The length of list is 0.' else: print 'The first element is '+list[0] |
Jython 语言通过 try 语句来完成错误处理。Try 语句可以获取语句执行时的错误,当错误被捕获时,except 句将被执行,否则,else 句都会被执行:
idef testFunc(): div = 0 try: result=10/div except: import sys print 'root cause: ', sys.exc_type, sys.exc_value else: print 'no exception is thrown.' |
在上例中,当 div 为 0 时,except 句会被执行,抛出异常。sys 是 Jython 的内置模块,开发人员可以通过 import 语句来导入内置模块。导入 sys 模块之后,开发人员就可以使用模块中的 exc_type 和 exc_value 属性显示错误信息了。以上语句的执行结果为:
root cause: exceptions.ZeroDivisionError integer division or modulo
wsadmin 工具通过使用下列 WebSphere 特定对象,对基本的脚本语言进行了扩展:
- AdminControl:用于运行操作命令。
- AdminConfig:运行配置 WAS 的命令,用来创建或者修改 WAS 的配置元素。
- AdminApp:应用程序的命令,包括部署、卸载、启动、停止等操作。
- AdminTask:用于 WAS 的系统管理,创建服务器、集群等。
- Help:用于获得命令帮助信息。
这些对象都有可用来执行管理任务的方法。要使用脚本编制对象,需要指定脚本编制对象、方法和方法参数。例如:
AdminConfig.attributes('ApplicationServer')
本文将在接下来的章节中详细介绍这些管理对象的相关方法和参数的使用。
在对 Jython 语言进行介绍之后,我们将对如何利用 Jython 脚本进行应用部署进行说明。一个典型的企业级 J2EE 应用,开发人员在部署阶段常做的事情有:(1)安装或更新应用程序的 ear 包;(2)设置应用的 classloader mode 和 classloader policy;(3)修改 war 包或 EJB 包的 classloader;(4)设置角色和用户的映射。
如果部署人员是首次在 WAS 服务器上部署应用程序,我们应当安装应用的 ear 包。Jython 语言允许开发人员将相关的代码封装成方法。以下就是一个在 WAS 服务器上部署应用的 ear 包的方法 installApp,它的参数是应用名称 appName、ear 文件路径 earFile 和服务器名称 server:
def installApp (server, appName, earFile): # declare global variable global AdminApp # install ear file server_attr = ['-server', server] AdminApp.install(earFile, server_attr) #endDef |
在 WAS 服务器上部署应用程序,是通过调用 AdminApp 对象提供的 install 方法来完成的。在方法体中,我们首先通过 global 关键字将 AdminApp 声明为全局变量,从而使方法体的其它部分可以使用该变量。之后再将服务器名称和 ear 文件路径作为参数传给 AdminApp 对象的 install 方法,完称 ear 包的部署。
假设部署人员需要在 server1 上部署应用 SampleApp,ear 包的路径是 C:\SampleAppEAR.ear,那么该方法的调用语句就是:
installApp('server1', 'SampleApp', 'C:/SampleAppEAR.ear')
如果部署人员因为应用程序的变化而对 ear 包进行更新,我们应当使用 AdminApp 对象的 update 方法更新应用,所需的参数是应用名称 appName 和 ear 文件路径 earFile:
def updateEar(appName, earFile): # declare global variable global AdminApp # update the existing application AdminApp.update(appName, "app", "-operation update -contents "+earFile ) #end update() |
如果应用 SamleApp 已经在 WAS 服务器上安装过,那么更新该应用 ear 包的语句就是:
updateEar('SampleApp', 'C:/SampleAppEAR.ear')
设置应用的 classloader mode 和 classloader policy
在安装或是更新应用的 ear 包之后,部署人员可能需要修改应用的 classloader mode 和 classloader policy。应用在部署时默认的 classloader mode 是 PARENT_FIRST,如果所部署的应用采用不同的 classloader mode,需要在部署时进行设定。Classloader policy 指的是应用部署时 war 包的载入策略,可能的值为 Module 和 Application:
def changeClassLoader(appName, classloaderMode, classloaderPolicy): # declare global variable global AdminConfig appid = AdminConfig.getid("/Deployment:"+appName+"/" ) deployedApp = AdminConfig.showAttribute(appid, "deployedObject") # update classloader policy if(len(classloaderPolicy) > 0): policy = AdminConfig.showAttribute(deployedApp, "warClassLoaderPolicy") if(cmp(classloaderPolicy.strip(), "Module") == 0): policy = "MULTIPLE" elif(cmp(classloaderPolicy.strip(), "Application") == 0): policy = "SINGLE" #end if AdminConfig.modify(deployedApp, [["warClassLoaderPolicy", policy]]) #end if # update classloader mode if(len(classloaderMode) > 0): classLoader = AdminConfig.showAttribute(deployedApp, "classloader") modeAttr = ["mode", classloaderMode] AdminConfig.modify(classLoader, [modeAttr]) #end if #end changeClassLoader() |
在更新 classloader 配置时,我们首先通过 appName 获取应用配置的 ID,并通过 ID 获得对应的配置信息对象 deployedApp。如果参数中的 classloaderMode 和 classloaderPolicy 非空,我们就通过 AdminConfig 对象的 modify 方法修改相应的配置。
如果我们需要配置 classloader mode 和 policy 分别为 PARENT_LAST和Application,我们可以使用以下语句:
changeClassLoader('SampleApp', 'PARENT_LAST', 'Application')
修改 war 包或 EJB 包的 classloader mode
使用 wsadmin 工具除了可以指定应用的 classloader mode,还可以为应用内的 war 包和 EJB 包设置特定的 classloader mode,需要传入的参数包括应用名称、模块名称和指定的classloader mode:
def changeModuleClassloaderMode(appName, moduleName, classloaderMode): # declare global variable global AdminConfig if len(classloaderMode) > 0: appid = AdminConfig.getid("/Deployment:"+appName+"/" ) deployedApp = AdminConfig.showAttribute(appid, "deployedObject") modules = AdminConfig.showAttribute(deployedApp, "modules") moduleList = modules[1:len(modules)-1].split(" ") for module in moduleList: uri = AdminConfig.showAttribute(module, "uri") if cmp(moduleName, uri) == 0: cmode = AdminConfig.showAttribute(module, "classloaderMode") if(cmp(cmode, classloaderMode) != 0): print "Modifying classloader for module: " + uri AdminConfig.modify(module, [["classloaderMode", classloaderMode]]) #end if #end if #end for #end if #end def |
修改模块的 classloader mode 与修改应用的很类似。假设 SampleAppEar.ear 中包含一个 web 模块 SampleWeb,那么我们可以用以下的语句修改它的 classloader mode:
changeModuleClassloaderMode('SampleApp', 'SampleWeb', 'PARENT_LAST')
一些应用需要对用户权限进行控制,需要将特定的角色映射到用户或用户组上,我们可以通过 AdminApp 对象的 edit 方法修改 MapRolesToUsers 属性来实现这一配置。需要传入的参数为角色列表 roles 和用户列表 users:
def mapRolesToUsers(roles, users): # declare global variable global AdminApp if(len(roles) > 0): roles_attr = [] for i in range(len(roles)): role = roles[i] user = users[i] if(len(role) > 0): print "Mapping role: " + role + " to user: " + user role_attr = [role, "AppDeploymentOption.Yes", "AppDeploymentOption.Yes", "", user] roles_attr.append(role_attr) #end if #end for mapRoles_attr = ["-MapRolesToUsers", roles_attr] AdminApp.edit(self.app.name, mapRoles_attr ) #end if #end def |
如果我们需要将角色 role1, role2 映射至用户 user1, user2,那么我们可以调用以下语句:
mapRolesToUsers(['role1', 'role2'], ['user1', 'user2'])
更新应用的 ear 包、设置应用的 classloader mode 和 classloader policy、设置 war 包或 EJB 包的 classloader mode、设置角色与用户的映射等等是部署一个 web 应用的典型步骤。通过对以上 Jython 代码的分析,我们发现完成特定步骤的代码只需要编写一次,在部署不同应用时,我们只需要传入不同的参数即可。
为了分离脚本代码和不同应用的特定配置,使用配置文件是不错的选择。Properties 文件是常用的配置文件格式,然而面对较为复杂的、包含层次的应用部署配置(应用的配置,应用内的 war 包,EJB 包的配置,应用的角色与用户的映射等等),properties 文件相对简单的结构无法完整的描述我们所需的结构。XML 文件本身所具有的结构特性和对语义的描述能力使得它成为我们最好的选择。接下来,本文将介绍如何通过 XML 文件对 wsadmin 工具和 Jython 脚本进行扩展。
使用 XML 文件扩展 wsamin 工具与 Jython 脚本
通过以上对应用部署脚本的分析,我们发现进行同一步骤配置的脚本都非常相似,所不同的仅仅是配置所需的参数(例如应用的名称和 ear 文件的路径)。如果能将某一类型的配置参数抽取出来,对已有的脚本程序进行抽象,通过 XML 文件来配置管理它们,那么接下来的开发和部署人员将无需重复编写雷同的 Jacl 或 Jython 脚本,只需要按照预定义的XML配置文件来提供外部参数就可以重用已有的脚本,完成 WAS 配置和应用部署的过程。
我们仍然以应用部署为例说明如何通过 XML 文件来配置应用部署的信息。我们首先创建一个<app>节点,该节点拥有 name、ear、classloader、classloader-policy 以及 map-to 等属性,这些属性是应用中不同的包所共有的属性,因此我们将它们放在<app>节点中。<app>节点可能拥有<war>、<ejb>、<rar>、<role-mapping>以及<conn-factory>等子节点,这些子节点都具有各自不同的属性以及属性值。通过这些节点的属性值,我们可以配置应用部署的信息:
<app name="SampleApp" ear="C:\SampleAppEAR.ear" classloader="PARENT_FIRST" classloader-policy="Module" map-to="WebSphere:cell=myCell,node=myNode,server=myServer"> <war name="SampleWar" classloader="PARENT_LAST" file="Sample.war" /> <ejb name="SampleEJB" file="SampleEJB.jar" jndi-name="eis/SampleEJB" auth-data-alias="jdbc/SAMPLE_DATA_SOURCE" /> <rar name="SampleRar" file="SampleRar.rar" conn-factory-name="sample conn factory" jndi-name="jndi/SAMPLE_CONN_FACTORY" /> <role-mapping> <map user="user1" role="role1" /> <map user="user2" role="role2" /> </role-mapping> <conn-factory name="SampleAppConnectionFactory" auth-data-alias="sample auth data"> <property-set> <property name="ApplicationServerHost" value="127.0.0.1" /> <property name="Client" value="600" /> </property-set> </conn-factory> </app> |
通过 XML 的结构特性,我们可以对应用部署不同层次的信息进行配置。通过读取这些配置,我们可以传递给 Jython 脚本正确的参数,从而完成 WAS 服务器上的应用部署。我们最后选用了 ANT 工具来完成读取 XML 文件并调用 Jython 脚本的过程,本文将在接下来的章节中进行详细的介绍。
通过 wsadmin 完成服务器配置和应用部署,除了需要对应用部署的各种参数进行配置,还需要指定服务器的单元 cell、节点 node 和服务器名称、设置 WAS 环境变量、配置 J2C 认证数据和数据库连接等等,接下来,我们对目前所支持的配置一一进行说明:
- 服务器配置
在现实中,应用部署的目标常常是服务器或是服务器集群(cluster)。同一个应用的目标服务器通常都具有相同的单元名称,因此我们可以配置服务器目标如下:
<dest> <cell name="myCell"> <cluster name="myCluster" /> <server name="myServer" node="myNode" /> </cell> </dest> |
- WAS 环境变量配置
WAS 环境变量是一个键值对,假设我们需要对 DB2 JDBC driver 路径进行设定,我们可以配置如下:
<env> <var name="DB2_JDBC_DRIVER_PATH" value="C:\IBM\SQLLIB\java" /> </env> |
- WAS 安全性配置
目前,我们所支持的 WAS 安全配置是 J2C 认证数据。J2C 认证数据包含的属性包括了名称(alias)、用户名(user-id)、密码(pw)和描述(desc)。如果需要定义一个 J2C 认证数据 sample auth data,我们可以配置如下:
<security> <j2c-auth-data alias="sample auth data" user-id="myUser" pw="myPassword" desc="sample auth data" /> </security> |
- WAS 资源配置
常用的 WAS 资源配置包括 JDBC Provider、JDBC 数据源、J2C 资源适配器、J2C 连接工厂、MQ 队列连接工厂和 MQ 队列。我们以 JDBC Provider 为例说明 WAS 资源配置:JDBC Provider 所包含的属性有名称(name)、实现类(implementation class)、类路径(classpath)和描述(desc)。一个 JDBC Provider 配置示例如下:
<jdbc-provider name="DB2 Legacy CLI-based Type 2 JDBC Driver" impl-class="COM.ibm.db2.jdbc.DB2ConnectionPoolDataSource" classpath="${DB2_JDBC_DRIVER_PATH}/db2java.zip" desc="DB2 Legacy CLI-based Type 2 JDBC Driver" /> |
在本文提供的源代码中,您可以找到示例的配置文件和相关的 DTD。你可以通过 DTD 获知不同的 WAS 资源配置所需的属性值。
为了能使读者能够方便的添加所需的功能模块,我们接下来将说明 XML 配置文件解析与运行模块的实现原理。我们采用的 XML 配置文件解析和运行模块也是用 Jython 脚本实现的(您可以在本文的附件中找到源代码),Jython 是 Python 的 Java 实现,Jython 的实现者们一直追求的目标就是能够无缝的调用强大的 Java 类库。在 Java 类库的支持下,我们得以在 Jython 脚本中轻松的对 XML 文件进行分析和处理。
XML 解析模块采用 XML 的 DOM 方法对配置文件进行解析。通过 Java 类库所提供的 DocumentBuilder,我们将 XML 文件 parse 成 Document 对象,通过对 Document 对象的分析,我们可以得到服务器资源和应用部署的信息:
def loadConfig(filepath): try: factory = DocumentBuilderFactory.newInstance(); builder = factory.newDocumentBuilder() fis = FileInputStream(filepath) document = builder.parse(fis) document.normalize() node = document.getDocumentElement() except: print "Exception: ", sys.exc_type, sys.exc_value print "Configuration file parsing failed." sys.exit() #end except ......#get config info from nodes #end def |
我们以应用部署为例介绍 XML 解析模块:DeployConfig 是 XML 解析模块中的核心类,根据 Node 接口返回的节点名,DeployConfig 生成一个 Application 实例。Application 实例解析本节点所包含的子元素,并根据子元素的节点名分别生成 War、Rar、EJB 和 RoleMapping 的实例。
我们以应用部署为例对文件的运行模块进行分析。DeployConfig 是运行模块提供给外界的接口,当运行模块开始部署应用时,它将调用 Application 对象的 deploy 方法。如果该应用是首次在 WAS 服务器上包的connection factory进行部署,那么 Application 对象将调用自身的 installEAR 方法安装 ear 包,否则将调用 updateEAR 方法更新 ear 包。在安装或更新完 ear 包之后,deploy 方法调用 RoleMapping 对象的 mapRoles 方法完整映射。
应用的 ear 包所包含的模块需要映射到对应的服务器上,deploy 方法将调用自身的 mapModulesToServers 来完成这一步骤。之后,deploy 方法依次修改应用和各个模块的 classloader 配置。如果其中的 EJB 模块需要调用某些服务器资源,mapResourcesToEJB 方法将被调用,此外,如果应用需要通过资源适配器访问外部资源,那么 deploy 将配置资源适配器对应的 rar 包的 connection factory。这些常用步骤完成之后,我们在 WAS 服务器上部署应用的过程就告一段落了。
通过 wsadmin 工具配置 WAS 服务器与部署应用,除了需要对服务器资源和应用信息进行配置,还需要指定 wsadmin.bat(或wsadmin.sh)所在的目录,deployment manager 所在的 host 以及连接 deployment manager 时所需的用户名和密码。
为了避免每次运行 Jython 脚本都需要在命令行中指定以上的参数,我们选择 ANT 工具作为我们的运行工具。我们将以上这些参数配置成 ANT 脚本的 property 元素,并在运行 Jython 脚本时作为参数传入:
<target name="init"> <!-- The path where wsadmin.sh(.bat) is in --> <property name="wsadmin.path" value="C:/SDP/pf/wps/bin/wsadmin.bat" /> <!-- Connect to which WAS server host to update target application --> <property name="wsadmin.host" value="127.0.0.1" /> <!-- The user name to run wsadmin on a secured server --> <property name="wsadmin.user" value="" /> <!-- The password to run wsadmin on a secured server --> <property name="wsadmin.pw" value="" /> <!-- Main script to run --> <property name="wsadmin.script" value="main.py" /> <!-- Configuration file path --> <property name="wsadmin.config" value="" /> </target> |
在 build.xml 中,我们将 wsadmin.host、wsadmin.user 和 wsadmin.pw 作为 wsadmin.bat(或wsadmin.sh)的参数,将配置文件的路径作为 Jython 脚本的参数,运行 wsadmin 工具和 Jython 脚本:
<target name="wsadmin"> <property name="args" value=" -host ${wsadmin.host} -user ${wsadmin.user} -password ${wsadmin.pw} -lang jython -f ${wsadmin.script} -config ${wsadmin.config} "/> <exec executable="${wsadmin.path}" failonerror="true"> <arg line="${args}"/> </exec> </target> |
通过 ANT 脚本,我们可以灵活的运行 Jython 脚本,从而完成 WAS 服务器的配置和应用部署。假定我们将脚本保存在 C:\wsadmin 目录下,配置的 xml 文件为 sample.xml,那么运行 wsadmin 脚步的 Ant 命令将是:
ant -f C:/wsadmin/build.xml –Dwsadmin.config=C:/wsadmin/sample.xml
本文对 wsadmin 工具和 Jython 脚本进行了介绍,并选取应用部署为例,对 wsadmin 工具使用和 Jython 脚本编写进行了说明。wsadmin 是一个功能强大的工具,它支持开发人员通过脚本部署应用、管理和配置服务器,从而使开发人员可以无需通过管理控制台(Web Admin Console)进行手工配置,节省了开发或部署人员的时间和精力,同时也将人为操作引入错误的机会降至最低。
在此基础上,本文还介绍了一种使用 XML 文件对 wsadmin 命令行工具和 Jython 脚本进行扩展,从而实现服务器的环境配置和应用部署的方法,并简要描述了配置文件的格式。通过这种方法,开发和部署人员将无需重复编写雷同的 Jacl 或 Jython 脚本,只需要按照预定义的 XML 配置文件来提供外部参数就可以重用已有的脚本,完成 WAS 配置和应用部署的过程,提高了部署人员的工作效率。
值得注意的是,我们并不能保证本文所提出的服务器配置和应用部署方案可以完美地解决所有的问题。如果读者希望采用本方案,请参考 IBM WebSphere Application Server 信息中心以了解更多的信息。