Ein Debugger ist bei der Fehlersuche in modernen Programmiersprachen wie Go/Golang nicht mehr wegzudenken. Er ermöglicht es ein Programm Schritt für Schritt auszuführen und die jeweiligen Ergebnisse zu untersuchen. Unter Umständen kann auch der Ablauf oder das Verhalten einer Anwendung während einer Debugging-Session durch Veränderung von einzelnen Werten im Speicher verändert werden.
In diesem Artikel wollen wir uns den Debugger Delve für Golang anschauen und damit eine Anwendung innerhalb eines Docker-Containers debuggen. Im Beispiel verwenden wir einen einfachen Microservice.
Das gezeigte Vorgehen funktioniert ebenfalls für Remote-Debugging-Sitzungen oder lokales Debugging, bei denen die Anwendung nicht in einem Docker-Container ausgeführt wird.
Delve is a debugger for the Go programming language. The goal of the project is to provide a simple, full featured debugging tool for Go. Delve should be easy to invoke and easy to use. Chances are if you’re using a debugger, things aren’t going your way. With that in mind, Delve should stay out of your way as much as possible. - Delve Website
Ich werde im Artikel die Verwendung innerhalb von VSCode und IntelliJ bzw GoLand zeigen. Der Einsatz innerhalb einer anderen IDE funktioniert analog.
Prinzipiell spielt es keine Rolle ob Sie eine Go-Anwendung direkt innerhalb einer Entwicklungsumgebung debuggen oder sich zu einer entfernten Maschine verbinden, um dort ihre Anwendungen zu untersuchen. Die Kommunikationsdetails unterscheiden sich nur minimal. Bei einer lokalen Debugging-Sitzung wird Ihre Entwicklungsumgebung Sie maximal unterstützen und Sie werden im Idealfall nichts von einer Kommunikation zwischen den Bestandteilen mitbekommen.
Wie erwähnt, ist in beiden Fällen der Ablauf einer Debugging Sitzung über Delve gleich (siehe Abbildung). Die Anwendung wird durch Delve gestartet und kann dann normal eingesetzt werden. Ein Delve-Frontend, z. B. ihre IDE, kann sich nach dem Start mit der laufenden Delve-Instanz verbinden und die eigentliche Anwendung untersuchen.
Ob Delve die Anwendung selbst übersetzt, oder ob ein bereits erstelltes Go-Binary verwendet werden soll, entscheiden Sie beim Start. Wenn Sie die Debugging-Sitzung über eine Remote-Verbindung durchführen, geben Sie die entsprechenden Kommunikationsparameter dafür beim Start von Delve an.
Im gezeigten Beispiel aus der Grafik handelte es sich um einen einfachen Microservice, der über Port 8080 erreichbar ist und dort seine Anwendungsdaten ausliefert. Die Anwendung wird nicht direkt sondern über Delve gestartet, das den Port 4040, über den sich eine Debugger-GUI (im Bild eine IDE) verbinden kann, öffnet.
Die Installation von Delve ist gewohnt einfach. Seit Version 1.16 ist folgendes Kommando für die Installation der neusten Delve-Version ausreichend:
go install github.com/go-delve/delve/cmd/dlv@latest
Zu beachten ist allerdings, dass nicht alle Kombinationen aus Betriebssystem und Rechnerarchitektur unterstützt werden. Die folgende Liste zeigt alle bisher möglichen Kombinationen:
Falls eine Kombination noch nicht angeboten wird, erhalten Sie während der Installation folgende Fehlermeldung und Sie können in den entsprechenden Bugtickts nach Informationen suchen (32bit ARM support, PowerPC support und OpenBSD).
found packages native (proc.go) and your_operating_system_and_architecture_combination_is_not_supported_by_delve (support_sentinel.go) in /home/pi/go/src/github.com/go-delve/delve/pkg/proc/native
Für macOS finden Sie auf den Wiki-Seiten des Delve Projektes weitere Überlegungen und Tipps für eine weiterführende Installation.
Ist die Installation geglückt und befindet sich Ihr $GOBIN
-Verzeichnis im Ausführungspfad, können Sie delve auf der Kommandozeile mittels dlv
starten:
$ dlv
Delve is a source level debugger for Go programs.
Delve enables you to interact with your program by controlling the execution of the process,
evaluating variables, and providing information of thread / goroutine state, CPU register state and more.
...
Die Beispielanwendung für diesen Artikel ist recht einfach aufgebaut. Es handelt sich um einen Microservice, der einen fixen String als Rückgabewert über HTTP liefert. Innerhalb des Projektes ist zusätzlich ein Dockerfile
vorhanden, mit dem der Service übersetzt und gestartet werden kann.
Folgende Dateien befinden sich innerhalb des Projektes:
.
├── cmd
│ └── main.go
├── Dockerfile
├── go.mod
├── internal
│ └── SomethingImportant.go
└── startup.sh
Die main.go
Datei wird nur dazu benutzt den eigentlichen Service innerhalb der Datei SomethingImportant.go
zu starten. Die Implementierung ist absichtlich extrem simple gehalten. Hier der Inhalt der Datei SomethingImportant.go
:
package internal
import (
"fmt"
"net/http"
)
func sayHello(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello world!")
}
func StartServer() {
http.HandleFunc("/", sayHello)
fmt.Println(http.ListenAndServe(":8080", nil))
}
Innerhalb der main.go
-Datei im cmd
-Verzeichnis wird die Anwendung, wie gesagt, ausschließlich gestartet:
package main
import "github.com/SourceFellows/samples/delve/internal"
func main() {
internal.StartServer()
}
Mit Hilfe des Dockerfile
s wird der Service übersetzt und kann ausgeführt werden. Den Start übernimmt das Skript startup.sh
, das wir uns gleich noch etwas genauer anschauen werden. Hier werden sich ein paar Delve-spezifische Werte wiederfinden.
Zum Debuggen der Anwendung innerhalb des Docker-Containers muss innerhalb des Containers Delve installiert sein. Mit dem Basis-Image golang:latest
können Sie dies recht leicht mit dem Kommando go install github.com/go-delve/delve/cmd/dlv@latest
erreichen und die neuste Delve Version installieren. Der restliche “Bauvorgang” ist analog zu einem normalen Imagebau (Kompilierflags schauen wir uns gleich noch gesondert an).
from golang:latest
RUN go install github.com/go-delve/delve/cmd/dlv@latest
RUN mkdir -p /app/cmd
WORKDIR /app/
COPY . .
#Anwendung kompilieren
RUN go build -gcflags="-N -l" cmd/main.go
#Port auf den sich der Debugger verbinden kann
#Angabe in 'startup.sh'
EXPOSE 4040
#Anwendung starten
RUN chmod +x /app/startup.sh
ENTRYPOINT [ "/app/startup.sh" ]
Den Docker-Container für das Beispiel können Sie über folgendes Kommando bauen (testing
bezeichnet den Image-Namen):
docker build -t testing .
… und schließlich Starten.
docker run -p 8080:8080 -p 4040:4040 testing
Die Portangaben
8080
und4040
im Kommando beziehen sich hierbei einmal auf die Anwendung selbst (Port 8080) und einmal auf den Port, über den die Delve-Debugging-Sitzung durchgeführt wird (Port 4040). Woher der Port4040
kommt, schauen wir uns gleich an.
Folgende Punkte fallen, im Unterschied zu einem normalen Go-Microservice, auf und wurden entweder bereits kurz angesprochen oder wurden nur ohne Erklärung verwendet.
In der Beispielanwendung wurden innerhalb des Dockerfiles die Kompilerflags -N -l
mitgegeben. Mit Ihnen werden Optimierungen und das sogenannte Inlining von Code ausgeschaltet damit der Sourcecode besser mit dem Debugger zugeordnet werden kann.
-gcflags="-N -l"
Übrigens: Alle möglichen Kompilerflags für Go lassen sich mit
go tool compile -help
anzeigen.
Gestartet wird die Anwendung innerhalb des Docker-Containers über das Skript startup.sh
. Hier werden Delve wichtige Startoptionen mitgegeben und so z. B. die Debugging-Kommunikation über Port 4040 konfiguriert. Das Skript zeigt den kompletten Aufruf von Delve:
#!/bin/bash
echo "You have to kill this container because delve ignores SIGINT in headless mode"
dlv exec --api-version 2 --headless --log --listen :4040 /app/main
Folgende Parameter werden mitgegeben:
--api-version 2
Beim Aufruf von Delve kann man definieren mit welcher API-Version kommuniziert werden soll. Frontend und Delve müssen hierbei auf die selbe Version konfiguriert sein.
--headless
Standardmäßig startet Delve direkt in einem interaktiven Modus. Dann kann der Debugger auf Kommandozeile gesteuert werden. Da wir uns remote verbinden wollen, starten wir Delve mit dieser Option und “unterdrücken” das eingebaute Kommandozeilen-Frontend.
--log
Steuert eine erweiterte Ausgabe
--listen :4040
Angabe des Binding-Ports des Debuggers
Haben Sie die Anwendung mittels Docker gestartet können Sie sich mit IntelliJ bzw. GoLand von Jetbrains mit der Anwendung verbinden. Über das Menü “Run / Edit Configurations…” können Sie recht einfach eine neue Debugging Konfiguration erstellen (siehe Screenshot) und verwenden.
Achten Sie auf die Angabe des korrekten Ports - in unserem Beispiel
4040
.
Hier werden Ihnen nocheinmal die Einstellungen für Delve gezeigt, die Sie beim Start der Anwendung mitgeben müssen (diese sind ja bereits im Shell-Skript vorhanden).
Jetzt können Sie wie gewohnt debuggen (HTTP aufruf nicht vergessen ;-) - http://localhost:8080). Innerhalb des Docker-Containers! Eine lokale Konfiguration funktioniert übrigens analog.
In Visual Studio Code (VSCode) ist der Ablauf ähnlich. Zuerst muss eine Debugging-Konfiguration angelegt werden, danach kann man sich mit dem Container verbinden.
Die Einrichtung ist nicht ganz so komfortable, da man direkt mit der JSON-Konfigurationsdatei in Berührung kommt. Über die Command-Palette kann eine neue launch.json
Konfiguration erstellt werden. Hier wählen Sie unter “Umgeubung” -> Go und beim Punkt “Debug Configuration” -> “Connect to server” (siehe Screenshot).
VSCode erstellt Ihnen nun eine Debugging Konfiguration, mit der Sie bereits den Service debuggen können:
{
"version": "0.2.0",
"configurations": [
{
"name": "Connect to server",
"type": "go",
"request": "attach",
"mode": "remote",
"port": 4040,
"host": "127.0.0.1",
"apiVersion": 2
}
]
}
Danach muss man auch hier nur noch auf “Play” drücken und kann den Service Schritt für Schritt überwachen (HTTP aufruf nicht vergessen ;-) - http://localhost:8080).
Viel Spaß beim Debuggen!
Das komplette Beispiel kann auch in GitHub gefunden werden.
12.05.2021
Der Author auf LinkedIn: Kristian Köhler und Mastodon: @kkoehler@mastodontech.de
Der praktische Soforteinstieg für Developer und Softwarearchitekten, die direkt mit Go produktiv werden wollen.
zur Buchseite beim Rheinwerk Verlag Rheinwerk Computing, ISBN 978-3-8362-7559-0 (als PDF, EPUB, MOBI und Papier)
Source Fellows GmbH
Lerchenstraße 31
72762 Reutlingen
Telefon: (0049) 07121 6969 802
E-Mail: info@source-fellows.com