복제된 유상태(Stateful) 애플리케이션 실행하기
복제된 유상태(Stateful) 애플리케이션 실행하기 (Run a Replicated Stateful Application)
이 페이지는 StatefulSet을 사용해 복제된 유상태 애플리케이션을 실행하는 방법을 보여줘요. 이 애플리케이션은 복제된 MySQL 데이터베이스예요. 예시 토폴로지는 비동기 행 기반 복제를 사용하는 단일 프라이머리 서버와 여러 복제본을 가져요.
출처: 문서
본문
시작하기 전에 (Before you begin)
- Kubernetes 클러스터가 있어야 하고 kubectl 명령줄 도구가 클러스터와 통신하도록 구성되어 있어야 해요. 이 튜토리얼은 컨트롤 플레인 호스트로 작동하지 않는 노드가 최소 두 개 있는 클러스터에서 실행하는 것을 권장해요. 아직 클러스터가 없다면 minikube를 이용해 만들거나, 아래 Kubernetes 플레이그라운드 중 하나를 사용할 수 있어요: iximiuz Labs (https://labs.iximiuz.com/playgrounds?category=kubernetes&filter=all), Killercoda (https://killercoda.com/playgrounds/scenario/kubernetes), KodeKloud (https://kodekloud.com/public-playgrounds).
- 기본 StorageClass를 가진 동적 PersistentVolume 프로비저너가 있거나, 여기서 사용하는 PersistentVolumeClaim을 충족시키기 위해 PersistentVolume을 직접 정적으로 프로비저닝해야 해요.
- 이 튜토리얼은 PersistentVolume과 StatefulSet, 그리고 Pods, Services, ConfigMaps 같은 다른 핵심 개념에 익숙하다고 가정해요.
- MySQL에 대한 약간의 친숙함이 도움이 되지만, 이 튜토리얼은 다른 시스템에도 유용해야 하는 일반적인 패턴을 제시하는 것을 목표로 해요.
- 기본 네임스페이스 또는 충돌하는 객체를 포함하지 않는 다른 네임스페이스를 사용하고 있어요.
- AMD64 호환 CPU가 필요해요.
목표 (Objectives)
- StatefulSet으로 복제된 MySQL 토폴로지 배포.
- MySQL 클라이언트 트래픽 보내기.
- 다운타임에 대한 저항 관찰.
- StatefulSet 확장·축소.
MySQL 배포 (Deploy MySQL)
예시 MySQL 배포는 ConfigMap, 두 개의 Service, 하나의 StatefulSet으로 구성돼요.
ConfigMap 만들기 (#configmap)
다음 YAML 구성 파일에서 ConfigMap을 만들어요:
apiVersion: v1
kind: ConfigMap
metadata:
name: mysql
labels:
app: mysql
app.kubernetes.io/name: mysql
data:
primary.cnf: |
# Apply this config only on the primary.
[mysqld]
log-bin
replica.cnf: |
# Apply this config only on replicas.
[mysqld]
super-read-only
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-configmap.yaml
이 ConfigMap은 프라이머리 MySQL 서버와 그 복제본에서 구성을 독립적으로 제어할 수 있게 하는 my.cnf 오버라이드를 제공해요. 이 경우 프라이머리 서버가 복제 로그를 복제본에 제공할 수 있게 하고 싶고, 복제본이 복제를 통하지 않는 모든 쓰기를 거부하기를 원해요.
ConfigMap 자체에 서로 다른 부분이 서로 다른 파드에 적용되게 하는 특별한 것은 없어요. 각 파드는 초기화할 때 StatefulSet 컨트롤러가 제공하는 정보에 기반해 어떤 부분을 볼지 결정해요.
Services 만들기 (#services)
다음 YAML 구성 파일에서 Services를 만들어요:
# Headless service for stable DNS entries of StatefulSet members.
apiVersion: v1
kind: Service
metadata:
name: mysql
labels:
app: mysql
app.kubernetes.io/name: mysql
spec:
ports:
- name: mysql
port: 3306
clusterIP: None
selector:
app: mysql
---
# Client service for connecting to any MySQL instance for reads.
# For writes, you must instead connect to the primary: mysql-0.mysql.
apiVersion: v1
kind: Service
metadata:
name: mysql-read
labels:
app: mysql
app.kubernetes.io/name: mysql
readonly: "true"
spec:
ports:
- name: mysql
port: 3306
selector:
app: mysql
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-services.yaml
Headless Service는 StatefulSet 컨트롤러가 집합의 일부인 각 파드에 대해 만드는 DNS 항목의 홈을 제공해요. Headless Service가 mysql이라는 이름이므로 파드는 같은 Kubernetes 클러스터와 네임스페이스의 다른 파드에서 <pod-name>.mysql을 해석해 접근할 수 있어요.
mysql-read라는 클라이언트 Service는 자신의 클러스터 IP를 가진 일반적인 Service이며, Ready를 보고하는 모든 MySQL 파드에 걸쳐 연결을 분산해요. 잠재적 엔드포인트 집합에는 프라이머리 MySQL 서버와 모든 복제본이 포함돼요.
로드 밸런싱된 클라이언트 Service는 읽기 쿼리만 사용할 수 있다는 점을 유의해요. 프라이머리 MySQL 서버가 하나뿐이므로 클라이언트는 쓰기를 실행하려면 프라이머리 MySQL 파드에 직접(headless Service 내의 DNS 항목을 통해) 연결해야 해요.
StatefulSet 만들기 (#statefulset)
마지막으로 다음 YAML 구성 파일에서 StatefulSet을 만들어요:
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: mysql
spec:
selector:
matchLabels:
app: mysql
app.kubernetes.io/name: mysql
serviceName: mysql
replicas: 3
template:
metadata:
labels:
app: mysql
app.kubernetes.io/name: mysql
spec:
initContainers:
- name: init-mysql
image: mysql:5.7
command:
- bash
- "-c"
- |
set -ex
# Generate mysql server-id from pod ordinal index.
[[ $HOSTNAME =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
echo [mysqld] > /mnt/conf.d/server-id.cnf
# Add an offset to avoid reserved server-id=0 value.
echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
# Copy appropriate conf.d files from config-map to emptyDir.
if [[ $ordinal -eq 0 ]]; then
cp /mnt/config-map/primary.cnf /mnt/conf.d/
else
cp /mnt/config-map/replica.cnf /mnt/conf.d/
fi
volumeMounts:
- name: conf
mountPath: /mnt/conf.d
- name: config-map
mountPath: /mnt/config-map
- name: clone-mysql
image: gcr.io/google-samples/xtrabackup:1.0
command:
- bash
- "-c"
- |
set -ex
# Skip the clone if data already exists.
[[ -d /var/lib/mysql/mysql ]] && exit 0
# Skip the clone on primary (ordinal index 0).
[[ `hostname` =~ -([0-9]+)$ ]] || exit 1
ordinal=${BASH_REMATCH[1]}
[[ $ordinal -eq 0 ]] && exit 0
# Clone data from previous peer.
ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
# Prepare the backup.
xtrabackup --prepare --target-dir=/var/lib/mysql
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
containers:
- name: mysql
image: mysql:5.7
env:
- name: MYSQL_ALLOW_EMPTY_PASSWORD
value: "1"
ports:
- name: mysql
containerPort: 3306
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: 500m
memory: 1Gi
livenessProbe:
exec:
command: ["mysqladmin", "ping"]
initialDelaySeconds: 30
periodSeconds: 10
timeoutSeconds: 5
readinessProbe:
exec:
# Check we can execute queries over TCP (skip-networking is off).
command: ["mysql", "-h", "127.0.0.1", "-e", "SELECT 1"]
initialDelaySeconds: 5
periodSeconds: 2
timeoutSeconds: 1
- name: xtrabackup
image: gcr.io/google-samples/xtrabackup:1.0
ports:
- name: xtrabackup
containerPort: 3307
command:
- bash
- "-c"
- |
set -ex
cd /var/lib/mysql
# Determine binlog position of cloned data, if any.
if [[ -f xtrabackup_slave_info && "x$(<xtrabackup_slave_info)" != "x" ]]; then
# XtraBackup already generated a partial "CHANGE MASTER TO" query
# because we're cloning from an existing replica. (Need to remove the tailing semicolon!)
cat xtrabackup_slave_info | sed -E 's/;$//g' > change_master_to.sql.in
# Ignore xtrabackup_binlog_info in this case (it's useless).
rm -f xtrabackup_slave_info xtrabackup_binlog_info
elif [[ -f xtrabackup_binlog_info ]]; then
# We're cloning directly from primary. Parse binlog position.
[[ `cat xtrabackup_binlog_info` =~ ^(.*?)[[:space:]]+(.*?)$ ]] || exit 1
rm -f xtrabackup_binlog_info xtrabackup_slave_info
echo "CHANGE MASTER TO MASTER_LOG_FILE='${BASH_REMATCH[1]}',\
MASTER_LOG_POS=${BASH_REMATCH[2]}" > change_master_to.sql.in
fi
# Check if we need to complete a clone by starting replication.
if [[ -f change_master_to.sql.in ]]; then
echo "Waiting for mysqld to be ready (accepting connections)"
until mysql -h 127.0.0.1 -e "SELECT 1"; do sleep 1; done
echo "Initializing replication from clone position"
mysql -h 127.0.0.1 \
-e "$(<change_master_to.sql.in), \
MASTER_HOST='mysql-0.mysql', \
MASTER_USER='root', \
MASTER_PASSWORD='', \
MASTER_CONNECT_RETRY=10; \
START SLAVE;" || exit 1
# In case of container restart, attempt this at-most-once.
mv change_master_to.sql.in change_master_to.sql.orig
fi
# Start a server to send backups when requested by peers.
exec ncat --listen --keep-open --send-only --max-conns=1 3307 -c \
"xtrabackup --backup --slave-info --stream=xbstream --host=127.0.0.1 --user=root"
volumeMounts:
- name: data
mountPath: /var/lib/mysql
subPath: mysql
- name: conf
mountPath: /etc/mysql/conf.d
resources:
requests:
cpu: 100m
memory: 100Mi
volumes:
- name: conf
emptyDir: {}
- name: config-map
configMap:
name: mysql
volumeClaimTemplates:
- metadata:
name: data
spec:
accessModes: ["ReadWriteOnce"]
resources:
requests:
storage: 10Gi
kubectl apply -f https://k8s.io/examples/application/mysql/mysql-statefulset.yaml
다음을 실행해 시작 진행 상황을 볼 수 있어요:
kubectl get pods -l app=mysql --watch
잠시 후 세 파드 모두 Running이 되는 것을 볼 수 있어요:
NAME READY STATUS RESTARTS AGE
mysql-0 2/2 Running 0 2m
mysql-1 2/2 Running 0 1m
mysql-2 2/2 Running 0 1m
watch를 취소하려면 Ctrl+C를 누르세요.
이 매니페스트는 StatefulSet의 일부로 유상태 파드를 관리하기 위한 다양한 기술을 사용해요. 다음 섹션은 StatefulSet이 파드를 만들 때 무슨 일이 일어나는지 설명하기 위해 이러한 기술 중 일부를 강조해요.
유상태 파드 초기화 이해하기 (Understanding stateful Pod initialization)
StatefulSet 컨트롤러는 파드를 서수 인덱스 순서대로 한 번에 하나씩 시작해요. 다음 파드를 시작하기 전에 각 파드가 Ready를 보고할 때까지 기다려요.
또한 컨트롤러는 각 파드에 <statefulset-name>-<ordinal-index> 형식의 고유하고 안정적인 이름을 할당하며, 이로 인해 mysql-0, mysql-1, mysql-2라는 파드가 생겨요.
위 StatefulSet 매니페스트의 파드 템플릿은 이러한 속성을 활용해 MySQL 복제의 순서 있는 시작을 수행해요.
구성 생성 (#generating-configuration)
파드 사양의 어떤 컨테이너도 시작하기 전에 파드는 먼저 정의된 순서대로 init 컨테이너를 실행해요.
init-mysql이라는 첫 init 컨테이너는 서수 인덱스에 기반해 특별한 MySQL 구성 파일을 생성해요.
스크립트는 hostname 명령이 반환하는 파드 이름의 끝에서 추출해 자신의 서수 인덱스를 결정해요. 그런 다음 (예약 값을 피하기 위한 숫자 오프셋으로) 서수를 MySQL conf.d 디렉터리의 server-id.cnf라는 파일에 저장해요. 이것은 StatefulSet이 제공하는 고유하고 안정적인 신원을 같은 속성을 요구하는 MySQL 서버 ID의 영역으로 변환해요.
init-mysql 컨테이너의 스크립트는 또한 ConfigMap에서 primary.cnf 또는 replica.cnf를 conf.d에 내용을 복사해 적용해요. 예시 토폴로지가 단일 프라이머리 MySQL 서버와 임의의 수의 복제본으로 구성되므로 스크립트는 서수 0을 프라이머리 서버로, 나머지를 복제본으로 할당해요. StatefulSet 컨트롤러의 배포 순서 보장(/docs/concepts/workloads/controllers/statefulset/#deployment-and-scaling-guarantees)과 결합해, 이것은 복제본을 만들기 전에 프라이머리 MySQL 서버가 Ready임을 보장해 복제를 시작할 수 있게 해요.
기존 데이터 복제 (#cloning-existing-data)
일반적으로 새 파드가 복제본으로 집합에 합류할 때 프라이머리 MySQL 서버에 이미 데이터가 있을 수 있다고 가정해야 해요. 또한 복제 로그가 처음부터 끝까지 완전히 백업되지 않을 수도 있다고 가정해야 해요. 이러한 보수적인 가정은 실행 중인 StatefulSet이 초기 크기에 고정되지 않고 시간이 지남에 따라 확장·축소할 수 있게 하는 핵심이에요.
clone-mysql이라는 두 번째 init 컨테이너는 복제본 파드가 빈 PersistentVolume에서 처음 시작할 때 복제 작업을 수행해요. 즉 다른 실행 중인 파드에서 모든 기존 데이터를 복사해 로컬 상태가 프라이머리 서버에서 복제을 시작하기에 충분히 일관되게 해요.
MySQL 자체는 이것을 수행하는 메커니즘을 제공하지 않으므로 예시는 Percona XtraBackup이라는 인기 있는 오픈소스 도구를 사용해요. 복제 중에 소스 MySQL 서버는 성능이 저하될 수 있어요. 프라이머리 MySQL 서버에 대한 영향을 최소화하기 위해 스크립트는 각 파드가 서수 인덱스가 하나 낮은 파드에서 복제하도록 지시해요. StatefulSet 컨트롤러가 항상 파드 N이 Ready가 된 후에 파드 N+1을 시작하기 때문에 이것이 동작해요.
복제 시작 (#starting-replication)
init 컨테이너가 성공적으로 완료된 후 일반 컨테이너가 실행돼요. MySQL 파드는 실제 mysqld 서버를 실행하는 mysql 컨테이너와 사이드카로 작동하는 xtrabackup 컨테이너로 구성돼요.
xtrabackup 사이드카는 복제된 데이터 파일을 살펴보고 복제본에서 MySQL 복제를 초기화하는 것이 필요한지 결정해요. 필요하면 mysqld가 준비될 때까지 기다린 다음 XtraBackup 복제 파일에서 추출된 복제 매개변수로 CHANGE MASTER TO와 START SLAVE 명령을 실행해요.
복제본이 복제를 시작하면 자신의 프라이머리 MySQL 서버를 기억하고 서버가 재시작되거나 연결이 끊어지면 자동으로 다시 연결해요. 또한 복제본은 안정적인 DNS 이름(mysql-0.mysql)에서 프라이머리 서버를 찾으므로, 재스케줄링으로 인해 새 파드 IP를 얻더라도 자동으로 프라이머리 서버를 찾아요.
마지막으로 복제를 시작한 후 xtrabackup 컨테이너는 데이터 복제를 요청하는 다른 파드의 연결을 수신해요. 이 서버는 StatefulSet이 확장되거나 다음 파드가 PersistentVolumeClaim을 잃고 복제를 다시 해야 하는 경우를 대비해 무기한 유지돼요.
클라이언트 트래픽 보내기 (Sending client traffic)
mysql:5.7 이미지로 임시 컨테이너를 실행하고 mysql 클라이언트 바이너리를 실행해 프라이머리 MySQL 서버(호스트네임 mysql-0.mysql)에 테스트 쿼리를 보낼 수 있어요.
kubectl run mysql-client --image=mysql:5.7 -i --rm --restart=Never --\
mysql -h mysql-0.mysql <<EOF
CREATE DATABASE test;
CREATE TABLE test.messages (message VARCHAR(250));
INSERT INTO test.messages VALUES ('hello');
EOF
mysql-read 호스트네임을 사용해 Ready를 보고하는 어떤 서버에든 테스트 쿼리를 보내요:
kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never --\
mysql -h mysql-read -e "SELECT * FROM test.messages"
다음과 같은 출력을 얻을 수 있어요:
Waiting for pod default/mysql-client to be running, status is Pending, pod ready: false
+---------+
| message |
+---------+
| hello |
+---------+
pod "mysql-client" deleted
mysql-read Service가 서버 간에 연결을 분산한다는 것을 입증하려면 루프에서 SELECT @@server_id를 실행할 수 있어요:
kubectl run mysql-client-loop --image=mysql:5.7 -i -t --rm --restart=Never --\
bash -ic "while sleep 1; do mysql -h mysql-read -e 'SELECT @@server_id,NOW()'; done"
각 연결 시도마다 다른 엔드포인트가 선택될 수 있으므로 보고되는 @@server_id가 무작위로 변경되는 것을 볼 수 있어요:
+-------------+---------------------+
| @@server_id | NOW() |
+-------------+---------------------+
| 100 | 2006-01-02 15:04:05 |
+-------------+---------------------+
+-------------+---------------------+
| @@server_id | NOW() |
+-------------+---------------------+
| 102 | 2006-01-02 15:04:06 |
+-------------+---------------------+
+-------------+---------------------+
| @@server_id | NOW() |
+-------------+---------------------+
| 101 | 2006-01-02 15:04:07 |
+-------------+---------------------+
루프를 멈추고 싶을 때 Ctrl+C를 누를 수 있지만, 다음 단계의 효과를 볼 수 있도록 다른 창에서 계속 실행하는 것이 유용해요.
파드와 노드 실패 시뮬레이션 (Simulate Pod and Node failure)
단일 서버 대신 복제본 풀에서 읽는 것의 향상된 가용성을 입증하려면 파드를 Ready 상태에서 강제로 빼내는 동안 위의 SELECT @@server_id 루프를 계속 실행하세요.
Readiness 프로브 깨기 (#break-the-readiness-probe)
mysql 컨테이너의 readiness 프로브는 서버가 떠 있고 쿼리를 실행할 수 있는지 확인하기 위해 mysql -h 127.0.0.1 -e 'SELECT 1' 명령을 실행해요.
이 readiness 프로브를 실패하게 만드는 한 가지 방법은 그 명령을 깨뜨리는 것이에요:
kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql /usr/bin/mysql.off
이것은 파드 mysql-2의 실제 컨테이너 파일시스템에 들어가 mysql 명령의 이름을 바꿔 readiness 프로브가 그것을 찾을 수 없게 해요. 몇 초 후 파드가 컨테이너 중 하나가 Ready가 아니라고 보고해야 하며, 이는 다음을 실행해 확인할 수 있어요:
kubectl get pod mysql-2
READY 열에서 1/2를 찾아요:
NAME READY STATUS RESTARTS AGE
mysql-2 1/2 Running 0 3m
이 시점에 SELECT @@server_id 루프가 계속 실행되는 것을 볼 수 있지만, 더 이상 102를 보고하지 않아요. init-mysql 스크립트가 server-id를 100 + $ordinal로 정의했음을 기억하세요. 따라서 서버 ID 102는 파드 mysql-2에 해당해요.
이제 파드를 수리하면 몇 초 후 루프 출력에 다시 나타날 거예요:
kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql.off /usr/bin/mysql
파드 삭제 (#delete-pods)
StatefulSet은 또한 파드가 삭제되면 다시 만들어요. 이는 ReplicaSet이 상태 없는 파드에 대해 하는 것과 유사해요.
kubectl delete pod mysql-2
StatefulSet 컨트롤러는 더 이상 mysql-2 파드가 존재하지 않음을 알아차리고 같은 이름과 같은 PersistentVolumeClaim에 연결된 새 파드를 만들어요. 잠시 동안 루프 출력에서 서버 ID 102가 사라졌다가 저절로 돌아오는 것을 볼 수 있어요.
노드 drain (#drain-a-node)
Kubernetes 클러스터에 여러 노드가 있다면 drain을 발행해 노드 다운타임(노드 업그레이드 시 같은 경우)을 시뮬레이션할 수 있어요.
먼저 MySQL 파드 중 하나가 어떤 노드에 있는지 결정해요:
kubectl get pod mysql-2 -o wide
노드 이름이 마지막 열에 나타나야 해요:
NAME READY STATUS RESTARTS AGE IP NODE
mysql-2 2/2 Running 0 15m 10.244.5.27 kubernetes-node-9l2t
그런 다음 다음 명령을 실행해 노드를 drain해요. 이 명령은 노드를 cordon해서 새 파드가 그곳에 스케줄링되지 못하게 하고 기존 파드를 축출해요. <node-name>을 마지막 단계에서 찾은 노드의 이름으로 바꿔요.
주의: drain 명령은 클러스터의 다른 워크로드에 영향을 줄 수 있어요. 이 실험을 격리된 테스트 환경에서 수행하는 것을 고려하세요.
# See above advice about impact on other workloads
kubectl drain <node-name> --force --delete-emptydir-data --ignore-daemonsets
이제 파드가 다른 노드에 다시 스케줄링되는 것을 볼 수 있어요:
kubectl get pod mysql-2 -o wide --watch
다음과 같이 보여야 해요:
NAME READY STATUS RESTARTS AGE IP NODE
mysql-2 2/2 Terminating 0 15m 10.244.1.56 kubernetes-node-9l2t
[...]
mysql-2 0/2 Pending 0 0s <none> kubernetes-node-fjlm
mysql-2 0/2 Init:0/2 0 0s <none> kubernetes-node-fjlm
mysql-2 0/2 Init:1/2 0 20s 10.244.5.32 kubernetes-node-fjlm
mysql-2 0/2 PodInitializing 0 21s 10.244.5.32 kubernetes-node-fjlm
mysql-2 1/2 Running 0 22s 10.244.5.32 kubernetes-node-fjlm
mysql-2 2/2 Running 0 30s 10.244.5.32 kubernetes-node-fjlm
그리고 다시 SELECT @@server_id 루프 출력에서 서버 ID 102가 잠시 사라졌다가 돌아오는 것을 볼 수 있어요.
이제 노드를 uncordon해 정상 상태로 되돌려요:
kubectl uncordon <node-name>
복제본 수 확장 (Scaling the number of replicas)
MySQL 복제를 사용할 때 복제본을 추가해 읽기 쿼리 용량을 확장할 수 있어요. StatefulSet의 경우 단일 명령으로 이를 달성할 수 있어요:
kubectl scale statefulset mysql --replicas=5
새 파드가 올라오는 것을 보려면 다음을 실행해요:
kubectl get pods -l app=mysql --watch
그것이 올라오면 SELECT @@server_id 루프 출력에 서버 ID 103과 104가 나타나기 시작하는 것을 볼 수 있어요.
또한 이 새 서버가 그것들이 존재하기 전에 추가한 데이터를 가지고 있는지 확인할 수 있어요:
kubectl run mysql-client --image=mysql:5.7 -i -t --rm --restart=Never --\
mysql -h mysql-3.mysql -e "SELECT * FROM test.messages"
Waiting for pod default/mysql-client to be running, status is Pending, pod ready: false
+---------+
| message |
+---------+
| hello |
+---------+
pod "mysql-client" deleted
축소도 원활해요:
kubectl scale statefulset mysql --replicas=3
확장하면 새 PersistentVolumeClaim이 자동으로 생성되지만, 축소하면 이러한 PVC가 자동으로 삭제되지 않아요.
이것은 스케일 업을 더 빠르게 하기 위해 초기화된 PVC를 유지하거나 삭제하기 전에 데이터를 추출할 선택을 제공해요.
이것은 다음을 실행해 볼 수 있어요:
kubectl get pvc -l app=mysql
StatefulSet을 3으로 축소했음에도 5개의 PVC가 모두 여전히 존재함을 보여줘요:
NAME STATUS VOLUME CAPACITY ACCESSMODES AGE
data-mysql-0 Bound pvc-8acbf5dc-b103-11e6-93fa-42010a800002 10Gi RWO 20m
data-mysql-1 Bound pvc-8ad39820-b103-11e6-93fa-42010a800002 10Gi RWO 20m
data-mysql-2 Bound pvc-8ad69a6d-b103-11e6-93fa-42010a800002 10Gi RWO 20m
data-mysql-3 Bound pvc-50043c45-b1c5-11e6-93fa-42010a800002 10Gi RWO 2m
data-mysql-4 Bound pvc-500a9957-b1c5-11e6-93fa-42010a800002 10Gi RWO 2m
추가 PVC를 재사용할 의도가 없다면 삭제할 수 있어요:
kubectl delete pvc data-mysql-3
kubectl delete pvc data-mysql-4
정리하기 (Cleaning up)
- 그 터미널에서 Ctrl+C를 눌러 SELECT @@server_id 루프를 취소하거나 다른 터미널에서 다음을 실행해요.
- StatefulSet을 삭제해요. 이것은 또한 파드 종료를 시작해요.
- 파드가 사라지는지 확인해요. 그것들은 종료를 마치는 데 시간이 걸릴 수 있어요. 위가 반환되면 파드가 종료되었음을 알 수 있어요.
- ConfigMap, Services, PersistentVolumeClaims를 삭제해요.
- PersistentVolume을 수동으로 프로비저닝했다면 그것을 삭제하고 기본 리소스를 해제하는 것도 수동으로 해야 해요. 동적 프로비저너를 사용했다면 PersistentVolumeClaim을 삭제했을 때 PersistentVolume을 자동으로 삭제해요. 일부 동적 프로비저너(예: EBS, PD용)는 PersistentVolume을 삭제할 때 기본 리소스도 해제해요.
다음 단계 (What's next)
- StatefulSet 확장에 대해 더 배우기.
- StatefulSet 디버깅에 대해 더 배우기.
- StatefulSet 삭제에 대해 더 배우기.
- StatefulSet 파드 강제 삭제에 대해 더 배우기.
- 다른 유상태 애플리케이션 예시를 위해 Helm Charts 리포지토리를 살펴봐요.