[클라우드 네이티브 스프링 인 액션] 2-4. 스프링 부트 컨테이너화

2025. 6. 27. 17:48·Programming
  • 지금까지 REST API 노출하고 컨테이너로 실행되는 PostgreSQL 데이터베이스를 통해 데이터를 저장하는 카탈로그 서비스 애플리케이션을 개발했다.
  • 쿠버네티스 클러스터로 배포하기 전에 컨테이너 이미지로 패키징하고 수명주기를 관리하는 법을 알아야 한다.
  • 여러 개의 컨테이너로 작업할 때는 도커 CLI가 효율적이지 않기 때문에 도커 컴포즈를 활용해 여러 컨테이너와 컨테이너의 라이프사이클을 관리한다.

 

1. 도커에서 컨테이너 이미지로 작업하기

  • 도커 엔진은 클라이언트/서버 구조를 가지고 있다.
  • 도커 CLI는 도커 서버와의 상호작용을 위해 사용하는 클라이언트다.
  • 도커 서버는 도커 데몬을 통해 도커의 모든 리소스를 관리한다.
  • 서버는 컨테이너 저장소와도 연결해 이미지를 업로드하고 다운로드할 수 있다.

 

[1] 컨테이너 이미지 이해

  • 컨테이너 이미지 : 여러 개의 명령을 순서대로 실행한 결과물로, 각 명령의 실행한 결과로 레이어가 만들어진다.
  • 각 이미지는 여러 개의 레이어로 이루어져 있으며, 각 층은 해당 명령어를 실행한 결과 생성된 변경 사항을 나타낸다.
  • 이미지는 이 일련의 명령을 실행해 얻은 최종 결과물이고 컨테이너로 실행할 수 있다.
  • 이미지는 베이스 이미지를 기반으로 만들거나 아예 처음부터 만들 수도 있다.
  • 베이스 이미지를 기반으로 새로운 이미지를 생성하는 것이 가장 일반적이다.
  • ex. 우분투 이미지로 부터 시작해 변경 사항 적용하기
    • 우분투를 베이스 이미지로 사용한다.
    • 자바 런타임 환경을 설치한다.
    • java —version 명령을 실행한다.
  • 걱 명령어는 하나의 레이어를 생성하고 최종적으로 컨테이너 이미지를 만든다.
  • 컨테이너 이미지의 모든 레이어는 읽기 전용이다. 일단 적용되면 더 이상 수정할 수 없다. 무언가를 변경해야 하면 기존의 레이어위에 새로운 레이어를 적용해야 한다.
  • 상위에 적용된 변경 사항은 하위 레이어에 영향을 미치지 않는다. → 카피 온 라이트 (copy-on-write : 원본 항목의 복사본을 상위 계층에 만들고 이 복사본을 변경한다.)
  • 이미지가 컨테이너로 실행되면 컨테이너 레이어라고 부르는 마지막 한 레이어가 기존의 모든 레이어 위에 자동으로 적용된다. → 유일하게 쓰기가 가능한 레이어 (실행 중 생성되는 데이터를 저장함) → 휘발성이 있음 (컨테이너 삭제 시 전부 삭제됨)

 

[2] 도커파일을 통한 이미지 생성

  • OCI 형식에 따라 도커파일이라고 부르는 특정 파일에 명령어를 순서대로 나열해 컨테이너 이미지를 정의할 수 있다.
# 새 이미지에 대한 베이스 이미지로 우분투 22.04를 지정한다.
FROM ubuntu:22.04

RUN apt-get update && apt-get install -y default-jre

ENTRYPOINT ["java", "--version"]
  • ENTRYPOINT : 컨테이너 실행을 위한 진입점 (진입점을 지정하기 않으면 컨테이너가 실행되지 않는다.)
  • 가상 머신과 달리 컨테이너는 운영체제를 실행하기 위한 것이 아니라 작업을 실행하기 위한 것이다.
명령 설명 예
FROM 후속 명령어의 대상이 될 베이스 이미지를 정의한다. 도커파일에서 가장 처음에 와야 하는 명령이다. FROM ubuntu:22.04
LABEL 키-값 형식에 따라 이미지에 메타 데이터를 추가한다. LABEL 명령은 여러 번 정의할 수 있다. LABEL version=”1.2.1”
ARG 사용자가 빌드 시에 전달할 수 있는 변수를 정의한다. ARG 명령은 여러 번 정의할 수 있다. ARG JAR_FILE
RUN 기존 레이어 위에서 인자로 전달된 명령어를 실행해 새로운 레이어를 생성한다. RUN 명령은 여러 번 정의할 수 있다. RUN apt-get update && apt-get install -y default-jre
COPY 호스트 파일 시스템의 파일 또는 디렉토리를 컨테이너 내부의 파일 또는 디렉토리로 복사한다. COPY app-0.0.1.jar app.jar
USER 모든 후속 명령어 및 컨테이너 실행을 위한 사용자를 정의한다. USER sheldon
ENTRYPOINT 이미지가 컨테이너로 실행될 때 프로그램을 정의한다. 여러 개의 ENTRYPOINT 명령이 있으면 마지막 명령만 고려된다. ENTRYPOINT ["/bin/bash"]
CMD 실행 중인 컨테이너에 대한 기본 설정을 지정한다. ENTRYPOINT 명령이 정의되어 있으면 그 명령의 인수로 전달되고 그렇지 않으면 실행 파일도 가능하다. 도커 파일에 여러 개의 CMD 명령이 있으면 마지막 명령만 고려된다. CM[”sleep”, “10]

 

 

