DEV Community

Jessica Cardoso
Jessica Cardoso

Posted on • Edited on

🌩️ [pt-BR] Ray no k8s: Executando o ray localmente no k3d

Continuando, nessa seção iremos executar uma aplicação extremamente básica. Escrevemos um código que calcula o quadrado de números de 1 até 10, antes de computar o quadrado pausamos a execução por alguns segundos para simular um processo demorado.

2.1 Código sem paralelismo

Abaixo o código que usamos, colocamos ele em um arquivo com o nome script.py.

import time


def square(x):
    time.sleep(x * 0.5)
    return {
        "numero": x,
        "quadrado": x * x
    }

start = time.time()
print([square(x+1) for x in range(10)])
print(f"Tarefa executou em {time.time() - start:.2f} segundos")
Enter fullscreen mode Exit fullscreen mode

Executamos o código que finalizou em 27.53 segundos, um tempo bem aceitável:

python script.py
# [{'numero': 1, 'quadrado': 1}, {'numero': 2, 'quadrado': 4}, {'numero': 3, 'quadrado': 9}, {'numero': 4, 'quadrado': 16}, {'numero': 5, 'quadrado': 25}, {'numero': 6, 'quadrado': 36}, {'numero': 7, 'quadrado': 49}, {'numero': 8, 'quadrado': 64}, {'numero': 9, 'quadrado': 81}, {'numero': 10, 'quadrado': 100}]
# Tarefa executou em 27.53 segundos
Enter fullscreen mode Exit fullscreen mode

2.2 Usando múltiplas CPUs localmente

Colocar esse código para usar os processadores é bem fácil, basta alguns ajustes:

import time
import ray # importar módulo do ray


def square(x):
    time.sleep(x * 0.5)
    return {
        "numero": x,
        "quadrado": x * x
    }

# criar uma task para o ray
@ray.remote
def square_task(x): 
    return square(x)

ray.init() # inicializar o ray
start = time.time()
# print([square(x+1) for x in range(10)]) # antes era assim
print(ray.get([square_task.remote(x+1) for x in range(10)]))
print(f"Tarefa executou em {time.time() - start:.2f} segundos")
Enter fullscreen mode Exit fullscreen mode

Fizemos poucas mudanças no código, mas conseguimos diminuir o tempo de execução (tempo poderá ser diferente pois varia de acordo com o equipamento). Abaixo podemos ver que o tempo de execução caiu para 5s, a máquina que executei tem mais de 6 cores.

python script_ray.py
# 2023-07-30 21:18:46,499 INFO worker.py:1612 -- Started a local Ray instance. View the dashboard at 127.0.0.1:8265
# [{'numero': 1, 'quadrado': 1}, {'numero': 2, 'quadrado': 4}, {'numero': 3, 'quadrado': 9}, {'numero': 4, 'quadrado': 16}, {'numero': 5, 'quadrado': 25}, {'numero': 6, 'quadrado': 36}, {'numero': 7, 'quadrado': 49}, {'numero': 8, 'quadrado': 64}, {'numero': 9, 'quadrado': 81}, {'numero': 10, 'quadrado': 100}]
# Tarefa executou em 5.06 segundos
Enter fullscreen mode Exit fullscreen mode

2.3 Usando o cluster local do k3d

Primeiro prosseguimos com a instalação do KubeRay operator.

helm repo add kuberay https://ray-project.github.io/kuberay-helm/
helm install kuberay-operator kuberay/kuberay-operator --version 0.6.0
Enter fullscreen mode Exit fullscreen mode

Para confirmar se o operador está em execução, verificamos se o pod está pronto.

kubectl get pods | grep kuberay-operator
# kuberay-operator-54f657c8cf-p2lh8           1/1     Running   2 (48s ago)   20h
Enter fullscreen mode Exit fullscreen mode

Em seguida, criamos um cluster do ray copiando o yaml exemplo da documentação fazendo pequenas mudanças. Colocamos 5 pods e menos recursos de memória e cpu, além de atribuirmos o head e os workers a nós específicos.

apiVersion: ray.io/v1alpha1
kind: RayCluster
metadata:
  labels:
    controller-tools.k8s.io: "1.0"
  name: raycluster-teste
