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.

Top comments (0)