[k8s]스테이트풀 셋
Stateless 한 파드를 Stateful 하게 관리하고 싶다.
파드는 일시적이며 Stateless하다. Deployment에서 파드의 교체와 배치를 담당하며, 레플리카셋을 통해 파드를 수평 확장할 수 있다. 파드가 Stateless 하기에 수평 확장이 쉽고 파드들은 상호 대체 가능하다. 이렇듯 쿠버네티스는 Stateless 한 애플리케이션을 관리하는데 특화되어 있다. 아무래도 컨테이너 기술 자체가 그렇듯이 말이다. 그러나 데이터베이스와 같이 Stateful 하게 관리해야 할 때도 있을 것이다. 그럴 땐 어떻게 할까?
MySQL, mongoDB, redis와 같은 데이터베이스 어플리케이션이 담긴 파드가 사라질 때, 데이터가 함께 사라지도록 두어서는 안 된다. 쿠버네티스에서 제공하는 Volume은 컨테이너 내의 디스크에 있는 임시적인 스토리지로, 컨테이너가 사라질 때 데이터도 손실된다. 그에 반해 PV(Persistence Volume)은 영속적인 데이터를 저장할 수 있다. 따라서 파드에 PV를 연결하면 데이터와 애플리케이션을 Stateful 하게 관리할 수 있다.
AWS의 인스턴스 스토어볼륨과 EBS의 차이와 비슷하다. 다음을 참고하자
다음도 참고하자
파드와 PV를 연결하자.
애플리케이션이 담긴 파드에 PV을 마운트하면, 파드에 문제가 생겨 교체되더라도 PV의 데이터는 사라지지 않는다. PV는 namespace를 갖지 않는 것으로 파드에 직접 연결하지 않고 PVC(Persistence Volume Claim)를 이용한다. PV는 클러스터 내에 있을 수도 있고, 로컬 디스크나 클라우드 스토리지 같은 어떠한 볼륨이 될 수도 있다. 파드와 PV를 직접 연결하기 위해서는 개발자가 볼륨에 대한 내용도 알아야겠지만, PVC를 이용하면 그럴 필요가 없다. 스토리지가 어디에 있든 잘 작동하면, 애플리케이션은 PVC로 볼륨 요청을 보내고 PV를 바인딩해준다. 즉, PV는 실제로 데이터가 저장되는 공간이고, PVC는 PV를 선택, 연결해주는 요청 그 자체이다.
PV의 간단한 예시이다.
#PV_AWS_EBS.yaml
apiVersion: v1
kind: Pod
metadata:
name: test-ebs
spec:
containers:
- image: k8s.gcr.io/test-webserver
name: test-container
volumeMounts:
- mountPath: /test-ebs
name: test-volume
volumes:
- name: test-volume
# 이 AWS EBS 볼륨은 이미 존재해야 한다.
awsElasticBlockStore:
volumeID: "vol-id"
fsType: ext4
위 처럼 클라우드 스토리지인 EBS를 활용하여 PV 파드를 간단하게 생성할 수 있다. 이는 namespace가 없기에 독립적으로 클러스터 내에 존재한다. 쿠버네티스는 볼륨에 대한 관리를 따로 해주지 않는다. 따라서 볼륨이 정상적으로 작동하는지는 사용자가 직접 체크해야 한다. 참고로 AWS CLI를 사용하여 간단히 EBS 볼륨을 만들 수 있다.
❯ aws ec2 create-volume --availability-zone=ap-northeast-2a --size=10 --volume-type=gp2
쿠버네티스 클러스터 내의 디스크 볼륨을 PV로 활용하여 PVC를 통해 파드와 연결하는 실습도 있다. 개념을 잡은 후 해보기 좋다.
동적 볼륨 프로비저닝
쿠버네티스 공식문서에 적힌 내용을 보자
동적 볼륨 프로비저닝을 통해 온-디맨드 방식으로 스토리지 볼륨을 생성할 수 있다.
동적 프로비저닝이 없으면 클러스터 관리자는 클라우드 또는 스토리지 공급자에게 수동으로 요청해서 새 스토리지 볼륨을 생성한 다음, 쿠버네티스에 표시하기 위해 PersistentVolume 오브젝트를 생성해야 한다.
동적 프로비저닝 기능을 사용하면 클러스터 관리자가 스토리지를 사전 프로비저닝 할 필요가 없다.
대신 사용자가 스토리지를 요청하면 자동으로 프로비저닝 한다.
다시말해, 파드마다 별도의 데이터를 저장할 수 있게 만들려면, 수평 확장된 파드에 서로 다른 PV가 연결되어야 한다.
스토리지 클래스(Storage Class)리소스는 파드가 PV를 요청할 때 동적으로 PV를 프로비저닝 할 수 있게 한다.
스테이트풀 셋
쿠버네티스 공식문서를 보자.
스테이트풀셋은 애플리케이션의 스테이트풀을 관리하는 데 사용하는 워크로드 API 오브젝트이다.
파드 집합의 디플로이먼트와 스케일링을 관리하며, 파드들의 순서 및 고유성을 보장한다.
디플로이먼트와 유사하게, 스테이트풀셋은 동일한 컨테이너 스펙을 기반으로 둔 파드들을 관리한다. 디플로이먼트와는 다르게, 스테이트풀셋은 각 파드의 독자성을 유지한다. 이 파드들은 동일한 스펙으로 생성되었지만, 서로 교체는 불가능하다. 다시 말해, 각각은 재스케줄링 간에도 지속적으로 유지되는 식별자를 가진다.
스토리지 볼륨을 사용해서 워크로드에 지속성을 제공하려는 경우, 솔루션의 일부로 스테이트풀셋을 사용할 수 있다. 스테이트풀셋의 개별 파드는 장애에 취약하지만, 퍼시스턴트 파드 식별자는 기존 볼륨을 실패한 볼륨을 대체하는 새 파드에 더 쉽게 일치시킬 수 있다.
이러한 스테이트풀 셋은 다음과 같은 경우의 애플리케이션에 적합하다.
- 안정된, 고유한 네트워크 식별자.
- 안정된, 지속성을 갖는 스토리지.
- 순차적인, 정상 배포(graceful deployment)와 스케일링.
- 순차적인, 자동 롤링 업데이트.
다음과 같은 제한 사항도 있다.
- 파드에 지정된 스토리지는 관리자에 의해 퍼시스턴트 볼륨 프로비저너를 기반으로 하는 storage class를 요청해서 프로비전 하거나 사전에 프로비전이 되어야 한다.
- 스테이트풀셋은 현재 파드의 네트워크 신원을 책임지고 있는 헤드리스 서비스가 필요하다. 사용자가 이 서비스를 생성할 책임이 있다.
예시를 보면 다음과 같다.
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: k8s.gcr.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
왜 스테이트풀셋은 헤드리스 서비스로부터 직접 트래픽을 받을까?
해당 의문에 대해 좋은 블로그 글이 있어서 가져와본다.
Headless 서비스는 해당 Pod Set에 대해 직접 접근 가능한 고유 IP를 부여한다.
ClusterIP, NodePort, LoadBalancer와 같은 다른 타입의 서비스는 클라이언트가 Pod에 접근하기 위해 kube-proxy로 접근한 후 프록시되어 pod에 액세스 하게 된다. 프록시로 접근하는 경우, 현재 연결된 Pod와 다음번에 연결될 Pod가 같다는 보장을 할 수 없기 때문에, Stateful 한 애플리케이션에 적합하지 않다.
Headless 타입의 서비스를 생성하면 클러스터 네트워크 내부에서 서비스에 속한 Pod에 직접 접근 가능한 고유 IP와 DNS가 생긴다.