监控 GKE 上的强化学习工作负载

本文档介绍了如何发出、收集和查看在 Google Kubernetes Engine (GKE) 上运行的基于 Python 的强化学习 (RL) 应用的关键指标和跟踪记录。

本文档介绍了如何完成以下任务:

  • 对 RL 应用进行插桩,以发出指标和跟踪记录。 使用的插桩适用于遵循 OpenTelemetry 格式的指标和跟踪记录。
  • 在应用在 GKE 上运行时收集指标和跟踪记录。 数据是使用适用于 GKE 的 Managed OpenTelemetry(预览版)收集的。
  • 在 Cloud Monitoring 中查看收集的指标,并在 Cloud Trace 中查看收集的跟踪记录。
  • 根据 OpenTelemetry 语义惯例和黄金信号识别和了解关键 RL 指标。黄金信号 是服务的四个关键指标,可提供 服务运行状况的高级概览:延迟时间流量错误饱和度

准备工作

  1. 确保您有一个基于 Python 的 RL 应用,并且您希望使用指标和跟踪记录数据对其进行监控。

  2. 确保您有启用了结算功能的 Google Cloud 项目。

  3. 您需要一个运行 GKE 1.34.1-gke.2178000 版或更高版本的 GKE 集群,这些版本中提供了适用于 GKE 的 Managed OpenTelemetry (预览版)。

  4. 已启用以下 Google Cloud API:

    • container.googleapis.com (GKE)
    • monitoring.googleapis.com (Monitoring)
    • cloudtrace.googleapis.com (Trace)
    • telemetry.googleapis.com (OpenTelemetry Telemetry API)

    您可以使用 gcloud 启用这些 API:

    gcloud services enable \
        container.googleapis.com \
        monitoring.googleapis.com \
        cloudtrace.googleapis.com \
        telemetry.googleapis.com
    
  5. 安装 OpenTelemetry SDK: 在 Python RL 应用的环境中,安装 OpenTelemetry SDK 和 OTLP 导出器:

    pip install opentelemetry-sdk \
                opentelemetry-exporter-otlp-proto-grpc \
                opentelemetry-api
    

    您可能还需要 RL 应用使用的任何框架的插桩库,例如 opentelemetry-instrumentation-flask

费用

向 Google Cloud发送遥测数据时,您需要按提取量 付费。指标按 Google Cloud Managed Service for Prometheus 价格收费,日志按 Cloud Logging 价格收费,跟踪记录按 Cloud Trace 价格收费。

如需了解与跟踪记录、日志和 Google Cloud Managed Service for Prometheus 指标提取关联的费用,请参阅 Google Cloud Observability 价格

使用 OpenTelemetry 对应用进行插桩

