项目作者: schwarzeni

项目描述 :
debug 部署至 k8s 中的 go 程序
高级语言: Shell
项目地址: git://github.com/schwarzeni/vscode-goapp-k8s-debug.git
创建时间: 2020-09-24T08:05:00Z
项目社区:https://github.com/schwarzeni/vscode-goapp-k8s-debug

开源协议:

下载


Debug 部署至 K8S 中的 Golang 程序

自用模板

现在研二了,论文开题的大概范围是需要围绕 Kubernetes & KubeEdge 做一些二次开发,这就涉及到编写应用以容器的形式部署至 Pod 中与 Kubernetes 进行交互。

那么问题来了,如果运行在容器中的应用出现问题了,同时s仅仅凭借打印的日志无法定位问题怎么办?如果这个容器是需要从集群内部访问 K8S 组件,那么我就无法将应用运行在外部使用 IDE 调试。

程序主要使用 Golang 开发,而 dlv 提供了远程调试的功能,最近研究了一下,如果使用 VSCode + dlv 调试运行在 Kubernetes 集群中的容器。

开发环境

  • Ubuntu Server 16.04
  • VSCode 1.49.1
  • Golang 1.14.4 linux/amd64
  • Kubernetes 1.17.0
  • dlv 1.4.1

我的 Kubernetes 只有一个节点,所以如果有多个节点,那么需要在部署的时候配了 tag 使 Pod 被调度到开发环境所在的节点上,这里就不介绍了。如果 Pod 运行在当前节点上就可以直接通过 127.0.0.1:2345 来访问 dlv 了。


代码结构

  1. .
  2. ├── debug
  3. ├── debug-pod.yaml
  4. ├── Dockerfile
  5. ├── post-debug.sh
  6. └── pre-debug.sh
  7. ├── go.mod
  8. ├── go.sum
  9. ├── main.go
  10. └── .vscode
  11. ├── launch.json
  12. └── tasks.json

main.go 就是需要调试的程序,内容如下

  1. package main
  2. import (
  3. "fmt"
  4. "io/ioutil"
  5. "os"
  6. "github.com/google/uuid"
  7. )
  8. const (
  9. tokenFile = "/var/run/secrets/kubernetes.io/serviceaccount/token"
  10. rootCAFile = "/var/run/secrets/kubernetes.io/serviceaccount/ca.crt"
  11. )
  12. func main() {
  13. fmt.Println("Hello world: " + uuid.New().String())
  14. host, port := os.Getenv("KUBERNETES_SERVICE_HOST"), os.Getenv("KUBERNETES_SERVICE_PORT")
  15. fmt.Println(host, port)
  16. readFile(tokenFile)
  17. readFile(rootCAFile)
  18. for {
  19. }
  20. }
  21. func readFile(path string) {
  22. data, err := ioutil.ReadFile(path)
  23. if err != nil {
  24. fmt.Println(err)
  25. return
  26. }
  27. fmt.Println(string(data))
  28. }

这里的两个文件 tokenFilerootCAFile 是只有在 Kubernetes 内部才能访问到的,那两个环境变量 KUBERNETES_SERVICE_HOSTKUBERNETES_SERVICE_PORT 也是。同时使用到了一个第三方库。


思路

调试前

在调试前,需要将相关的程序内容打包成镜像,然后以 Pod 的形式部署到 Kubernetes 集群里,之后启动 dlv 服务。debug/pre-debug.sh 中就是执行这些步骤的相关代码。下面的两个 while 循环是为了等待 dlv 编译并启动项目。

  1. #!/bin/bash
  2. go mod vendor
  3. cp /root/gopkg/bin/dlv ./ # 这里是将本地的 dlv 程序拷贝到项目中
  4. docker build -t my-golang-app-image -f debug/Dockerfile .
  5. rm dlv
  6. kubectl apply -f debug/debug-pod.yaml
  7. while :
  8. do
  9. status=$(kubectl get pods | grep my-golang-app | awk '{print $3}')
  10. if [ "$status" = "Running" ]; then
  11. break
  12. fi
  13. sleep .5
  14. done
  15. while :
  16. do
  17. line_num=$(kubectl get pods | grep my-golang-app | awk '{print $1}' | xargs kubectl logs | wc -l)
  18. if (( $line_num > 1 )); then
  19. break
  20. fi
  21. sleep .5
  22. done

debug/Dockerfile 如下

  1. FROM golang:1.14
  2. EXPOSE 2345
  3. WORKDIR /go/src/app
  4. COPY . .
  5. ENTRYPOINT ["./dlv", "debug", "--headless", "--listen=:2345", "--log", "--api-version=2"]

debug/debug-pod.yaml 如下

  1. apiVersion: v1
  2. kind: Pod
  3. metadata:
  4. name: my-golang-app
  5. annotations:
  6. seccomp.security.alpha.kubernetes.io/defaultProfileName: "unconfined"
  7. spec:
  8. containers:
  9. - name: my-golang-app
  10. image: my-golang-app-image:latest
  11. imagePullPolicy: Never
  12. ports:
  13. - containerPort: 2345
  14. hostPort: 2345

调试后

调试结束后,需要将 Pod 和之前打包的镜像销毁掉,同时清理文件夹 vendor 。相关步骤在 debug/post-debug.sh

  1. #!/bin/bash
  2. rm -rf vendor
  3. kubectl delete -f debug/debug-pod.yaml
  4. docker ps -a | grep my-golang-app | awk '{print $1}' | xargs docker rm
  5. docker images | grep my-golang-app-image | awk '{print $3}' | xargs docker rmi

vscode 相关配置

.vscode/launch.json 用于配置执行调试的主逻辑

  1. {
  2. "version": "0.2.0",
  3. "configurations": [
  4. {
  5. "name": "Launch remote",
  6. "type": "go",
  7. "request": "attach",
  8. "mode": "remote",
  9. "remotePath": "/go/src/app",
  10. "cwd": "${workspaceFolder}",
  11. "port": 2345,
  12. "host": "127.0.0.1",
  13. "preLaunchTask": "pre-debug",
  14. "postDebugTask": "post-debug",
  15. "showLog": true
  16. }
  17. ]
  18. }

.vscode/tasks.json 用于配置预执行任务 pre-debug 和后执行任务 post-debug

  1. {
  2. "version": "2.0.0",
  3. "tasks": [
  4. {
  5. "label": "pre-debug",
  6. "type": "process",
  7. "command": "./debug/pre-debug.sh",
  8. },
  9. {
  10. "label": "post-debug",
  11. "type": "process",
  12. "command": "./debug/post-debug.sh"
  13. }
  14. ]
  15. }

一些问题

  • 无法在 vscode 中获取调试代码 fmt/log 的输出。另外执行如下执行获得日志
  1. kubectl get pods | grep my-golang-app | awk '{print $1}' | xargs kubectl logs -f
  • 无法远程调试 dlv test ,主要问题是无法打断点,目前无解决方案