ASP.NET 应用程序部署系统






3.90/5 (6投票s)
2005年5月9日
3分钟阅读

63225

564
这是一个 ASP.NET 应用程序部署系统,包括将 DEV 发布到 QA、QA 发布到 Staging 以及 Staging 发布到 Production 的代码发布或非代码发布。该项目使用异步技术来处理长时间运行的进程,如文件复制、状态更新和应用程序预热。
摘要
这是一个 ASP.NET 应用程序部署系统,包括将 DEV 发布到 QA、QA 发布到 Staging 以及 Staging 发布到 Production 的代码发布或非代码发布。该项目使用异步技术来处理长时间运行的进程,如文件复制、状态更新、应用程序预热以及同时发布到多个服务器。
引言
在构建应用程序部署系统时,我们期望它能够以高效、健壮和便捷的方式管理从 DEV 到 QA、QA 到 Staging 以及 Staging 到 Production 的不同部署阶段。
此外,还期望具备以下功能:
- 当有更新时,部署过程的状态会报告给用户。
- 应用程序构建可以同时交付到集群内的多个生产服务器。
- 部署后应用程序预热。
- 文件复制应健壮且高效。
- 非代码部署方便且无需构建。
- 需要一定级别的日志记录来审计事件。
- 管理通过 Web 界面完成,因此可以通过浏览器轻松访问。
异步请求处理
关键在于利用 .NET 中的异步技术来处理长时间运行的进程,如文件复制、状态更新、应用程序预热以及同时发布到多个服务器。
QAPublish
、StagingPublish
和 ProductionPublish
是处理部署三个阶段的三个类。它们都实现了 IAsyncRequest
接口。AsyncRequestState
是一个包装类,用于保存 HttpContext
、一个回调函数以及传递给 IAsyncRequest
成员的额外数据。对于 ProductionPublish
,HttpContext.Cache
被用作部署状态和应用程序预热结果的临时存储。
public interface IAsyncRequest
{
void ProcessRequest();
AsyncRequestState AsyncRequestState{get;}
}
public class ProductionPublish : IAsyncRequest
{
private AsyncRequestState _asyncRequestState;
private static string lockString = string.Empty;
public ProductionPublish(AsyncRequestState ars)
{
_asyncRequestState = ars;
}
private void UpdateStatus(string server, PublishStatus status)
{
lock(lockString)
{
Hashtable ht =
_asyncRequestState._ctx.Cache[SR.PubStatusSessionName] as Hashtable;
if (ht.ContainsKey(server))
{
ht[server] = status;
}
else
{
ht.Add(server, status);
}
_asyncRequestState._ctx.Cache[SR.PubStatusSessionName] = ht;
}
}
private void CheckStatus()
{
lock(lockString)
{
Hashtable ht =
_asyncRequestState._ctx.Cache[SR.PubStatusSessionName] as Hashtable;
if (ht == null)
{
ht = new Hashtable();
_asyncRequestState._ctx.Cache[SR.PubStatusSessionName] = ht;
}
}
}
private PublishStatus GetStatus(string server)
{
Hashtable ht =
_asyncRequestState._ctx.Cache[SR.PubStatusSessionName] as Hashtable;
if (ht.ContainsKey(server))
{
return (PublishStatus)ht[server];
}
return null;
}
AsyncRequestState IAsyncRequest.AsyncRequestState
{
get
{
return _asyncRequestState;
}
}
void IAsyncRequest.ProcessRequest()
{
Process proc = null;
PublishStatus st;
string output;
try
{
string server = (string)((Pair)_asyncRequestState._extraData).First;
ProcessStartInfo procInfo = new ProcessStartInfo();
// run Robocopy batch file
procInfo.UseShellExecute = true;
//If this is false, only .exe's can be run.
procInfo.WorkingDirectory = Settings.ProdPubBatchPath;
procInfo.FileName = Settings.ProdPubBatchFile;
// Program or Command to Execute.
procInfo.Arguments = string.Format("{0} {1}",
server, Settings.StagingRootFolder);
procInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
CheckStatus();
UpdateStatus(server, new PublishStatus(server, SR.Copying, null));
System.Diagnostics.Trace.WriteLine(SR.CopyStartedLogMsg(server,
Utility.Now()));
proc = Process.Start(procInfo);
proc.WaitForExit();
System.Diagnostics.Trace.WriteLine(SR.CopyEndedLogMsg(server,
Utility.Now()));
UpdateStatus(server, new PublishStatus(server,
SR.Copied, SR.WarmingUp));
// warm up
System.Diagnostics.Trace.WriteLine(SR.FirstLoadStartedLogMsg(server,
Utility.Now()));
output = Utility.MakeWebRequest(server);
System.Diagnostics.Trace.WriteLine(SR.FirstLoadEndedLogMsg(server,
Utility.Now()));
st = GetStatus(server);
if (output != null)
{
st.WarmUpResult = output;
st.WarmUpStatus = SR.WarmedUp;
}
else
{
st.WarmUpResult = string.Empty;
st.WarmUpStatus = SR.Timeout;
}
UpdateStatus(server, st);
st = GetStatus(server);
st.LoadStatus = SR.Loading;
UpdateStatus(server, st);
// 2nd attempt warm up
System.Diagnostics.Trace.WriteLine(
SR.SecondLoadStartedLogMsg(server, Utility.Now()));
output = Utility.MakeWebRequest(server);
System.Diagnostics.Trace.WriteLine(
SR.SecondLoadEndedLogMsg(server, Utility.Now()));
st = GetStatus(server);
if (output != null)
{
st.LoadResult = output;
st.LoadStatus = SR.Loaded;
}
else
{
st.LoadResult = string.Empty;
st.LoadStatus = SR.Timeout;
}
UpdateStatus(server, st);
_asyncRequestState.CompleteRequest();
}
catch (Exception ex)
{
// exception management
}
}
}
以下是在基于 Web 的管理界面中启动异步发布过程的代码:
AsyncRequestState reqState =
new AsyncRequestState(Context, null, new Pair(srv, null));
ProductionPublish ar = new ProductionPublish(reqState);
Publisher pub = new Publisher();
pub.BeginProcessRequest(ar);
文件复制引擎 – Robocopy
无需开发新的文件复制程序,因为 Windows Resource Kit 中已有一个——Robocopy。这个强大的命令行工具可以完成各种脚本化的复制任务,包括大型数据迁移和服务器合并。它可以配置为过滤文件、操作文件属性以及在复制过程中进行适当的日志记录。无需重复造轮子,该工具可以轻松地作为文件复制引擎采用。可以通过谷歌搜索“robocopy”从互联网上的各种位置下载。手册可以在这里找到。
我们将模拟一个拥有足够权限的帐户来运行 Robocopy 作为外部进程,并访问所有涉及服务器所在的内部网络,从而实现 Web 启用的部署系统。
Robocopy 通过批处理文件使用,这些文件被脚本化以处理各种文件复制阶段以及不同的部署前和部署后场景。
项目中包含了一个示例批处理文件。
站点刷新 / 非代码发布
除了按计划正常部署到 QA 或 Staging 环境的日常或夜间构建外,有时还需要快速修复,以便在不重新运行构建的情况下部署非代码文件,例如 aspx、ascs、js、css 等。我将此称为站点刷新。
在此系统中,我包含了一个站点刷新管理界面,可以将 DEV 中的多个非代码文件添加到发布队列中,并将其部署到 QA 或 Staging,同时对这些文件进行适当的 Visual SourceSafe 标记。(如果您的 DEV 构建不进行 VSS 标记,您可以从系统中删除 VSS 访问。)
VSS 访问通过 SourceSafe 帮助类中的方法实现,该类取自 Microsoft BuildIt 工具,并进行了一些小的修改。请参考BuildIt。
配置
该系统使用 web.config 存储应用程序设置。为了使应用程序运行,某些配置是必不可少的。
web.config 中的以下 appSettings
部分应该相当易于理解:
<appSettings>
<!-- Site refresh section -->
<add key="RootPath" value="C:\Projects\NPublisher\Web\"/>
<!-- Web root of application in Dev for site refresh -->
<add key="AllowExtensions"
value="|.aspx|.ascx|.xml|.ico|.config|.js|.txt|.html|.css|"/>
<!-- File extensions allowed in site refresh -->
<!-- VSS & Dev specification section -->
<add key="VSSUsername" value="Username"/>
<add key="VSSPassword" value="Password"/>
<add key="IniFilePath" value="\\VssServer\DBFolder"/>
<!-- VSS DB folder -->
<add key="SrcVSSRootFolder" value="$/NPublisher/Web/"/>
<!-- Web root on VSS -->
<add key="SrcFileRootFolder" value="C:\Projects\NPublisher\Web\"/>
<!-- Web root of application in Dev -->
<!-- QA section -->
<add key="QAServer" value="QAServer"/>
<add key="QARootFolder" value="\\QAServer\WebRoot\"/>
<!-- Web root of application in QA -->
<add key="QAPubBatchFile" value="Publisher.bat" />
<add key="QAPubBatchPath" value="C:\Batch\QA\" />
<!-- Staging section -->
<add key="StagingServer" value="StagingServer"/>
<add key="StagingRootFolder" value="\\StagingServer\WebRoot\"/>
<!-- Web root of application in Staging -->
<add key="StagingPubBatchFile" value="Publisher.bat" />
<add key="StagingPubBatchPath" value="C:\Batch\Staging\" />
<!-- Production section -->
<add key="ProdServers"
value="ProdServer1|ProdServer2|ProdServer3|ProdServer4|ProdServer5" />
<add key="ProdPubBatchFile" value="Publisher.bat" />
<add key="ProdPubBatchPath" value="C:\Batch\Production\" />
<!-- Warmup section -->
<add key="WarmUpUrl" value="http://{0}/Warmup.aspx" />
<add key="WarmUpTimeout" value="100000" />
<!-- Http request timeout setting(in milliseconds) -->
</appSettings>
另外,请确保 ASP.NET 运行的帐户具有足够的权限来写入跟踪日志并访问本地文件或远程网络共享。
您可以通过在 web.config 中添加以下行来模拟应用程序中的域帐户:
<identity impersonate="true" userName="username" password="password" />
最新发布
请在NPublisher GotDotNet Workplace获取最新版本。