본문 바로가기
Infra/Kubernetes

[Kubernetes] Volume - emptyDir, hostPath, PV/PVC

by 진꿈청 2024. 8. 28.

Kubernetes

 

 

이번 포스팅에서는 `emptyDir`과 `hostPath` 그리고 `pvc`와 `pv`라고 하는

퍼시스턴트 볼륨 클라임퍼시스턴트 볼륨에 관해 알아보도록 하자.

 

 

 

emptyDir

 

먼저 `emptyDir`은 컨테이너들끼리 데이터를 공유하기 위해 `Volume`을 사용하는 것이다.

최초로 해당 `Volume` 이 생성될 때는 항상 해당 `Volume`안의 내용이 비어있기 때문에 `emptyDir`이라는 명칭이 붙어졌다.

 

만약, 컨테이너1이 웹 역할을 하는 서버이고 컨테이너2가 백엔드단을 처리해주는 서버라고 했을 때,

 

해당 웹서버로 받은 어떤 특정 파일을 마운트가 된 `Volume`에 저장을 해놓고,

백엔드단의 컨테이너 역시 해당 볼륨을 마운트를 하면 이 두 서버가 해당 `Volume`을 자신의 로컬에 있는 파일처럼 사용을 한다.

 

따라서, 두 서버가 서로 파일을 주고 받을 필요 없이 편하게 사용이 가능하다.

 

 

중요한 점은 `emptyDir`은 위의 그럼처럼 `Pod`안에 생성이 되기에 `Pod`에 문제가 생겨 재생성되면

데이터가 싹 없어진다는 걸 의미한다.

 

따라서, 해당 `Volume`에 쓰이는 데이터는 꼭 일시적인 활용 목적에 의한 데이터만 넣는 것이 좋다.

 

apiVersion: v1
kind: Pod
metadata:
  name: pod-volume-1
spec:
  containers:
  - name: container1
    image: kubetm/init
    volumeMounts:
    - name: empty-dir
      mountPath: /mount1
  - name: container2
    image: kubetm/init
    volumeMounts:
    - name: empty-dir
      mountPath: /mount2
  volumes:
  - name : empty-dir
    emptyDir: {}

 

해당 `emptyDir`을 활용한 `Pod`를 생성하려면 위의 YAML 파일과 같이 생성하면 된다.

 

YAML 파일의 내용을 살펴보면 containers에 두 컨테이너가 존재하고 둘 다 `Volume`을 마운트하고 있다.

 

이때, 마운트하는 Path를 보면 컨테이너1은 `/mount1`이라는 Path를 사용했고,

컨테이너2는 `/mount2`라는 Path를 사용했다.

 

이 `mount` Path의 의미는 이 컨테이너가 해당 Path로 `Volume`을 연결하겠다는 의미이다.

컨테이너1컨테이너2의 Path의 이름이 틀리더라도 결국 각각의 Path가 지정되는 볼륨의 이름은

`empty-dir`로 똑같은 `Volume`을 지정하고 있기 때문에 결국 한 `Volume`을 마운트를 하고 있는 것이다.

 

  

 

hostPath

 

`hostPath`는 이름 그대로 한 호스트, 그러니까 해당 `Pod`들이 올라가져 있는 노드의 Path를 `Volume`으로써 사용하는 것이다.

 

`emptyDir`과 다른 점은 해당 Path를 각각의 `Pod`들이 마운트를 해서 공유하기 때문에,

`Pod`들이 죽어도 노드에 있는 데이터는 사라지지 않는다.

 

해당 부분에서는 다소 좋아 보일 수 있지만, `Pod` 입장에서는 한가지 큰 문제가 존재한다.

 

만약, 재생성되는 순간에 스케줄러가 자원 상황을 보고 노드2에 `Pod`를 만들어 줄 수도 있고,

노드1에 장애가 생겨 다른 노드에 파드가 옮겨질 수도 있다.

 

즉, `Pod`가 다른 노드로 옮겨졌을 때 해당 `Pod`는 그 전 노드에 있는 `Volume`을 마운트할 수가 없게 된다.

`hostPath`기 때문에 `Pod`가 올라가져 있는 노드의 `Volume`만을 사용할 수 있기 때문이다.

 

만약, 굳이 방법을 찾는다면  노드2가 추가가 될 때마다 똑같은 이름의 경로를 만들어서

