해당 문서의 쿠버네티스 버전: v1.28

Kubernetes v1.28 문서는 더 이상 적극적으로 관리되지 않음. 현재 보고있는 문서는 정적 스냅샷임. 최신 문서를 위해서는, 다음을 참고. 최신 버전.

복제 스테이트풀 애플리케이션 실행하기

이 페이지에서는 스테이트풀셋(StatefulSet) 으로 복제 스테이트풀 애플리케이션을 실행하는 방법에 대해 소개한다. 이 애플리케이션은 복제 MySQL 데이터베이스이다. 이 예제의 토폴로지는 단일 주 서버와 여러 복제 서버로 이루어져있으며, row-based 비동기 복제 방식을 사용한다.

시작하기 전에

  • 쿠버네티스 클러스터가 필요하고, kubectl 커맨드-라인 툴이 클러스터와 통신할 수 있도록 설정되어 있어야 한다. 이 튜토리얼은 컨트롤 플레인 호스트가 아닌 노드가 적어도 2개 포함된 클러스터에서 실행하는 것을 추천한다. 만약, 아직 클러스터를 가지고 있지 않다면, minikube를 사용해서 생성하거나 다음 쿠버네티스 플레이그라운드 중 하나를 사용할 수 있다.

    버전 확인을 위해서, 다음 커맨드를 실행 kubectl version.
  • 여기에서 사용된 퍼시스턴트볼륨클레임을 만족하기 위하여, 기본 스토리지클래스가 설정되어 있는 상태의 동적 퍼시스턴트볼륨 프로비저너, 또는 정적으로(statically) 프로비저닝된 퍼시스턴트볼륨이 필요하다.

  • 이 튜토리얼은 퍼시스턴트볼륨 그리고 스테이트풀셋, 파드, 서비스, 컨피그맵(ConfigMap)와 같은 핵심 개념들에 대해 알고 있다고 가정한다.
  • MySQL에 대한 지식이 있으면 도움이 되지만, 이 튜토리얼은 다른 시스템을 활용하였을 때도 도움이 되는 일반적인 패턴을 다루는데 중점을 둔다.
  • default 네임스페이스를 사용하거나, 다른 오브젝트들과 충돌이 나지 않는 다른 네임스페이스를 사용한다.

목적

  • 스테이트풀셋을 이용한 복제 MySQL 토폴로지를 배포한다.
  • MySQL 클라이언트에게 트래픽을 보낸다.
  • 다운타임에 대한 저항력을 관찰한다.
  • 스테이트풀셋을 확장/축소한다.

MySQL 배포하기

MySQL 디플로이먼트 예시는 컨피그맵과, 2개의 서비스, 그리고 스테이트풀셋으로 구성되어 있다.

컨피그맵 생성하기

다음 YAML 설정 파일로부터 컨피그맵을 생성한다.

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  labels:
    app: mysql
    app.kubernetes.io/name: mysql
data:
  primary.cnf: |
    # Primary에만 이 구성을 적용한다.
    [mysqld]
    log-bin        
  replica.cnf: |
    # 레플리카에만 이 구성을 적용한다.
    [mysqld]
    super-read-only        

kubectl apply -f https://k8s.io/examples/application/mysql/mysql-configmap.yaml

이 컨피그맵은 당신이 독립적으로 주 MySQL 서버와 레플리카들의 설정을 컨트롤할 수 있도록 my.cnf 을 오버라이드한다. 이 경우에는, 주 서버는 복제 로그를 레플리카들에게 제공하고 레플리카들은 복제를 통한 쓰기가 아닌 다른 쓰기들은 거부하도록 할 것이다.

컨피그맵 자체가 다른 파드들에 서로 다른 설정 영역이 적용되도록 하는 것이 아니다. 스테이트풀셋 컨트롤러가 제공해주는 정보에 따라서, 각 파드들은 초기화되면서 설정 영역을 참조할지 결정한다.

서비스 생성하기

