From 3b1f6ffacff6ce3b255950bb3c03b9b305019d1a Mon Sep 17 00:00:00 2001 From: Chris Campo Date: Fri, 9 Dec 2022 20:56:54 -0500 Subject: [PATCH] Initial implementation --- .gitignore | 3 +- .run/remote-debug.run.xml | 6 ++++ Dockerfile | 32 +++++++++++++++++ README.md | 75 ++++++++++++++++++++++++++++++++++++++- go.mod | 3 ++ go.sum | 0 main.go | 21 +++++++++++ run.sh | 22 ++++++++++++ 8 files changed, 159 insertions(+), 3 deletions(-) create mode 100644 .run/remote-debug.run.xml create mode 100644 Dockerfile create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100755 run.sh diff --git a/.gitignore b/.gitignore index 66fd13c..bb001c4 100644 --- a/.gitignore +++ b/.gitignore @@ -11,5 +11,4 @@ # Output of the go coverage tool, specifically when used with LiteIDE *.out -# Dependency directories (remove the comment below to include it) -# vendor/ +.idea diff --git a/.run/remote-debug.run.xml b/.run/remote-debug.run.xml new file mode 100644 index 0000000..c71c4c3 --- /dev/null +++ b/.run/remote-debug.run.xml @@ -0,0 +1,6 @@ + + + + \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b5d903e --- /dev/null +++ b/Dockerfile @@ -0,0 +1,32 @@ +FROM golang:alpine3.17 AS build + +WORKDIR /app + +# Turning off Cgo is required to install Delve on Alpine. +ENV CGO_ENABLED=0 + +# Download and install the Delve debugger. +RUN go install github.com/go-delve/delve/cmd/dlv@latest + +COPY go.mod . +COPY go.sum . + +RUN go mod download + +# You'll want to copy all your .go files here in a bigger project. +COPY main.go . + +# Disable inlining and optimizations that can interfere with debugging. +RUN go build -gcflags "all=-N -l" -o /main main.go + +FROM alpine:3.17 + +WORKDIR / + +COPY --from=build /go/bin/dlv /bin/dlv +COPY --from=build /main /bin/app +COPY run.sh /bin/run.sh + +EXPOSE 8080 + +ENTRYPOINT ["/bin/run.sh"] diff --git a/README.md b/README.md index e25a41d..855712d 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,75 @@ # go-docker-alpine-remote-debug -Example how to enable remote debugging using Delve for a Dockerized Go application. + +A simple example on how to enable remote debugging using [Delve](https://github.com/go-delve/delve) +for a Go application running in an Alpine Linux Docker container. + +## Background + +This example contains a simple Go web application which returns `Hello world!` +on all HTTP requests to the endpoint `/hello`. The application is packaged into +a Docker image which includes the Delve debugger and can accept remote debugging +sessions on a user specified port. + +Explore the code to see how this is accomplished :). + +## Build and Run + +Build the application and Docker image. This example utilizes a multi-stage +Docker build to download and install Delve, and compile the application: +```sh +$ docker build ./ -t debug-example:latest -f Dockerfile + +[+] Building 9.5s (18/18) FINISHED +... +```` + +Run a container: +```sh +$ docker run \ + --rm \ + -p 8080:8080 \ + -p 40000:40000 \ + -e REMOTE_DEBUG_PORT=40000 \ + -e REMOTE_DEBUG_PAUSE_ON_START=true \ + debug-example:latest + +Starting application with remote debugging on port 40000 +Process execution will be paused until a debug session is attached +Executing command: /bin/dlv --listen=:40000 --headless=true --log --api-version=2 --accept-multiclient exec /bin/app -- +API server listening at: [::]:40000 +... +``` + +Once the container is running, you can establish a remote debugging session for +the application on port `40000` (feel free to change the port - there's nothing +special about `40000`, I just like that number). While you can always use the +[Delve command line client](https://github.com/go-delve/delve/blob/master/Documentation/cli/README.md) +to debug, I recommend using your IDE for this: + +* [GoLand / IDEA](https://www.jetbrains.com/help/go/attach-to-running-go-processes-with-debugger.html) +* [Visual Studio Code](https://github.com/golang/vscode-go/blob/master/docs/debugging.md) + +Note that because we set `REMOTE_DEBUG_PAUSE_ON_START`, the `main` function will +not be executed until a debug session is connected. This is particularly useful +if you want to debug an application from its first line of execution, however if +you don't need that, feel free to omit that environment variable and the +application will start normally. + +Finally, set some breakpoints and make a request: +```sh +$ curl http://localhost:8080/hello + +Hello world! +``` + +## GoLand/IDEA Run/Debug Configuration + +The [`.run`](.run) directory contains a +[run/debug configuration](https://www.jetbrains.com/help/go/run-debug-configuration.html) +which you use for this example. + +## References + +* https://github.com/go-delve/delve/blob/master/Documentation/faq.md#how-do-i-use-delve-with-docker +* https://github.com/go-delve/delve/blob/master/Documentation/usage/dlv_exec.md +* https://www.jetbrains.com/help/go/attach-to-running-go-processes-with-debugger.html#attach-to-a-process-on-a-remote-machine diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..5a721be --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/ccampo133/go-docker-alpine-remote-debug + +go 1.19 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/main.go b/main.go new file mode 100644 index 0000000..9556f49 --- /dev/null +++ b/main.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + "net/http" +) + +func main() { + // This is a good spot for a breakpoint :) + http.HandleFunc("/hello", hello) + if err := http.ListenAndServe(":8080", nil); err != nil { + panic(err) + } +} + +func hello(w http.ResponseWriter, req *http.Request) { + // Try setting a breakpoint here too :) + if _, err := fmt.Fprintf(w, "Hello world!\n"); err != nil { + panic(err) + } +} diff --git a/run.sh b/run.sh new file mode 100755 index 0000000..89d354e --- /dev/null +++ b/run.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env sh + +# Ensure this script is executable! + +cmd="/bin/app" +if [ "$REMOTE_DEBUG_PORT" ]; then + echo "Starting application with remote debugging on port $REMOTE_DEBUG_PORT" + dlvFlags="--listen=:$REMOTE_DEBUG_PORT --headless=true --log --api-version=2 --accept-multiclient" + execFlags="" + # Simply setting this environment variable is enough to force the debugger to + # pause on start --- we don't care about the value. + if [ "$REMOTE_DEBUG_PAUSE_ON_START" ]; then + echo "Process execution will be paused until a debug session is attached" + else + execFlags="$execFlags --continue" + fi + cmd="/bin/dlv $dlvFlags exec $cmd $execFlags -- " +fi + +echo "Executing command: $cmd $*" + +exec $cmd "$@"