이 문서는 StatefulSet 에서 로컬 디스크 사용 에 대한 글이다. Kubernetes 에서 디스크 사용은 어려운감이 있다. PersistentVolume 과 PersistentVolumeClaim 이라는 것을 알아야하고 이를 알고나서도 여러가지 속성들때문에 헷깔리는 경우가 많다.
PersistentVolume 관련해서 문서를 보면 대부분 클라우드가 제공하는 스토리지를 이용하는 예제가 많다. 그것이 아니라면 nfs 를 이용하는 경우가 많아서 나처럼 집에 컴퓨터를 이용하는 경우에 실습해 보기가 쉽지 않은게 사실이다.
StatefulSet 의 경우가 바로 이런 경우였다. StatefulSet 을 연구하기 위해서 여러가지 예제를 찾아봤고 다음과 같은 파일을 찾아냈다.
---
apiVersion: v1
kind: Service
metadata:
name: nginx
namespace: perf-poc
labels:
app: nginx
spec:
ports:
- port: 80
name: web
clusterIP: None
selector:
app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: web
namespace: perf-poc
spec:
selector:
matchLabels:
app: nginx
serviceName: "nginx"
replicas: 2
template:
metadata:
labels:
app: nginx
spec:
terminationGracePeriodSeconds: 10
containers:
- name: nginx
image: k8s.gcr.io/nginx-slim:0.8
ports:
- containerPort: 80
name: web
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
nodeSelector:
kubernetes.io/hostname: knode
volumeClaimTemplates:
- metadata:
name: www
namespace: perf-poc
spec:
accessModes: [ "ReadWriteOnce" ]
resources:
requests:
storage: 1Gi
StatefulSet 은 비상태(stateless) 자원을 생성하는게 아니라 상태를 가지는 자원을 생성하게 해주는 Kubernetes 의 객체다. 대부분 Persistent Data 를 위한 것이기에 PersistentVolume 을 대부분 필요로 한다.
위 예제를 가지고 생성을 해보자.
]$ kubectl apply -f web.yaml service/nginx created statefulset.apps/web created
정상적으로 생성이 됐다고 나온다. 확인을 해보자.
]$ kubectl get po,svc,sts NAME READY STATUS RESTARTS AGE pod/web-0 0/1 Pending 0 2m7s NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE service/kubernetes ClusterIP 10.96.0.1443/TCP 14d service/nginx ClusterIP None 80/TCP 2m7s NAME READY AGE statefulset.apps/web 0/2 2m7s
Pod 생성이 Pending 상태를 보이고 있다. 왜 이럴까? 앞에서 객체 생성을 정의한 yaml 파일을 자세히 보면 Persistent Volume 을 요청하고 있다. volumeClaimTemplates 이 바로 그것이다. 이것은 PersistentVolumeClaim 을 요청하는 것으로 나타난다. 확인해 보자.
]$ kubectl get po,pvc NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim/www-web-0 Pending 5m29s
persistentvolumeclaim 이 Pending 상태이다. 결국에는 이것으로 인해서 Pod 생성도 Pending 이 된 것이다.
해결방안
PersistentVolumeClaim 은 PersistentVolume 이 있어야 한다. 그리고 이것을 묶어줘야하는데 이것을 Bound 라고 한다. 문제는 web.yaml 을 실행했을때에 생성되는 PersistentVolumeClaim 과 PersistentVolume 을 어떻게 연결할 것인가?
좀더 정확하게 말하면 StatefulSet 이 자동으로 생성하는 PersistentVolumeClaim 과 PersistentVolume 을 어떻게 연결할 것인가?
StatefulSet 의 특징은 생성하는 객체의 이름이 임의대로 정해지지 않는다. 정확하게 사람이 인식하기 쉬운 이름으로 결정된다. 앞에서보면 Pod 의 이름이 web-0 이다. 이는 Pod 이름에 Replicas 의 첫번째인 0 을 붙여서 만든다.
StatefulSet 에서 PersistentVolumeClaim 이름은 예측 가능한 패턴을 따른다. volumeclaimtemplates-name–statefulset-name–replica-index. 그래서 생성된 PersistentVolumeClaim 의 이름이 www-web-0 가 된 것이다.
PersistentVolumeClaim 은 PersistentVolume 을 필요로 한다. PersistentVolume 을 생성할때에 claimRef.name 을 PersistentVolumeClaim 이름으로 지정한다. 이렇게 하면 PersistentVolumeClaim 이 생성될때에 PersistentVolume 이 이름과 연결되어 자동으로 Bound 되어진다. 다음과 같이 PersistentVolume 을 만든다.
apiVersion: v1
kind: PersistentVolume
metadata:
name: pv-statefulset-demo-0
namespace: perf-poc
spec:
storageClassName: "example-storageclass"
capacity:
storage: 2Gi
volumeMode: Filesystem
accessModes:
- ReadWriteOnce
claimRef:
namespace: perf-poc
name: www-web-0
persistentVolumeReclaimPolicy: Delete
hostPath:
path: /tmp/k8s-pv-statefulset-demo
type: DirectoryOrCreate
nodeAffinity:
required:
nodeSelectorTerms:
- matchExpressions:
- key: "kubernetes.io/hostname"
operator: "In"
values:
- knode
이것은 Kubernetes Worker 호스트에 파일시스템에 특정 디렉토리를 PersistentVolume 으로 생성한다. 만일 디렉토리가 존재하지 않을 경우에는 생성하도록 한다. 그리고 PersistentVolumeClaim 이 삭제되면 이 볼륨도 함께 삭제되도록 했다.
여기서 중요한 것이 claimRef 필드이다. name 속성에 PersistentVolumeClaim 이름을 지정해준다.
또, nodeAffinity 필드를 이용해서 특정한 Worker 에서 생성되도록 했다.
StatefulSet 에서 replicas 를 2 로 했기 때문에 위 PersistentVolume 생성은 claimRef.name 이 www-web-0, www-web-1 로 두개가 필요하다.
두개의 PersistentVolume 을 생성하고 시간을 갖고 기다리면 다음과 같은 결과를 얻게 된다.
]$ kubectl get po,pv,pvc NAME READY STATUS RESTARTS AGE pod/web-0 1/1 Running 0 21m pod/web-1 1/1 Running 0 33s NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE persistentvolume/pv-statefulset-demo-0 2Gi RWO Delete Bound default/www-web-0 example-storageclass 57s persistentvolume/pv-statefulset-demo-1 2Gi RWO Delete Bound default/www-web-1 example-storageclass 23s NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE persistentvolumeclaim/www-web-0 Bound pv-statefulset-demo-0 2Gi RWO 21m persistentvolumeclaim/www-web-1 Bound pv-statefulset-demo-1 2Gi RWO 33s
Worker 호스트에 PersistentVolume 에서 생성한 디렉토리가 있는지 확인해 보자.
$ ls -lh /tmp/ total 0 drwxr-xr-x 2 root root 6 Aug 8 21:42 k8s-pv-statefulset-demo-0 drwxr-xr-x 2 root root 6 Aug 8 21:44 k8s-pv-statefulset-demo-1
이렇게 문제를 해결할 수 있다.