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

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

本页面介绍如何完成以下任务:

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

准备工作

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

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

  3. 您需要运行 GKE 1.34.1-gke.2178000 版或更高版本的 GKE 集群,这些版本支持 GKE 专用托管式 OpenTelemetry(预览版)。

  4. 启用以下 Google Cloud API:

    • container.googleapis.com (GKE)
    • monitoring.googleapis.com(监控)
    • cloudtrace.googleapis.com(轨迹)
    • 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 启用和配置托管式 OpenTelemetry,请执行以下操作:

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

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

    请改为在 Deployment 清单中将以下 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 中查看指标

在启用了 Managed OpenTelemetry 的情况下,当 RL 应用在 GKE 上运行时,指标会发送到 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 控制台中,打开 Google Cloud 控制台中的 Trace 探索器:

    前往 Trace 探索器页面

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

    轨迹中的各个 span 代表强化学习工作负载的不同部分。这些 span 可能包括对外部服务的调用或 RL 循环的不同阶段,具体取决于您在应用中检测跟踪的方式。

RL 语义惯例和黄金信号

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

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

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

如需使用 OpenTelemetry 有效监控 RL 工作负载,最好重点关注“黄金信号”。黄金信号是指服务的四项关键指标,可提供服务健康状况的概要视图:延迟时间、流量、错误和饱和度。通过使用这些指标检测 RL 应用,您可以快速了解并调试性能问题。

以下部分包含 semantic conventions 和按其在 RL 上下文中表示的黄金信号分类的指标名称。

RL 语义惯例

以下是指标的属性。这些属性为监控中的过滤和分析提供背景信息。

  • RL_SYSTEM = "rl.system":强化学习系统或框架的名称(例如“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":强化学习环境的名称(例如“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":训练期间处理的 token 数。
  • 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.step.duration(直方图):各个环境步的精细延迟时间。

流量和吞吐量

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

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

错误

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

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

饱和度

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

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

后续步骤