Kubernetes/05. 컨트롤러 종류 및 설명

[쿠버네티스] k8s 스테이트풀셋을 이용한 MySQL 백업 복제본 구성

Jaden Park 2021. 6. 25. 18:55

들어가며: 전체적인 설명

MySQL 설정 파일 컨피그맵 생성

서비스 생성

고가용성을 위한 MySQL 데이터베이스 생성

MySQL 데이터베이스 확인


들어가며: 전체적인 설명

공식 MySQL이미지를 이용하여 스테이트풀셋이 관리할 파드를 2개를 생성합니다.

2개를 생성하는 이유는 데이터베이스의 백업 복제본을 제공하기 위함입니다.

당연하지만 각각의 파드는 각각의 PVC를 요청하고 다른 PV를 사용함에 따라 별도의 상태를 가질 수 있습니다.

 

MySQL 데이터베이스의 백업 복제본을 위해 xtrabackup을 이용하여 복제본을 구성하고, 첫 번째 생성되는 파드는 마스터(읽기/쓰기)로, 두 번째부터 생성되는 파드는 슬레이브(읽기 전용)로 구성됩니다.

 

마스터와 슬레이브용 설정 파일을 컨피그맵에 등록하고, 서비스는 데이터베이스 쓰기를 위해 마스터를 구별하기 위한 헤드리스 서비스를 구성하고, 데이터베이스 읽기를 위해 일반적인(비-헤드리스) 서비스를 구성하도록 하겠습니다.

 

 

xtrabackup 이란?

xtrabackup은 percona 에서 무료로 제공하는 DB 핫 백업 툴입니다.
백업 시 암호화, 압축, 증분 백업 등 많은 기능이 포함되어있어
다양한 백업 정책을 만들 수 있으며, 핫 백업으로 mysqldump보다 빠른 백업 및 복구가 가능합니다.
지원되는 DB는 mysql (5.1, 5.5, 5.6, 5.7), xtraDB 가 있습니다.


MySQL 설정 파일 컨피그맵 생성

 

//mydb-cm-mysql.yaml

---
apiVersion: v1
kind: ConfigMap
metadata:
  name: mydb-config
  labels:
    app: mydb
data:
  master.cnf: |
    [mysqld]
    log-bin
  slave.cnf: |
    [mysqld]
    super-read-only

MySQL에 사용할 컨피그맵 리소스, 데이터베이스 설정 파일입니다.

마스터와 슬레이브를 독립적으로 제어할 수 있도록 합니다.

마스터는 복제 로그를 슬레이브로 제공하도록 하는 설정이고,

슬레이브는 읽기 전용으로 쓰기를 금지하도록 하는 설정입니다.

 

마스터의 master.cnf 설정은 슬레이브가 데이터베이스를 복제하기 위한 복제 로그를 남기도록 하는 설정이고

슬레이브의 slave.cnf 파일에는 읽기 전용으로 구성하기 위한 설정입니다.

 

컨피그맵을 생성한 뒤 등록되었는지 확인해보겠습니다.

 


서비스 생성

 

 

//mydb-svc-write.yaml

---
apiVersion: v1
kind: Service
metadata:
  name: mydb
  labels:
    app: mydb
spec:
  ports:
  - name: mysql
    port: 3306
  clusterIP: None
  selector:
    app: mydb

데이터베이스 쓰기를 위한 헤드리스 서비스 입니다.

각 파드에 대해 따로 접근하기 위해 헤드리스 서비스로 정의되었고, 특히 마스터를 구별하기 위해 반드시 필요합니다.

 

 

//mydb-svc-read.yaml

---
apiVersion: v1
kind: Service
metadata:
  name: mydb-read
  labels:
    app: mydb
spec:
  ports:
  - name: mysql
    port: 3306
  selector:
    app: mydb

데이터베이스 읽기를 위한 비-헤드리스 서비스 입니다.

일반적인 Cluster IP 서비스이며, mydb-read 주소로 접근했을 때 부하 분산을 통해 아무 노드나 접근할 수 있도록 합니다. 

 

두 서비스를 생성한 뒤 재대로 생성된 것을 확인할 수 있습니다.

 


고가용성을 위한 MySQL 데이터베이스 생성

다음은 MySQL 데이터베이스의 고가용성을 위한 스테이트풀셋 리소스 정의입니다.

$ cat mydb-sts-mysql.yaml

