[Kubernetes in action] chapter 10. statefulset

6 minute read

서론

  • 클러스터된 스테이트풀 애플리케이션 배포
  • 파드 레플리카 인스턴스에 별도의 스토리지 제공
  • 파드 레플리카에 안정적인 이름과 호스트 이름 보장
  • 예측 가능한 순서대로 파드 레플리카의 시작과 중지
  • DNS 서비스 레코드를 통한 피어 디스커버리

스테이트풀 파드 복제

레플리카셋은?

레플리카셋은 이름과 ip 주소를 제외하고 모두 똑같은 파드를 만듬. (똑같은 파드 == 내용물이 똑같음)

그리고 모두 똑같은 볼륨을 사용함 => 다른 퍼시스턴트볼륨클레임은 사용 불가

replicaSet

왜 볼륨을 각자 쓰고 싶을까? 저의 생각이지만 각 인스턴스가 각자 정보를 저장하고 싶다. 단순히 로그같은 것만 생각해도 서버마다 정보가 너무 다름. 프로메테우스 또한 로컬에 데이터를 저장하게 되는데 그것에 대해서 독립성을 제공

레플리카에 개별 스토리지 붙이기 (스토리지에 대한 안정적인 제공에 대한 고민)

  • 수동으로 하나하나 pod를 생성
    • 나중에 너무 귀찮음. 죽으면 계속 살려야되는데 말이 안됨.
  • 파드 인스턴스별로 하나의 레플리카셋 사용
    • 단일 레플리카를 사용하는 것에 비해 너무 불편함.
    • 하나 배포한다고 해도 각 레플리카에 대해서 배포를 직접 해야됨
    • 스케일도 귀찮음

eachpod

  • 동일 볼륨을 여러 개 디렉토리로 분할 사용
    • 하나의 퍼시스턴스 볼륨 + 각 파트들이 다른 디렉토리 공간 지정해서 사용
    • 인스턴스가 실행되기 전에는 어차피 모두 같이 설정을 가지고 있기 때문에 하지 못함.
    • 인스턴스가 생성된 이후에 사용하지 않는 디렉토리를 선택할 수는 있도록 가능.
    • 하지만 잘 동작하는 것에 대한 보장이 안되고 볼륨 병목 현상 발생

eachPodSameVolume

각 파드에 안정적인 아이덴티티 제공

새로운 파드를 교체해야되는 경우 (죽던지 혹은 배포를 했던지) 레플리카셋같은 새로운 호스트 네임과 IP를 제공한다.

특정 애플리케이션에서 이전 인스턴스 데이터를 가지고 시작할 때 네트워크 아이덴티티로 인한 문제가 발생한다.

간단한 예시로 클러스터 멤버마다 설정파일을 제공해주는 경우가 있는데 -> (?)

Pod이 종료되어 다시 올라오는 경우가 있을 때 지정된 ip가 바뀌어서 새로 셋업을 해야된다.

각 파드 인스턴스별 전용 서비스 사용

각 Pod마다 서비스를 제공해보자!

  • 서비스같은 경우 변경되지 않는 ip를 제공
  • 파드별로 서비스를 가지고 있으면 새로운 파드가 만들어 진다고해도 문제가 되지 않지 않을까?
  • 할수는 있겠지만 각 파드가 어떤 서비스에 매핑되는지 알기 어렵기 때문에 그 IP를 사용해 다른 파드에 자신을 등록할 수 없다. (?)

eachService

스테이트풀셋

스테이트풀셋과 레플리카셋 비교

레플리카셋 (가축)

  • 파드끼리 내부 구성이 거의 동일함
  • 이전 파드와 새로운 파드가 식별만 다르지 동작은 그래도.
  • 대부분 stateless 언제든지 완전히 새로운 파드로 교체가능.

스테이트풀셋 (애완동물)

  • 새로운 파드 인스턴스에 대해서 이전 파드와 동일한 이름, 네트워크 아이덴티티, 상태까지 모두 동일
  • 레플리카를 줄인다고 해서 스토리지가 없어지지 않고 계속 남아있다가 레플리카를 다시 늘렸을 때 같이 매핑됨. -> 이전 상태에 대해서 계속 보관
  • 완전히 무작위하게 생성되는게 아니고 규칙을 가지고 있음.

안정적인 네트워크 아이덴티티 제공

  • 인덱스를 이용해서 파드의 이름과 호스트 이름, 스토리지 이름을 붙이는데 사용 (ex. hoonPod-0, hoonPod-1)

comparingReplicaAndStateful

거버닝 서비스

  • 인스턴스에 대해서 동일한 이름을 제공하지만 호스트 아름를 통해서 접근을 해야되는 경우가 있음.
  • 그 뿐만 아니라 스테이트리스 인스턴스들은 모두 상태가 없기 때문에 그 중에 아무나 선택하면 됨.
  • 하지만 스테이트풀 파드들은 각자 상태가 다르기 때문에 특별하게 인식할 필요가 있다.

