볼륨과 스테이트 풀셋

Son_Doobu96·2023년 2월 15일
1

DevOps 이론

목록 보기
23/25
post-thumbnail

◎ 볼륨과 스테이트풀셋의 기본 개념

■ 파드는 Stateless하다.

  • 파드는 일시적이다. (언제든지 삭제될 수 있다.)
  • 파드 그 자체는 Stateless하다. 따라서 디플로이먼트를 통해 교체와 배치되는 파드들은 상호 대체 가능하다.

=> 파드의 데이터는 언제든지 삭제 될 수 있다.

■ 영속적인 데이터를 남기는 방법?

  • 파드 그 자체에 상태(데이터)를 남겨야 하는 애플리케이션 : MySQL, mongoDB, redis와 같은 DB
  • 볼륨(Volume) : 프로그램이 종료되어도 사라지지 않는 영속적인 데이터를 저장하기 위해 분리된 서비스

=> 볼륨을 이용해 파드가 삭제되어도 데이터를 영속적으로 남길 수 있다.


■ 스테이트풀셋

스테이트풀셋은 애플리케이션 구성(파드)을 복제하더라도, 스토리지 클래스를 이용해 파드가 필요로 하는 볼륨을 자동으로 프로비저닝하여 연결합니다.

따라서 첫번째 파드를 primary(master), 두번째 파드를 secondary 복제본처럼 구성하는 것도 가능하다.

▶ 디플로이먼트와 스테이트풀셋의 공통점

동일한 컨테이너 스펙을 기반으로 둔 파드들을 관리한다.

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

● 디플로이먼트

  • 파드의 순서와 고유성을 보장하지 않는다.
  • 파드를 상호 대체 가능한 리소스로 취급한다.

● 스테이트풀셋

  • 파드의 순서와 고유성을 보장
  • 파드를 상호 대체 할 수 없는 리소스로 취급한다.

■ 스테이트풀셋을 사용할 때의 주의사항

  • 파드에 지정된 스토리지는 관리자에 의해 퍼시스턴트 볼륨 프로비저너를 기반으로 하는 storage class를 요청해서 프로비저닝하거나 사전에 프로비전이 되어야 합니다.
  • 헤드리스 서비스가 필요합니다.

◎ 생각해볼만한 문제

Q.애플리케이션에 HTTP 500과 같은 에러가 발생한 경우, 컨테이너를 다시 실행해야 할 것 입니다. HTTP 에러가 발생했다는 것을 어떻게 알 수 있을까요? 어떻게 해야 쿠버네티스가 에러가 발생한 컨테이너를 자동으로 재시작하게 만들 수 있을까요?

Project2에서 ECS를 활용하면서 해당 기능을 사용한 적이 있다. 로드밸런서를 이용해 대상그룹 내에 포함된 컨테이너들의 연결 상태를 체크하는 Health Check 기능이었다.

Kubernetes에도 같은 기능을 하는 장치들이 있다.

  • Liveness Probe : 컨테이너의 활성 상태를 체크 (컨테이너의 동작을 체크한다.)

    • Pod가 정상 실행되었더라도 애플리케이션에 문제가 생겨서 접속이 안되는 경우를 감지하고 문제 발생시 Pod를 죽이고 재실행한다.
  • Readiness Probe : 서비스 가능 상태를 체크
    (컨테이너가 요청을 처리할 준비가 되었는지 확인 한다.)

    • Liveness Probe와의 차이는 Pod의 제거 및 재실행이 아닌 Pod를 서비스로부터 제외시킨다는 점이다.
  • Startup Probe : 컨테이너 내 애플리케이션이 시작되었는지를 나타낸다.

    • startup probe를 정의할 경우 헬스체크에 성공할 때 까지 다른 나머지 probe는 활성화 되지 않는다. 만약 startup probe가 실패하면, kubelet이 컨테이너를 죽이고, 컨테이너 재시작 정책에 따라 컨테이너를 재시작한다.

위의 3가지 Probe들을 활용해 컨테이너 혹은 애플리케이션을 나의 서비스 환경에 맞게 상태 체크를 진행할 수 있다.

