Skip to content

fix: DNS resolution flakiness in prod, and let backfill scripts run there - #2693

Merged
Nayor merged 2 commits into
mainfrom
fix/tracking-dns-resolver-and-prod-scripts
Sep 17, 2026
Merged

Nayor merged 2 commits into
mainfrom
fix/tracking-dns-resolver-and-prod-scripts

Conversation

@Nayor

@Nayor Nayor commented Sep 17, 2026

Copy link
Copy Markdown
Contributor

Contexte

Deux problèmes trouvés en creusant les échecs DNS en prod (getaddrinfo EAI_AGAIN sur Strava/Coros/Suunto) et en essayant de faire tourner un script de backfill en prod.

1. DNS flaky - résolveur embarqué Docker

Confirmé empiriquement en prod : les serveurs DNS custom configurés dans le compose de l'infra sont bien appliqués au container (docker inspect confirme HostConfig.Dns) et directement joignables/fonctionnels (requête directe en contournant 127.0.0.11 → succès), mais le chemin de résolution normal de l'appli échoue quand même. Ça isole le problème au résolveur DNS embarqué de Docker (127.0.0.11) lui-même : glibc envoie les requêtes A et AAAA en parallèle, et certains environnements de résolveur embarqué perdent une des deux réponses.

C'est exactement le même bug déjà corrigé pour les containers v6_api (docker/scripts/entrypoint.sh là-bas) - c2c_tracking n'avait jamais eu l'équivalent, faute d'entrypoint custom du tout.

docker-entrypoint.sh ajoute options single-request-reopen à /etc/resolv.conf au démarrage (force des requêtes séquentielles). Ça nécessite root (resolv.conf n'est pas modifiable par l'utilisateur applicatif), donc l'image démarre maintenant en root et bascule vers l'utilisateur node via su juste avant de lancer l'appli.

Vérifié en local (l'image complète était trop lourde à builder sur la machine du moment, donc testé par couches sur une image déjà construite) :

  • le fix DNS est bien appliqué (options single-request-reopen présent dans /etc/resolv.conf)
  • le process applicatif tourne bien en uid 1000 (node), pas root
  • SIGTERM atteint bien le process pour un arrêt propre (code de sortie 143, confirmé via un handler trap, pas juste un chronométrage)

2. Les scripts de backfill ne pouvaient pas tourner en prod

scripts/ et tsconfig.json n'étaient jamais copiés dans l'image, et ts-node/typescript étaient supprimés (devDependencies, prunées par npm prune --omit=dev). Maintenant copiés dans les deux étapes du build, et ts-node/typescript déplacés vers dependencies pour survivre au prune.

Test plan

  • Vérifications locales détaillées ci-dessus (DNS, privilege drop, signal)
  • CI verte
  • Test en prod (déploiement direct, la machine locale n'a pas pu builder l'image complète)

…here

Two independent problems found while chasing prod DNS failures
(getaddrinfo EAI_AGAIN on Strava/Coros/Suunto calls) and trying to
actually run a backfill script in prod:

1. Docker's embedded DNS resolver (127.0.0.11) can drop one of
   glibc's parallel A/AAAA queries. Confirmed empirically in prod:
   the custom upstream DNS servers configured in the infra repo's
   compose file were correctly applied to the container (verified via
   docker inspect) and directly reachable/functional (verified with a
   direct query bypassing 127.0.0.11), yet the app's normal resolution
   path still failed - isolating the fault to Docker's embedded
   resolver itself, not the network or the upstream servers. This is
   the exact same issue already fixed for v6_api's containers
   (docker/scripts/entrypoint.sh there); c2c_tracking never had the
   equivalent fix, since it has no custom entrypoint at all.
   docker-entrypoint.sh appends `options single-request-reopen` to
   /etc/resolv.conf at container start, forcing sequential A/AAAA
   queries instead. Since this needs root (resolv.conf isn't writable
   by the app's unprivileged user) and the image now boots as root to
   apply it, the entrypoint drops to the node user via `su` before
   exec'ing the actual app. Verified locally: DNS fix applied, app
   process runs as uid 1000 not root, and SIGTERM still reaches it
   correctly for graceful shutdown (exit code 143, confirmed via a
   trap handler, not just timing).

2. The production image couldn't run scripts/backfill-*.ts at all:
   `scripts/` and `tsconfig.json` were never copied into the image,
   and `ts-node`/`typescript` were pruned as devDependencies. Now
   copied through both build stages, and ts-node/typescript moved to
   `dependencies` so `npm prune --omit=dev` keeps them.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 8d183329-6b71-4aec-b3ff-1bf790c3d650


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added the bug Something isn't working label Sep 17, 2026
@codacy-production

codacy-production Bot commented Sep 17, 2026 •

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 0 complexity · 0 duplication

Metric Results
Complexity 0
Duplication 0

View in Codacy

🟢 Coverage ∅ diff coverage · +0.00% coverage variation

Metric Results
Coverage variation ✅ +0.00% coverage variation (-3.00%)
Diff coverage ✅ ∅ diff coverage

View coverage diff in Codacy

Coverage variation details
Coverable lines Covered lines Coverage
Common ancestor commit (f312b61) 2214 2037 92.01%
Head commit (a05a29e) 2215 (+1) 2038 (+1) 92.01% (+0.00%)

Coverage variation is the difference between the coverage for the head and common ancestor commits of the pull request branch: <coverage of head commit> - <coverage of common ancestor commit>

Diff coverage details
Coverable lines Covered lines Diff coverage
Pull request (#2693) 0 0 ∅ (not applicable)

Diff coverage is the percentage of lines that are covered by tests out of the coverable lines that the pull request added or modified: <covered lines added or modified>/<coverable lines added or modified> * 100%

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@Nayor Nayor self-assigned this Sep 17, 2026
ts-node pulls in diff@4.0.2, vulnerable to a ReDoS in parsePatch/
applyPatch (GHSA-73rr-hh4g-fpgx). Pre-existing, but only surfaced as
a production-scope Snyk failure once ts-node moved from
devDependencies to dependencies in the previous commit. Force the
patched version via npm overrides rather than waiting on ts-node to
bump its own dependency.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@Nayor
Nayor merged commit adb33c8 into main Sep 17, 2026
9 checks passed
@Nayor
Nayor deleted the fix/tracking-dns-resolver-and-prod-scripts branch September 17, 2026 17:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant