쿠버네티스에서 Volume은 컨테이너 내부 또는 컨테이너 간 데이터를 저장하고 공유하기 위한 추상적인 개념이다.
일반적으로 쿠버네티스의 Pod는 내부에서 컨테이너가 실행되며 서로 리소스(CPU, RAM 등)을 공유하고 있지만 각각의 분리된 파일 시스템을 가지고 있다.
이는 컨테이너의 파일 시스템은 컨테이너 이미지에서 제공하기 때문인데, Docker Volume과 동일하게 쿠버네티스 컨테이너 내에 존재하는 파일은 임시로 운영되며 데이터가 영구적으로 저장되지 않는다는 특성으로 인해 컨테이너에 문제가 생기거나 여러 컨테이너 간 파일 공유가 불가능하다는 문제점이 있다.
쿠버네티스는 Volume을 통해 이러한 문제점을 해결할 수 있다.

emptyDir은 파드 내에서 컨테이너끼리 공유하는 저장소이다.
파드가 노드에 할당될 때 처음 생성되며, 파드 내 모든 컨테이너는 emptyDir을 통해 동일한 파일을 읽고 쓸 수 있지만 만일 노드에서 파드가 제거 될 경우 emptyDir의 데이터 또한 영구적으로 삭제된다.
컨테이너가 크래시될 경우 노드에서는 파드를 제거하는 것이 아니기 때문에 emptyDir의 데이터는 컨테이너 크래시로부터 안전하다는 것을 참고하도록 하자.

hostPath는 호스트 노드의 파일 시스템 경로를 Pod 내에서 사용할 수 있도록 공유하는 저장소이다. 노드에 종속되어 있으므로 파드가 죽더라도 볼륨에는 영향이 없다.
단, 공식문서에서는 hostPath에 많은 보안 위험이 있어 가능하면 사용하지 않는 것을 권장하고 있다. 사용해야하는 경우 필요한 파일 또는 디렉터리로만 범위를 지정하고 ReadOnly로 마운트하도록 설명한다.
위처럼 매번 YAML 파일에 Volume을 정의한다면 많은 Pod를 배포할때마다 Pod 별로 저장소를 구성해야 한다.
번거롭게 변경 사항이 있을 때마다 모든 Pod를 수정하는 대신 중앙에서 관리할 수 있도록 하는것이 PV/PVC 이다.

쿠버네티스에서는 스토리지 볼륨을 마운트하여 사용할때 PV라고 명칭하고 있다. 관리자에 의해 프로비저닝 된 쿠버네티스의 스토리지로, 노드가 클러스터의 리소스인 것처럼 PV 또한 클러스터 리소스이다.
PV를 사용하여 저장소 풀을 생성하고 필요에 따라 각 Pod에서 이를 사용할 수 있도록 설정할 수 있다.