So, 그래서 거버닝 헤드리스 서비스를 생성해서 호스트에 대한 네트워크 아이덴티티를 제공

  • 각 파드는 자체 DNS 엔트리를 가짐.
  • 클러스터 내/외부에 있는 클라이언트는 특정 파드의 주소를 지정할 수 있음.

  • 실제 예시 ``` namespace: default service name (거버닝 서비스): foo pod name: a-0

FQDN: a-0.foo.default.svc.cluster.local


- `foo.default.svc.cluster.local` 라는 도메인의 SRV레코드를 조회해서 스테이트풀셋의 파드 이름을 찾을 수도 있음.


- 특정 노드에 문제가 발생하는 경우
![comparing_2](https://drek4537l1klr.cloudfront.net/luksa/Figures/10fig06_alt.jpg)

### 스테이트풀셋 스케일링
만약 2개의 레플리카 파드가 있다가 과정했을 때 새로운 파드에 이름에는 다음 인덱스인 2가 붙여서 생성될 것이다. 

ex) a-0, a-1, a-2 (New)

또한 삭제될 때는 그 순서에 반대대로 삭제된다.

ex) a-0, a-1, a-2 (will be removed), a-3 (is removed)

스케일 다운시 좋은 점은 어떤 파드가 제거될지 알 수 있다는 점이다. <-> 레플리카셋: 어떤 것이 먼저, 그리고 지정해서 스케일 다운하지 못함.

![scailing](https://drek4537l1klr.cloudfront.net/luksa/Figures/10fig07_alt.jpg)

> 특정 스테이트풀 애플리케이션 같은 경우 빠르게 스케일 다운이 처리하기 어려워 한 시점의 하나의 파드 인스턴스만 스케일 다운함

예) 분산 데이터 스토어 -> 사실 내용을 이해하지 못함...

## 각 스테이트풀 인트선스에 안정적인 스토리지 제공
**안정적인 아이덴티티를 갖도록 보장**

이전 파드가 사라지고 새로 만들어진 파드에 대해서도 이전 스토리지을 똑같이 할당해주어야 함.

> 그러기 위해서 스테이트풀셋은?

스토리지는 영구적이어야하지만 파드와는 분리되어야한다.
=> Pod에 내부에 있다면 분명히 Pod가 스케줄링 되는 단계에서 데이터가 손실될 가능성이 있음.

#### 볼륨 클레임 템플릿

팟을 만들 때에 대한 템플릿을 만드는 것과 비슷하게 볼륨 클레임을 템플릿 통해서 만듬.

맨 처음 이야기했던 내용처럼 각 Pod 별로 별도로 볼륨이 있어야 한다.

6장에서 배운 것처럼 퍼시스턴트볼륨클레임을 이용해서 볼륨과는 1:1 매핑이 진행되고 

파드와 퍼시스턴스볼륨 클레임을 1:1 매핑해서 각자 스토리지를 가지도록 한다.

``` text
pod 1 : 1 pvc 
pvc 1 : 1 pv

pod -> pvc -> pv

볼륨클레임템플릿

이 가정에는 관리자가 프로비저닝한다거나 혹은 동적 프로비저닝이 지원한다고 가정

새로운파드볼륨

  • 레플리카를 줄여서 스케일 다운을 하게 되는 경우 Pod만 삭제된다. PVC는 남는다.
  • 다시 스케일 업하게 되는 경우 Pod이 기존에 있던 PVC를 이용해서 볼륨을 사용

주의: 스케일 다운을 하고 남은 볼륨이 중요한 데이터라면 직접 관리하는 것이 중요하다. (퍼시스턴트볼륨클레임을 직접 삭제) PVC를 그냥 삭제하게 된다면 정책에 따라서 PV이 삭제되거나 재활용될 것이다. 재활용이 된다고 했을 때 다시 스케일 업을 하게 되는 경우 데이터가 손상될 수 있다.

스테이트풀셋 사용

format

apiVersion: apps/v1beta1
kind: StatefulSet
metadata:
  name: kubia
spec:
  serviceName: kubia
  replicas: 2
  template:
    metadata:
      labels:
        app: kubia
    spec:
      containers:
      - name: kubia
        image: luksa/kubia-pet
        ports:
        - name: http
          containerPort: 8080
        volumeMounts:
        - name: data
          mountPath: /var/data
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      resources:
        requests:
          storage: 1Mi
      accessModes:
      - ReadWriteOnce

deployment와 다르게 sts는 파드가 모두 완벽하게 뜬 이후에 다음 파드를 만든다.

여러 개를 동시에 만들다가 race condition을 발생할 가능성이 있기 때문이다.

실행 이후 모습

  • Pod ``` yaml $ kubectl get po kubia-0 -o yaml

apiVersion: v1 kind: Pod metadata: … spec: containers:

  • image: luksa/kubia-pet … volumeMounts:
    • mountPath: /var/data name: data … … volumes:
  • name: data persistentVolumeClaim: claimName: data-kubia-0 … ```

  • PVC
    $ kubectl get pvc
    NAME           STATUS    VOLUME    CAPACITY   ACCESSMODES   AGE
    data-kubia-0   Bound     pv-c      0                        37s
    data-kubia-1   Bound     pv-a      0                        37s
    

