deveez

Debugging Go applications in TILT

Mario Martin
By Mario Martin ·

Man working on a computer

One of the biggest challenges when working with a microservice system is spinning up all of your services locally during development. In recent years, Docker Compose has been widely used, simplifying the management of your entire application stack and making it easy to manage services, networks, and volumes.

However, in some scenarios, it may not be enough, and this is where TILT can help us. Using TILT, our application runs on a local Kubernetes cluster with the specific peculiarities of this scenario. In this post, we will see how to efficiently debug a Go application that runs using the package tilt-enhancements.

When docker-compose is not enough

Docker Compose is a great tool for managing multi-container applications in development, but it falls short when your environment starts to mimic production setups, especially those involving Kubernetes. If your application consists of numerous microservices that need to interact within a Kubernetes cluster, or if you require real-time feedback and streamlined debugging, TILT becomes the better choice.

Unlike Docker Compose, TILT is specifically designed to handle the complexities of Kubernetes-based microservices, offering features like automated rebuilds, redeployments, and an integrated UI for monitoring and debugging. This makes it ideal for developers who need a more robust, production-like environment during development, where Docker Compose's capabilities are limited. And if you're looking for a fancy UI for your microservices, TILT has you covered!

For more insights, please check the official doc

TILT for GO Applications

Imagine you have a API Rest implemented in Go, and you want to develop it using TILT. First, you need a Dockerfile to build the image (in addition to Kubernetes manifests to deploy the application and other resources):

FROM golang:1.21
WORKDIR /usr/src/app
COPY . .
RUN go build -gcflags="all=-N -l" -o example-go cmd/main.go
EXPOSE 8080
ENTRYPOINT ["/usr/src/app/example-go"]

Now you can run the application using TILT with the following Tiltfile

docker_build('example-go', '.', dockerfile='./Dockerfile')
k8s_yaml('deployments/kubernetes.yaml')
k8s_resource('example-go', port_forwards=8080)

This is a basic configuration that is enough for this section, but you can take advantage of more features such as compilation optimizations and live updates on changes. More info here

With this configuration you can run your application in local k8s cluster using TILT but... who doesn't need to debug something while developing?

How to debug a Go application running with TILT

Since the application is not running on your host you need to add delve to your Dockerfile and expose a port to allow remote debugging connections.

FROM golang:1.21
WORKDIR /usr/src/app
COPY . .
+ # add delve
+ RUN go install github.com/go-delve/delve/cmd/dlv@v1.21.2
RUN go build -gcflags="all=-N -l" -o example-go cmd/main.go
EXPOSE 8080
+ # allow remote debugging
+ EXPOSE 40000
ENTRYPOINT ["/usr/src/app/example-go"]

After that, you should tell Tilt that execute the binary with delve and export the port to be forwarded as well to the cluster. Modify your Tiltfile to do so:

- docker_build('example-go', '.', dockerfile='./Dockerfile')
+ docker_build_with_restart(
+  'example-go',
+  '.',
+  entrypoint='GOPATH/bin/dlv listen=40000 api-version=2 headless=true exec /usr/src/app/example-go continue accept-multiclient',
+  dockerfile='./Dockerfile'
+)
 
k8s_yaml('deployments/kubernetes.yaml')
- k8s_resource('example-go', port_forwards=8080)
+ k8s_resource('example-go', port_forwards=[8080, 40000])

And finally you only need to configure your IDE - or whatever tool you use - to connect to the remote debugger. For example the following configuration for VSCode:

{
    "version": "0.2.0",
    "configurations": [
        {
            "name": "Attach to Delve",
            "type": "go",
            "request": "attach",
            "mode": "remote",
            "showLog": true,
            "trace": "log",
            "port": 40000,
            "host": "127.0.0.1",
            "debugAdapter": "dlv-dap",
            "substitutePath": [
                {
                    "from": "${workspaceFolder}",
                    "to": "/usr/src/app"
                }
            ]
        }
    ]
}

Voilà! Now you can debug the remote application just like you would debug a local application... right? Well, not exactly. The debugger isn't ready to connect until the container and the application have fully started. So, how do you know when to start debugging? Do you just take a chance each time and hope you get lucky?

For example, if you are developing a REST API, you can debug the endpoints as they are called due to the nature of the application. However, if you need to debug the application from the moment it starts, you need to pause the process until a specific signal is received.

How to debug a Go application on TILT from startup

Here is a simple way to stop the application until the debugger is connected using the await package from tilt-enhancements repository:

package main
 
import (
  [...]
 
	_ "github.com/deveeztech/tilt-enhancements/pkg/await"
)
 
func main() {
	
  initConfigurations()
  initClients()
  addHandlers()
 
  starServer()
 
}

By default the await package is disabled, to enable it you should set the environment variable TILT_AWAIT_DEBUGGER_ENABLED=true as environment variable.

Now you have the control since the first line of the main function 🐛

Conclusion

TILT provides a powerful environment for running and managing applications within a local Kubernetes cluster but it can make debugging complex and challenging. The await package from the tilt-enhancements repository offers an effective solution by allowing developers to pause the application startup until the debugger is connected. This feature provides precise control over the debugging process from the very first line of code, making it easier to troubleshoot and manage complex microservices environments.

Remember to keep the local environment for development as simple as possible as long as it works for everyone in the team and meets the organization's standard with the minimum feedback loop


At Deveez, we create healthy environments for developers, empowering them to grow business.