KubeRay 中 GCS 的容错#
全局控制服务 (GCS) 管理集群级别的元数据。默认情况下,GCS 缺乏容错能力,因为它将所有数据存储在内存中,一旦发生故障,整个 Ray 集群都会崩溃。要使 GCS 具有容错能力,您必须拥有一个高可用的 Redis。这样,在 GCS 重启时,它会从 Redis 实例中检索所有数据并恢复其正常功能。
GCS 未启用容错时的命运共享
在 GCS 未启用容错的情况下,Ray 集群、GCS 进程和 Ray Head Pod 之间存在命运共享。如果 GCS 进程崩溃,Ray Head Pod 将在 RAY_gcs_rpc_server_reconnect_timeout_s 秒后随之崩溃。如果 Ray Head Pod 根据 Pod 的 restartPolicy 重启,Worker Pod 会尝试重新连接到新的 Head Pod。Worker Pod 会被新的 Head Pod 终止;在未启用 GCS 容错的情况下,集群状态会丢失,新的 Head Pod 会将 Worker Pod 视为“未知 Worker”。这对大多数 Ray 应用程序来说是足够好的;但是,对于 Ray Serve 来说,这并不理想,尤其是当高可用性对您的用例至关重要时。因此,我们建议在 RayService 自定义资源上启用 GCS 容错以确保高可用性。有关更多信息,请参阅 Ray Serve 端到端容错文档。
另请参阅
如果您还需要 Redis 的容错,请参阅 为持久化容错 GCS 调优 Redis。
用例#
Ray Serve:推荐的配置是在 RayService 自定义资源上启用 GCS 容错以确保高可用性。有关更多信息,请参阅 Ray Serve 端到端容错文档。
其他工作负载:不推荐 GCS 容错,且兼容性不保证。
先决条件#
Ray 2.0.0+
KubeRay 1.3.0+
Redis:单分片 Redis Cluster 或 Redis Sentinel,一个或多个副本
快速入门#
步骤 1:使用 Kind 创建 Kubernetes 集群#
kind create cluster --image=kindest/node:v1.26.0
步骤 2:安装 KubeRay operator#
按照 本指南 通过 Helm 仓库安装最新的稳定版 KubeRay Operator。
步骤 3:安装启用了 GCS FT 的 RayCluster#
curl -LO https://raw.githubusercontent.com/ray-project/kuberay/v1.5.1/ray-operator/config/samples/ray-cluster.external-redis.yaml
kubectl apply -f ray-cluster.external-redis.yaml
步骤 4:验证 Kubernetes 集群状态#
# Step 4.1: List all Pods in the `default` namespace.
# The expected output should be 4 Pods: 1 head, 1 worker, 1 KubeRay, and 1 Redis.
kubectl get pods
# [Example output]
# NAME READY STATUS RESTARTS AGE
# kuberay-operator-6bc45dd644-ktbnh 1/1 Running 0 3m4s
# raycluster-external-redis-head 1/1 Running 0 2m41s
# raycluster-external-redis-small-group-worker-dwt98 1/1 Running 0 2m41s
# redis-6cf756c755-qljcv 1/1 Running 0 2m41s
# Step 4.2: List all ConfigMaps in the `default` namespace.
kubectl get configmaps
# [Example output]
# NAME DATA AGE
# kube-root-ca.crt 1 3m4s
# ray-example 2 5m45s
# redis-config 1 5m45s
该 ray-cluster.external-redis.yaml 文件定义了 RayCluster、Redis 和 ConfigMaps 的 Kubernetes 资源。此示例中有两个 ConfigMaps:ray-example 和 redis-config。ray-example ConfigMap 包含两个 Python 脚本:detached_actor.py 和 increment_counter.py。
detached_actor.py是一个创建名为counter_actor的独立 Actor 的 Python 脚本。import ray @ray.remote(num_cpus=1) class Counter: def __init__(self): self.value = 0 def increment(self): self.value += 1 return self.value ray.init(namespace="default_namespace") Counter.options(name="counter_actor", lifetime="detached").remote()
increment_counter.py是一个递增计数器的 Python 脚本。import ray ray.init(namespace="default_namespace") counter = ray.get_actor("counter_actor") print(ray.get(counter.increment.remote()))
步骤 5:创建独立 Actor#
# Step 5.1: Create a detached actor with the name, `counter_actor`.
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
# 2025-04-18 02:51:25,359 INFO worker.py:1514 -- Using address 127.0.0.1:6379 set in the environment variable RAY_ADDRESS
# 2025-04-18 02:51:25,361 INFO worker.py:1654 -- Connecting to existing Ray cluster at address: 10.244.0.8:6379...
# 2025-04-18 02:51:25,557 INFO worker.py:1832 -- Connected to Ray cluster. View the dashboard at 10.244.0.8:8265
# Step 5.2: Increment the counter.
kubectl exec -it $HEAD_POD -- python3 /home/ray/samples/increment_counter.py
# 2025-04-18 02:51:29,069 INFO worker.py:1514 -- Using address 127.0.0.1:6379 set in the environment variable RAY_ADDRESS
# 2025-04-18 02:51:29,072 INFO worker.py:1654 -- Connecting to existing Ray cluster at address: 10.244.0.8:6379...
# 2025-04-18 02:51:29,198 INFO worker.py:1832 -- Connected to Ray cluster. View the dashboard at 10.244.0.8:8265
# 1
步骤 6:检查 Redis 中的数据#
# Step 6.1: Check the RayCluster's UID.
kubectl get rayclusters.ray.io raycluster-external-redis -o=jsonpath='{.metadata.uid}'
# [Example output]: 864b004c-6305-42e3-ac46-adfa8eb6f752
# Step 6.2: Check the head Pod's environment variable `RAY_external_storage_namespace`.
kubectl get pods $HEAD_POD -o=jsonpath='{.spec.containers[0].env}' | jq
# [Example output]:
# [
# {
# "name": "RAY_external_storage_namespace",
# "value": "864b004c-6305-42e3-ac46-adfa8eb6f752"
# },
# ...
# ]
# Step 6.3: Log into the Redis Pod.
# The password `5241590000000000` is defined in the `redis-config` ConfigMap.
# Step 6.4: Check the keys in Redis.
# Note: the schema changed in Ray 2.38.0. Previously we use a single HASH table,
# now we use multiple HASH tables with a common prefix.
export REDIS_POD=$(kubectl get pods --selector=app=redis -o custom-columns=POD:metadata.name --no-headers)
kubectl exec -it $REDIS_POD -- env REDISCLI_AUTH="5241590000000000" redis-cli KEYS "*"
# [Example output]:
# 1) "RAY864b004c-6305-42e3-ac46-adfa8eb6f752@INTERNAL_CONFIG"
# 2) "RAY864b004c-6305-42e3-ac46-adfa8eb6f752@KV"
# 3) "RAY864b004c-6305-42e3-ac46-adfa8eb6f752@NODE"
# [Example output Before Ray 2.38.0]:
# 2) "864b004c-6305-42e3-ac46-adfa8eb6f752"
#
# Step 6.5: Check the value of the key.
kubectl exec -it $REDIS_POD -- env REDISCLI_AUTH="5241590000000000" redis-cli HGETALL RAY864b004c-6305-42e3-ac46-adfa8eb6f752@NODE
# Before Ray 2.38.0:
# HGETALL 864b004c-6305-42e3-ac46-adfa8eb6f752
在 ray-cluster.external-redis.yaml 中,RayCluster 未设置 gcsFaultToleranceOptions.externalStorageNamespace 选项。因此,KubeRay 默认会将环境变量 RAY_external_storage_namespace 自动注入到 RayCluster 管理的所有 Ray Pod 中,并使用 RayCluster 的 UID 作为外部存储命名空间。请参阅 本节 了解更多关于该选项的信息。
步骤 7:杀死 Head Pod 中的 GCS 进程#
# Step 7.1: Check the `RAY_gcs_rpc_server_reconnect_timeout_s` environment variable in both the head Pod and worker Pod.
kubectl get pods $HEAD_POD -o=jsonpath='{.spec.containers[0].env}' | jq
# [Expected result]:
# No `RAY_gcs_rpc_server_reconnect_timeout_s` environment variable is set. Hence, the Ray head uses its default value of `60`.
export YOUR_WORKER_POD=$(kubectl get pods -l ray.io/group=small-group -o jsonpath='{.items[0].metadata.name}')
kubectl get pods $YOUR_WORKER_POD -o=jsonpath='{.spec.containers[0].env}' | jq
# [Expected result]:
# KubeRay injects the `RAY_gcs_rpc_server_reconnect_timeout_s` environment variable with the value `600` to the worker Pod.
# [
# {
# "name": "RAY_gcs_rpc_server_reconnect_timeout_s",
# "value": "600"
# },
# ...
# ]
# Step 7.2: Kill the GCS process in the head Pod.
kubectl exec -it $HEAD_POD -- pkill gcs_server
# Step 7.3: The head Pod fails and restarts after `RAY_gcs_rpc_server_reconnect_timeout_s` (60) seconds.
# In addition, the worker Pod isn't terminated by the new head after reconnecting because GCS fault
# tolerance is enabled.
kubectl get pods -l=ray.io/is-ray-node=yes
# [Example output]:
# NAME READY STATUS RESTARTS AGE
# raycluster-external-redis-head 1/1 Running 1 (64s ago) xxm
# raycluster-external-redis-worker-small-group-yyyyy 1/1 Running 0 xxm
在 ray-cluster.external-redis.yaml 中,RayCluster 内的 Head Pod 或 Worker Pod 的规范中未设置 RAY_gcs_rpc_server_reconnect_timeout_s 环境变量。因此,KubeRay 会自动将环境变量 RAY_gcs_rpc_server_reconnect_timeout_s 的值设置为 600 注入到 Worker Pod,并为 Head Pod 使用默认值 60。Worker Pod 的超时值必须长于 Head Pod 的超时值,这样 Worker Pod 才不会在 Head Pod 从故障中重启之前终止。
步骤 8:再次访问独立 Actor#
kubectl exec -it $HEAD_POD -- python3 /home/ray/samples/increment_counter.py
# 2023-09-07 17:31:17,793 INFO worker.py:1313 -- Using address 127.0.0.1:6379 set in the environment variable RAY_ADDRESS
# 2023-09-07 17:31:17,793 INFO worker.py:1431 -- Connecting to existing Ray cluster at address: 10.244.0.21:6379...
# 2023-09-07 17:31:17,875 INFO worker.py:1612 -- Connected to Ray cluster. View the dashboard at http://10.244.0.21:8265
# 2
在此示例中,独立 Actor 始终位于 Worker Pod 上。
Head Pod 的 rayStartParams 设置为 num-cpus: "0"。因此,不会在 Head Pod 上调度任何任务或 Actor。
启用 GCS 容错后,即使 GCS 进程死亡并重启,您仍然可以访问独立 Actor。请注意,容错不会持久化 Actor 的状态。结果是 2 而不是 1 的原因是独立 Actor 位于始终运行的 Worker Pod 上。另一方面,如果 Head Pod 托管独立 Actor,increment_counter.py 脚本在此步骤中将产生 1 的结果。
步骤 9:删除 RayCluster 时从 Redis 中移除存储的键#
# Step 9.1: Delete the RayCluster custom resource.
kubectl delete raycluster raycluster-external-redis
# Step 9.2: KubeRay operator deletes all Pods in the RayCluster.
# Step 9.3: KubeRay operator creates a Kubernetes Job to delete the Redis key after the head Pod is terminated.
# Step 9.4: Check whether the RayCluster has been deleted.
kubectl get raycluster
# [Expected output]: No resources found in default namespace.
# Step 9.5: Check Redis keys after the Kubernetes Job finishes.
export REDIS_POD=$(kubectl get pods --selector=app=redis -o custom-columns=POD:metadata.name --no-headers)
kubectl exec -i $REDIS_POD -- env REDISCLI_AUTH="5241590000000000" redis-cli KEYS "*"
# [Expected output]: (empty list or set)
在 KubeRay v1.0.0 中,KubeRay Operator 为启用了 GCS 容错的 RayCluster 添加了 Kubernetes finalizer,以确保 Redis 的清理。KubeRay 仅在 Kubernetes Job 成功清理 Redis 后才移除此 finalizer。
换句话说,如果 Kubernetes Job 失败,RayCluster 将不会被删除。在这种情况下,您应该手动移除 finalizer 并清理 Redis。
kubectl patch rayclusters.ray.io raycluster-external-redis --type json --patch='[ { "op": "remove", "path": "/metadata/finalizers" } ]'
从 KubeRay v1.1.0 开始,KubeRay 将 Redis 清理行为从强制性改为尽力而为。如果 Kubernetes Job 失败,KubeRay 仍然会从 RayCluster 中移除 Kubernetes finalizer,从而解除 RayCluster 删除的阻塞。
用户可以通过设置功能门 ENABLE_GCS_FT_REDIS_CLEANUP 来关闭此功能。有关更多详细信息,请参阅 KubeRay GCS 容错配置 部分。
步骤 10:删除 Kubernetes 集群#
kind delete cluster
KubeRay GCS 容错配置#
快速入门示例中使用的 ray-cluster.external-redis.yaml 包含有关配置选项的详细注释。*请结合 YAML 文件阅读本节。*
这些配置需要 KubeRay 1.3.0+
以下部分使用 KubeRay 1.3.0 中引入的新 gcsFaultToleranceOptions 字段。对于旧的 GCS 容错配置,包括 ray.io/ft-enabled 注解,请参阅 旧文档。
1. 启用 GCS 容错#
gcsFaultToleranceOptions:将gcsFaultToleranceOptions字段添加到 RayCluster 自定义资源以启用 GCS 容错。kind: RayCluster metadata: spec: gcsFaultToleranceOptions: # <- Add this field to enable GCS fault tolerance.
2. 连接到外部 Redis#
redisAddress:将redisAddress添加到gcsFaultToleranceOptions字段。使用此选项指定 Redis 服务的地址,从而允许 Ray Head 连接到它。在 ray-cluster.external-redis.yaml 中,RayCluster 自定义资源使用redisKubernetes ClusterIP 服务名称作为连接 Redis 服务器的接入点。ClusterIP 服务也由 YAML 文件创建。kind: RayCluster metadata: spec: gcsFaultToleranceOptions: redisAddress: "redis:6379" # <- Add redis address here.
redisPassword:将redisPassword添加到gcsFaultToleranceOptions字段。使用此选项指定 Redis 服务的密码,从而允许 Ray Head 连接到它。在 ray-cluster.external-redis.yaml 中,RayCluster 自定义资源从 Kubernetes secret 加载密码。kind: RayCluster metadata: spec: gcsFaultToleranceOptions: redisAddress: "redis:6379" redisPassword: # <- Add redis password from a Kubernetes secret. valueFrom: secretKeyRef: name: redis-password-secret key: password
3. 使用外部存储命名空间#
externalStorageNamespace(可选):将externalStorageNamespace添加到gcsFaultToleranceOptions字段。KubeRay 使用此选项的值将环境变量RAY_external_storage_namespace设置到 RayCluster 管理的所有 Ray Pod 中。在大多数情况下,*您不需要设置externalStorageNamespace*,因为 KubeRay 会自动将其设置为 RayCluster 的 UID。仅当您完全了解 GCS 容错和 RayService 的行为时,才修改此选项,以避免 此问题。有关更多详细信息,请参阅早期快速入门示例中的 本节。kind: RayCluster metadata: spec: gcsFaultToleranceOptions: externalStorageNamespace: "my-raycluster-storage" # <- Add this option to specify a storage namespace
4. 关闭 Redis 清理#
ENABLE_GCS_FT_REDIS_CLEANUP:默认值为 True。您可以通过设置 KubeRay operator 的 Helm chart 中的环境变量来关闭此功能。
下一步#
有关更多信息,请参阅 Ray Serve 端到端容错文档。
有关更多信息,请参阅 Ray Core GCS 容错文档。