위의 세 가지 Probe들이 이용하는 상태 체크의 방식은 크게 3가지로 분류된다.


▶ Command Probe

  • Command Probe에서의 상태체크는 쉘 명령으로 수행된다.
  • 결과 값이 0이면 성공, 0이 아니면 실패로 간주한다.
<# Command Probe yaml 예시>
apiVersion: v1
kind: Pod
metadata:
  name: liveness-exec
spec:
  containers:
  - name: liveness
    image: registry.k8s.io/busybox
    imagePullPolicy: Always
    ports:
    - containerPort: 8080
 <# livenessProbe  Command Probe 방법을 이용>
    livenessProbe:
      exec:
        command:
        - cat
        - /tmp/healthy
      initialDelaySeconds: 5
      periodSeconds: 5

initialDelaySeconds : Kubelet에게 첫 번째 probe를 실행하기 전
대기 해야 할 초를 정의한다. (컨테이너 실행 후 대기시간)
PeriodSeconds : Kubelet이 5초마다 LivenessProbe를 실행하도록 정의한다.

▶ Command Probe의 Health Check를 학인하는 방법은 아래와 같다.
$ kubectl describe pod liveness-exec
describe 명령어를 통해 pod의 상세정보를 확인하면 된다.

# 쿠버네티스 공식문서 기반 로그 예시
 Type     Reason     Age                From               Message
  ----     ------     ----               ----               -------
  Normal   Scheduled  57s                default-scheduler  Successfully assigned default/liveness-exec to node01
  Normal   Pulling    55s                kubelet, node01    Pulling image "registry.k8s.io/busybox"
  Normal   Pulled     53s                kubelet, node01    Successfully pulled image "registry.k8s.io/busybox"
  Normal   Created    53s                kubelet, node01    Created container liveness
  Normal   Started    53s                kubelet, node01    Started container liveness
  Warning  Unhealthy  10s (x3 over 20s)  kubelet, node01    Liveness probe failed: cat: can't open '/tmp/healthy': No such file or directory
  Normal   Killing    10s                kubelet, node01    Container liveness failed liveness probe, will be restarted

▶ HTTP probe

  • 가장 많이 사용하는 방식이다. HTTP GET을 이용해 컨테이너의 상태를 체크한다.
  • 응답 코드가 200~399 사이에만 Probe를 정상으로 판단하고 그 이외의 값일 경우 비정상으로 판단한다.
  • HTTP GET prot에 대한 DDos 공격등의 보안적인 이슈가 발생할 수 있어 서비스 포트와 Probe포트를 분리하여 사용할 수도 있다.
<# HTTP Probe yaml 예시>
apiVersion: v1
kind: Pod
metadata:
  labels:
    test: liveness
  name: liveness-http
spec:
  containers:
  - name: liveness
    image: registry.k8s.io/liveness
    args:
    - /server
<# livenessProbe  HTTP Probe 방법을 이용>
    livenessProbe:
      httpGet:
        path: /healthz
        port: 8080
        httpHeaders:
        - name: Custom-Header
          value: Awesome
      initialDelaySeconds: 3
      periodSeconds: 3

HTTP 요청의 경우 Kubelet에 내장된 함수를 이용해 GET 요청을 보내고 상태를 체크한다.
공식 문서에서 소개하는 healthz HandleFunc는 아래와 같다.

http.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
    duration := time.Now().Sub(started)
    if duration.Seconds() > 10 {
        w.WriteHeader(500)
        w.Write([]byte(fmt.Sprintf("error: %v", duration.Seconds())))
    } else {
        w.WriteHeader(200)
        w.Write([]byte("ok"))
    }
})

▶ HTTP probe의 Health Check를 학인하는 방법은 아래와 같다.
$ kubectl describe pod liveness-http


▶ TCP Probe

  • 지정된 포트에 TCP 연결을 시도하여 연결이 성공하면 컨테이너가 정상인 것으로 판단한다.
<# TCP Probe yaml 예시>
apiVersion: v1
kind: Pod
metadata:
  name: goproxy
  labels:
    app: goproxy