직접 노드에 있는 Path끼리 마운트를 시켜주면 문제는 없어질 것이다.

 

하지만, 이거는 `Kubernetes`가 해주는 역할은 아니고 운영자가 노드가 추가될 때마다

직접 리눅스 시스템별도 마운트 기술을 사용하여 연결을 해야 한다.

 

이거는 뭔가 자동화를 시키는데 사람의 개입이 들어가기 때문에 실수 발생 여지가 많아져 추천되는 방법은 아니라 한다.

 

 

 

그럼 이 `hostPath`는 어떨 때 써야 될까?

 

각각의 노드에는 기본적으로 각 노드 자신을 위해 사용되는 파일이 있다. 시스템 파일들이나 여러 설정 파일들이 있는데,

`Pod` 자신이 할당되어 있는 호스트의 데이터를 읽거나 써야 될 때 사용하면 된다.

 

 

아래는 `hostPath`를 사용한 `Pod`의 YAML 파일이다.

apiVersion: v1
kind: Pod
metadata:
  name: pod-volume-3
spec:
  nodeSelector:
    kubernetes.io/hostname: k8s-node1
  containers:
  - name: container
    image: kubetm/init
    volumeMounts:
    - name: host-path
      mountPath: /mount1
  volumes:
  - name : host-path
    hostPath:
      path: /node-v
      type: DirectoryOrCreate

 

`Pod`를 만들 때 컨테이너에서 `Volume`을 마운트를 할 건데 해당 Path는 `/mount1`이고,

해당 Path에 관한 `hostPath`라는 이름의 `Volume`은 아래에 작성되어 있다.

 

그리고 이 `hostPath`라는 `Volume`은 위의 YAML 파일처럼 `hostPath`라는 속성이 들어가며 Path는 `/node-v`이다.

또한, 타입은 `DirectoryOrCreate`이다.

 

 

다시 한번 설명하자면 `hostPath`는 `Pod`의 데이터를 저장하기 위한 용도가 아닌,

노드에 있는 데이터를 `Pod`에서 쓰기 위한 용도이다.

 

 

PVC / PV

 

다음으로는 퍼시스턴트 볼륨 클라임퍼시스턴트 볼륨에 관한 설명이다.

`PVC`와 `PV`는 `Pod`에 영속성 있는 `Volume`을 제공하기 위한 개념이다.

 

실제 `Volume`들의 형태는 매우 다양하다.

 

로컬 볼륨도 있고 외부에 원격으로 사용되는 형태의 `Volume`들도 있다.

위의 그림처럼 아마존이나 Git에 연결을 할 수도 있으며, `NFS`를 써서 다른 서버와 연결을 할 수도 있다.

 

그리고 `Storage OS` 같이 `Volume`을 직접 만들고 관리할 수 있는 솔루션들도 있다고 한다.

 

위처럼 다양한 `Volume`에 관한 퍼시스턴트 볼륨을 정리하고 연결을 한다.

근데, `Pod`는 해당 `PV`에 바로 연결을 하는 것이아닌 `PVC`(퍼시스턴트 볼륨 클라임)을 통해 `PV`와 연결이 된다.

 

 

바로 `Pod`에서 `PV`로 연결을 하는게 더 깔끔해 보이는데 왜 중간에 `PVC`를 둘까?

 

`Kubernetes`는 이 `Volume` 사용에 있어 `유저 영역`과 `어드민 영역`으로 나눴기 때문이다.

 

어드민은 `Kubernetes`를 담당하는 `Kubernetes` 운영자일 것이며,

유저는 `Pod`에 서비스를 만들고 배포를 관리하는 서비스 담당자일 것이다.

 

`Volume`들의 종류는 많고 각각의 `Volume`들을 연결하기 위한 설정도 각각 틀리기 때문에 위와 같이 구성한 것이다.

 

 

밑의 YAML 내용을 보면 PV을 정의하는데 각각의 `Volume`에 따라 이걸 연결하기 위한 속성들이 다르게 들어간다.

apiVersion: v1
kind: PersistentVolume
metadata:
 name: pv-01
spec:
  nfs:
  	server: 192.168.0.xxx
    path: /sda/data
  iscsi:
  	targetPortal: 163.180.11
    iqn: iqn.200.qnap:...
    lun: 0
    fsType: ex4
    readOnly: no
    chapAuthSession: true
  gitRepo:
  	repository: github.com
    revision: master
    directory: .

 

