GoKubernetesTiltDebuggingDX

Debugging Go applications in TILT

Deveez Team Platform Engineering
6 min read May 25, 2024

TL;DR (Executive Summary)

  • Docker Compose falls short for complex K8s microservices.
  • Tilt offers a powerful UI and live updates for K8s dev environments.
  • Use `delve` and `tilt-enhancements` to pause execution and debug from line one.

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!

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:

# Tiltfile
# ...
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, 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()
    startServer()
}

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

TILT_AWAIT_DEBUGGER_ENABLED=true

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.

D
Written by
Deveez Team
Platform Engineering

Ready to accelerate your engineering velocity?