Remote JVM Debug (feat. IntelliJ, k8s)

2024. 6. 18. 23:45·JAVA

 

애플 로그인 만드는 작업을 했는데, 애플 로그인은 다른 로그인과는 다른 특징이 있었습니다.

  • http나 로컬호스트에서 사용 불가

결국 Dev나 Stag 서버에 배포해 테스트해보는 수밖에 없었는데 디버거 없이 개발하는 과정이 굉장히 불편하게 느껴졌습니다. 하나하나 로그 찍고 배포하고, 또 확인할 거 생겼을 때 다시 로그 찍고 배포해서 확인할 수도 없고...

어떻게 하면 원격에서도 디버거를 사용할 수 있을까요?

본격적으로 시작하기 전에 디버거에 대해 알아봅시다.

 

 

Debugger

백엔드 애플리케이션을 만들어 보신 분이라면 대부분 IDE에 내장된 디버거를 사용해보신 경험이 있으실 겁니다.

Eclipse, IntelliJ IDEA와 같은 IDE는 디버거를 가지고 있는데요. 개발자들은 이 디버거를 사용해 코드의 플로우나 변수의 값을 확인해 버그를 쉽게 잡을 수 있습니다.

Java platform debugger architecture

그렇다면 자바에서 디버그는 어떤 원리로 실행될까요? Java Platform debugger architecture에 대해 알아봅시다.

이 아키텍처는 디버깅을 당하는 대상인 Debuggee(서버)와 디버깅을 하는 Debugger로 구성됩니다.

여기서 Debuggee는 Debugger는 JDWP라는 프로토콜로 메시지를 주고 받는데요. 이 때 중요한 역할을 하는 것이 Debuggee의 JMVTI 인터페이스, Debugger의 JDI 인터페이스 입니다.

relationship of debuggee and debugger

 

간단하게 설명하자면, JMVTI(JVM Tools Interface)는 JVM에서 실행되는 애플리케이션의 상태를 검사하고 실행을 제어해주는 네이티브 인터페이스입니다. JDI(Java Debug Interface)는 애플리케이션이 디버깅되는 동안 개발자가 디버거를 통해 애플리케이션과 상호 작용할 수 있도록 해주는 인터페이스입니다. JDI를 통해 중단점 설정, Stepping, 스레드 처리, 변수 검사 등의 작업을 처리할 수 있습니다.

JDWP(Java Debugging Wire Protocol)는 Debuggee와 Debugger가 소통할 때 사용하는 프로토콜입니다.

 

그런데 로컬에서 디버거를 사용할 수 없다면?

그런데 서론에 적혀 있는 것처럼 로컬에서 개발해야하는 핵심 로직을 실행할 수 없다면? 그래서 디버그를 사용할 수 없는 상황이라면 어떻게 해야할까요? 그럴 때 사용할 수 있는 것이 Remote JVM Debug 기능입니다.

 

 

Remote Debug

서버는 Debuggee, IDE는 Debugger.

이렇게 간단하게 나누고 보면, 원격으로 디버깅을 하나 로컬로 디버깅을 하나 원리는 같습니다.

서버와 IDE를 JWDP로 연결해주면 로컬에서 사용하는 IDE에서 디버깅이 가능해집니다.

로컬에서 디버거를 사용할 때는 별도의 설정 없이 IDE가 Debuggee를 제어할 수 있기에 연결이 자유자재였습니다만 원격 서버는 아무런 설정 없이 IDE가 제어하기란 불가능하니 디버거를 연결하고 싶으면 별도의 설정이 필요합니다.

Java 옵션 명령어 사용

Java 명령어로 Application을 실행할 때 agentlib 옵션을 설정하면 됩니다.

$ java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5556 -jar application.jar
  • agentlib 옵션
    • jdwp=transport=dt_socket: JVM에 JDWP를 에이전트로 등록하여 디버거가 JVM에 소켓 방식으로 연결할 수 있도록 설정. 실행 중인 두 애플리케이션을 연결할 때, 소켓이 표준으로 사용.
    • server=y: y로 설정할 경우, 서버 역할을 합니다.
    • suspend=n: 디버거를 기다리지 않고, 즉시 프로그램을 실행합니다.
    • address=*:5556: 5556 포트에 디버거가 연결될 수 있도록 Listening 합니다.

