DevOps/Kubernetes

[Kubernetes] 볼륨(Volume)

seungwook_TIL 2025. 5. 12. 00:26

이 글은 인프런의 지식 공유자 박재성님의 강의를 듣고 개인적으로 정리하는 글임을 알립니다.


기존 파드를 새로운 파드로 교체하면, 기존 파드 내부에 있던 데이터도 같이 삭제된다. 만약 이 파드가 MySQL을 실행시키는 파드였다면 MySQL에 저장된 데이터도 같이 삭제 돼버린다. 

따라서 파드 내부에 저장된 데이터가 삭제되면 안 되는 경우에는 볼륨(Volume)이라는 개념을 활용해야 한다.

 

Volume

볼륨(Volume)이란 데이터를 영속적으로 저장하기 위한 방법이다. 쿠버네티스에서 볼륨은 크게 2가지 종류로 나뉜다.

  • 로컬 볼륨(Local Volume)
  • 퍼시스턴트 볼륨(Persistent Volume, PV)

 

로컬 볼륨

  • 파드 내부의 공간 일부를 볼륨(Volume)으로 활용하는 방식이다.
  • 이 방식은 파드가 삭제되는 즉시 데이터도 함께 삭제된다. 이런 불편함 때문에 실제로 사용되는 일이 잘 없다. 

 

퍼시스턴트 볼륨

  • 파드 외부의 공간 일부를 볼륨(Volume)으로 활용하는 방식이다.
  • 이 방식은 파드가 삭제되는 것과 상관없이 데이터를 영구적으로 사용할 수 있다는 장점이 있다. 현업에서는 주로 이 방식을 많이 활용한다.
  • 퍼시스턴트 볼륨은 쿠버네티스 내부에 이 공간을 할당할 수도 있고, 쿠버네티스 외부에 이 공간을 할당할 수 있다.

위 그림의 왼쪽과 같은 경우, 쿠버네티스 공간 일부를 사용하는 경우이고, 오른쪽과 같은 경우는 쿠버네티스 외부 저장소(AWS EBS 등)를 사용하는 경우를 나타낸다.

 

  • 파드(Pod)가 퍼시스턴트 볼륨(PV)에 직접 연결할 수 없다. 따라서 퍼시스턴트 볼륨 클레임(PVC)이라는 중개자가 있어야 한다.
  • 그렇기 때문에 아래와 같은 구조로 파드는 PV와 연결된다.

 

 

볼륨을 활용해 MySql 실행

포트

  • 쿠버네티스 내부 : 3306
  • 쿠버네티스 외부 : 30002

 

데이터베이스

  • 사용자 이름 : root
  • 사용자 비밀번호 : password123
  • 데이터베이스 : kub-practice

아래는 위의 조건을 만족하는 설정이다.

 

mysql-deployment.yaml

apiVersion: apps/v1
kind: Deployment

# Deployment 기본 정보
metadata:
  name: mysql-deployment # Deployment 이름

# Deployment 세부 정보
spec:
  replicas: 1 # 생성할 파드의 복제본 개수
  selector:
    matchLabels:
      app: mysql-db # 아래에서 정의한 Pod 중 'app: backend-app'이라는 값을 가진 파드를 선택

  # 배포할 Pod 정의
  template:
    metadata:
      labels: # 레이블 (= 카테고리)
        app: mysql-db
    spec:
      containers:
        - name: mysql-container # 컨테이너 이름
          image: mysql # 컨테이너를 생성할 때 사용할 이미지
          ports:
            - containerPort: 3306  # 컨테이너에서 사용하는 포트를 명시적으로 표현
          env:
            - name: MYSQL_ROOT_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mysql-secret
                  key: mysql-root-password
            - name: MYSQL_DATABASE
              valueFrom:
                configMapKeyRef:
                  name: mysql-config
                  key: mysql-database
          # 컨테이너 내에서 어떤 경로를 볼륨으로 사용할 지 지정
          volumeMounts:
            - name: mysql-persistent-storage # 밑에서 설정할 volumes.name과 값이 같아야 함
              mountPath: /var/lib/mysql # mysql 컨테이너 내부에 있는 경로
      # 파드가 사용할 볼륨을 지정
      volumes:
        - name: mysql-persistent-storage # 위에서 설정할 volumeMounts.name과 일치해야 함
          persistentVolumeClaim:
            claimName: mysql-pvc # 연결시킬 PVC의 name과 동일해야 함

 

