# Kubernetes(쿠버네티스) 환경에서 GO 어플리케이션 디버깅 환경 구성

2020. 8. 5. 00:14 개발 이야기/Golang

 

정말 오랜만에 개발일기를 쓰는 해커의 개발일기 입니다.

 

 

저는 요즘 Kubernetes 환경을 모니터링할 수 있는 솔루션인 CloudMOA라는 제품을 만들고 있는데요

 

kubernetes 환경에서만 돌아갈 수 있는 어플리케이션인 일종의 agent를 개발하는데 있어서

 

디버깅을 할 수 있는 환경을 구성하느라 애를 먹었는데요 ..

 

이런 경험을 공유하고자 합니다.

 

개발을 함에 있어서 디버깅은 아주 큰 비중을 차지하는데요 .. 

 

오픈소스를 커스터마이징 할 때도 디버깅 환경을 구성하는 것도 아주 일인 것 같습니다.

 

저는 이번 프로젝트를 하면서 아주 다양한 환경을 구성했는데요 ..

좋은 경험이었던 것 같습니다.

 

참조한 소스코드는 

https://github.com/dlsniper/dockerdev 

 

dlsniper/dockerdev

Example of working with Go, Docker and Kubernetes from GoLand - dlsniper/dockerdev

github.com

 

여기 git에서 branch를 kubernetes로 변경해 다운로드합니다.

 

 

이 프로젝트는 docker로 개발 시 디버깅할 수 있는 환경을 예시로 보여주고

 

kubernetes 환경도 예시로 작성해줬는데요

 

여기에는 간단한 웹서비스 소스코드가 존재합니다.

 

func main() {
	// Set the flags for the logging package to give us the filename in the logs
	log.SetFlags(log.LstdFlags | log.Lshortfile)

	dbPool := getDBConnection(context.Background())
	defer dbPool.Close()

	log.Println("starting server...")
	http.HandleFunc("/", homeHandler(dbPool))
	log.Fatal(http.ListenAndServe(":8000", nil))
}

func homeHandler(db *pgxpool.Pool) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		visitorID := 0
		//language=sql
		err := db.QueryRow(r.Context(), "INSERT INTO visitors(user_agent, datetime) VALUES ($1, now()) RETURNING id", r.UserAgent()).Scan(&visitorID)
		if err != nil {
			w.WriteHeader(http.StatusInternalServerError)
			_, _ = w.Write([]byte("failed to update the database"))
			log.Printf("[or] db error: %v\n", err)
			return
		}

		w.WriteHeader(http.StatusOK)
		_, _ = fmt.Fprintf(w, `Hello, visitor %d!`, visitorID)
	}
}

 

8000 포트로 request가 오면 db에 insert 하고 ID를 reponse에 담아서 주는 간단한 로직인데요!

 

이 간단한 어플리케이션을 이용해서 디버깅 환경을 구성해보겠습니다.

 

코드를 작성하고 Dockerfile을 만들어주는데요 기본적인 Dockerfile은 아래와 같습니다.

 

 

간단히 설명하면 build-env라는 빌드 환경을 만들어 빌드하고 이를 debian linux에 담에서 실행합니다.

 

하지만 이렇게 docker 이미지를 만들어서 kubernetes 환경에 올리면 디버깅할 수 없는데요..

 

때문에 아래와 같은 Dockerfile을 만들어 디버깅용 이미지를 만들어줍니다.

 

# Compile stage
FROM golang:1.13.8 AS build-env

# Build Delve
RUN go get github.com/go-delve/delve/cmd/dlv

ADD . /dockerdev
WORKDIR /dockerdev

RUN go build -gcflags="all=-N -l" -o /server

# Final stage
FROM debian:buster

EXPOSE 8000 40000

WORKDIR /
COPY --from=build-env /go/bin/dlv /
COPY --from=build-env /server /

CMD ["/dlv", "--listen=:40000", "--headless=true", "--api-version=2", "--accept-multiclient", "exec", "/server"]

 

docker build -f Dockerfile -t dockerdev-web:debug .

 

빌드 시 gcflags를 달아서 빌드하고 40000번 포트로 dlv를 이용해 디버깅을 하기 위한 설정인데요

 

이 도커 이미지를 이용해 kubernetes 환경에서 디버깅을 해보겠습니다.

 

먼저 kube 환경에서 사용할 yaml 파일을 작성합니다

 

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    dockerdev: web
  name: web
