Migrando de kubernetes cronjobs a quantum-core
Introducción
Por si te lo preguntás, la imagen está pensada para representar al trickster, ya que planeo hacer muchas publicaciones sobre tips y trucos y pensé que sería algo gracioso 😄.
En este artículo, exploraremos cómo pasar de tareas regulares y el clásico programador de cron a quantum-core. Aprovecharemos el BEAM que ya estamos ejecutando para gestionar tareas programadas, lo que tiene algunos beneficios y desventajas. Para mi caso de uso, debería funcionar bien. Actualmente hay dos escenarios que necesitan ejecutarse periódicamente: uno es diario y el otro cada hora. No hay muchos cambios en estas partes del sistema, por lo que esto es perfecto, y también reducirá la cantidad de recursos en el clúster, ya que todo será manejado por la app.
Cabe señalar que podés gestionar las tareas de manera programática, lo que hace que esta librería sea muy flexible. Podrías gestionarlo todo desde una interfaz web o desde iex
. Sin embargo, para simplificar las cosas, y porque así es como lo usaré, veremos cómo reemplazar los cronjobs actuales con el enfoque de configuración regular.
Si querés aprender más sobre cómo ejecutar algunas tareas de Elixir en Kubernetes, podés dirigirte al artículo anterior: ejecutar cronjobs en Kubernetes.
Pasos
Algunos pasos son necesarios para que este cron funcione:
-
Agregar la librería a las dependencias en
mix.exs
-
Crear tu scheduler en
lib/tr/scheduler.ex
-
Agregarlo a la lista de hijos en el supervisor de la app en
lib/tr/application.ex
-
Configurar tus tareas programadas en
config/config.ex
- Reiniciar tu servidor y ¡verificar!
Agregar la librería a las dependencias en mix.exs
{:quantum, "~> 3.5"}
Ejecutá mix.deps get
para obtener la nueva dependencia.
Crear tu scheduler lib/tr/scheduler.ex
Esto es clave, ya que será la app que se ejecute como el cron.
defmodule Tr.Scheduler do
@moduledoc """
Cron-like scheduler
"""
use Quantum, otp_app: :tr
end
Agregarlo a la lista de hijos en el supervisor de la app lib/tr/application.ex
Por último, pero no menos importante, iniciá la app, de lo contrario nuestro cron no funcionará 😅. Colocalo antes del Endpoint, ya que usualmente el Endpoint sería lo último que querés iniciar para que ningún cliente pueda alcanzar la aplicación antes de que todo esté en marcha y funcionando como se espera.
children = [
# ...
# Iniciar el scheduler
Tr.Scheduler,
# ...
]
Configurar tus tareas programadas en config/config.ex
Bien, eso fue fácil. Ahora el último paso: configurar las tareas reales. Quantum soporta varios formatos, pero yo usé el clásico MFA (Módulo-Función-Argumentos).
config :tr, Tr.Scheduler,
jobs: [
# Cada 30 minutos
{"*/30 * * * *", {Tr.Tracker, :start, []}},
# Cada 15 minutos
{"*/15 * * * *", {Tr.Approver, :start, []}}
]
Reiniciar tu servidor y verificar
¡Excelente! Parece que está funcionando como se esperaba.
iex -S mix phx.server
[debug] Loading Initial Jobs from Config
[debug] Adding job
[debug] Adding job
[info] Running TrWeb.Endpoint with Bandit 1.4.2 at 127.0.0.1:4000 (http)
[info] Access TrWeb.Endpoint at http://localhost:4000
Erlang/OTP 26 [erts-14.2.3] [source] [64-bit] [smp:32:32] [ds:32:32:10] [async-threads:1] [jit:ns]
[watch] build finished, watching for changes...
Interactive Elixir (1.16.2) - press Ctrl+C to exit (type h() ENTER for help)
Rebuilding...
[debug] Scheduling job for execution
[debug] Task for job started on node
[debug] Execute started for job
[debug] QUERY OK source="comments" db=0.2ms queue=0.4ms idle=1076.1ms
SELECT c0."id", c0."slug", c0."body", c0."parent_comment_id", c0."approved", c0."inserted_at", c0."updated_at", c0."user_id" FROM "comments" AS c0 WHERE (NOT (c0."approved")) []
↳ Tr.Post.get_unapproved_comments/0, at: lib/tr/post.ex:158
[debug] Execution ended for job
Puntos clave
Como podés ver, ejecutar tu propio programador tipo cron en el BEAM es bastante simple. Tenés control total, más flexibilidad y también es muy fácil comenzar con esto.
Pero, ¿qué pasa con nuestro código anterior que tenía que llamar a Application.load(:app_name)
y Application.ensure_all_started(:app_name)
? Bueno, eso puede seguir sin cambios. Si ya estaba cargado e iniciado, no hará nada, por lo que si alguna vez queremos volver a un cronjob externo, podemos hacerlo, o si no, podemos seguir usando nuestro nuevo cron moderno 😄.
Ejemplo de código para la tarea de Approver: lib/tr/approver.ex
defmodule Tr.Approver do
@moduledoc """
Tarea básica para aprobar comentarios si pasan el análisis de sentimiento
"""
@app :tr
defp load_app do
Application.load(@app)
end
defp start_app do
load_app()
Application.ensure_all_started(@app)
end
@doc """
Si la llama está de acuerdo con el sentimiento, entonces el comentario puede ser aprobado automáticamente.
"""
def check_comment_sentiment(comment) do
ollama_sentiment = Tr.Ollama.send(comment.body)
ollama_sentiment
end
def start do
start_app()
comments = Tr.Post.get_unapproved_comments()
Enum.each(comments, fn comment ->
if check_comment_sentiment(comment) do
Tr.Post.approve_comment(comment)
end
end)
end
end
Un par de notas: podrías hacer muchas cosas de manera diferente. Por ejemplo, podrías querer establecer diferentes horarios en distintos entornos. Usá el archivo de configuración que tenga sentido para vos. También podés explorar y usar el cron con configuración en tiempo de ejecución. En cualquier caso, asegurate de que haga el trabajo que esperás de manera eficiente.
¿Tenés alguna pregunta? Dejá un comentario 👇, es posible que tengas que esperar 15 minutos para que la tarea se ejecute antes de que pueda ser leída 😄, ¡pero al menos ahora ya sabés cómo funciona!
Errata
Si encontrás algún error o tenés alguna sugerencia, mandame un mensaje para que se pueda corregir.
También podés revisar el código fuente y los cambios en los sources aquí.
No tienes cuenta? Regístrate aqui
Ya registrado? Iniciar sesión a tu cuenta ahora.
-
Comentarios
Online: 0
Por favor inicie sesión para poder escribir comentarios.