KubeRay 自动伸缩#

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

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

何时使用自动扩展?

自动扩展可以降低工作负载成本,但会增加节点启动开销,并且配置起来可能很棘手。如果你是Ray的新手,我们建议从非自动扩展集群开始。

Ray 自动扩展 V2 alpha 版与 KubeRay (@ray 2.10.0)

在 Ray 2.10 中,Ray Autoscaler V2 alpha 版本与 KubeRay 一起可用。它在可观察性和稳定性方面有所改进。详情请参见 section

概述#

下图展示了 Ray Autoscaler 与 KubeRay 操作符的集成。虽然为了清晰起见被描绘为一个独立的实体,但实际上 Ray Autoscaler 是在实际实现中 Ray 头 Pod 内的一个边车容器。

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

KubeRay 中的 3 级自动扩展

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

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

  • Kubernetes 节点:如果 Kubernetes 集群缺乏足够的资源来创建 Ray Autoscaler 生成的新 Ray Pod,Kubernetes Autoscaler 可以提供一个新的 Kubernetes 节点。您必须自行配置 Kubernetes Autoscaler。

  • Autoscaler 通过以下事件序列扩展集群:

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

    2. Ray 头容器聚合了工作负载的资源需求,并将它们传达给 Ray Autoscaler 边车。

    3. 自动缩放器决定添加一个 Ray 工作节点 Pod 以满足工作负载的资源需求。

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

    5. KubeRay 操作符会创建一个 Ray 工作 Pod 以匹配新的 replicas 规格。

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

  • 自动伸缩器还会通过移除空闲的工作者Pod来缩减集群。如果它发现一个空闲的工作者Pod,它会减少RayCluster CR的replicas字段的计数,并将识别出的Pod添加到CR的workersToDelete字段中。然后,KubeRay操作员会删除workersToDelete字段中的Pod。

快速入门#

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

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

步骤 2:安装 KubeRay 操作员#

按照 此文档 通过 Helm 仓库安装最新稳定版本的 KubeRay 操作员。

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

kubectl apply -f https://raw.githubusercontent.com/ray-project/kuberay/v1.1.1/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 有一个头部 Pod 和零个工作 Pod。头部 Pod 有两个容器:一个 Ray 头部容器和一个 Ray Autoscaler 辅助容器。此外,ray-cluster.autoscaler.yaml 包含一个名为 ray-example 的 ConfigMap,其中存放了两个 Python 脚本:detached_actor.pyterminate_detached_actor.py

  • detached_actor.py 是一个创建需要1个CPU的分离角色的Python脚本。

    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脚本。

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

步骤5:通过创建分离的执行者来触发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 为每个新的分离角色生成一个新的工作 Pod。这是因为 Ray 头中的 rayStartParams 字段指定了 num-cpus: "0",阻止了 Ray 调度器在 Ray 头 Pod 上调度任何 Ray 角色或任务。此外,每个 Ray 工作 Pod 都有 1 个 CPU 的容量,因此 Autoscaler 创建一个新的工作 Pod 以满足分离角色所需的 1 个 CPU 的资源要求。

  • 使用分离的执行者并不是触发集群扩展的必要条件。普通的执行者和任务也可以启动它。分离的执行者 即使在作业的驱动进程退出后仍然保持持久,这就是为什么当 detached_actor.py 进程退出时,自动缩放器不会自动缩小集群规模,从而使本教程更加方便。

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

  • (高级) Ray Autoscaler 还提供了一个 Python SDK,使高级用户(如 Ray 维护者)能够直接从 Autoscaler 请求资源。通常,大多数用户不需要使用该 SDK。

步骤 6:通过终止分离的执行者来触发 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.1.1/ray-operator/config/samples/ray-cluster.autoscaler.yaml

# Uninstall the KubeRay operator
helm uninstall kuberay-operator

KubeRay 自动伸缩配置#

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

1. Enabling autoscaling#

  • enableInTreeAutoscaling: 通过设置 enableInTreeAutoscaling: true,KubeRay 操作符会自动为 Ray 头 Pod 配置一个自动缩放的边车容器。

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

2. Scale-up and scale-down speed#

如有必要,您可以调节向集群添加或移除节点的速度。对于具有大量短期任务的应用程序,考虑采取更为保守的方法来调整扩展和缩减速度可能是有益的。

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

  • upscalingMode: 这控制了放大过程的速率。有效值为:

    • 保守的: 升级是速率限制的;待处理的 worker Pod 数量最多为连接到 Ray 集群的 worker Pod 数量。

    • 默认: 放大不受速率限制。

    • Aggressive: 是 Default 的别名;放大不受速率限制。

  • idleTimeoutSeconds (默认 60s): 这表示在缩减空闲工作节点之前等待的秒数。当工作节点没有任何活动任务、角色或引用对象(无论是存储在内存中还是溢出到磁盘)时,该节点被视为空闲。

3. Autoscaler sidecar container#

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

  • resources: autoscalerOptionsresources 子字段为 Autoscaler 边车容器设置了可选的资源覆盖。这些覆盖应按照标准的 容器资源规格格式 指定。默认值如下所示:

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

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

  • envenvFrom: 这些字段指定 Autoscaler 容器的环境变量。这些字段应按照 Kubernetes API 中容器环境变量的格式进行格式化。

4. Set the rayStartParams and the resource limits for the Ray container#

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 will fail to start if the CPU resource limit for the worker
            # container is commented out because `rayStartParams` is empty.
            cpu: "1"
            memory: "1G"
          requests:
            cpu: "1"
            memory: "1G"

下一步#

有关 Ray Autoscaler 和 Kubernetes 自动缩放器之间关系的更多详情,请参阅 (高级) 理解 Ray Autoscaler 在 Kubernetes 环境中的应用

带有 KubeRay 的自动扩展器 V2#

先决条件#

  • Ray 2.10.0 或 nightly Ray 版本

  • KubeRay 1.1.0 或更高版本

Ray 2.10.0 的发布引入了与 KubeRay 集成的 Ray Autoscaler V2 的 alpha 版本,在可观察性和稳定性方面带来了增强:

  1. 可观测性:Autoscaler V2 提供了每个 Ray 工作节点生命周期的实例级追踪,使得调试和理解 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 不同,后者可能在终止处理时过早地终止不再空闲的节点(可能导致任务或角色失败),V2 采用了 Ray 的优雅排空机制,确保空闲节点在不影响正在进行的任务或角色的情况下被终止。

为了启用 Autoscaler V2,可以按如下方式修改 ray-cluster.autoscaler.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.10.0'
...


# 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.10.0
          # 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
        ...