Зачем вам это нужно?

При разработке кода на стороне сервера время от времени возникает проблема, которую очень трудно воспроизвести, наблюдаются утечки памяти или скачки процессора, которые вы не можете смоделировать локально, либо необходимо добавлять специальные журналы в приложение. При локальной разработке приложения используется инспектор Node.js для отладки и создания снапшотов памяти/процессора, которые помогут вам найти проблему, но как сделать то же самое в удаленной среде? К счастью, Node.js располагает отличной поддержкой для удаленной отладки, и в этой статье мы рассмотрим, как использовать ее в kubernetes.

Пример приложения

Мы будем использовать простой пример приложения для демонстрации всего процесса. Код демонстрационного приложения находится здесь: https://github.com/amirilovic/example-app.

Как включить режим отладки в процессе Node.js?

Конечно, режим отладки по умолчанию не включен для процесса Node.js, поскольку это позволяет выполнить произвольный код на удаленной машине. Есть два способа включить режим отладки в Node.js, используя флаг --inspect, либо посылая процессу сигнал SIGUSR1. Более подробную информацию об этом вы можете найти здесь.

Использование флага -- inspect

Когда вы запускаете процесс Node.js с флагом --inspect, режим отладки включается немедленно:

$ node --inspect dist/index.js Debugger listening on ws://127.0.0.1:9229/1393d28d-2e81-4096-85e2-f0f877f22c10 For help, see: https://nodejs.org/en/docs/inspector Server listening on port 3000...

В сообщении выше видно, что отладчик прослушивает порт 9229.

Теперь вы можете использовать vscode или chrome инспектор для присоединения отладчика к этому процессу:

В приведенном выше примере это конфигурация по умолчанию для присоединения к процессу Node.js:

{
  "name": "Attach",
  "port": 9229,
  "request": "attach",
  "skipFiles": ["<node_internals>/**"],
  "type": "pwa-node"
}

Важно отметить, что отладчик по умолчанию прослушивает только соединения, приходящие с localhost - он будет отклонять сессии отладки с удаленных адресов. Чтобы обойти это, вам нужно либо разрешить удаленные адреса, используя --inspect=0.0.0.0:9229, либо создать некий сетевой туннель между удаленным сервером и вашим компьютером; в следующих разделах я покажу, как легко сделать туннель в kubernetes.

Отправка сигнала SIGUSR1 запущенному процессу

Отправка сигнала процессу очень полезна для проведения спонтанной отладки без перезапуска процесса.

Откройте одно окно терминала, чтобы запустить процесс:

# start Node.js app
$ node dist/index.js
Server listening on port 3000...

Откройте другое окно терминала, чтобы включить режим отладки:

# find PID of Node.js process
$ ps aux | grep "node dist/index.js"
# use the PID to send USR1 signal to the process
$ kill -USR1 [PID]

Выполнив команду kill, вы заметите в первом окне терминала, что отладчик включен, со следующим сообщением:

Debugger listening on ws://127.0.0.1:9229/e2576437-dc86-4d42-9307-e9bf5db34e70
For help, see: https://nodejs.org/en/docs/inspector

Более подробную информацию об использовании kill для отправки сигналов процессам вы можете найти здесь.

Как проводить отладку в Kubernetes?

Обновление liveness и readiness проб 

Во время отладки, если ваш процесс прервется на точке останова, Node.js не сможет ответить на запросы liveness (работоспособности) и readiness (готовности) в kubernetes, и kubernetes решит, что необходимо перезапустить под, завершая вашу сессию отладки. Для предотвращения этого необходимо изменить пробы таким образом, чтобы они допускали более продолжительные остановки процессов:

# removing livenessProbe
$ kubectl patch deploy/example-app --type json -p='[{"op": "remove", "path": "/spec/template/spec/containers/0/livenessProbe"}]'
# removing readinessProbe
$ kubectl patch deploy/example-app --type json -p='[{"op": "remove", "path": "/spec/template/spec/containers/0/readinessProbe"}]'
# adding dummy livenessProbe
$ kubectl patch deploy/example-app -p '{"spec": {"template": {"spec": {"containers": [{"name": "example-app", "livenessProbe": {"initialDelaySeconds": 5, "periodSeconds": 5, "exec": {"command": ["true"]}}}]}}}}'
# adding dummy readinessProbe
$ kubectl patch deploy/example-app -p '{"spec": {"template": {"spec": {"containers": [{"name": "example-app", "readinessProbe": {"initialDelaySeconds": 5, "periodSeconds": 5, "exec": {"command": ["true"]}}}]}}}}'

Уменьшение масштаба (опционально)

Для отладки вам необходимо убедиться, что запрос действительно попадает в тот под, к которому вы подключаетесь. Это может быть непросто, если у вас запущено несколько подов. В примере мы определили 3 реплики в нашем развертывании, а также выполнили автоматическое масштабирование подов с помощью ресурса HorizontalPodAutoscaler. Поэтому нам нужно сообщить kubernetes, чтобы он масштабировал все до 1 пода и сохранил это в таком виде.