도커파일에서 명령어 적용해보기

아마 실제 배포 환경에서는 도커 이미지를 많이 사용하실텐데요. 간단합니다.

도커에서 Application을 실행하는 명령어를 동작시킬 때 위의 옵션을 그대로 적용하면 됩니다.

FROM gradle:8.4.0-jdk21-alpine AS builder

ARG name=my-server

WORKDIR /app
COPY gradle ./gradle
COPY build.gradle.kts ./
COPY settings.gradle.kts ./
COPY ${name} ./${name}

RUN gradle :${name}:bootJar

FROM openjdk:21
ARG name=my-server
ENV jar_name=${name}

WORKDIR /home/my-server
COPY --from=builder app/${name}/build/libs/${name}.jar ./${name}.jar

ENTRYPOINT exec java \
  -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5556 \
  -jar ${jar_name}.jar

원격 연결하기

위 이미지로 배포에 성공했다면 이제 IDE의 Remote JVM Debug 기능을 사용해 원격의 Debuggee에 연결을 시도해봅시다.

  • Edit Configurations로 들어갑시다.

edit configurations

  • Add New Configuration을 클릭하고 Remote JVM Debug를 선택합니다.

Remote JVM Debug

 

  • Attach to remote JVM을 선택합니다.

configure remote jvm

  • 호스트 주소와 Listening 포트를 적어주고 Apply -> OK! 
    • Listening 포트는 위의 명령에서 작성한 5556입니다.

configure remote jvm

 

이제 위 Configuration으로 디버그를 실행하면 원격 서버에서 실행되는 로직이 로컬에서 디버그 가능해집니다!!!

(대신 원격 서버의 5556가 열려 있어야 합니다!!)

 

 

 

보너스! k8s에서 Remote JVM Debug 쉽게 사용하기

kubernetes 환경에서 Remote JVM Debug를 쉽게 사용할 수 있는 방법 2가지를 소개해드리도록 하겠습니다!

Pod가 여러 개 띄워져 있을 때 사용이 불가능합니다. API 요청 시 로드밸런싱에 의해 요청이 나눠지게 되는데, 그 요청이 포트포워딩된 파드나 노드 포트로 연결된 파드로 무조건 전달된다는 보장이 없기 때문입니다.

1. 포트포워딩하기

Application Pod를 로컬호스트로 포트포워딩하여 IDE의 Remote JVM Debug 설정 시 호스트 주소를 로컬호스트로 두는 방식입니다. 가장 간단한 방법입니다.

  • 아래 명령어에 대한 설명
    • {로컬에서 사용할 포트}:{서버에서 리스닝하고 있는 포트}
    • 서버에서 리스닝하고 있는 포트는 도커 파일에서 설정해준 5556이 들어가야합니다.
    • 로컬에서 사용할 포트는 IntelliJ에서 설정할 포트 값으로 해주시면 됩니다. (현재 로컬에서 사용하고 있지 않은 포트 사용)
$ kubectl port-forward -n my-namespace pods/my-server-deploy-69d75789cf-psfcw 5556:5556
Forwarding from 127.0.0.1:5556 -> 5556
Forwarding from [::1]:5556 -> 5556
  • 아래와 같이 호스트를 localhost로 둬도 연결이 됩니다!

port forward configuration

 

2. 노드포트 사용하기

포트포워딩은 정말 간단한 방법입니다만 모종의 이유로 갑자기 포트포워딩이 끊겨버려 디버깅에 불편함을 겪게될 수 있습니다.

 

서비스의 Type을 NodePort 두는 것이 방법이 될 수 있습니다.

아래 코드를 확인해봅시다. Remote JVM Debug에 사용할 포트를 고정하기 위해 debug 포트를 32000번으로 고정해줬습니다. (주의! deployment에서도 5556번 포트를 열어줘야함.)