对 Python RL 应用代码进行插桩,使其能够发出 OpenTelemetry 指标。如需对应用进行插桩,请执行以下操作:

  1. 通过向应用添加以下代码来初始化 OpenTelemetry:

    import os
    import time
    from opentelemetry import metrics, trace
    from opentelemetry.sdk.metrics import MeterProvider
    from opentelemetry.sdk.metrics.export import PeriodicExportingMetricReader
    from opentelemetry.exporter.otlp.proto.grpc.metric_exporter import OTLPMetricExporter
    from opentelemetry.sdk.trace import TracerProvider
    from opentelemetry.sdk.trace.export import BatchSpanProcessor
    from opentelemetry.exporter.otlp.proto.grpc.trace_exporter import OTLPSpanExporter
    from opentelemetry.sdk.resources import Resource
    from opentelemetry.metrics import Counter, Histogram, UpDownCounter
    
    resource = Resource.create({
        "service.name": "rl-training-service",
        "service.namespace": "opentelemetry-demo",
    })
    
    # Initialize Metrics
    reader = PeriodicExportingMetricReader(
        OTLPMetricExporter(
            endpoint=os.environ.get("OTEL_EXPORTER_OTLP_METRICS_ENDPOINT", "localhost:4317"),
            insecure=True
        )
    )
    meter_provider = MeterProvider(metric_readers=[reader], resource=resource)
    metrics.set_meter_provider(meter_provider)
    meter = metrics.get_meter("rl-training-meter")
    
    # Initialize Tracing
    trace_provider = TracerProvider(resource=resource)
    trace_processor = BatchSpanProcessor(
        OTLPSpanExporter(
            endpoint=os.environ.get("OTEL_EXPORTER_OTLP_TRACES_ENDPOINT", "localhost:4317"),
            insecure=True
        )
    )
    trace_provider.add_span_processor(trace_processor)
    trace.set_tracer_provider(trace_provider)
    tracer = trace.get_tracer("rl-training-tracer")
    
  2. 为每个指标创建插桩,并记录您希望应用发出的值。将相关的语义惯例作为属性附加。

    使用语义惯例和黄金信号列表来 帮助确定要为应用插桩哪些指标。

    以下是特定指标的插桩示例:

    # Latency Histograms
    rl_loop_duration = meter.create_histogram(
        name="rl.loop.duration", description="Duration of a single RL loop iteration.", unit="ms"
    )
    rl_sample_duration = meter.create_histogram(
        name="rl.sample.duration", description="Duration of the sampling phase.", unit="ms"
    )
    rl_train_duration = meter.create_histogram(
        name="rl.train.duration", description="Duration of the training phase.", unit="ms"
    )
    # ... create other duration histograms (reward, train, sync, step)
    
    # Throughput Counters
    rl_sample_samples = meter.create_counter(
        name="rl.sample.samples", description="Number of samples generated.", unit="{samples}"
    )
    rl_train_steps = meter.create_counter(
        name="rl.train.steps", description="Number of training steps completed.", unit="{steps}"
    )
    # ... create other counter metrics (rl.sample.episodes, rl.train.tokens)
    
    # Performance/Saturation Gauges (using UpDownCounter)
    rl_reward_mean = meter.create_up_down_counter(
        name="rl.environment.reward.mean", description="Mean reward observed.", unit="1"
    )
    rl_train_loss = meter.create_up_down_counter(
        name="rl.train.loss", description="Current training loss.", unit="1"
    )
    rl_train_mfu = meter.create_up_down_counter(
        name="rl.train.mfu", description="Model Flop Utilization.", unit="1"
    )
    _rl_reward_mean_val, _rl_train_loss_val = 0.0, 0.0
    
    def get_common_attributes(rl_system, rl_run_id, rl_algorithm, rl_env_name, rl_model_name):
        return {
            "rl.system": rl_system,
            "rl.run.id": rl_run_id,
            "rl.algorithm": rl_algorithm,
            "rl.environment.name": rl_env_name,
            "rl.model.name": rl_model_name,
        }
    
    # Example Usage within your RL code:
    common_attrs = get_common_attributes("MyPPO", "run-42", "PPO", "Acrobot-v1", "PolicyModelV1")
    
    # Inside the main RL loop:
    with tracer.start_as_current_span("rl_loop_iteration", attributes={**common_attrs, "rl.loop.iteration": 5}) as span:
        loop_start_time = time.perf_counter()
    # --- Sampling Phase ---
    sample_start = time.perf_counter()
    # ... perform sampling ...
    sampled_count = 1024
    rl_sample_samples.add(sampled_count, attributes={**common_attrs, "rl.sample.batch_size": 128})
    rl_sample_duration.record((time.perf_counter() - sample_start) * 1000, attributes=common_attrs)
    
    # --- Training Phase ---
    train_start = time.perf_counter()
    # ... perform training step ...
    rl_train_steps.add(1, attributes={**common_attrs, "rl.loop.iteration": 5})
    current_loss = 0.125
    rl_train_loss.add(current_loss - _rl_train_loss_val, attributes=common_attrs) # Record current loss
    _rl_train_loss_val = current_loss
    rl_train_duration.record((time.perf_counter() - train_start) * 1000, attributes=common_attrs)
    
    # --- Record Mean Reward ---
    current_mean_reward = -5.5
    rl_reward_mean.add(current_mean_reward - _rl_reward_mean_val, attributes=common_attrs)
    _rl_reward_mean_val = current_mean_reward
    
    loop_duration = (time.perf_counter() - loop_start_time) * 1000
    rl_loop_duration.record(loop_duration, attributes={**common_attrs, "rl.loop.iteration": 5})
    
    # Ensure metrics are pushed before application exit in short-lived scripts
    # For long-running services, PeriodicExportingMetricReader handles this.
    # meter_provider.shutdown()
    

现在,您已初始化 OpenTelemetry 并为特定指标创建了插桩,应用在运行时会发出指定的遥测数据。

在 GKE 中启用指标和跟踪记录数据收集

如需收集应用在运行时发出的遥测数据,您 可以使用适用于 GKE 的 Managed OpenTelemetry (预览版)。此功能会收集遥测数据(例如指标和跟踪记录),并将数据发送到 Google Cloud Observability。

