Kubernetes

워크로드 API 카테고리(스테이트풀셋)

jih0ssang 2024. 1. 13. 14:56

참고 사이트: https://yeoulcoding.me/239

워크로드 API 카테고리

컨테이너 실행에 관련된 리소스

- 파드

- 레플리케이션 컨트롤러

- 레플리카셋

- 디플로이먼트

- 데몬셋

- 스테이트풀셋

- 잡

- 크론잡

 

스테이트풀셋

스테이트풀셋은 레플리카셋의 특수한 형태라고 할 수 있는 리소스이다.

데이터베이스 등과 같은 스테이트풀(stateful)한 워크로드에 사용하기 위한 리소스이다.

 

레플리카셋과의 차이점

  • 생성된 파드명의 접미사는 인덱스가 부여된 것임
    • sample-statefulset-0, sample-statefulset-1, ... (파드명 바뀌지 않음)
  • 데이터를 영구적으로 저장하기 위한 구조로 되어있음

 

Stateful vs Stateless

  • stateless한 서비스 (웹, 앱)
  • stateful한 서비스 (DB, network)

상태는 별도의 볼륨에 저장한다.
레플리카셋은 파드를 아무리 늘려도 같은 PVC를 바라본다.
모든 파드는 동일한 정보(템플릿)을 가지기 때문이다.
PVC가 같으면 PV가 같고, PV가 같으면 같은 스토리지를 바라보고 있으므로
별도의 고유 상태를 가지고 있는 것이 아니다.

스테이트풀셋을 통해 복제된 파드는 각각 고유한 상태를 가져야  한다.
파드가 별도의 스토리지를 각각 가져야 한다.
스토리지와 파드는 고유성을 가지고 있으므로 서로 교체할 수 없다.

Deployment(ReplicaSet): 동일하고 교체 가능한 서버
StatefulSet: 개별 관리가 필요하고 대체 불가능한 서버

 

스테이트풀셋 생성

스테이트풀셋에서는 spec.volumeClaimTemplates를 지정할 수 있다.

 

이 설정은 스테이트풀셋으로 생성되는 각 파드에 영구 볼륨 클레임(영구 볼륨 요청)을 설정할 수 있다.

영구 볼륨 클레임을 사용하면 클러스터 외부의 네트워크를 통해 제공되는 영구 볼륨을 파드에 연결할 수 잇으므로,

파드를 재기동할 때나 다른 노드로 이동할 때 같은 데이터를 보유한 상태로 컨테이너가 다시 생성된다.

영구 볼륨은 하나의 파드가 소유할 수도 있고 영구 볼륨 종류에 따라 여러 파드에서 공유할 수도 있다.

 

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sample-statefulset
spec:
  serviceName: sample-statefulset
  replicas: 3
  selector: 
    matchLabels:
      apps: value
  template:
    metadata:
      labels:
        apps: value
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.16
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes:
      - ReadWriteOnce
      resources:
        requests:
          storage: 1G

 

스테이트풀셋 생성

# 스테이트풀셋 생성
$ kubectl apply -f sample-statefulset.yaml
statefulset.apps/sample-statefulset created

 

스테이트풀셋 확인

# 스테이트풀셋 확인
$ kubectl get statefulsets
NAME		   READY   	AGE
sample-statefulset  3/3		91s
# 파드 목록 표시
$ kubectl get pods -o wide
NAME		      READY  STATUS  RESTARTS  AGE     IP      NODE
sample-statefulset-1  1/1   Running     0      92s  10.0.2.36  gke-k8s-default-abc
sample-statefulset-2  1/1   Running     0      66s  10.0.1.33  gke-k8s-default-def
sample-statefulset-3  1/1   Running     0      48s  10.0.0.25  gke-k8s-default-lmn

파드를 확인해보면 스테이트풀셋으로 생성된 파드명에 인덱스가 접미사로 부여된 것을 확인할 수 있다.

 

스테이트풀셋 스케일링

스케일링 방법에는

  • kubectl apply -f 명령어 사용
  • kubectl scale 명령어 사용

두 가지 방법이 있다.

# 레플리카 수를 3에서 4로 변경한 manifest를 apply
$ sed -i -e 's|replicas: 3|replicas: 4|' sample-stateful-set.yaml
$ kubectl apply -f sample-statefulset.yaml
statefulset.apps/sample-statefulset configured

# kubectl scale을 사용한 스케일링
$ kubectl scale statefulset sample-statefulset --replicas=5
statefulset.apps/sample-statefulset scaled

 

스테이트풀셋에서 레플리카 수를 변경하여 파드를 생성하고 삭제하면

레플리카셋이나 데몬셋 등과 달리, 기본적으로 파드를 동시에 하나씩만 생성하고 삭제하기 때문에 시간이 걸린다.

 

스케일 인일 때는 인덱스가 가장 큰 것부터 파드를 하나씩 삭제한다.

 

반대로, 스케일 아웃일 때는 인덱스가 가장 작은 것부터 파드를 하나씩 생성하고