스테이트풀셋의 피어 디스커버리

클러스터된 애플리케이션의 중요한 요구 사항으로 피어 디스커버리 (클러스터된 다른 멤버를 찾는 기능)이라고 합니다.

쿠버네티스 API 서버에 요청을 해서 알아낼 수 있지만 그것은 애플리케이션에 독립적이지 못하기 때문에 좋지 않은 방법이다.

우리는 DNS에 SRV 레코드를 통해서 기능을 제공할 것이다.

헤드레스트 서비스

apiVersion: v1
kind: Service
metadata:
  name: kubia
spec:
  clusterIP: None
  selector:
    app: kubia
  ports:
  - name: http
    port: 80

SRV 레코드

  • 특정 서비스를 제공하는 서버의 호스트 이름과 포트를 가리키는데 사용.
  • 쿠버네티스는 헤드리스 서비스를 뒷밤침하는 파드의 호스트 이름을 가리키도록 SRV 레코드를 생성.

  • dig (DNS lookup 도구)을 이용한 테스트 ``` bash dig SRV kubia.default.svc.cluster.local

… ;; ANSWER SECTION: // <- 헤드리스트 서비스를 뒷받침하는 파드의 SRV 레코드 k.d.s.c.l. 30 IN SRV 10 33 0 kubia-0.kubia.default.svc.cluster.local. k.d.s.c.l. 30 IN SRV 10 33 0 kubia-1.kubia.default.svc.cluster.local.

;; ADDITIONAL SECTION: // <- A 레코드 kubia-0.kubia.default.svc.cluster.local. 30 IN A 172.17.0.4 kubia-1.kubia.default.svc.cluster.local. 30 IN A 172.17.0.6 …


위의 모습처럼 파드가 스테이트풀셋의 모든 파드 목록을 가져오려면 SRV DNS 룩업을 수행하기만 하면 된다.

## DNS를 통한 피어 디스커버리
쿠버네티스 서버는 모든 파드들에 대한 정보를 가지고 있다. 

하지만 클라이언트는 모른다. 알기 위해서 서버에 요청을 해야되는데 너무 번거롭고 시간도 오래 걸림.

그런 상황에서 스테이트풀셋과 SRV 레코드를 사용하면 편하게 해결 가능하다.

![dnsLookUp](https://drek4537l1klr.cloudfront.net/luksa/Figures/10fig12_alt.jpg)

## 클러스터된 데이터 저장소 사용
전략에 따라 다를 수 있겠지만 우리는 스케일 다운해서 새로운 올린 파드는 이전 파드에 연결되어진 스토리지가 있다.

그러기 때문에 이전에 있던 파일정도 스토리지에 저장된 결과는 달라지지 않는다. 그리고 바로 반환 가능하다.


# 스테이트풀셋 노드 실패를 처리하는 과정

## 스테이트풀셋의 최대 하나 (p.448)

최대 하나의 의미: 스테이트풀셋은 동일한 아이덴티티를 가지는 파드를 가지는 것을 절대적으로 막는다

예) 쿠버네티스가 파드 상태를 확신할 수 없을때? 스테이트풀셋이 교체 파드를 생성하게 되면 동일한 아이텐티티를 가진 파드가 생긴고 동일한 볼륨에 데이터를 저장할 것이다.

그런 것을 막겠다! 두 개의 아이텐디디로 실행되지 않도록 동일한 PVC에 바인딩 되지 않도록 보장 -> 절대적인 확신이 있어야지만 교체한다.

## 상황

> 가정: 노드에 네트워크가 끊어져서 쿠버네티스가 해당 노드 내부의 정보를 얻지 못할 경우

- 해당 `Node` status -> NotReady
- 해당 `Node` 안에 `Pod` status -> Unknown

### Unknown 상태에 Pod은?
- 노드에 다시 연결이 되면 Running이 되겠지만 특정 시간(설정 가능)이 지나게 되면 노드에서 자동으로 삭제됨.
- `Kubelet`이 deletion을 확인하면 파드를 종료시킴
  - But, 현재 상황에서는 `Kubelet`에게 명령을 못내림.
  - Pod는 살아있는 상황이다.
  - 시간이 지나면 `Terminating`이라는 상태가 되지만 실제로 삭제는 안된다.

## 수동으로 삭제하기
``` bash
$ k delte po kubia-0
  • 실제로 삭제되지 않음.
  • 아까 상황에서 본 것처럼 네트워크 연결이 끊어졌기 때문에 해당 명령을 받을 수가 없는 것 (벌써 마스터는 이놈을 없애버리고 싶지만…)

강제 삭제하기

$ k delete po kubia-0 --force --grace-period 0

확실하게 손절해버린다.

  • grace-period: (default) 30s

약간 애매했던 것은 replicaSet은 손절하나 싶었는데 Unknown이면 결국 아무것도 못한다는 내용을 읽었습니다.

결국 Unknown이면 모두 저렇게 삭제하는 거 말고는 방법이…?

ref

책: 쿠버네티스 인 액션

이미지 출처: https://livebook.manning.com/book/kubernetes-in-action/chapter-10/55

Leave a comment