Exécuter des tâches planifiées et en arrière-plan

CloudflareBeginner
Pratiquer maintenant

Introduction

L’équipe de support a besoin de deux façons d’exécuter le même petit contrôle : après la demande d’un utilisateur et périodiquement, sans demande utilisateur. Vous allez implémenter ces deux modes, distinguer l’accusé de réception de l’achèvement, tester localement la durée de vie des événements et observer une véritable invocation planifiée dans le cloud.

Cette VM indépendante démarre dans /home/labex/project/task-monitor avec Node.js 22.22.0, Wrangler 4.131.1 installé dans le projet, Miniflare 4.20260730.0 pour une évaluation isolée et un service interne synthétique d’intégrité. Vous réutiliserez les compétences acquises précédemment sur les liaisons de services et l’autorisation d’appareil ; vous n’utiliserez ni VM ni ressource d’un laboratoire précédent. Utilisez votre propre compte d’apprentissage ; aucun domaine, produit de stockage ni abonnement payant n’est requis. Les requêtes et les invocations planifiées sont comptabilisées dans l’utilisation normale du compte.

Gardez un terminal ouvert. La planification d’une minute sert uniquement à des tests temporaires. Terminez en arrêtant les flux de journaux, en supprimant les deux Workers tant que vous êtes autorisé, en confirmant leur absence et en vous déconnectant. Le travail en arrière-plan de ce laboratoire est limité dans le temps et non durable ; ne l’utilisez pas comme garantie de livraison fiable dans une file d’attente.

Retourner avant la fin du travail en arrière-plan

Dans cette étape, un endpoint public accusera réception d’une petite requête de contrôle d’intégrité pendant qu’un service interne fourni effectuera le travail asynchrone. Il s’agit d’une VM indépendante ; le service est un nouveau composant de test, et non une ressource provenant d’un laboratoire précédent.

cd /home/labex/project/task-monitor
node --version
npx wrangler --version
cat health/index.js

Le composant de test attend 250 millisecondes, puis renvoie un JSON synthétique. Il n’effectue aucune requête externe et ne stocke aucune donnée. Générez un nom de base unique, puis configurez le Worker public et son service interne HEALTH. Il s’agit des liaisons de services standard utilisées précédemment.

WORKER_NAME="labex-tasks-$(openssl rand -hex 6)"
cat > wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME",
  "main": "src/index.js",
  "compatibility_date": "2026-07-30",
  "workers_dev": true,
  "preview_urls": false,
  "services": [
    {
      "binding": "HEALTH",
      "service": "$WORKER_NAME-health"
    }
  ],
  "triggers": {
    "crons": []
  }
}
CONFIG
cat > health/wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME-health",
  "main": "index.js",
  "compatibility_date": "2026-07-30",
  "workers_dev": false,
  "preview_urls": false
}
CONFIG
cat > src/index.js <<'JS'
async function checkHealth(env, details) {
  const url = new URL('https://health.internal/health');
  url.searchParams.set('probe', details.probe);
  const response = await env.HEALTH.fetch(url, {signal: AbortSignal.timeout(3000)});
  if (!response.ok) throw new Error('health_service_unavailable');
  const data = await response.json();
  if (data.status !== 'ok' || data.service !== 'labex-health-fixture' || data.probe !== details.probe) {
    throw new Error('unexpected_health_response');
  }
  console.log(JSON.stringify({event: 'health_check', ...details, status: data.status, service: data.service}));
}

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    if (url.pathname === '/health' && request.method === 'GET') {
      return Response.json({status: 'ok'}, {headers: {'Cache-Control': 'no-store'}});
    }
    if (url.pathname !== '/checks') return Response.json({error: 'not_found'}, {status: 404});
    if (request.method !== 'POST') {
      return Response.json({error: 'method_not_allowed'}, {status: 405, headers: {Allow: 'POST'}});
    }
    const probe = url.searchParams.get('probe') || '';
    if (!/^[a-z0-9-]{1,48}$/.test(probe)) {
      return Response.json({error: 'invalid_probe'}, {status: 400});
    }
    ctx.waitUntil(checkHealth(env, {source: 'request', probe}).catch(() => {
      console.error(JSON.stringify({event: 'health_check_failed', source: 'request', probe}));
    }));
    return Response.json({accepted: true, probe}, {status: 202, headers: {'Cache-Control': 'no-store'}});
  }
};
JS

