KubeRay 自动扩缩容#

本指南介绍了如何在 Kubernetes 上配置 Ray Autoscaler。Ray Autoscaler 是一个 Ray 集群进程,它根据资源需求自动向上或向下扩展集群。Autoscaler 通过根据任务、Actor 或放置组所需的资源来调整集群中的节点(Ray Pod)数量来实现这一点。

Autoscaler 利用逻辑资源请求进行扩展,这些请求在 @ray.remote 中指定并在 ray status 中显示,而不是根据物理机器的利用率。如果你启动一个 Actor、任务或放置组,而资源不足,Autoscaler 会将请求排队。它会调整节点数量以满足队列需求,并随着时间推移移除没有任务、Actor 或对象的空闲节点。

何时使用自动扩缩容?

自动扩缩容可以降低工作负载成本,但会增加节点启动开销,并且配置可能比较棘手。如果你刚开始使用 Ray,我们建议从非自动扩缩容集群开始。

KubeRay 中的 Ray Autoscaling V2 alpha (@ray 2.10.0)

从 Ray 2.10 开始,Ray Autoscaler V2 alpha 在 KubeRay 中可用。它在可观测性和稳定性方面有所改进。更多详细信息请参见该章节

概述#

下图说明了 Ray Autoscaler 与 KubeRay operator 的集成。尽管为了清晰起见将其描绘为独立的实体,但在实际实现中,Ray Autoscaler 实际上是 Ray head Pod 中的一个 sidecar 容器。

../../../_images/AutoscalerOperator.svg

KubeRay 中的 3 级自动扩缩容

  • Ray actor/任务:一些 Ray 库,如 Ray Serve,可以根据传入的请求量自动调整 Serve 副本(即 Ray actor)的数量。

  • Ray 节点:Ray Autoscaler 根据 Ray actor/任务的资源需求自动调整 Ray 节点(即 Ray Pod)的数量。

  • Kubernetes 节点:如果 Kubernetes 集群缺少 Ray Autoscaler 创建的新 Ray Pod 所需的足够资源,Kubernetes Autoscaler 可以配置新的 Kubernetes 节点。你必须自己配置 Kubernetes Autoscaler。

  • Autoscaler 通过以下一系列事件向上扩展集群

    1. 用户提交 Ray 工作负载。

    2. Ray head 容器汇总工作负载资源需求并将其传达给 Ray Autoscaler sidecar。

    3. Autoscaler 决定添加一个 Ray worker Pod 来满足工作负载的资源需求。

    4. Autoscaler 通过增加 RayCluster CR 的 replicas 字段来请求一个额外的 worker Pod。

    5. KubeRay operator 创建一个 Ray worker Pod 以匹配新的 replicas 规范。

    6. Ray 调度器将用户的工作负载放置到新的 worker Pod 上。

  • Autoscaler 还通过移除空闲的 worker Pod 向下扩展集群。如果发现空闲的 worker Pod,它会减少 RayCluster CR 中 replicas 字段的计数,并将识别出的 Pod 添加到 CR 的 workersToDelete 字段中。然后,KubeRay operator 删除 workersToDelete 字段中的 Pod。

快速入门#

步骤 1:使用 Kind 创建 Kubernetes 集群#

kind create cluster --image=kindest/node:v1.26.0

步骤 2:安装 KubeRay operator#

按照本文档,通过 Helm 仓库安装最新的稳定版 KubeRay operator。

步骤 3:创建一个启用自动扩缩容的 RayCluster 自定义资源#

kubectl apply -f https://raw.githubusercontent.com/ray-project/kuberay/v1.3.0/ray-operator/config/samples/ray-cluster.autoscaler.yaml

步骤 4:验证 Kubernetes 集群状态#

# Step 4.1: List all Ray Pods in the `default` namespace.
kubectl get pods -l=ray.io/is-ray-node=yes

# [Example output]
# NAME                               READY   STATUS    RESTARTS   AGE
# raycluster-autoscaler-head-6zc2t   2/2     Running   0          107s

# Step 4.2: Check the ConfigMap in the `default` namespace.
kubectl get configmaps