spec:
  rayVersion: '2.6.1'
  headGroupSpec:
    serviceType: ClusterIP
    rayStartParams:
      dashboard-host: '0.0.0.0'
      block: 'true'
    template:
      spec:
        nodeSelector:
          type: node1 # definimos o head no nó 1
        containers:
        - name: ray-head
          image: rayproject/ray:2.6.1
          imagePullPolicy: Always
          resources: # mudamos para usar menos recursos
            limits: 
              cpu: "1"
              memory: "1Gi"
              ephemeral-storage: "1Gi"
            requests:
              cpu: "1"
              memory: "1Gi"
              ephemeral-storage: "1Gi"
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh","-c","ray stop"]
  workerGroupSpecs: # definimos para criar 5 pods
  - replicas: 5
    minReplicas: 5
    maxReplicas: 5
    rayStartParams:
      block: 'true'
    template:
      spec:
        nodeSelector: 
          type: node2 # workers no nó 2
        containers:
        - name: ray-worker
          image: rayproject/ray:2.6.1
          resources: # mudamos para usar menos recursos
            limits:
              cpu: "1"
              memory: "1Gi"
              ephemeral-storage: "1Gi"
            requests:
              cpu: "1"
              memory: "1Gi"
              ephemeral-storage: "1Gi"
          lifecycle:
            preStop:
              exec:
                command: ["/bin/sh","-c","ray stop"]
        - name: init-myservice
          image: busybox:1.28
          command: ['sh', '-c', "until nslookup $RAY_IP.$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace).svc.cluster.local; do echo waiting for myservice; sleep 2; done"]

Enter fullscreen mode Exit fullscreen mode

Após aplicar o comando kubectl apply -f ray-cluster.yaml, verificamos se todos os pods subiram com sucesso. Abaixo vemos os pods do ray em execução.

kubectl get pods
# NAME                                        READY   STATUS    RESTARTS   AGE
# kuberay-operator-54f657c8cf-52gsk           1/1     Running   0          7m46s
# raycluster-teste-head-mkzr5                 1/1     Running   0          4m6s
# raycluster-teste-worker-large-group-gns8w   1/1     Running   0          4m6s
# raycluster-teste-worker-large-group-cf2br   1/1     Running   0          4m6s
# raycluster-teste-worker-large-group-kl5ln   1/1     Running   0          4m6s
# raycluster-teste-worker-large-group-sslmb   1/1     Running   0          4m6s
# raycluster-teste-worker-large-group-ht64t   1/1     Running   0          4m6s
Enter fullscreen mode Exit fullscreen mode

Conforme recomendação da documentação do k3d, expomos a aplicação do ray ao criar um Ingress.

# verificar nome do serviço do ray
kubectl get services | grep raycluster
# raycluster-teste-head-svc   ClusterIP   10.43.99.42    <none>        10001/TCP,8265/TCP,8080/TCP,6379/TCP,8000/TCP   25m
Enter fullscreen mode Exit fullscreen mode
# criar ingress
kubectl apply -f - << END
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: mycluster-ingress
  annotations:
    ingress.kubernetes.io/ssl-redirect: "false"
spec:
  rules:
  - http:
      paths:
      - path: /
        pathType: Prefix
        backend:
          service:
            name: raycluster-teste-head-svc # serviço do ray
            port:
              number: 8265 # porta do serviço do ray
END
Enter fullscreen mode Exit fullscreen mode

Por fim, vamos conectar nossa aplicação ao cluster. Na documentação do ray é recomendado utilizar Ray Jobs para executar uma aplicação em um cluster. Assim, utilizamos o seguinte comando no mesmo diretório do script.py, não precisamos alterar o código em si:

ray job submit --address http://localhost:8265 --working-dir . -- python script.py
Enter fullscreen mode Exit fullscreen mode

Image description

Podemos notar que com 5 workers, conseguimos processar a aplicação em 7 segundos. Além disso, vimos que para executar o mesmo código local no cluster basta usar os Ray Jobs.

Limpeza

Deletar cluster do ray

Para apagar o cluster que criamos nesse exemplo, use o comando abaixo:

kubectl delete -f ray-cluster.yaml
# Pode levar um tempo para deletar, verifique com o `kubectl get pods`
Enter fullscreen mode Exit fullscreen mode

Deletar o operator do kubernetes

Normalmente deixaríamos o operador executando para poder subir outros clusters do ray, mas para deletar o recurso usamos o comando abaixo:

# desinstalar o operator
helm uninstall kuberay-operator
# deletar o ingress que nomeamos como `mycluster-ingress`
kubectl delete ingress mycluster-ingress
Enter fullscreen mode Exit fullscreen mode

Deletar o cluster local do k3d

Por fim, se não desejarmos mais fazer experimentos locais no kubernetes podemos deletar nosso cluster do k3d:

k3d cluster delete --config k3d-config.yml
Enter fullscreen mode Exit fullscreen mode

Esse tutorial foi parte do meu estudo explorando as documentações do Ray e do k3d.

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more

Top comments (0)

Image of Docusign

🛠️ Bring your solution into Docusign. Reach over 1.6M customers.

Docusign is now extensible. Overcome challenges with disconnected products and inaccessible data by bringing your solutions into Docusign and publishing to 1.6M customers in the App Center.

Learn more