K8s 실전 활용을 위한 10단계 Ch12
in Devops on Kubernetes
스테이트풀셋
Statefulset은 퍼시스턴트 볼륨과 파드를 함께 조합하여 제어하기에 적합한 컨트롤러이다.
컨테이너와 파드는 태생적으로 데이터 보관이 어렵기 때문에 파드와 퍼시스턴트 볼륨을 조합해야한다.
이 때 k8s에서는 이를 편리하게 관리해줄 수 있는 Statefulset라는 이름의 컨트롤러를 제공한다.
디플로이먼트와의 차이
스테이트풀셋의 특징을 디플로이먼트와 비교하자면 다음과 같다.
파드의 이름과 퍼시스턴트 볼륨의 이름
스테이트풀셋 또한 지정한 레플리카 수에 해당하는 파드를 파드 템플릿에 기술하고 그 내용에 따라 기동한다.
디플로이먼트의 경우 해시값이지만 스테이트풀셋의 이름 뒤에 순서대로 번호가 부여된 것을 볼 수 있다.
그리고 스테이트풀셋의 퍼시스턴트 볼륨은 하나의 단위로 취급하여 동일한 번호로 묶인 것을 확인할 수 있다.
서비스의 연결 및 이름 해결
스테이트풀셋 관리하의 파드에 요청을 전송하기 위해서는 대표IP를 가지지 않는 ClusterIP의 헤드리스 모드를 사용해야한다.
스테이트풀셋의 매니페스트에 spec.serviceName
에 서비스 이름을 설정하면 각 파드의 이름으로 파드의 IP 주소를 얻을 수 있다.
파드 분실 시 동작
스테이트풀셋 관리하의 파드가 노드 장애 등으로 없어지면 동일한 이름으로 새롭게 파드가 기동된다.
그리고 기존 파드가 사용하던 퍼시스턴트 볼륨을 그대로 이어서 사용한다.
여기서 주의할 점은 파드의 이름이 같더라도 IP는 분명히 바뀌었다는 점이다.
이것이 디플로이먼트와 스테이트풀셋의 또 다른 점이다.
노드 정지 시의 동작
하드웨어 장애나 네트워크로 장애로 특정 노드가 마스터와의 연결이 끊어졌을 때, 스테이트풀셋은 새로운 파드를 기동하지 않는다.
가령 kubelet과 마스터와의 통신이 일시적으로 끊겨서 파드가 정상적으로 동작하고 있는데, 만약 마스터가 대체 파드를 기동하여 퍼시스턴트 볼륨을 마운트해버리면 데이터가 파손될 위험이 있다.
그래서 스테이트풀셋에서는 노드나 네트워크에 장애가 발생하여 마스터와의 연결이 끊겨도 파드를 새로 생성해주지않는다.
그러나 다음 중 하나에 해당하는 경우에만 스테이트풀셋이 분실된 파드를 다른 노드에서 기동해준다.
장애 노드를 k8s의 클러스터 멤버에서 제외하는 경우
문제가 있는 파드를 강제 종료 하는 경우
장애로 인해 정지한 노드를 재가동하는 경우
위의 경우에만 파드를 다른 노드에 띄워주는 이유는 이미 정상 구동하는 파드가 있는데 새로운 파드를 띄우면 데이터 손실 위험이 있기 때문이다.
노드의 장애상황에서 이를 쉽게 해결할 수 있는 방법은 장애 노드를 k8s 클러스터에서 제거하면 된다.
그런데 스테이트풀셋의 역할은 파드를 컨트롤하는 것이므로, 노드를 재기동하거나 제외하는 것을 담당하지 않는다.
이 때 사용할 수 있는 명령어가 kubectl delte node [장애_발생_노드]
이다.
파드 순번 제어
스테이트풀셋의 파드 이름에 붙는 번호는 파드의 기동과 정지 뿐만 아니라, 롤링 업데이트의 순서에도 사용된다.
스테이트풀셋의 번호는 다음과 같은 규칙을 갖는다.
레플리카 숫자에 도달할 때 까지 파드와 퍼시스턴트 볼륨이 짝을 지어 차례로 기동하며 정지할 때는 파드의 이름 뒤에 번호가 큰 순서대로 정지한다.
레플리카 값을 늘리면 파드 이름 뒤에 붙는 순서가 늘어나면서 파드가 기동한다. 반대로 레플리카 값을 줄이면 번호가 큰 값부터 줄어든다.
롤링 업데이트할 때에도 파드의 이름에 붙는 번호에 따라 갱신된다.
매니페스트 작성법
MySQL을 예제로 스테이트풀셋 매니페스트를 어떻게 작성할 수 있는지 직접 눈으로 살펴보자.
스테이트풀셋은 매니페스트의 특징 네 가지를 갖는다.
clusterIP:None
: 헤드리스 서비스 설정servieName: 서비스명
: 연동할 서비스의 이름을 지정template
: 볼륨 요구 템플릿의 이름으로 마운트 포인트 지정volumeClaimTemplates
: 레플리카 수만큼 볼륨 요구를 작성
다음은 위의 개념을 토대로 작성한 예제이다.
여기서는 MySQL 서버를 하나로 구성하였는데 spec.replicas를 2 이상으로 설정하면 복수의 MySQL 서버로 샤딩하여 부하를 분산하는 것도 가능하다.
# 서비스
apiVersion: v1
kind: Service
metadata:
name: mysql ## 이 이름이 k8s의 DNS로 등록됨
labels:
app: mysql-sts
spec:
ports:
- port: 3306
name: mysql
clusterIP: None # 특징 1. 헤드리스 서비스 설정
selector:
app: mysql-sts # 스테이트풀셋과 연결하는 라벨
---
# MySQL 스테이트풀셋
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
serviceName: mysql # 특징 2. 연계하는 서비스명 설정
replicas: 1
selector:
matchLabels:
app: mysql-sts
template:
metadata:
labels:
app: mysql-sts
spec:
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ROOT_PASSWORD
value: qwerty
ports:
- containerPort: 3306
name: mysql
volumeMounts: # 특징 3. 컨테이너 마운트 경로 설정
- name: pvc
mountPath: /var/lib/mysql
subPath: data # 초기화 시에 빈 디렉터리가 필요
livenessProbe: # Mysql 가동 체크
exec:
command:
- mysqladmin
- -p $MYSQL_ROOT_PASSOWRD
- ping
initialDelaySeconds: 60
timeoutSeconds: 10
volumeClaimTemplates: # 볼륨 요구 템플릿
- metadata:
name: pvc
spec:
accessModes:
- ReadWriteOnce
# storageClassName: ibmc-file-bronze # 용량 20Gi IKS
# storageClassName: gluster-heketi # 용량 12Gi GlusterFS
storageClassName: standard # 용량 2Gi Minikube/GKE
resources:
request:
storage: 2Gi
노드 장애 시의 동작
스테이트풀셋은 갑작스러운 노드 정지에 대해서 함부로 다른 노드에 파드를 옮기지 않는다.
그리고 스테이트풀셋은 파드의 컨트롤러라는 역할을 넘어 노드를 삭제하는 행동을 하지 않는다.
그러나 퍼블릭 클라우드에서의 스테이트풀셋은 노드 장애시의 동작이 다를 수 있다.
당장 GKE의 경우만 보더라도 노드의 가상서버가 지워지면 노드 수를 유지할 수 있도록 자동으로 노드를 만들어 기동한다.
온프레미스라면 이를 k8s API를 사용하여 개발해주어야할 필요가 있을 수 있다.
마무리
스테이트풀셋은 데이터를 보관해야 하는 애플리케이션에 적합한 컨트롤러다.
스테이트풀셋을 만들 때 퍼시스턴트 볼륨도 함께 만들어진다. 반면, 스테이트풀셋이 지워질 때 퍼시스턴트 볼륨은 지워지지 않는다.
스테이트풀셋은 데이터 보호를 우선시 하기 떄문에 노드에 장애가 발생하더라도 함부로 파드를 다른 노드로 옮기지 않는다. 또한 노드를 지우는 일도 하지 않는다. 때문에 노드 장애 상황에서는 정지한 노드를 재가동하거나 멤버를 지워야하는 필요가 있을 수 있다.
데몬셋은 모든 노드에서 파드를 돌리기 위한 컨트롤러다.