checkHealth valide la réponse réelle de la dépendance et émet un petit résultat structuré. Son signal d’interruption de trois secondes limite la durée de la requête. Le gestionnaire public transmet sa promesse à ctx.waitUntil, puis renvoie immédiatement le statut HTTP 202. Le statut 202 accuse réception de cette tentative de courte durée ; il ne garantit pas une livraison durable. Le bloc catch consigne l’échec sans afficher les exceptions brutes, les requêtes ni les identifiants. Utilisez uniquement les valeurs de sonde synthétiques présentées ici.

npx wrangler dev -c wrangler.jsonc -c health/wrangler.jsonc --ip 0.0.0.0 --port 8080 > dev.log 2>&1 &

Attendez l’invite de commande, puis consultez le journal. Continuez lorsque le serveur de développement public est prêt sur le port 8080. S’il est encore en cours de démarrage, patientez brièvement et relisez le journal.

cat dev.log
curl -i http://127.0.0.1:8080/health
curl -i -X POST "http://127.0.0.1:8080/checks?probe=manual-one"
cat dev.log

La route d’intégrité exécutée au premier plan renvoie le statut 200 et l’état ok. La requête POST renvoie le statut 202 avec accepted à true et la sonde manual-one. Une fois la dépendance terminée, le journal contient event à health_check, source à request, la même sonde et le statut ok. Si vous consultez le journal trop rapidement, relisez-le après un court instant. Le fait que la réponse arrive avant une entrée ultérieure du journal est une observation, pas une mesure précise des performances.

Utilisez la vérification pendant que le serveur fonctionne. Un environnement d’exécution isolé conserve son propre composant d’intégrité derrière une porte de contrôle, exige que la réponse au premier plan arrive avant d’ouvrir cette porte, puis exige la fin du travail en arrière-plan. Il vérifie également la route d’intégrité publique ainsi que les cas de méthode et de sonde rejetés. Ce test n’appelle pas Cloudflare.

Invoquer localement le gestionnaire planifié

Dans cette étape, réutilisez l’opération de contrôle d’intégrité depuis un gestionnaire déclenché par Cron. Identifiez et arrêtez le processus de développement réel avant de modifier le code. Remplacez le numéro de tâche si celui-ci diffère de l’exemple.

jobs
kill %1
cat > src/index.js <<'JS'
async function checkHealth(env, details) {
  const url = new URL('https://health.internal/health');
  url.searchParams.set('probe', details.probe);
  const response = await env.HEALTH.fetch(url, {signal: AbortSignal.timeout(3000)});
  if (!response.ok) throw new Error('health_service_unavailable');
  const data = await response.json();
  if (data.status !== 'ok' || data.service !== 'labex-health-fixture' || data.probe !== details.probe) {
    throw new Error('unexpected_health_response');
  }
  console.log(JSON.stringify({event: 'health_check', ...details, status: data.status, service: data.service}));
}

export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    if (url.pathname === '/health' && request.method === 'GET') {
      return Response.json({status: 'ok'}, {headers: {'Cache-Control': 'no-store'}});
    }
    if (url.pathname !== '/checks') return Response.json({error: 'not_found'}, {status: 404});
    if (request.method !== 'POST') {
      return Response.json({error: 'method_not_allowed'}, {status: 405, headers: {Allow: 'POST'}});
    }
    const probe = url.searchParams.get('probe') || '';
    if (!/^[a-z0-9-]{1,48}$/.test(probe)) {
      return Response.json({error: 'invalid_probe'}, {status: 400});
    }
    ctx.waitUntil(checkHealth(env, {source: 'request', probe}).catch(() => {
      console.error(JSON.stringify({event: 'health_check_failed', source: 'request', probe}));
    }));
    return Response.json({accepted: true, probe}, {status: 202, headers: {'Cache-Control': 'no-store'}});
  },
  async scheduled(controller, env) {
    await checkHealth(env, {
      source: 'scheduled',
      probe: `cron-${controller.scheduledTime}`,
      cron: controller.cron,
      scheduledTime: controller.scheduledTime
    });
  }
};
JS
cat > wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME",
  "main": "src/index.js",
  "compatibility_date": "2026-07-30",
  "workers_dev": true,
  "preview_urls": false,
  "services": [
    {
      "binding": "HEALTH",
      "service": "$WORKER_NAME-health"
    }
  ],
  "triggers": {
    "crons": [
      "* * * * *"
    ]
  }
}
CONFIG