# [Example output]
# NAME                  DATA   AGE
# ray-example           2      21s
# ...

RayCluster 有一个 head Pod 和零个 worker Pod。head Pod 有两个容器:一个 Ray head 容器和一个 Ray Autoscaler sidecar 容器。此外,ray-cluster.autoscaler.yaml 中包含一个名为 ray-example 的 ConfigMap,其中存放着两个 Python 脚本:detached_actor.pyterminate_detached_actor.py

  • detached_actor.py 是一个 Python 脚本,它创建一个需要 1 个 CPU 的 detached actor。

    import ray
    import sys
    
    @ray.remote(num_cpus=1)
    class Actor:
      pass
    
    ray.init(namespace="default_namespace")
    Actor.options(name=sys.argv[1], lifetime="detached").remote()
    
  • terminate_detached_actor.py 是一个 Python 脚本,它终止一个 detached actor。

    import ray
    import sys
    
    ray.init(namespace="default_namespace")
    detached_actor = ray.get_actor(sys.argv[1])
    ray.kill(detached_actor)
    

步骤 5:通过创建 detached actor 触发 RayCluster 向上扩展#

# Step 5.1: Create a detached actor "actor1" which requires 1 CPU.
export HEAD_POD=$(kubectl get pods --selector=ray.io/node-type=head -o custom-columns=POD:metadata.name --no-headers)
kubectl exec -it $HEAD_POD -- python3 /home/ray/samples/detached_actor.py actor1

# Step 5.2: The Ray Autoscaler creates a new worker Pod.
kubectl get pods -l=ray.io/is-ray-node=yes

# [Example output]
# NAME                                             READY   STATUS    RESTARTS   AGE
# raycluster-autoscaler-head-xxxxx                 2/2     Running   0          xxm
# raycluster-autoscaler-worker-small-group-yyyyy   1/1     Running   0          xxm

# Step 5.3: Create a detached actor which requires 1 CPU.
kubectl exec -it $HEAD_POD -- python3 /home/ray/samples/detached_actor.py actor2
kubectl get pods -l=ray.io/is-ray-node=yes

# [Example output]
# NAME                                             READY   STATUS    RESTARTS   AGE
# raycluster-autoscaler-head-xxxxx                 2/2     Running   0          xxm
# raycluster-autoscaler-worker-small-group-yyyyy   1/1     Running   0          xxm
# raycluster-autoscaler-worker-small-group-zzzzz   1/1     Running   0          xxm

# Step 5.4: List all actors in the Ray cluster.
kubectl exec -it $HEAD_POD -- ray list actors


# ======= List: 2023-09-06 13:26:49.228594 ========
# Stats:
# ------------------------------
# Total: 2

# Table:
# ------------------------------
#     ACTOR_ID  CLASS_NAME    STATE    JOB_ID    NAME    ...
#  0  xxxxxxxx  Actor         ALIVE    02000000  actor1  ...
#  1  xxxxxxxx  Actor         ALIVE    03000000  actor2  ...

Ray Autoscaler 会为每个新的 detached actor 生成一个新的 worker Pod。这是因为 Ray head 中的 rayStartParams 字段指定了 num-cpus: "0",这阻止了 Ray 调度器在 Ray head Pod 上调度任何 Ray actor 或任务。此外,每个 Ray worker Pod 的容量是 1 个 CPU,因此 Autoscaler 会创建一个新的 worker Pod 来满足需要 1 个 CPU 的 detached actor 的资源需求。

  • 使用 detached actor 并非触发集群向上扩展的必要条件。普通 actor 和任务也可以触发。 Detached actor 即使在作业的 driver 进程退出后也仍然存在,这就是为什么当 detached_actor.py 进程退出时,Autoscaler 不会自动缩减集群,这使得本教程更加方便。

  • 在这个 RayCluster 自定义资源中,从 Ray Autoscaler 的角度来看,每个 Ray worker Pod 只有 1 个逻辑 CPU。因此,如果你创建一个需要 2 个 CPU 的 detached actor(使用 @ray.remote(num_cpus=2)),Autoscaler 不会启动新的 worker Pod 创建,因为现有 Pod 的容量仅限于 1 个 CPU。

  • (高级) Ray Autoscaler 还提供了 Python SDK,使高级用户(如 Ray 维护者)能够直接向 Autoscaler 请求资源。一般来说,大多数用户无需使用该 SDK。

