컨테이너는 개발, 테스트 및 프로덕션 단계에서 Java 애플리케이션에 일관되고 이식 가능한 환경을 제공합니다. 이 문서에서는 Java 애플리케이션에 대한 컨테이너화 개념을 소개하고 컨테이너화된 Java 애플리케이션을 만들고, 디버깅하고, 최적화하고, Azure Container Apps에 배포하는 방법을 안내합니다.
이 문서에서는 Java 개발자를 위한 필수 컨테이너화 개념과 다음 기술을 알아봅니다.
- 컨테이너화된 Java 애플리케이션에 대한 개발 환경 설정
- Java 워크로드에 최적화된 Dockerfiles 만들기
- 컨테이너를 사용하여 로컬 개발 워크플로 구성
- 컨테이너화된 Java 애플리케이션 디버깅
- 프로덕션을 위해 Java 컨테이너 최적화
- Azure Container Apps에 컨테이너화된 Java 애플리케이션 배포
Java 애플리케이션을 컨테이너화하면 일관된 환경, 간소화된 배포, 효율적인 리소스 사용률 및 향상된 확장성을 얻을 수 있습니다.
Java 애플리케이션용 컨테이너
컨테이너는 애플리케이션을 종속성으로 패키지하여 환경 간에 일관성을 보장합니다. Java 개발자의 경우 애플리케이션, 해당 종속성, JRE/JDK(Java 런타임 환경/Java 개발 키트) 및 구성 파일을 이식 가능한 단일 단위로 묶는 것을 의미합니다.
컨테이너화는 클라우드 개발에 적합하게 만드는 가상화에 비해 주요 이점이 있습니다. 가상 머신과 달리 컨테이너는 서버의 호스트 OS 커널에서 실행됩니다. 이는 Java JVM(가상 머신)에서 이미 실행 중인 Java 애플리케이션에 유용합니다. Java 애플리케이션을 컨테이너화하면 오버헤드가 최소화되고 상당한 배포 이점이 제공됩니다.
컨테이너 에코시스템에는 다음과 같은 주요 구성 요소가 포함됩니다.
- 이미지 - 청사진.
- 컨테이너 - 실행 중인 인스턴스입니다.
- 레지스트리 - 이미지가 저장되는 위치입니다.
- 오케스트레이터 - 대규모 컨테이너를 관리하는 시스템입니다.
Docker는 가장 인기 있는 컨테이너화 플랫폼이며 Azure Container Apps를 통해 Azure 에코시스템에서 잘 지원됩니다.
개발 환경 설정
이 섹션에서는 필요한 도구를 설치하고 컨테이너화된 Java 애플리케이션을 빌드, 실행 및 디버그하도록 개발 환경을 구성하는 방법을 안내합니다.
필요한 도구 설치
Java 애플리케이션을 컨테이너화하려면 개발 머신에 다음 도구가 설치되어 있어야 합니다.
- Docker Desktop. Windows 또는 macOS용 Docker 엔진, CLI 및 Docker Compose를 제공합니다.
- Visual Studio Code 무료 코드 편집기로 사용할 수 있습니다.
- 다음 Visual Studio Code 확장 프로그램:
다음 명령을 사용하여 설치를 확인합니다.
docker --version
docker compose version
컨테이너 개발을 위한 Visual Studio Code 구성
컨테이너에서 Java 개발을 위해 Java 확장 팩을 설치하고 JDK를 설정하여 Visual Studio Code를 구성합니다. Dev Containers 확장을 사용하면 컨테이너 내의 폴더를 열고 해당 컨테이너 내에서 Visual Studio Code의 전체 기능 집합을 사용할 수 있습니다.
Visual Studio Code가 개발 컨테이너를 자동으로 빌드하고 연결할 수 있도록 하려면 프로젝트에 .devcontainer/devcontainer.json 파일을 만듭니다.
예를 들어 다음 예제 구성은 Java 빌드를 정의합니다.
{
"name": "Java Development",
"image": "mcr.microsoft.com/devcontainers/java:21",
"customizations": {
"vscode": {
"extensions": [
"vscjava.vscode-java-pack",
"ms-azuretools.vscode-docker"
]
}
},
"forwardPorts": [8080, 5005],
"remoteUser": "vscode"
}
이 구성은 Microsoft의 Java 개발 컨테이너 이미지를 사용하고, 필수 확장을 추가하고, 애플리케이션 포트와 디버깅 포트8080
5005
를 모두 전달합니다.
Dockerfile 만들기
Dockerfile에는 Docker 이미지를 빌드하기 위한 지침이 포함되어 있습니다. Java 애플리케이션의 경우 Dockerfile 에는 일반적으로 다음 구성 요소가 포함됩니다.
- JDK 또는 JRE가 있는 기본 이미지입니다.
- 애플리케이션 파일을 복사하는 지침입니다.
- 환경 변수를 설정하는 명령입니다.
- 진입점 구성.
기본 이미지 선택
올바른 기본 이미지를 선택하는 것이 중요합니다. 다음 옵션을 고려합니다.
설명 | 이름 | 비고 |
---|---|---|
Microsoft Java 개발 이미지 | mcr.microsoft.com/java/jdk:21-zulu-ubuntu |
전체 JDK 및 Azure에 최적화 |
Microsoft Java 프로덕션 이미지 | mcr.microsoft.com/java/jre:21-zulu-ubuntu |
런타임만 가능하고 Azure에 최적화됨 |
공식 OpenJDK 개발 이미지 | openjdk:21-jdk |
전체 JDK |
공식 OpenJDK 프로덕션 이미지 | openjdk:21-jre |
실행 시간에만 |
개발 환경의 경우 전체 JDK 이미지를 사용합니다. 프로덕션의 경우 JRE 또는 배포판 없는 이미지를 사용하여 애플리케이션의 크기와 공격 표면을 최소화합니다.
Microsoft Java 이미지는 Azure 특정 최적화와 함께 제공되며 정기적으로 보안 패치로 업데이트되므로 Azure Container Apps를 대상으로 하는 애플리케이션에 적합합니다.
기본 Dockerfile 예제
다음 예제에서는 Java 애플리케이션에 대한 간단한 Dockerfile 을 보여줍니다.
FROM mcr.microsoft.com/java/jdk:21-zulu-ubuntu
WORKDIR /app
COPY target/myapp.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-jar", "app.jar"]
Spring Boot 애플리케이션의 경우 다음 기본으로 Dockerfile 을 설정할 수 있습니다.
FROM mcr.microsoft.com/java/jdk:21-zulu-ubuntu
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-Dspring.profiles.active=docker", "-jar", "app.jar"]
프로덕션 배포의 경우 다음 예제에 표시된 JRE 이미지를 사용하여 크기를 줄이고 애플리케이션의 공격 표면을 최소화합니다.
FROM mcr.microsoft.com/java/jre:21-zulu-ubuntu
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENV JAVA_OPTS="-Dserver.port=8080"
ENTRYPOINT ["java", ${JAVA_OPTS}, "-jar", "app.jar"]
컨테이너를 사용한 로컬 개발
컨테이너는 다양한 컨텍스트에서 실행하기 위한 것입니다. 이 섹션에서는 컨테이너와 함께 사용할 로컬 개발 흐름을 알아봅니다.
다중 컨테이너 애플리케이션에 Docker Compose 사용
대부분의 Java 애플리케이션은 데이터베이스, 캐시 또는 기타 서비스와 상호 작용합니다. Docker Compose를 사용하면 간단한 YAML 구성 파일을 사용하여 다중 컨테이너 애플리케이션을 정의하고 오케스트레이션할 수 있습니다.
Docker Compose란?
Docker Compose는 다음 작업을 수행할 수 있는 도구입니다.
- 단일 파일에서 다중 컨테이너 애플리케이션을 정의합니다.
- 시작, 중지 및 다시 빌드를 포함하여 애플리케이션 수명 주기를 관리합니다.
- 격리된 환경을 유지 관리합니다.
- 서비스 통신을 위한 네트워크를 만듭니다.
- 볼륨을 사용하여 데이터를 유지합니다.
예: 데이터베이스가 있는 Java 애플리케이션
다음 compose.yml 파일은 PostgreSQL 데이터베이스를 사용하여 Java 애플리케이션을 구성합니다.
version: '3.8'
services:
app:
build: . # Build from Dockerfile in current directory
ports:
- "8080:8080" # Map HTTP port
- "5005:5005" # Map debug port
environment:
- SPRING_PROFILES_ACTIVE=dev
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/myapp
volumes:
- ./target:/app/target # Mount target directory for hot reloads
depends_on:
- db # Ensure database starts first
db:
image: postgres:13
environment:
- POSTGRES_USER=postgres
- POSTGRES_PASSWORD=postgres
- POSTGRES_DB=myapp
ports:
- "5432:5432" # Expose PostgreSQL port
volumes:
- postgres-data:/var/lib/postgresql/data # Persist database data
volumes:
postgres-data: # Named volume for database persistence
이 파일의 특징은 다음과 같습니다.
- 서비스는 이름(예
db
: JDBC URL)으로 서로를 참조할 수 있습니다. - Docker Compose는 서비스에 대한 네트워크를 자동으로 만듭니다.
- Java 애플리케이션은
depends_on
때문에 데이터베이스가 시작될 때까지 기다립니다. - 데이터베이스 데이터는 명명된 볼륨을 사용하여 다시 시작할 때 유지됩니다.
일반적인 Docker Compose 명령
compose.yml 파일을 만든 후 다음 명령을 사용하여 애플리케이션을 관리합니다.
# Build images without starting containers
docker compose build
# Start all services defined in compose.yml
docker compose up
# Start in detached mode (run in background)
docker compose up -d
# View running containers managed by compose
docker compose ps
# View logs from all containers
docker compose logs
# View logs from a specific service
docker compose logs app
# Stop all services
docker compose down
# Stop and remove volumes (useful for database resets)
docker compose down -v
개발 워크플로
Docker Compose를 사용하는 일반적인 Java 개발 워크플로에는 다음 단계가 포함되어 있습니다.
- compose.yml 파일 및 Dockerfile을 만듭니다.
- 실행
docker compose up
하여 모든 서비스를 시작합니다. - Java 코드를 변경합니다.
- 애플리케이션을 다시 빌드합니다. 구성에 따라 컨테이너를 다시 시작해야 할 수 있습니다.
- 컨테이너화된 환경의 변경 내용을 테스트합니다.
- 완료되면
docker compose down
을(를) 실행하세요.
Docker를 사용하여 단일 컨테이너 실행
상호 연결된 서비스가 여러 개 필요하지 않은 경우 더 간단한 시나리오를 위해 이 명령을 사용하여 docker run
개별 컨테이너를 시작할 수 있습니다.
Java 애플리케이션에는 다음과 같은 Docker 명령이 일반적입니다.
# Run a Java application JAR directly
docker run -p 8080:8080 myapp:latest
# Run with environment variables
docker run -p 8080:8080 -e "SPRING_PROFILES_ACTIVE=prod" myapp:latest
# Run in detached mode (background)
docker run -d -p 8080:8080 myapp:latest
# Run with a name for easy reference
docker run -d -p 8080:8080 --name my-java-app myapp:latest
# Run with volume mount for persistent data
docker run -p 8080:8080 -v ./data:/app/data myapp:latest
컨테이너화된 애플리케이션 디버그
컨테이너화된 Java 애플리케이션 디버깅은 코드가 컨테이너 내의 격리된 환경에서 실행되기 때문에 어려운 경우가 있습니다.
표준 디버깅 방법이 항상 직접 적용되는 것은 아니지만 올바른 구성을 사용하면 애플리케이션에 대한 원격 디버깅 연결을 설정할 수 있습니다. 이 섹션에서는 디버깅을 위해 컨테이너를 구성하고, 개발 도구를 실행 중인 컨테이너에 연결하고, 일반적인 컨테이너 관련 문제를 해결하는 방법을 보여 줍니다.
원격 디버깅 설정
컨테이너화된 Java 애플리케이션을 디버깅하려면 디버그 포트를 노출하고 연결하도록 IDE를 구성해야 합니다. 다음 단계를 사용하여 이러한 작업을 수행할 수 있습니다.
디버깅을 사용하도록 설정하려면 다음 콘텐츠를 포함하도록 Dockerfile 을 수정합니다.
비고
대신 컨테이너 시작 명령을 수정할 수 있습니다.
FROM mcr.microsoft.com/java/jdk:21-zulu-ubuntu WORKDIR /app COPY target/*.jar app.jar EXPOSE 8080 5005 ENTRYPOINT ["java", "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005", "-jar", "app.jar"]
다음 예제와 같이 디버그 포트에 연결하도록 Visual Studio Code의 launch.json 파일을 구성합니다.
{ "version": "0.2.0", "configurations": [ { "type": "java", "name": "Debug in Container", "request": "attach", "hostName": "localhost", "port": 5005 } ] }
호스트에 매핑된 포트
5005
로 컨테이너를 시작한 다음 Visual Studio Code에서 디버거를 시작합니다.
컨테이너 문제 해결
컨테이너가 예상대로 작동하지 않는 경우 앱의 로그를 검사하여 문제를 조사할 수 있습니다.
다음 명령을 사용하여 애플리케이션 문제를 해결합니다. 이러한 명령을 실행하기 전에 자리 표시자(<...>
)를 사용자 고유의 값으로 바꿔야 합니다.
# View logs
docker logs <CONTAINER_ID>
# Follow logs in real-time
docker logs -f <CONTAINER_ID>
# Inspect container details
docker inspect <CONTAINER_ID>
# Get a shell in the container
docker exec -it <CONTAINER_ID> bash
Java 관련 문제의 경우 다음 예제와 같이 더 나은 진단을 위해 JVM 플래그를 사용하도록 설정합니다.
ENTRYPOINT ["java", "-XX:+PrintFlagsFinal", "-XX:+PrintGCDetails", "-jar", "app.jar"]
다음 표에는 일반적인 문제 및 해당 솔루션이 나와 있습니다.
오류 | 가능한 솔루션 |
---|---|
메모리가 부족합니다. | 컨테이너 메모리 제한 늘리기 |
연결 제한 시간 | 네트워크 구성에서 오류를 확인합니다. 포트 및 라우팅 규칙을 확인합니다. |
사용 권한 문제 | 파일 시스템 권한을 확인합니다. |
클래스 경로 문제 | JAR 구조 및 종속성을 확인합니다. |
Java 컨테이너 최적화
컨테이너의 Java 애플리케이션은 최적의 성능을 위해 특별히 고려해야 합니다. JVM은 컨테이너가 일반화되기 전에 설계되었습니다. 컨테이너를 사용하면 제대로 구성되지 않은 경우 리소스 할당 문제가 발생할 수 있습니다.
메모리 설정을 미세 조정하고, 이미지 크기를 최적화하고, 가비지 수집을 구성하여 컨테이너화된 Java 애플리케이션의 성능과 효율성을 크게 향상시킬 수 있습니다. 이 섹션에서는 메모리 관리, 시작 시간 및 리소스 사용률에 중점을 두고 Java 컨테이너에 대한 필수 최적화에 대해 설명합니다.
컨테이너의 JVM 메모리 구성
JVM은 Java 8에서 컨테이너 메모리 제한을 자동으로 검색하지 않습니다. Java 9 이상의 경우 컨테이너 인식은 기본적으로 사용하도록 설정됩니다.
다음 예제와 같이 컨테이너 제한을 준수하도록 JVM을 구성합니다.
FROM mcr.microsoft.com/java/jre:21-zulu-ubuntu
WORKDIR /app
COPY target/*.jar app.jar
EXPOSE 8080
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]
다음 JVM 플래그는 컨테이너화된 애플리케이션에 중요합니다.
-
-XX:MaxRAMPercentage=75.0
; 최대 힙을 사용 가능한 메모리의 백분율로 설정합니다. -
-XX:InitialRAMPercentage=50.0
; 초기 힙 크기를 설정합니다. -
-Xmx
및-Xms
. 이러한 플래그도 사용할 수 있지만 고정 값이 필요합니다.
프로덕션 배포 준비
컨테이너화된 Java 애플리케이션을 프로덕션으로 이동하려면 기본 기능 이상의 고려 사항이 필요합니다.
프로덕션 환경에는 강력한 보안, 신뢰할 수 있는 모니터링, 적절한 리소스 할당 및 구성 유연성이 필요합니다.
이 섹션에서는 프로덕션 사용을 위해 Java 컨테이너를 준비하는 데 필요한 필수 사례 및 구성에 대해 설명합니다. 이 섹션에서는 애플리케이션이 프로덕션 환경에서 안정적으로 실행되도록 보안, 상태 검사 및 구성 관리에 중점을 둡니다.
보안 모범 사례
다음 방법을 사용하여 컨테이너화된 Java 애플리케이션을 보호합니다.
기본 보안 컨텍스트입니다. 다음 예제와 같이 루트가 아닌 사용자로 애플리케이션을 실행합니다.
FROM mcr.microsoft.com/java/jre:21-zulu-ubuntu WORKDIR /app COPY target/*.jar app.jar RUN addgroup --system javauser && adduser --system --ingroup javauser javauser USER javauser ENTRYPOINT ["java", "-jar", "app.jar"]
사전에 문제를 찾습니다. 다음 명령을 사용하여 컨테이너 이미지에서 취약성을 정기적으로 검사합니다.
docker scan myapp:latest
기본 이미지 신선도 기본 이미지를 최신 상태로 유지합니다.
비밀 관리. 적절한 비밀 관리를 구현합니다. 예를 들어 중요한 데이터를 애플리케이션에 하드 코딩하지 말고 가능하면 키 볼트를 사용합니다.
제한된 보안 컨텍스트. 모든 보안 컨텍스트에 최소 권한 원칙을 적용합니다.
파일 시스템 액세스. 가능한 경우 읽기 전용 파일 시스템을 사용합니다.
상태 검사 및 모니터링
프로브를 사용하여 애플리케이션 상태를 확인하여 애플리케이션이 올바르게 실행되고 있는지 확인합니다.
Spring Boot 애플리케이션의 경우 다음 예제와 같이 포괄적인 상태 엔드포인트에 대한 Actuator 종속성을 포함합니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
JSON과 같은 컨테이너 환경에 적합한 형식으로 로그를 출력하도록 애플리케이션을 구성합니다.
Azure Container Apps에 배포
이 섹션에서는 Azure Container Apps 배포를 위해 Java 컨테이너를 준비하는 방법을 안내하고 주요 구성 고려 사항을 강조 표시합니다.
Azure용 컨테이너 준비
포트 구성. 다음 예제와 같이 컨테이너가 Azure에서 제공하는 포트에서 수신 대기하는지 확인합니다.
FROM mcr.microsoft.com/java/jre:21-zulu-ubuntu WORKDIR /app COPY target/*.jar app.jar ENV PORT=8080 EXPOSE ${PORT} CMD java -jar app.jar --server.port=${PORT}
건강을 점검합니다. Azure의 활성 상태 및 준비 상태에 대한 헬스 프로브를 구현합니다.
로그 구성. 로깅을 출력으로 구성합니다
stdout
/stderr
.예기치 않은 작업에 대한 계획입니다. 시간 제한 구성을 사용하여 적절한 정상 종료 처리를 설정합니다. 자세한 내용은 Azure Container Apps의 애플리케이션 수명 주기 관리를 참조하세요.