땃쥐네

[Kubernetes] 스프링부트 파드 실행 (+ ImagePullPolicy 주의점) 본문

DevOps/Kubernetes

[Kubernetes] 스프링부트 파드 실행 (+ ImagePullPolicy 주의점)

ttasjwi 2025. 3. 2. 10:59

이전 글에서는 파드를 생성하여 외부에서 접속하는 방법을 다뤄봤는데

예시가 부족하므로 스프링부트 파드를 추가적으로 실행해 접속해보도록 할 것이다.


스프링부트 프로젝트 생성

 

Spring Initializr 를 통해 Spring Boot 프로젝트를 생성해봤다.

Project 는 Gradle - Kotlin 으로, Language 는 Kotlin, Spring Boot 버전은 SNAPSHOT 이나 M2가 아닌 최신 버전

Dependencies 로는 Spring Web 을 설정했다.

 

 

Java 버전은 21

 

Generate 버튼을 클릭해 생성되는 압축파일을 풀어서 프로젝트를 생성한다.

 

 

 

압축을 풀면 이런 폴더가 생성되는데 이 경로를 IntelliJ 를 통해 열어서 수정한다.

 

package hello.example

import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController

@RestController
class HelloController {

    @GetMapping("/")
    fun hello(): String {
        return "Hello, World!!!"
    }
}

 

간단하게 Hello, World 문자열을 반환하는 API 를 만들었다.

 

 

일단 로컬에서 실행했을 때 잘 돌아가는 지 확인해보기 위해 IDE 를 통해 실행해봤다.

애플리케이션을 실행해서 http://localhost:8080 으로 접속 시 Hello World 문자열이 응답으로 오면 성공.

 

$ ./gradlew clean build
OpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended

BUILD SUCCESSFUL in 7s
9 actionable tasks: 9 executed



이 상태로, 프로젝트 루트 경로에서 ./gradlew clean build 명령을 통해 프로젝트를 jar 로 빌드한다.

빌드에 성공하면 build/libs 에 jar 파일이 생성되어 있다.


도커 이미지 빌드