mysql-secret.yaml

apiVersion: v1
kind: Secret

# Secret 기본 정보
metadata:
  name: mysql-secret # Secret 이름

# Key, Value 형식으로 값 저장
stringData:
  mysql-root-password: password123

 

mysql-config.yaml

apiVersion: v1
kind: ConfigMap

# ConfigMap 기본 정보
metadata:
  name: mysql-config # ConfigMap 이름

# Key, Value 형식으로 설정값 저장
data:
  mysql-database: kub-practice

 

mysql-service.yaml

apiVersion: v1
kind: Service

# Service 기본 정보
metadata:
  name: mysql-service # Service 이름
  
# Service 세부 정보
spec:
  type: NodePort # Service의 종류
  selector:
    app: mysql-db # 실행되고 있는 파드 중 'app: mysql-db'이라는 값을 가진 파드와 서비스를 연결
  ports:
    - protocol: TCP # 서비스에 접속하기 위한 프로토콜
      port: 3306 # 쿠버네티스 내부에서 Service에 접속하기 위한 포트 번호
      targetPort: 3306 # 매핑하기 위한 파드의 포트 번호
      nodePort: 30002 # 외부에서 사용자들이 접근하게 될 포트 번호

 

mysql-pv.yaml

apiVersion: v1
kind: PersistentVolume

# PersistentVolume 기본 정보
metadata:
  name: mysql-pv # PersistentVolume 이름

# PersistentVolume 세부 정보
spec:
  storageClassName: my-storage # PV와 PVC의 storageClassName이 같다면 볼륨이 연결된다.
  capacity: 
    storage: 1Gi # 볼륨이 사용할 용량을 설정
  accessModes:
    - ReadWriteOnce # 아래 hostPath 타입 활용 시 이 옵션만 사용 가능
  hostPath: # hostPath 타입을 활용 (hostPath : 쿠버네티스 내부 공간을 활용)
    path: "/mnt/data" # 쿠버네티스 내부의 공간에서 /mnt/data의 경로를 볼륨으로 사용 

 

mysql-pvc.yaml

apiVersion: v1
kind: PersistentVolumeClaim

# PersistentVolumeClaim 기본 정보
metadata:
  name: mysql-pvc # PersistentVolumeClaim 이름
  
# PersistentVolumeClaim 세부 정보
spec:
  storageClassName: my-storage # PV와 PVC의 storageClassName이 같다면 볼륨이 연결된다.
  accessModes:
    - ReadWriteOnce # 볼륨에 접근할 때의 권한
  resources: # PVC가 PV에 요청하는 리소스의 양을 정의
    requests: # 필요한 최소 리소스
      storage: 1Gi # PVC가 PV에 요청하는 스토리지 양 (PV가 최소 1Gi 이상은 되어야 한다.)

 

오브젝트 생성

kubectl apply -f mysql-secret.yaml
kubectl apply -f mysql-config.yaml
kubectl apply -f mysql-pv.yaml
kubectl apply -f mysql-pvc.yaml
kubectl apply -f mysql-deployment.yaml
kubectl apply -f mysql-service.yaml

 

이렇게 설정하면 PV에 접근을 PVC가 대신하여주고, 파드가 종료되더라도 DB의 내용이 사라지지 않고 남아있게 된다.

 

 

SpringBoot와 MySQL 연동

외부에서 MySQL에 접속 가능

구성하고자하는 아키텍쳐는 위와 같다.

 

Application.yml