관리자가 PV를 생성하면 PVC는 사용자가 볼륨을 사용하기 위해 PV에 요청하고 바인딩하기 위한 객체이다. Pod가 노드의 리소스를 소비하는 것처럼 PVC는 PV 리소스를 소비한다고 보면 된다.
PVC는 PV와 다르게 네임스페이스 내에서 사용되며 PV와 연결되는데, 이 때문에 Namespace에따라 제약이 걸리지만
정적 또는 동적의 PV를 생성하는 단계이다.
정적 프로비저닝은 특정 용량을 가진 PV를 미리 생성해두고 PVC로부터 요청이 있을 경우에 사용되며 동적 프로비저닝의 경우 사용자의 요청 시에 PV를 생성하는데 Storage Class를 사용하여 사용자가 원하는 만큼의 용량을 생성하고 자유롭게 사용할 수 있도록 한다.
PV와 PVC를 연결시키는 단계로 PVC는 사용자가 요청하는 볼륨을 PV에 요청하고 PV는 사용자가 요청한 볼륨을 할당해준다. 만일 일치하는 볼륨이 없을 경우 PVC는 무한정 대기 상태로 남게 되고 정상적으로 완료되면 bound 상태가 된다.
Pod는 PVC를 볼륨으로 사용한다. 바인딩된 PV를 찾고 해당 볼륨을 Pod에서 사용가능하도록 만들어준다.
Pod(Service)는 PVC를 볼륨으로 사용합니다. 바인딩된 PV를 찾고 해당 볼륨을 Pod에서 사용가능하도록 만들어줍니다.
사용자가 볼륨을 다 사용하고 나면 리소스를 반환할 수 있는 API를 사용하여 PVC를 삭제할 수 있다. PV의 반환 정책은 PVC를 해제한 후에 볼륨에 수행할 작업을 클러스터에 알려준다.
Retain(보존): 리소스를 수동으로 반환할 수 있게 한다. PV가 삭제되어도 이전 요청자의 데이터가 여전히 남아있기 때문에 다른 요청에 대해 사용할 수 없다. 따라서 관리자는 쿠버네티스가 제공하는 가이드에 따라 볼륨을 수동으로 반환할 수 있다.
Delete(삭제): PV와 외부 인프라(AWS, GCP 등)의 관련 스토리지 자산을 모두 삭제한다.
Recycle(재활용)
Recycle 반환 정책은 더 이상 사용하지 않는다. 대신 동적 프로비저닝을 사용하는 것을 권장하고 있다.
NFS를 이용하여 PV/PVC를 할당하고 Nginx와 연결하는 실습을 해보자.
NFS(Network File System) 서버를 이용하여 프로비저닝을 하게된다면 모든 워커 노드에서 NFS-Client 설정을 해주어야함에 유의하자. 쿠버네티스가 어느 워커 노드에 Pod를 배치할지 모르기때문이다.
NFS를 구축하는 과정은 별도로 설명하지 않을 것이기 때문에 아래 링크를 참고해주세요.
https://dongle94.github.io/ubuntu/ubuntu-nfs-setting/

관리자가 수동으로 PV를 하나 생성한다. Pod는 기본적으로 삭제 시 내부에 존재했던 내용들도 모두 삭제되기 때문에 이를 막기위해 PV와 PVC를 활용해 데이터를 관리할 것이다.
-pv.yaml
apiVersion: v1
kind: PersistentVolume
metadata:
name: nfs-pv
spec:
capacity:
storage: 200Mi
accessModes:
- ReadWriteMany
nfs:
server: 10.178.0.3 # NFS 서버 IP 주소
path: /home/test # 공유 디렉터리 경로
-pvc.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: nfs-pvc
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 100Mi
위와 같이 PV와 PVC를 정의해주고 생성해보자. pvc.yaml 파일에서 storage는 PV의 storage 보다 작거나 같아야 한다.
$ kubectl get pv,pvc

STATUS 값을 확인했을 때 위의 출력처럼 Bound가 되어야 한다. 만일 PVC의 STATUS 값이 Pending에서 변경되지 않는다면 PVC가 요청한 내용과 PV가 일치하지 않은 경우이기 때문에 설정 파일을 다시한번 확인해보길 바란다.
이제 pod.yaml을 통해 파드를 생성해보자.
-pod.yaml
apiVersion: v1
kind: Pod
metadata:
name: nfs-pod
spec:
containers:
- name: test-container
image: nginx
ports:
- containerPort: 80
name: http
volumeMounts:
- name: nfs-volume
mountPath: "/usr/share/nginx/html"
volumes:
- name: nfs-volume
persistentVolumeClaim:
claimName: nfs-pvc
컨테이너의 Volume과 연결하기 위해서는 volumeMounts와 volumes가 중요한데 연결하고자 하는 nginx의 경로, 앞서 생성해두었던 PVC의 이름을 등록하면 된다.
$ kubectl get pods -o wide

파드가 생성된 것이 확인되었으면 curl 명령어를 통해 내가 원하는 내용으로 조회가 되는 것을 볼 수 있다.

만일 NFS의 index.html의 내용이 변경된다면 이미지를 재빌드하고 파드를 재배포할 필요 없이 새로운 버전의 소스 코드가 적용될 것이다.
[참고 자료]
https://kubernetes.io/ko/docs/concepts/storage/persistent-volumes/
https://kubernetes.io/docs/tasks/configure-pod-container/configure-persistent-volume-storage/#create-a-persistentvolume
https://velog.io/@rockwellvinca/kubernetes-%EB%B3%BC%EB%A5%A8-emptyDir-hostDir-NFS
https://do-hansung.tistory.com/57