다음 YAML 설정 파일로부터 서비스를 생성한다.

# 스테이트풀셋 멤버의 안정적인 DNS 엔트리를 위한 헤드리스 서비스.
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
---
# 읽기용 MySQL 인스턴스에 연결하기 위한 클라이언트 서비스.
# 쓰기용은 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

헤드리스 서비스는 스테이트풀셋 컨트롤러가 집합의 일부분인 파드들을 위해 생성한 DNS 엔트리들(entries)의 위한 거점이 된다. 헤드리스 서비스의 이름이 mysql이므로, 파드들은 같은 쿠버네티스 클러스터나 네임스페이스에 존재하는 다른 파드들에게 <pod-name>.mysql라는 이름으로 접근될 수 있다.

mysql-read라고 불리우는 클라이언트 서비스는 고유의 클러스터 IP를 가지며, Ready 상태인 모든 MySQL 파드들에게 커넥션을 분배하는 일반적인 서비스이다. 잠재적인 엔드포인트들의 집합은 주 MySQL 서버와 해당 레플리카들을 포함한다.

오직 읽기 쿼리들만 로드-밸런싱된 클라이언트 서비스를 이용할 수 있다는 사실에 주목하자. 하나의 주 MySQL 서버만이 존재하기 떄문에, 클라이언트들은 쓰기 작업을 실행하기 위해서 주 MySQL 파드에 (헤드리스 서비스 안에 존재하는 DNS 엔트리를 통해) 직접 접근해야 한다.

스테이트풀셋 생성하기

마지막으로, 다음 YAML 설정 파일로부터 스테이트풀셋을 생성한다.

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
          # 파드의 원래 인덱스에서 mysql server-id를 생성.
          [[ $HOSTNAME =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          echo [mysqld] > /mnt/conf.d/server-id.cnf
          # 예약된 server-id=0 값을 피하기 위해 오프셋 추가.
          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
          # config-map에서 emptyDir로 적당한 conf.d 파일들을 복사.
          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
          # 데이터가 이미 존재하면 복제 생략. 
          [[ -d /var/lib/mysql/mysql ]] && exit 0
          # Primary에 복제 생략(ordinal index 0).
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          [[ $ordinal -eq 0 ]] && exit 0
          # 이전 피어(peer)에서 데이터 복제.
          ncat --recv-only mysql-$(($ordinal-1)).mysql 3307 | xbstream -x -C /var/lib/mysql
          # 백업 준비.
          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:
            # TCP 상에서 쿼리를 실행할 수 있는지 확인(skip-networking은 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

          # 복제된 데이터의 binlog 위치를 확인.
          if [[ -f xtrabackup_slave_info && "x$(<xtrabackup_slave_info)" != "x" ]]; then
            # XtraBackup은 기존 레플리카에서 복제하기 때문에
            # 일부 "CHANGE MASTER TO" 쿼리는 이미 생성했음. (테일링 세미콜론을 제거해야 한다!)
            cat xtrabackup_slave_info | sed -E 's/;$//g' > change_master_to.sql.in
            # 이 경우에는 xtrabackup_binlog_info는 무시(필요없음).
            rm -f xtrabackup_slave_info xtrabackup_binlog_info
          elif [[ -f xtrabackup_binlog_info ]]; then
            # Primary로부터 직접 복제함. binlog 위치를 파싱.
            [[ `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

          # 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
            # 컨테이너가 다시 시작하는 경우, 이 작업을 한번만 시도한다.
            mv change_master_to.sql.in change_master_to.sql.orig
          fi

          # 피어가 요청할 때 서버를 시작하여 백업을 보냄.
          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

잠시 뒤에, 3개의 파드들이 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

Ctrl+C를 입력하여 watch를 종료하자.

해당 메니페스트에는 스테이트풀셋의 일부분인 스테이트풀 파드들을 관리하기 위한 다양한 기법들이 적용되어 있다. 다음 섹션에서는 스테트풀셋이 파드들을 생성할 때 일어나는 일들을 이해할 수 있도록 일부 기법들을 강조하여 설명한다.

스테이트풀 파드 초기화 이해하기

스테이트풀셋 컨트롤러는 파드들의 인덱스에 따라 순차적으로 시작시킨다. 컨트롤러는 다음 파드 생성 이전에 각 파드가 Ready 상태가 되었다고 알려줄 때까지 기다린다.

추가적으로, 컨트롤러는 각 파드들에게 <스테이트풀셋 이름>-<순차적 인덱스> 형태의 고유하고 안정적인 이름을 부여하는데, 결과적으로 파드들은 mysql-0, mysql-1, 그리고 mysql-2 라는 이름을 가지게 된다.

스테이트풀셋 매니페스트의 파드 템플릿은 해당 속성들을 통해 순차적인 MySQL 복제의 시작을 수행한다.

설정 생성하기

파드 스펙의 컨테이너를 시작하기 전에, 파드는 순서가 정의되어 있는 초기화 컨테이너들을 먼저 실행시킨다.

init-mysql라는 이름의 첫 번째 초기화 컨테이너는, 인덱스에 따라 특별한 MySQL 설정 파일을 생성한다.

스크립트는 인덱스를 hostname 명령으로 반환되는 파드 이름의 마지막 부분에서 추출하여 결정한다. 그리고 인덱스(이미 사용된 값들을 피하기 위한 오프셋 숫자와 함께)를 MySQL의 conf.d 디렉토리의 server-id.cnf 파일에 저장한다. 이는 스테이트풀셋에게서 제공된 고유하고, 안정적인 신원을 같은 속성을 필요로 하는 MySQL 서버 ID의 형태로 바꾸어준다.

또한 init-mysql 컨테이너의 스크립트는 컨피그맵을 conf.d로 복사하여, primary.cnf 또는 replica.cnf을 적용한다. 이 예제의 토폴로지가 하나의 주 MySQL 서버와 일정 수의 레플리카들로 이루어져 있기 때문에, 스크립트는 0 인덱스를 주 서버로, 그리고 나머지 값들은 레플리카로 지정한다. 스테이트풀셋 컨트롤러의 디플로이먼트와 스케일링 보증과 합쳐지면, 복제를 위한 레플리카들을 생성하기 전에 주 MySQL 서버가 Ready 상태가 되도록 보장할 수 있다.

기존 데이터 복제(cloning)

일반적으로, 레플리카에 새로운 파드가 추가되는 경우, 주 MySQL 서버가 이미 데이터를 가지고 있다고 가정해야 한다. 또한 복제 로그가 첫 시작점부터의 로그들을 다 가지고 있지는 않을 수 있다고 가정해야 한다. 이러한 보수적인 가정들은 스테이트풀셋이 초기 크기로 고정되어 있는 것보다, 시간에 따라 확장/축소하게 할 수 있도록 하는 중요한 열쇠가 된다.

clone-mysql라는 이름의 두 번째 초기화 컨테이너는, 퍼시스턴트볼륨에서 처음 초기화된 레플리카 파드에 복제 작업을 수행한다. 이 말은 다른 실행 중인 파드로부터 모든 데이터들을 복제하기 때문에, 로컬의 상태가 충분히 일관성을 유지하고 있어서 주 서버에서부터 복제를 시작할 수 있다는 의미이다.

MySQL 자체가 이러한 메커니즘을 제공해주지는 않기 때문에, 이 예제에서는 XtraBackup이라는 유명한 오픈소스 도구를 사용한다. 복제 중에는, 복제 대상 MySQL 서버가 성능 저하를 겪을 수 있다. 주 MySQL 서버의 이러한 충격을 최소화하기 위해, 스크립트는 각 파드가 자신의 인덱스보다 하나 작은 파드로부터 복제하도록 지시한다. 이것이 정상적으로 동작하는 이유는 스테이트풀셋 컨트롤러가 파드 N+1을 실행하기 전에 항상 파드 N이 Ready 상태라는 것을 보장하기 때문이다.

복제(replication) 시작하기

초기화 컨테이너들의 성공적으로 완료되면, 일반적인 컨테이너가 실행된다. MySQL 파드는 mysqld 서버를 구동하는 mysql 컨테이너로 구성되어 있으며, xtrabackup 컨테이너는 사이드카(sidecar)로서 작동한다.

xtrabackup 사이드카는 복제된 데이터 파일들을 보고 레플리카에 MySQL 복제를 시작해야 할 필요가 있는지 결정한다. 만약 그렇다면, mysqld이 준비될 때까지 기다린 후 CHANGE MASTER TO, 그리고 START SLAVE를 XtraBackup 복제(clone) 파일들에서 추출한 복제(replication) 파라미터들과 함께 실행시킨다.

레플리카가 복제를 시작하면, 먼저 주 MySQL 서버를 기억하고, 서버가 재시작되거나 커넥션이 끊어지면 다시 연결한다. 또한 레플리카들은 주 서버를 안정된 DNS 이름 (mysql-0.mysql)으로 찾기 때문에, 주 서버가 리스케쥴링에 의해 새로운 파드 IP를 받아도 주 서버를 자동으로 찾는다.

마지막으로, 복제를 시작한 후에는, xtrabackup 컨테이너는 데이터 복제를 요청하는 다른 파드들의 커넥션을 리스닝한다. 이 서버는 스테이트풀셋이 확장하거나, 다음 파드가 퍼시스턴트볼륨클레임을 잃어서 다시 복제를 수행해햐 할 경우를 대비하여 독립적으로 존재해야 한다.

클라이언트 트래픽 보내기

임시 컨테이너를 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 서비스가 커넥션을 서버들에게 분배하고 있다는 사실을 확인하기 위해서, 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를 입력하면 되지만, 다음 단계에서의 영향을 보기 위해서 다른 창에 계속 실행시켜 놓는 것이 좋다.

노드와 파드 실패 시뮬레이션하기

단일 서버 대비 레플리카들의 풀에 의한 읽기의 가용성 향상을 시연하기 위해, 파드를 Ready 상태로 강제하면서 SELECT @@server_id 루프를 돌리자.

준비성 프로브 고장내기

mysql 컨테이너의 준비성 프로브는 쿼리를 실행할 수 있는지를 확인하기 위해 mysql -h 127.0.0.1 -e 'SELECT 1' 명령을 실행한다.

준비성 프로브를 강제로 실패시키는 방법은 해당 명령을 고장내는 것이다.

kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql /usr/bin/mysql.off

이것은 mysql-2 파드의 실제 컨테이너의 파일시스템에 접근해서 mysql 명령의 이름을 변경하여, 준비성 프로브가 찾을 수 없도록 한다. 몇 초 뒤에, 파드는 컨터이너 중 하나가 준비되지 않았다고 보고할 것이다. 다음 명령을 실행시켜서 확인할 수 있다.

kubectl get pod mysql-2

READY 항목의 1/2를 확인하자.

NAME      READY     STATUS    RESTARTS   AGE
mysql-2   1/2       Running   0          3m

이 시점에서, 102를 보고하지 않지만, 계속해서 SELECT @@server_id 루프가 실행되는 것을 확인할 수 있을 것이다. init-mysql 스크립트에서 server-id100 + $ordinal로 정의했기 때문에, 서버 ID 102mysql-2 파드에 대응된다는 사실을 상기하자.

이제 파드를 고치면 몇 초 뒤에 루프 출력에 나타날 것이다.

kubectl exec mysql-2 -c mysql -- mv /usr/bin/mysql.off /usr/bin/mysql

파드 삭제하기

레플리카셋이 스테이트리스 파드들에게 하는 것처럼, 스테이트풀셋 또한 파드들이 삭제되면 파드들을 다시 생성한다.

kubectl delete pod mysql-2

스테이트풀셋 컨트롤러는 mysql-2 파드가 존재하지 않는 것을 인지하고, 같은 이름으로 새로운 파드를 생성하고 같은 퍼시스턴트볼륨클레임에 연결한다. 잠시동안 서버 ID 102가 루프 출력에서 사라지고 다시 나타나는 것을 확인할 수 있을 것이다.

노드 드레인하기

만약 당신의 쿠버네티스 클러스터가 여러 노드들을 가지고 있으면, 드레인을 사용하여 노드 다운타임을 시뮬레이션할 수 있다.

먼저 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

그 후에, 다음 명령을 실행하여 노드를 드레인한다. 그러면 새로운 파드가 스케줄되지 않게 방지하고(cordon), 기존의 파드들을 추방(evict)한다. <node-name>를 이전 단계에서 찾았던 노드의 이름으로 바꾸자.

# 위에 명시된 다른 워크로드들이 받는 영향에 대한 주의사항을 확인한다.
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

그리고, 서버 ID 102SELECT @@server_id 루프 출력에서 잠시 사라진 후 다시 보이는 것을 확인할 수 있을 것이다.

이제 노드의 스케줄 방지를 다시 해제(uncordon)해서 정상으로 돌아가도록 조치한다.

kubectl uncordon <node-name>

레플리카 스케일링하기

MySQL 레플리케이션을 사용하면, 레플리카를 추가하는 것으로 읽기 쿼리 용량을 키울 수 있다. 스테이트풀셋을 사용하면, 단 한 줄의 명령으로 달성할 수 있다.

kubectl scale statefulset mysql  --replicas=5

명령을 실행시켜서 새로운 파드들이 올라오는 것을 관찰하자.

kubectl get pods -l app=mysql --watch

파드들이 올라오면, SELECT @@server_id 루프 출력에 서버 ID 103104가 나타나기 시작할 것이다.

그리고 해당 파드들이 존재하기 전에 추가된 데이터들이 해당 새 서버들에게도 존재하는 것을 확인할 수 있다.

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

다음 명령을 실행하여 확인할 수 있다.

kubectl get pvc -l app=mysql

스테이트풀셋을 3으로 축소했음에도 PVC 5개가 아직 남아있음을 보여준다.

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

정리하기

  1. SELECT @@server_id 루프를 끝내기 위해, 터미널에 Ctrl+C를 입력하거나, 해당 명령을 다른 터미널에서 실행시키자.

    kubectl delete pod mysql-client-loop --now
    
  2. 스테이트풀셋을 삭제한다. 이것은 파드들 또한 삭제할 것이다.

    kubectl delete statefulset mysql
    
  3. 파드들의 삭제를 확인한다. 삭제가 완료되기까지 시간이 걸릴 수 있다.

    kubectl get pods -l app=mysql
    

    위와 같은 메세지가 나타나면 파드들이 삭제되었다는 것을 알 수 있다.

    No resources found.
    
  4. 컨피그맵, 서비스, 그리고 퍼시스턴트볼륨클레임들을 삭제한다.

    kubectl delete configmap,service,pvc -l app=mysql
    
  5. 만약 수동으로 퍼시스턴스볼륨들을 프로비저닝했다면, 수동으로 삭제하면서, 그 밑에 존재하는 리소스들을 또한 삭제해야 한다. 만약 동적 프로비저너를 사용했다면, 당신이 퍼시스턴트볼륨클레임으로 삭제하면 자동으로 퍼시스턴트볼륨을 삭제한다. 일부 (EBS나 PD와 같은) 동적 프로비저너들은 퍼시스턴트볼륨을 삭제 하면 그 뒤에 존재하는 리소스들도 삭제한다.

다음 내용

최종 수정 June 07, 2023 at 5:58 PM PST: [ko] Update links in dev-1.26-ko.1 (00461e0912)