위처럼 각각의 속성이 다르기 때문에 이런 걸 전문적으로 관리하는 어드민이 `PV`를 만들어 놓으면,

유저는 이걸 사용을 하기 위해 `PVC`를 만들어야 한다.

 

 

이때 `PVC`를 만들기 위한 YAML 레이아웃이 아래와 같다고 가정해보자.

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: pvc-01
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1G
  storageClassName: ""

 

위의 YAML 파일 내용을 살펴보면 나는 읽기 쓰기 모드로 용량이 1기가인 볼륨을 할당해달라는 것을 말한다.

 

맨 밑의 라인을 보면 `storageClassName`이 있는데 이렇게만 넣으면,

현재 만들어져 있는 `PVC`들 중 선택이 된다고만 알고 우선 넘어가자.

 

이걸 `""`을 안 넣고 생략을 하면 또 다른 동작으로 사용될 수 있기 때문이다.

`스토리지 클래스`는 다음 과정에서 더 배워볼 것이다.

 

 

apiVersion: v1
kind: Pod
metadata:
  name: pod-volume-3
spec:
  containers:
  - name: container
    image: kubetm/init
    volumeMounts:
    - name: pvc-pv
      mountPath: /volume
  volumes:
  - name : pvc-pv
    persistentVolumeClaim:
      claimName: pvc-01

 

마지막으로, `Pod`를 만들 때 `claimName`에 앞서 만들어 놓은 `PVC` 이름으로 연결을 한다.

그렇게 `Volume`을 만들어 놓으면 해당 `Volume`을 컨테이너에서 사용을 하면 된다.

 

정리하자면, 최초 어드민이 `PV`를 만들어 놓고 사용자가 `PVC`를 만들면

`Kubernetes`가 이 `PVC` 내용에 맞는 적절한 `Volume`에 연결을 해준다.

 

그리고 나서 `Pod`를 만들 때 이 `PVC`를 사용하면 된다.

 

 

좀 더 추가적인 설명으로, 아래 형식의 로컬 `PV`를 만들었다고 생각해보자.(로컬 PV는 실제로 잘 사용하지 않는다고 한다.)

apiVersion: v1
kind: PersistentVolume
metadata:
  name: pv-04
  labels:
    pv: pv-04
spec:
  capacity:
    storage: 2G
  accessModes:
  - ReadWriteOnce
  local:
    path: /node-v
  nodeAffinity:
    required:
      nodeSelectorTerms:
      - matchExpressions:
        - {key: kubernetes.io/hostname, operator: In, values: [k8s-node1]}

 

위의 YAML 파일 내용을 살펴보면 `capacity`와 `accessModes`가 존재한다.

 

아까 `PVC`가 `PV`를 연결할 때 `Kubernetes`가 알아서 자동으로 연결을 해준다고 했다. 

자동으로 연결을 해주려면 뭔가 근거가 있어야 되는데 바로 위의 `capacity`와 `accessModes` 같은 걸 참조해서

`Kubernetes`가 연결을 해주는 것이다.

 

특정 스펙의 `capacity`와 `accessModes`가 지정이 되면 `PVC`에서 요청하는 내용에 맞게 연결이 되는 것이다. 

 

 

실습

 

여기까지 `Kubernetes`에서 지원해주는 `Volume`에 관해 알아보았다.

각 `Volume` 유형에 관한 실습과 함께 좀 더 자세히 알아보자.

 

emptyDir

apiVersion: v1
kind: Pod
metadata:
  name: pod-volume-1
spec:
  containers:
  - name: container1
    image: kubetm/init
    volumeMounts:
    - name: empty-dir
      mountPath: /mount1
  - name: container2
    image: kubetm/init
    volumeMounts:
    - name: empty-dir
      mountPath: /mount2
  volumes:
  - name : empty-dir
    emptyDir: {}

 

우선 해당 YAML 파일을 활용하여 `emptyDir` 설정이 되어있는 `Pod`를 하나 생성해보자.

 

앞서 설명한 것처럼 `emptyDir`은 `Pod`안에서 생성이되어 사용이되므로, `Pod`에 접속하여 각 컨테이너를 확인해보자.

 

 

컨테이너1에 접속하여 확인해보면 위의 YAML 파일에 작성한 것처럼 `/mount1` 디렉토리가 생성이 되어있다.

이는 `Pod`의 `empty-dir`과 연결되어 있는 것으로 다음 명령어로 실제 마운트가 되어 있는지 확인해보자.

 

 

