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")
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
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")
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
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
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
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"]
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
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
# 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
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
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`
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
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
Esse tutorial foi parte do meu estudo explorando as documentações do Ray e do k3d.
Top comments (0)