随着云计算技术的发展,云环境被认为是向客户提供行业解决方案的最有前途的方式。 为了确保持续交付软件,开发,测试和运营团队必须协作并有效地协同工作。 云环境适合这种类型的交互。 但是,由于部署阶段涉及复杂的分布式拓扑,因此容易出错,通常需要手动进行故障排除。 在许多情况下,部署设计支持单个部署,而不是连续部署。 应用连续交付原理的产品部署阶段通常会遇到瓶颈,并对DevOps流程的效率产生负面影响。
使用真实的客户场景,探索挑战以及如何解决挑战以确保持续部署。
致力于行业部署自动化的软件工程师会发现本文对帮助实现在云上的持续交付很有用。 这些说明假定您具有部署行业解决方案和开发脚本的技能。
持续交付过程
连续交付的目标是确保可以使用最有效,最安全的方法来开发,测试,部署和交付软件。 从基础结构级别,应用程序级别或定制数据级别对软件系统的任何部分进行的更改,都会通过特定的交付管道连续应用于生产环境。 这种方法在用户之间建立了信心,使生产环境可以访问最新的可发布代码。
通用模型和框架
图1中所示的通用模型已广泛应用于大多数IBM行业解决方案的交付中。
图1.常见的连续交付模型
图1展示了连接解决方案开发和生产环境的端到端自动化通道。 它使用Jenkins
作为自动化引擎,可以:
- 检测代码更改并触发连续构建。
- 将内部版本安装到“
Blue
区域。Blue
区域被视为DevOps团队在本地进行验证的过渡环境。 - 在
Blue
区域上启动自动化测试并验证测试结果。 -
Blue
区域准备就绪并经过验证后,将登台环境切换到实时生产环境(Red
区域)。
持续交付的复杂因素
步骤2是本文的重点。 在连续交付过程中,最具挑战性的方面是如何有效地实施连续部署。 部署阶段必须适应复杂的分布式拓扑,不断变化的基础架构以及配置更改。 这些复杂的因素使丢失客户数据变得容易。 传统的解决方案部署设计支持单个部署,而不是连续部署。
为了减少错误,提高流程效率,节省DevOps的时间和精力,最大程度地实现解决方案部署和故障排除的自动化。 第一步是了解实际解决方案部署方案中遇到的挑战。
示例行业解决方案部署案例
考虑每月在云平台上交付行业解决方案的场景。 由于DevOps团队必须在生产环境上部署每个月度构建,因此安装程序需要能够在部署过程开始时在生产环境上运行。
持续交付过程中的常见问题
以下示例从安装角度说明了常见问题:
- 文件替换
- 资源更新
- 数据库配置
文件替换
通常,配置文件需要更新或替换。 在清单1中,安装程序在安装时替换了第3行中的占位符。 第4行中的URL根据运行时的用户输入进行设置。
一项新要求要求安装程序在安装时在以后的迭代中添加新的URL someURL
。 传统的解决方案是将< someURL >@ url @< someURL >
附加到第4行,并完全替换占位符,但是这种方法会导致错误并丢失用户对customizationURL
输入。
清单1.带有示例占位符的脚本,用于主机和URL
1 <servers>
2 <server id="appServer">
3 <host>@host@</host>
4 <customizationURL>@cusUrl@</customizationURL>
5 </servers>
资源更新
假设您有一个使用Java命名和目录接口(JNDI)创建的应用程序服务器调度程序,名称为test/schedulerA
。 相关表以前缀testA_
创建。 一段时间后,您需要将此调度程序的表前缀重命名为testB_
。 通常,您可以调用WebSphere Application Server API AdminControl来更新调度程序对新表的引用。 但是,对于连续交付,已经创建了旧表,即使这些旧表无用,也不会删除它们。 当另一个调度程序在将来使用testA_
前缀时,将导致错误。
数据库配置
数据库配置更改造成了最具挑战性的问题类型。 随着解决方案的更新,数据库将经历表结构,用户权限,存储的数据和其他更改的更改。 容纳更改的一个选项是SAMPLE.TEST_TAB
表,然后在其中插入新的COL_C
列。 但是,在下一次迭代中,表结构将发生变化。 例如,需要删除COL_B
列。 传统部署通过向清单2中所示的数据库脚本添加第6行来适应更改。
清单2.带有用于在数据库表中删除一列的新行的脚本
1 CREATE TABLE SAMPLE.TEST_TAB (
2 "COL_A" INTEGER NOT NULL ,
3 "COL_B" VARCHAR(100) )
4 IN "USERSPACE1" ;
5 ALTER TABLE SAMPLE.TEST_TAB
6 DROP COLUMN COL_B
7 ADD COLUMN COL_C INTEGER;
但是,在连续交付中,添加一行会导致错误,原因是:
- 脚本第二次运行时,
SAMPLE.TEST_TAB
已经存在。 - 列
COL_B
已删除。 因此,SQL处理将停止而不添加COL_C
。
适用于解决持续交付问题的原则
为了克服文件更改,资源更新和配置更改导致的问题,请遵循以下原则。
最小化解决方案安装程序所需的开发工作。 不要为每次交付开发不同的安装程序,而应确保可以将部署阶段的任何更改快速安全地交付生产。 在开发新部署时,请尽可能从过去的迭代中继承。 例如,假设您在先前的迭代中已经部署了资源A。 在当前迭代中,对资源A没有任何更改,但是您需要添加一个新资源,即资源B。添加脚本以创建资源B,即使对资源没有新要求,也要保留创建资源A的先前脚本。一个。
在安装阶段使用自动故障排除。 连续交付的效率在很大程度上取决于流程的自动化程度。 由于环境的差异,手动操作中引入的错误以及其他类似因素,您必须提供自动处理故障排除和安装故障的机制。
支持重复部署解决方案的能力。 此原则是最关键的原则,也是最难实施的原则。 要从过去的迭代中尽可能多地继承,您必须确定如何:
- 管理重复的部署。
- 使用现有的基础架构和资源来部署新代码。
- 更新以前的配置或删除过时的部署。
解决方案安装程序的元素
通过对典型解决方案安装程序的以下方面进行更改来改进解决方案部署:
文件工件
为了连续交付,安装过程必须能够:
- 复制和替换工件
- 整合工件
- 合并工件。
为防止丢失文件替换问题中提到的自定义数据,请将自定义合并到新版本的文件中,而不是简单地复制和替换文件。 清单3显示了过去和当前迭代中的文件old.xml和new.xml。
清单3. old.xml和new.xml之间的比较
[root@server diff]# cat old.xml
<servers>
<server id="appServer">
<host>appserver.test.com</host>
<customizationURL>http://abc.com</customizationURL>
</servers>
[root@server diff]# cat new.xml
<servers>
<server id="appServer">
<host>appserver.test.com</host>
<customizationURL>@cusUrl@</customizationURL>
<someURL>http://someOtherUrl<someURL>
</servers>
要以编程方式合并文件,请将脚本(Linux的Shell脚本)添加到安装程序中以查找版本之间的差异。 将差异记录在要作为补丁处理的文件中。 然后,通过搜索补丁从文件的旧版本中获取定制信息,然后将每个更改合并到当前文件中,如清单4所示。
清单4.合并文件的程序
[root@server diff]# diff old.xml new.xml > patch.txt
[root@server diff]# cat patch.txt
4c4,5
< <customizationURL>http://abc.com</customizationURL>
---
> <customizationURL>@cusUrl@</customizationURL>
> <someURL>http://someOtherUrl<someURL>
[root@server diff]#
企业应用
在整个持续交付过程中,企业应用程序都会发生变化。 更改发生在代码,资源和EAR结构中。 由于更改会影响安装脚本,因此您必须了解对模型的所有更改以及EAR模块与目标部署服务器之间的映射关系的更改。 要管理更改,请使用自动程序来生成EAR部署代码。
首先,在构建阶段从application.xml文件中提取EAR中定义的模型信息。 清单5显示了目标application.xml文件的示例。
清单5.示例应用程序描述符
<?xml version="1.0" encoding="UTF-8"?>
<application xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee version="5"
http://java.sun.com/xml/ns/javaee/application_5.xsd">
<display-name>sample_rest_ear</display-name>
<module id="Module_1361859780281">
<web>
<web-uri>sample_test_rest.war</web-uri>
<context-root>/ibm/test/api/services</context-root>
</web>
</module>
<module id="Module_1364467704309">
<ejb>sample_test_ejb.jar</ejb>
</module>
</application>
接下来,自动生成部署描述符。 描述符基于获取的模型信息。 清单6显示了一个示例部署描述符属性文件,该文件描述了显示名称,EAR文件位置以及映射到要部署的应用程序的模块。 通过使用属性文件,可以确保应用程序部署参数更加灵活并且更易于自动化。
清单6.示例属性文件描述了应用程序的部署
application.0.name=sample_rest_ear
application.0.earfile=content/config/apps/sample_rest_ear.ear
application.0.module.0.name=sample_test_rest
application.0.module.0.moduleFile=sample_test_rest.war
application.0.module.0.deployment=WEB-INF/web.xml
application.0.module.0.cluster.0.name=~{PORTAL_CLUSTER_NAME}
application.0.module.1.name=sample_test_ejb
application.0.module.1.moduleFile=sample_test_ejb.jar
application.0.module.1.deployment=META-INF/ejb-jar.xml
application.0.module.1.cluster.0.name=~{PORTAL_CLUSTER_NAME}
后端代码解析属性,并调用应用程序服务器API进行部署。 清单7中显示的参数deploymentInstruction_attrs
是根据清单6中的属性生成的。
清单7. DeploymentInstruction_attrs的样本值
[ -operation update -contents
sample_rest_ear.ear
-installed.ear.destination
$(APP_INSTALL_ROOT)/cell1 -distributeApp
-MapModulesToServers [[sample_test_rest sample_test_rest.war,WEB-INF/web.xml
WebSphere:cell=cell1,cluster=ClusterA+WebSphere:cell=cell1, node=ihsnode1,server=ihsserver1 ]
[sample_test_ejb sample_test_ejb.jar META-INF/ejb-jar.xml
WebSphere:cell=cell1,cluster=ClusterA]]]
清单8显示了如何调用应用程序服务器API来执行可重复的安装。
清单8.调用应用服务器API执行可重复安装的示例脚本
if (exist == "true"): # Existing application found.
AdminApp.update(appNametrim,
'app',
deploymentInstruction_attrs)
else: # Existing application not found. Create it if requested
AdminApp.install(earFile, deploymentInstruction_attrs)
#endIf
AdminApp
提供了update()
, install()
和uninstall()
函数来配置EAR部署。 使用update
功能而不是delete
功能,以便可以重复部署应用程序。
企业应用程序资源
部署应用程序资源的配置类似于部署应用程序的配置。 使用update
功能而不是delete
和re-create
功能,以避免丢失配置中的任何自定义设置。 因为应用程序资源的部署包括资源之间的关系,所以请使用类似于Java中的垃圾收集方法的方法来维护这些关系。
清单9显示了“资源更新问题”部分提到的应用程序服务器调度schedulerA
的示例部署描述符属性。 选择是否创建相对数据库表。 如果选择创建相对表,那么应用程序服务器将使用数据源定义的架构(在本例中为jdbc/ds)
和前缀(在本例中为testA
)生成数据库表。
清单9.示例属性文件描述了调度程序的部署
scheduler.0.name=schedulerA
scheduler.0.jndiname=test/schedulerA
scheduler.0.description=Scheduler for Task A
scheduler.0.datasourceJNDIName=jdbc/ds
scheduler.0.datasourceAlias=dbuser
scheduler.0.workManagerInfoJNDIName=wm/default
scheduler.0.tablePrefix=testA_
scheduler.0.createTables=true
scheduler.0.target.cluster=~{PORTAL_CLUSTER_NAME}
用DBUSR
和前缀TESTA_
创建的相对表如图2所示。
图2.创建的表列表
每次更改调度程序表或数据源引用的前缀时,都需要在创建新表之前回收引用的表。 为此,请使用类似于清单10的部署脚本。
清单10.调用应用服务器API来执行可重复的调度程序安装的示例脚本
if (existScheduler):
if (createTables):
#drop existing old tables if new tables required
cellNameStr = 'cell=' + getCellName()
nodeNameStr = 'node=' + getNodeName()
Scheduler_Config_Helper_str = AdminControl.completeObjectName
('WebSphere:name=Scheduler_Config_Helper,process=dmgr,platform=dynamicproxy,' + nodeNameStr +
',type=WASSchedulerCfgHelper,mbeanIdentifier=Scheduler_Config_Helper,' + cellNameStr + ',*')
AdminControl.invoke(Scheduler_Config_Helper_str, 'dropTables', scheduler, '[java.lang.String]')
#endIf
AdminConfig.modify(scheduler, deploymentInstruction_attrs)
#endIf
else:
attrs.append(["name", name])
scheduler = AdminConfig.create("SchedulerConfiguration", schedulerProvider,
deploymentInstruction_attrs)
#endIf
数据库
数据库配置是连续交付处理中最复杂的方面。 对数据库的频繁配置更改和频繁的代码更新使安装过程易于出错。 考虑以下将数据库更改填充到测试或生产环境中时发生的错误示例。
错误1:创建表错误
在传统的安装程序中,每个SQL指令仅运行一次。 但是,在连续交付的情况下,SQL指令可能会重复运行以适应每月,每周或每天的交付。 在清单11中,表创建失败,因为脚本不适应重复运行。 您可以忽略这种类型的错误。
清单11.示例表创建错误
CREATE TABLE SAMPLE.TEST_TAB(
COLA INTEGER NOT NULL,
COLB VARCHAR(200) NOT NULL UNIQUE ) ;
SQL failed with: The name of the object to be created is identical to the existing name
"SAMPLE.TEST_TAB" of type "TABLE".. SQLCODE=-601, SQLSTATE=42710, DRIVER=3.64.106
错误2:变更表格错误
更改表时,会遇到与错误1类似的情况。如果多次放置代码,则失败,如清单12所示。但是,与错误1不同,您不能忽略此错误,因为其余代码for COL_C
无法运行。
清单12.样本更改表错误
ALTER TABLE SAMPLE.TEST_TAB DROP COLUMN COL_B ADD COLUMN COL_C INTEGER;
SQL failed with: Column, attribute, or period "COL_B" is not defined in
"SAMPLE.TEST_TAB"..SQLCODE=-205,SQLSTATE=42703, DRIVER=3.64.106
错误3:数据错误
假定COL_A
已被设置为在主键TEST_TAB
。 先前SQL使用清单13中所示SQL指令将数据插入到数据库中。
清单13.先前交付中执行SQL
INSERT INTO SAMPLE.TEST_TAB (COL_A, COL_B) VALUES ('max_number_to_display', '20');
当业务逻辑改变时,需求也改变了,SQL指令也被更新了,如清单14所示。
清单14.当前交付中更新SQL
INSERT INTO SAMPLE.TEST_TAB (COL_A, COL_B) VALUES ('max_number_to_display', '40');;
DB2 SQL error: SQLCODE: -803, SQLSTATE: 23505, SQLERRMC: 1;SAMPLE.TEST_TAB, DRIVER=4.12.55
此更改对于传统的安装程序来说效果很好,但是由于连续的交付已经部署了先前的代码,因此在连续交付中不会生效。
这些样本错误表明,在数据库配置过程中需要为连续交付部署实现安装可重复性。 用于连续交付部署的安装程序必须提供更高的可重复性。 应用以下模式来实现数据库配置的安装可重复性。
模式A:使SQL脚本安全运行一次以上
为了防止类似于错误2的错误并确保SQL脚本多次运行时的有效性,请禁止检入直接更新的现有SQL语句的功能。
避免在代码开发和代码审查期间直接更新SQL脚本。 取而代之的是,通过先将更改分成最小的单元,然后将代码段附加到现有脚本的末尾,将代码更改传递到现有配置(无论更改是对现有表结构还是对现有数据而言)。 为了避免类似于错误2的错误 ,请使用清单15中SQL脚本。此脚本确保在COL_C
运行脚本时成功添加了COL_C
。
清单15.优化的变更表脚本
ALTER TABLE SAMPLE.TEST_TAB
DROP COLUMN COL_B;
ALTER TABLE SAMPLE.TEST_TAB
ADD COLUMN COL_C INTEGER;
模式B:应用选择性安装验证
在验证传统安装时,安装验证器将检查数据库安装日志并记录已检测到的所有SQL错误。 但是,在连续交付中,SQL脚本可以运行多次。 几乎不可能避免每个SQL错误。 过滤掉不重要的错误以及可以忽略的错误,例如:
- 创建重复的表,列,模式,键等
- 删除不存在的对象
- 插入重复数据
选择性验证可确保重要的错误得到最多的关注,并且可以对其进行诊断和修复。
模式C:简化并加速SQL脚本的运行
随着数据库相关代码的不断添加,安装程序变得越来越大。 为了提高部署成功率和性能,请执行以下过程。
- 定期定期存档脚本。 例如,假设代码每个月都交付生产。 脚本每月存档一次,如图3所示。
图3.存档脚本的文件结构
除了文件夹之外, content-spec_< time stamp >
标识调用脚本的命令。 如清单16所示,该脚本来自content-spec_201311.xml文件,它调用了November归档文件夹中的脚本。
清单16. content-spec_201311.xml的内容
<SYS command="db2 connect to DB" />
<SQL file="content/config/script/201311/delta_
update.ddl" />
<SQL file="content/config/script/201311/delta_sample_data_
update.ddl" />
<SYS command="db2 commit work;db2 CONNECT RESET;db2 TERMINATE;" />
- 将时间戳映射到存档文件夹。 在归档脚本之后,设置一个自动方法以将时间戳记与归档的工件(如清单17中所示的映射文件)进行匹配。在清单中,
timestamp
参数指示了脚本被归档的具体日期。 截至该日期为止所做的代码更改已包含在存档文件夹中。
清单17.示例映射文件
<buildHistory projectName="Sample_Project">
<build timestamp="20131130" folder="201311" />
<build timestamp="20131230" folder="201312" />
<build timestamp="20140130" folder="201401" />
<build timestamp="20140228" folder="201402" />
</buildHistory>
- 自动生成脚本队列。 在将时间戳与已归档的文件夹映射之后,安装程序将合并目标脚本并自动生成脚本执行队列。 首先,它检测当前的构建级别,并将该构建级别与清单17中定义的
timestamp
参数进行比较。对于每个时间戳都晚于当前构建级别的条目,将运行映射文件夹中的脚本,如清单18所示。 。
清单18.合并脚本以管理归档的dbscripts
scriptLocation=$(cd -P -- "$(dirname -- "$0")" && pwd -P)|
# build level get from user's current environment
startVersion=$1
if [[ $startVersion == "" ]]; then
$startVersion="00000000"
fi
# mapping file containing relationship between time stamp and archived folders
fname="$scriptLocation/buildHistory.xml"
exec<$fname
while read line
do
if grep -q timestamp <<<$line;
then
timestamp=`echo $line | awk '{print $2}' | sed 's/timestamp=//' | sed 's/.\(.*\)/\1/' | sed 's/\(.*\)./\1/'`
if (( "$timestamp" > "$startVersion" ));then
folder=`echo $line | awk '{print $3}' | sed 's/folder=//' | sed 's/.\(.*\)/\1/' | sed 's/\(.*\)./\1/'`
FOLDERARRAY[$index]="$folder"
index=$(($index+1))
fi
fi
done
例如,如果当前用户环境的构建级别为20140205,则仅调用在文件夹201402中存档的脚本,因为该文件夹具有映射时间戳记20140228。对于其他脚本,安装程序假定它们在与Windows Server 2003一起部署后已运行。构建于构建级别20140205。合并的cont-spec
文件类似于清单19。它包含上个月的所有delta脚本。
清单19.由合并脚本生成的Delta脚本
<SYS command="db2 connect to DB" />
<SQL file="content/config/script/201311/delta_
update.ddl" />
<SQL file="content/config/script/201311/delta_sample_data_
update.ddl" />
<SQL file="content/config/script/201312/delta_
update.ddl" />
<SQL file="content/config/script/201401/delta_
update.ddl" /
<SQL file="content/config/script/201401/delta_sample_data_
update.ddl" />
<SQL file="content/config/script/201402/delta_
update.ddl" />
<SYS command="db2 commit work;db2 CONNECT RESET;db2 TERMINATE;" />
改善连续交付部署的技巧
在连续交付环境中部署时,请应用以下提示:
- 谨慎对待客户数据。
- 不要损坏现有数据。
- 始终使用
update
命令,而不要使用drop
和recreate
命令,以避免丢失客户数据和设置。 - 如果进行复杂的故障排除,请使用备份和还原机制。
结论
本文介绍如何改进用于连续交付环境的解决方案安装程序,以减少部署错误并使部署和集成任务运行更流畅。