【问题标题】:Is there way to determine why Azure App Service restarted?有没有办法确定 Azure 应用服务重新启动的原因?
【发布时间】:2022-04-02 18:45:58
【问题描述】:

我有一堆网站在 Azure 应用服务的单个实例上运行,它们都设置为 Always On。它们都突然同时重新启动,导致一切都慢了几分钟,因为一切都遇到了冷请求。

如果服务已将我转移到新主机,我会预料到这一点,但这并没有发生——我仍然使用相同的主机名。

重启时 CPU 和内存使用情况正常,我没有启动任何部署或类似的事情。我没有看到重启的明显原因。

是否有任何我可以看到的日志记录以找出为什么他们都重新启动了?还是这只是 App Service 时常做的一件很正常的事情?

【问题讨论】:

  • 您是否尝试将您的应用服务连接到 Application Insights,这样您就可以查看详细的历史记录和日志?
  • 我有。我可以从我的 log4net 跟踪中看到提示,这些跟踪转发给 AI,因为我在实例上托管了一个 Azure Functions 应用程序,并且调用了它的 CancellationToken,所以这是一次“优雅”的重启。但没有任何痕迹显示他们为什么都重新启动。
  • 在那种情况下,我会冒险猜测它是一个 IIS 应用程序池回收,如果你想测试的话,你可以让 IIS 记录这些:iis.net/configreference/system.applicationhost/applicationpools/…跨度>
  • They all suddenly restarted at the same time 该问题是偶尔出现还是经常出现?此外,如果可能,请尝试将您的应用扩展到其他实例,并检查是否有助于缓解问题。
  • 您是否在 Basic/std/premium 应用服务计划上运行您的应用?如果是,您可以使用“资源运行状况”来检查资源状态以及它是否按预期运行。它可能会给你更多的见解。

标签: azure azure-web-app-service


【解决方案1】:

所以,这个问题的答案似乎是“不,你不能真正知道为什么,你可以推断它确实。”

我的意思是,您可以添加一些 Application Insights 日志记录,例如

    private void Application_End()
    {
        log.Warn($"The application is shutting down because of '{HostingEnvironment.ShutdownReason}'.");

        TelemetryConfiguration.Active.TelemetryChannel.Flush();

        // Server Channel flush is async, wait a little while and hope for the best
        Thread.Sleep(TimeSpan.FromSeconds(2)); 
    }

您最终会得到"The application is shutting down because of 'ConfigurationChange'.""The application is shutting down because of 'HostingEnvironment'.",但它并不能真正告诉您主机级别发生了什么。

我需要接受的是,App Service 会不时重启,并问自己为什么在乎。 App Service 应该足够聪明,可以在向应用程序池发送请求之前等待应用程序池预热(如重叠回收)。然而,我的应用在回收后会在 CPU 上运行 1-2 分钟。

我花了一段时间才弄明白,但罪魁祸首是我所有的应用程序都有一个重写规则,可以从 HTTP 重定向到 HTTPS。这不适用于应用程序初始化模块:它向根发送一个请求,并且它从 URL Rewrite 模块获得了一个 301 重定向,并且 ASP.NET 管道根本没有受到影响,辛苦的工作没有t实际上完成了。 App Service/IIS 然后认为工作进程已准备好,然后向它发送流量。但是第一个“真正的”请求实际上遵循 301 重定向到 HTTPS URL,并且 bam!该用户遇到了冷启动的痛苦。

I added a rewrite rule described here 免除应用程序初始化模块需要 HTTPS,所以当它到达站点的根目录时,它实际上会触发页面加载,从而触发整个管道:

<rewrite>
  <rules>
    <clear />
    <rule name="Do not force HTTPS for application initialization" enabled="true" stopProcessing="true">
      <match url="(.*)" />
      <conditions>
        <add input="{HTTP_HOST}" pattern="localhost" />
        <add input="{HTTP_USER_AGENT}" pattern="Initialization" />
      </conditions>
      <action type="Rewrite" url="{URL}" />
    </rule>
    <rule name="Force HTTPS" enabled="true" stopProcessing="true">
      <match url="(.*)" ignoreCase="false" />
      <conditions>
        <add input="{HTTPS}" pattern="off" />
      </conditions>
      <action type="Redirect" url="https://{HTTP_HOST}/{R:1}" appendQueryString="true" redirectType="Permanent" />
    </rule>
  </rules>
