Remote debugging v Golangu

V Golangu dělám už druhým rokem a je to láska. Je to první jazyk, po kterém sáhnu, když potřebuju udělat nějaký PoC. Nejčastěji používám GoLand IDE, občas píšu ve Vimu s vim-go pluginem. Zkoušel jsem i VS Code, ale nějak jsme se neskamarádili.

Vzhledem k tomu, že poslední tři roky píšu cloudovou infrastrukturu, jsou moje aplikace relativně jednoduché micro-servisy. A tak jsem si prozatím vystačil jen s lokálním debuggingem přímo z IDE. Ale na každého jednou dojde…

Pokud tomu rozumím, stačí logování a testy. Debugguju, jen když nechápu, jak to funguje. ~ SoftWare Samuraj

Tak co se to stalo, že jsem najednou potřeboval remote debugging? Tož, to bylo tak…

Měl jsem napsanou aplikaci, krásně otestovanou, lokálně běhala jak víno. Pak jsem ji vrazil do Dockeru a najednou nefungovala. Marně jsem měnil parametry, marně jsem přidával logovací hlášky, marně jsem pročítal zdrojáky třetích stran. Musel jsem si přiznat, že nic jiného, než remote debugging mi nepomůže.

Čili otázka zní: jak debuggovat Golang aplikaci běžící v Dockeru?

Golang debugger

Golang debugger se jmenuje Delve a žije na GitHubu. Instalace je přímočará a jakmile ji dokončímě, měli bychom vidět:

$ go get github.com/go-delve/delve/cmd/dlv
$ dlv version
Delve Debugger
Version: 1.5.1
Build: $Id: bca418ea7ae2a4dcda985e623625da727d4525b5 $

Jelikož se chci věnovat pouze remote debuggingu, tak pominu ostatní finesy, které Delve nabízí a zaměřím se pouze na dva následující příkazy: dlv attach & dlv exec.

dlv attach

Příkaz dlv attach způsobí, že se debugger připojí k běžícímu procesu a začne novou debug session. Po skončení session je možné proces buď nechat běžet, nebo ukončit. Syntaxe je jednoduchá:

dlv attach <pid>

dlv exec

Příkaz dlv exec spustí danou binárku a nastartuje novou debug session. Při exitování se opět můžeme rozhodnout, jestli proces killnout, nebo nechat žít. Syntaxe taktéž jednoduchá:

dlv exec <path-to-binary>

Options pro remote debugging

Když jsem před chvílí říkal, že syntaxe je jednoduchá, tak jsem trochu lhal. Aby se ty dva předešlé příkazy daly použít pro remote debugging, je potřeba je trochu vylepšit pár nutnými, či vhodnými přepínači. Jsou to:

  • --listen=:2345 port, ev. adresa, na kterém naslouchá debug server.
  • --headless=true spustí debug server v headless modu.
  • --api-version=2 nutné, aby si s debug serverem rozuměl Goland (IDEA).
  • --accept-multiclient debug server akceptuje více klientských spojení.

Nastavení Goland IDE

Konfigurace v Golandu (identické platí pro IntelliJ IDEA) je jednoduchoučká: stačí nastavit adresu host a port na kterém běží debug server.

Jelikož my budeme debugovat proces běžící v (lokálním) Dockeru, můžeme nechat localhost.

Nastavení Go remote debuggingu v Goland IDE (taktéž IntellJ IDEA)

Nastavení Go remote debuggingu v Goland IDE (taktéž IntellJ IDEA)

Příprava Docker image

Nachystat si Docker image pro remote debugging také není nijak složité, stačí dvě věci:

  1. Zkopírovat binárku debuggeru dlv do Docker image.
  2. Spustit debugovanou aplikaci pomocí dlv exec.

Spouštěcí příkaz bude vypadat takto:

dlv --listen=:2345 --headless=true --api-version=2 \
    --accept-multiclient exec ./remote-debug

Ukázkový Dockerfile:

Remote debugging in Action

Nyní již máme všechno nachystáno a můžeme začít debuggovat:

1) Spustit Docker image

Docker image se spustí běžným příkazem — Docker o probíhajícím debuggování nic neví. Debuggovaná aplikace čeká na připojení debuggeru z IDE. Na výpisu vidíme: API server listening at: [::]:2345.

container="golang-remote-debug"
image="sw-samuraj/${container}"

docker run --rm --net=host --name "${container}" "${image}"
Logování aplikace běžící v Dockeru. V úvodu je vidět, jak debug server naslouchá.

Logování aplikace běžící v Dockeru. V úvodu je vidět, jak debug server naslouchá.

2) Nastavit breakpoint v IDE a spustit debug session

Debuggovat umíte, ne? A jak se to dělá ve vašem IDE snad taky víte, ne? 😉

3) Spuštění kódu s breakpointem

V ukázkovém projektu uvedeném na konci článku, běží v Dockeru jednoduchá webová služba poslouchající na portu 4040. Stačí ji provolat pomocí curl:

curl -v localhost:4040
Provolání debuggované aplikace pomocí curl

Provolání debuggované aplikace pomocí curl

4) Klasický debugging

Teď by se mělo ozvat naše IDE a je čas na klasický debugging: step over, step into, atd.

Debuggování v Goland IDE

Debuggování v Goland IDE

5) Ukončení debuggingu

Našli jsme chybu? Paráda! 🙌

V IDE ukončíme debug session a rozhodneme, jestli ukončíme i debuggovaný process v Dockeru nebo ho nacháme běžet dál.

Ukázkový projekt

Jednoduchý projekt na otestování popsaného postupu lze nají na GitHubu:

Pokud jsem nic neopomněl, mělo by stačit pustit následující příklady a nastavit ve vašem IDE remote debugging session.

./build-docker.sh
./run-docker.sh