
쿠버네티스에서 파드(Pod)는 업데이트 시 기존 파드의 일부만 수정하지 않고, 새로운 파드를 생성하여 교체하는 방식으로 동작합니다. 이로 인해 파드가 삭제되면 내부에 저장된 데이터도 함께 사라지게 됩니다.
이러한 특성은 일시적인 데이터 처리에는 적합하지만, 데이터베이스나 로그 저장소처럼 데이터 영속성이 필요한 애플리케이션에서는 문제가 될 수 있습니다.
이 문제를 해결하기 위해 볼륨(Volume)이라는 개념이 사용됩니다. 볼륨은 데이터를 영구적으로 저장할 수 있는 공간을 제공하며, 파드가 삭제되어도 데이터를 유지할 수 있도록 설계되었습니다.
볼륨이란 데이터를 영속적으로 저장하기 위한 방법입니다. 쿠버네티스에서 볼륨은 크게 2가지 종류로 나뉩니다.
로컬 볼륨은 파드 내부의 공간 일부를 볼륨으로 사용하는 방식입니다. 이 방식은 파드가 삭제되면 데이터도 함께 삭제됩니다.
데이터 영속성이 없기 때문에 실제로는 거의 사용하지 않으며, 임시 데이터 저장이나 테스트 용도로만 적합합니다.

퍼시스턴트 볼륨은 파드 외부의 공간 일부를 볼륨으로 사용하는 방식입니다. 이 방식은 파드가 삭제되더라도 데이터가 유지되며, 클라우드 스토리지(AWS EBS 등) 또는 네트워크 스토리지를 활용하여 구현됩니다.
데이터베이스나 로그 저장소처럼 영속성이 필요한 애플리케이션에 주로 사용되며, 현업에서는 주로 이 방식을 많이 활용합니다.


퍼시스턴트 볼륨(PV)은 직접 파드에 연결되지 않고, 퍼시스턴트 볼륨 클레임이라는 일종의 중개자를 통해 연결됩니다. PVC는 사용자가 요청하는 스토리지의 크기, 접근 모드 등을 정의하고, 이를 기반으로 적절한 PV를 할당받아 데이터를 사용할 수 있도록 합니다.
이 구조를 통해 사용자는 스토리지 리소스의 세부 사항을 몰라도 쉽게 접근할 수 있습니다.