spec:
  containers:
  - name: goproxy
    image: registry.k8s.io/goproxy:0.1
    ports:
    - containerPort: 8080
<# livenessProbe  TCP Probe 방법을 이용>
    livenessProbe:
      tcpSocket:
        port: 8080
      initialDelaySeconds: 15
      periodSeconds: 20

▶ TCP Probe의 Health Check를 학인하는 방법은 아래와 같다.
$ kubectl describe pod goproxy

■ 개인적인 견해

Probe의 사용이 왜 pod라는 kind에서 명세되어야 할까를 처음에는 고민되었다. 하지만 Probe의 목적을 생각하니 그 해답이 나왔다.

만약 Probe가 pod spec이 아닌 Service spec에 위치하게 된다, 그 서비스 타입을 load balacer로 가정하겠다.

Probe는 상태를 체크하기 위해 요청을 보낼 것이다. 결국 이것도 요청이라는 점이 이때 떠올랐다.
load balacer는 그 요청을 각 pod들에 무작위로 전달할 것이고 상태체크를 지속적으로 하기 때문에 결국에는 상태 확인을 할 수 있겠지만 결국 5개의 컨테이너의 상태 체크를 위해서는 요청이 5번 진행될때까지를 기다려야 한다는 점이었다.

그렇기 때문에 컨테이너의 갯수가 수십 수백개가 된다면 사실상 사용이 불가능한 수준에 이를 것이기에 Probe를 pod spec에 명시하지 않았을까 라고 생각하게 되었다.


Q. 왜 파드와 PV(퍼시스턴스볼륨)를 직접 연결하지 않는걸까요?

먼저 볼륨과 퍼시스턴스 볼륨에 대해 알아봤다.

▶ 볼륨과 퍼시스턴스 볼륨의 등장 배경?

파드는 Stateless한 특징을 가지고 있다. 즉 데이터의 소실 가능성을 언제든 가지고 있는 불완전한 서비스이다.
이 때문에 지속적으로 보관이 필요한 데이터의 소실 문제를 해결하기 위해 볼륨이 등장하게 되었다.

▶ 볼륨과 퍼시스턴스 볼륨의 공통점?

  • 볼륨은 기본적을 파드의 구성요소로써 컨테이너와 동일하게 파드 스펙에서 정의될 수 있다.
  • 기본적으로 볼륨은 디렉토리의 개념이다.
  • 볼륨은 파드의 모든 컨테이너에서 사용(공유) 가능하며 이는 접근하려는 컨테이너에서 마운트함으로써 수행할 수 있다.

▶ 볼륨(Voulume)과 퍼시스턴스 볼륨(Persistent Voulume)의 차이점?

  • 볼륨 (Voulume)

    • 볼륨의 수명주기는 파드와 같다. (인스턴스 스토어)
    • Pod와 직접 연결되어 있기 때문에 속도가 빠르다.
  • 퍼시스턴스(Persistent Voulume)

    • 퍼시스턴스 볼륨의 수명주기는 파드와 별개의 수명주기를 갖는다.
      (AWS EBS)
    • 보다 안정적이고 영구적인 데이터 보관이 필요할 때 사용한다.

▶볼륨에서 반드시 알아야 할 퍼시스턴스 볼륨 클레임(Persistent Voulume Claim)의 개념

쿠버네티스 공식문서에서 PVC는 사용자의 스토리지에 대한 요청이라고 나와 있다. 해당 요청을 통해 PV의 특정 크기 및 접근 모드 등을 요청할 수 있다.

PVC에는 두 가지의 프로비저닝이 존재하는데

정적 프로비저닝의 경우
클러스터 관리자가 여러 PV를 만들고 각 개발자에게 실제 스토리지의 세부 사항을 제공하여 요청을 받는 형태이다.

동적 프로비저닝의 경우
스토리지클래스를 기반으로 관리자가 생성한 PV가 사용자의 PVC와 일치하지 않으면 요청된 PVC에 맞춰 PV를 동적으로 프로비저닝 하려고 시도하는 형태이다.

