개발자

'실전 예제로 보는' 자바 개발에서의 도커 사용법

Matthew Tyson | InfoWorld 2021.11.10
개발 중 도커(Docker)를 사용할 때 얻는 이점은 개발 기기와 다양한 환경(QA, 프로덕션 등)에 걸쳐 일관적인 테스트 환경을 제공할 수 있다는 것이다. 반면 어려운 점도 있다. 도커 컨테이너로 인해 개발자가 코딩 중 관리해야 하는 부가적인 추상화 계층이 발생한다. 

도커를 이용하면 애플리케이션 코드를 시스템 요구사항 정의와 함께 여러 플랫폼에서 실행 가능한 패키지로 묶을 수 있다. 소프트웨어 런타임 배포와 관리에서 근본적으로 필요한 부분을 해결하는 효과적인 추상화지만, 대신 프로그래머가 대처해야 하는 부가적인 간접성 계층이 발생해 소프트웨어와 그 종속 항목의 내부 구조를 반복적으로 수정하고 테스트해야 한다. 이러한 개발 속도 저하는 개발자가 가장 피하고 싶어하는 일이다. 이를 둘러싼 다양한 찬반 양론이 있다.
 
ⓒ Getty Images Bank

개발 기기 전반에서 본격적으로 도커를 사용하지 않는다고 해도 컨테이너 내에서 실행되는 코드를 수정, 디버깅하는 다양한 사례가 있다. 예를 들어 개발자는 도커를 사용해 프로덕션 환경을 똑같이 만들어 오류 또는 기타 조건을 재현할 수 있다. 또한 도커화된 앱을 실행하는 호스트에 대한 원격 디버깅 기능은 QA와 같이 실행 중인 환경에 대한 직접적인 문제 해결을 가능하게 해준다. 

여기서는 구글 클라우드 플랫폼(GCP)에서 VM에 간단한 자바 앱을 만들어서 도커화한 다음 원격으로 디버깅하고, 로컬 호스트의 비주얼 스튜디오 코드에서 앱 코드를 수정하는 과정을 살펴보자.

실행할 2가지 중요한 작업은 컨테이너를 재시작하지 않고 실행 중인 코드베이스 업데이트하기, 그리고 실행 중인 컨테이너화된 앱 디버깅하기다. 또한 이 프로세스를 원격으로 실행되는 컨테이너에서 수행한다. 즉, 로컬 개발 호스트 외에 QA 서버와 같은 서비스를 원격으로 디버깅하는 방법도 살펴본다.
 

자바와 스프링 부트 설정하기

시작 지점은 GCP 콘솔이다(계정이 없다면 무료 계정 가입). 컴퓨트 엔진(Compute Engine) 링크로 이동하면 VM 목록이 표시되는데, 여기서 인스턴스 만들기(Create Instance)를 클릭한다. 

N1 마이크로 서버를 선택하는 경우 서버는 무료 티어에 속한다. 그러나 도커는 리소스를 많이 소비하는 편이므로 범용 E2 서버를 사용할 것을 권한다. 24/7 사용 가능하며 월 비용은 25달러 정도다. 필자는 서버 이름을 dev-1로 설정했다. 

이 인스턴스를 위한 네트워크를 구성한다. VM 세부 정보의 중간에 있는 네트워크(Network) 탭을 클릭하고 네트워크 태그(Network Tags) 필드에 port8080port8000을 추가한다. 

이제 왼쪽 메뉴에서 VPC 네트워크(VPC Networks) > 방화벽(Firewall)을 연다. 새 규칙 2개를 만들어(방화벽 규칙 만들기(Create Firewall Rule) 버튼 클릭) 모든 소스 IP(0.0.0.0/0)가 TCP 포트 8080(port8080 레이블 사용)과 TCP 포트 8000(port8000 레이블 사용)에 액세스하도록 허용한다. 이렇게 하면 새 VM 인스턴스는 여기서 만들 앱 서버(8080)와 기본 자바 디버그 포트 8000에 대한 트래픽을 허용한다. 