L’expression à cinq champs * * * * * signifie « toutes les minutes », en UTC. Cette planification volontairement fréquente sert uniquement à une courte expérience temporaire. scheduled attend la fin de l’opération d’intégrité afin que le résultat de l’invocation reflète son achèvement ; il ne renvoie pas de réponse HTTP. Le journal enregistre l’expression Cron réelle du déclencheur ainsi que l’heure planifiée.

npx wrangler dev -c wrangler.jsonc -c health/wrangler.jsonc --ip 0.0.0.0 --port 8080 > dev.log 2>&1 &

Attendez l’invite de commande, puis consultez le journal. Continuez lorsque le serveur de développement public est prêt sur le port 8080. S’il est encore en cours de démarrage, patientez brièvement et relisez le journal.

cat dev.log
curl -i "http://127.0.0.1:8080/cdn-cgi/local/scheduled?cron=*+*+*+*+*&time=1700000000000&format=json"
cat dev.log

Le déclencheur local doit renvoyer outcome ok, et le journal de l’application doit contenir source scheduled, le cron * * * * * et scheduledTime 1700000000000. Cet ancien horodatage est une entrée de test synthétique volontaire ; il ne prouve pas l’exécution actuelle dans le cloud. Utilisez la vérification pour exécuter une autre heure planifiée contrôlée et confirmer que le service d’intégrité a été appelé.

Pour les invocations HTTP, waitUntil peut prolonger le travail jusqu’à 30 secondes après l’envoi de la réponse ou la déconnexion du client ; cette durée est partagée par les promesses d’arrière-plan de la requête. Il ne s’agit ni d’une file d’attente durable ni d’une garantie de nouvelle tentative. La petite opération de ce laboratoire, limitée à trois secondes, correspond à ce rôle. Tout travail nécessitant une livraison ou des tentatives fiables doit utiliser une architecture appropriée de file d’attente ou de workflow, en dehors de ce laboratoire. Consultez la documentation de l’API context et du gestionnaire scheduled pour connaître les différentes durées de vie des invocations.

Déployer et observer le travail d’arrière-plan d’une requête

Dans cette étape, déployez les deux nouveaux Workers dans votre propre compte d’apprentissage. Arrêtez le processus local réel et autorisez cette nouvelle VM avec le flux d’appareil habituel.

jobs
kill %1
npx wrangler login --device --browser=false --scopes account:read user:read workers_scripts:write workers_tail:read

Ouvrez dans votre navigateur connecté le lien d’appareil affiché, saisissez son code, vérifiez les accès demandés et sélectionnez le compte d’apprentissage. Attendez la confirmation de réussite dans le terminal.

npx wrangler whoami --json

Confirmez le nom du compte, même si un seul compte est proposé. Remplacez YOUR_ACCOUNT_ID dans les deux configurations ci-dessous par son identifiant réel. Conservez les mêmes noms de Workers générés et la planification d’une minute. Le Worker public active également Workers Logs ; ses journaux d’invocation et d’application sont donc disponibles sous Observability dans le Dashboard. Cela est distinct de la connexion live wrangler tail. Ce laboratoire temporaire n’émet que des données synthétiques de contrôle d’intégrité ; n’enregistrez jamais d’identifiants. Consultez la documentation Workers Logs.

cat > wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME",
  "main": "src/index.js",
  "compatibility_date": "2026-07-30",
  "workers_dev": true,
  "preview_urls": false,
  "services": [
    {
      "binding": "HEALTH",
      "service": "$WORKER_NAME-health"
    }
  ],
  "triggers": {
    "crons": [
      "* * * * *"
    ]
  },
  "observability": {
    "enabled": true
  },
  "account_id": "YOUR_ACCOUNT_ID"
}
CONFIG
cat > health/wrangler.jsonc <<CONFIG
{
  "name": "$WORKER_NAME-health",
  "main": "index.js",
  "compatibility_date": "2026-07-30",
  "workers_dev": false,
  "preview_urls": false,
  "account_id": "YOUR_ACCOUNT_ID"
}
CONFIG
npx wrangler deploy -c health/wrangler.jsonc
npx wrangler deploy