步骤 6:通过终止 detached actor 触发 RayCluster 向下扩展#

# Step 6.1: Terminate the detached actor "actor1".
kubectl exec -it $HEAD_POD -- python3 /home/ray/samples/terminate_detached_actor.py actor1

# Step 6.2: A worker Pod will be deleted after `idleTimeoutSeconds` (default 60s) seconds.
kubectl get pods -l=ray.io/is-ray-node=yes

# [Example output]
# NAME                                             READY   STATUS    RESTARTS   AGE
# raycluster-autoscaler-head-xxxxx                 2/2     Running   0          xxm
# raycluster-autoscaler-worker-small-group-zzzzz   1/1     Running   0          xxm

# Step 6.3: Terminate the detached actor "actor2".
kubectl exec -it $HEAD_POD -- python3 /home/ray/samples/terminate_detached_actor.py actor2

# Step 6.4: A worker Pod will be deleted after `idleTimeoutSeconds` (default 60s) seconds.
kubectl get pods -l=ray.io/is-ray-node=yes

# [Example output]
# NAME                                             READY   STATUS    RESTARTS   AGE
# raycluster-autoscaler-head-xxxxx                 2/2     Running   0          xxm

步骤 7:Ray Autoscaler 可观测性#

# Method 1: "ray status"
kubectl exec $HEAD_POD -it -c ray-head -- ray status

# [Example output]:
# ======== Autoscaler status: 2023-09-06 13:42:46.372683 ========
# Node status
# ---------------------------------------------------------------
# Healthy:
#  1 head-group
# Pending:
#  (no pending nodes)
# Recent failures:
#  (no failures)

# Resources
# ---------------------------------------------------------------
# Usage:
#  0B/1.86GiB memory
#  0B/514.69MiB object_store_memory

# Demands:
#  (no resource demands)

# Method 2: "kubectl logs"
kubectl logs $HEAD_POD -c autoscaler | tail -n 20

# [Example output]:
# 2023-09-06 13:43:22,029 INFO autoscaler.py:421 --
# ======== Autoscaler status: 2023-09-06 13:43:22.028870 ========
# Node status
# ---------------------------------------------------------------
# Healthy:
#  1 head-group
# Pending:
#  (no pending nodes)
# Recent failures:
#  (no failures)

# Resources
# ---------------------------------------------------------------
# Usage:
#  0B/1.86GiB memory
#  0B/514.69MiB object_store_memory

# Demands:
#  (no resource demands)
# 2023-09-06 13:43:22,029 INFO autoscaler.py:464 -- The autoscaler took 0.036 seconds to complete the update iteration.

步骤 8:清理 Kubernetes 集群#

# Delete RayCluster and ConfigMap
kubectl delete -f https://raw.githubusercontent.com/ray-project/kuberay/v1.3.0/ray-operator/config/samples/ray-cluster.autoscaler.yaml

# Uninstall the KubeRay operator
helm uninstall kuberay-operator

KubeRay 自动扩缩容配置#

快速入门示例中使用的 ray-cluster.autoscaler.yaml 包含有关配置选项的详细注释。建议结合 YAML 文件阅读本节。

1. 启用自动扩缩容#

  • enableInTreeAutoscaling:通过设置 enableInTreeAutoscaling: true,KubeRay operator 会自动为 Ray head Pod 配置一个 autoscaling sidecar 容器。

  • minReplicas / maxReplicas / replicas:设置 minReplicasmaxReplicas 字段来定义 autoscaling workerGroupreplicas 的范围。通常,在部署 autoscaling 集群时,会将 replicasminReplicas 初始化为相同的值。随后,Ray Autoscaler 会根据集群中添加或移除的 Pod 来调整 replicas 字段。