---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mydb
spec:
  selector:
    matchLabels:
      app: mydb
  serviceName: mydb
  replicas: 2 
  template:
    metadata:
      labels:
        app: mydb
    spec:
      initContainers:
      - name: init-mysql
        image: mysql:5.7
        command:
        - bash
        - "-c"
        - |
          set -ex
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          echo [mysqld] > /mnt/conf.d/server-id.cnf
          echo server-id=$((100 + $ordinal)) >> /mnt/conf.d/server-id.cnf
          if [[ $ordinal -eq 0 ]]; then
            cp /mnt/config-map/master.cnf /mnt/conf.d/
          else
            cp /mnt/config-map/slave.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
          [[ `hostname` =~ -([0-9]+)$ ]] || exit 1
          ordinal=${BASH_REMATCH[1]}
          [[ $ordinal -eq 0 ]] && exit 0
          ncat --recv-only mydb-$(($ordinal-1)).mydb 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
        livenessProbe:
          exec:
            command: ["mysqladmin", "ping"]
          initialDelaySeconds: 30
          periodSeconds: 10
          timeoutSeconds: 5
        readinessProbe:
          exec:
            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

          if [[ -f xtrabackup_slave_info && "x$(<xtrabackup_slave_info)" != "x" ]]; then
            cat xtrabackup_slave_info | sed -E 's/;$//g' > change_master_to.sql.in
            rm -f xtrabackup_slave_info xtrabackup_binlog_info
          elif [[ -f xtrabackup_binlog_info ]]; then
            [[ `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

          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='mydb-0.mydb', \
                          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
      volumes:
      - name: conf
        emptyDir: {}
      - name: config-map
        configMap:
          name: mydb-config
  volumeClaimTemplates:
  - metadata:
      name: data
    spec:
      accessModes: ["ReadWriteOnce"]
      resources:
        requests:
          storage: 1Gi

statefulset.spec.template.initContainers 필드는 컨트롤러가 파드/컨테이너 최초 배포 시 실행할 초기화 작업입니다.

복제 작업 시 사용할 각 MySQL 서버의 ID를 지정하는 부분과, 복제 작업을 위한 초기화 작업이 포함되어 있습니다.

 

statefuleset.spec.template.containers 필드는 애플리케이션 컨테이너를 정의하는 부분이며, 하나의 파드에 컨테이너는 2개가 있습니다. MySQL 컨테이너는 MySQL 애플리케이션을 작동시킬 컨테이너이며, xtrabackup 컨테이너는 마스터-슬레이브 간에 데이터베이스의 복제를 담당하는 컨테이너입니다.

 

마지막으로 PVC 템플릿입니다. 스테이트풀셋은 PVC 템플릿을 사용하여 각 파드마다 다른 PVC를 사용할 수 있도록 합니다.

별도로 storageClass를 정의하지 않았으므로 기본 스토리지 클래스가 적용될 것 입니다. (기본 스토리지 클래스 설정이 안되어있다면 pod 가 pending 로 동작하지 않습니다.)

 

복제본 개수는 2개로 설정되었으며, 마스터 하나, 슬레이브 하나가 될 것 입니다.

 

  • initContainers(초기화 컨테이너)
    • init-mysql
      • /mnt/conf.d/server-id.cnf MySQL 설정 파일 생성
      • server-id=100+N MySQL 서버 ID 지정

 

  • clone-mysql
    • mySQL 슬레이브 파드가 처음 실행되기 전 PV가 비워져 있기 때문에, 복제 작업 수행

 

 

  • containers(애플리케이션 컨테이너)
    • mysql: MySQL 데이터베이스 애플리케이션 컨테이너
    • xtrabackup: 데이터 복제

 

 

스테이트풀셋 리소스를 생성하기 앞서 기본 스토리지를 지정해줍니다.

kubectl patch storageclasses.storage.k8s.io rook-ceph-block  -p '{"metadata":{"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'

rook-ceph-block 으로 기본스토리지 클래스를 지정했습니다. 더 자세한 내용은 이곳을 참조하시기 바랍니다.

 

 

 

MySQL 스테이트풀셋 리소스를 생성합니다.

$ kubectl create -f mydb-sts-mysql.yaml

 

 

스테이트풀셋 및 파드의 목록 및 상태를 확인해보겠습니다.

mysql-0 파드가 생성되었으며, 초기화 작업을 진행합니다.

초기화할 작업은 총 2개(init-mysql, clone-mysql)이 있습니다.

 

statefulset.spec.volumeClaimTemplates 필드에 의해 요청된 PVC를 확인해보겠습니다.

statefulset.spec.volumeClaimTemplates 필드에서 별도로 storageClass를 정의하지 않았기 때문에 기본 스토리지 클래스인 csi-ceph-block 스토리지 클래스를 사용할 것 입니다.

 

 

PVC에 의해 요청된 PV도 확인해보겠습니다.

파드의 복제본 개수가 2개이기 때문에 두 개의 PVC와 PV가 생성될 것 입니다.

 

 

 


MySQL 데이터베이스 확인

파드 주소 확인

네트워크 도구가 포함된 이미지에는 MySQL 클라이언트 도구도 포함하고 있습니다. 실행시켜 헤드리스 서비스가 적용된 레플리카셋이 실행한 파드의 주소를 살펴보겠습니다.

 

 

 

마스터 데이터베이스에 데이터베이스, 테이블 및 레코드 생성

마스터 데이터베이스에 myapp 데이터베이스를 생성하고 message 테이블을 생성합니다.

 

마스터(mydb-0) 데이터베이스 레코드 확인

myapp.message 테이블 전체를 조회해보겠습니다.

 

슬레이브(mydb-1) 데이터베이스 레코드 확인

mydb-read 서비스 주소를 이용해 조회해보겠습니다. 사실 마스터를 조회하는데 슬레이브를 조회하는지 여기선 알 수 없습니다.

 

mydb-1.mydb 슬레이브에 테이블을 조회해보겠습니다.

똑같은 레코드 인 것을 확인할 수 있습니다.

 

 

파드 스케일 아웃

여러 방법이 있지만, kubectl scale 명령으로 복제본 3개로 확장해보겠습니다.

 

PV,PVC 도 3개로 확장된 것을 확인할 수 있습니다.

 

슬레이브 mydb-2.mydb 테이블을 조회해보겠습니다.

 

파드 스케일 인

스테이브풀셋의 복제본 개수를 2개로 줄인 뒤 스케일 인 되었는지 확인해보겠습니다. 

PV와 PVC 리소스는 데이터의 안전을 보장하기 위한 조치로 불필요한 경우 수동으로 삭제해야 합니다.

 

 

 

실습 종료 리소스 삭제

kubectl delete svc mydb mydb-read
kubectl delete sts mydb
kubectl delete pv,pvc --all