spec:
  selector:
    matchLabels:
      dockerdev: web
  template:
    metadata:
      labels:
        dockerdev: web
    spec:
      containers:
        - name: dockerdev-web
          image: dockerdev-web:debug
          imagePullPolicy: IfNotPresent
#          env:
#            - name: DD_DB_HOST
#              value: "dockerdev-db-exported"
          ports:
            - containerPort: 8000
            - containerPort: 40000
---
apiVersion: v1
kind: Service
metadata:
  labels:
    dockerdev: web
  name: dockerdev-web-exported
spec:
  type: NodePort
  ports:
    - name: 8000-tcp
      port: 8000
      targetPort: 8000
      nodePort: 30800
    - name: 40000-tcp
      port: 40000
      targetPort: 40000
      nodePort: 30801
  selector:
    dockerdev: web

 

도커 이미지에서 오픈한 포트를 kube 환경에서 사용할 포트와 바인딩을 해줘야 하는데요

 

저희는 테스트 환경을 1개의 노드로 구성된 쿠버네티스로 구성을 하고 

이 노드에 포트와 바인딩을 해줍니다

 

8000 포트는 30800 노드 포트와

40000 포트는 30801 노드 포트와 바인딩을 해줍니다.

그리고! kubernetes 환경을 구성해야겠죠?

 

정말 간단하게~~ 윈도우 환경에서 1 노드 쿠버네티스 환경을 구성할 수 있는 꿀팀을 공개하겠습니다.

 

1. window용 docker-desktop을 설치 후 settings

 

 

여기서 Enable kubernetes를 클릭해주고 apply & restart를 눌러줍니다!~

 

그러면 1 노드 쿠베환경 구성 끝~~

 

그리고 저는 goland IDE를 이용해 개발을 하는데요

 

여기서 사용하기 편하게 Setting -> Plugins에서 kubernetes Plugins을 설치해줍니다.

 

 

그리고 나서 web.yaml 파일을 kubectl apply 해주면 실행되겠죠?

 

 

 

아까 설치한 kubernetes 탭(Services)을 눌러서 확인하면 아래와 같이 pods가 잘 실행된 것을 볼 수가 있습니다.

 

정말 어~썸~~ 하지 않나요 ? 

 

어려운 쿠베 환경을 이렇게 보기 쉽게 구성할 수 있다니.. 개발하고 싶은 욕구가 마구마구 솟구칩니다.

 

 

로그를 보면 이렇게 40000 포트가 리슨 상태인 것을 볼 수 있는데요

 

이제 디버깅을 할 수 있는 바이너리를 만들고 이를 실행시켰으니 디버깅을 시도해보겠습니다!

 

 

dlv를 이용해서 디버깅을 할 것이니 설정을 해줍니다.

 

아까 kube환경에서 바이너리를 실행할 때 40000 포트를 30801 포트와 바인딩한 것을 기억하시죠?

 

도커 이미지에서 40000 포트를 열었다고 거기를 보게 하면 묵묵 부답을 경험하실 수 있습니다.

 

그리고 디버깅하고 싶은 코드에 Breakpoint를 걸어줍니다!

/ 경로에 리퀘스트가 오면 처리하는 homehandler에 포인트를 걸었습니다! 그러면 이제

 

go remote를 실행하고 / 경로로 리퀘스트를 날려보겠습니다!

 

저 벌레를 눌러주면 .. 

 

빰!

 

breakpoint에 걸린 것을 볼 수 있습니다! 

 

그리고 

 

동일하게 변수에 어떤것들이 있는지도 볼 수 있는데요 

 

이제 리퀘스트를 날려보겠습니다.

 

 

리턴값이 뭐인지 바로 보이죠 ?? visitorID가 4이니 

"Hello, visitor 4!"

이렇게 리턴되겠죠 ? 

 

 

네 .. 정상적으로 디버깅이 잘 ~ 되고 있는것을 확인 할 수가 있습니다.

 

저는 이 환경을 구성하고 아주 뿌듯했습니다. 

 

자고로 디버깅없는 개발은 없다고 생각하는 1인이라 ..

 

구름 코딩한 사람이 대단 ...

 

아무튼! 이런식으로 kube에서만 동작 가능한 GO언어 어플리케이션도 디버깅이 가능하다는 것을 보여주고 싶었습니다!.

 

저와 비슷한 상황에 처하신분들은 꼭 참고하셔서 스트레스받는 개발 안하시길 바랄게요~~