다시 컴퓨트 엔진(Computer Engine) > VM 인스턴스로 클릭해 돌아와서 새 인스턴스(dev-1)를 찾아 SSH 버튼을 클릭, 새 서버에 SSH를 설정한다. 

이제 자바를 설정할 차례다. sudo apt-get update를 입력하고 그다음 sudo apt-get install default-jdk를 입력한다. 완료되면 java --version에서 값이 반환된다. 그다음 셸에서 Initializr를 사용할 수 있도록 SDKMAN(SDK 관리자)을 통해 스프링 CLI를 설치한다. 다음 명령을 실행한다. 
 
sudo apt install zip
curl -s "https://get.sdkman.io" | bash
source "/home//.sdkman/bin/sdkman.sh"

이제 sdk version을 사용할 수 있다. sdk install springboot를 사용해서 스프링 CLI를 설치한다. 다음 명령으로 새 스프링 부트 자바 웹 앱을 신속하게 만들 수 있다. 
 
spring init --dependencies=web idg-java-docker

새 프로젝트는 /idg-java-docker에 위치한다. cd를 사용해 이 디렉터리로 이동한다. 스프링 부트 앱에는 mvnw 스크립트가 포함되므로 메이븐(Maven)을 수동으로 설치할 필요는 없다. sudo ./mvnw spring-boot:run을 입력해 개발 모드로 앱을 가동한다. 

브라우저에서 http://<인스턴스 IP>:8080으로 이동하면(GCP 콘솔의 목록에서 IP 주소를 찾을 수 있음) 매핑된 경로가 없으므로 스프링 화이트 레이블 오류(Spring White Label Error) 페이지가 뜰 것이다. 

URL 경로 매핑 
테스트를 위해 간단한 엔드포인트를 추가해 보자. vi src/main/java/com/example/javadocker/DemoApplication.java를 사용해서(또는 원하는 다른 편집기를 사용해도 됨) 주 클래스를 <리스트 1>과 같이 수정한다.
 
리스트 1. 엔드포인트 추가
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class DemoApplication {
  @RequestMapping("/")
  public String home() {
     return "Hello InfoWorld!";
  }
  public static void main(String[] args) {
    SpringApplication.run(DemoApplication.class, args);
  }
}

이제 Ctrl-c로 톰캣(Tomcat)을 중지하고 ./mvnw spring-boot:run을 입력해 다시 빌드하고 재시작할 수 있다. 브라우저에서 앱으로 이동하면 간단한 “Hello InfoWorld” 응답을 볼 수 있다. 
 

프로젝트 도커화 

먼저 데비안용 공식 도커 지침에 따라 도커를 설치한다. 다음 각 명령을 차례로 입력한다. 
 
sudo apt-get install apt-transport-https ca-certificates curl gnupg lsb-release
curl -fsSL https://download.docker.com/linux/debian/gpg | sudo gpg --dearmor -o /usr/share/keyrings/docker-archive-keyring.gpg
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/debian $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update 
sudo apt-get install docker-ce docker-ce-cli containerd.io
 

도커파일 만들기 

도커파일(Dockerfile)을 만드는 방법은 메이븐 플러그인을 사용하는 방법을 포함해 여러 가지다. 여기서는 수동으로 간단한 도커파일을 만들어 살펴본다. 편집기를 사용해 dockerfile이라는 파일을 만들고 <리스트 2> 내용을 추가한다. 여기서는 간단히 하기 위해 그룹과 사용자를 무시하지만 실제 상황에서는 고려해야 한다. 
 
리스트 2. 기본적인 자바/스프링 도커파일 
# syntax=docker/dockerfile:1

FROM openjdk:16-alpine3.13

WORKDIR /app