마운트 설정이 잘되어있는 것을 확인할 수 있다.

 

그럼 해당 `/mount1` 경로에 파일을 생성하게 되면 같은 `emptyDir`을 공유하는 컨테이너2에도 적용이 되어야 한다.

 

 

위 사진과 같이 `file.txt` 파일을 하나 생성하고 컨테이너2에 접속하여 확인해보자.

 

 

컨테이너2에도 파일이 잘 생성된 것을 확인할 수 있다.

 

 

 

그런데 아까 말했던 것과 같이 `emptyDir`은 `Pod`안에서만 존재하기에 `Pod`가 삭제가 되면 사라진다. 한번 확인해보자.

 

 

 

`Pod`를 삭제하고 다시 `/mount1` 경로로 접근하면 기존의 `file.txt`가 존재하지 않는 것을 확인할 수 있다.

 

 

따라서, `emptyDir`은 컨테이너들이 파일을 공유를 해서 쓰되 언제 삭제돼도 상관이 없는 내용들을 담아야 한다.

 

 

hostPath

 

다음은 `hostPath`에 관한 실습이다. 우선, 아래 YAML 파일 내용을 살펴보자.

 

apiVersion: v1
kind: Pod
metadata:
  name: pod-volume-3
spec:
  nodeSelector:
    kubernetes.io/hostname: k8s-node1
  containers:
  - name: container
    image: kubetm/init
    volumeMounts:
    - name: host-path
      mountPath: /mount1
  volumes:
  - name : host-path
    hostPath:
      path: /node-v
      type: DirectoryOrCreate

 

 

YAML 파일의 내용을 살펴보면 위 `Pod`는 `Node1`에다가 생성한다.

또한, 접근할 마운트 패스는 `/mount1`이고 마운트 할 `Volume`의 이름은 `host-path`이다.

 

그래서, `hostPath`가 정의되어 있는 아래 `volumes`를 보면 `hostPath`라고 정의가 되어있다.

이때, `hostPath`의 경로는 `/node-v`라고 정의가 되어있다.

 

그리고 `type: DirectoryOrCreate`은 해당 디렉토리가 없으면 생성이 된다.

 

 

위의 YAML 파일을 활용하여 `Pod`를 2개 생성해보자.

 

 

미리 예상을 해보면 두 `Pod`는 같은 `Node1` 위에 만들어질 것이다.

그럼 두 `Pod`는 `Node1`의 `/node-v`라는 디렉토리를 공유하게 될 것이다.

 

 

 

 

현재 `/node-v` 경로에 아무 파일이 없으므로, 새로 생성해준 뒤

같은 디렉토리를 공유하는 `pod-volume-3`에 들어가서 확인해보자.

 

 

들어가보면 잘 공유되고 있는 것을 확인할 수 있다.

 

 

그럼 이제 실제로 `Node`에 해당 `Volume`이 잘 공유되고 있는지 확인해보자.

 

 

 

`Node1`에 접근을 해서 확인해보면 `/node-v` 디렉토리가 생성되어 있는 것을 확인할 수 있고

아래와 같이 파일이 존재하는 것을 확인할 수 있다.

 

 

즉, 실제 노드에 디렉토리와 파일이 존재하고 있다.

그렇기 때문에 `Pod`를 삭제하고 다시 만들더라도 `/node-1`의 `hostPath`는 그대로 있기 때문에 데이터가 유지된다.

 

 

하지만 이는 앞서, 설명했던 것처럼 문제가 있다.

 

`Pod` 생성시 노드를 지정하지 않고 생성해보자.

 

 

이렇게 `Pod`가 아까 두 `Pod`와는 달리 `Node2`에 생겼다.

 

 

그 후, 접속해서 `/mount1` 경로로 접근해보면 디렉토리는 새로 만들어져 있지만 아까의 `Node1`과는 다른 경로이기 때문에

`Node1`에서 만든 파일이 보이지 않는다.

 

그렇기에 `hostPath`를 사용할 때는 이 점을 유의해야 한다.

 

PVC/PV

다음으로는 퍼센트 볼륨 클레임퍼센트 볼륨에 관한 내용인데,

`PVC`와 `PV`는 좀 더 깊이있는 `Storage`와 `Volume`에 관한 학습이 필요하므로, 추후에 다뤄야 할 것 같다.