[Kubernetes] StatefulSet

스테이트풀셋

공식문서: https://kubernetes.io/ko/docs/concepts/workloads/controllers/statefulset/

스테이트풀셋은 애플리케이션의 스테이트풀을 관리하는데 사용하는 워크로드 API 오브젝트이다.

파드 집합의 디플로이먼트와 스케일링을 관리하며, 파드들의 순서 및 고유성을 보장한다.

디플로이먼트와 유사하게, 스테이트풀셋은 동일한 컨테이너 스펙을 기반으로 둔 파드들을 관리한다. 디플로이먼트와는 다르게, 스테이트풀셋은 각 파드의 독자성을 유지한다. 이 파드들은 동일한 스팩으로 생성되었지만, 서로 교체는 불가능하다. 다시 말해, 각각은 재스케줄링 간에도 지속적으로 유지되는 식별자를 가진다.

스토리지 볼륨을 사용해서 워크로드에 지속성을 제공하려는 경우, 솔루션의 일부로 스테이트풀셋을 사용할 수 있다. 스테이트풀셋의 개별 파드는 장애에 취약하지만, 퍼시스턴트 파드 식별자는 기존 볼륨을 실패한 볼륨을 대체하는 새 파드에 더 쉽게 일치시킬 수 있다.

스테이트풀셋 사용

스테이트풀셋은 다음 중 하나 또는 이상이 필요한 애플리케이션에 유용하다.

  1. 고유한 네트워크 ID:
    • 스테이트풀셋은 각 파드에 고유한 이름을 부여한다. 이 이름은 고정되며 Pod-0, Pod-1, Pod-2처럼 번호가 매겨진다. 이 네이밍 규칙은 파드가 재시작되거나 업데이트되더라도 그대로 유지된다.
    • 예시: myapp-0, myapp-1, myapp-2
  2. 고정된 저장소(Volume):
    • 각 파드는 PV(Persistent Volume)와 연결된다. 이 저장소는 파드가 삭제되고 다시 생성되더라도 데이터를 유지할 수 있게 해준다. 이를 통해 데이터베이스와 같은 상태 저장 애플리케이션을 사용할 수 있다.
    • 예시: myapp-0은 myapp-data-0라는 Persistent Volume을 사용하고, myapp-1은 myapp-data-1을 사용한다.
  3. 배포 및 업데이트 순서:
    • 스테이트풀셋은 파드를 순차적으로 업데이트한다. 파드가 하나씩 업데이트되며, 이전 파드가 정상적으로 업데이트되고 준비된 후에 다음 파드가 업데이트된다. 
  4. Pod 안정성 보장:
    • 스테이트풀셋은 최소한 하나의 파드가 항상 실행되도록 보장한다. 예를 들어, 파드 하나가 실패해도 새로운 파드를 순차적으로 생성하여 시스템이 안정적이고 지속적으로 실행되도록 한다.
  5. 세션 스티키(Session Stickiness):
    • 각 파드는 고유한 네트워크 ID와 저장소를 가지고 있어, 클라이언트는 세션 유지가 필요한 애플리케이션에 접근할 때 특정 파드에 항상 연결될 수 있도록 할 수 있다. 

위의 안정은 파드의 (재)스케줄링 전반에 걸친 지속성과 같은 의미이다. 만약 애플리케이션이 안정적인 식별자 또는 순차적인 배포, 삭제 또는 스케일링이 필요하지 않으면, 스테이트리스 레플리카셋(ReplicaSet)을 제공하는 워크로드 오브젝트를 사용해서 애플리케이션을 배포해야 한다. 디플로이먼트 또는 레플리카셋과 같은 컨트롤러가 스테이트리스 요구에 더 적합할 수 있다.

제한사항

  • 파드에 지정된 스토리지는 관리자에 의해 퍼시스턴트 볼륨 프로비저너 를 기반으로 하는 storage class 를 요청해서 프로비전하거나 사전에 프로비전이 되어야 한다.
  • 스테이트풀셋을 삭제 또는 스케일 다운해도 스테이트풀셋과 연관된 볼륨이 삭제되지 않는다. 이는 일반적으로 스테이트풀셋과 연관된 모든 리소스를 자동으로 제거하는 것보다 더 중요한 데이터의 안전을 보장하기 위함이다.
  • 스테이트풀셋은 현재 파드의 네트워크 신원을 책임지고 있는 헤드리스 서비스 가 필요하다. 사용자가 이 서비스를 생성할 책임이 있다.
  • 스테이트풀셋은 스테이트풀셋의 삭제 시 파드의 종료에 대해 어떠한 보증을 제공하지 않는다. 스테이트풀셋에서는 파드가 순차적이고 정상적으로 종료(graceful termination)되도록 하려면, 삭제 전 스테이트풀셋의 스케일을 0으로 축소할 수 있다.
  • 롤링 업데이트와 기본 파드 매니지먼트 폴리시 (OrderedReady)를 함께 사용시 복구를 위한 수동 개입이 필요한 파손 상태로 빠질 수 있다.