spring:
  datasource:
    url: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}
    username: ${DB_USERNAME}
    password: ${DB_PASSWORD}
    driver-class-name: cohttp://m.mysql.cj.jdbc.Driver

 

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: kub-practice

 

 

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
              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
            - name: DB_USERNAME
              valueFrom:
                secretKeyRef:
                  name: spring-secret
                  key: db-username
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: spring-secret
                  key: db-password

 

 

spring-secret.yaml

apiVersion: v1
kind: Secret

# Secret 기본 정보
metadata:
  name: spring-secret # Secret 이름

# Key, Value 형식으로 값 저장
stringData:
  db-username: root
  db-password: password123

 

 

spring-service.yaml

apiVersion: v1
kind: Service

# Service 기본 정보
metadata:
  name: spring-service

# Service 세부 정보
spec:
  type: NodePort # Service의 종류
  selector:
    app: backend-app # 실행되고 있는 파드 중 'app: backend-app'이라는 값을 가진 파드와 서비스를 연결
  ports:
    - protocol: TCP # 서비스에 접속하기 위한 프로토콜
      port: 8080 # 쿠버네티스 내부에서 Service에 접속하기 위한 포트 번호 (Service
      targetPort: 8080 # 매핑하기 위한 파드의 포트 번호
      nodePort: 30000 # 외부에서 사용자들이 접근하게 될 포트 번호

 

Dokcerfile

FROM openjdk:17-jdk

COPY build/libs/*SNAPSHOT.jar app.jar

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

 

프로젝트 및 도커 빌드

./gradlew clean build
docker build -t spring-server .

 

오브젝트 생성

kubectl apply -f spring-deployment.yaml
kubectl apply -f spring-service.yaml
kubectl apply -f spring-secret.yaml
kubectl apply -f spring-config.yaml
kubectl apply -f spring-deployment.yaml

 

 

보안 강화

  • 위의 구조에서 MySQL을 중점적으로 살펴보면 보안에 취약한 점이 있다.
  • 30002번 포트로 MySQL에 직접적으로 접근할 수 있게끔 보안이 설정되어 있다는 점이다.
  • Service의 NodePort를 활용해 30002번 포트를 외부로 오픈해서 MySQL에 아무나 접근할 수 있게 만들었다.
  • 보안적인 문제점 해결을 위해 Service의 종류 중 NodePort를 사용하지 않고 ClusterIP를 활용해야 한다. ClusterIP를 활용함으로써 외부에서 아무나 MySQL에 접근하지 못하게 막아야 한다.

 

Service 종류
- NodePort : 쿠버네티스 내부에서 해당 서비스에 접속하기 위한 포트를 열고 외부에서 접속 가능하도록 한다.
- ClusterIP : 쿠버네티스 내부에서만 통신할 수 있는 IP 주소를 부여. 외부에서는 요청할 수 없다.
- LoadBalancer : 외부의 로드밸런서(AWS의 로드밸런서 등)를 활용해 외부에서 접속할 수 있도록 연결한다.

 

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 # 매핑하기 위한 파드의 포트 번호
      nodePort: 30002 # 외부에서 사용자들이 접근하게 될 포트 번호 -> 이 부분을 지운다.

 

서비스 오브젝트 변경사항 반영

kubectl apply -f mysql-service.yaml

 

이렇게 설정했다면 아래와 같은 아키텍쳐가 된 것이다.

 

DB를 관리하기 위해 접속해야 할 때는 쿠버네티스의 포트 포워딩을 활용해서 접속하면 된다.

아래 포트 포워딩 명령어를 사용하면 내 로컬 컴퓨터에서만 해당 파드와 연결을 허용시킬 수 있게 된다.

kubectl port-forward pod/[MySQL 파드명] 3306:3306
포트 설정 주의

이미 로컬에서 3306번 포트로 실행중이라면 접속에 실패할 수 있다.
따라서 아래와 같이 로컬에서는 13306번 포트로 접속해야한다.
kubectl port-forward pod/파드명 13306:3306​