$ docker build -t my-java-image:1.0.0 .
$ docker images my-java-image
REPOSITORY      TAG       IMAGE ID       CREATED          SIZE
my-java-image   1.0.0     d32c658f145d   14 seconds ago   577MB
  • 각 이미지 레이어는 이전 이미지 레이어의 증분이며 도커는 모든 이미지 레이어를 캐시한다.
  • 한 레이어를 변경하고 이미지를 다시 빌드하면 해당 레이어와 그 이후의 레이어만 다시 생성된다.
  • 레지스트리에 저장된 이미지의 새 버전을 컨테이너로 실행하면 새로운 레이어만 다운로드 받기 때문에 런타임 성능이 향상된다.
  • 자주 변경될 가능성이 많을수록 도커 파일의 뒤에 오도록 하는 것이 좋다.
  • docker run 명령으로 실행할 수 있는데, 이 명령은 컨테이너를 시작하고 도커 파일에서 진입점으로 지정된 프로세스를 실행한다.
$ docker run --rm my-java-image:1.0.0
openjdk 11.0.27 2025-04-15
OpenJDK Runtime Environment (build 11.0.27+6-post-Ubuntu-0ubuntu122.04)
OpenJDK 64-Bit Server VM (build 11.0.27+6-post-Ubuntu-0ubuntu122.04, mixed mode)

 

 

[3] 깃허브 컨테이너 저장소로 이미지 저장

  • 자신의 이미지를 저장하려면 도커 허브 또는 애저 컨테이너 저장소처럼 클라우드 공급 업체가 제공하는 저장소 중 하나를 사용할 수 있다.
  • 현재 프로젝트는 깃허브 컨테이너 저장소를 사용할 것이다.
  • 깃허브 개인 토큰 생성 : repo 전체, write:package
