How to Debug Golang app running in K8S (Okteto) using Neovim
chama-chomo
Posted on October 29, 2022
Introduction
Since it is fairly complicated to achieve state described in a title, I've decided to create this post and share my findings. Without too much splattering around, let's right jump to it.
Please note, I'm not going to describe all technologies I use in my workflow. Since this is quite specific topic, I assume everyone to be on the same wave.
What we need to make remote debugging in Neovim real:
- delve debugger installed in Okteto's container (this is where our application will be running
- Neovim >= 0.8.0
- set of DAP plugins to be installed for Neovim
Kubernetes (Okteto) part
In order to replace original container with our development container, let's create a manifest for Okteto first.
In my case it looks like this (your may vary, of course):
name: t-s-generator
build:
t-s-generator:
image: golang:1.18-alpine3.14
dockerfile: ./Dockerfile
deploy:
- <replace by the command you use to deploy your app in k8s>
dev:
t-s-generator:
sync:
rescanInterval: 15
folders:
- t-s-generator:/t-s-generator
forward:
- 8080:8080
- 38697:38697 # this port definition is important, it's port we are going to use for our debugger running inside Okteto and we need to have it accessible from our local PC, so our DAP client can connect to it
Once we have Okteto environment set up for our GO application and we're able to execute okteto up
successfully, we need to prepare application's binary, so it can be inspected by the debugger.
This is done by compiling our code with some specific parameters. I do it by running below command on my local PC, where my go.mod
can be normally found.
GOOS=linux GOARCH=amd64 go build -gcflags=all="-N -l" .
As you can see, I'm creating a binary that can run inside K8S container, which is Alpine Linux in my case, that's why all these Linux related flags. However, those gcflags
are important at the moment.
Since we're syncing our local folder with code with container's remote folder, this binary should appear also in our dev container inside K8S, once created.
Okteto set up, binary ready, the next step is to install and run our debugger in a dev container:
...
ā Images successfully pulled
ā Files synchronized
Context: kg01.i2.w.com
Namespace: chama-chomo
Name: t-s-generator
Forward: 8080 -> 8080
38697 -> 38697
/app # go install github.com/go-delve/delve/cmd/dlv@latest
...
..
.
/app # dlv dap -l 127.0.0.1:38697 --log --log-output="dap"
DAP server listening at: 127.0.0.1:38697
...
At this stage, we're ready to proceed with debug client setup inside Neovim.
Neovim part
I'm not going to describe how to install plugins in Neovim, I'll leave it up to you, use your preferred way. As I use Astronvim for configuring Neovim, I'm going to outline exactly what I have inserted into plugins table in init.lua:
plugins = {
init = {
{ "mfussenegger/nvim-dap" },
{
"rcarriga/nvim-dap-ui",
config = function() require("dapui").setup() end,
},
{
"theHamsta/nvim-dap-virtual-text",
config = function() require("nvim-dap-virtual-text").setup() end,
},
...
..
.
Above plugins have certain prerequisites, such as Treesitter etc., please make sure you have all of them installed.
DAP configuration
For using DAP in Neovim we need to configure DAP 'configurations' and 'adapters'. I personally prefer creating one wrapper function that I named DapDebug()
, where I set up all previously mentioned and additionally call real DAP client at the end.
Please note, that for specifying binary that is supposed to be inspected I use an anonymous function that interactively asks for a binary path.
local dap = require "dap"
function DapDebug()
dap.configurations.go = {
{
type = "delve",
name = "Debug (Remote binary)",
request = "launch",
mode = "exec",
hostName = "127.0.0.1",
port = "38697",
program = function()
local argument_string = vim.fn.input "Path to binary: "
vim.notify("Debugging binary: " .. argument_string)
return vim.fn.split(argument_string, " ", true)[1]
end,
},
}
-- we want to run delve manually
dap.adapters.delve = {
type = "server",
host = "127.0.0.1",
port = 38697,
}
require("dap").continue()
end
Even though it is fully optional, I've set few key bindings for interacting with debugger inside Neovim. I will share them for you, so you can create your own set of bindings as you like.
["<leader>ds"] = { "<cmd>lua DapDebug()<CR>", desc = "start-dap-debugger" },
["<leader>dC"] = { "<cmd>lua require('dapui').close()<CR>", desc = "close-dap-ui" },
["<leader>dT"] = { "<cmd>lua require('dapui').toggle()<CR>", desc = "toggle-dap-ui" },
["<leader>dO"] = { "<cmd>lua require('dapui').open()<CR>", desc = "open-dap-ui" },
["<leader>dc"] = { "<cmd> lua require'dap'.continue()<CR>", desc = "dap-continue" },
["<leader>do"] = { "<cmd> lua require'dap'.step_over()<CR>", desc = "dap-step-over" },
["<leader>di"] = { "<cmd> lua require'dap'.step_into()<CR>", desc = "dap-step-into" },
["<leader>du"] = { "<cmd> lua require'dap'.step_out()<CR>", desc = "dap-step-out" },
["<leader>db"] = { "<cmd>lua require'dap'.toggle_breakpoint()<CR>", desc = "set-breakpoint" },
["<leader>"] = {
"<cmd>lua require'dap'.set_breakpoint(vim.fn.input('breakpoint condition: '))<CR>",
desc = "set-conditional-breakpoint",
},
Running debugger
In this part I wanted share with you how debugging session in Neovim for the application deployed in Kubernetes (using Okteto) may look like. Enjoy the fruits!
starting debugging session with :lua DapDebug()
it's going to ask to provide where the binary is located on the remote side
and finally..
Posted on October 29, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.