|
| 1 | +# RayService high availability |
| 2 | +RayService provides high availability (HA) to ensure services continue serving requests without failure during scaling up, scaling down, and upgrading the RayService configuration (zero-downtime upgrade). |
| 3 | + |
| 4 | +## Quickstart |
| 5 | + |
| 6 | +### Step 1: Create a Kubernetes cluster with Kind |
| 7 | + |
| 8 | +```sh |
| 9 | +kind create cluster --image=kindest/node:v1.24.0 |
| 10 | +``` |
| 11 | + |
| 12 | +### Step 2: Install the KubeRay operator |
| 13 | +Follow the instructions in [this document](/helm-chart/kuberay-operator/README.md) to install the latest stable KubeRay operator, or follow the instructions in [DEVELOPMENT.md](/ray-operator/DEVELOPMENT.md) to install the nightly KubeRay operator. |
| 14 | + |
| 15 | +### Step 3: Create a RayService and a locust cluster |
| 16 | +```sh |
| 17 | +# Path: kuberay/ |
| 18 | +kubectl apply -f ./ray-operator/config/samples/ray-service.high-availability-locust.yaml |
| 19 | +kubectl get pod |
| 20 | +# NAME READY STATUS RESTARTS AGE |
| 21 | +# kuberay-operator-64b4fc5946-zbfqd 1/1 Running 0 72s |
| 22 | +# locust-cluster-head-6clr5 1/1 Running 0 38s |
| 23 | +# rayservice-ha-raycluster-pfh8b-head-58xkr 2/2 Running 0 36s |
| 24 | +``` |
| 25 | +The [ray-service.high-availability-locust.yaml](/ray-operator/config/samples/ray-service.high-availability-locust.yaml) has several Kubernetes objects: |
| 26 | +- A RayService with serve autoscaling and Pod autoscaling enabled. |
| 27 | +- A RayCluster functioning as locust cluster to simulate users sending requests. |
| 28 | +- A configmap with a locustfile sets user request levels: starts low, spikes, then drops. |
| 29 | + |
| 30 | +### Step 4: Use Locust cluster to simulate users sending requests |
| 31 | +```sh |
| 32 | +# Open a new terminal and log into the locust cluster. |
| 33 | +kubectl exec -it $(kubectl get pods -o=name | grep locust-cluster-head) -- bash |
| 34 | + |
| 35 | +# Install locust and download locust_runner.py. |
| 36 | +# locust_runner.py helps distribute the locust workers accross the RayCluster. |
| 37 | +pip install locust && wget https://raw.githubusercontent.com/ray-project/serve_workloads/main/microbenchmarks/locust_runner.py |
| 38 | + |
| 39 | +# Start sending requests to the RayService. |
| 40 | +python locust_runner.py -f /locustfile/locustfile.py --host http://rayservice-ha-serve-svc:8000 |
| 41 | +``` |
| 42 | + |
| 43 | +### Step 5: Verify high availability during scaling up and down |
| 44 | + |
| 45 | +The locust cluster sends requests to the RayService, starting with a low number of requests, then spiking, and finally dropping. This will trigger the RayService to scale up and down. You can verify the high availability by observing the Ray Pod and the failure rate in the locust terminal. |
| 46 | + |
| 47 | +```sh |
| 48 | +watch -n 1 "kubectl get pod" |
| 49 | +# Satge 1: Low request rate. |
| 50 | +# NAME READY STATUS RESTARTS AGE |
| 51 | +# rayservice-ha-raycluster-pfh8b-head-58xkr 2/2 Running 0 78s |
| 52 | +# rayservice-ha-raycluster-pfh8b-worker-worker-rd22n 0/1 Init:0/1 0 9s |
| 53 | + |
| 54 | +# Stage 2: High request rate |
| 55 | +# rayservice-ha-raycluster-pfh8b-head-58xkr 2/2 Running 0 113s |
| 56 | +# rayservice-ha-raycluster-pfh8b-worker-worker-7thjv 0/1 Init:0/1 0 4s |
| 57 | +# rayservice-ha-raycluster-pfh8b-worker-worker-nt98j 0/1 Init:0/1 0 4s |
| 58 | +# rayservice-ha-raycluster-pfh8b-worker-worker-rd22n 1/1 Running 0 44s |
| 59 | + |
| 60 | +# Stage 3: Low request rate |
| 61 | +# NAME READY STATUS RESTARTS AGE |
| 62 | +# rayservice-ha-raycluster-pfh8b-head-58xkr 2/2 Running 0 3m38s |
| 63 | +# rayservice-ha-raycluster-pfh8b-worker-worker-7thjv 0/1 Terminating 0 109s |
| 64 | +# rayservice-ha-raycluster-pfh8b-worker-worker-nt98j 0/1 Terminating 0 109s |
| 65 | +# rayservice-ha-raycluster-pfh8b-worker-worker-rd22n 1/1 Running 0 2m29s |
| 66 | +``` |
| 67 | +Let's describe how KubeRay and Ray ensure high availability during scaling, using the example provided. |
| 68 | + |
| 69 | +In the above example, the RayService configuration is as follows: |
| 70 | +- Every node can have at most one serve replica. |
| 71 | +- The initial number of serve replicas is set to zero. |
| 72 | +- The head node will not be scheduled for any workloads to follow best practices. |
| 73 | + |
| 74 | +With the above settings, when serve replicas scale up: |
| 75 | +1. KubeRay creates a new worker Pod. Since no serve replicas are currently running, the readiness probe for the new Pod fails. As a result, the endpoint is not added to the serve service. |
| 76 | +2. Ray then schedules a new serve replica to the newly created worker Pod. Once the serve replica is running, the readiness probe passes, and the endpoint is added to the serve service. |
| 77 | + |
| 78 | +When serve replicas scale down: |
| 79 | +1. The proxy actor in the worker Pod that is scaling down changes its stage to `draining`. The readiness probe fails immediately, and the endpoint starts to be removed from the serve service. However, this process takes some time, so incoming requests are still redirected to this worker Pod for a short period. |
| 80 | +2. During the draining stage, the proxy actor can still redirect incoming requests. The proxy actor is only removed and changes to the `drained` stage when the following conditions are met: |
| 81 | + - There are no ongoing requests. |
| 82 | + - The minimum draining time has been reached, which can be controlled by an environmental variable: `RAY_SERVE_PROXY_MIN_DRAINING_PERIOD_S`. |
| 83 | + |
| 84 | + Also, removing endpoints from the serve service does not affect the existing ongoing requests. All of the above ensures high availability. |
| 85 | +3. Once the worker Pod becomes idle, KubeRay removes it from the cluster. |
| 86 | + |
| 87 | + > Note, the default value of `RAY_SERVE_PROXY_MIN_DRAINING_PERIOD_S` is 30s. You may change it to fit with your k8s cluster. |
| 88 | +
|
| 89 | +### Step 6: Verify high availability during upgrade |
| 90 | +The locust cluster will continue sending requests for 600s. Before the 600s is up, upgrade the RayService configuration by adding a new environment variable. This will trigger a rolling update. You can verify the high availability by observing the Ray Pod and the failure rate in the locust terminal. |
| 91 | +```sh |
| 92 | +kubectl patch rayservice rayservice-ha --type='json' -p='[ |
| 93 | + { |
| 94 | + "op": "add", |
| 95 | + "path": "/spec/rayClusterConfig/headGroupSpec/template/spec/containers/0/env", |
| 96 | + "value": [ |
| 97 | + { |
| 98 | + "name": "RAY_SERVE_PROXY_MIN_DRAINING_PERIOD_S", |
| 99 | + "value": "30" |
| 100 | + } |
| 101 | + ] |
| 102 | + } |
| 103 | +]' |
| 104 | + |
| 105 | +watch -n 1 "kubectl get pod" |
| 106 | +# stage 1: New head pod is created. |
| 107 | +# NAME READY STATUS RESTARTS AGE |
| 108 | +# rayservice-ha-raycluster-nhs7v-head-z6xkn 1/2 Running 0 4s |
| 109 | +# rayservice-ha-raycluster-pfh8b-head-58xkr 2/2 Running 0 4m30s |
| 110 | +# rayservice-ha-raycluster-pfh8b-worker-worker-rd22n 1/1 Running 0 3m21s |
| 111 | + |
| 112 | +# stage 2: Old head pod terminates after new head pod is ready and k8s service is fully updated. |
| 113 | +# NAME READY STATUS RESTARTS AGE |
| 114 | +# rayservice-ha-raycluster-nhs7v-head-z6xkn 2/2 Running 0 91s |
| 115 | +# rayservice-ha-raycluster-nhs7v-worker-worker-jplrp 0/1 Init:0/1 0 3s |
| 116 | +# rayservice-ha-raycluster-pfh8b-head-58xkr 2/2 Terminating 0 5m57s |
| 117 | +# rayservice-ha-raycluster-pfh8b-worker-worker-rd22n 1/1 Terminating 0 4m48s |
| 118 | +``` |
| 119 | +When a new configuration is applied, the Kuberay operator always creates a new RayCluster with the new configuration and then removes the old RayCluster. |
| 120 | +Here are the details of the rolling update: |
| 121 | +1. KubeRay creates a new RayCluster with the new configuration. At this time, all requests are still being served by the old RayCluster. |
| 122 | +2. After the new RayCluster and the server app on it are ready, KubeRay updates the serve service to redirect the traffic to the new RayCluster. At this point, traffic is being served by both the old and new RayCluster as it takes time to update the k8s service. |
| 123 | +3. After the serve service is fully updated, KubeRay removes the old RayCluster. The traffic is now fully served by the new RayCluster. |
| 124 | + |
| 125 | +### Step 7: Examine the locust results |
| 126 | +In your locust terminal, You will see the faile rate is 0.00%. |
| 127 | +```sh |
| 128 | + # fails | |
| 129 | +|-------------| |
| 130 | + 0(0.00%) | |
| 131 | +|-------------| |
| 132 | + 0(0.00%) | |
| 133 | +``` |
| 134 | + |
| 135 | +### Step 8: Clean up |
| 136 | +```sh |
| 137 | +kubectl delete -f ./ray-operator/config/samples/ray-service.high-availability-locust.yaml |
| 138 | +kind delete cluster |
| 139 | +``` |
0 commit comments