COPY .mvn/ .mvn
COPY mvnw pom.xml ./
RUN ./mvnw dependency:go-offline

COPY src ./src

CMD ["./mvnw", "spring-boot:run"]

이 도커파일은 OpenJDK를 기본 계층으로 사용한다. /app 작업 디렉터리로 이동한 다음 모든 메이븐 파일을 가져와 오프라인 모드로 메이븐을 실행한다. 이렇게 하면 나중에 종속 항목을 다시 다운로드할 필요가 없다. 도커파일이 앱 소스를 복사하고 spring-boot:run 명령을 실행한다. 

단, 프로덕션이 아닌 개발(dev) 이미지임을 주목해야 한다. spring-boot:run 명령은 프로덕션에서 사용하기에는 적절하지 않다. 아직 실행 중인 앱이 있다면 중지한다. 지금 빌드해서 실행하기 위해 먼저 docker build 명령을 실행한다. 
 
sudo docker build --tag idg-java-docker

빌드를 기다렸다가 docker run을 실행한다. 
 
sudo docker run -d -p 8080:8080 idg-java-docker

도커 이미지가 빌드된 다음 새 컨테이너에서 시작된다. run 명령을 호출하면 다음과 같은 UID가 반환된다. 
 
d98e4d19dab71fa69b2331485b70b5c87f20de864238e5798ad3aa8c5b576014

이제 브라우저에서 앱을 방문해 앱이 실행 중이고 포트 8080에서 사용 가능한지 재차 확인할 수 있다. sudo docker ps를 사용해서 실행 중인 컨테이너를 확인할 수 있다. 같은 UID가 실행 중인 것을 볼 수 있을 것이다. sudo docker kill을 사용해서 중지한다. UID는 고유해지는 부분까지만 입력하면 된다. 깃의 체크인 ID와 비슷하다. 필자의 경우 sudo docker kill d98이다.

도커파일은 앱 실행을 위한 출발점으로 좋지만(사용자와 계층이 그 다음), 여기서 잠깐 멈추고 실행 중인 애플리케이션을 업데이트하기 위해 무엇을 해야 하는지를 생각해 보자. 간단한 인사말 변경이라 해도 코드를 변경하고 실행 중인 도커 컨테이너를 중지하고 docker build로 이미지를 빌드하고 docker run으로 컨테이너를 시작해야 한다. 이런 번거로움을 개선할 수 있을까? 
 

도커 컴포즈 사용

이 질문에 대한 답이 있다. 원격 디버그가 활성화된 devtools로 스프링 부트를 실행하고 도커에서 디버그 포트를 노출한다. 이를 (명령줄 인수 대신) 선언적 방식으로 관리하기 위해 도커 컴포즈(Compose)를 사용한다. 도커 컴포즈는 도커의 실행 방식을 표현하는 강력한 수단이며 여러 타겟(멀티 스테이지 빌드라고도 함)과 외부 볼륨 마운트를 지원한다. 

기본 구성 파일은 docker-compose.yml이며 도커파일에 있는 구성을 기반으로 그 위에서 실행된다. 먼저 도커 바이너리를 설치한다. 
 
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose

다음을 실행한다. 
 
sudo chmod +x /usr/local/bin/docker-compose

이제 다음을 실행할 수 있다. 
 
docker-compose --version

팁을 하나 소개하면, 실행 중인 컨테이너의 내부를 탐색해야 하는 경우 다음 명령 중 하나를 실행하면 된다. 이미지의 기반 OS에 따라 선택하면 된다.
 
  • sudo docker exec -it 646 /bin/sh 
  • sudo docker exec -it 646 /bin/bash 
  • sudo docker exec -it 646 powershell 

이제 도커 컴포즈를 사용할 수 있으므로 <리스트 3>과 같이 구성 파일인 docker-compose.yml을 만들어 보자. 
 
