Удаленная отладка c Delve

10.07.2020 docker debugger delve vscode goland


Удаленная отладка

Ранее мы рассмотрели локальное использование отладчика в Goland IDE. В данной статье рассмотрим, как удаленно отладить программу, запущенную в Docker-контейнере из Visual Studio Code или Goland IDE.

При локальной отладке процессом полностью управляет IDE — компилирует программу и подключается к ней.

Иногда может потребоваться удаленная отладка — приложение запускается отдельно от IDE. Предварительная подготовка программы в данном случае ложится на плечи разработчика, а со стороны IDE необходимо подключиться к запущенной удаленно программе.

Почему это может потребоваться? Вероятно, это не слишком частый случай, но причины могут быть следующие:

  • программа не может быть запущена или протестирована локально, только в специальном тестовом окружении;
  • вы отлаживаете сложно уловимый баг на удаленном окружении, например во время integration testing;

Перед тем, как приниматься за удаленную отладку, помните, что проще всего поправить баг на более ранних стадиях — локально. Локально вы можете запустить программу мгновенно с помощью go run или delve debug или любой IDE; при работе с удаленным окружением придется подготавливать программу, отладчик, ожидать загрузки программы на удаленном окружении - все это будет отнимать ваше время, а значит, замедлять поиск ошибки.

Delve

Отладчик, используемый "под капотом" Goland IDE или Visual Studio Code, — Delve.

Отладка в IDE с помощью Delve всегда работает так:

  • Delve запускается как серверное приложение, слушает подключения на определенном порту.
  • Delve запускает нашу программу (скомпилированную ранее или использует исходники на GO).
  • При подключении отладчика из Goland IDE или Visual Studio Code, Delve принимает данные об установленных брейкпоинтах.
  • При наступлении брейкпоинта, Delve останавливает программу, сообщает подключенному клиенту состояние переменных и другие отладочные данные.

Delve — утилита командной строки, полный список параметров которой описан здесь. Delve умеет как самостоятельно компилировать программу, так и запускать ранее скомпилированную программу (в зависимости от параметров).
Delve также имеет интерфейс отладки из командной строки (для отладки без IDE), однако данным функционалом мы пользоваться не будем.

Итак, нам необходимо запустить Delve c нашей программой и обеспечить удаленное подключение IDE к нему.

Исходники

Для демонстрации удаленной отладки я подготовил репозиторий Github, который содержит весь исходный код демонстрируемой в статье программы.

Docker

Docker-контейнеры — популярный способ деплоя программ. Подготовка минимально возможного контейнера Docker, а также деплой c помощью docker swarm рассматривались ранее. В нашем случае подключение к программе, запущенной в Docker-контейнере, послужит демонстрацией удаленной отладки.

Подготовим Dockerfile с Delve и нашей программой:

FROM rhaps1071/golang-1.14-alpine-git AS build
WORKDIR /
COPY . .
RUN CGO_ENABLED=0 go get -ldflags "-s -w -extldflags '-static'" github.com/go-delve/delve/cmd/dlv
RUN CGO_ENABLED=0 go build -gcflags "all=-N -l" -o ./app

FROM scratch
COPY --from=build /go/bin/dlv /dlv
COPY --from=build /app /app
ENTRYPOINT [ "/dlv" ]

Здесь используется двухэтапная сборка и статическая компиляция бинарных файлов для использования затем в Docker образе FROM scratch с минимальным размером.
В нашем результирующем образе мы сохраняем бинарные файлы двух программ — /go/bin/dlv — Delve; /app — наша программа.

Флаги сборки GO поддерживаются сразу многими утилитами - build, clean, get, install, list, run, test. Благодаря данной возможности, бинарник Delve, получаемый через go get, также собирается статически. По умолчанию, после сборки в окружении Alpine Linux, он зависит от двух библиотек. Проверить это можно через консольную команду ldd:

ldd /go/bin/dlv 
/lib/ld-musl-x86_64.so.1 (0x7f761f8b7000)
libc.musl-x86_64.so.1 => /lib/ld-musl-x86_64.so.1 (0x7f761f8b7000)

Флаги -gcflags "all=-N -l", используемые для компиляции нашей программы, необходимы для работы Delve с ней.

Для сборки контейнера можно использовать команду docker build -f ./docker/debug/Dockerfile -t debug ., которая также реализована как команда Makefile с именем docker-build-debug, так что можно выполнить make docker-build-debug.

Запуск контейнера

Запускаем собранный контейнер с помощью docker-compose, что также как и сборка доступно через Makefilemake docker-run-debug.

Содержимое файла docker-compose.yml:

version: "3"

services:
  debug:
    build:
      dockerfile: docker/debug/Dockerfile
      context: ../../
    ports:
      — 2345:2345
	command: "--listen=:2345 --headless=true --log=true --log-output=debugger,debuglineerr,gdbwire,lldbout,rpc --accept-multiclient --api-version=2 exec ./app"

Так как ранее мы установили ENTRYPOINT в образе как /dlv, то в параметре command мы передаем только параметры для Delve. Переданные параметры направлены на многократную удаленную отладку и подробное логгирование.

Вызываем docker-compose -f ./docker/debug/docker-compose.yml up и изучаем логи контейнера:

Starting debug_debug_1 ... done
Attaching to debug_debug_1
debug_1  | API server listening at: [::]:2345
debug_1  | 2020-07-10T13:36:06Z debug layer=rpc API server pid = 1
debug_1  | 2020-07-10T13:36:06Z info layer=debugger launching process with args: [./app]

Если в логах видим то, что сервер запущен на порту 2345, то все хорошо. Давайте теперь подключимся к нему из IDE.

Visual Studio Code

В IDE мы должны открыть проект с исходниками отлаживаемой програмы.

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

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Attach",
            "type": "go",
            "request": "attach",
            "mode": "remote",
            "remotePath": "",
            "port":2345,
            "host":"127.0.0.1",
            "showLog": true,
            "trace": "log",
            "logOutput": "rpc"
        }
    ]
}
  • Параметр "request": "attach" — позволяет нашему IDE именно подключаться удаленно, а не стартовать новую сессию отладки;
  • port,host — порт и хост запущенного удаленно Delve. Так как c помощью docker-compose мы запустили Docker-контейнер локально и пробросили порт 2345 на наш локальный компьютер, то подключаемся к 127.0.0.1 или localhost.
  • remotePath — один из критически важных параметров, влияющий на то, будут ли корректно ставиться breakpoints. Это путь к папке с исходниками нашей программы при компиляции. Мы компилировали нашу программу с помощью Dockerfile, находясь в корневой директории ( WORKDIR / ). Следовательно, наша директория компиляции — корневая, поэтому оставляем поле remotePath пустым.

Итак, перед действиями в IDE, проверим, что отлаживаемый нами контейнер работает.

Далее средствами VSCode запускаем задачу отладки "Attach":

Мы можем ставить breakpoints и они должны оставаться видимыми в IDE, при этом в debug console отображается наше сетевое взаимодействие с Delve в контейнере и в нем не должно быть ошибок.

Goland IDE

При удаленной отладке у нас должен быть запущен наш Docker-контейнер с Delve и нашей программой, а в IDE открыт проект с исходниками программы.

Для данной IDE настройка проходит визуально. Важно — необходимо включить GO modules integration в настройках IDE:

Далее — главное меню — Run — Edit configurations, добавляем новую конфигурации откладки, выбираем "Go Remote":

Аналогично VSCode, после подключения отладчиком, ваши breakpoints должны ставится. При возникновении проблем, обратите внимание на консоль контейнера, на сообщения о папках, например, could not find file somedir/main.go.
В этом случае необходимо включить GO modules integration.

Другие статьи