FROM amazoncorretto:21-alpine3.20-jdk
COPY build/libs/*SNAPSHOT.jar app.jar

ENTRYPOINT ["java", "-jar", "app.jar"]

 

프로젝트 루트 경로에서 Dockerfile 이름으로 파일을 생성하고 위 내용을 작성한다.

위에서 만들어진 jar 파일을 이미지 구성에 사용하는데,

이 Dockerfile을 통해 이미지를 만들었다면, 추후 컨테이너 실행 시 해당 jar 파일을 실행을 해야함을 기술한 것이다.

 

docker build -t spring-server .

 

Dockerfile 작성 후 해당 경로에서 이 명령을 작성하면 도커 이미지가 빌드된다.

 

$ docker images
REPOSITORY                                                   TAG                                                                           IMAGE ID       CREATED         SIZE
spring-server                                                latest                                                                        c29b81d3fc7e   4 seconds ago   343MB

 

이 과정을 거치면 spring-server 이미지가 빌드되고 로컬 리포지토리에 저장된다.


파드 매니페스트 파일 작성

spring-pod.yaml

apiVersion: v1
kind: Pod

# 파드 기본 정보
metadata:
  name: spring-pod # 파드명

# 파드 상세 정보
spec:
  containers:
    - name: spring-container # 파드에서 사용할 컨테이너명
      image: spring-server # 컨테이너 실행을 위해 사용할 이미지
      ports:
        - containerPort: 8080 # 컨테이너가 파드 내에서 실행될 포트(문서화용, 실제 8080 에서 실행함을 보장하진 않음)

 

파드를 구성하기 위한 매니페스트 파일을 작성해보자. 

기존에 tomcat-pod 를 작성했던 것을 응용해서 방금 만들어둔

spring-server 컨테이너를 실행하는 파드를 매니페스트 파일을 통해 기술했다.


실행시도

$ kubectl apply -f spring-pod.yaml
pod/spring-pod created

 

kubectl apply -f 명령을 통해, 방금 작성한 매니페스트 파일을 기반으로 파드를 생성해보면...

 

 

STATUS 쪽에서 ImagePullBackOff 오류가 발생한다.

이미지를 Pull 받아오는 과정 중에 문제가 생긴 것이다.

 

이 문제의 원인은 ImagePullPolicy 라는 부분에 있다.


 

이미지 풀 정책(ImagePullPolicy)

이미지 풀 정책(Image Pull Policy)이란 쿠버네티스가 yaml 파일을 읽어들여 파드를 생성할 때,
이미지를 어떻게 Pull을 받아올 건지에 대한 정책을 의미한다.

 

이 정책은 3가지 중 하나를 선택할 수 있는데, 그 종류는 Always, IfNotPresent, Never 이다.

 

Always

로컬에서 이미지를 가져오지 않고, 무조건 레지스트리(= DockerHub, ECR 과 같은 원격 이미지 저장소) 에서 가져온다.


IfnotPresent
로컬에서 이미지를 먼저 가져온다.로컬에서 이미지가 없는 경우에만 레지스트리에서 가져온다.


Never
로컬에서만 이미지를 가져온다.

 

이러한 이미지 풀 정책은 매니페스트 파일을 통해 설정할 수 있는데, 별도로 설정을 하지 않으면 다음 조건에 따라 설정된다.

 

기본 이미지 풀 정책

이미지의 태그가 lastest 이거나 명시되지 않은 경우: imagePullPolicy가 Always로 설정된다.
이미지의 태그가 latest 가 아닌 경우: imagePullPolicy 가 IfNotPresent 로 설정된다.


이미지 풀 정책 설정방법

apiVersion: v1
kind: Pod

metadata:
  name: spring-pod

spec:
  containers:
    - name: spring-container
      image: spring-server
      imagePullPolicy: IfNotPresent # Always / IfNotPresent, Never 중 1개 선택
      ports:
        - containerPort: 8080

 

이미지 풀 정책은 spec.containers 아래의 컨테이너 설명부에서 imagePullPolicy 를 통해 설정 가능하다.

Always, IfNotPresent, Never 중 하나를 선택하면 된다.


매니페스트 파일을 다시 살펴보기

apiVersion: v1
kind: Pod

# 파드 기본 정보
metadata:
  name: spring-pod # 파드명

# 파드 상세 정보
spec:
  containers:
    - name: spring-container # 파드에서 사용할 컨테이너명
      image: spring-server # 컨테이너 실행을 위해 사용할 이미지
      ports:
        - containerPort: 8080 # 컨테이너가 파드 내에서 실행될 포트(문서화용, 실제 8080 에서 실행함을 보장하진 않음)

 

기본 매니페스트 파일에서는 image 지정시, spring-server 로 지정하고(태그 미지정)

imagePullPolicy 를 별도로 설정하지 않았다.

 

이 경우, 이미지 태그가 지정되어 있지 않아, latest로 간주되고, imagePullPolicy 가 Always 로 설정되진다.

이렇게 되면 로컬에서 이미지를 가져오지 않고, 무조건 레지스트리(= DockerHub, ECR 과 같은 원격 이미지 저장소) 에서 가져오려고 시도하는데 외부 저장소에 spring-server 라는 이미지를 올려둔 게 없으니 풀링을 시도하고, 에러가 발생하는 것이다.

 

 


기본 매니페스트 파일 - ImagePullPolicy 지정

apiVersion: v1
kind: Pod

# 파드 기본 정보
metadata:
  name: spring-pod # 파드명

# 파드 상세 정보
spec:
  containers:
    - name: spring-container # 파드에서 사용할 컨테이너명
      image: spring-server # 컨테이너 실행을 위해 사용할 이미지
      imagePullPolicy: IfNotPresent # 로컬에서 가져오는 것을 우선 시도하고, 없으면 레지스트리에서 가져온다.
      ports:
        - containerPort: 8080 # 컨테이너가 파드 내에서 실행될 포트(문서화용, 실제 8080 에서 실행함을 보장하진 않음)

 

이미지 풀 정책을 IfNotPresent 로 명시적으로 지정하면, 우선 로컬에서 spring-server 이미지를 찾게 되므로

파드를 실행할 때 로컬 이미지를 통해 컨테이너를 실행하게 된다.

 


파드 재생성

$ kubectl delete pod spring-pod
pod "spring-pod" deleted

 

다시 기존 파드 spring-pod 를 제거한다.

 

kubectl apply -f spring-pod.yaml

 

수정한 파드 매니페스트 파일을 통해 다시 파드를 생성하고

 

kubectl get pod

 

파드를 조회해보면 일단 실행은 되어있는 것 같다.


파드가 제대로 잘 실행 됐는 지 확인

파드가 실행은 된 것 같은데 잘 실행됐는 지 확인해볼 필요가 있다.

 

1. 파드에 직접 접속해서 확인하는 방법

# 이미지 사양에 따라 sh / bash 바꿔서 쓰기
kubectl exec -it spring-pod -- sh


# curl localhost:8080 로도 가능
wget -qO- http://localhost:8080 && echo
Hello, World!!!

 

kubectl exec -it 명령을 통해, spring-pod 에 직접 접속하는 방법이다.

 

최초 접속 시  sh 를 사용했는데 이미지 사양에 따라 bash 또는 sh 를 써야하는 지 달라질 수 있다.

또 curl 명령을 통해서 get 요청을 보낼 수 있는데, 이 방법이 되지 않으면 wget 명령을 통해서 요청을 보내보면 된다.

 

명령을 날려보니 Hello, World!!! 응답이 잘 온 것을 볼 수 있다.

 

 

2. 로컬 컴퓨터 측에서, 포트포워딩을 통해 접속하는 방법

# kubectl port-forward pod/[파드명] [로컬컴퓨터의 포트]:[파드 내부 포트]
kubectl port-forward pod/spring-pod 30000:8080

 

 


로컬 컴퓨터의 특정 포트를, 파드 내부의 원하는 포트로 포트포워딩시켜서 확인하는 방법도 있다.

로컬의 30000 포트를, 파드 내부의 8080 포트로 포워딩 시켰다.

 

브라우저를 통해 localhost:30000 을 통해 접속하면 포트 포워딩되어, 파드에 요청을 잘 보내고 응답을 받을 수 있게 된다.

 

여기까지 한 결과를 도식화 시키면 위와 같다.


파드 삭제

kubectl delete pod spring-pod
pod "spring-pod" deleted

 

더 이상 파드는 필요 없으므로 제거한다.

 


 

이렇게 스프링 부트 프로젝트를 기반으로, 파드를 생성하는 방법을 알아봤다.

 

비슷한 방식으로 Next.js, Nest.js 등의 프로젝트를 기반으로 파드를 생성할 수도 있고 여러 군데에 응용하면서 파드를 실행시키는 경험을 가지면 좋을 것 같다.

 

Comments