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)