지금까지는 위의 질문에 대한 기본 개념이었고 아래부터가 해답이다.

나는 이 해답을 두 개로 정리했다.

첫 번째, 내 생각

Pod와 PV를 직접 연결하게 된다면 각 개발자가 PV를 Pod에 mount함으로써 PV 규격의 통일성을 해치고 IaC를 통해 관리하는 쿠버네티스의 장점을 소실하게 된다.

하지만 PVC를 통해 연결한다면 각 개발자는 PV를 ‘mount’하는 것이 아니라 ‘요청’할 수 있게 되고 어떤 PV를 연결시켜 줄지에 대한 권한은 클러스터 관리자인 DevOps에게로 이전된다.

이렇게 PVC를 통해 요청에 맞는 적절한 PV를 연결시켜 줌으로써 각 개발자는 PV를 Pod에 마운트한 것처럼 사용할 수 있고

DevOps는 각 PV를 리소스로써 활용하면서 IaC를 통해 관리하는 장점을 유지하면서도 원활한 서비스를 구성할 수 있다고 생각한다.

두 번째, AI의 답변

PV와 포드를 분리하는 이유는 Kubernetes 워크로드의 유연성, 이식성 및 내구성을 향상시키기 때문이다.

워크로드의 유연성이란?
PV가 필요한 상태 저장 애플리케이션이 있는 경우 PV를 포드에서 분리하고 PVC를 통해 포드와 연결함으로써 상황, 환경에 맞는 PV를 적절히 선택하여 사용할 수 있고 반드시 고정된 PV에 포드에 데이터가 저장되어지지 않아도 된다.

워크로드의 이식성이란?
애플리케이션을 수평으로 확장해야 하는 경우 더 많은 포드를 생성하고 이를 PVC를 통해 포드와 연결시켜줌으로써 데이터가 모든 포드 간에 공유 될 수 있다.

워크로드의 내구성이란?
PV의 경우 포드가 삭제된 후에도 지속되도록 설계되어 있다. PVC를 이용할 경우 의존성까지 줄일 수 있게 되어 컴퓨팅 리소스와 스토리지 리소스간에 영향을 미치지 않고 독립적으로 확장 및 변경이 가능하다.


아래는 PVC를 통해 포드와 PV를 연결하는 매니패스트 구성이다.
쿠버네티스 공식 문서에 따르면 아래 3가지 순서대로 진행하라고 되어 있다.

  1. PV 생성
  2. PV에 바인딩되는 PVC 생성
  3. PVC를 이용하는 Pod 생성
# 1. PV 생성
apiVersion: v1
kind: PersistentVolume
metadata:
  name: mongodb-pv
spec:
  capacity:                                    # 크기
    storage: 1Gi
  accessModes:
    - ReadWriteOnce                        # 하나의 PVC만 읽거나 쓴다
    - ReadOnlyMany                         # 여러 PVC가 읽기만 한다
  persistentVolumeReclaimPolicy: Retain   # PVC 와 연결이 해제되면 지우거나 삭제하지 않고 유지한다
  hostPath:
    path: /tmp/mongodb
# 2. PV에 바인딩되는 PVC 생성
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mongodb-pvc
spec:
  resources:
    requests:
      storage: 1Gi      #스토리지 사이즈
  accessModes:        # 연결할 PV는 아래 옵션으로 접근이 가능해야 한다. (Label을 맞추는 것과 동일한 조건의 개념으로 이해했다)
  - ReadWriteOnce
  storageClassName: ""  # 동적 프로비저닝을 위해 사용
# 3. PVC를 이용하는 Pod 생성
apiVersion: v1
kind: Pod
metadata:
  name: mongodb 
spec:
  containers:
  - image: mongo
    name: mongodb
    volumeMounts:
    - name: mongodb-data
      mountPath: /data/db
    ports:
    - containerPort: 27017
      protocol: TCP
  volumes:
  - name: mongodb-data
    persistentVolumeClaim:
      claimName: mongodb-pvc    # PVC 의 이름
profile
DevOps를 꿈꾸는 엔지니어 지망생!

0개의 댓글