# check the number of pods
$ kubectl get pods | grep example-app
NAME                           READY   STATUS        RESTARTS      AGE
example-app-56cf79964d-g92n4   1/1     Running   0             53s
example-app-6b8fb58764-hhk46   1/1     Running   0             14s
example-app-6f79d6cf66-wdxqk   1/1     Running   0             71s
# update HPA to run only one replica
$ kubectl patch hpa/example-app -p '{"spec": {"minReplicas": 1, "maxReplicas": 1}}'
# update deployment to run only one replica
$ kubectl scale --replicas=1 deploy/example-app
# check the number of pods again
$ kubectl get pods | grep example-app
NAME                           READY   STATUS    RESTARTS   AGE
example-app-6b8fb58764-2qlgn   1/1     Running   0          110s

Включение отладчика

Поскольку флаг --inspect не должен быть включен при запуске приложений в продакшене, проще сделать это в специальном порядке, отправив ему сигнал USR1, чем создавать новый под. Но сначала вам нужно найти PID вашего приложения Node.js.

Если в вашем образе docker вы запускаете приложение следующим образом:

CMD [ "node", "dist/index.js" ]

PID вашего приложения будет равен 1. Чтобы выяснить это точнее, в случае неуверенности, используйте следующие команды:

# find Node.js process PID
$ kubectl exec -it deploy/example-app -- /bin/sh -c "ps aux"
# if there is no ps in the docker image, use:
$ kubectl exec -it deploy/example-app -- /bin/sh -c "find /proc -mindepth 2 -maxdepth 2 -name exe -exec ls -lh {} \; 2>/dev/null"
lrwxrwxrwx 1 node node 0 Sep 30 04:11 /proc/1/exe -> /usr/local/bin/node
lrwxrwxrwx 1 node node 0 Sep 30 04:11 /proc/587/exe -> /bin/dash
lrwxrwxrwx 1 node node 0 Sep 30 04:11 /proc/594/exe -> /usr/bin/find

Затем включаем отладчик, в моем случае я использую PID 1.

# enable debugger
kubectl exec -it deploy/example-app -- /bin/sh -c "kill -USR1 1"
# verify that debugger has been enabled
$ kubectl logs deploy/example-app
Server listening on port 3000...
Debugger listening on ws://127.0.0.1:9229/496132b9-ec4b-43db-b103-4e15ba80518a
For help, see: https://nodejs.org/en/docs/inspector

Перенаправление портов

Для отладки с локальной машины на удаленный под, мы будем использовать функцию kubernetes port-forward:

# forward debug port from pod to our machine
$ kubectl port-forward deploy/example-app 9229:9229
Forwarding from 127.0.0.1:9229 -> 9229
Forwarding from [::1]:9229 -> 9229

После включения этой функции мы можем подключить отладчик Node.js к localhost:9229.

Отладка с помощью VSCode

Теперь вы можете подключить отладчик с помощью vscode. Но проблема в том, что вы не можете устанавливать точки останова, как обычно:

Вопрос заключается в том, что vscode не знает, как сопоставить файлы из контейнера с локальным исходным кодом. Чтобы это осуществить, нам нужно обновить конфигурацию присоединения в файле .vscode/launch.json:

 {
      "name": "Attach",
      "port": 9229,
      "request": "attach",
      "skipFiles": ["<node_internals>/**"],
      "type": "pwa-node",
      "localRoot": "${workspaceFolder}",
      "remoteRoot": "/app",
      "sourceMaps": true
    }

Свойства, которые мы добавили, следующие:

  • localRoot сообщает vscode, что является корнем проекта на вашем компьютере

  • remoteRoot сообщает vscode, какой путь в контейнерах соответствует нашему localRoot

  • sourceMaps сообщает vscode учитывать информацию из исходных кодовых карт, для того, чтобы мы действительно могли осуществлять отладку из файлов с исходными кодами.

После установки этого параметра мы сможем выполнять отладку нормально:

Профилирование процессора и снапшоты памяти

Если вам необходимо сделать отладку по утечке памяти или скачкам ЦП, лучшим инструментом для этого является chrome dev tools “Профилирование ЦП и памяти”. После включения дополнительных возможностей отладки можно легко подключиться с помощью chrome к удаленному процессу и выполнить профилирование. Откройте Chrome и перейдите в chrome: // inspect:

Дополнительную информацию о том, как использовать снапшоты памяти для поиска утечек, вы можете найти здесь.

Заключение

Надеюсь, прочитанное показало вам, насколько мощной является удаленная отладка в Node.js, и какой замечательный набор инструментов существует, чтобы вам в этом помочь. Не так уж часто возникают проблемы, требующие отладки удаленного процесса, но если применять методики, подобные этой, они могут предотвратить массу неприятностей.


Материал подготовлен в рамках курса «Инфраструктурная платформа на основе Kubernetes».

Всех желающих приглашаем на бесплатное demo-занятие «Контейнерная оркестрация с плавным переходом к k8s». На занятии поговорим о том, как было до оркестрации, какие проблемы пытались решить, что сейчас: обзор оркестраторов, плавный переход к k8s. На уроке расскажем основные компоненты k8s и их связь, варианты локального развертывания k8s, а также варианты развертывания на виртуальных машинах или baremetall.
>> РЕГИСТРАЦИЯ

Комментарии (1)


  1. muturgan
    28.11.2021 10:26

    А зачем вам экспресс в приложении, которое отдаёт 200 на любой запрос? :)