Бесконечные циклы могут остановить работу любого приложения Node.js. В одну минуту ваша служба работает, а следующая загрузка процессора достигает 100%, и время выполнения запросов истекает. Каждый разработчик боится, что бесконечный цикл приведет к сбою производства.
В этом подробном руководстве вы узнаете, как устранять эти сложные проблемы:
- Подключение отладчика для печати трассировок стека запущенных процессов Node.
- Использование тестов активности Kubernetes для автоматического запуска отладки
- Сохранение трассировок стека путем подключения сетевого хранилища
Следуйте инструкциям, и вы приобретете навыки диагностики и устранения даже самых упорных бесконечных циклов в вашей производственной среде Node.js.
«Кошмар: бесконечный цикл» останавливает производство
Недавно наша команда столкнулась с бесконечным циклом, вызвавшим хаос в нашей производственной среде.
Мы запускали экспресс-приложение Node.js в контейнерах Docker без сохранения состояния, организованных Kubernetes. Трафик проходил через вход NGINX.
Internet
|
|------------|
| NGINX LB |
|------------|
|
[PODs]
/ \
Pod 1 Pod 2
App 1 App 2
Наша производственная архитектура до инцидента
Внезапно мы заметили скачок задержки балансировщика нагрузки. Приложение все еще отвечало на проверки работоспособности, но время ожидания запросов потребителей истекло.
При проверке модулей выяснилось, что загрузка ЦП одного контейнера достигла 100%. Бесконечный цикл!
| Looping |
| Pod 1 |
| <CPU 100%> |
| |
Бесконечный цикл привел к сбою пода 1
Наши обычные инструменты отладки, такие как --prof
и --cpu-prof
было недостаточно — цикл был вызван недетерминированно, поэтому нам нужно было копать глубже.
Нам нужно было найти способ поставить точку останова в работающем контейнере и распечатать трассировку стека именно в том месте, где происходит цикл.
Отладка с GDB
Решением оказался отладчик GNU (GDB). Присоединив его к нашему процессу Node, мы могли бы:
- Точка останова в работающем коде JavaScript
- Распечатайте трассировку стека для диагностики цикла
Вот как можно использовать GDB для отладки Node.js в рабочей среде:
1. Установите ГБД
Сначала установите gdb
в вашем приложении Dockerfile:
RUN apt-get update && apt-get install -y gdb
Это связывает его с образом контейнера вашего приложения.
2. Подключите GDB к процессу вашего узла.
Затем выполните команду в работающем контейнере и прикрепите gdb
:
docker exec -it <container> bash
gdb -p <PID>
Замените <container>
с вашим идентификатором или именем контейнера, и <PID>
с вашим идентификатором процесса Node.
Присоединение открывает интерактивный сеанс GDB, подключенный к вашему работающему процессу Node.
3. Установите точку останова при переполнении стека
В GDB установите точку останова, чтобы приостановить выполнение при переполнении стека V8:
break v8::Isolate::HandleStackOverflow
continue
Это останавливает процесс Node сразу в тот момент, когда бесконечная рекурсия вызывает переполнение стека.
4. Распечатайте трассировку стека
При приостановке выполнения распечатайте трассировку стека, чтобы увидеть, где в вашем коде возникает цикл:
bt
Обратная трассировка покажет стек вызовов, ведущий к бесконечному циклу.
Например, если ваш код имеет:
function leakMemory() {
while(true) {
// Infinite loop!
}
}
leakMemory();
Ваш сеанс GDB будет захватывать:
#0 loop (cpu=100%)
#1 leakMemory()
#2 main()
Теперь вы точно определили, откуда берется петля!
Быстрый пример GDB
Давайте рассмотрим простой практический пример:
Создайте бесконечный цикл в Node:
// index.js const infiniteLoop = () => { while(true) {} } infiniteLoop();
Запустите его в Docker:
docker build -t debug-demo . docker run -it debug-demo
Подключите GDB в другом терминале:
docker ps # Get container ID docker exec -it <ID> bash gdb -p <PID> break v8::Isolate::HandleStackOverflow continue bt # Print stack trace
Как только вы установите точку останова и bt
, GDB напечатает что-то вроде:
#0 v8::Isolate::HandleStackOverflow
#1 infiniteLoop() at index.js:4
#2 main() at index.js:8
GDB приостанавливает выполнение и печатает стек вызовов, показывая infiniteLoop
вызывает проблему.
Теперь у вас есть практический опыт отладки процессов Node.js с помощью GDB! Давайте посмотрим, как применить эти методы в масштабе Kubernetes.
Использование зондов жизнеспособности
С помощью GDB мы можем прерывать процессы отдельных узлов. Но как автоматически выполнять отладку в кластере контейнеров?
Ответ кроется в Kubernetes зонды жизнеспособности. Объединив проверки работоспособности с нашим сценарием GDB, мы можем автоматически отлаживать бесконечные циклы между модулями.
Вот как это настроить:
1. Понимание датчиков активности
Kubernetes использует проверки работоспособности для отслеживания работоспособности контейнера. Например:
livenessProbe:
httpGet:
path: /healthz
port: 3000
periodSeconds: 5 # Check every 5 sec
failureThreshold: 4 # Allow 3 failures
Это делает HTTP GET /healthz
каждые 5 секунд, допуская 3 сбоя перед перезапуском модуля.
Когда происходит четвертый сбой, Kubernetes считает под «неработоспособным» и убивает его.
2. Использование сбоев зондирования для запуска GDB
Мы можем использовать эти события сбоя для автоматического запуска GDB!
Например, создайте скрипт-оболочку для вашего зонда:
#!/bin/bash
# Do normal health check
response=$(curl -s http://localhost:3000/healthz)
if [ $? != 0 ]; then
failures=$((failures+1))
if [ "$failures" -ge 4 ]; then
# Attach GDB on 4th failure
gdb -p <PID> -x /gdbscript.gdb
fi
fi
- Пройдите обычную проверку здоровья
- Если это не удалось, увеличьте счетчик
- При 4 неудачах запустите сценарий GDB.
Теперь сбои проверки работоспособности заставляют GDB подключать и отлаживать ваше приложение!
3. Обновление определения датчика жизнеспособности
Укажите свой livenessProbe
к этому сценарию-оболочке:
livenessProbe:
exec:
command:
- /bin/bash
- /wrapperscript.sh
# Other probe parameters
Теперь Kubernetes запустит ваш скрипт, содержащий логику отладки GDB.
Всякий раз, когда цикл вызывает сбой, GDB запускается автоматически!
Полный пример
Вот полный пример livenessProbe
упаковка отладки GDB:
gdbscript.gdb:
break v8::Isolate::HandleStackOverflow
continue
bt
живость.sh:
#!/bin/bash
response=$(curl -s http://localhost:3000/healthz || exit 1)
if [ $? != 0 ]; then
failures=$((failures+1))
if [ "$failures" -ge 4 ]; then
gdb -p <PID> /gdbscript.gdb
fi
fi
развертывание.yaml:
livenessProbe:
exec:
command:
- /bin/bash
- /liveness.sh
periodSeconds: 5
failureThreshold: 4
Теперь ваш зонд живучести автоматически отлаживает любые бесконечные циклы!
Сохраняющиеся следы стека
При описанной выше настройке, если ваше приложение входит в бесконечный цикл, проверка работоспособности запустит GDB и распечатает трассировку стека.
Но поскольку вскоре после этого модуль завершается, выходные данные трассировки стека теряются при перезапуске контейнера.
Чтобы сохранить следы, нам нужно хранить их снаружи, используя сетевое хранилище. Kubernetes предлагает несколько отличных вариантов подключения общих томов для сохранения ваших данных.
Использование постоянных томов
A Постоянный том (PV) предоставляет сетевое хранилище для модулей Kubernetes. Некоторые варианты включают в себя:
- Тома EBS (хранилище эластичных блоков)
- EFS (эластичная файловая система)
- ISCSI
- NFS (сетевая файловая система)
Например, чтобы смонтировать том EFS:
- Создайте файловую систему EFS
- Создайте PV и PersistentVolumeClaim (PVC).
- Установите ПВХ в капсулу
apiVersion: v1
kind: PersistentVolume
metadata:
name: gdb-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
storageClassName: efs
csi:
driver: efs.csi.aws.com
volumeHandle: <file-system-id>
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: gdb-pvc
spec:
accessModes:
- ReadWriteMany
storageClassName: efs
resources:
requests:
storage: 1Gi
---
# Pod spec
volumes:
- name: debugger-storage
persistentVolumeClaim:
claimName: gdb-pvc
Теперь ваш модуль может монтировать PV в качестве внешнего хранилища!
Сохранение трассировок стека
Подключив сетевое хранилище, вы можете сохранять следы GDB:
# liveness.sh
gdb -p <PID> /gdbscript.gdb | tee /debugger/stacktrace.txt
Это перенаправляет вывод GDB на внешний stacktrace.txt
.
При следующем сбое ваш датчик работоспособности:
- Присоедините GDB и распечатайте стек
- Сохраните трассировку на свой постоянный том.
Таким образом, даже когда модуль перезапускается, вы сохраняете данные отладки!
Пример из реального мира
Давайте рассмотрим реальный пример того, как мы использовали эти методы:
Наш кластер Kubernetes размещался на AWS, поэтому в качестве хранилища мы использовали тома EFS.
Dockerfile
RUN apt-get update && apt-get install -y gdb
живость.sh
#!/bin/bash
response=$(curl -s http://localhost:3000/healthz || exit 1)
if [ $? != 0 ]; then
# Increment failure counter
...
# On 4th failure, attach GDB
if [ "$failures" -ge 4 ]; then
gdb -p <PID> /gdbscript.gdb | tee /efs/stacktrace.txt
fi
fi
развертывание.yaml
// Container spec
volumeMounts:
- name: debugger-efs
mountPath: /efs
// Pod spec
volumes:
- name: debugger-efs
persistentVolumeClaim:
claimName: efs-pvc # EFS storage
// Liveness probe
livenessProbe:
exec:
command:
- /bin/bash
- /liveness.sh
failureThreshold: 4
Это дало нам возможность автоматической отладки на основе проверок работоспособности. Всякий раз, когда цикл вызывал сбой, он:
- Запустить GDB с помощью трассировки стека
- Направьте вывод в постоянное хранилище EFS.
В конце концов мы обнаружили, что виноват неправильный импорт библиотеки. После исправления зависимости наш кошмар с бесконечным циклом был наконец решен!
Основные выводы
Вот ключевые методы отладки бесконечных циклов в Node.js:
- Используйте ГБД для точки останова и печати трассировок стека запущенных процессов Node
- Используйте зонды жизнеспособности для автоматического запуска GDB в случае сбоя
- Сохранять данные путем передачи трассировок в сетевые тома, такие как EFS
Хотя в примерах используются Docker и Kubernetes, те же принципы применимы к любой производственной среде:
- Подключите отладчик, например GDB, для приостановки и проверки.
- Автоматизация отладки с помощью проверок работоспособности
- Экстернализация данных, чтобы они сохранялись при перезапусках
С помощью этих стратегий вы сможете быстро диагностировать и устранить даже самые болезненные бесконечные циклы. Больше никаких сбоев в производстве!
Я надеюсь, что эти методы помогут вам отладить сложные бесконечные циклы. Дайте мне знать в комментариях, если у вас есть еще какие-либо советы или рекомендации!