개발/Infra

[k8s] StatefulSet Controller

devriver 2021. 8. 3. 22:45
대세는 쿠버네티스 [초급~중급] 강의를 보고 정리한 글입니다.
[중급 편] 컨트롤러 StatefulSet

Application의 종류

Stateless Application

  • 앱이 여러 개 배포되어도 다 똑같은 서비스의 역할을 한다.
  • 하나의 앱이 죽는다면 같은 서비스의 역할을 하는 앱을 복제하면 되고 이름도 어떻게 설정하든 상관없다.
  • 볼륨이 반드시 필요하진 않다. 볼륨 하나에 모든 앱의 로그를 저장할 수 있다.
  • 대체로 사용자들이 접속하며 여러 앱에 트래픽을 분산한다. (단순 분산)
  • ReplicaSet 컨트롤러
  • 예) Web Server(Apache, Nginx, IIS)

Stateful Application

  • 각각의 앱마다 자신의 역할이 있다.
  • 하나의 앱(Arbiter 역할을 하는 앱)이 죽는다면 반드시 Arbiter 역할을 하는 앱을 만들어야 하며 앱 이름은 고유 식별자이므로 변경되어서는 안 된다.
  • 각각의 역할이 다르므로 각각 다른 볼륨을 연결해야 한다.
  • 대체로 내부 시스템들이 데이터베이스 저장을 위해 네트워크에 연결되며 트래픽은 각 앱의 특징에 맞게 전달되야한다.
    예) Primary 앱(read, write), Secondary 앱(read) 라면 조회만 하는 내부 시스템은 Secondary로 트래픽이 가야 한다.
  • StatefulSet 컨트롤러는 Stateful Application이 유지되도록 관리한다.
  • 목적에 따라 Pod(앱)에 연결하기 위해 Headless Service를 달아준다.
  • 예) Database(Primary, Secondary, Arbiter)

StatefulSet Controller

ReplicaSet vs StatefulSet의 기능적 차이

공통점

  • replicas 개수만큼 pod를 관리한다.

차이점

  • pod가 생길 때 ReplicaSet은 이름을 랜덤 생성하지만 StatefulSet은 0부터 순차적으로 생성한다.
  • ReplicaSet은 동시에 pod를 생성하지만 StatefulSet은 순차적으로 pod를 생성한다.
  • 하나의 pod가 삭제되면 ReplicaSet은 새 이름으로 pod를 생성하지만 StatefulSet은 삭제된 pod의 이름으로 생성한다.
  • replicas 3이고 pod가 3개 있는 상태에서 replicas를 0으로 한다면? ReplicaSet은 모든 pod를 동시에 삭제하지만 StatefulSet은 인덱스가 높은 pod부터 순차적으로 삭제한다.

ReplicaSet vs StatefulSet의 PersistentVolumeClaim, Headless Service 연결 방법의 차이

ReplicaSet

  • 미리 PVC를 직접 생성해놔야 하며 ReplicaSet은 template의 persistentVolumeClaim: "PVC1" 설정에 따라 pod를 생성할 때 PVC1에 바로 연결한다.
  • replicas를 1 -> 3으로 변경한다면? ReplicaSet은 만든 pod를 모두 같은 PVC에 연결한다.
  • ReplicaSet에서 PVC1이 노드 1에 있다면 pod들도 노드1에 있어야 한다. pod가 node2에 생성되면 PVC1에 연결할 수 없어 pod 생성에 오류가 발생한다. 이를 방지하고자 nodeSelector: "node1"으로 설정해야 한다.

StatefulSet

  • template에 따라 pod를 생성하고 volumeClaimTemplates에 따라 PVC를 동적으로 생성하여 pod와 바로 연결한다.
  • replicas를 1 -> 3으로 변경한다면? StatefulSet은 pod가 추가될 때마다 동적으로 PVC를 만든 후 연결한다.
  • pod2를 삭제한다면? pod2와 동일한 이름과 역할의 pod를 만들고 기존에 만들어진 PVC-2에 연결한다.
  • pod가 있는 노드에 PVC가 동적으로 만들어지기 때문에 알아서 각 노드에 균등하게 분포된다.
  • replicas를 3 -> 0으로 변경한다면? pod는 순차적으로 삭제하지만 PVC는 삭제하지 않는다. 삭제하려면 사용자가 직접 삭제해야 한다.
  • StatefulSet을 만들 때 ServiceName 속성에 서비스 이름 지정할 수 있다. 예를 들어, ServiceName: "Headless"로 설정하고 Headless Service를 만들면 pod2에 예측 가능한 도메인 이름(Pod-2.Headless)이 만들어진다. 다른 인터널 pod는 이 도메인으로 Statefulset이 관리하는 pod에 접근할 수 있다

yaml 예시

StatefulSet

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: stateful-db
spec:
  replicas: 1
  selector:
    matchLabels:
      type: db
  template:
    metadata:
      labels:
        type: db
    spec:
      containers:
      - name: container
        image: kubetm/app
      terminationGracePeriodSeconds: 10

PVC

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: replica-pvc1
spec:
  accessModes:
  - ReadWriteOnce
  resources:
    requests:
      storage: 1G
  storageClassName: "fast" // 동적으로 PV 생성/연결

ReplicaSet

apiVersion: apps/v1
kind: ReplicaSet
metadata:
  name: replica-pvc
spec:
  replicas: 1
  selector:
    matchLabels:
      type: web2
  template:
    metadata:
      labels:
        type: web2
    spec:
      nodeSelector:
        kubernetes.io/hostname: k8s-node1 // pod와 pvc가 같은 노드에 생성한다.
      containers:
      - name: container
        image: kubetm/init
        volumeMounts:
        - name: storageos
          mountPath: /applog
      volumes:
      - name: storageos
        persistentVolumeClaim:
          claimName: replica-pvc1
      terminationGracePeriodSeconds: 10 // 10초 후에 삭제한다.

StatefulSet

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: stateful-pvc
spec:
  replicas: 1
  selector:
    matchLabels:
      type: db2
  serviceName: "stateful-headless" // 이 이름의 서비스를 만든다. Headless Service와 연결되는 이름
  template:  // template 기반으로 pod를 만들며 이름은 stateful-pvc에 0부터 인덱스가 붙는다.
    metadata:
      labels:
        type: db2
    spec:
      containers:
      - name: container
        image: kubetm/app
        volumeMounts:
        - name: volume
          mountPath: /applog
      terminationGracePeriodSeconds: 10
  volumeClaimTemplates: // 기반으로 volume-stateful-pvc-<index> 이름의 pvc를 만든다.
  - metadata:
      name: volume // volumeMounts의 name과 이름이 동일해야한다.
    spec:
      accessModes:
      - ReadWriteOnce
      resources:  // PVC 만들 때 쓰던 내용과 동일
        requests:
          storage: 1G
      storageClassName: "fast"

Service(Headless)

apiVersion: v1
kind: Service
metadata:
  name: stateful-headless // StatefulSet의 ServiceName과 일치해야한다.
spec:
  selector:
    type: db2
  ports:
    - port: 80
      targetPort: 8080    
  clusterIP: None // None으로 설정하여 Headless 서비스로 만든다.