클러스터 관리자가 데이터를 저장할 외부 저장소를 미리 준비하고 정의합니다. 예를 들어, 클라우드 스토리지(AWS EBS 등)나 네트워크 드라이브(NFS)를 설정해 둡니다.
개발자가 애플리케이션에서 필요한 저장 공간(크기와 사용 방식)을 요청합니다. 이 요청은 PVC를 통해 퍼시스턴트 볼륨(PV)과 연결됩니다.
PVC를 참조하여 필요한 저장 공간을 볼륨으로 마운트(연결 작업)합니다. 이렇게 연결된 저장소를 통해 데이터를 저장하거나 불러올 수 있습니다.
mysql-deployment.yaml
apiVersion: apps/v1
kind: Deployment
# 메타데이터: 디플로이먼트의 이름과 기본 정보를 정의
metadata:
name: mysql-deployment # 디플로이먼트 이름
# 디플로이먼트의 스펙 정의
spec:
replicas: 1 # 생성할 파드의 복제본 개수
selector:
matchLabels:
app: mysql-db # 파드 선택 기준. 아래 정의된 파드와 매칭되는 레이블
# 디플로이먼트가 관리할 파드의 템플릿 정의
template:
metadata:
labels:
app: mysql-db # 파드 레이블. 디플로이먼트의 selector와 매칭됨
spec:
containers:
- name: mysql-container # 컨테이너 이름
image: mysql # MySQL 이미지를 사용
ports:
- containerPort: 3306 # 컨테이너에서 MySQL이 사용하는 포트
env:
# 환경 변수 설정: MySQL 루트 비밀번호
- name: MYSQL_ROOT_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-secret # mysql-secret에서 값을 가져옴
key: mysql-root-password # secret 내에서 사용하는 키
# 환경 변수 설정: 기본 데이터베이스 이름
- name: MYSQL_DATABASE
valueFrom:
configMapKeyRef:
name: mysql-config # mysql-config ConfigMap에서 값을 가져옴
key: mysql-database # ConfigMap 내에서 사용하는 키
mysql-secret.yaml
apiVersion: v1
kind: Secret
# 메타데이터: Secret의 이름과 기본 정보를 정의
metadata:
name: mysql-secret # Secret 이름
# stringData: Key-Value 형식으로 민감한 데이터를 저장
stringData:
mysql-root-password: password123 # MySQL의 루트 비밀번호
mysql-config.yaml
apiVersion: v1
kind: ConfigMap
# 메타데이터: ConfigMap의 이름과 기본 정보를 정의
metadata:
name: mysql-config # ConfigMap 이름
# 데이터: Key-Value 형식으로 저장할 설정값
data:
mysql-database: kube-practice # MySQL에서 사용할 기본 데이터베이스 이름
mysql-service.yaml
apiVersion: v1
kind: Service
# 메타데이터: Service의 이름과 기본 정보를 정의
metadata:
name: mysql-service # Service 이름
# 스펙: Service의 동작 방식을 정의
spec:
type: NodePort # Service의 타입을 NodePort로 설정 (외부에서 접근 가능)
selector:
app: mysql-db # 해당 레이블을 가진 파드와 연결
ports:
- protocol: TCP # 사용 프로토콜. MySQL은 TCP를 사용
nodePort: 30002 # 클러스터 외부에서 접근할 때 사용할 포트
port: 3306 # 클러스터 내부에서 Service에 접근하기 위한 포트
targetPort: 3306 # 파드의 컨테이너에서 사용하는 포트
# Secret 오브젝트 생성
$ kubectl apply -f mysql-secret.yaml
# ConfigMap 오브젝트 생성
$ kubectl apply -f mysql-config.yaml
# Deployment 오브젝트 생성
$ kubectl apply -f mysql-deployment.yaml
# Service 오브젝트 생성
$ kubectl apply -f mysql-service.yaml
DataGrip에서 DB 접속을 확인합니다.
root 계정으로 접속합니다.

kube-practice 데이터베이스가 잘 생성된 것을 확인할 수 있습니다.


$ kubectl rollout restart deployment mysql-deployment

