你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

在 AKS 中监视微服务应用程序

Azure Monitor Azure Kubernetes 服务 (AKS)

本文介绍监视在 Azure Kubernetes 服务 (AKS) 上运行的微服务应用程序的最佳做法。 具体主题包括遥测数据收集、监视群集的状态、指标、日志记录、结构化日志记录和分布式跟踪。 后者如下图所示:

下载此体系结构的 Visio 文件

遥测数据的收集

任何复杂的应用程序偶尔都会出现错误。 在微服务应用程序中,需要跟踪几十甚至几百个服务发生的情况。 若要了解所发生的情况,需要通过应用程序收集遥测数据。 可以将遥测数据分为以下类别:日志、跟踪和指标。

“日志”是运行应用程序时发生的事件的文本形式记录。 日志包括应用程序日志(跟踪语句)和 Web 服务器日志等。 日志主要用于取证和根本原因分析。

“跟踪”(也称为操作)连接微服务中和跨微服务的跨多个调用的单个请求的步骤。 它们可以在系统组件的交互中提供结构化的可观测性。 跟踪可以在请求过程的早期开始,例如在应用程序的 UI 中,并且可以通过网络服务在处理请求的微服务网络中传播。

  • 跨度是跟踪中的工作单位。 每个跨度都与单个跟踪连接,并且可以与其他跨度嵌套。 它们通常对应于跨服务操作中的各个请求,但它们还可以在服务中的各个组件中定义工作。 跨度还跟踪从一个服务到另一个服务的出站调用。 (有时跨度被称为依赖项记录。)
  • 指标是可以分析的数字值。 可以使用指标实时(或接近实时)观察系统,或者分析不同时间段的性能趋势。 若要全面了解系统,需要在体系结构的各个级别收集指标,从物理基础结构到应用程序,包括:

  • 节点级 指标,包括 CPU、内存、网络、磁盘和文件系统的使用情况。 借助系统指标可以了解群集中每个节点的资源分配,以及排查异常。

  • 容器 指标。 对于容器化应用程序,需要在容器级别而不仅仅是 VM 级别收集指标。

  • 应用程序 指标。 这些指标与了解服务行为相关。 示例包括排队的入站 HTTP 请求数、请求延迟和消息队列长度。 应用程序还可以使用特定于域的自定义指标,例如每分钟处理的业务事务数。

  • 依赖服务 指标。 服务有时会调用外部服务或终结点,例如托管的 PaaS 或 SaaS 服务。 第三方服务不一定提供指标。 如果未提供,则需要依赖自己的应用程序指标来跟踪有关延迟和错误率的统计信息。

    监视群集状态

    使用 Azure Monitor 监视群集的运行状况。 以下屏幕截图显示了用户部署的 Pod 中出现关键错误的群集:

    在此处,可以进一步钻取以查找问题。 例如,如果 Pod 状态为 ImagePullBackoff ,则 Kubernetes 无法从注册表中拉取容器映像。 此问题可能是由于容器标记无效或从注册表拉取时的身份验证错误导致的。

    如果容器崩溃,则容器 State 将变为 Waiting Reason CrashLoopBackOff 。 对于 Pod 是副本集的一部分且重试策略是 Always 的典型场景,此问题不会在群集状态中显示为错误。 但是,可以针对此条件运行查询或设置警报。 有关详细信息,请参阅 通过 Azure Monitor 容器见解了解 AKS 群集性能

    AKS 资源的工作簿窗格中有多个特定于容器的工作簿。 可以将这些工作簿用于快速概览、故障排除、管理和见解。 以下屏幕截图显示默认情况下可用于 AKS 工作负载的工作簿列表。

    建议使用 Monitor 来收集和查看 AKS 群集和任何其他依赖性 Azure 服务的指标。

  • 对于群集和容器指标,请启用 Azure Monitor 容器见解 。 启用此功能后,Monitor 通过 Kubernetes 指标 API 从控制器、节点和容器中收集内存和处理器指标。 有关通过容器见解提供的指标的详细信息,请参阅 使用 Azure Monitor 容器见解了解 AKS 群集性能

  • 通过 Application Insights 来收集应用程序指标。 Application Insights 是一项可扩展应用程序性能管理 (APM) 服务。 若要使用它,请在应用程序中安装一个检测包。 此包可监视应用,并将遥测数据发送到 Application Insights。 此外,它还可以从宿主环境提取遥测数据。 然后,数据会发送到 Monitor。 Application Insights 还提供内置的关联和依赖项跟踪。 (请参阅本文后面的 分布式跟踪 。)

    Application Insights 提供以每秒事件数测量的最大吞吐量,如果数据速率超过限制,则会限制遥测数据。 有关详细信息,请参阅 Application Insights 限制 。 为每个环境创建不同的 Application Insights 实例,使开发/测试环境不会针对配额与生产遥测进行竞争。

    单个操作可能生成许多遥测事件,因此,如果应用程序遇到较大的流量,其遥测捕获可能会受到限制。 若要缓解此问题,可以执行采样来减少遥测流量。 折中的情况是指标不太精确,除非检测支持 预聚合 。 在这种情况下,用于故障排除的跟踪示例减少,但指标仍可保持准确性。 有关详细信息,请参阅 Application Insights 中的采样 。 还可以通过预先聚合指标来减少数据量。 也就是说,可以计算统计值(如平均值和标准偏差),并发送这些值而不是原始遥测数据。 此博客文章介绍了大规模使用 Application Insights 的方法: 大规模 Azure 监视和分析

    如果数据速率足够高,可以触发限制,并且不可接受采样或聚合,请考虑将指标导出到在群集中运行的时序数据库,例如 Azure 数据资源管理器、Prometheus 或 InfluxDB。

  • Azure 数据资源管理器 是 Azure 原生的高度可缩放的数据探索服务,适用于日志和遥测数据。 它可支持多种数据格式、具有丰富的查询语言和连接,用于在常用工具(如 Jupyter Notebook Grafana )中使用数据。 Azure 数据资源管理器具有内置连接器,通过 Azure 事件中心引入日志和指标数据。 有关详细信息,请参阅 在 Azure 数据资源管理器中引入和查询监视数据

  • InfluxDB 是基于推送的系统。 某个代理需要推送指标。 可以使用 TICK 堆栈 来设置 Kubernetes 的监视。 接下来,可以使用 Telegraf (用于收集和报告指标的代理)将指标推送到 InfluxDB。 可以将 InfluxDB 用于不规则的事件和字符串数据类型。

  • Prometheus 是基于提取的系统。 它定期从配置的位置擦除指标。 Prometheus 可以 抓取 Azure Monitor 生成的指标 或 kube 状态指标。 kube-state-metrics 服务从 Kubernetes API 服务器收集指标,然后将这些指标提供给 Prometheus(或者与 Prometheus 客户端终结点兼容的擦除程序)。 对于系统指标,请使用 节点导出程序 ,这是一个针对系统指标的 Prometheus 导出程序。 Prometheus 支持浮点数据,但不支持字符串数据,因此,它适合用于系统指标,而不适合用于日志。 Kubernetes 指标服务器 是资源使用情况数据的群集范围聚合器。

    下面是微服务应用程序中日志记录的一些一般难题:

  • 了解客户端请求的端到端处理,其中可能会调用多个服务来处理单个请求。
  • 将来自多个服务的日志合并到单个聚合视图中。
  • 分析来自多个来源的日志,这些日志使用自己的日志记录架构或没有特定架构。 日志可能由你无法控制的第三方组件生成。
  • 微服务体系结构通常生成比传统单体架构更大的日志量,因为事务中有更多的服务、网络调用和步骤。 这意味着日志记录本身可能是应用程序的性能或资源瓶颈。
  • 基于 Kubernetes 的体系结构还有其他一些难题:

  • 容器可四处移动并可重新计划。
  • Kubernetes 有使用虚拟 IP 地址和端口映射的网络抽象。
  • 在 Kubernetes 中,日志记录的标准方法是让容器将日志写入 stdout 和 stderr。 容器引擎将这些流重定向到日志记录驱动程序。 为了便于查询以及在节点停止响应时防止可能的日志数据丢失,通常的方法是从每个节点收集日志并将其发送到中央存储位置。

    Azure Monitor 与 AKS 集成以支持此方法。 Monitor 收集容器日志并将其发送到 Log Analytics 工作区。 在此处,可以使用 Kusto 查询语言 跨聚合日志写入查询。 例如,下面是用于显示指定 Pod 的 容器日志的 Kusto 查询

    ContainerLogV2
    | where PodName == "podName" //update with target pod
    | project TimeGenerated, Computer, ContainerId, LogMessage, LogSource
    

    Azure Monitor 是一项托管服务,将 AKS 群集配置为使用 Monitor 是 CLI 或 Azure 资源管理器模板中的简单配置更改。 (有关详细信息,请参阅如何启用 Azure Monitor 容器见解。)使用 Azure Monitor 的另一个优势是,它将 AKS 日志与其他 Azure 平台日志合并,提供统一的监视体验。

    Azure Monitor 根据引入到服务中的数据的 GB 单位数量计费。 (请参阅 Azure Monitor 定价。)在量大的情况下,成本可能会成为一个考虑因素。 有许多开源替代项可用于 Kubernetes 生态系统。 例如,许多组织将 Fluentd 与 Elasticsearch 配合使用。 Fluentd 是一个开源数据收集器,Elasticsearch 是用于搜索的文档数据库。 这些方式带来的难题是,它们要求对群集进行额外的配置和管理。 对于生产工作负载,可能需要试验配置设置。 还需要监视日志记录基础结构的性能。

    OpenTelemetry

    OpenTelemetry 是一项跨行业工作,通过标准化应用程序、库、遥测和数据收集器之间的接口来改进跟踪。 使用通过 OpenTelemetry 检测的库和框架时,通常由基础库处理跟踪操作(属于传统系统操作)的大部分工作,其中包括以下常见场景:

  • 基本请求操作的日志记录,例如开始时间、退出时间和持续时间
  • 上下文传播(例如跨 HTTP 调用边界发送相关 ID)
  • 处理这些操作的基本库和框架会创建丰富的关联范围和跟踪数据结构,并跨上下文进行传播。 在 OpenTelemetry 之前,这些通常只是作为特殊的日志消息注入,或者作为特定于构建监视工具的供应商的专有数据结构注入。 OpenTelemetry 还建议使用比传统的日志记录优先方法更丰富的检测数据模型,并且日志会更加有用,因为日志消息链接到生成日志消息的跟踪和跨度。 这通常使查找与特定操作或请求关联的日志变得简单。

    许多 Azure SDK 已使用 OpenTelemetry 进行检测,或者正在实现过程中。

    应用程序开发人员可以使用 OpenTelemetry SDK 添加手动检测,以执行以下活动:

  • 在基础库未提供检测的情况下添加检测。
  • 通过添加跨度来扩充跟踪上下文,以公开特定于应用程序的工作单元(例如创建用于处理每个订单行的跨度的订单循环)。
  • 使用实体键扩充现有跨度,以便更轻松地进行跟踪。 (例如,将 OrderID 键/值添加到处理该订单的请求。)这些键由监视工具呈现为结构化值,用于查询、筛选和聚合(无需分析日志消息字符串或查找日志消息序列的组合,而日志记录优先方法通常需要分析日志消息字符串或查找日志消息序列的组合)。
  • 通过访问跟踪和跨度属性传播跟踪上下文,将 traceId 注入响应和有效负载并/或从传入消息读取 traceId,以便创建请求和跨度。
  • 如需详细了解检测和 OpenTelemetry SDK,请参阅 OpenTelemetry 文档

    Application Insights

    Application Insights 从 OpenTelemetry 及其检测库收集丰富的数据,并在高效的数据存储中捕获它,以提供丰富的可视化和查询支持。 Application Insights 基于 OpenTelemetry 的检测库(适用于 .NET、Java、Node.js 和 Python 等语言)可轻松将遥测数据发送到 Application Insights。

    如果使用 .NET Core,建议也考虑适用于 Kubernetes 的 Application Insights 库。 此库使用其他信息(如容器、节点、Pod、标签和副本集)扩充 Application Insights 跟踪。

    Application Insights 将 OpenTelemetry 上下文映射到其内部数据模型:

  • 跟踪 -> 操作
  • 跟踪 ID -> 操作 ID
  • 跨度 -> 请求或依赖项
  • 请考虑以下注意事项:

  • 如果数据速率超过最大限制,则 Application Insights 会限制遥测数据。 有关详细信息,请参阅 Application Insights 限制。 单个操作可能生成多个遥测事件,因此,如果应用程序遇到较大的流量,可能会受到限制。
  • 由于 Application Insights 批处理数据,如果进程失败并出现未经处理的异常,则可能会丢失批处理。
  • Application Insights 基于数据量计费。 有关详细信息,请参阅在 Application Insights 中管理定价和数据量
  • 结构化日志记录

    若要使日志更易于分析,请尽可能使用结构化日志记录。 使用结构化日志记录时,应用程序以结构化格式(如 JSON)写入日志,而不是输出非结构化文本字符串。 可以使用许多结构化日志记录库。 例如,以下是使用适用于 .NET Core 的 Serilog 库的日志记录语句:

    public async Task<IActionResult> Put([FromBody]Delivery delivery, string id)
        logger.LogInformation("In Put action with delivery {Id}: {@DeliveryInfo}", id, delivery.ToLogInfo());
    

    在这里,对 LogInformation 的调用包括 Id 参数和 DeliveryInfo 参数。 使用结构化日志记录时,这些值不会内插到消息字符串中。 日志输出将如下所示:

    {"@t":"2019-06-13T00:57:09.9932697Z","@mt":"In Put action with delivery {Id}: {@DeliveryInfo}","Id":"36585f2d-c1fa-4a3d-9e06-a7f40b7d04ef","DeliveryInfo":{...
    

    这是一个 JSON 字符串,其中 @t 字段是时间戳,@mt 是消息字符串,其余键/值对是参数。 使用输出 JSON 格式可以更轻松地以结构化方式查询数据。 例如,以下 Log Analytics 查询(采用 Kusto 查询语言编写)从所有名为 fabrikam-delivery 的容器中搜索此特定消息的实例:

    traces
    | where customDimensions.["Kubernetes.Container.Name"] == "fabrikam-delivery"
    | where customDimensions.["{OriginalFormat}"] == "In Put action with delivery {Id}: {@DeliveryInfo}"
    | project message, customDimensions["Id"], customDimensions["@DeliveryInfo"]
    

    如果在 Azure 门户中查看结果,可以看到 DeliveryInfo 是包含 DeliveryInfo 模型的串行化表示形式的结构化记录:

    以下是此示例中的 JSON:

    "Id": "36585f2d-c1fa-4a3d-9e06-a7f40b7d04ef", "Owner": { "UserId": "user id for logging", "AccountId": "52dadf0c-0067-43e7-af76-86e32b48bc5e" "Pickup": { "Altitude": 0.29295161612934972, "Latitude": 0.26815900219052985, "Longitude": 0.79841844309047727 "Dropoff": { "Altitude": 0.31507750848078986, "Latitude": 0.753494655598651, "Longitude": 0.89352830773849423 "Deadline": "string", "Expedited": true, "ConfirmationRequired": 0, "DroneId": "AssignedDroneId01ba4d0b-c01a-4369-ba75-51bde0e76cc9"

    许多日志消息标记工作单元的开始或结束,或者将业务实体与一组消息和操作连接,以实现可跟踪性。 在许多情况下,扩充 OpenTelemetry 跨度和请求对象是比只记录该操作的开始和结束更好的方法。 执行该操作会将该上下文添加到所有连接的跟踪和子操作中,并将该信息置于完整操作的范围内。 适用于各种语言的 OpenTelemetry SDK 支持创建跨度或在跨度上添加自定义属性。 例如,以下代码使用 Application Insights 支持的Java OpenTelemetry SDK。 现有父级跨度(例如与 REST 控制器调用相关的、由正在使用的 web 框架创建的请求跨度)可以通过与其关联的实体 ID 进行扩充,如下所示:

    import io.opentelemetry.api.trace.Span;
    // ...
    Span.current().setAttribute("A1234", deliveryId);
    

    此代码设置当前跨度上的键或值,它连接到在该跨度下发生的操作和日志消息。 该值显示在 Application Insights 请求对象中,如下所示:

    requests
    | extend deliveryId = tostring(customDimensions.deliveryId)  // promote to column value (optional)
    | where deliveryId == "A1234"
    | project timestamp, name, url, success, resultCode, duration, operation_Id, deliveryId
    

    当与日志、筛选配合使用并使用跨度上下文批注日志跟踪时,此技术会变得更加强大,如下所示:

    requests
    | extend deliveryId = tostring(customDimensions.deliveryId)  // promote to column value (optional)
    | where deliveryId == "A1234"
    | project deliveryId, operation_Id, requestTimestamp = timestamp, requestDuration = duration  // keep some request info
    | join kind=inner traces on operation_Id   // join logs only for this deliveryId
    | project requestTimestamp, requestDuration, logTimestamp = timestamp, deliveryId, message
    

    如果使用已通过 OpenTelemetry 检测的库或框架,那么它可以处理创建跨度和请求,但应用程序代码也可能创建工作单元。 例如,循环访问一系列实体并对每个实体执行工作的方法可能会为处理循环的每个迭代创建一个跨度。 有关将检测添加到应用程序和库代码的信息,请参阅 OpenTelemery 检测文档

    分布式跟踪

    使用微服务时的难题之一是如何了解跨服务的事件流。 单个事务可能涉及对多个服务的调用。

    分布式跟踪示例

    此示例通过一组微服务介绍分布式事务的路径。 该示例基于无人机交付应用程序

    在此场景中,分布式事务包括以下步骤:

  • 引入服务在 Azure 服务总线队列上放置消息。
  • 工作流服务从队列中拉取消息。
  • 工作流服务调用三个后端服务来处理请求(无人机计划程序、包和交付)。
  • 以下屏幕截图显示了无人机交付应用程序的应用程序映射。 此映射显示对公共 API 终结点的调用,这些调用导致涉及五个微服务的工作流。

    fabrikam-workflowfabrikam-ingestion 到服务总线队列的箭头显示发送和接收消息的位置。 无法通过示意图判断哪个服务正在发送消息以及哪个服务正在接收消息。 箭头仅显示这两个服务正在调用服务总线。 但是,详细信息中提供了有关哪个服务正在发送消息以及哪个服务正在接收消息的信息:

    由于每个调用都包含操作 ID,因此你还可以查看单个事务的端到端步骤,包括计时信息和每个步骤中的 HTTP 调用。 下面是一个此类事务的可视化效果:

    此可视化效果显示了从引入服务到队列、从队列到工作流服务以及从工作流服务到其他后端服务的步骤。 最后一步是工作流服务将服务总线消息标记为已完成。

    下面是调用后端服务失败的示例:

    此映射表明,在查询期间,对无人机计划程序服务的调用很大一部分 (36% ) 失败。 端到端事务视图显示向服务发送 HTTP PUT 请求时发生异常:

    如果进一步钻取,可以看到异常是套接字异常:“没有此类设备或地址”。

    Fabrikam.Workflow.Service.Services.BackendServiceCallFailedException: 
    No such device or address 
    ---u003e System.Net.Http.HttpRequestException: No such device or address 
    ---u003e System.Net.Sockets.SocketException: No such device or address
    

    此异常表明无法访问后端服务。 此时,可以使用 kubectl 查看部署配置。 在此示例中,由于 Kubernetes 配置文件中出现错误,服务主机名无法解析。 Kubernetes 文档中的调试服务一文提供了诊断此类错误的提示。

    以下是错误的一些常见原因:

  • 代码 bug。 这些 bug 可能显示为:
  •