구성 요소

아래의 예시에서는 스테이트풀셋의 구성요소를 보여 준다.

apiVersion: v1
kind: Service
metadata:
  name: nginx
  labels:
    app: nginx
spec:
  ports:
  - port: 80
    name: web
  clusterIP: None
  selector:
    app: nginx
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: web
spec:
  selector:
    matchLabels:
      app: nginx # .spec.template.metadata.labels 와 일치해야 한다
  serviceName: "nginx"
  replicas: 3 # 기본값은 1
  minReadySeconds: 10 # 기본값은 0
  template:
    metadata:
      labels:
        app: nginx # .spec.selector.matchLabels 와 일치해야 한다
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: nginx
        image: registry.k8s.io/nginx-slim:0.8
        ports:
        - containerPort: 80
          name: web
        volumeMounts:
        - name: www
          mountPath: /usr/share/nginx/html
  volumeClaimTemplates:
  - metadata:
      name: www
    spec:
      accessModes: [ "ReadWriteOnce" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 1Gi

위의 예시에서

  • 이름이 nginx라는 헤드리스 서비스는 네트워크 도메인을 컨트롤하는데 사용 한다.
  • 이름이 web인 스테이트풀셋은 3개의 nginx 컨테이너의 레플리카가 고유의 파드에서 구동될 것이라 지시하는 Spec을 갖는다.
  • volumeClaimTemplates은 퍼시스턴트 볼륨 프로비저너에서 프로비전한 퍼시스턴트 볼륨을 사용해서 안정적인 스토리지를 제공한다.

스테이트풀셋 오브젝트의 이름은 유효한 DNS 서브도메인 이름이어야 한다.

스테이트풀셋과 디플로이먼트의 차이점

특성 디플로이먼트(Deployment) 스테이트풀셋(StatefulSet)
파드 이름 일시적이고 동적으로 할당 고유하고 정해진 이름 (<name>-<ordinal>)
저장소 공유된 볼륨 사용 고유한 Persistent Volume 사용
네트워크 ID 고정되지 않음 고유한 네트워크 ID 사용
순차적 업데이트 동시에 여러 파드가 업데이트 순차적으로 업데이트 (Pod-0 -> Pod-1 -> Pod-2)
안정성 보장 파드 수만큼 실행 파드 수만큼 안정적 실행 보장

헤드리스(Headless) 서비스

때때로 로드-밸런싱과 단일 서비스 IP는 필요치 않다. 이 경우, "헤드리스" 서비스라는 것을 만들 수 있는데, 명시적으로 클러스터 IP (.spec.clusterIP)에 "None"을 지정한다.

쿠버네티스의 구현에 묶이지 않고, 헤드리스 서비스를 사용하여 다른 서비스 디스커버리 메커니즘과 인터페이스할 수 있다.

헤드리스 서비스의 경우, 클러스터 IP가 할당되지 않고, kube-proxy가 이러한 서비스를 처리하지 않으며, 플랫폼에 의해 로드 밸런싱 또는 프록시를 하지 않는다. DNS가 자동으로 구성되는 방법은 서비스에 셀렉터가 정의되어 있는지 여부에 달려있다.


헤드리스 서비스에 셀렉터(.spec.selector) 필드를 설정하면 쿠버네티스 API로 확인할 수 있는 엔드포인트(endpoint)가 만들어집니다. 서비스와 연결된 파드를 직접 가리키는 DNA A 레코드도 만들어집니다.

만약 셀렉터가 없는 경우 엔드포인트가 만들어지지 않습니다. 단, 셀렉터가 없더라도 DNS 시스템은 ExternalName 타입의 서비스에서 사용할 CNAME 레코드가 만들어집니다.

예시: Redis 클러스터 StatefulSet 배포

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: redis-sts
spec:
  replicas: 3
  serviceName: redis-svc-headless  # Headless Service 이름
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
        - name: redis
          image: redis:6.2
          ports:
            - containerPort: 6379  # Redis 기본 포트
---
apiVersion: v1
kind: Service
metadata:
  name: redis-svc-headless  # Headless Service 이름
spec:
  clusterIP: None  # Headless Service로 설정
  selector:
    app: redis
  ports:
    - port: 6379
      targetPort: 6379
  • StatefulSet:
    • replicas: 3: 3개의 Redis 인스턴스를 생성한다.
    • serviceName: redis-svc-headless: 이 StatefulSet이 사용할 Headless Service의 이름을 지정한다. 
    • selector: 파드에 할당할 레이블을 지정합니다 (app: redis).
    • 각 파드는 redis 이미지를 사용하여 6379 포트를 통해 Redis 서비스에 접근합니다.
  • Headless Service:
    • clusterIP: None: 서비스의 ClusterIP를 설정하지 않음으로써 Headless Service로 설정합니다. 각 파드에 개별적인 DNS 이름을 부여하고, 서비스의 IP 주소는 각 파드로 분산된다. 
    • ports: 서비스는 6379 포트를 사용하여 파드에 트래픽을 전달한다. 
$ kubectl apply -f redis-sts.yml
$ kubectl get sts,po

NAME                            READY   STATUS    RESTARTS   AGE
statefulset.apps/redis-sts      3/3     Running   0          5m

NAME                                        READY   STATUS    RESTARTS   AGE
pod/redis-sts-0                             1/1     Running   0          5m
pod/redis-sts-1                             1/1     Running   0          4m
pod/redis-sts-2                             1/1     Running   0          3m

redis-sts-0, redis-sts-1, redis-sts-2라는 이름으로 3개의 Redis 파드가 실행 중이다. 

$ kubectl run nettool -it --image ghcr.io/c1t1d0s7/network-multitool --rm --command -- /bin/bash
> host redis-svc-headless
redis-svc-headless.default.svc.cluster.local has address 10.233.75.31
redis-svc-headless.default.svc.cluster.local has address 10.233.94.21
redis-svc-headless.default.svc.cluster.local has address 10.233.89.12

> host redis-sts-0.redis-svc-headless
redis-sts-0.redis-svc-headless.default.svc.cluster.local has address 10.233.75.31

> host redis-sts-1.redis-svc-headless
redis-sts-1.redis-svc-headless.default.svc.cluster.local has address 10.233.94.21

> host redis-sts-2.redis-svc-headless
redis-sts-2.redis-svc-headless.default.svc.cluster.local has address 10.233.89.12
  • redis-svc-headless는 3개의 Redis 파드를 가리키며, 각 파드는 고유한 IP 주소를 가지고 있다.
  • redis-sts-0, redis-sts-1, redis-sts-2는 각각 고유한 DNS 이름과 IP 주소를 가진다.

PV 공유 예시

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: my-mysql-sts-vol
spec:
  replicas: 3
  serviceName: my-mysql-svc-headless
  selector:
    matchLabels:
      app: mysql
  template:
    metadata:
      labels:
        app: mysql
    spec:
      containers:
        - name: mysql
          image: mysql:5.7
          env:
            - name: MYSQL_ROOT_PASSWORD
              value: "rootpassword"
          ports:
            - containerPort: 3306
              protocol: TCP
          volumeMounts:
            - name: mysql-pvc
              mountPath: /var/lib/mysql
  volumeClaimTemplates:
    - metadata:
        name: mysql-pvc
      spec:
        accessModes:
          - ReadWriteOnce
        resources:
          requests:
            storage: 1Gi
        storageClassName: standard
-----
apiVersion: v1
kind: Service
metadata:
  name: my-mysql-svc-headless
spec:
  type: ClusterIP
  clusterIP: None  # <-- Headless Service
  selector:
    app: mysql
  ports:
    - port: 3306
      targetPort: 3306

결과 출력

$ kubectl apply -f .
$ kubectl get sts,po,pv,pvc

NAME                             READY   AGE
statefulset.apps/my-mysql-sts-vol   3/3     25s

NAME                                          READY   STATUS    RESTARTS       AGE
pod/my-mysql-sts-vol-0                           1/1     Running   0              25s
pod/my-mysql-sts-vol-1                           1/1     Running   0              22s
pod/my-mysql-sts-vol-2                           1/1     Running   0              19s

NAME                                                        CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                               STORAGECLASS   REASON   AGE
persistentvolume/pvc-43f39dc8-b423-4412-bf27-1dffa70829a1   1Gi         RWO            Delete           Bound    default/mysql-pvc-my-mysql-sts-vol-1   standard              22s

 

각 MySQL 파드는 고유한 PVC를 가지며, 각 PVC는 NFS 서버에 연결되어 있다. 

StatefulSet 스케일링

kubectl scale sts mysql-sts-vol --replicas=2
kubectl scale sts mysql-sts-vol --replicas=4

replicas 수를 변경하여 MySQL 파드 수를 확장하거나 축소할 수 있다. 

데이터 격리 확인

kubectl exec -it mysql-sts-vol-0 -- sh
# MySQL에 접속 후 데이터를 추가
kubectl exec -it mysql-sts-vol-1 -- sh
# 동일한 데이터를 확인할 수 없습니다

'kubernetes' 카테고리의 다른 글

[Kubernetes] 인증(RBAC)  (0) 2024.12.11
[kubernetes] Pod AutoScaling(HPA)  (0) 2024.12.10
[kubernetes] deployment  (0) 2024.12.10
[Kubernetes] DeamonSet & Job, Cronjob  (0) 2024.12.06
[Kubernetes] Ambassador Pod Design Pattern  (0) 2024.12.05