artp 데이터베이스가 사라졌습니다. 이는 MySQL 파드가 삭제되면서 파드 내부에 저장된 데이터가 함께 삭제되었기 때문입니다.데이터베이스와 같은 영구 데이터를 보존하기 위해서는 볼륨(Volumne)을 설정해야 합니다.
mysql-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: mysql-pv # PV(Persistent Volume)의 이름입니다.
spec:
storageClassName: my-storage # StorageClass의 이름입니다.
capacity:
storage: 1Gi # 제공되는 스토리지 용량입니다 (1GiB).
accessModes:
- ReadWriteOnce # 한 노드에서만 읽기 및 쓰기가 가능합니다.
hostPath:
path: "/mnt/data" # 호스트 머신의 경로입니다.
mysql-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mysql-pvc # PVC(Persistent Volume Claim)의 이름입니다.
spec:
storageClassName: my-storage # 사용할 StorageClass의 이름입니다.
accessModes:
- ReadWriteOnce # 한 노드에서만 읽기 및 쓰기가 가능합니다.
resources:
requests:
storage: 1Gi # 요청하는 스토리지 용량입니다 (1GiB).
mysql-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: mysql-deployment # Deployment의 이름입니다.
spec:
replicas: 1 # 실행할 Pod의 복제본 수를 설정합니다.
selector:
matchLabels:
app: mysql-db # 이 레이블을 기준으로 Pod를 선택합니다.
template:
metadata:
labels:
app: mysql-db # Pod에 추가할 레이블입니다.
spec:
containers:
- name: mysql-container # 컨테이너의 이름입니다.
image: mysql # 사용할 Docker 이미지를 지정합니다.
ports:
- containerPort: 3306 # MySQL의 기본 포트입니다.
env:
- name: MYSQL_ROOT_PASSWORD # MySQL의 루트 비밀번호 환경 변수를 설정합니다.
valueFrom:
secretKeyRef:
name: mysql-secret # 비밀번호를 저장한 Secret의 이름입니다.
key: mysql-root-password # Secret에서 사용할 키입니다.
- name: MYSQL_DATABASE # MySQL 데이터베이스 이름 환경 변수를 설정합니다.
valueFrom:
configMapKeyRef:
name: mysql-config # ConfigMap의 이름입니다.
key: mysql-database # ConfigMap에서 사용할 키입니다.
volumeMounts:
- name: mysql-persistent-storage # Persistent Volume을 마운트합니다.
mountPath: /var/lib/mysql # 데이터가 저장될 경로입니다.
volumes:
- name: mysql-persistent-storage # Persistent Volume의 이름입니다.
persistentVolumeClaim:
claimName: mysql-pvc # PVC(Persistent Volume Claim)의 이름입니다.
$ kubectl apply -f mysql-pv.yaml
$ kubectl apply -f mysql-pvc.yaml
$ kubectl apply -f mysql-deployment.yaml
데이터베이스 접속 후 새로운 데이터베이스(artp)를 생성합니다.

$ kubectl rollout restart deployment mysql-deployment
디플로이먼트를 재시작한 후에도 artp 데이터베이스가 정상적으로 유지되고 있음을 확인할 수 있습니다.

hostPath로 /mnt/data를 지정하여 클러스터의 특정 노드 로컬 디스크를 활용합니다./var/lib/mysql 디렉토리를 로컬 스토리지(/mnt/data)와 연결합니다.hostPath는 특정 노드의 로컬 디스크에 있는 폴더를 가져다가 Pod(애플리케이션)에서 사용할 수 있도록 연결하는 방법입니다./mnt/data 폴더를 MySQL Pod가 데이터를 저장할 수 있는 장소로 지정한 것입니다./var/lib/mysql 디렉토리를 사용합니다.hostPath를 설정하면 MySQL Pod의 /var/lib/mysql과 노드의 /mnt/data가 연결됩니다./mnt/data 폴더에 저장됩니다./mnt/data에 MySQL 데이터를 저장하면, Pod가 실행된 그 노드의 디스크에 저장이 됩니다.hostPath)를 사용하는 경우hostPath를 사용할 경우, 특정 노드의 로컬 디스크를 스토리지로 지정합니다.Spring Boot DevTools, Spring Web, Spring Data JPA, MySQL Driver@RestController
public class AppController {
@GetMapping("/")
public String home() {
return "Hello, World!";
}
}
src - main - resources - application.yaml
application.yaml
spring:
datasource:
url: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
프로젝트 생성 시 자동으로 생성된 테스트 코드를 삭제하여 초기 빌드 오류를 방지합니다.

