DEV Community

chama-chomo
chama-chomo

Posted on • Updated on

How to Debug Golang app running in K8S (Okteto) using Neovim

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
Enter fullscreen mode Exit fullscreen mode

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
...
Enter fullscreen mode Exit fullscreen mode

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,
      },
...
..
.
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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",
      },
Enter fullscreen mode Exit fullscreen mode

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()
Image description

it's going to ask to provide where the binary is located on the remote side
Image description

and finally..

Image description

Oldest comments (0)