</rewrite>

这是将旧应用程序迁移到 Azure 的日记中的众多条目之一 - 事实证明,当某些东西在很少重新启动的传统 VM 上运行时,您可以摆脱很多事情,但它需要一些 TLC 来解决在迁移到我们勇敢的云端新世界时解决问题....

--

2017 年 10 月 27 日更新: 自撰写本文以来,Azure 在“诊断和解决问题”下添加了一个新工具。点击“Web App Restarted”,它会告诉你原因,通常是因为存储延迟或基础设施升级。尽管如此,上述内容仍然有效,因为当迁移到 Azure 应用服务时,最好的前进方式是你真的只需要哄你的应用适应随机重启。

--

2018 年 2 月 11 日更新: 在将多个旧系统迁移到中型应用服务计划的单个实例(具有大量 CPU 和内存开销)之后,我遇到了一个令人烦恼的问题,我的暂存槽的部署可以无缝进行,但每当我因为 Azure 基础设施维护而被引导到新主机时,一切都会陷入混乱,停机时间为 2-3 分钟。我一直在努力弄清楚为什么会这样,因为应用服务应该等到它收到来自您的应用的成功响应后,才能将您引导到新主机。

对此我感到非常沮丧,以至于我准备将应用服务归类为企业垃圾并返回到 IaaS 虚拟机。

事实证明这是多个问题,我怀疑其他人在将他们自己的野兽般的遗留 ASP.NET 应用程序移植到应用服务时会遇到它们,所以我想我会在这里解决所有问题。

首先要检查的是您是否真的在您的Application_Start 中进行实际工作。例如,我正在使用 NHibernate,它虽然擅长很多事情,但在加载其配置方面却是一头猪,所以我确保在 Application_Start 期间实际创建 SessionFactory 以确保完成艰苦的工作。

如上所述,要检查的第二件事是您没有干扰应用服务预热检查的 SSL 重写规则。如上所述,您可以从重写规则中排除预热检查。或者,自从我最初编写该解决方法以来,应用服务添加了一个 HTTPS Only 标志,允许您在负载均衡器而不是在 web.config 文件中执行 HTTPS 重定向。由于它是在应用程序代码上方的间接层处理的,因此您不必考虑它,因此我建议将 HTTPS Only 标志作为要走的路。

要考虑的第三件事是您是否使用App Service Local Cache Option。简而言之,这是一个选项,应用服务会将应用程序的文件复制到其正在运行的实例的本地存储中,而不是从网络共享中复制,如果您的应用程序不关心它,这是一个很好的选择丢失写入本地文件系统的更改。它提高了 I/O 性能(这很重要,因为请记住,App Service runs on potatoes)并消除了由网络共享的任何维护引起的重新启动。但是,关于应用服务的基础架构升级有一个特定的细微之处,记录不充分,您需要注意。具体来说,Local Cache 选项在第一次请求后在单独的应用程序域中在后台启动,然后当本地缓存准备好时切换到应用程序域。这意味着应用服务将对您的站点发出预热请求,获得成功的响应,将流量指向该实例,但是(哎呀!)现在本地缓存正在后台研磨 I/O,如果您有很多站点在这种情况下,您已经停下来,因为应用服务 I/O 非常可怕。如果您不知道这种情况正在发生,它在日志中看起来很诡异,因为就好像您的应用程序在同一个实例上启动了两次(因为确实如此)。解决方案是遵循此Jet blog post 并创建一个应用程序初始化预热页面来监视环境变量,该环境变量会告诉您本地缓存何时准备就绪。这样,您可以强制应用服务延迟启动到新实例,直到本地缓存完全准备好。这是我用来确保我也可以与数据库通信的一个:

public class WarmupHandler : IHttpHandler
{
    public bool IsReusable
    {
        get
        {
            return false;
        }
    }

    public ISession Session
    {
        get;
        set;
    }

    public void ProcessRequest(HttpContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException("context");
        }

        var request = context.Request;
        var response = context.Response;

        var localCacheVariable = Environment.GetEnvironmentVariable("WEBSITE_LOCAL_CACHE_OPTION");
        var localCacheReadyVariable = Environment.GetEnvironmentVariable("WEBSITE_LOCALCACHE_READY");
        var databaseReady = true;

        try
        {
            using (var transaction = this.Session.BeginTransaction())
            {
                var query = this.Session.QueryOver<User>()
                    .Take(1)
                    .SingleOrDefault<User>();
                transaction.Commit();
            }
        }
        catch
        {
            databaseReady = false;
        }

        var result = new
        {
            databaseReady,
            machineName = Environment.MachineName,
            localCacheEnabled = "Always".Equals(localCacheVariable, StringComparison.OrdinalIgnoreCase),
            localCacheReady = "True".Equals(localCacheReadyVariable, StringComparison.OrdinalIgnoreCase),
        };

        response.ContentType = "application/json";

        var warm = result.databaseReady && (!result.localCacheEnabled || result.localCacheReady);

        response.StatusCode = warm ? (int)HttpStatusCode.OK : (int)HttpStatusCode.ServiceUnavailable;

        var serializer = new JsonSerializer();
        serializer.Serialize(response.Output, result);
    }
}

还记得映射一个路由并将应用程序初始化添加到您的web.config

<applicationInitialization doAppInitAfterRestart="true">
  <add initializationPage="/warmup" />
</applicationInitialization>

要考虑的第四件事是,有时应用服务会因为看似垃圾的原因重新启动您的应用。似乎将fcnMode 属性设置为Disabled 会有所帮助;如果有人在服务器上使用配置文件或代码,它会阻止运行时重新启动您的应用程序。如果您正在使用暂存槽并以这种方式进行部署,那么这不应该打扰您。但是,如果您希望能够通过 FTP 输入文件并欺骗文件并看到该更改反映在生产中,那么请不要使用此选项:

     <httpRuntime fcnMode="Disabled" targetFramework="4.5" />

要考虑的第五件事,这一直是我的主要问题,是您是否使用启用了AlwaysOn 选项的暂存槽。 AlwaysOn 选项的工作原理是每分钟左右对您的站点进行一次 ping 操作,以确保它是温暖的,这样 IIS 就不会停止它。莫名其妙地,this isn't a sticky setting,所以你可能在你的生产和暂存槽上都打开了AlwaysOn,这样你就不必每次都搞砸了。这会导致应用服务基础结构升级在将您引导到新主机时出现问题。情况如下:假设您在一个实例上托管了 7 个站点,每个站点都有自己的暂存槽,所有站点都启用了AlwaysOn。应用服务对您的 7 个生产槽进行预热和应用程序初始化,并尽职尽责地等待它们成功响应,然后再重定向流量。 但它不会对暂存槽执行此操作。因此它将流量引导到新实例,但随后 AlwaysOn 会在 1-2 分钟后在暂存槽上启动,所以现在你有了另有 7 个站点同时启动。请记住,App Service runs on potatoes,因此同时发生的所有这些额外 I/O 会破坏生产槽的性能,并且会被视为停机。

解决方案是将AlwaysOn 在您的暂存槽上保持关闭状态,这样您就不会在基础架构更新后被这种同时发生的 I/O 狂热所困扰。如果您通过 PowerShell 使用交换脚本,那么保持这种“暂存关闭,生产中开启”的操作非常冗长:

Login-AzureRmAccount -SubscriptionId {{ YOUR_SUBSCRIPTION_ID }}

$resourceGroupName = "YOUR-RESOURCE-GROUP"
$appName = "YOUR-APP-NAME"
$slotName = "YOUR-SLOT-NAME-FOR-EXAMPLE-STAGING"

