StatefulSet 에서 로컬 디스크 사용

이 문서는 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.1            443/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-namestatefulset-namereplica-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

이렇게 문제를 해결할 수 있다.

참고: StatefulSet 에서 기존 디스크 사용

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다