[Retry, k8s] 07. 워크로드 API - 스테이트풀셋
카테고리: KUBERNETES
태그: kubernetes
[Retry, k8s] 07. 워크로드 API - 스테이트풀셋
🔔 시작하기 전 …
레플리카셋은 파드 복제본을 생성할 때 이름 및 주소만 다를 뿐 나머지는 모두 똑같은 파드를 생성된다.
만약 파드가 PVC를 참조한다면 이 역시 똑같은 PVC를 연결하게 되고 해당 PVC는 특정 하나의 PV에 연결한다. (즉, 항상 똑같은 볼륨에 연결한다는 의미)
-
레플리카셋이 제공하는 파드는 각 별도의 볼륨을 사용할 수 있는 방법을 제공해주지 않아 모두 같은 볼륨으로 같은 상태를 가질수 밖에 없었다.
-
또한 데이터베이스처럼 상태를 갖는(Stateful) 애플리케이션을 쿠버네티스에서 실행하는 것은 매우 복잡한 일이다.
-
파드 내부의 데이터를 어떻게 관리해야 할지, 상태를 갖는 파드에는 어떻게 접근할 수 있을지 등을 꼼꼼히 고려해야 하기 때문이다.
-
쿠보네티스는 이에 대한 해결책을 스테이트풀셋이라는 오브젝트를 제공하고 있다.
-
🔔 스테이트풀셋
스테이트풀셋은 상태를 가지고 있는(statefull) 애플리케이션을 관리하는데 사용하기 위한 리소스
-
Stateful 애플리케이션 : 애플리케이션의 실행 상태나 사용자의 입력 등을 저장하고 유지하는 애플리케이션
-
Stateless 애플리케이션 : 사용자의 요청에 따라 결과를 반환하거나 처리하는데만 집중하는 애플리케이션
📜 스테이트풀셋 특징
【스테이트풀셋은 파드들의 순서와 고유성을 보장함】
- 각 파드는 고유한 이름과 네트워크 식별자를 가지며, 스테이트풀셋에 속한 파드들은 순차적으로 생성 및 삭제된다.
【스테이트풀셋은 안정적인 지속성을 갖는 각각 고유한 스토리지를 제공함】
- 각 파드는 자신에게 할당된 퍼시스턴트 볼륨을 사용하며, 파드가 재생성되어도 데이터를 유지할 수 있다.
【스테이트풀셋은 롤링 업데이트 전략을 통해 파드들을 순차적으로 삭제하고 재생성이 가능함】
- 이때 가장 큰 순서 색인에서부터 작은 순서 색인쪽으로 업데이트가 진행되며, 한 번에 하나씩 업데이트된다.
📜 스테이트풀셋 주의사항
【파드에 사용할 스토리지는 PVC를 통해서만 가능하다.】
- 미리 PV를 생성해놓거나 StorageClass를 사용해 동적 프로비저닝을 사용
【스테이트풀셋을 삭제하거나 파드를 삭제하더라도 볼륨은 삭제되지 않는다.】
-
데이터의 안전을 보장하기 위해서 스테이트풀셋 관리하의 파드가 분실하는 경우, 동일한 이름으로 새롭게 파드가 기동되며 이때 기존 파드가 사용했던 퍼시스턴트 볼륨을 이어서 사용한다.
-
특정 노드가 연결이 끊어졌을 때 스테이트풀셋은 새로운 파드를 기동하지 않는다. 만약 마스터와의 통신이 일시적으로 끊겼지만 파드는 계속해서 돌아가고 있는 경우, 마스터가 대체 파드를 기동하여 퍼시스턴트 볼륨을 마운트하게 되면 오히려 데이터가 파손될 수 있기 때문이다.
-
다음 중 한 가지 경우에만 스테이트풀셋이 분실된 파드를 다른 노드에서 다시 기동한다.
-
장애 노드를 K8s 클러스터의 맴버에서 제외함
-
문제가 있는 파드를 강제 종료함
-
장애로 인해 정지한 노드를 재기동함
-
【헤드리스 서비스 필요】
-
Service는 기본적으로 레이블 셀렉터가 일치하는 랜덤한 파드를 선택해 트래픽을 전달하기 때문에 요청을 분산시키지만, 스테이트풀셋의 각 파드는 고유하게 식별되어야하며, 포드에 접근할 때에도 개별 파드에 접근해야 한다.
-
헤드리스 서비스는 ClusterIP가 없는 서비스를 의미하며, 각 파드에 고유한 DNS 이름을 부여할 수 있어 파드에 직접 접근할 수 있다.
-
헤드리스 서비스는 로드밸런싱이나 단일 서비스 IP가 필요하지 않을 경우에 사용된다.
📜 스테이트풀셋의 파드 이름
# 파드 이름 형식
{StatefulSet-Name}-{Order}
# 예시
mysql-0
mysql-1
📜 스테이트풀셋의 파드 DNS 주소
# 헤드리스 서비스와 스테이트풀셋을 같이 사용한 경우, 파드의 DNS 주소 형식
# Governing-Service-Domain은 statefulset.spec.serviceName에 선언하며 해당 필드에 헤드리스 서비스의 이름을 지정함
{Pod-Name}.{Governing-Service-Domain}.{Namespace}.svc.cluster.local
# 예시
mysql-0.mysql.default.svc.cluster.local
mysql-1.mysql.default.svc.cluster.local
📜 스테이트풀셋 생성
spec.volumeClaimTemplates를 통해 스테이트풀셋으로 생성되는 각 파드에 영구 볼륨 클레임을 설정할 수 있다.
영구 볼륨 클레임을 사용하면 클러스터 외부의 네트워크를 통해 제공되는 영구 볼륨을 파드에 연결할 수 있으므로, 파드를 재기동할 때나 다른 노드로 이동할 때 깥은 데이터를 보유한 상태로 컨테이너가 다시 생성된다.
cat <<EOF > sample-statefullset.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: sample-statefulset
spec:
serviceName: sample-statefulset
replicas: 3
selector:
matchLabels:
app: sample-app
template:
metadata:
labels:
app: sample-app
spec:
containers:
- name: nginx-container
image: nginx:1.16
volumeMounts:
- name: www
mountPath: /usr/share/nginx/html
volumeClaimTemplates:
- metadata:
name: www
spec:
accessModes:
- ReadWriteOncePod
resources:
requests:
storage: 1Gi
storageClassName: csi-fs-sc
EOF
$ kubectl get all,pvc,pv
NAME READY STATUS RESTARTS AGE
pod/sample-statefulset-0 1/1 Running 0 98s
pod/sample-statefulset-1 1/1 Running 0 86s
pod/sample-statefulset-2 1/1 Running 0 73s
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
service/kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 49m
NAME READY AGE
statefulset.apps/sample-statefulset 3/3 99s
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
persistentvolumeclaim/www-sample-statefulset-0 Bound pvc-5633344a-787b-4b07-aed9-5c5f2ee3a766 956Mi RWO csi-fs-sc 98s
persistentvolumeclaim/www-sample-statefulset-1 Bound pvc-e77285ae-be88-4c68-a23a-e4703eed1e22 956Mi RWO csi-fs-sc 86s
persistentvolumeclaim/www-sample-statefulset-2 Bound pvc-5f7917a8-024e-428e-8a42-7e1169b441fc 956Mi RWO csi-fs-sc 73s
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
persistentvolume/pvc-5633344a-787b-4b07-aed9-5c5f2ee3a766 956Mi RWO Delete Bound default/www-sample-statefulset-0 csi-fs-sc 98s
persistentvolume/pvc-5f7917a8-024e-428e-8a42-7e1169b441fc 956Mi RWO Delete Bound default/www-sample-statefulset-2 csi-fs-sc 73s
persistentvolume/pvc-e77285ae-be88-4c68-a23a-e4703eed1e22 956Mi RWO Delete Bound default/www-sample-statefulset-1 csi-fs-sc 86s
📜 스테이트풀셋 스케일링
스테이트풀셋에서 레플리카 수를 변경하여 파드를 생성/삭제하면 레플리케셋이나 데몬셋 등과 달리 하나씩 진행하기 때문에 조금 시간이 걸린다.
-
스케일 아웃일 때는 인덱스가 가장 작은 것부터 파드를 하나씩 생성된다.
-
스케일 인일 때는 인덱스가 가장 큰 파드(가장 최근에 생성된 컨테이너)부터 삭제된다.
-
레플리카셋의 경우 파드가 무작위로 삭제되기 때문에 특정 파드가 마스터가 되는 애플리케이션에는 맞지 않지만, 스테이트풀셋은 0번째 파드가 항상 먼저 생성되고 늦게 삭제되기 때문에 0번째 파드를 마스터 노드로 사용하는 이중화 구조 애플리케이션에 적합하다.
# 레플리카 수를 3에서 4로 변경한 매니페스트를 apply
$ sed -i -e 's|replicas: 3|replicas: 4|' sample-statefullset.yaml
$ kubectl apply -f sample-statefullset.yaml
# kubectl scale을 사용한 스케일링
$ kubectl scale statefulset sample-statefulset --replicas=2
📜 스테이트풀셋 라이프사이클
스테이트풀셋에서도 spec.podManagementPolicy를 Parallel(기본값은 OrderedReady)로 설정하여 레플리카셋 등과 마찬가지로 병령로 동시에 파드릴 기동시킬 수 있다.
cat <<EOF > sample-statefulset-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:
- naem: nginx-container
image: nginx:1.16
EOF
📜 스테이트풀셋 업데이트 전략
-
OnDelete : 스테이트풀셋 매니페스트가 변경되었을 때 파드를 업데이트하지 않고 다른 이유로 파드가 다시 생성될 때 새로운 설정으로 파드가 생성된다.
-
스테이트풀셋에서 OnDelete는 영속성 영역을 가진 데이터베이스나 클러스터 등에서 많이 사용되기 때문에 수동으로 업데이트를 하고 싶은 경우 사용
-
또한 type외에 지정할 수 있는 항목은 없다.
-
cat <<EOF > sample-statefulset-ondelete
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: sample-statefulset-ondelete
spec:
updateStrategy:
type: OnDelete
serviceName: sample-statefulset-ondelete
replicas: 3
selector:
matchLabels:
app: sample-app
template:
metadata:
labels:
app: sample-app
spec:
containers:
- name: nginx-container
image: nginx:1.16
EOF
-
RollingUpdate : 디플로이먼트와 마찬가지로 즉시 파드를 업데이트한다. (스테이트풀셋의 업데이트 전략도 기본값으로 설정됨)
-
그러나 스테이트풀셋에는 영속성 데이터가 있으므로 디플로이먼트와 다르게 추가 파드를 생성해서 롤링 업데이트를 할 수 없고, 또한 동시에 정지 가능한 최대 파드 수(maxUnavailable)를 지정하여 롤링 업데이트를 할 수 없으므로 파드마다 Ready 상태인지를 확인하고 업데이트하게 된다.
-
spec.podManagementPolicy가 Parallel로 설정되어 있는 경우에도 병렬로 동시에 처리되지 않고 파드를 하나씩 업데이트를 한다.
-
스테이트풀셋의 RollingUpdate에서는 partition을 통해 전체 파드 중 어떤 파드까지 업데이트할지를 지정할 수 있는데, 이 설정을 사용하면 전체에 영향을 주지 않고 부분적으로 업데이틀 적용하고 확인할 수 있어 안전한 업데이트가 가능하다.
-
# 0~4 인덱스를 가진 5개의 파드가 생성되고, partition=3(3 이상의 인덱스를 가진 파드가 업데이트 대상)이기 때문에 인덱스 4와 3인 파드가 이 순서대로 하나씩 업데이트된다.
# 이때 0~2 인덱스를 가진 파드는 업데이트되지 않으며, 이후에 partition 값을 3에서 1로 변경하면, 대상 인덱스가 1이상이 되고 업데이트되지 않은 인덱스 2와 1인 파드도 순서대로 업데이트 된다.
cat <<EOF > sample-statefulset-rollingupdate.yaml
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: sample-statefulset-rollingupdate
spec:
updateStrategy:
type: RollingUpdate
rollingUpdate:
partition: 3
serviceName: sample-statefulset-rollingupdate
replicas: 5
selector:
matchLabels:
app: sample-app
template:
metadata:
labels:
app: sample-app
spec:
containers:
- name: nginx-container
image: nginx:1.16
EOF
📜 영구 볼륨 데이터 확인
# 약 1GB 영구 볼륨이 마운트되어 있는 것을 확인
$ kubectl exec -it sample-statefulset-0 -- df -h
Filesystem Size Used Avail Use% Mounted on
overlay 9.8G 6.1G 3.2G 66% /
tmpfs 64M 0 64M 0% /dev
/dev/mapper/ubuntu--vg-ubuntu--lv 9.8G 6.1G 3.2G 66% /etc/hosts
shm 64M 0 64M 0% /dev/shm
192.168.219.51:6789:/volumes/csi/csi-vol-d89ca152-6f76-47cb-a27b-34d6f6bf03a4/9099aa93-61d6-448c-96e4-d83cd530301f 956M 0 956M 0% /usr/share/nginx/html
tmpfs 1.8G 12K 1.8G 1% /run/secrets/kubernetes.io/serviceaccount
tmpfs 971M 0 971M 0% /proc/acpi
tmpfs 971M 0 971M 0% /proc/scsi
tmpfs 971M 0 971M 0% /sys/firmware
# 영구 볼륨에 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 code 2
# 영구 볼륨에 sample.html 생성
$ kubectl exec -it 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
파드가 삭제되거나 컨테이너 내부 에러로 컨테이너가 정지된 경우 등의 상황에서도 복구 후에 파일이 유실되지 않는다.
# 예상치 못한 파드 정지 1(파드 삭제)
$ kubectl delete pod sample-statefulset-0
# 예상치 못한 파드 정지 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
📜 스테이트풀셋 삭제와 영구 볼륨 삭제
-
영구 볼륨은 스테이트풀셋이 삭제되어도 동시에 해제되지 않는데, 스테이트풀셋이 영구 볼륨을 해제하기 전에 볼륨에서 데이터를 백업할 수 있도록 시간을 주기 때문이다.
-
스테이트풀셋을 삭제하고 스테이트풀셋이 PVC으로 확보한 PV을 해제하지 않고 다시 스테이트풀셋을 생성한 경우 PV 데이터는 그대로 파드가 기동된다. (스케일 인으로 레플리카 수를 줄일 경우도 마찬가지)
$ kubectl delete statefulset sample-statefulset
statefulset.apps "sample-statefulset" deleted
$ kubectl get pvc
NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE
www-sample-statefulset-0 Bound pvc-623efc04-b12f-4df4-85c6-7750b5b3c9c1 956Mi RWO csi-fs-sc 16m
www-sample-statefulset-1 Bound pvc-5911ed41-87e0-45a1-88e9-2c12c627ad7e 956Mi RWO csi-fs-sc 15m
www-sample-statefulset-2 Bound pvc-1ee2d2d9-04e0-4289-9d7a-5cdccfb75e9d 956Mi RWO csi-fs-sc 15m
www-sample-statefulset-3 Bound pvc-7293cc9f-00b7-4eca-afa4-061cfc2219f4 956Mi RWO csi-fs-sc 15m
$ kubectl get pv
NAME CAPACITY ACCESS MODES RECLAIM POLICY STATUS CLAIM STORAGECLASS REASON AGE
pvc-1ee2d2d9-04e0-4289-9d7a-5cdccfb75e9d 956Mi RWO Delete Bound default/www-sample-statefulset-2 csi-fs-sc 16m
pvc-5911ed41-87e0-45a1-88e9-2c12c627ad7e 956Mi RWO Delete Bound default/www-sample-statefulset-1 csi-fs-sc 16m
pvc-623efc04-b12f-4df4-85c6-7750b5b3c9c1 956Mi RWO Delete Bound default/www-sample-statefulset-0 csi-fs-sc 16m
pvc-7293cc9f-00b7-4eca-afa4-061cfc2219f4 956Mi RWO Delete Bound default/www-sample-statefulset-3 csi-fs-sc 16m
$ kubectl apply -f sample-statefullset.yaml
statefulset.apps/sample-statefulset created
$ kubectl exec -it sample-statefulset-0 -- ls /usr/share/nginx/html/sample.html
/usr/share/nginx/html/sample.html
# 스테이트풀셋이 확보한 PV 해제
$ kubectl delete pvc www-sample-statefulset-{0..3}
persistentvolumeclaim "www-sample-statefulset-0" deleted
persistentvolumeclaim "www-sample-statefulset-1" deleted
persistentvolumeclaim "www-sample-statefulset-2" deleted
persistentvolumeclaim "www-sample-statefulset-3" deleted
댓글 남기기