apiVersion: v1
kind: Service
metadata:
  name: my-server-svc
  namespace: my-namespace
spec:
  ports:
    - port: 8080
      targetPort: 8080
      protocol: TCP
      name: server
    - port: 5556
      targetPort: 5556
      protocol: TCP
      nodePort: 32000
      name: debug
  type: NodePort
  selector:
    app: my-server
  • 파드가 어떤 노드에 속하는지 알아봅시다.
$ kubectl get pod -o wide -n lohasmeal-dev
NAME                               READY   STATUS    RESTARTS        AGE     IP             NODE                    NOMINATED NODE   READINESS GATES
my-server-deploy-795888d8c5-jcp27  1/1     Running   0               3d23h   xxx.xx.x.xx    my-nodepool-AAAAAAA     <none>           <none>
  • 노드의 INTERNAL-IP를 호스트주소 삼아 원격 연결을 시도하면 됩니다.
    • xx.x.x.xx:32000 형식으로 연결!
kubectl get node -o wide
NAME                   STATUS   ROLES    AGE    VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE             KERNEL-VERSION     CONTAINER-RUNTIME
my-nodepool-AAAAAAA    Ready    <none>   88d    v1.23.9   xx.x.x.xx     <none>        Ubuntu 20.04.3 LTS   5.4.0-99-generic   containerd://1.6.16

 

 

참고 자료

  • Java Remote Debugging

 

 

 

'JAVA' 카테고리의 다른 글
  • Jacoco 설정하기 (build.gradle & .kts)
gakko
gakko
좌충우돌 개발기
  • gakko
    MYVELOP 마이벨롭
    gakko
  • 전체
    오늘
    어제
    • 분류 전체보기 (203)
      • Spring (23)
        • Spring (10)
        • Spring Boot (7)
        • Spring Security (1)
        • Hibernate (4)
      • Test (3)
      • 끄적끄적 (6)
      • 활동 (35)
        • 부스트캠프 (23)
        • 동아리 (3)
        • 컨퍼런스 (3)
        • 글또 (5)
        • 오픈소스 컨트리뷰션 (1)
      • 디자인패턴 (0)
      • Git & GitHub (22)
        • Git (13)
        • Github Actions (1)
        • 오류해결 (5)
        • 기타(마크다운 등) (3)
      • 리눅스 (6)
        • 기초 (6)
        • 리눅스 서버 구축하기 (0)
      • Infra (2)
        • Docker (1)
        • Elastic Search (0)
        • Jenkins (1)
        • AWS (1)
      • MySQL (7)
        • 기초 (6)
        • Real MySQL (1)
      • 후기 (3)
        • Udemy 리뷰 (3)
      • CS (26)
        • 웹 기본지식 (0)
        • 자료구조 (13)
        • 운영체제 OS (12)
        • 데이터베이스 (1)
        • 시스템 프로그래밍 (0)
        • 기타 (0)
      • Tools (1)
        • 이클립스 (1)
        • IntelliJ (0)
      • 프로젝트 (1)
        • 모여모여(부스트캠프) (1)
      • JAVA (32)
        • Maven (6)
        • 오류해결 (11)
        • 자바 클래스&메소드 (1)
        • JSP & Servlet (12)
      • Javascript (5)
        • 기초 (3)
        • React (2)
      • Python (28)
        • 파이썬 함수 (9)
        • 알고리즘 문제풀이 (16)
        • 데이터 사이언스 (2)
        • 웹 크롤링 (1)
      • 단순정보전달글 저장소 (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

    • 우진님
  • 공지사항

  • 인기 글

  • 태그

    부스트캠프
    Git
    부스트캠프 멤버십
    jsp
    오류해결
    MySQL
    java
    파이썬
    알고리즘
    운영체제
    Spring
    스프링부트
    웹개발
    os
    부스트캠프 7기
    자바스크립트
    Python
    자바
    스프링
    GitHub
  • 최근 댓글

  • hELLO· Designed By정상우.v4.10.0
gakko
Remote JVM Debug (feat. IntelliJ, k8s)
상단으로

티스토리툴바