Skip to content

[Test][KubeRay] Add doctests for Kuberay Autoscaling #51884

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

JiangJiaWei1103
Copy link

@JiangJiaWei1103 JiangJiaWei1103 commented Apr 1, 2025

Why are these changes needed?

To automate doc testing and reduce the manual testing burden, as highlighted in ray-project/kuberay#3157, this PR introduces doctests for Kuberay Autoscaling section.

Doc link: https://anyscale-ray--51884.com.readthedocs.build/en/51884/cluster/kubernetes/user-guides/configuring-autoscaling.html

Related issue number

ray-project/kuberay#3157

Checks

  • I've signed off every commit(by using the -s flag, i.e., git commit -s) in this PR.
  • I've run scripts/format.sh to lint the changes in this PR.
  • I've included any doc changes needed for https://docs.ray.io/en/master/.
    • I've added any new APIs to the API Reference. For example, if I added a
      method in Tune, I've added it in doc/source/tune/api/ under the
      corresponding .rst file.
  • I've made sure the tests are passing. Note that there might be a few flaky tests, see the recent failures at https://flakey-tests.ray.io/
  • Testing Strategy
    • Unit tests
    • Release tests
    • This PR is not tested :(

@JiangJiaWei1103 JiangJiaWei1103 requested review from pcmoritz, kevin85421 and a team as code owners April 1, 2025 14:35
@jcotant1 jcotant1 added the core Issues that should be addressed in Ray Core label Apr 2, 2025
Copy link
Member

@MortalHappiness MortalHappiness left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can omit checking output for

  • kubectl exec -it $HEAD_POD -- ray list actors
  • kubectl exec $HEAD_POD -it -c ray-head -- ray status
  • kubectl logs $HEAD_POD -c autoscaler | tail -n 20

Otherwise, we would need to create a lot of special regex.

cc @kevin85421 Do you think this is okay?

Comment on lines 29 to 31
[time-stamp]
regex: \d{4}-\d{2}-\d{2}\s\d{2}:\d{2}:\d{2}[.,]?\d*|[A-Z][a-z]{2}\s[A-Z][a-z]{2}\s+\d{1,2}\s\d{2}:\d{2}:\d{2}\s\d{4}
replace: TIME-STAMP
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't use |. Split it into multiple regex for readability. Like [time-stamp] and [time-stamp-miliseconds]

@hainesmichaelc hainesmichaelc added the community-contribution Contributed by the community label Apr 4, 2025
@kevin85421
Copy link
Member

I think we can omit checking output for ...

makes sense to me

@JiangJiaWei1103
Copy link
Author

I think we can omit checking output for

  • kubectl exec -it $HEAD_POD -- ray list actors
  • kubectl exec $HEAD_POD -it -c ray-head -- ray status
  • kubectl logs $HEAD_POD -c autoscaler | tail -n 20

Otherwise, we would need to create a lot of special regex.

cc @kevin85421 Do you think this is okay?

We now ignore those checks and remove the corresponding regex patterns. Thanks!

@MortalHappiness
Copy link
Member

Could you resolve the conflicts? Thanks.

@JiangJiaWei1103
Copy link
Author

Could you resolve the conflicts? Thanks.

Done. Thanks a lot!

}
],
"source": [
"sleep 10 && export WORKER_POD1=$(kubectl get pods --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1:].metadata.name}')\n",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This may become a flaky test if we wait for a fixed time. We might need to extend the timeout. By the way, is there a selector to specifically target the worker pod? It doesn't seem ideal to fetch all pods and sort them by timestamp just to find the newly created worker pod.

}
],
"source": [
"sleep 10 && export WORKER_POD2=$(kubectl get pods --sort-by=.metadata.creationTimestamp -o jsonpath='{.items[-1:].metadata.name}')\n",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

Comment on lines 851 to 911
"source": [
"### Step 8: Clean up the Kubernetes cluster"
]
},
{
"cell_type": "code",
"execution_count": 22,
"id": "92d74542-1984-4519-adde-641b05f9efe8",
"metadata": {
"editable": true,
"slideshow": {
"slide_type": ""
},
"tags": [
"nbval-ignore-output",
"remove-output"
]
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"raycluster.ray.io \"raycluster-autoscaler\" deleted\n",
"configmap \"ray-example\" deleted\n"
]
}
],
"source": [
"# Delete RayCluster and ConfigMap\n",
"kubectl delete -f https://raw.githubusercontent.com/ray-project/kuberay/v1.3.0/ray-operator/config/samples/ray-cluster.autoscaler.yaml"
]
},
{
"cell_type": "code",
"execution_count": 23,
"id": "429d84e4-eb5f-4174-8344-306916216dfa",
"metadata": {
"editable": true,
"slideshow": {
"slide_type": ""
},
"tags": [
"nbval-ignore-output",
"remove-output"
]
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"release \"kuberay-operator\" uninstalled\n"
]
}
],
"source": [
"# Uninstall the KubeRay operator\n",
"helm uninstall kuberay-operator"
]
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For step 8, simply kind delete cluster is enough.

Comment on lines 388 to 397
"while true; do\n",
" WORKER_POD1=$(kubectl get pods --selector=ray.io/node-type=worker -o custom-columns=POD:metadata.name --no-headers)\n",
" if [[ -n \"$WORKER_POD1\" ]]; then\n",
" export WORKER_POD1\n",
" break\n",
" fi \n",
" sleep 2\n",
"done\n",
"kubectl wait --for=condition=ready pod/$WORKER_POD1 --timeout=500s\n",
"echo $WORKER_POD1"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we can use kubectl wait to wait for the status.availableWorkerReplicas field of raycluster/raycluster-autoscaler becomes to 1 instead of waiting for pod name becoming non-empty string.