2. 向上扩展和向下扩展速度#

如有必要,你可以调节集群添加或移除节点的节奏。对于具有大量短时任务的应用,考虑采用更保守的方式来调整向上扩展和向下扩展的速度可能会有益。

使用 RayCluster CR 的 autoscalerOptions 字段来实现这一点。该字段包含以下子字段

  • upscalingMode:这控制向上扩展过程的速度。有效值包括

    • Conservative(保守):向上扩展速率受限;待处理的 worker Pod 数量最多与连接到 Ray 集群的 worker Pod 数量相等。

    • Default(默认):向上扩展不受速率限制。

    • Aggressive(激进):Default 的别名;向上扩展不受速率限制。

  • idleTimeoutSeconds(默认 60 秒):这表示在缩减空闲 worker pod 之前等待的时间(秒)。当 worker 节点没有活跃的任务、actor 或引用的对象(无论是存储在内存中还是溢出到磁盘上)时,它被认为是空闲的。

3. Autoscaler sidecar 容器#

autoscalerOptions 字段还提供了配置 Autoscaler 容器的选项。通常情况下,无需指定这些选项。

  • resources:《code class="docutils literal notranslate">autoscalerOptionsresources 子字段设置了 Autoscaler sidecar 容器的可选资源覆盖。这些覆盖应按照标准的 容器资源规范格式 指定。默认值如下所示

    resources:
      limits:
        cpu: "500m"
        memory: "512Mi"
      requests:
        cpu: "500m"
        memory: "512Mi"
    
  • image:此字段覆盖 Autoscaler 容器镜像。默认情况下,该容器使用与 Ray 容器相同的 image

  • imagePullPolicy:此字段覆盖 Autoscaler 容器的镜像拉取策略。默认值为 IfNotPresent

  • envenvFrom:这些字段指定 Autoscaler 容器的环境变量。这些字段的格式应遵循 Kubernetes API 中关于容器环境变量的规定。

4. 设置 rayStartParams 和 Ray 容器的资源限制#

从 Ray 2.41.0 开始,资源限制是可选的

从 Ray 2.41.0 开始,Ray Autoscaler 可以从 Ray 容器的 rayStartParams、资源限制或资源请求中读取资源规范。你必须至少指定其中一个字段。早期版本仅支持 rayStartParams 或资源限制,并且不识别资源请求。

Ray Autoscaler 读取 RayCluster 自定义资源规范中的 rayStartParams 字段或 Ray 容器的资源限制来确定 Ray Pod 的资源需求。关于 CPU 数量的信息对于 Ray Autoscaler 扩展集群至关重要。因此,如果没有这些信息,Ray Autoscaler 将报告错误并无法启动。以下面的 ray-cluster.autoscaler.yaml 为例

  • 如果用户在 rayStartParams 中设置了 num-cpus,则无论容器上的资源限制如何,Ray Autoscaler 都可以工作。

  • 如果用户没有设置 rayStartParams,则 Ray 容器必须指定 CPU 资源限制。

headGroupSpec:
  rayStartParams:
    num-cpus: "0"
  template:
    spec:
      containers:
      - name: ray-head
        resources:
          # The Ray Autoscaler still functions if you comment out the `limits` field for the
          # head container, as users have already specified `num-cpus` in `rayStartParams`.
          limits:
            cpu: "1"
            memory: "2G"
          requests:
            cpu: "1"
            memory: "2G"
...
workerGroupSpecs:
- groupName: small-group
  rayStartParams: {}
  template:
    spec:
      containers:
      - name: ray-worker
        resources:
          limits:
            # The Ray Autoscaler versions older than 2.41.0 will fail to start if the CPU resource limit for the worker
            # container is commented out because `rayStartParams` is empty.
            # The Ray Autoscaler starting from 2.41.0 will not fail but use the resource requests if the resource
            # limits are commented out and `rayStartParams` is empty.
            cpu: "1"
            memory: "1G"
          requests:
            cpu: "1"
            memory: "1G"

5. Autoscaler 环境变量配置#