리스트 3. docker-compose.yml 
version: '3.3'
services:
  idg-java-docker:
    build:
      context: .
    ports:
      - 8000:8000
      - 8080:8080
    environment:
      - SERVER_PORT=8080
    volumes:
      - ./:/app
    command: ./mvnw spring-boot:run -Dspring-boot.run.jvmArguments="-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:8000"

여기서 살펴볼 첫 번째 핵심은 포트 8080과 8000 모두 열려 있다는 점이다. 8000은 일반적인 자바 디버그 포트이며 명령 문자열에서 참조한다. docker-compose 명령은 도커파일의 CMD 정의보다 우선한다. 다시 말하지만 docker-compose는 도커파일 위에서 실행된다. 

sudo docker-compose build --no-cache idg-java-docker를 입력해 이미지를 빌드한다. sudo docker-compose up으로 앱을 시작하고, Ctrl-c로 종료한다. 이제 분리 모드에 대해 sudo docker-compose up -d를 사용해 백그라운드에서 컨테이너를 실행한다. 그러면 sudo docker-compose down으로 종료할 수 있다. git init, git add ., git commit -m "initial"로 새 앱을 커밋한다. GitHub.com을 방문해 새 리포지토리를 만든다. 안내에 따라 프로젝트를 푸시한다.  
 
git remote add origin https://github.com//.git
git branch -M main
git push -u origin main

로컬 시스템에서 비주얼 스튜디오 코드를 연다. 또는 원격 디버그가 활성화된 자바 IDE에서도 가능하다. VS 코드 및 자비 실행에 관한 더 자세한 정보는 여기서 확인할 수 있다. 최신 IDE 대부분은 깃허브 리포지토리 클론 주소에서 직접 리포지토리를 클론할 수 있다.

이제 IDE에 대한 자바 디버그 구성을 연다. VS 코드에서는 launch.json 파일이다. <리스트 4>와 같이 구성 항목을 만든다. 이클립스, 인텔리J 등 다른 IDE도 시작 구성 대화상자는 비슷하고 입력 필드도 같다.
 
리스트 4. IDE 클라이언트를 위한 디버그 구성 
{
  "type": "java",
  "name": "Attach to Remote Program",
  "request": "attach",
  "hostName": "<The host name or ip address of remote debuggee>",
  "port": 8000
},

VM에서 IP 주소를 연결한 다음 디버그로 이동해 “원격 프로그램에 연결(Attach to Remote Program)” 구성을 실행해서 이 구성을 적용한다. 

디버거가 연결되면 DemoApplication.java 파일을 수정하고(예를 들어 인사말을 “Hello InfoWorld!”로 바꾸기) 저장할 수 있다. 이제 “핫 모듈 스왑(Hot module swap)” 버튼(번개 아이콘)을 클릭하면 VS 코드가 실행 중인 프로그램을 업데이트한다. 브라우저에서 다시 앱으로 이동하면 변경 사항이 적용된 것을 볼 수 있다. 마지막으로, IDE에서 13번 라인을 두 번 클릭해 중단점을 설정한다. 다시 앱을 방문하면 중단점에 도달하면서 IDE가 열리고 전체 디버깅 기능을 사용할 수 있다. 

여기서 살펴본 방법 외에 개발 흐름을 도커화하는 방법은 다양하다. 하지만 이 방법으로 비교적 간단하게 로컬 호스트와 원격 시스템 모두를 위한 업데이트 및 디버깅 기능을 제공한다. editor@itworld.co.kr

회사명 : 한국IDG | 제호: ITWorld | 주소 : 서울시 중구 세종대로 23, 4층 우)04512
| 등록번호 : 서울 아00743 등록발행일자 : 2009년 01월 19일

발행인 : 박형미 | 편집인 : 박재곤 | 청소년보호책임자 : 한정규
| 사업자 등록번호 : 214-87-22467 Tel : 02-558-6950

Copyright © 2024 International Data Group. All rights reserved.