pod안의 각 컨테이너는 컨테이너 이미지에서 가져온 각자의 파일시스템을 갖는다. pod의 컨테이너가 재시작 된다면, 새 컨테이너는 이전 컨테이너에 의해 공유된 어떤 기록도 볼 수 없다.
데이터를 보관하는 디렉토리를 보존하기 위해 쿠버네티스는 볼륨 디렉토리를 제공한다. pod의 구성요소로서 컨테이너와 같이 pod의 사양에 정의되어있고, pod와 생명주기를 함께 한다. 볼륨은 독립된 쿠버네티스의 객체가 아니라서 스스로 생성되거나 삭제될 수 없다.
좌측의 각 컨테이너는 잘 정의된 단일 작업을 갖지만 웹 서버가 각 파일에 접근할 수 없다. 공유 디스크가 없는 세 컨테이너로 pod를 생성하는 것은 의미가 없다.
우측과 같이 2개의 볼륨을 pod에 추가하고 컨테이너 내부 경로에 마운트하면 파일시스템의 내용을 마운트된 디렉토리에서 액세스할 수 있다. 이렇게 하면, 3개의 컨테이너는 함께 일할 수 있다.
단일 pod는 서로 다른 유형의 여러 볼륨을 동시에 사용할 수 있다.
emptyDir 볼륨은 빈 디렉토리로 시작한다. 생명주기를 pod와 함께하기 때문에 볼륨의 내용은 pod가 삭제될 때 같이 사라진다. 같은 pod 안의 컨테이너 사이에서 파일을 공유할 때, 데이터를 일시적으로 기록할 때 사용한다.
apiVersion: v1
kind: Pod
metadata:
name: fortune
spec:
containers:
- image: luksa/fortune
name: html-generator
volumeMounts:
- name: html
mountPath: /var/htdocs
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
emptyDir: {}
html이름을 가진 단일 emptyDir 볼륨이 위의 두 컨테이너에 마운트 된다. html-generator에서는 /var/htdocs에, web-server에서는 /usr/share/nginx/html에 읽기 전용으로 마운트 된다.
출력물을 보려면 로컬에서 pod로 포트포워딩 후 접근
$ kubectl port-forward fortune 8080:80
$ curl http://localhost:8080
volumes:
- name: html
emptyDir:
medium: Memory
워커 노드의 실제 디스크 대신 메모리에 볼륨을 생성할 수 있다.
gitRepo 볼륨은 pod가 시작되고 컨테이너가 생성되기 전에 git 저장소를 복제하는 emptyDir 볼륨의 종류이다. 볼륨이 생성된 후에는 저장소와 동기화되지 않는다. pod가 재생성됐을 때 최신 commit이 포함된 볼륨이 생성된다. emptyDir처럼 pod가 삭제되면 볼륨과 콘텐츠도 함께 삭제된다.
apiVersion: v1
kind: Pod
metadata:
name: gitrepo-volume-pod
spec:
containers:
- image: nginx:alpine
name: web-server
volumeMounts:
- name: html
mountPath: /usr/share/nginx/html
readOnly: true
ports:
- containerPort: 80
protocol: TCP
volumes:
- name: html
gitRepo:
repository: https://github.com/junghyun326/kubia-website-example.git
revision: master
directory: .
pod를 생성하면 볼륨이 빈 디렉토리로 초기화되고, 지정된 git 저장소가 복제된다. master 브랜치를 쿠버네티스가 체크하도록 지정해주고, 볼륨의 root 디렉토리로 복제하고자 .으로 설정해준다.
git repo가 변경될 때마다 pod를 삭제할 필요 없이 동기화하는 방법에는 사이드카 컨테이너가 있다. 사이드카 컨테이너는 pod의 메인 컨테이너의 변경 없이 독립적으로 동작하는 컨테이너를 붙여 기능을 향상시키는 컨테이너이다.
Docker Hub에서 git sync
를 검색해서 로컬 디렉토리가 git 저장소와 동기화된 상태로 유지되는 컨테이너 이미지를 찾는다. git sync 컨테이너를 구성하여 파일이 git 저장소와 동기화되도록 한다.
대부분의 pod는 호스트 노드를 인식하지 못해서 노드의 파일 시스템에 액세스할 일이 없지만, 특정 시스템 레벨의 pod는 노드의 파일을 읽거나 액세스할 필요가 생긴다.
hostPath 볼륨은 노드의 파일시스템에 있는 특정 파일 또는 디렉토리를 가리킨다. 같은 노드에서 실행되고 hostPath 볼륨에서 같은 경로를 사용하는 pod들은 같은 파일을 볼 수 있다. pod를 종료할 때 hostPath 볼륨은 삭제되지 않는다.
그러나 hostPath 볼륨은 pod가 설정된 노드에 민감하게 작용해서 동일한 노드에서만 데이터가 유효하다. 때문에 단일 노드 클러스터의 지속되는 스토리지로 시범적으로 사용하는 경우가 많다.
모든 클러스터 노드에서 액세스 하려면 특정한 유형의 NAS(Network-Attached Storage)에 저장해야 한다.
GCE(Google Compute Engine)에서 클러스터 노드를 실행하는 GKE(Google Kubernetes Engine)에서 pod를 생성한다면, gcePersistentDisk를 스토리지 매커니즘으로 사용한다.
GCE persistent disk 생성
$ gcloud compute disks create --size=1GiB --zone=europe-west1-b mongodb
1GB사이즈의 디스크를 쿠버네티스 클러스터와 같은 존에 생성한다.
mongodb-pod-gcepd.yaml
apiVersion: v1
kind: Pod
metadata:
name: mongodb
spec:
volumes:
- name: mongodb-data
gcePersistentDisk:
pdName: mongodb
fsType: ext4
containers:
- image: mongo
name: mongodb
volumeMounts:
- name: mongodb-data
mountPath: /data/db
ports:
- containerPort: 27017
protocol: TCP
persistent disk의 이름은 위에서 만든 PD와 같아야 한다. /data/db에서 컨테이너의 볼륨을 마운트하는데, 그 곳이 MongoDB가 데이터를 저장하는 곳이다.
(minikube에서는 gce Persistent Disk 사용 불가)
이제 MongoDB에 데이터를 저장하면, persistent disk에 저장이 된다. pod를 다른 노드에서 재생성해도 동일한 데이터를 유지할 수 있다. (kubectl get po -o wide
로 노드 확인)
gcePersistentDisk 볼륨을 사용한 이유는 쿠버네티스 클러스터가 GKE에서 실행되기 때문이다. 클러스터를 다른 곳에서 실행하면 인프라에 따라 다른 유형의 볼륨을 사용하면 된다.
AWS Elastic Block Storage에 사용
apiVersion: v1
kind: Pod
metadata:
name: mongodb
spec:
volumes:
- name: mongodb-data
awsElasticBlockStore:
volumeId: my-volume
fsType: ext4
containers:
- ...
volumeId는 EBS의 ID로 설정한다.
단순 NFS를 마운트하려면 NFS 서버와 서버에서 노출된 경로를 지정한다.
volumes:
- name: mongodb-data
nfs:
server: 1.2.3.4
path: /some/path
인프라 관련 정보를 pod 정의에 포함한다는 것은 특정 쿠버네티스 클러스터에 상당히 연결되어 있음을 의미한다. 또한 동일한 pod 정의를 다른 pod 생성에서 사용하기 어렵다.
위의 볼륨 타입들은 pod 개발자가 실제 네트워크 스토리지 인프라에 대한 지식을 갖추어야 했다. 쿠버네티스에 애플리케이션을 배포하는 개발자는 시스템 아래에서 어떤 종류의 스토리지 기술이 사용되는지 알 필요가 없다. 개발자는 그저 필요한 스토리지를 쿠버네티스에 요청하면 된다.
: 애플리케이션을 통해 인프라 세부 사항을 처리하지 않고도 쿠버네티스 클러스터에 스토리지를 요청할 수 있게 하는 리소스
1) 클러스터 관리자가 기본 스토리지를 설정
2) kubernetes API 서버를 통해 PersistentVolume(PV) 리소스를 생성하여 쿠버네티스에 스토리지를 등록
3) 클러스터 사용자는 필요한 최소 크기 및 액세스 모드를 지정하는 PersistentVolumeClaim(PVC) 매니페스트를 생성
4) 매니페스트를 kubernetes API 서버에 제출하면 쿠버네티스가 적절한 PV를 찾아 PVC를 바인딩
5) PVC를 참조하는 볼륨을 pod 내부 볼륨 중 하나로 사용
바인딩된 PVC를 삭제하여 해제할 때까지 다른 사용자는 동일한 PV를 사용할 수 없다.
apiVersion: v1
kind: PersistentVolume
metadata:
name: mongodb-pv
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteOnce
- ReadOnlyMany
persistentVolumeReclaimPolicy: Retain
gcePersistentDisk:
pdName: mongodb
fsType: ext4
해당 볼륨은 단일 클라이언트에게 읽기 쓰기 허용으로 마운트되거나 여러 클라이언트에게 읽기 허용으로 마운트될 수 있다. claim이 해제되어도 PV는 삭제되지 않고 유지(retain)된다. gcePersistentDisk 기반으로 생성된다.
PV 목록 확인
$ kubectl get pv
- pv : persistentvolume 축약형
아직 PVC를 만들지 않아서 STATUS가 Available일 것
PV는 클러스터 레벨 리소스
PV는 네임스페이스에 속하지 않아서 클러스터에서 공용으로 사용이 가능하다. PVC는 네임스페이스 객체이기 때문에 네임스페이스에 속한다. 특정 네임스페이스에서만 생성이 가능하고 동일한 네임스페이스에 있는 pod에서만 사용이 가능하다.
PV를 할당하는 것은 pod를 만드는 것과는 완전히 별개의 프로세스이다.
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodb-pvc
spec:
resources:
requests:
storage: 1Gi
accessModes:
- ReadWriteOnce
storageClassName: ""
단일 클라이언트에서 읽기 쓰기 가능한 1GB 용량의 스토리지 요청
claim을 생성하는 즉시 쿠버네티스는 요청을 만족하는 PV를 찾아 claim에 바인딩한다.
$ kubectl get pvc
이제 PV의 STATUS는 Bound로 변경되고, CLAIM에 mongodb-pvc가 바인딩 되어있을 것이다.
pod에서 PVC를 사용하려면 pod의 볼륨 내에서 PVC를 이름으로 참조한다.
apiVersion: v1
kind: Pod
metadata:
name: mongodb
spec:
containers:
- image: mongo
name: mongodb
volumeMounts:
- name: mongodb-data
mountPath: /data/db
ports:
- containerPort: 27017
protocol: TCP
volumes:
- name: mongodb-data
persistentVolumeClaim:
claimName: mongodb-pvc
이전에 MongoDB를 통해 볼륨에 저장한 데이터가 있다면, MongoDB 셸 실행 시 확인이 가능하다.
위 그림은 pod가 GCE Persistent Disk를 사용하는 두 가지 방법(직접 연결과 Claim을 통한 연결)
$ kubectl delete pod mongodb
$ kubectl delete pvc mongodb-pvc
$ kubectl get pvc
claim의 STATUS가 Bound가 아닌 Pending으로 표시된다.
$ kubectl get pv
볼륨의 STATUS는 available이 아닌 released로 표시된다.
한 번 사용된 볼륨은 데이터를 포함할 수 있기 때문에 관리자에게 볼륨을 정리할 기회를 주기 위해 released로 표시되고 즉시 바인딩되지 않는다.
PV 수동 반환
persistentVolumeReclaimPolicy를 Retain으로 설정한다. 재활용하기 위해서는 PV를 삭제한 뒤 재생성한다. 기본 스토리지의 파일을 삭제하거나 재사용하도록 그대로 둘 수 있다는 장점이 있다.
PV 자동 반환
스토리지를 초기에 프로비저닝하려면 클러스터 관리자가 필요한데, 다행히 쿠버네티스에서는 PV의 동적 프로비저닝을 통해 해당 작업을 자동으로 수행할 수 있다.
클러스터 관리자가 PV provisioner를 배포하고 하나 이상의 StorageClass 객체를 정의해서 개발자가 작성한 PVC요청이 있을 때 마다 새 PV를 생성할 수 있게 한다. PV와 마찬가지로 StorageClass 리소스도 namespace에 속하지 않는다.
StorageClass 리소스는 PVC가 이 StorageClass를 요청할 때, PV를 프로비저닝하는 데 사용할 프로비저너를 지정한다.
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
provisioner: kubernetes.io/gce-pd
parameters:
type: pd-ssd
zone: europe-west1-b
(minikube 사용 시엔 storageclass-fast-hostpath.yaml 배포)
매개 변수는 프로비저너에게 전달된다.
스토리지 클래스 리소스를 생성하면 PVC에서 스토리지 클래스를 이름으로 참조할 수 있다. 존재하지 않는 클래스를 참조할 경우 프로비저닝이 실패한다(ProvisioningFailed 이벤트 발생).
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: mongodb-pvc
spec:
storageClassName: fast
resources:
requests:
storage: 100Mi
accessModes:
- ReadWriteOnce
이제 동적 프로비저닝이 가능하다. 기존에 수동으로 프로비저닝된 PV가 일치하는 경우에도 provisioner가 사용된다.$ gcloud compute disks list
명령어를 사용하여 볼륨 뿐만 아니라 실제 디스크도 프로비저닝된 것을 확인할 수 있다.
claim이 이름으로 참조하기 때문에 클래스 이름이 동일한 클러스터 간에는 PVC 정의의 이동이 가능하다.
PVC를 생성할 때, storageClassName 속성을 지정하지 않고 매니페스트 파일 작성이 가능하다. 그럴 때도 동적 프로비저닝은 가능하다.
스토리지 클래스 목록 확인
$ kubectl get sc
스토리지 클래스의 자세한 정보 확인
$ kubectl get sc <sc-name> -o yaml
Standard 스토리지 클래스
$ kubectl get sc standard -o yaml
명령어로 standard 클래스 정의를 확인하면, annotation에 default 클래스임을 볼 수 있다. PVC에서 사용할 SC를 지정하지 않은 경우 PV를 프로비저닝하는 데 사용되는 클래스이다.
kind: PersistentVolumeClaim
spec:
storageClassName: ""
storageClassName 속성을 빈 문자열로 설정하지 않으면 프로비저너는 사전에 프로비저닝된 PV이 있어도 새 PV를 프로비저닝했을 것이다.
지속적인 스토리지를 pod에 연결하는 가장 좋은 방법은 PVC(필요하다면 storageClassName을 가진)와 pod(이름으로 PVC를 참조하는)만을 생성하는 것이다. 다른 작업은 동적 PV provisioner가 처리한다.
출처
Kubernetes in Action
Q. persistentVolume 반환 정책 중 볼륨의 내용을 삭제하고 다시 할당할 수 있도록 하는 정책은 무엇인가? 그리고 해당 정책은 자동인가 수동인가?
Q. PersistentVolumeClaim을 새로 생성된 PV가 아닌 사전에 수동 프로비저닝한 PersistentVolume에 강제로 바인딩 하고자한다. 어떻게 설정 가능한가?