🥞 BE
home

Ch14. 데이터 저장

목차
컨테이너 안에 저장된 데이터는 해당 컨테이너가 삭제됐을 때 모두 사라진다. 컨테이너가 사라지더라도 데이터를 보존하도록 컨테이너 외부에 데이터를 저장하는 방법을 알아보자.

14.1 볼륨

컨테이너의 장점은 기본적으로 상태가 없는(stateless) 앱 컨테이너를 사용하는 것. stateless란 컨테이너에 문제가 있거나, 노드에 장애가 발생해서 컨테이너를 새로 실행 시, 다른 노드로 자유롭게 옮길 수 있는 것을 말한다.
but, 어떤 이유로든 컨테이너가 실행되지 않거나 삭제되었을 때 저장 데이터가 사라진다는 단점이 있다.
앱의 특성에 따라 컨테이너에 문제가 생기더라도 데이터를 보존해야 할 때가 있다. 대표적으로 데이터를 파일로 저장하는 젠킨스나, MySQL 같은 데이터베이스가 있다. 이런 상황에서 볼륨(volume)을 사용한다. 볼륨은 파드의 일부분으로 정의되며 파드와 동일한 라이프사이클을 갖는 디스크 스토리지이다.
볼륨을 사용하면 컨테이너를 재시작하더라도 데이터를 유지한다. 퍼시스턴트 볼륨을 사용하면 데이터를 저장했던 노드가 아닌 다른 노드에서 컨테이너를 재시작하더라도 데이터를 저장한 볼륨을 그대로 사용할 수 있도록 해준다.
볼륨 관련 필드 중 .spec.container.volumeMounts.mountPropagation가 있다. 파드 하나 안에 있는 컨테이너들끼리 또는 같은 노드에 실행된 파드들끼리 볼륨을 공유해서 사용할지를 설정한다. 필드 값으로 다음 세 가지를 사용할 수 있다.
None : 이 필드 값으로 볼륨을 마운트했으면, 호스트에서 볼륨에 해당하는 디렉토리 하위에 마운트한 다른 마운트들은 볼 수 없다. 컨테이너가 만들어 놓은 마운트를 호스트에서 볼 수도 없다. 기본 필드 값이다.
HostToContainer : 이 필드 값으로 볼륨을 마운트했으면, 호스트에서 해당 볼륨 하위에 마운트된 다른 디렉토리들도 해당 볼륨에서 볼 수 있도록 한다.
Bidirectional : 이 필드 값으로 볼륨을 마운트했으면 HostToContainer 처럼 하위에 마운트 된 디렉토리도 볼 수 있고, 호스트 안 다른 모든 컨테이너난 파드에서 같은 볼륨을 사용할 수 있다.
로컬 서버에서 사용할 수 있는 볼륨 중에서 내부 호스트의 디스크를 사용하는 emptyDir, hostPath를 살펴보자.

14.1.1 emptyDir

emptyDir파드가 실행되는 호스트의 디스크를 임시로 컨테이너에 볼륨으로 할당해서 사용하는 방법이다. 파드가 사라지면 emptyDir에 할당해서 사용했던 볼륨의 데이터도 함께 사라진다. 주로 메모리와 디스크를 함께 이용하는 대용량 데이터 계산 or 오랜 시간 연산 시 중간 데이터 저장용으로 디스크가 필요할 때 사용한다.
연산 중 컨테이너에 문제가 생겨 재시작하더라도 파드는 살아있으므로 emptyDir에 저장해둔 데이터를 계속 이용할 수 있다.
emptyDir 설정 예
apiVersion: v1 kind: Pod metadata: name: kubernetes-simple-pod spec: containers: - name: kubernetes-simple-pod image: arisu1000/simple-container-app:latest volumeMounts: - mountPath: /emptydir name: emptydir-vol # ports: # - containerPort: 8080 volumes: - name: emptydir-vol emptyDir: {}
YAML
복사
volume/volume-emptydir.yaml
.spec.volumes[] 하위 필드에 사용하려는 볼륨들을 먼저 선언. 여기서는 .name 필드 값을 emptydir-vol으로 설정했고, emptyDir을 사용하려고 .emptyDir{}을 설정했다.
이렇게 선언한 볼륨을 컨테이너 설정(.spec.containers[].volumeMounts[] 하위 필드)에서 불러와서 사용할 수 있다. .spec.containers[].volumeMounts[].mountPath 필드 값으로 컨테이너의 /emptydir 디렉토리를 설정해 볼륨을 마운트했다.
쿠버네티스의 볼륨 설정은 이렇게 볼륨 선언과 컨테이너에서 마운트하는 부분을 분리해서 설정한다. 그래서 다양한 형식의 볼륨을 컨테이너에 마운트해서 사용할 수 있다.

14.1.2 hostPath

hostPath파드가 실행된 호스트의 파일이나 디렉토리를 파드에 마운트한다. emptyDir이 임시 디렉토리를 마운트하는 것이라면 hostPath는 호스트에 있는 실제 파일이나 디렉토리를 마운트한다.
hostPath는 파드를 재시작해도 호스트에 데이터가 남기 때문에 호스트의 중요 디렉토리를 컨테이너에 마운트해서 사용할 수 있다. 시스템용 디렉토리를 마운트해서 시스템을 모니터링하는 용도로도 사용할 수 있다.
apiVersion: v1 kind: Pod metadata: name: kubernetes-hostpath-pod spec: containers: - name: kubernetes-hostpath-pod image: arisu1000/simple-container-app:latest volumeMounts: - mountPath: /test-volume name: hostpath-vol ports: - containerPort: 8080 volumes: - name: hostpath-vol hostPath: path: /tmp type: Directory
YAML
복사
volume/volume-hostpath.yaml
.spec.containers[].volumeMounts[] 하위 필드를 살펴보면 .mountPath 필드는 볼륨을 컨테이너의 /test-volume라는 디렉토리에 마운트하도록 값을 설정했다. .name 필드값으로는 hostpath-vol을 설정해 볼륨의 이름을 정했다.
.spec.volumes[] 의 하위 필드를 보면, 경로를 뜻하는 .hostPath.path 필드 값으로는 호스트의 /tmp 디렉토리를 설정했고 설정한 경로가 어떤 타입인지 뜻하는 .type 필드 값으로는 Directory를 설정했다.
hostPath 볼륨을 설정할 때 사용하는 .spec.volumes[].hostpath.type 필드 값은 다음과 같다.
필드 값
내용
설정하지 않음
hostPath 볼륨을 마운트하기 전 아무것도 확인하지 않는다.
DirectoryOrCreate
설정한 경로에 디렉토리가 없으면 퍼미션이 755인 빈 디렉토리를 만든다.
Directory
설정한 경로에 디렉토리가 존재해야 한다. 호스트에 해당 디렉토리가 없으면 파드는 ContainerCreating 상태로 남고 생성이 안된다.
FileOrCreate
설정한 경로에 파일이 없으면 퍼미션 644인 빈 파일을 만든다.
File
설정한 경로에 파일이 있는지 확인한다. 파일이 없으면 파드 생성이 안된다.
Socket
설정한 경로에 유닉스 소켓 파일이 있어야 한다.
CharDevice
설정한 경로에 문자(character) 디바이스가 있는지 확인한다.
BlockDevice
설정한 경로에 블록(block) 디바이스가 있는지 확인한다.
위 yaml파일을 kubectl apply -f volume-hostpath.yaml 명령으로 클러스터에 적용한다. 다음 kubectl exec kubernetes-hostpath-pod -it -- sh 명령으로 컨테이너 안에 접속해서 /test-volume 디렉토리에 test.txt 파일을 생성한다.
즉, volume-hostpath.yaml 에서 설정했던 내용대로 컨테이너 안 /test-volume 디렉토리는 컨테이너 외부 호스트의 /tmp 디렉토리를 마운트했고 데이터를 보존함을 알 수 있다.

14.1.3 nfs

nfs(network file system) 볼륨기존에 사용하는 NFS 서버를 이용해 파드에 마운트하는 것이다. NFS 클라이언트 역할이라고 생각하면 된다.
여러 개 파드에서 볼륨 하나를 공유해 읽기/쓰기를 동시에 할 때도 사용한다. 쿠버네티스를 지원하는 스토리지들이 많지만 여러 개의 파드에서 읽기/쓰기를 지원하는 스토리지를 사용할 수 없는 상황이 발생할 수 있기 때문이다. 이때는 파드 하나에 안정성이 높은 외부 스토리지를 볼륨으로 설정한 후 해당 파드에 NFS 서버를 설정한다. 다른 파드는 해당 파드의 NFS 서버를 nfs 볼륨으로 마운트한다.
다음 코드는 NFS서버를 실행하는 nfs-server라는 이름의 디플로이먼트 템플릿이다.
apiVersion: apps/v1 kind: Deployment metadata: name: nfs-server labels: app: nfs-server spec: replicas: 1 selector: matchLabels: app: nfs-server template: metadata: labels: app: nfs-server spec: containers: - name: nfs-server image: arisu1000/nfs-server:latest ports: - name: nfs containerPort: 2049 - name: mountd containerPort: 20048 - name: rpcbind containerPort: 111 securityContext: privileged: true volumeMounts: - mountPath: /exports name: hostpath-vol volumes: - name: hostpath-vol hostPath: path: /tmp type: Directory
YAML
복사
volume/volume-nfsserver.yaml
.spec.template.spec.containers[].ports[]의 두 번째 .name 필드 값 mountd는 NFS서버에서 사용하는 프로세스이다. 요청이 왔을 때 지정한 디렉토리로 볼륨을 마운트하는 mountd 데몬이 사용하는 포트를 지정한다.
.spec.template.spec.containers[].ports[]의 세 번째 .name 필드 값 rpcbind도 NFS서버에서 사용하는 프로세스이다. 시스템에서 RPC(Remote Procedure Call) 서비스를 관리할 rpcbind 데몬이 사용하는 포트를 지정한다.
.spec.template.spec.containers[].securityContext컨테이너의 보안 설정을 한다. 여기서는 컨테이너가 실행 중인 호스트 장치의 접근 권한을 설정하는 .privileged 필드 값으로 true를 설정해 모든 호스트 장치에 접근할 수 있도록 했다.
.spec.template.spec.containers[].volumeMounts[].mountPath 필드 값은 볼륨을 마운트할 디렉토리 경로로 /exports를 설정했다.
위 코드를 저장 후 kubectl apply -f volume-nfsserver.yaml 로 클러스터에 적용한다.
kubectl get pods -o wide -l app=nfs-server 명령으로 실행한 컨테이너의 IP를 확인한다.
IP를 확인했으면 NFS 서버에 접속할 클라이언트 앱 컨테이너를 실행할 차례이다.
apiVersion: apps/v1 kind: Deployment metadata: name: kubernetes-nfsapp-pod labels: app: nfs-client spec: replicas: 2 selector: matchLabels: app: nfs-client template: metadata: labels: app: nfs-client spec: containers: - name: kubernetes-nfsapp-pod image: arisu1000/simple-container-app:latest volumeMounts: - mountPath: /test-nfs name: nfs-vol ports: - containerPort: 8080 volumes: - name: nfs-vol nfs: server: 10.1.6.126 # 여기에 nfs-server pod의 ip를 넣는다. path: /
YAML
복사
volume/volume-nfsapp.yaml
.spec.template.spec.containers[].volumeMounts[].mountPath 필드에는 nfs 볼륨을 마운트할 디렉토리로 /test-nfs를 설정했다.
nfsserver 코드로 만든 nfs-server 파드의 IP를 .spec.template.spec.volumes[].nfs.server 필드 값으로 설정했다.
다시 kubectl apply -f volume-nfsapp.yaml 으로 클러스터에 적용하면 파드 2개가 nfs 볼륨을 사용할 수 있는 상태로 실행된다.
파드 2개 중 하나에 접속해서 파일을 변경해보자. kubectl get pods -l app=nfs-client 명령으로 실행 중인 파드를 확인한 후, 파드 하나를 선택해 kubectl exec -it 파드 이름 -- sh 명령으로 파드에 접속한다.
그리고 마운트한 디렉토리인 /test-nfs/로 이동해 index.html 파일을 확인한다.

14.2 퍼시스턴트 볼륨과 퍼시스턴트 볼륨 클레임

쿠버네티스에서 볼륨을 사용하는 구조는 PV라고 하는 퍼시스턴트 볼륨(PersistentVolume)PVC라고 하는 퍼시스턴트 볼륨 클레임(PersistentVolumeClaim) 2개로 분리되어 있다.
PV는 볼륨 자체를 뜻하며 클러스터 안에서 자원으로 다룬다. 파드하고는 별개로 관리되고 별도의 생명주기가 있다.
PVC는 사용자가 PV에 하는 요청이다. 사용하고 싶은 용량은 얼마인지, 읽기/쓰기는 어떤 모드로 설정하고 싶은지 등을 정해서 요청한다.
PV와 PVC는 다음과 같은 생명 주기가 있다.

14.2.1 프로비저닝

프로비저닝(Provisioning)PV가 만들어지는 단계이다.
PV를 제공하는 방법은 PV를 직접 생성하고 사용하는 정적 프로비저닝과 추가 리소스(스토리지 클래스)가 필요하지만 볼륨 사용 요청이 있을 때마다 자동으로 생성하는 동적 프로비저닝이 있다.
정적으로 프로비저닝할 때는 클러스터 관리자가 미리 적정 용량의 PV를 만들어 두고 사용자의 요청이 있으면 미리 만들어둔 PV를 할당한다. 사용할 수 있는 스토리지 용량에 제한이 있을 때 유용하다.
미리 만들어둔 PV의 용량이 100GB라면 150GB를 사용하려는 요청들은 실패한다. 1TB 스토리지를 사용하더라도 미리 만들어둔 PV용량이 150GB 이상인 것이 없으면 실패.
동적으로 프로비저닝할 때는 사용자가 PVC를 거쳐서 PV를 요청했을 때 생성해 제공한다. 쿠버네티스 클러스터에 사용할 1TB 스토리지가 있다면 사용자가 필요할 때 원하는 용량만큼을 생성해서 사용할 수 있다. 한번에 200GB PV도 만들어 사용할 수 있다.

14.2.2 바인딩

바인딩(Binding)PVC 리소스를 만들어 준비된 PV 리소스와 연결하는 단계이다.
PVC 리소스에는 원하는 PV 리소스나 스토리지 용량 및 접근 방법 등을 정의하는데, 적절한 PV리소스가 없다면 요청이 실패하고, 적절한 PV 리소스가 생성되어 연결될 때까지 대기한다.
PV와 PVC는 반드시 1:1로만 연결되어야 한다.
→ A- PVC가 사용하고 있는 A- PV를 B- PVC가 A- PV에 바인딩 할 수 없다.

14.2.3 사용

사용(Using)PVC에서 제공한 볼륨을 파드가 마운트해서 사용하고 있는 단계이다.
파드가 사용 중인 PVC 와 PVC가 사용 중인 PV는 임의로 삭제되지 않는다. 이 기능을 ‘사용중인 스토리지 오브젝트 보호’라고 한다. 파드가 사용중인 PVC를 삭제하려고 하면 상태가 Terminating이지만 해당 PVC를 사용 중인 파드가 남아있을 때는 PVC도 삭제되지 않고 남아있다.

14.2.4 반환

반환(Reclaiming)사용이 끝난 PVC가 종료/삭제되면 연결된 PV를 초기화하는 단계이다.
초기화 정책은 3가지가 있다.
Retain
유지(Retain)PV를 그대로 보존한다. PVC가 삭제되면 사용중이던 PV는 해제 상태라서 아직 다른 PVC가 재사용할 수 없다. 단순히 사용 해제 상태이므로 PV안의 데이터는 그대로 남아있다.
이 PV를 재사용하려면 관리자가 다음 순서대로 직접 초기화해줘야 한다.
PV Reclaiming 절차
1.
PV를 삭제한다. (외부 스토리지는 데이터가 유지됩니다)
2.
데이터가 더 이상 필요하지 않다면 스토리지에서 삭제한다.
3.
동일한 데이터를 사용해야 한다면, 해당 데이터를 가지고 있는 볼륨을 이용해 PV 리소스를 다시 생성한다.
Delete
삭제(Delete)PVC 리소스가 삭제되면 PV 리소스도 같이 삭제되고, 연결되어 있는 외부 스토리지의 데이터도 삭제한다. 동적 프로비저닝, AWS, GCP, Azure의 기본 정책이다.
Recycle
재활용(Recycle)PV의 데이터들을 삭제하고 다시 새로운 PVC에서 PV를 사용할 수 있도록 한다. 특별한 파드를 만들어 두고 데이터를 초기화하는 기능도 있다.
현재는 NFS, hostPath 볼륨만 지원한다.
이 정책은 추후에 없어질 예정이며, 동적 볼륨 할당을 기본 사용할 것을 권장.

14.3 퍼시스턴트 볼륨 템플릿

apiVersion: v1 kind: PersistentVolume metadata: name: pv-hostpath spec: capacity: storage: 2Gi volumeMode: Filesystem accessModes: - ReadWriteOnce storageClassName: manual persistentVolumeReclaimPolicy: Delete hostPath: path: /tmp/k8s-pv
YAML
복사
volume/pv-hostpath.yaml
.spec.capacity.storage 필드 값은 이진법 접두어인 2Gi를 설정했다. 이는 스토리지 용량으로 2GB를 설정했다는 뜻이다.
.spec.volumeMode 필드의 기본 필드 값은 Filesysytem으로 볼륨을 파일 시스템 형식으로 설정해서 사용한다. 추가로 raw라는 필드 값을 설정할 수 있다.
.spec.accessModes 필드는 볼륨의 읽기/쓰기 옵션을 설정한다. 볼륨은 한번에 .spec.accessModes 필드를 하나만 설정할 수 있으며 필드 값은 세 가지가 있다.
ReadWriteOnce : 노드 하나에만 볼륨을 읽기/쓰기하도록 마운트할 수 있음.
ReadOnlyMany : 여러 개 노드에서 읽기 전용으로 마운트할 수 있음.
ReadWriteMany : 여러 개 노드에서 읽기/쓰기 가능하도록 마운트할 수 있음.
.spec.storageClassName스토리지 클래스를 설정하는 필드이다. 특정 스토리지 클래스가 있는 PV는 해당 스토리지 클래스에 맞는 PVC만 연결된다. PV에 .spec.storageClassName 필드 설정이 없으면 .spec.storageClassName 필드 설정이 없는 PVC와만 연결된다.
.spec.persistanteVolumeReclaimPolicy 필드는 PV가 해제되었을 때의 초기화 옵션을 설정한다. 앞에서 살펴봤던 것처럼 Retain, Recycle, Delete 정책 중 하나를 설정한다.
.spec.hostPath 필드는 해당 PV의 볼륨 플러그인을 명시한다. 하위의 .path 필드에는 마운트시킬 로컬 서버의 경로를 설정한다.
위 코드를 클러스터에 적용하고 kubectl get pv 명령으로 PV의 상태를 확인한다.
STATUS 항목이 Available이면 정상적으로 설정된 것이다.
PV상태는 PVC에서 사용할 수 있도록 준비된 Available, 특정 PVC에 연결된 Bound, PVC는 삭제되었고 PV는 아직 초기화되지 않는 Released, 자동 초기화를 실패한 Failed가 있다.

14.4 퍼시스턴트 볼륨 클레임 템플릿

kind: PersistentVolumeClaim apiVersion: v1 metadata: name: pvc-hostpath spec: accessModes: - ReadWriteOnce volumeMode: Filesystem resources: requests: storage: 1Gi storageClassName: manual
YAML
복사
volume/pvc-hostpath.yaml
.spec.resources.requests.storage 필드는 자원을 얼마나 사용할 것인지 요청(request)한다. 여기서는 필드 값으로 1Gi(1GB)를 설정했다. 필드 값을 설정할 때는 앞에서 만든 PV의 용량을 초과하면 안된다. 만약 .spec.resources.requests.storage 필드 값으로 PV의 용량 이상을 설정하면 사용할 수 있는 PV가 없으므로 PVC를 생성할 수 없는 Pending 상태가 된다.

14.5 레이블로 PVC와 PV 연결하기

apiVersion: v1 kind: PersistentVolume metadata: name: pv-hostpath-label labels: location: local spec: capacity: storage: 2Gi volumeMode: Filesystem accessModes: - ReadWriteOnce storageClassName: manual persistentVolumeReclaimPolicy: Delete hostPath: path: /tmp/k8s-pv
YAML
복사
volume/pv-hostpath-label.yaml
kind: PersistentVolumeClaim apiVersion: v1 metadata: name: pvc-hostpath-label spec: accessModes: - ReadWriteOnce volumeMode: Filesystem resources: requests: storage: 1Gi storageClassName: manual selector: matchLabels: location: local
YAML
복사
volume/pvc-hostpath-label.yaml
pv-hostpath-label에서는 .metadata.labels.location 필드에 local이라는 값을 설정해서 레이블을 추가했다.
pvc-hostpath-label에서는 아래의 코드 처럼 .metadata.labels.location 필드 대신 .spec.selector.matchExpressions[] 필드로 원하는 레이블 조건을 설정할 수도 있다.
spec: selector: matchExpressions: - {key: stage, operator: In, values: [development]}
YAML
복사
두 파일 모두 kubectl apply -f 명령으로 클러스터에 적용한다.

14.6 파드에서 PVC를 볼륨으로 사용하기

앞서 만든 PVC를 실제 파드에서 사용하도록 설정한다.
apiVersion: apps/v1 kind: Deployment metadata: name: kubernetes-simple-app labels: app: kubernetes-simple-app spec: replicas: 1 selector: matchLabels: app: kubernetes-simple-app template: metadata: labels: app: kubernetes-simple-app spec: containers: - name: kubernetes-simple-app image: arisu1000/simple-container-app:latest ports: - containerPort: 8080 imagePullPolicy: Always volumeMounts: - mountPath: "/tmp" name: myvolume volumes: - name: myvolume persistentVolumeClaim: claimName: pvc-hostpath
YAML
복사
volume/deployment-pvc.yaml
.spec.template.spec.volumes[].name 필드 값은 사용할 볼륨을 설정한다. 여기에서는 myvolume이라고 했다. .spec.template.spec.volumes[].persistentVolumeClaim 필드 값으로는 pvc-hostpath.yaml 파일로 만든 pvc-hostpath를 사용할 PVC로 설정한다.
준비한 볼륨을 실제 컨테이너에 연결하는 것은 .spec.template.spec.containers[].volumeMounts[]의 하위 필드이다. .mountPath 필드 값은 마운트 경로를 컨테이너의 /tmp 디렉토리로 설정했다. .name 필드 값은 마운트할 볼륨 이름인 myvolume을 설정했다. 참고로 kubernetes-pvc-app 디플로이먼트는 /tmp 디렉토리에 app.log라는 이름으로 접속로그를 남긴다.
파일을 클러스터에 적용 후 kubectl port-forward pods/파드이름 8080:8080 명령으로 파드에 접근할 수 있는 포트 번호를 8080으로 설정한다.
앞서 PV를 컨테이너 안 /tmp/k8s-pv라는 디렉토리에 만들도록 설정했다. 그럼 로컬 서버의 /tmp/k8s-pv 디렉토리가 컨테이너의 /tmp 디렉토리 하위에 마운트되었을 것이다. cat /tmp/k8s-pv/app.log 명령으로 확인한다.
kubernetes-simple-app 디플로이먼트의 접속 로그인 app.log를 확인할 수 있다.

14.7 PVC 크기 늘리기

gcePersistentDisk, awsElasticBlockStore, cinder, glusterfs, rbd, azureFile, azureDisk, portworxVolume 등의 볼륨 플러그인이라면 한번 할당한 PVC의 용량을 늘릴 수 있다. 이 기능을 사용하려면 .spec.storageClassName.allowVolumeExpansion 필드 값이 true로 설정되어야 한다.
PVC의 크기를 늘릴 때는 기존 .spec.resources.requests.storage 필드 값에 더 높은 용량을 설정한 후 클러스터에 적용한다. 볼륨에서 사용 중인 파일 시스템이 XFS, Ext3, Ext4라면 파일 시스템이 있더라도 볼륨 크기를 늘릴 수 있다.
파일 시스템이 있는 볼륨 크기를 늘리는 작업은 해당 PVC를 사용하는 새로운 파드를 실행할 때만 진행된다. 그래서 기존에 특정 파드가 사용 중인 볼륨 크기를 늘리려면 파드를 재시작해야한다.

14.8 노드별 볼륨 개수 제한

쿠버네티스에서는 노드 하나에 설정할 수 있는 볼륨 개수에 제한을 둔다. kube-scheduler 컴포넌트의 KUBE_MAX_PD_VOLS 환경 변수를 이용해서 설정할 수 있다.
클라우드 서비스별로는 다음과 같은 제한 사항이 있다.
클라우드 서비스
노드별 최대 볼륨 개수
Amazon Elastic Block Store (EBS)
39
Google Persistent Disk
16
Microsoft Azure Disk Storage
16

Reference