$props = @{ siteConfig = @{ alwaysOn = $true; } }

Set-AzureRmResource `
    -PropertyObject $props `
    -ResourceType "microsoft.web/sites/slots" `
    -ResourceGroupName $resourceGroupName `
    -ResourceName "$appName/$slotName" `
    -ApiVersion 2015-08-01 `
    -Force

Swap-AzureRmWebAppSlot `
    -SourceSlotName $slotName `
    -ResourceGroupName $resourceGroupName `
    -Name $appName

$props = @{ siteConfig = @{ alwaysOn = $false; } }

Set-AzureRmResource `
    -PropertyObject $props `
    -ResourceType "microsoft.web/sites/slots" `
    -ResourceGroupName $resourceGroupName `
    -ResourceName "$appName/$slotName" `
    -ApiVersion 2015-08-01 `
    -Force

此脚本将暂存槽设置为打开AlwaysOn,进行交换以使暂存现在处于生产状态,然后将暂存槽设置为关闭AlwaysOn,因此它不会在基础设施升级。

一旦你完成这项工作,拥有一个为你处理安全更新和硬件故障的 PaaS 确实很棒。但在实践中实现它比营销材料可能暗示的要困难一些。希望这对某人有所帮助。

--

2020 年 7 月 17 日更新: 在上面的简介中,我谈到了如果您使用暂存插槽需要使用“AlwaysOn”,因为它会与插槽交换,并且具有它在所有插槽上都会导致性能问题。在某些时候我不清楚,they seem to have fixed this so that "AlwaysOn" isn't swapped。我的脚本实际上仍然在使用 AlwaysOn,但实际上它现在最终变成了无操作。因此,为您的暂存槽关闭 AlwaysOn 的建议仍然有效,但您不应该再在脚本中做这个小杂耍了。

【讨论】:

  • 我永远不会知道新的诊断和解决问题选项。谢谢。
  • 由于您的更新,我投了赞成票。不知道那是在那里,它非常有帮助。谢谢!
  • @NeilThompson 感谢您为记录您的发现所做的努力。半年多来,这仍然是 Azure 中的一个开放错误。微软不知道有些人实际上在生产中使用 Azure 吗?
  • 你是我的英雄,尝试使用部署槽进行此设置,因为我的应用程序不断从存储问题中删除并且性能正在扼杀我,尽管应用程序启动需要永远(3 分钟)ugg ,有什么方法可以加快应用程序的启动速度?我还注意到现在重新启动应用程序要快得多,不知道幕后发生了什么来解决这个问题
  • 感谢您提供所有这些发现,截至 2018 年 11 月,它仍然是黄金。
【解决方案2】:

如果您的服务因 OutOfMemoryExceptions 重新启动,Application_End 可能由于应用程序崩溃而无法运行。

我们将 ASP.NET 4.8 MVC 5 应用程序移至 Azure 应用程序服务(使用 Windows 容器),并在上线后面临 OOM。应用程序崩溃非常严重,以至于 Application_End 事件无法记录任何消息。我们确实收到了 AppInsights 能够在重启前调度的间歇性 OOME。

我们的工程师一直在寻找增加网站内存的方法(因为我们在之前的环境中确实使用了很多),但找不到任何可用的参考。我们最终被微软支持人员保存,他们建议使用此应用程序设置(将在配置下添加)来增加内存:

WEBSITE_MEMORY_LIMIT_MB = 3072

他们将此引用添加到 Azure 文档: https://github.com/MicrosoftDocs/azure-docs/issues/13263#issuecomment-655051828

现在我们的应用程序运行愉快,在高峰时间提交了大约 4200M。我的服务计划有32G,有2个app服务,一共5个slot,其中一个配置使用5120M。仍有大约 40% 的内存可用于启动暂存槽。

【讨论】:

    猜你喜欢
    • 2011-01-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-05-15
    • 1970-01-01
    相关资源
    最近更新 更多