你可以使用 RayCluster 自定义资源的 autoscalerOptions 部分下的 envenvFrom 字段中指定的环境变量来配置 Ray autoscaler。这些变量提供了对 autoscaler 内部行为的细粒度控制。

例如,AUTOSCALER_UPDATE_INTERVAL_S 决定了 autoscaler 检查集群状态并决定是否向上或向下扩展的频率。

完整示例请参阅 ray-cluster.autoscaler.yamlray-cluster.autoscaler-v2.yaml

autoscalerOptions:
  env:
    - name: AUTOSCALER_UPDATE_INTERVAL_S
      value: "5"

下一步#

有关 Ray Autoscaler 与 Kubernetes autoscaler 之间关系的更多详细信息,请参阅(高级) 理解 Ray Autoscaler 在 Kubernetes 环境中的作用

KubeRay 中的 Autoscaler V2#

先决条件#

  • KubeRay v1.3.0 和最新的 Ray 版本是使用 Autoscaler V2 的首选设置。

Ray 2.10.0 版本引入了与 KubeRay 集成的 Ray Autoscaler V2 alpha 版本,带来了可观测性和稳定性的增强

  1. 可观测性:Autoscaler V2 为每个 Ray worker 的生命周期提供了实例级跟踪,使得调试和理解 Autoscaler 的行为更容易。它还报告了每个节点的空闲信息,包括节点空闲或活跃原因的详细信息

> ray status -v

======== Autoscaler status: 2024-03-08 21:06:21.023751 ========
GCS request time: 0.003238s

Node status
---------------------------------------------------------------
Active:
 1 node_40f427230584b2d9c9f113d8db51d10eaf914aa9bf61f81dc7fabc64
Idle:
 1 node_2d5fd3d4337ba5b5a8c3106c572492abb9a8de2dee9da7f6c24c1346
Pending:
 (no pending nodes)
Recent failures:
 (no failures)

Resources
---------------------------------------------------------------
Total Usage:
 1.0/64.0 CPU
 0B/72.63GiB memory
 0B/33.53GiB object_store_memory

Total Demands:
 (no resource demands)

Node: 40f427230584b2d9c9f113d8db51d10eaf914aa9bf61f81dc7fabc64
 Usage:
  1.0/32.0 CPU
  0B/33.58GiB memory
  0B/16.79GiB object_store_memory
 # New in autoscaler V2: activity information
 Activity:
  Busy workers on node.
  Resource: CPU currently in use.

Node: 2d5fd3d4337ba5b5a8c3106c572492abb9a8de2dee9da7f6c24c1346
 # New in autoscaler V2: idle information
 Idle: 107356 ms
 Usage:
  0.0/32.0 CPU
  0B/39.05GiB memory
  0B/16.74GiB object_store_memory
 Activity:
  (no activity)
  1. 稳定性:Autoscaler V2 在空闲节点处理方面进行了重大改进。V1 autoscaler 可能会在终止处理过程中停止那些变得活跃的节点,从而可能导致任务或 actor 失败。V2 使用 Ray 的优雅排除机制,可以安全地停止空闲节点,而不会中断正在进行的工作。

ray-cluster.autoscaler-v2.yaml 是一个启用 Autoscaler V2 的 RayCluster YAML 示例文件。

# Change 1: Select the Ray version to either the nightly build or version 2.10.0+
spec:
  # Specify Ray version 2.10.0 or use the nightly build.
  rayVersion: '2.X.Y'
...


# Change 2: Enable Autoscaler V2 by setting the RAY_enable_autoscaler_v2 environment variable on the Ray head container.
  headGroupSpec:
    template:
      spec:
        containers:
        - name: ray-head
          image: rayproject/ray:2.X.Y
          # Include the environment variable.
          env:
            - name: RAY_enable_autoscaler_v2
              value: "1"
        restartPolicy: Never # Prevent container restart to maintain Ray health.


# Change 3: Prevent Kubernetes from restarting Ray worker pod containers, enabling correct instance management by Ray.
  workerGroupSpecs:
  - replicas: 1
    template:
      spec:
        restartPolicy: Never
        ...