Comment on lines 480 to 494
"while true; do\n",
" WORKER_POD2=$(kubectl get pods \\\n",
" --selector=ray.io/node-type=worker \\\n",
" --field-selector=\"metadata.name!=$WORKER_POD1\" \\\n",
" -o custom-columns=POD:metadata.name \\\n",
" --no-headers\n",
" )\n",
" if [[ -n \"$WORKER_POD2\" ]]; then\n",
" export WORKER_POD2\n",
" break\n",
" fi \n",
" sleep 2\n",
"done\n",
"kubectl wait --for=condition=ready pod/$WORKER_POD2 --timeout=500s\n",
"echo $WORKER_POD2"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Similarly, wait for availableWorkerReplicas becoming to 2.

@MortalHappiness MortalHappiness added the go add ONLY when ready to merge, run all tests label Apr 15, 2025
@MortalHappiness
Copy link
Member

MortalHappiness commented Apr 15, 2025

cc @dayshah for review as ray-docs code owner. Thanks.

@MortalHappiness
Copy link
Member

cc @kevin85421 for review as KubeRay owner. Thanks.

Copy link
Member

@kevin85421 kevin85421 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are there any changes to the text instead of the instructions? If so, would you mind adding some comments to the paragraph so that I can review the changes?

In addition, please make sure the test can pass at least 10 consecutive runs in your local environment. cc @MortalHappiness

@MortalHappiness
Copy link
Member

@kevin85421 No. But I asked the contributor to split some code blocks into multiple cells. You can just check the code blocks in https://anyscale-ray--51884.com.readthedocs.build/en/51884/cluster/kubernetes/user-guides/configuring-autoscaling.html to see if they look good to you.

@MortalHappiness
Copy link
Member

In addition, please make sure the test can pass at least 10 consecutive runs in your local environment.

@JiangJiaWei1103 Could you post a screenshot of running it and pass 10 consecutive runs in your local environement here?

Copy link
Member

@kevin85421 kevin85421 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The change looks good to me. Waiting for the screenshot to prove that it's not flaky locally.

@JiangJiaWei1103
Copy link
Author

JiangJiaWei1103 commented Apr 16, 2025

I noticed that the test can be a bit flaky locally. As shown in the screenshot below, 1 out of 10 runs failed because the second worker pod wasn’t ready, even though we explicitly use

# kubectl wait --for=condition=ready pod/$WORKER_POD2 --timeout=500s

to wait for it to reach the ready state.

Screenshot 2025-04-16 at 10 55 09 PM

To confirm this, I ran the following script to test the notebook 10 times:

#!/bin/bash

success=0
total=10

for i in $(seq 1 $total); do
    echo "Run #$i"
    py.test --nbval user-guides/configuring-autoscaling.ipynb --nbval-kernel-name bash --sanitize-with doc_sanitize.cfg
    if [ $? -eq 0 ]; then
        ((success++))
    fi
done

echo "===================="
echo "Total Runs: $total"
echo "Successful Runs: $success"
echo "Success Rate: $((success * 100 / total))%"

Final result:
Screenshot 2025-04-16 at 11 00 27 PM

Let me know if there’s a more stable way to ensure the pod is truly ready before proceeding—happy to update accordingly! Thanks!

@MortalHappiness
Copy link
Member

MortalHappiness commented Apr 17, 2025

The failed cell is step 5.2 so it is not related to kubectl wait for the worker pod 2. The problem is why there are 2 worker pods in step 5.2?

@JiangJiaWei1103
Copy link
Author

JiangJiaWei1103 commented Apr 17, 2025

The failed cell is step 5.2 so it is not related to kubectl wait for the worker pod 2. The problem is why there are 2 worker pods in step 5.2?

Sorry about the confusion. That was my mistake in misinterpreting the failed block.

We use kubectl wait to ensure that worker pod 1 reached the Ready status. However, it appears that consistent reads aren’t guaranteed by the API server due to the use of the watch cache. I suspect this issue is caused by outdated information (specifically, the pod still appearing as Init because the cache hasn’t been fully updated yet). This might be relevant since we’re using Kubernetes v1.26.

I looked into this and came across this blog post, which explains the situation well:

Kubernetes has long used a watch cache to optimize read operations. The watch cache stores a snapshot of the cluster state and receives updates through etcd watches. However, until now, it couldn't serve consistent reads directly, as there was no guarantee the cache was sufficiently up-to-date.

To verify, I tried disabling --watch-cache when spinning up the kind cluster, but unfortunately, the same error still occurred. Do you think it's acceptable to add one more field selector to make selection stricter as follows?

kubectl get pods -l=ray.io/is-ray-node=yes --field-selector=status.phase=Running

(Update) By adding one more field selector as shown above, experiments indicate that the tests can consistently pass at least 20 times in a row:

Screenshot 2025-04-18 at 9 29 16 AM

I'll continue surveying and experimenting to make this test more stable. Thanks!

cc @kevin85421

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
community-contribution Contributed by the community core Issues that should be addressed in Ray Core go add ONLY when ready to merge, run all tests
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants