你当前正在访问 Microsoft Azure Global Edition 技术文档网站。 如果需要访问由世纪互联运营的 Microsoft Azure 中国技术文档网站,请访问 https://docs.azure.cn

在 Azure Kubernetes 服务 (AKS) 上配置和部署 Ray 群集

本文介绍了如何使用 KubeRay 在 Azure Kubernetes 服务 (AKS) 上配置和部署 Ray 群集, 还介绍了如何使用 Ray 群集训练简单的机器学习模型,并在 Ray 仪表板上显示结果。

本文提供了两种在 AKS 上部署 Ray 群集的方法:

  • 非交互式部署:使用 GitHub 存储库中的 deploy.sh 脚本以非交互方式部署完整的 Ray 示例。
  • 手动部署:按照手动部署步骤将 Ray 示例部署到 AKS。

先决条件

以非交互方式部署 Ray 示例

如果要以非交互方式部署完整的 Ray 示例,可以使用 GitHub 存储库中的 deploy.sh 脚本 (https://github.com/Azure-Samples/aks-ray-sample)。 此脚本将完成 Ray 部署过程部分中所述的步骤。

  1. 使用以下命令在本地克隆 GitHub 存储库,但要更改为存储库的根目录:

    git clone https://github.com/Azure-Samples/aks-ray-sample
    cd aks-ray-sample
    
  2. 使用以下命令部署完整的示例:

    chmod +x deploy.sh
    ./deploy.sh
    
  3. 部署完成后,请在 Azure 门户中查看日志输出和资源组,以查看创建的基础结构。

手动部署 Ray 示例

Fashion MNIST 是 Zalando 的文章图像的数据集,其中包含一个由 60,000 个示例组成的训练集和一个由 10,000 个示例组成的测试集。 每个示例都是一个 28x28 的灰度图像,并与十个类中的某个标签关联。 在本指南中,你将使用 Ray 群集来基于此数据集训练一个简单的 PyTorch 模型。

部署 RayJob 规范

若要训练模型,需要向在专用 AKS 群集上运行的 KubeRay 操作器提交 RayJob 规范。 RayJob 规范是一个 YAML 文件,用于描述运行相应作业所需的资源,包括 Docker 映像、要运行的命令以及要使用的工作器数。

查看 RayJob 说明时,可能需要修改一些字段以匹配自己的环境:

  • rayClusterSpecworkerGroupSpecs 部分下的 replicas 字段指定 KubeRay 计划到 Kubernetes 群集的工作器 Pod 数。 每个工作器 Pod 需要 3 个 CPU 以及 4 GB 内存。 每个头 Pod 需要 1 个 CPU 以及 4 GB 内存。 如果将 replicas 字段设置为 2,则需要在用于为相应作业实现 RayCluster 的节点池中具有 8 个 vCPU
  • specruntimeEnvYAML 下的 NUM_WORKERS 字段指定要启动的 Ray 执行器的数量。 每个 Ray 执行器都必须由 Kubernetes 群集中的工作器 Pod 提供服务,因此此字段必须小于或等于 replicas 字段。 在此示例中,我们将 NUM_WORKERS 设置为 2,这与 replicas 字段匹配。
  • CPUS_PER_WORKER 字段必须设置为小于或等于由分配给每个工作器 Pod 的 CPU 数减 1 得到的值。 在此示例中,每个工作器 Pod 的 CPU 资源请求为 3,因此应将 CPUS_PER_WORKER 设置为 2

总之,节点池中总共需要有 8 个 vCPU 才能运行 PyTorch 模型训练作业。 由于我们在系统节点池中添加了一个污点,因此无法为其计划任何用户 Pod,我们必须创建一个具有至少 8 个 vCPU 的新节点池来托管 Ray 群集。

  1. 使用以下命令下载 RayJob 规范文件:

    curl -LO https://raw.githubusercontent.com/ray-project/kuberay/master/ray-operator/config/samples/pytorch-mnist/ray-job.pytorch-mnist.yaml
    
  2. 对 RayJob 规范文件进行任何必要的修改。

  3. 使用 kubectl apply 命令启动 PyTorch 模型训练作业。

    kubectl apply -n kuberay -f ray-job.pytorch-mnist.yaml
    

验证 RayJob 部署

  1. 使用 kubectl get pods 命令验证命名空间中是否具有两个工作器 Pod 和一个头 Pod 在运行。

    kubectl get pods -n kuberay
    

    输出应类似于以下示例输出:

    NAME                                                      READY   STATUS    RESTARTS   AGE
    kuberay-operator-7d7998bcdb-9h8hx                         1/1     Running   0          3d2h
    pytorch-mnist-raycluster-s7xd9-worker-small-group-knpgl   1/1     Running   0          6m15s
    pytorch-mnist-raycluster-s7xd9-worker-small-group-p74cm   1/1     Running   0          6m15s
    rayjob-pytorch-mnist-fc959                                1/1     Running   0          5m35s
    rayjob-pytorch-mnist-raycluster-s7xd9-head-l24hn          1/1     Running   0          6m15s
    
  2. 使用 kubectl get 命令检查 RayJob 的状态。

    kubectl get rayjob -n kuberay
    

    输出应类似于以下示例输出:

    NAME                   JOB STATUS   DEPLOYMENT STATUS   START TIME             END TIME   AGE
    rayjob-pytorch-mnist   RUNNING      Running             2024-11-22T03:08:22Z              9m36s
    
  3. 等待 RayJob 完成。 这可能需要几分钟的时间。 当 JOB STATUS 变为 SUCCEEDED 后,可以检查训练日志。 为此,可以首先使用 kubectl get pods 命令获取运行 RayJob 的 Pod 的名称。

    kubectl get pods -n kuberay
    

    在输出中,应该会有一个名称以 rayjob-pytorch-mnist 开头的 Pod,类似于以下示例输出:

    NAME                                                      READY   STATUS      RESTARTS   AGE
    kuberay-operator-7d7998bcdb-9h8hx                         1/1     Running     0          3d2h
    pytorch-mnist-raycluster-s7xd9-worker-small-group-knpgl   1/1     Running     0          14m
    pytorch-mnist-raycluster-s7xd9-worker-small-group-p74cm   1/1     Running     0          14m
    rayjob-pytorch-mnist-fc959                                0/1     Completed   0          13m
    rayjob-pytorch-mnist-raycluster-s7xd9-head-l24hn          1/1     Running     0          14m
    
  4. 使用 kubectl logs 命令查看 RayJob 的日志。 请确保将 rayjob-pytorch-mnist-fc959 替换为运行 RayJob 的 Pod 的名称。

    kubectl logs -n kuberay rayjob-pytorch-mnist-fc959
    

    在输出中,应该会显示 PyTorch 模型的训练日志,类似于以下示例输出:

    2024-11-21 19:09:04,986 INFO cli.py:39 -- Job submission server address: http://rayjob-pytorch-mnist-raycluster-s7xd9-head-svc.kuberay.svc.cluster.local:8265
    2024-11-21 19:09:05,712 SUCC cli.py:63 -- -------------------------------------------------------
    2024-11-21 19:09:05,713 SUCC cli.py:64 -- Job 'rayjob-pytorch-mnist-hndpx' submitted successfully
    2024-11-21 19:09:05,713 SUCC cli.py:65 -- -------------------------------------------------------
    2024-11-21 19:09:05,713 INFO cli.py:289 -- Next steps
    2024-11-21 19:09:05,713 INFO cli.py:290 -- Query the logs of the job:
    2024-11-21 19:09:05,713 INFO cli.py:292 -- ray job logs rayjob-pytorch-mnist-hndpx
    2024-11-21 19:09:05,713 INFO cli.py:294 -- Query the status of the job:
    ...
    
    View detailed results here: /home/ray/ray_results/TorchTrainer_2024-11-21_19-11-23
    To visualize your results with TensorBoard, run: `tensorboard --logdir /tmp/ray/session_2024-11-21_19-08-24_556164_1/artifacts/2024-11-21_19-11-24/TorchTrainer_2024-11-21_19-11-23/driver_artifacts`
    
    Training started with configuration:
    ╭─────────────────────────────────────────────────╮
    │ Training config                                 │
    ├─────────────────────────────────────────────────┤
    │ train_loop_config/batch_size_per_worker      16 │
    │ train_loop_config/epochs                     10 │
    │ train_loop_config/lr                      0.001 │
    ╰─────────────────────────────────────────────────╯
    (RayTrainWorker pid=1193, ip=10.244.4.193) Setting up process group for: env:// [rank=0, world_size=2]
    (TorchTrainer pid=1138, ip=10.244.4.193) Started distributed worker processes:
    (TorchTrainer pid=1138, ip=10.244.4.193) - (node_id=3ea81f12c0f73ebfbd5b46664e29ced00266e69355c699970e1d824b, ip=10.244.4.193, pid=1193) world_rank=0, local_rank=0, node_rank=0
    (TorchTrainer pid=1138, ip=10.244.4.193) - (node_id=2b00ea2b369c9d27de9596ce329daad1d24626b149975cf23cd10ea3, ip=10.244.1.42, pid=1341) world_rank=1, local_rank=0, node_rank=1
    (RayTrainWorker pid=1341, ip=10.244.1.42) Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz
    (RayTrainWorker pid=1193, ip=10.244.4.193) Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/train-images-idx3-ubyte.gz to /home/ray/data/FashionMNIST/raw/train-images-idx3-ubyte.gz
    (RayTrainWorker pid=1193, ip=10.244.4.193)
      0%|          | 0.00/26.4M [00:00<?, ?B/s]
    (RayTrainWorker pid=1193, ip=10.244.4.193)
      0%|          | 65.5k/26.4M [00:00<01:13, 356kB/s]
    (RayTrainWorker pid=1193, ip=10.244.4.193)
    100%|██████████| 26.4M/26.4M [00:01<00:00, 18.9MB/s]
    (RayTrainWorker pid=1193, ip=10.244.4.193) Extracting /home/ray/data/FashionMNIST/raw/train-images-idx3-ubyte.gz to /home/ray/data/FashionMNIST/raw
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    100%|██████████| 26.4M/26.4M [00:01<00:00, 18.7MB/s]
    ...
    Training finished iteration 1 at 2024-11-21 19:15:46. Total running time: 4min 22s
    ╭───────────────────────────────╮
    │ Training result               │
    ├───────────────────────────────┤
    │ checkpoint_dir_name           │
    │ time_this_iter_s        144.9 │
    │ time_total_s            144.9 │
    │ training_iteration          1 │
    │ accuracy                0.805 │
    │ loss                  0.52336 │
    ╰───────────────────────────────╯
    (RayTrainWorker pid=1193, ip=10.244.4.193)
    Test Epoch 0:  97%|█████████▋| 303/313 [00:01<00:00, 269.60it/s]
    Test Epoch 0: 100%|██████████| 313/313 [00:01<00:00, 267.14it/s]
    (RayTrainWorker pid=1193, ip=10.244.4.193)
    Train Epoch 1:   0%|          | 0/1875 [00:00<?, ?it/s]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Test Epoch 0: 100%|██████████| 313/313 [00:01<00:00, 270.44it/s]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Train Epoch 0: 100%|█████████▉| 1866/1875 [00:24<00:00, 82.49it/s] [repeated 35x across cluster]
    (RayTrainWorker pid=1193, ip=10.244.4.193)
    Train Epoch 0: 100%|██████████| 1875/1875 [00:24<00:00, 77.99it/s]
    Train Epoch 0: 100%|██████████| 1875/1875 [00:24<00:00, 76.19it/s]
    (RayTrainWorker pid=1193, ip=10.244.4.193)
    Test Epoch 0:   0%|          | 0/313 [00:00<?, ?it/s]
    (RayTrainWorker pid=1193, ip=10.244.4.193)
    Test Epoch 0:  88%|████████▊ | 275/313 [00:01<00:00, 265.39it/s] [repeated 19x across cluster]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Train Epoch 1:  19%|█▉        | 354/1875 [00:04<00:18, 82.66it/s] [repeated 80x across cluster]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Train Epoch 1:   0%|          | 0/1875 [00:00<?, ?it/s]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Train Epoch 1:  40%|████      | 757/1875 [00:09<00:13, 83.01it/s] [repeated 90x across cluster]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Train Epoch 1:  62%|██████▏   | 1164/1875 [00:14<00:08, 83.39it/s] [repeated 92x across cluster]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Train Epoch 1:  82%|████████▏ | 1533/1875 [00:19<00:05, 68.09it/s] [repeated 91x across cluster]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Train Epoch 1:  91%|█████████▏| 1713/1875 [00:22<00:02, 70.20it/s]
    (RayTrainWorker pid=1193, ip=10.244.4.193)
    Train Epoch 1:  91%|█████████ | 1707/1875 [00:22<00:02, 70.04it/s] [repeated 47x across cluster]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Test Epoch 1:   0%|          | 0/313 [00:00<?, ?it/s]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Test Epoch 1:   8%|▊         | 24/313 [00:00<00:01, 237.98it/s]
    (RayTrainWorker pid=1193, ip=10.244.4.193)
    Test Epoch 1:  96%|█████████▋| 302/313 [00:01<00:00, 250.76it/s]
    Test Epoch 1: 100%|██████████| 313/313 [00:01<00:00, 262.94it/s]
    (RayTrainWorker pid=1193, ip=10.244.4.193)
    Train Epoch 2:   0%|          | 0/1875 [00:00<?, ?it/s]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Test Epoch 1:  92%|█████████▏| 289/313 [00:01<00:00, 222.57it/s]
    
    Training finished iteration 2 at 2024-11-21 19:16:12. Total running time: 4min 48s
    ╭───────────────────────────────╮
    │ Training result               │
    ├───────────────────────────────┤
    │ checkpoint_dir_name           │
    │ time_this_iter_s       25.975 │
    │ time_total_s          170.875 │
    │ training_iteration          2 │
    │ accuracy                0.828 │
    │ loss                  0.45946 │
    ╰───────────────────────────────╯
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Test Epoch 1: 100%|██████████| 313/313 [00:01<00:00, 226.04it/s]
    (RayTrainWorker pid=1193, ip=10.244.4.193)
    Train Epoch 1: 100%|██████████| 1875/1875 [00:24<00:00, 76.24it/s] [repeated 45x across cluster]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Train Epoch 2:  13%|█▎        | 239/1875 [00:03<00:24, 67.30it/s] [repeated 64x across cluster]
    (RayTrainWorker pid=1193, ip=10.244.4.193)
    Test Epoch 1:   0%|          | 0/313 [00:00<?, ?it/s]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Test Epoch 1:  85%|████████▍ | 266/313 [00:01<00:00, 222.54it/s] [repeated 20x across cluster]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    ..
    
    Training completed after 10 iterations at 2024-11-21 19:19:47. Total running time: 8min 23s
    2024-11-21 19:19:47,596 INFO tune.py:1009 -- Wrote the latest version of all result files and experiment state to '/home/ray/ray_results/TorchTrainer_2024-11-21_19-11-23' in 0.0029s.
    
    Training result: Result(
      metrics={'loss': 0.35892221605786073, 'accuracy': 0.872},
      path='/home/ray/ray_results/TorchTrainer_2024-11-21_19-11-23/TorchTrainer_74867_00000_0_2024-11-21_19-11-24',
      filesystem='local',
      checkpoint=None
    )
    (RayTrainWorker pid=1341, ip=10.244.1.42) Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz [repeated 7x across cluster]
    (RayTrainWorker pid=1341, ip=10.244.1.42) Downloading http://fashion-mnist.s3-website.eu-central-1.amazonaws.com/t10k-labels-idx1-ubyte.gz to /home/ray/data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz [repeated 7x across cluster]
    (RayTrainWorker pid=1341, ip=10.244.1.42) Extracting /home/ray/data/FashionMNIST/raw/t10k-labels-idx1-ubyte.gz to /home/ray/data/FashionMNIST/raw [repeated 7x across cluster]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Train Epoch 9:  91%|█████████ | 1708/1875 [00:21<00:01, 83.84it/s] [repeated 23x across cluster]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Train Epoch 9: 100%|██████████| 1875/1875 [00:23<00:00, 78.52it/s] [repeated 37x across cluster]
    (RayTrainWorker pid=1341, ip=10.244.1.42)
    Test Epoch 9:   0%|          | 0/313 [00:00<?, ?it/s]
    (RayTrainWorker pid=1193, ip=10.244.4.193)
    Test Epoch 9:  89%|████████▉ | 278/313 [00:01<00:00, 266.46it/s] [repeated 19x across cluster]
    (RayTrainWorker pid=1193, ip=10.244.4.193)
    Test Epoch 9:  97%|█████████▋| 305/313 [00:01<00:00, 256.69it/s]
    Test Epoch 9: 100%|██████████| 313/313 [00:01<00:00, 267.35it/s]
    2024-11-21 19:19:51,728 SUCC cli.py:63 -- ------------------------------------------
    2024-11-21 19:19:51,728 SUCC cli.py:64 -- Job 'rayjob-pytorch-mnist-hndpx' succeeded
    2024-11-21 19:19:51,728 SUCC cli.py:65 -- ------------------------------------------
    

在 Ray 仪表板上查看训练结果

RayJob 成功完成后,可以在 Ray 仪表板上查看训练结果。 Ray 仪表板将提供对 Ray 群集的实时监视信息和可视化结果。 你可以使用 Ray 仪表板监视 Ray 群集的状态、查看日志以及直观呈现机器学习作业的结果。

若要访问 Ray 仪表板,需要通过创建服务填充码来向公共 Internet 公开 Ray 头服务,以在端口 80(而不是端口 8265)上公开 Ray 头服务。

注意

上一部分所述的 deploy.sh 会自动向公共 Internet 公开 Ray 头服务。 deploy.sh 脚本中包括以下步骤。

  1. 使用以下命令获取 Ray 头服务的名称并将其保存在 shell 变量中:

    rayclusterhead=$(kubectl get service -n $kuberay_namespace | grep 'rayjob-pytorch-mnist-raycluster' | grep 'ClusterIP' | awk '{print $1}')
    
  2. 使用 kubectl expose service 命令创建服务填充码,以在端口 80 上公开 Ray 头服务。

    kubectl expose service $rayclusterhead \
    -n $kuberay_namespace \
    --port=80 \
    --target-port=8265 \
    --type=NodePort \
    --name=ray-dash
    
  3. 使用以下命令创建入口,以使用入口控制器公开服务填充码:

    cat <<EOF | kubectl apply -f -
    apiVersion: networking.k8s.io/v1
    kind: Ingress
    metadata:
      name: ray-dash
      namespace: kuberay
      annotations:
        nginx.ingress.kubernetes.io/rewrite-target: /
    spec:
      ingressClassName: webapprouting.kubernetes.azure.com
      rules:
      - http:
          paths:
          - backend:
              service:
                name: ray-dash
                port:
                  number: 80
            path: /
            pathType: Prefix
    EOF
    
  4. 使用 kubectl get service 命令获取入口控制器的公共 IP 地址。

    kubectl get service -n app-routing-system
    
  5. 在输出中,应该会显示附加到入口控制器的负载均衡器的公共 IP 地址。 复制该公共 IP 地址并将其粘贴到 Web 浏览器中。 这样,便可看到 Ray 仪表板。

清理资源

若要清理在本指南中创建的资源,可以删除包含 AKS 群集的 Azure 资源组。

后续步骤

若要详细了解 AKS 上的 AI 和机器学习工作负载,请参阅以下文章:

供稿人

Microsoft 会维护本文。 本系列文章为以下参与者的原创作品:

  • Russell de Pina | 首席 TPM
  • Ken Kilty | 首席 TPM
  • Erin Schaffer | 内容开发人员 2
  • Adrian Joian | 首席客户工程师
  • Ryan Graham | 首席技术专家