如需启用和配置适用于 GKE 的 Managed OpenTelemetry,请执行以下操作:

  1. 在运行应用的集群上启用适用于 GKE 的 Managed OpenTelemetry。为此,请按照 在集群中启用适用于 GKE 的 Managed OpenTelemetry中的步骤操作。

  2. 使用环境变量为应用部署添加注解,以指示 OpenTelemetry SDK 将遥测数据发送到托管收集器的 OTLP 端点。对于基于 Python 的 RL 应用,您无法使用适用于 GKE 的 Managed OpenTelemetry 中的自动配置功能。

    请改为在部署清单的容器规范中添加以下 env 部分:

    env:
      - name: OTEL_COLLECTOR_NAME
        value: 'opentelemetry-collector'
      - name: OTEL_COLLECTOR_NAMESPACE
        value: 'gke-managed-otel'
      - name: OTEL_EXPORTER_OTLP_METRICS_ENDPOINT
        value: $(OTEL_COLLECTOR_NAME).$(OTEL_COLLECTOR_NAMESPACE).svc.cluster.local:4317
      - name: OTEL_EXPORTER_OTLP_TRACES_ENDPOINT
        value: $(OTEL_COLLECTOR_NAME).$(OTEL_COLLECTOR_NAMESPACE).svc.cluster.local:4317
      - name: OTEL_SERVICE_NAME
        value: 'rl-training-service'
      - name: OTEL_RESOURCE_ATTRIBUTES
        value: service.namespace=opentelemetry-demo
    

现在,应用已插桩,并且托管收集器已启用并配置完毕,当应用在 GKE 集群上运行时,指标和跟踪记录会发送到 Google Cloud Observability。

您可以在 Monitoring 和 Trace 中查看此遥测数据。

在 Monitoring 中查看指标

在 GKE 上运行 RL 应用并启用 Managed OpenTelemetry 后,指标会发送到 Monitoring。这些指标通常位于 prometheus.googleapis.com/ 网域下。

如需在 Monitoring 中查看自定义 RL 指标,请执行以下操作:

  1. 如需在信息中心内查看 RL 指标,您可以执行以下任一操作:

  2. 在信息中心的指标 字段中,搜索以 prometheus.googleapis.com/ 开头的指标。可用的指标与您在应用中插桩的指标相对应。 这些指标的示例可能包括:

    • prometheus.googleapis.com/rl_loop_duration_histogram/
    • prometheus.googleapis.com/rl_sample_samples_total/
    • prometheus.googleapis.com/rl_environment_reward_mean_total/
  3. 过滤和分组: 您可以使用 Metrics Explorer 中的过滤条件来利用您添加为属性的语义惯例。例如,以下内容指定了特定运行和算法的循环时长:

    • 过滤条件:metric.label."rl_run_id" == "run-42"
    • 过滤条件:metric.label."rl_algorithm" == "PPO"
    • 分组依据:metric.label."rl_environment_name",以比较不同环境中的性能。

在 Trace 中查看跟踪记录

分布式跟踪记录提供了操作时间轴,可帮助您调试 RL 系统中的执行流程。

  1. 在 Google Cloud 控制台中,打开Trace Explorer在 Google Cloud 控制台中:

    前往 Trace Explorer 页面

  2. 您可以查询和过滤跟踪记录。由于您将 "service.name": "rl-training-service" 设置为资源属性,因此您可以按 resource.labels.service_name="rl-training-service" 过滤跟踪记录。

    跟踪记录中的各个 span 代表 RL 工作负载的不同部分。这些 span 可能包括对外部服务的调用或 RL 循环的不同阶段,具体取决于您在应用中插桩跟踪记录的方式。

RL 语义惯例和黄金信号

本部分列出了 OpenTelemetry 指标,这些指标可帮助您识别 RL 应用在 GKE 上运行时出现的问题。

使用本部分中的信息执行以下操作:

  • 确定要为应用收集哪些指标和跟踪记录。
  • 确定如何查看和使用从应用收集的指标和跟踪记录数据。

如需使用 OpenTelemetry 有效监控 RL 工作负载,最好专注于“黄金信号”。 黄金信号是服务的四个关键指标,可提供服务运行状况的高级概览:延迟时间流量错误饱和度。通过使用这些指标对 RL 应用进行插桩,您可以快速了解和调试性能问题。

以下部分按其在 RL 上下文中代表的黄金信号对 语义惯例指标名称进行了分类。

RL 语义惯例

以下是指标的属性。这些属性为 Monitoring 中的过滤和分析提供了上下文。

  • RL_SYSTEM = "rl.system":RL 系统或框架的名称(例如“MyCustomRL”)。
  • RL_SYSTEM_VERSION = "rl.system.version":RL 系统的版本。
  • RL_RUN_ID = "rl.run.id":特定训练运行的唯一标识符。
  • RL_ALGORITHM = "rl.algorithm":正在使用的 RL 算法(例如“PPO”“DQN”)。
  • RL_ENVIRONMENT_NAME = "rl.environment.name":RL 环境的名称(例如“CartPole-v1”)。
  • RL_MODEL_NAME = "rl.model.name":政策/值模型的名称或标识符。
  • RL_LOOP = "rl.loop":主训练循环的标识符。
  • RL_LOOP_ITERATION = "rl.loop.iteration":RL 循环的当前迭代次数。
  • RL_SAMPLE = "rl.sample":采样阶段的上下文。
  • RL_SAMPLE_EPISODES = "rl.sample.episodes":采样的剧集数。
  • RL_SAMPLE_STEPS = "rl.sample.steps":采样的步数。
  • RL_SAMPLE_BATCH_SIZE = "rl.sample.batch_size":采样期间使用的批次大小。
  • RL_REWARD = "rl.reward":奖励计算的上下文。
  • RL_REWARD_BATCH_SIZE = "rl.reward.batch_size":奖励计算的批次大小。
  • RL_REWARD_SANDBOX = "rl.reward.sandbox":奖励计算沙盒的标识符。
  • RL_TRAIN = "rl.train":训练阶段的上下文。
  • RL_TRAIN_STEPS = "rl.train.steps":训练步数。
  • RL_TRAIN_BATCH_SIZE = "rl.train.batch_size":训练期间使用的批次大小。
  • RL_TRAIN_TOKENS = "rl.train.tokens":训练期间处理的令牌数。
  • RL_SYNC = "rl.sync":同步操作的上下文。
  • RL_SYNC_BYTES = "rl.sync.bytes":同步期间传输的字节数。
  • RL_SYNC_SOURCE = "rl.sync.source":同步的来源。
  • RL_SYNC_DESTINATION = "rl.sync.destination":同步的目标。

黄金信号和 RL 指标

以下部分列出了与四个黄金 信号相关的 RL 指标:延迟时间流量错误饱和度

如需详细了解黄金信号,请参阅《Google 站点可靠性工程 (SRE)》一书第 6 章中的四个黄金信号

延迟时间

完成关键操作需要多长时间?延迟时间过长可能表示在完成关键操作时存在延迟。以下指标可帮助您识别 RL 应用在 GKE 上运行时出现的任何延迟时间问题。

  • rl.loop.duration (直方图):循环时长过长会减慢整个训练过程。监控此指标有助于识别 RL 周期任何部分的性能回归。
  • rl.sample.duration (直方图):采样速度慢会直接影响为训练生成新数据的速度。
  • rl.reward.duration (直方图):奖励计算可能很复杂;跟踪其延迟时间有助于优化此关键步骤。
  • rl.train.duration (直方图):训练时间对于迭代速度至关重要。此处的峰值可能指向训练算法或硬件中的问题。
  • rl.sync.duration (直方图):高效同步在分布式 RL 中至关重要。同步时间过长可能会导致数据过时并减慢学习速度。
  • rl.step.duration (直方图):各个环境步骤的精细延迟时间。

流量和吞吐量

正在完成多少工作?吞吐量低可能意味着资源使用效率低下。以下指标可帮助您识别 RL 应用在 GKE 上运行时出现的任何流量或吞吐量问题。

  • rl.sample.samples (计数器):表示收集的体验数据量。下降表示采样过程中存在问题。
  • rl.sample.episodes (计数器):跟踪运行的完整剧集数。
  • rl.train.steps (计数器):根据优化步骤衡量训练进度。
  • rl.train.tokens (计数器):跟踪处理的令牌总数。此指标与大型模型 RL 相关。
  • rl.tokens.rate / rl.tokens.rate_per_gpu(测量值/速率):直接衡量训练速度和效率,尤其是在基于 token 的模型中。
  • rl.samples.rate / rl.samples.rate_per_gpu(测量值/速率):衡量系统收集新样本的速度。

错误

是否存在任何性能或运行错误?在 RL 中,“错误”可能会表现为意外行为或性能不佳。以下指标可帮助您识别 RL 应用在 GKE 上运行时出现的任何错误。

  • rl.environment.reward.mean (测量值):虽然不是传统错误,但平均奖励的 大幅下降 是一个关键信号,表明代理或环境互动存在问题。此指标直接反映了学习进度和代理性能。
  • rl.environment.episode.length.mean (测量值):与奖励类似,剧集长度的意外变化可能表示存在问题。
  • rl.train.loss (测量值):训练损失的突然 增加 或异常行为表明模型没有有效学习。训练稳定性和成功的基本指标。

饱和度

系统是否过载?饱和度过高可能会导致性能下降。以下指标可帮助您识别 RL 应用在 GKE 上运行时出现的任何饱和度问题。

  • rl.train.mfu(测量值):模型 Flop 利用率 (MFU)。表示在训练期间计算资源(例如 GPU 或 TPU)的使用效率。MFU 低表示利用率不足或存在瓶颈。

后续步骤