$ docker login ghcr.io
username : github_name
password : github_token
  • 컨테이너 이미지는 OCI 호환 컨테이너 저장소에서 채택한 공통의 명명 규칙을 따르는데, <container_registry>/<namespace>/<name>[:<tag>]: 형식이다.
  • 컨테이너 저장소 : 이미지가 저장되는 컨테이너 저장소의 호스트 이름을 의미한다. 도커 엔진은 저장소를 지정하지 않으면 docker.io를 이미지 이름 앞에 추가하기 때문에 깃허브 컨테이너 저장소를 사용할 때는 호스트 이름인 ghcr.io를 명시적으로 지정해야 한다.
  • 네임 스페이스 : 도커 허브나 깃허브 컨테이너 저장소를 사용할 때는, 소문자로 된 도커 또는 깃허브의 사용자 이름을 네임 스페이스로 사용한다. 다른 저장소에서는 저장소에 대한 경로일 수도 있다.
  • 이름과 태그 : 이미지 이름은 이미지의 모든 버전을 포함하는 저장소를 의미한다. 특정 버전을 선택하기 위한 태그가 그 뒤에 올 수 있고, 태그가 없는 경우에는 기본 설정으로 latest 태그가 사용된다.
  • 내가 만든 이미지를 깃허브 컨테이너 저장소에 업로드할 때는 [ghcr.io/<github-username>/<image_name>](http://ghcr.io/<github-username>/<image_name>) 형식을 따른다.
  • 이전에 my-java-image:1.0.0 라는 이름으로 이미지를 만들었으므로 컨테이너 저장소에 저장하기 전에 이름을 완전하게 지정해야 한다. docker tag 명령어를 사용한다.
$ docker tag my-java-image:1.0.0 ghcr.io/suji-j/my-java-image:1.0.0
$ docker push ghcr.io/<github_username>/my-java-image:1.0.0 #깃허브 컨테이너 저장소로 푸시한다.

 


 

2. 스프링 부트 애플리케이션을 컨테이너 이미지로 패키지

  • 카탈로그 서비스를 도커 컨테이너로 실행할 이미지로 만든다.

 

[1] 스프링 부트의 컨테이너화를 위한 준비

  • 스프링 부트 애플리케이션을 컨테이너 이미지로 패키징한다는 것은 고립된 콘텍스트에서 계산 리소스와 네트워크를 가지고 애플리케이션을 실행한다는 것을 의미한다.
    • 어떻게 네트워크를 통해 애플리케이션에 도달할 수 있을까?
    • 어떻게 다른 컨테이너와 연결해 상호작용할 수 있을까?

 

(1) 포트 전달을 사용한 애플리케이션 서비스 노출

  • 카탈로그 서비스를 컨테이너로 실행할 때 애플리케이션의 서비스를 노출하는 8080 포트를 로컬 컴퓨터의 8080 포트로 매핑했다. → http://localhost:8080 에서 애플리케이션을 사용할 수 있었다. (== 포트 포워딩, 포트 매핑, 포트 공개) → 외부에서 컨테이너화된 애플리케이션을 액세스하기 위해 사용한다.
  • 기존 설정 상 컨테이너는 도커 호스트 내부의 격리된 네트워크로 연결한다.
  • 자신의 로컬 네트워크에서 컨테이너로 연결하려면 포트 매핑을 명시적으로 설정해야 한다.
  • 사용자 — GET http://localhost:8080 → 도커 네트워크 8080 -매핑→ 컨테이너 8080

 

(2) 서비스 발견을 위한 도커 내장 DNS 서버의 사용

  • 카탈로그 서비스 애플리케이션은 jdbc:postgresql:/localhost:5432 URL을 통해 컨테이너로 실행 중인 PostgreSQL 데이터베이스 서버에 액세스할 수 있었는데 포트포워딩 덕분에 그렇게 할 수 있었다.
  • 카탈로그 서비스를 컨테이너로 실행한다면 localhost는 더 이상 자신의 로컬 컴퓨터가 아닌 컨테이너 내부를 나타내기 때문에 더 이상 데이터베이스 컨테이너에 연결할 수 없다.
  • 도커는 동일한 네트워크의 컨테이너가 호스트 이름이나 IP 주소가 아닌 컨테이너 이름을 사용해 서로를 찾을 수 있도록 해주는 DNS 서버를 내장하고 있다.
  • 카탈로그 서비스와 PostgreSQL이 IP 주소 또는 호스트 이름 대신 컨테이너 이름을 사용해 서로 연결할 수 있도록 네트워크를 만든다.
$ docker network create catalog-network
$ docker network ls
NETWORK ID     NAME                     DRIVER    SCOPE
8c40f7efa6de   catalog-network          bridge    local
  • 다음, PostgreSQL 컨테이너를 시작하면서 방금 만든 카탈로그 네트워크에 연결하도록 지정할 수 있다.
  • —net 인수를 사용하면 컨테이너는 인수로 지정된 네트워크에 가입하고 도커 내장 DNS 서버를 사용한다.
$ docker run -d \\
--name polar-postgres \\
--net catalog-network \\
-e POSTGRES_USER=user \\
-e POSTGRES_PASSWORD=password \\
-e POSTGRES_DB=polardb_catalog \\
-p 5432:5432 \\
postgres:14.4
$ docker container ls
CONTAINER ID   IMAGE                       COMMAND                   CREATED          STATUS          PORTS                    NAMES
68bf49c6a95d   postgres:14.4               "docker-entrypoint.s…"   58 seconds ago   Up 57 seconds   0.0.0.0:5432->5432/tcp   polar-postgres

 

 

[2] 도커파일로 스프링 부트 컨테이너화

  • 스프링 부트를 사용하면 런타임 환경을 제외하고 애플맄이션이 실행하기 위해 필요한 모든 것을 독립 실행 가능한 JAR로 패키징할 수 있다.
  • JAR 아티팩트 외에도 컨테이너 이미지에 필요한 것은 운영체제와 JRE 뿐이므로 컨테이너 이미지를 간단하게 만들 수 있다.
  • 로컬에서 사용했던 것과 동일한 OpenJDK 배포판인 이클립스 테무린17을 사용한다.
  • 카탈로그 서비스의 JAR 파일을 이미지에 복사해야 한다.
  • JRE에서 애플리케이션을 실행하는 명령으로 컨테이너의 진입점을 선언한다.
FROM eclipse-temurin:17  # JRE가 이미 설치되어 있는 이클립스 테무린 배포판 우분투 베이스 이미지

WORKDIR workspace # 현재 작업 폴더를 workspace로 변경

ARG JAR_FILE=build/libs/*.jar # 프로젝트에서 애플리케이션 JAR 파일의 위치를 지정하는 빌드 인수

COPY ${JAR_FILE} catalog-service.jar # 애플리케이션 JAR 파일을 로컬 머신에서 이미지 안으로 복사한다.

ENTRYPOINT ["java", "-jar", "catalog-service.jar"] # 애플리케이션을 실행하기 위한 컨테이너 진입점을 지정한다.
  • 카탈로그 서비스 애플리케이션을 JAR 아티팩트로 빌드해야 한다.
$ ./gradlew clean bootJar
  • 도커파일 스크립트는 기본적으로 그래들에서 사용하는 경로인 build/lobs/에서 애플리케이션 JAR 파일을 복사하도록 작성됐다. 그래들을 사용하는 경우 JAR_FILE 인수 없이 다음과 같은 명령으로 컨테이너 이미지를 만들 수 있다.
  • 버전이 지정되지 않았기 때문에 이미지가 자동으로 latest 태그로 표시된다.
$ docker build -t catalog-service .
$ docker images catalog-service
  • docker run 명령에 두 가지 인수를 추가하여 처리할 수 있다.
    • -p 9001:9001 : 컨테이너 내부의 9001 포트(카탈로그 서비스가 노출되면)를 로컬 호스트의 9001 포트에 매핑한다.
    • —net catalog-network : 카탈로그 서비스 컨테이너를 이전에 만든 카탈로그 네트워크로 연결해 PostgreSQL 컨테이너를 사용할 수 있다.
  • 카탈로그 서비스에 대한 spring.datasource.url 속성을 jdbc:postgresql:/localhost:5432/polardb_catalog로 지정했다.
  • localhost를 가르키기 때문에 컨테이너 내에서 작동하지 않는다.
  • 환경변수를 통해, 동일한 URL에서 localhost를 PostgreSQL 컨테이너 이름인 polar-postgres로 대체해 spring.datasource.url 속성을 덮어써야 한다.
$ docker run -d \\
--name catalog-service \\
--net catalog-network \\
-p 9001:9001 \\
-e SPRING_DATASOURCE_URL=jdbc:postgresql://polar-postgres:5432/polardb_catalog \\
-e SPRING_PROFILES_ACTIVE=testdata \\
-e SPRING_CLOUD_CONFIG_FAILFAST=false \\
catalog-service

 

 

[3] 프로덕션을 위한 컨테이너 이미지 빌드

  • 앞에서 만든 이미지를 개선하는 방법을 알아본다.

 

(1) 성능

  • 컨테이너 이미지를 만들 때 빌드할 때와 런타임의 성능을 고려해야 한다.
  • OCI 이미지의 특징인 계층화된 아키텍처를 통해 이미지를 만들 때 변경되지 않은 레이어를 캐싱하고 재사용할 수 있다.
  • 카탈로그 서비스 독립 실행형 JAR 파일을 이미지의 레이어로 복사했다. 결과적으로 애플리케이션에서 무언가를 변경할 때마다 전체 레이어를 다시 작성해야 한다.
  • ex. 애플리케이션에서 새 REST 엔드 포인트를 추가하는 경우 : 스프링 라이브러리와 의존성은 변경되지 않고 오직 애플리케이션 코드만 변경되지만 모든 것이 함께 패키징되어 있기 때문에 전체 레이블을 다시 빌드해야 한다. → 스프링 부트를 통해 개선할 수 있다.
  • 우버 JAR을 컨테이너 이미지에 넣는 것은 효율적이지 않다. JAR 아티팩트는 애플리케이션에서 사용되는 모든 의존성, 클래스 및 자원을 포함하고 있는 압축 아카이브이다. 이 모든 파일은 JAR 내에서 폴더의 계층 구조로 구성된다. 표준 JAR 아티팩트를 확장해 각 폴더를 다른 컨테이너 이미지 수준으로 배치할 수 있다.
  • 계층화된 JAR 모드를 사용해 패키징된 애플리케이션은 컨테이너 이미지와 유사하게 여러 레이어로 만들어진다.
  • 새로운 JAR 패키지를 사용할 때, JAR 아티팩트를 확장한 다음 각 JAR 레이어에 대해 다른 이미지 레이어를 만들 수 있다. 여기서 목표는 자주 변경되는 클래스를 자주 변경되지 않는 프로젝트 의존성 라이브러리와는 분리해 다른 계층에 두는 것이다.
  • 기본적으로 스프링 부트 애플리케이션은 다음과 같은 계층으로 이루어진 JAR 아티팩트로 패키징된다.
    • 의존성 계층 : 프로젝트에 추가된 모든 주요 의존성
    • 스프링 부트 로더 계층 : 스프링 부트 로더 컴포넌트가 사용하는 클래스
    • 스냅숏 의존성 계층 : 모든 스냅숏 의존성
    • 애플리케이션 계층 : 애플리케이션 클래스 및 리소스
  • 기존 애플리케이션에 새로운 REST 엔드 포인트를 추가하는 시나리오라면, 애플리케이션을 컨테이너화 할 때 application 계층만 작성하면 된다.
  • 도커 파일을 수정한다.
    • JAR 파일을 이미지로 복사하고 네 개의 계층으로 확장하기 위한 준비 작업을 수행한다.
    • 원래의 JAR 파일은 이미지에 있으면 안 된다.
    • 다단계 빌드 : 첫 번째 단계의 결과는 폐기 처분되고, 두 번째 단계에서 최종 컨테이너 이미지가 생성된다.
FROM eclipse-temurin:17 AS builder # 첫 번째 단계를 위한 OpenJDK 베이스 이미지
WORKDIR workspace
ARG JAR_FILE=build/libs/*.jar # 프로젝트 내에서 애플리케이션 JAR 파일의 위치를 지정하는 빌드 인수
COPY ${JAR_FILE} catalog-service.jar # 애플리케이션 JAR 파일을 로컬 머신에서 이미지의 'workspace' 폴더로 복사
RUN java -Djarmode=layertools -jar catalog-service.jar extract # 계층 JAR 모드를 적용해 아카이브에서 계층을 추출한다.

FROM eclipse-temurin:17 # 두 번째 단계를 위한 OpenJDK 베이스 이미지
WORKDIR workspace
COPY --from=builder workspace/dependencies/ ./ # 첫 번째 단계에서 추출한 JAR 계층을 두 번째 단계로 복사한다.
COPY --from=builder workspace/spring-boot-loader/ ./
COPY --from=builder workspace/snapshot-dependencies/ ./
COPY --from=builder workspace/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"] # 스프링 부트 런처를 사용해 우버 JAR 대신 계층으로 애플리케이션을 시작한다.

 

 

(2) 보안

  • 컨테이너는 기본적으로 루트 사용자로 실행되므로 잠재적으로 도커 호스트에 루트 액세스를 허용한다.
  • 최소 권한의 원칙에 따라 필요 이상의 권한을 갖지 않는 사용자를 만들어 이 사용자가 도커 파일에 정의된 진입점 프로세스를 실행하게 하면 위험을 완화할 수 있다.
FROM eclipse-temurin:17 AS builder
WORKDIR workspace
ARG JAR_FILE=build/libs/*.jar
COPY ${JAR_FILE} catalog-service.jar
RUN java -Djarmode=layertools -jar catalog-service.jar extract

FROM eclipse-temurin:17
RUN useradd spring # spring 이라는 이름의 유저를 만든다.
USER spring # spring을 현재 유저로 설정한다.
WORKDIR workspace
COPY --from=builder workspace/dependencies/ ./
COPY --from=builder workspace/spring-boot-loader/ ./
COPY --from=builder workspace/snapshot-dependencies/ ./
COPY --from=builder workspace/application/ ./
ENTRYPOINT ["java", "org.springframework.boot.loader.JarLauncher"]
  • 시크릿을 컨테이너 이미지에 저장하면 안 된다.
  • 도커 파일에서 최신 베이스 이미지와 라이브러리를 사용하는 것도 중요하다.
$ docker build -t catalog-service .
$ grype catalog-service

 

 

(3) 도커파일 대 빌드팩

  • 도커파일은 매우 강력하고 결과를 완벽하게 제어할 수 있다. 그러나 관리 및 유지보수에 추가적인 노력이 필요하다.
  • 클라우드 네이티브 빌드팩은 일관성, 보안, 성능 및 거버넌스에 중점을 둔 다른 접근법을 제공한다.
  • 개발자는 도커파일을 작성하지 않고도 애플리케이션 소스 코드에서 프로덕션 환경에 적합한 OCI 이미지를 자동으로 생성할 수 있다.

 

 

[4] 클라우드 네이티브 빌드팩을 이용한 스프링 부트 컨테이너화

  • 클라우드 네이티브 빌드팩 : CNCF가 ‘어느 클라우드에서나 실행할 수 있는 이미지로 애플리케이션 소스 코드를 변환’하기 위해 유지 관리하는 프로젝트
  • 빌드팩이 제공하는 기능
    • 애플리케이션 유형을 자동으로 감지해 도커파일 없이 패키지를 생성한다.
    • 다양한 언어와 플랫폼을 지원한다.
    • 캐시와 레이어를 통해 높은 성능을 갖는다.
    • 재현 가능한 빌드를 보장한다.
    • 보안 측면에서 모법 사례를 사용한다.
    • 프로덕션 환경에 적합한 이미지를 만든다.
    • 그랄VM을 사용해 네이티브 이미지의 빌드를 지원한다.
  • 컨테이너 생성 프로세스는 애플리케이션을 컨테이너화하는 방법에 대한 모든 정보를 가지고 있는 빌더 이미지에 의해 조정된다.
  • 이러한 정보는 애플리케이션의 특정 측면을 전담하는 빌드팩을 통해 제공된다.
  • 패키토 빌더 컴포넌트는 일련의 기본 빌드팩 시퀀스를 사용해 실제 빌드 작업을 수행한다.
  • 스프링 부트 플러그인에서 제공하는 빌드팩 통합은 카탈로그 서비스 프로젝트의 build.gradle 파일에서 설정할 수 있다.
bootBuildImage { // 빌드팩을 사용해 OCI 이미지를 빌드하기 위한 스프링 부트 플러그인 작업
    imageName = "${project.name}" // 빌드할 이미지의 이름, 프로젝트 설정에서 정의한 이름과 암묵적인 latest 태그를 사용한다.
    environment = ["BP_JVM_VERSION" : "17.*"] // 이미지에 설치할 JVM 버전
}
$ ./gradlew bootBuildImage
  • 카탈로그 서비스를 컨테이너로 다시 실행한다.
$ docker run -d \\
--name polar-postgres \\
--net catalog-network \\
-e POSTGRES_USER=user \\
-e POSTGRES_PASSWORD=password \\
-e POSTGRES_DB=polardb_catalog \\
-p 5432:5432 \\
postgres:14.4

$ docker run -d \\
--name catalog-service \\
--net catalog-network \\
-p 9001:9001 \\
-e SPRING_DATASOURCE_URL=jdbc:postgresql://polar-postgres:5432/polardb_catalog \\
-e SPRING_PROFILES_ACTIVE=testdata \\
-e SPRING_CLOUD_CONFIG_FAILFAST=false \\
catalog-service
  • 브라우저 창을 열고 http://localhost:9001/books 로 애플리케이션을 호출하고 제대로 작동하는지 확인한다.
$ docker rm -f catalog-service polar-postgres
$ docker network rm catalog-network
  • 스프리 부트 2.4 이후로는 스프링 부트 플러그인 설정을 통해 이미지를 컨테이너 저장소로 직접 저장할 수도 있다. → 특정 컨테이너 저장소로 인증하기 위한 설정을 build.gradle 파일에 추가한다.
bootBuildImage {
    imageName = "ghcr.io/<github_username>/catalog-service:latest"
    environment = ["BP_JVM_VERSION" : "17.*"]

    docker { # 컨테이너 저장소 연결을 설정하기 위한 섹션
        publishRegistry { # 컨테이너 저장소 인증을 설정하기 위한 섹션, 값은 그래들 속성을 통해 전달된다.
            username = project.findProperty("registryUsername")
            password = project.findProperty("registryToken")
            url = project.findProperty("registryUrl")
        }
    }
}
  • 스프링 부트 플러그인을 사용하면 암호를 통해 저장소로 인증할 수 있지만 암호 대신 토큰을 사용해야 한다.
  • bootBuildImage 작업을 실행해 새로운 이미지를 만들고 저장할 수 있다.
  • —imageName 인수 : 컨테이너 저장소가 요구하는 대로 완전한 형식의 이미지 이름을 정의할 수 있다.
  • —publishImage 인수 : 스프링 부트 플러그인이 컨테이너 저장소에 이미지를 직접 푸시하도록 지시할 수 있다.
$ ./gradlew bootBuildImage \\
--publishImage \\
-PregistryUrl=ghcr.io \\
-PregistryUsername=suji-j \\
-PregistryToken=<github-token>
  • 깃허브 → 프로필 페이지 → 패키지 섹션 → catalog-service 항목 생성됨
  • 아직 catlog-service 패키지는 소스 코드 저장소에 연결되지 않았다.
  • 지금은 패키지를 삭제한다. 깃허브 액션을 사용해 이미지를 저장할 때 충돌이 일어난다.

 


 

3. 도커 컴포즈를 통한 스프링 부트 컨테이너의 관리

  • 도커 컴포즈를 사용하면 시스템을 구성하는 모든 애플리케이션과 서비스를 한 곳에서 정의하고 라이프사이클을 함께 관리할 수 있다.

 

 

[1] 도커 컴포즈를 통한 컨테이너 라이플사이클 관리

  • docker-compose.yml 파일의 루트 섹션
    • version : 사용할 구문을 지정
    • services : 실행할 모든 컨테이너에 대한 사양을 지정
  • polar-deplyment 저장소 새로 만든다. → docker 폴더도 생성한다. → docker-compose.yml 파일 생성
version: "3.8" # 도커 컴포즈 구문 버전
services: # 실행할 모든 컨테이너를 나열하는 섹션

  catalog-service: # catalog-service 컨테이너를 기술하는 섹션
    depends_on:
      - polar-postgres
    image: "catalog-service" # 컨테이너를 실행하는 데 사용할 이미지
    container_name: "catalog-service" # 컨테이너의 이름
    ports: # 포트 매핑 목록을 위한 섹션
      - 9001:9001
    environment: # 환경 변수를 나열하는 섹션
      - BPL_JVM_THREAD_COUNT=50 # 메모리 계싼을 위한 스레드의 수를 설정하는 패키토 빌드팩 환경 변수
      - SPRING_DATASOURCE_URL=jdbc:postgresql://polar-postgres:5432/polardb_catalog
      - SPRING_PROFILES_ACTIVE=testdata # testdata 프로파일을 활성화

  polar-postgres: # poalr-postgres 컨테이너를 기술하는 섹션
    image: "postgres:14.12"
    container_name: "polar-postgres"
    ports:
      - 5432:5432
    environment:
      - POSTGRES_USER=user
      - POSTGRES_PASSWORD=password
      - POSTGRES_DB=polardb_catalog
  • 도커 컴포즈는 두 개의 컨테이너를 기본 설정상 같은 네트워크에 연결하기 때문에 이전처럼 명시적으로 네트워크를 지정할 필요가 없다.
$ docker compose up -d
  • http://localhost:9001/books 로 작동 확인

 

 

[2] 스프링 부트 컨테이너 디버깅

  • IDE에서 스프링 부트 애플리케이션을 표준 자바로 실행할 때 디버그 모드로 실행하도록 지정할 수 있다.
  • 그러면 IDE는 애플리케이션을 실행하는 로컬 자바 프로세스에 디버거를 부착한다.
  • 그러나 컨테이너 내에서 실행하면 프로세스가 로컬 컴퓨터에서 실행되지 않기 때문에 IDE가 더 이상 그렇게 할 수 없다.
  • 컨테이너에서 실행되는 스프링 부트 애플리케이션을 로컬로 실행할 때와 거의 동일하기 디버깅할 수 있다.
  • 먼저 컨테이너 내부의 JVM에게 특정 포트를 통해 디버그 연결을 듣도록 지시해야 한다. 패키토 빌드팩이 생성한 컨테이너 이미지는 디버그 모드에서 애플리케이션을 실행하기 위한 전용 환경 변수를 지원한다.
  • 그 다음 디버그 포트를 컨테이너 외부로 노출하면 IDE가 그 포트를 통해 연결할 수 있다.
version: "3.8"
services:

  catalog-service:
    depends_on:
      - polar-postgres
    image: "catalog-service"
    container_name: "catalog-service"
    ports:
      - 9001:9001
      - 8001:8001 # JVM이 디버그 연결을 듣는 포트
    environment:
      - BPL_JVM_THREAD_COUNT=50
      - BPL_DEBUG_ENABLED=true # 디버그 연결을 수확하기 위한 JVM 설정을 활성화
      - BPL_DEBUG_PORT=8801 # 디버그 연결은 8001 포트를 통해 받는다.
      - SPRING_DATASOURCE_URL=jdbc:postgresql://polar-postgres:5432/polardb_catalog
      - SPRING_PROFILES_ACTIVE=testdata
$ docker compose up -d
  • 도커 컴포즈는 PostgreSQL 컨테이너의 설정이 변경되지 않았다는 것을 인식하고 PostgreSQL 컨테이너에 대해서는 아무 일도 하지 않는다. 반면에 카탈로그 서비스 컨테이너는 새로운 설정으로 다시 로드한다.
$ docker compose down

 


 

4. 배포 파이프라인 : 패키지 및 등록

  • 커밋 단계 : 개발자가 새로운 코드를 메인 브랜치에 커밋하면 빌드, 단위 테스트, 통합 테스트, 정적 코드 분석, 패키지 같은 여러 과정을 거치게 된다.
  • 이 단계의 마지막에 실행 가능한 애플리케이션 아티팩트를 아티팩트 저장소로 등록한다. → 이 아티팩트가 릴리스 후보다.

 

 

[1] 커밋 단계에서 릴리스 후보 빌드

  • 실행 가능한 아티팩트는 컨테이너 저장소에 등록될 컨테이너 이미지다.
  • 아티팩트는 단 한 번만 생성한다는 것이 지속적 전달의 핵심적인 아이디어다.
  • 커밋 단계의 마지막 과정에서 배포 파이프라인은 다음 단계에서 사용할 컨테이너 이미지를 생성한다.
  • 개발자 푸시 - 소스 코드 체크 아웃 - 소스 코드 취약성 스캔 - 빌드 - 단위 테스트, 통합 테스트 - 컨테이너 이미지로 패키지 - OCI 이미지 취약성 스캔 - 컨테이너 이미지 등록 - 컨테이너 저장소

 

 

[2] 깃허브 액션을 통한 컨테이너 이미지 등록

  • 깃허브 액션은 깃허브 저장소에서 직접 소프트웨어 워크플로를 자동화하는 데 사용할 수 있는 엔진이다.
  • 워크플로 정의는 깃허브의 저장소 루트 아래의 .github/workflow/ 디렉터리에 저장된다.
  • catalog-service의 commit-stage.yml을 열고 컨테이너 이미지를 만들 때 필요한 환경 변수를 정의한다.
env:
  REGISTRY: ghcr.io # 깃허브 컨테이너 저장소를 사용한다.
  IMAGE_NAME: suji-j/catalog-service # 이미지의 이름
  VERSION: latest # 새 이미지를 latest로 태깅한다.
  • 카탈로그 서비스를 컨테이너 이미지로 패키징하기 위해 로컬에서 사용했던 것과 동일한 전략인 스프링 부트 그래들 플러그인에서 제공하는 빌드팩 통합을 사용한다.
  package: # 잡의 고유 식별자
    name: Package and Publish
    if: ${{ github.ref == 'refs/heads/main' }} # 잡을 main 브랜치에 대해서만 실행한다.
    needs: [ build ] # build 잡이 성공적으로 수행된 경우에만 이 잡을 실행한다.
    runs-on: ubuntu-22.04 # 우분투 22.04에서 잡을 실행
    permissions:
      contents: read # 현재 깃 저장소를 체크아웃하기 위한 권한
      packages: write # 깃허브 컨테이너 저장소로 이미지를 업로드하기 위한 권한
      security-events: write # 깃허브로 보안 이벤트를 제출하기 위한 권한
    steps:
      - name: Checkout source code
        uses: actions/checkout@v3 # 현재 깃 저장소(catalog-service)를 체크아웃한다.
      - name: Set up JDK
        uses: actions/setup-java@v3 # 자바 런타임을 설치하고 설정한다.
        with:
          distribution: temurin
          java-version: 17
          cache: gradle
      - name: Build container image
        run: |
          chmod +x gradlew
          ./gradlew bootBuildImage \\ # 컨테이너 이미지를 빌드하고 릴리스 후보를 위한 이름을 정의하기 위해 스프링 부트의 빌드팩 통합을 사용한다.
            --imageName ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }}
  • 애플리케이션을 컨테이너 이미지로 패키징한 후에는, 그라이프를 사용해 취약점을 스캔하고 깃허브에 보고서를 제출하기 위한 세부 단계를 추가한다.
  • 마지막으로 컨테이너 저장소로 인증하고 릴리스 후보 이미지를 푸시한다.
      - name: OCI image vulnerability scanning
        uses: anchore/scan-action@v3 # 취약성 검사를 위해 그라이프를 사용해 릴리스 후보 이미지를 스캔한다.
        id: scan
        with: # 스캔할 이미지는 릴리스 후보다.
          image: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }}
          fail-build: false # 이미지에서 취약점이 발견되더라도 빌드를 실패로 만들지 않는다.
          severity-cutoff: high
      - name: Upload vulnerability report
        uses: github/codeql-action/upload-sarif@v3 # 깃허브로 보안 취약성 리포트를 업로드한다.
        if: success() || failure()
        with:
          sarif_file: ${{ steps.scan.outputs.sarif }}
      - name: Log into container registry
        uses: docker/login-action@v3 # 깃허브 컨테이너 저장소와 인증한다.
        with:
          registry: ${{ env.REGISTRY }} # 저장소 정보는 환경 변수로 정의된다.
          username: ${{ github.actor }} # 깃허브 액션이 제공하는 현재 사용자의 깃허브 유저명
          password: ${{ secrets.GITHUB_TOKEN }} # 저장소와 인증하기 위해 필요한 토큰
      - name: Publish container image # 릴리스 후보를 저장소로 푸시한다.
        run: docker push ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ env.VERSION }}
  • 워크플로 실행을 완료하면 packages가 생성된다.

  • 지금까지 REST API를 노출시키고 관계형 데이터베이스와 상호작용하는 스프링 부트 애플리케이션을 만들었고, 애플리케이션에 대한 단위 및 통합 테스트를 작성해봤다.
  • 플라이웨이로 데이터베이스 스키마를 처리하게 함으로써 프로덕션 환경에 대한 준비를 끝냈다.
  • 마지막으로 컨테이너 내에서 모든 것을 실행하고 이미지 생성, 도커, 클라우드 네이티브 빌드팩 및 취약성 검색을 구현했다.
  • 다음 편에선, 쿠버네티스를 좀 더 자세히 공부하겠다!
반응형

'Programming' 카테고리의 다른 글

[클라우드 네이티브 스프링 인 액션] 3-1. 리액티브 스프링: 복원력과 확장성  (0) 2025.07.07
[클라우드 네이티브 스프링 인 액션] 2-5. 스프링 부트를 위한 쿠버네티스 기초  (2) 2025.07.02
[클라우드 네이티브 스프링 인 액션] 2-3. 클라우드에서 데이터 저장과 관리  (4) 2025.06.25
[클라우드 네이티브 스프링 인 액션] 2-2. 외부화 설정 관리  (6) 2025.06.19
[클라우드 네이티브 스프링 인 액션] 2-1. 클라우드 네이티브 개발  (6) 2025.06.13
'Programming' 카테고리의 다른 글
  • [클라우드 네이티브 스프링 인 액션] 3-1. 리액티브 스프링: 복원력과 확장성
  • [클라우드 네이티브 스프링 인 액션] 2-5. 스프링 부트를 위한 쿠버네티스 기초
  • [클라우드 네이티브 스프링 인 액션] 2-3. 클라우드에서 데이터 저장과 관리
  • [클라우드 네이티브 스프링 인 액션] 2-2. 외부화 설정 관리
ssu_dev
ssu_dev
  • ssu_dev
    ssu
    ssu_dev
  • 전체
    오늘
    어제
    • 분류 전체보기 (98)
      • Cloud (10)
      • HCI (2)
      • Algorithm (54)
      • Programming (13)
      • Computer Science (5)
      • System (6)
      • Trouble Shooting (6)
      • Work (1)
  • 블로그 메뉴

    • 홈
    • 태그
  • 링크

  • 인기 글

  • 태그

    투포인터
    Java
    EKS
    node scaling
    Pod Scheduling
    자료구조
    Karpenter
    cs
    BOJ
    bfs
    Stack
    K8s
    dfs
    priorityqueue
    sort
    OS
    플로이드 워셜
    Deque
    구현
    docker
  • 최근 글

  • hELLO· Designed By정상우.v4.10.1
ssu_dev
[클라우드 네이티브 스프링 인 액션] 2-4. 스프링 부트 컨테이너화
상단으로

티스토리툴바