StorageClass란?
Kubernetes에서는 Dynamic Provisioning이라는 기능을 통해 PV를 자동으로 생성할 수 있습니다. 이 메커니즘의 핵심은 StorageClass API 객체입니다.
StorageClass는 다음 두 가지 내용을 정의합니다:
1. PV 속성: 스토리지 유형, 볼륨 크기 등.
2. PV 생성에 필요한 스토리지 플러그인.
이 정보를 바탕으로 Kubernetes는 사용자가 제출한 PVC에 맞는 StorageClass를 찾아 해당 플러그인을 호출하여 필요한 PV를 생성합니다.
사용 방법은 간단하며, 요구사항에 따라 YAML 파일을 작성하고 kubectl create 명령어로 실행하면 됩니다.
왜 StorageClass가 필요한가?
대규모 Kubernetes 클러스터에서는 수천 개의 PVC가 존재할 수 있으며, 이를 위해 관리자는 많은 PV를 미리 준비해야 합니다. 새로운 PVC가 추가될 때마다 새로운 PV도 계속 추가해야 하며, 그렇지 않으면 새로운 Pod가 PVC를 바인딩하지 못해 생성에 실패할 수 있습니다. 또한, 애플리케이션의 다양한 저장소 요구사항(읽기/쓰기 속도, 동시성 성능 등)을 충족시키기 위해 StorageClass를 사용하여 다양한 종류의 스토리지 리소스를 정의하고 사용자에게 제공할 수 있습니다.
StorageClass 동작 원리 및 배포 절차
NFS를 사용하는 경우 nfs-client라는 프로비저너를 설치해야 합니다. 이 프로그램은 이미 설정된 NFS 서버를 사용하여 PV를 자동으로 생성합니다.
1. 자동 생성된 PV는 ${namespace}-${pvcName}-${pvName} 형식으로 NFS 서버의 공유 데이터 디렉토리에 생성됩니다.
2. PV가 해제되면 archived-${namespace}-${pvcName}-${pvName} 형식으로 NFS 서버에 유지됩니다.
1. 동작 원리 및 배포 절차 설명
다음과 같이 StorageClass+NFS를 설정하는 주요 단계는 다음과 같습니다:
1. NFS 서버 설정
2. Service Account 생성: NFS 프로비저너의 권한 관리를 위한 계정
3. StorageClass 생성: PVC 생성 및 NFS 프로비저너 호출을 담당하며 PV와 PVC 연결 관리
4. NFS 프로비저너 생성: NFS 공유 디렉토리에 마운트 포인트 생성 및 PV 생성 후 NFS 마운트 포인트와 연결
StorageClass 생성
1. NFS 공유 서비스 설정
설정 과정은 간단하므로 생략하며, 필요 시 검색을 통해 확인 가능합니다.
현재 환경에서의 NFS 서버 및 공유 디렉토리 정보
IP: 192.168.100.10
Export PATH: /data/volumes/
2. RBAC 관련 설정 문서
rbac.yaml: namespace만 수정하면 됩니다.
apiVersion: v1
kind: ServiceAccount
metadata:
name: nfs-provisioner-sa
namespace: default # 실제 환경에 맞게 수정
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-provisioner-role
rules:
- apiGroups: [""]
resources: ["persistentvolumes"]
verbs: ["get", "list", "watch", "create", "delete"]
- apiGroups: [""]
resources: ["persistentvolumeclaims"]
verbs: ["get", "list", "watch", "update"]
- apiGroups: ["storage.k8s.io"]
resources: ["storageclasses"]
verbs: ["get", "list", "watch"]
- apiGroups: [""]
resources: ["events"]
verbs: ["create", "update", "patch"]
---
kind: ClusterRoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: nfs-provisioner-binding
subjects:
- kind: ServiceAccount
name: nfs-provisioner-sa
namespace: default
roleRef:
kind: ClusterRole
name: nfs-provisioner-role
apiGroup: rbac.authorization.k8s.io
---
kind: Role
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-provisioner
namespace: default
rules:
- apiGroups: [""]
resources: ["endpoints"]
verbs: ["get", "list", "watch", "create", "update", "patch"]
---
kind: RoleBinding
apiVersion: rbac.authorization.k8s.io/v1
metadata:
name: leader-locking-nfs-provisioner-binding
subjects:
- kind: ServiceAccount
name: nfs-provisioner-sa
namespace: default
roleRef:
kind: Role
name: leader-locking-nfs-provisioner
apiGroup: rbac.authorization.k8s.io
3. StorageClass 생성
nfs-storage-class.yaml
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: managed-nfs-sc
provisioner: my-nfs-provisioner # provisioner 설정 파일의 PROVISIONER_NAME 환경 변수와 일치해야 함
parameters:
archiveOnDelete: "false"
4. NFS 프로비저너 생성
nfs-provisioner-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: nfs-provisioner-deploy
namespace: default
spec:
replicas: 1
selector:
matchLabels:
app: nfs-provisioner-app
template:
metadata:
labels:
app: nfs-provisioner-app
spec:
serviceAccountName: nfs-provisioner-sa
containers:
- name: nfs-provisioner-container
image: quay.io/external_storage/nfs-client-provisioner:latest
env:
- name: PROVISIONER_NAME
value: my-nfs-provisioner # provisioner 이름, nfs-storage-class.yaml 파일의 provisioner 이름과 일치해야 함
- name: NFS_SERVER
value: 192.168.100.10 # NFS 서버 IP 주소
- name: NFS_PATH
value: /data/volumes # NFS 마운트 볼륨
volumeMounts:
- name: nfs-root
mountPath: /persistentvolumes
volumes:
- name: nfs-root
nfs:
server: 192.168.100.10 # NFS 서버 IP 주소
path: /data/volumes # NFS 마운트 볼륨
테스트 Pod 생성 및 검증
1. Pod + PVC
test-claim.yaml
kind: PersistentVolumeClaim
apiVersion: v1
metadata:
name: test-pvc
annotations:
volume.beta.kubernetes.io/storage-class: "managed-nfs-sc" # nfs-storage-class.yaml의 metadata.name과 일치해야 함
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Mi
PVC 상태가 Bound인지 확인
[root@k8s-master ~]# kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-pvc Bound pvc-abc12345-1234-5678-abcd-1234567890ab 1Mi RWX managed-nfs-sc 2m48s
[root@k8s-master ~]# kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
pvc-abc12345-1234-5678-abcd-1234567890ab 1Mi RWX Delete Bound default/test-pvc managed-nfs-sc 4m13s
테스트 Pod 생성 및 정상적으로 마운트되는지 확인
test-pod.yaml
kind: Pod
apiVersion: v1
metadata:
name: test-pod
spec:
containers:
- name: test-container
image: busybox:latest
command:
- "/bin/sh"
args:
- "-c"
- "touch /mnt/SUCCESS && exit 0 || exit 1" # SUCCESS 파일 생성 후 종료
volumeMounts:
- name: nfs-volume
mountPath: "/mnt"
restartPolicy: Never
volumes:
- name: nfs-volume
persistentVolumeClaim:
claimName: test-pvc # PVC 이름과 일치해야 함
검증 결과:
[root@nfs-server ~]# ls /data/volumes/default-test-pvc-pvc-abc12345-1234-5678-abcd-1234567890ab/
total 0
-rw-r--r-- 1 root root 0 Jan 15 17:51 SUCCESS # SUCCESS 파일이 존재함으로써 검증 성공
2. StatefulSet + volumeClaimTemplates로 PV 자동 생성
nginx-statefulset.yaml
---
apiVersion: v1
kind: Service
metadata:
name: nginx-headless-service
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None # None 값은 Headless 서비스를 의미
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web-statefulset
spec:
serviceName: "nginx"
replicas: 2 # 2개의 복제본
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx-container
image: nginx:1.14.2
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www-data
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www-data
annotations:
volume.beta.kubernetes.io/storage-class: "managed-nfs-sc" # 생성한 storage-class 이름
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
검증 결과:
클러스터 노드에서:
[root@k8s-master ~]# kubectl delete -f nginx-statefulset.yaml
[root@k8s-master ~]# kubectl get pods -l app=nginx # Pod 상태 확인
NAME READY STATUS RESTARTS AGE
web-0 1/1 Running 0 115m
web-1 1/1 Running 0 114m
[root@k8s-master ~]# kubectl get pvc # PVC 확인
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
test-pvc Bound pvc-abc12345-1234-5678-abcd-1234567890ab 1Mi RWX managed-nfs-sc 19h
www-data-web-0 Bound pvc-1234abcd-5678-1234-abcd-1234567890ab 1Gi RWO managed-nfs-sc 115m
www-data-web-1 Bound pvc-5678efgh-1234-5678-abcd-1234567890ab 1Gi RWO managed-nfs-sc 115m
[root@k8s-master ~]# kubectl get pv # PV 확인
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS AGE
pvc-1234abcd-5678-1234-abcd-1234567890ab 1Gi RWO Delete Bound default/www-data-web-0 managed-nfs-sc 115m
pvc-5678efgh-1234-5678-abcd-1234567890ab 1Gi RWO Delete Bound default/www-data-web-1 managed-nfs-sc 115m
pvc-abc12345-1234-5678-abcd-1234567890ab 1Mi RWX Delete Bound default/test-pvc managed-nfs-sc 19h
NFS 서버에서:
[root@nfs-server ~]# cd /data/volumes/
[root@nfs-server volumes]# ll # 디렉토리 명명 규칙 확인
total 0
drwxrwxrwx 2 root root 21 Jan 15 17:51 default-test-pvc-pvc-abc12345-1234-5678-abcd-1234567890ab
drwxrwxrwx 2 root root 6 Jan 16 11:28 default-www-data-web-0-pvc-1234abcd-5678-1234-abcd-1234567890ab
drwxrwxrwx 2 root root 6 Jan 16 11:28 default-www-data-web-1-pvc-5678efgh-1234-5678-abcd-1234567890ab
[root@nfs-server volumes]# echo "web-00" > default-www-data-web-0-pvc-1234abcd-5678-1234-abcd-1234567890ab/index.html # 각각 index 파일 생성
[root@nfs-server volumes]# echo "web-01" > default-www-data-web-1-pvc-5678efgh-1234-5678-abcd-1234567890ab/index.html
클러스터 임의 노드에서:
[root@k8s-master ~]# kubectl exec -it pod-cm-1 -- /bin/sh # 임의의 Pod에서 nginx-headless 서비스 확인
/ # nslookup nginx-headless-service
nslookup: can't resolve '(null)': Name does not resolve
Name: nginx-headless-service
Address 1: 172.17.136.7 172-17-136-7.nginx-headless-service.default.svc.cluster.local # 두 개의 주소가 있음을 확인
Address 2: 172.17.248.5 172-17-248-5.nginx-headless-service.default.svc.cluster.local
[root@k8s-master ~]# curl 172.17.248.5 # 각각 접속하여 결과 확인
web-00
[root@k8s-master ~]# curl 172.17.136.7
web-01
StatefulSet의 Pod 복제본 수를 변경하면서 PV/PVC 상태 변화를 관찰할 수 있습니다.
StorageClass 재활용 정책이 데이터에 미치는 영향
1. 첫 번째 구성
archiveOnDelete: "false"
reclaimPolicy: Delete # 기본값은 Delete
테스트 결과:
1. Pod 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
2. StorageClass 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
3. PVC 삭제 시 PV 및 NFS 서버 데이터 삭제됨
2. 두 번째 구성
archiveOnDelete: "false"
reclaimPolicy: Retain
테스트 결과:
1. Pod 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
2. StorageClass 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
3. PVC 삭제 시 PV는 삭제되지 않고 Released 상태로 변환되며, NFS 서버 데이터 유지됨
4. 새로운 StorageClass 생성 후 새로운 PVC 생성 시 이전 데이터는 새로운 PV로 복사 가능
3. 세 번째 구성
archiveOnDelete: "true"
reclaimPolicy: Retain
결과:
1. Pod 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
2. StorageClass 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
3. PVC 삭제 시 PV는 삭제되지 않고 Released 상태로 변환되며, NFS 서버 데이터 유지됨
4. 새로운 StorageClass 생성 후 새로운 PVC 생성 시 이전 데이터는 새로운 PV로 복사 가능
4. 네 번째 구성
archiveOnDelete: "true"
reclaimPolicy: Delete
결과:
1. Pod 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
2. StorageClass 삭제 후 재생성 시 데이터 유지, 이전 Pod 이름 및 데이터 유지됨
3. PVC 삭제 시 PV는 삭제되지 않고 Released 상태로 변환되며, NFS 서버 데이터 유지됨
4. 새로운 StorageClass 생성 후 새로운 PVC 생성 시 이전 데이터는 새로운 PV로 복사 가능
요약: 첫 번째 구성 외에는 나머지 구성 모두에서 PV/PVC 삭제 후 데이터 유지됨