Le service interne d’intégrité est déployé en premier afin que la liaison du Worker public puisse le résoudre. Vérifiez que la sortie du déploiement public contient schedule: * * * * *. Copiez ci-dessous son adresse workers.dev réelle.

APP_URL="https://YOUR_WORKER.YOUR_SUBDOMAIN.workers.dev"
curl -i "$APP_URL/health"
npx wrangler tail --format pretty > events.log 2> tail-errors.log &

Laissez quelques secondes à la connexion tail pour s’initialiser, puis envoyez un contrôle synthétique.

sleep 5
curl -i -X POST "$APP_URL/checks?probe=remote-one"
cat events.log

Recherchez l’événement POST et son journal health_check contenant source request et la sonde remote-one. Si aucun événement n’est encore visible, consultez tail-errors.log, patientez brièvement, envoyez de nouveau la requête et relisez events.log. Un fichier journal provenant d’un laboratoire précédent ne constitue pas une preuve pour ce déploiement.

Dans le Dashboard, ouvrez Compute → Workers & Pages pour ce compte. Confirmez les deux noms exacts, l’adresse du Worker public et sa liaison HEALTH vers le service interne correspondant. Utilisez la vérification : elle contrôle la propriété authentifiée et les métadonnées de l’endpoint et de la liaison, puis ouvre sa propre session tail en direct de courte durée et envoie une nouvelle sonde pour confirmer l’achèvement du travail en arrière-plan. L’évaluateur ne considère pas votre fichier events.log comme une preuve. Laissez votre tâche tail d’apprenant s’exécuter pour l’étape suivante.

Observer une véritable exécution Cron

Dans cette étape, distinguez la configuration du déploiement de l’exécution. Ouvrez le Worker public dans le Dashboard et consultez Settings → Trigger events → Cron triggers. Vérifiez que la planification affichée est Every minute. Next time est une prédiction, et non une exécution terminée. Une planification configurée ne prouve pas à elle seule que son gestionnaire s’est exécuté.

L’exemple ci-dessous présente l’intervalle minimal pris en charge : * * * * *, soit une fois par minute. Comparez le nom du Worker avec votre propre configuration. Le nom et l’heure affichée sont des exemples ; vous n’avez pas besoin d’ajouter un autre déclencheur dans le Dashboard puisque Wrangler l’a déjà configuré.

Déclencheur Cron configuré pour s’exécuter chaque minute dans les paramètres du Worker

Laissez la connexion tail de l’apprenant fonctionner. Attendez un événement planifié réel et consultez le même fichier journal :

sleep 60
cat events.log

Recherchez dans la sortie lisible de tail une invocation planifiée réussie, identifiée par l’expression Cron et son heure d’exécution. Son journal health_check doit contenir source scheduled, le cron * * * * *, le statut ok, le service labex-health-fixture et scheduledTime. La sonde est composée de cron- suivi de cette valeur scheduledTime. Le vérificateur indépendant compare séparément les métadonnées d’événement structurées de Cloudflare avec ce journal d’application. Un événement POST, un horodatage synthétique local ou un journal vide ne permet pas d’établir ce résultat.

Pour relier cette sortie à l’interface du navigateur, ouvrez la page Observability → Events du même Worker. Utilisez Live pendant l’attente, ou actualisez la requête d’événements enregistrée avec une période contenant votre déploiement. Les lignes d’invocation de ce gestionnaire affichent * * * * *. Développez-en une, sélectionnez View invocation, puis développez la ligne de journal d’application associée pour examiner les champs du contrôle d’intégrité. Un journal d’application structuré peut présenter une cellule Message vide ; développez la ligne au lieu de considérer les données comme absentes. Vous pouvez mettre l’affichage en direct en pause pendant la lecture.

Dans cet exemple réel, source vaut scheduled, status vaut ok et service vaut labex-health-fixture. La sonde cron-... correspond au scheduledTime du journal d’application, exprimé en millisecondes. Le Dashboard affiche l’horodatage visible dans le fuseau configuré (GMT+8 ici), tandis que la planification Cron utilise l’UTC. Votre nom, votre identifiant d’invocation et votre heure seront différents. Lisez ces champs avec le résultat de l’invocation ; la capture d’écran seule ne constitue pas une preuve d’achèvement.

Invocation Cron réelle avec résultat du contrôle d’intégrité planifié dans le Dashboard