이전에 생성된 파드가 Ready 상태가 되고 나서야 다음 파드를 생성하기 시작한다.

따라서, sample-statefulset-0, sample-statefulset-1, sample-statefulset-2 순서로 생성된다.

 

스테이트풀셋의 라이프사이클

레플리카셋 등과 다르게 여러 파드가 동시에 기동시키고자 한다면

spec.podManagementPolicy를 Parallel로 설정하면 병렬로 동시에 파드를 기동시킬 수 있다.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sample-statefulset-parallel
spec:
  podManagementPolicy: Parallel
  serviceName: sample-statefulset-parallel
  replicas: 3
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.16

 

스테이트풀셋 업데이트 전략

데몬셋과 마찬가지로 업데이트 전략에는

  • OnDelete
  • RollingUpdate

두 가지가 있다.

 

OnDelete

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sample-statefulset-rollingupdate
spec:
  updateStrategy:
    type: OnDelete
  serviceName: sample-statefulset-ondelete
  replicas: 3
  selector:
    matchLaels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.16

manifest를 수정하여 이미지 등을 변경했더라도 기존 파드는 업데이트 되지 않는다.

스테이트풀셋은 영속성 영역을 가진 데이터베이스나 클러스터에서 많이 사용되기 때문에

수동으로 업데이트하고 싶은 경우, OnDelete를 사용한다.

type 이외의 지정할 수 있는 항목은 없다.

 

RollingUpdate

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: sample-statefulset-rollingupdate
spec:
  updateStrategy:
    type: RollingUpdate
    rollingUpdate:
      partition:  3
  serviceName: sample-statefulset-rollingupdate
  replicas: 4
  selector:
    matchLabels:
      app: sample-app
  template:
    metadata:
      labels:
        app: sample-app
    spec:
      containers:
      - name: nginx-container
        image: nginx:1.16

스테이트풀셋에는 영속성 데이터가 있으므로

디플로이먼트와 다르게, 추가 파드를 생성해서 롤링 업데이트를 할 수 없다.

또한 동시에 정지 가능한 최대 파드 수(maxUnavailable)를 지정할 수 없으므로 파드마다 Ready 상태인지 확인하고

업데이트 해야한다.

spec.podManagementPolicy가 Parallel로 설정되어있어도 병렬로 동시에 처리되지 않고 파드 하나씩 업데이트가 이루어진다.

 

rollingUpdate.partition 옵션은 전체 파드 중 어떤 파드까지 업데이트할지를 지정할 수 있다.

전체에 영향을 주지 않고 부분적으로 업데이트를 적용하고 확인할 수 있어 안전한 업데이트를 할 수 있다.

또한 OnDelete와 달리, 수동으로 재기동한 경우에도 partition 값보다 작은 인덱스를 가진 파드는 업데이트 되지 않는다.

# 파드 목록 표시
$ kubectl get pods --watch
NAME					READY	STATUS      RESTARTS  AGE
sample-statefulset-rollingupdate-0	1/1	Running       0       6s
sample-statefulset-rollingupdate-1	1/1     Running       0       5s
sample-statefulset-rollingupdate-2	1/1     Running       0       3s
sample-statefulset-rollingupdate-3	1/1     Running       0       2s
sample-statefulset-rollingupdate-3	0/1     Terminating   0       2s


# 이미지 수정
sample-statefulset-rollingupdate-3      1/1     Running       0       3s
sample-statefulset-rollingupdate-2      0/1     Terminating   0       28s
sample-statefulset-rollingupdate-2      1/1     Running       0       1s

# 2 이상만 순서대로 업데이트
sample-statefulset-rollingupdate-2      0/1     Terminating   0      3m42s

# partition: 3 → 1
sample-statefulset-rollingupdate-2      1/1     Running       0      2s
sample-statefulset-rollingupdate-1      0/1     Terminating   0      3m47s
sample-statefulset-rollingupdate-1      1/1     Running       0      3s

# 1 이상만 순서대로 업데이트

스테이트풀셋을 기동하니 0~3 인덱스를 가진 네 개의 파드가 생성된다.

이 상태에서 스테이트풀셋 이미지를 수정하면 partition=2 (2 이상의 인덱스를 가진 파드가 업데이트 대상)이면

인덱스 3, 2가 순서대로 하나씩 업데이트 된다. 이때 0,1 인덱스를 가진 파드는 업데이트 되지 않는다.

 

영구 볼륨 데이터 저장 확인

# 약 1GB 영구 볼륨이 마운트되어 있는 것을 확인 (/dev/sdb > /usr/share/nginx/html>
$ kubectl exec -it sample-statefulset-0 -- df -h | grep /dev/sd
/dev/sda1  95G  2.7G  92G   3%  /etc/hosts
/dev/sdb  976M  2.6M  958M  1%  /usr/ahre/nginx/html

먼저 컨테이너 내부에 영구 볼륨이 마운트되어 있는지 확인한다. 영구 볼륨을 사용하고 있을 경우, /dev/sdb/ 등의 별도 디스크(PV)가 마운트되어 있다.

# 영구 볼륨에 sample.html이 없는지 확인
$ kubectl exec -it sample-statefulset-0 -- ls /usr/share/nginx/html/sample.html
ls: cannot access '/usr/share/nginx/html/sample.html': No such file or directory
command terminated with exit code2

# 영구 볼륨에 sample.html 생성
$ kubectl exec -it sample sample-statefulset-0 -- touch /usr/share/nginx/html/sample.html

# 영구 볼륨에 sample.html 팡리이 있는지 확인
$ kubectl exec -it sample-statefulset-0 -- ls /usr/share/nginx/html/sample.html
/usr/share/nginx/html/sample.html

마운트된 영구 볼륨에 샘플 파일(sample.html)을 생성한다

 

# 예상치 못한 파드 정지 1(파드 삭제)
$ kubectl delete pod sample-statefulset-0
pod "sample-statefulset-0" delete

# 예상치 못한 파드 정지 2(nginx 프로세스 정지)
$ kubectl exec -it sample-statefulset-0 -- /bin/bash -c 'kill 1'

# 파드 정지, 복구 이후에도 파일 유실 없음
$ kubectl exec -it sample-statefulset-0 -- ls /usr/share/nginx/html/sample.html
/usr/share/nginx/html/sample.html

kubectl을 사용하여 파드를 삭제하거나 컨테이너 내부 에러로 컨테이너가 정지된 경우 등의 상황에서도

복구 후에 파일이 유실되지 않는다.

# 파드 목록 표시
$ kubectl get pods -o wide
NAME	             READY  STATUS  RESTARTS AGE    IP         NODE
sample-statefulset-0  1/1   Running   0      25s    10.0.2.55  gke-k8s-default-abc
sample-statefulset-1  1/1   Running   0      4m21s  10.0.1.52  gke-k8s-default-def
sample-statefulset-2  1/1   Running   0      4m3s   10.0.0.32  gke-k8s-default-ghi

복구 후 스테이트풀셋을 확인해보면 IP 주소는 변경되었지만 파드는 그대로인 것을 알 수 있다.

 

스테이트풀셋 삭제와 영구 볼륨 삭제

# 스테이트풀셋 삭제
$ kubectl delete statefulset sample-statefulset
statefulset.apps "sample-statefulset" deleted

# 스테이트풀셋에 연결되는 영구 볼륨 클레임(영구 볼륨 요청) 확인(출력 일부 수정)
$ kubectl get persistentvolumeclaims
NAME                      STATUS VOLUME    CAPACITY ACCESS MODES STORAGECLASS AGE
www-sample-statefulset-0  Bound  pvc-c554  1Gi	    RWO           standard    74m
www-sample-statefulset-1  Bound  pvc-2bbb  1Gi 	    RWO           standard    74m
www-sample-statefulset-2  Bound  pvc-7fbe  1Gi 	    RWO           standard    74m


# 스테이트풀셋에 연결되는 영구 볼륨 확인
$ kubectl get persistentvolumes 
NAME      CAPACITY   RECLAIM POLICY  CLAIM                             AGE
pvc-2bbb  1Gi        Delete          default-www-sample-statefulset-1  76m
pvc-7fbe  1Gi        Delete          default-www-sample-statefulset-2  76m
pvc-554c  1Gi        Delete          default-www-sample-statefulset-3  76m

# 다시 스테이트풀셋 생성
$ kubectl apply -f sample-statefulset.yaml
statefulset.apps/sample-statefulset created

스테이트풀셋을 생성하면 파드에 영구 볼륨 클레임을 설정할 수 있어 용구 볼륨도 동시에 확보할 수 있다.

이렇게 확보한 영구 볼륨은 스테이트풀셋이 삭제되어도 동시에 해제되지 않는다.

스테이트풀셋이 영구 볼륨을 해제하기 전에 볼륨에서 데이터를 백업하기 때문이다.

영구 볼륨을 해제하지 않고 다시 스테이트풀셋을 생성한 경우, 영구 볼륨 데이터는 그대로 파드가 기동된다.

이것이 스케일 인하여 레플리카 수를 줄인 경우도 마찬가지이다.

 

# 스테이트풀셋을 한 번 삭제한 후에도 영구 볼륨에 sample.html이 있는지 확인
$ kubectl exec -it sample-statefulset-0 -- ls /usr/share/nginx/html/sample.html
/usr/shared/nginx/html/sample.html

스테이트풀셋을 삭제해도 영구 볼륨이 확보된 상태일 경우 비용이 발생한다.

# 스테이트풀셋이 확보한 영구볼륨 해제
$ kubectl delete persistentvolumeclaims www-sample-statefulset-{0..2}
persistentvolumeClaim "www-sample-statefulset-0" deleted
persistentvolumeClaim "www-sample-statefulset-1" deleted
persistentvolumeClaim "www-sample-statefulset-2" deleted

영구 볼륨 클레임을 삭제해도 영구 볼륨은 삭제되지 않는 경우가 있다. 

이것은 영구 볼륨의 ClaimPolicy 설정 때문이다.