Dockerfile
FROM openjdk:17-jdk
COPY build/libs/*SNAPSHOT.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
$ ./gradlew clean build
$ docker build -t spring-server .
spring-deployment.yaml
apiVersion: apps/v1
kind: Deployment
# Deployment 기본 정보
metadata:
name: spring-deployment # Deployment 이름
# Deployment 세부 정보
spec:
replicas: 3 # 생성할 파드의 복제본 수
selector:
matchLabels:
app: backend-app # 아래에서 정의한 Pod 중 'app: backend-app'이라는 값을 가진 파드를 선택
# 배포할 Pod 정의
template:
metadata:
labels: # 레이블(= 카테고리)
app: backend-app
spec:
containers:
- name: spring-container # 컨테이너 이름
image: spring-server # 컨테이너를 생성할 때 사용할 이미지
imagePullPolicy: IfNotPresent # 로컬에서 이미지를 먼저 가져오고 없으면 레지스트리에서 가져옴
ports:
- containerPort: 8080 # 컨테이너에서 사용하는 포트를 명시적으로 표현
env:
- name: DB_HOST
value: mysql-service # Service의 name만 입력하면 다른 서비스와 통신할 수 있음
- name: DB_PORT
value: "3306" # 숫자값을 문자로 인식하게 만들기 위해 쌍따옴표를 붙여야 함
- name: DB_NAME
value: kub-practice
- name: DB_USERNAME
value: root
- name: DB_PASSWORD
value: password123
spring-service.yaml
apiVersion: v1
kind: Service
# Service 기본 정보
metadata:
name: spring-service
# Service 세부 정보
spec:
type: NodePort
selector:
app: backend-app
ports:
- protocol: TCP # 서비스에 접속하기 위한 프로토콜
port: 8080 # 쿠버네티스 내부에서 서비스에 접속하기 위한 포트 번호
targetPort: 8080 # 매핑하기 위한 파드의 포트 번호
nodePort: 30000 # 외부에서 사용자들이 접근하게 될 포트 번호
$ kubectl apply -f spring-deployment.yaml
$ kubectl apply -f spring-service.yaml
localhost:30000

스프링 부트 백엔드와 MySQL은 쿠버네티스 환경에서 다음 그림과 같이 연동됩니다.

8080번 포트(targetPort)에서 애플리케이션을 실행합니다.30000번 포트(nodePort)를 통해 서비스에 접근할 수 있습니다.3306번 포트(targetPort)에서 실행됩니다.30000번 포트(nodePort)를 통해 Spring Boot 애플리케이션에 요청을 보냅니다.mysql-service)를 통해 MySQL 파드에 요청을 보냅니다.3306번 포트를 통해 MySQL 파드와 통신합니다.spring-config.yaml
apiVersion: v1
kind: ConfigMap
# ConfigMap 기본 정보
metadata:
name: spring-config # ConfigMap 이름
# Key-Value 형식으로 설정값 저장
data:
db-host: mysql-service
db-port: "3306"
db-name: kube-practice
spring-secret.yaml
apiVersion: v1
kind: Secret
# Secret 기본 정보
metadata:
name: spring-secret # Secret 이름
# Key-Value 형식으로 민감한 정보 저장
stringData:
db-username: root
db-password: password123
spring-deployment.yaml
apiVersion: apps/v1
kind: Deployment
# Deployment 기본 정보
metadata:
name: spring-deployment # Deployment 이름
# Deployment 세부 정보
spec:
replicas: 3 # 생성할 파드의 복제본 개수
selector:
matchLabels:
app: backend-app # 아래 정의한 Pod 중 'app: backend-app'인 값을 선택
# 배포할 Pod 정의
template:
metadata:
labels:
app: backend-app
spec:
containers:
- name: spring-container # 컨테이너 이름
image: spring-server # 사용할 이미지
imagePullPolicy: IfNotPresent # 로컬에서 이미지를 먼저 가져오고 없으면 레지스트리에서 가져옴
ports:
- containerPort: 8080 # 컨테이너에서 사용하는 포트를 명시적으로 표현
env:
# ConfigMap을 활용한 환경변수 설정
- name: DB_HOST
valueFrom:
configMapKeyRef:
name: spring-config
key: db-host
- name: DB_PORT
valueFrom:
configMapKeyRef:
name: spring-config
key: db-port
- name: DB_NAME
valueFrom:
configMapKeyRef:
name: spring-config
key: db-name
# Secret을 활용한 민감한 환경변수 설정
- name: DB_USERNAME
valueFrom:
secretKeyRef:
name: spring-secret
key: db-username
- name: DB_PASSWORD
valueFrom:
secretKeyRef:
name: spring-secret
key: db-password
# ConfigMap 생성
$ kubectl apply -f spring-config.yaml
# Secret 생성
$ kubectl apply -f spring-secret.yaml
# Deployment 생성
$ kubectl apply -f spring-deployment.yaml
# Deployment 강제 재배포
$ kubectl rollout restart deployment spring-deployment


현재 구조에서는 MySQL Service가 NodePort 방식으로 설정되어 있어, 외부에서 30002번 포트를 통해 MySQL에 직접 접근할 수 있습니다. 이는 보안상 취약한 점으로, 외부 사용자에게 DB에 대한 접근 권한이 노출될 위험이 있습니다.
NodePort)를 통해 접속합니다.NodePort를 ClusterIP로 변경하여 MySQL에 대한 외부 접근을 차단합니다.
mysql-service.yaml
apiVersion: v1
kind: Service
# Service 기본 정보
metadata:
name: mysql-service # Service 이름
# Service 세부 정보
spec:
type: ClusterIP # 외부 접근 차단을 위한 Service 종류
selector:
app: mysql-db # 'app: mysql-db' 레이블을 가진 파드와 연결
ports:
- protocol: TCP # 프로토콜
port: 3306 # 내부에서 Service가 사용할 포트
targetPort: 3306 # 파드의 MySQL 컨테이너 포트
# NodePort는 제거합니다.
type 필드 수정:nodePort → 외부 네트워크 접근을 허용합니다.clusterIP → 클러스터 내부에서만 접근이 가능합니다.nodePort 제거:nodePort: 30002)가 있었습니다.# 기존 서비스 삭제
kubectl delete service mysql-service
# Deployment 재시작
kubectl rollout restart deployment spring-deployment

# 수정된 Service 파일 적용
$ kubectl apply -f mysql-service.yaml
# Deployment 재시작
$ kubectl rollout restart deployment spring-deployment


MySQL 관리 목적으로 특정 로컬 환경에서만 DB 접근이 필요할 경우, 포트 포워딩을 활용합니다.
포트 포워딩은 로컬 컴퓨터에서 특정 Pod와 연결을 허용합니다.
# kubectl port-forward pod/<파드 이름> <로컬 포트>:<파드의 컨테이너 포트>
$ kubectl port-forward pod/mysql-deployment-79b56744b4-pk87t 3306:3306


위 그림은 쿠버네티스 클러스터 내부에서 MySQL과 Spring Boot 애플리케이션 간의 통신 구조를 보여줍니다. 그림을 바탕으로 흐름을 정리하면 다음과 같습니다.
MySQL Service가 NodePort(30002번 포트)로 설정되어 있어, 클러스터 외부에서 MySQL에 직접 접근할 수 있었습니다.Service를 ClusterIP로 변경하여 외부 네트워크에서 접근할 수 없도록 설정했습니다.spring-service)이 내부적으로 MySQL에 접근하며, 외부에서는 이를 직접적으로 호출할 방법이 없습니다.Service는 여전히 NodePort를 통해 사용자와 통신하므로, 애플리케이션 서비스는 외부에서 접근이 가능합니다.Spring Boot 애플리케이션과 사용자 간 통신:
30000번 포트)를 호출합니다.Service(spring-service)와 연결되며, 클러스터 내부의 Spring Pod로 요청을 전달합니다.Spring Boot와 MySQL 간 통신:
Service(mysql-service)와 통신합니다.Service는 ClusterIP로 설정되어 있어 클러스터 내부에서만 접근 가능하며, Spring Boot 애플리케이션만 MySQL에 접근할 수 있습니다.MySQL 데이터베이스와 퍼시스턴트 스토리지:
PVC)을 통해 퍼시스턴트 볼륨(PV)에 데이터를 저장합니다.NodePort가 제거됨으로써, 외부 네트워크에서의 MySQL 접근이 차단되었습니다.$ kubectl port-forward pod/<MySQL Pod 이름> 3306:3306