La propagation des mises à jour Cron peut prendre jusqu’à 15 minutes. Répétez le cycle d’attente d’une minute et de consultation, pendant au maximum 17 minutes après la réussite du déploiement. Consultez tail-errors.log si le flux est vide ou arrêté. Si aucun événement correspondant n’apparaît dans ce délai, arrêtez-vous et diagnostiquez la configuration, l’autorisation et l’état du déclencheur ; ne signalez pas de réussite. Il s’agit d’une observation d’apprentissage limitée dans le temps, et non d’une garantie de latence d’exécution exacte. La documentation Cron Triggers explique la propagation et la planification en UTC. Les Workers Logs enregistrés peuvent également mettre un court instant à apparaître ; actualisez la requête après avoir attendu l’ingestion. L’historique distinct Past Cron Events d’un nouveau Worker peut prendre jusqu’à 30 minutes avant d’afficher les événements. Un historique vide ne prouve pas un échec ; utilisez l’observation en temps réel ci-dessus dans la limite prévue par ce laboratoire.

Utilisez la vérification après avoir observé une exécution. Elle contrôle la planification déployée et surveille un flux en direct distinct pendant 70 secondes maximum afin de détecter un événement planifié réel accompagné du résultat d’intégrité. Cette durée couvre une frontière complète d’une minute après le démarrage de la connexion. L’absence d’événement est inconclusive ; vérifiez donc l’état de la connexion et de la propagation, puis réessayez dans la même limite d’observation. L’évaluateur ne fabrique aucun événement planifié. Arrêtez votre tâche tail uniquement après la réussite de la vérification ; utilisez le numéro réel de la tâche.

jobs
kill %1

Terminez rapidement la planification temporaire en effectuant l’étape suivante. Ne laissez pas fonctionner sans surveillance une tâche d’apprentissage exécutée chaque minute.

Supprimer les deux Workers de test planifiés

Dans cette étape, supprimez le Worker public et son déclencheur, puis le composant interne de test d’intégrité, tout en restant autorisé. Vérifiez que les deux configurations contiennent les noms exacts utilisés par ce laboratoire ainsi que le compte prévu.

cat wrangler.jsonc
cat health/wrangler.jsonc
npx wrangler delete
npx wrangler delete -c health/wrangler.jsonc

À chaque invite correspondant au nom attendu, appuyez sur la seule touche y. Préservez les projets sans rapport, le compte et son sous-domaine. La version épinglée de Wrangler peut afficher le diagnostic d’authentification connu lié au nettoyage de l’ancien KV après la suppression d’un Worker ; ni ce message ni une requête réseau échouée ne prouvent la suppression. N’élargissez pas les permissions pour résoudre ce diagnostic.

Actualisez le Dashboard et utilisez la vérification. Un inventaire authentifié réussi des Workers doit montrer que les deux noms sont absents. Cela confirme la suppression des ressources déployées, et non la propagation mondiale instantanée de chaque modification du planificateur. Se déconnecter ou fermer la VM ne réaliserait pas ce nettoyage.

Déconnecter la VM du laboratoire

Dans cette étape, confirmez que la tâche tail est arrêtée et que les ressources ont disparu avant de déconnecter cette VM.

jobs
npx wrangler logout
npx wrangler whoami --json

Exigez explicitement loggedIn: false. La commande structurée sans authentification peut se terminer avec un code différent de zéro ; une erreur réseau ne donne pas le même résultat. Utilisez la vérification, puis terminez la VM. La connexion de votre navigateur peut rester active pour d’autres laboratoires indépendants.

Résumé

Vous avez utilisé waitUntil pour permettre à un contrôle d’intégrité limité dans le temps de s’achever après un accusé de réception au premier plan, puis vous avez réutilisé cette opération dans un gestionnaire planifié. Les tests locaux contrôlés ont séparé la réponse du travail différé, tandis qu’un événement réel dans le cloud a établi l’exécution planifiée après le déploiement. La configuration, l’invocation manuelle et l’exécution en direct ont fourni des types de preuves différents.

Vous avez vérifié la propriété et les liaisons de services, mis en relation les sondes synthétiques avec les journaux structurés, respecté les limites de durée de vie des invocations et supprimé l’application planifiée temporaire avant la déconnexion. Une livraison fiable sur une longue durée nécessite une architecture différente de ce modèle de travail en arrière-plan de courte durée.