Notice
Recent Posts
Recent Comments
«   2025/08   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31
Archives
Today
Total
관리 메뉴

ITFragile

[Kubernetes] 02. DB StatefulSet으로 이중화 구성 본문

Project

[Kubernetes] 02. DB StatefulSet으로 이중화 구성

경요 2023. 3. 21. 10:10

 

구성 목표

1. DB 배포 방식 : StatefulSet 스테이트풀셋
- 파드들의 순서 및 고유성을 보장하기 때문에 상태가 있는 DB같은 경우 스테이트풀셋으로 배포한다.

이때 클러스터 IP가 없는 헤드리스 서비스(IP주소를 할당하지 않는 방식)를 사용
파드 이름이 고정이고 serviceName 설정을 해서 특정 파드를 지정해서 접속 가능하도록 함.

2. DB 이중화 : Master, Slave로 운영
- mysql 설정 파일 컨피그맵 : Master, Slave 독립적으로 제어 (Slave는 읽기 전용)

3. 서비스 : 각 파드에 따로 접근하기 위해 헤드리스 서비스 이용
- 단, DB를 외부에서 읽기 위한 비헤드리스 서비스도 추가하여,

db-read로 접근시 로드밸런서를 통해 아무 노드나 접근 가능하도록 함

4. 스토리지 : nfs 이용

- db별로 고유한 pv,pvc를 만들어 데이터를 관리
- 스테이트풀셋으로 만들었기 때문에 지우고 다시 만든다 해도 볼륨은 남아있고, 다시 생성시에도 그 볼륨에 다시 연결됨

 

 


 

1. nfs를 이용한 스토리지 클래스 생성

 

github 에서 스토리지 패키지 설치

# control-plane01
git clone https://github.com/kubernetes-sigs/nfs-subdir-external-provisioner.git

 

2. nfs-kernel-server 패키지 설치

# control-plane01

sudo apt update
sudo apt install nfs-kernel-server

 

3. 공유 디렉토리 생성 후 권한 부여

sudo mkdir /dynamic # 디렉토리 생성

vim /etc/exports

/dynamic *(rw,sync,no_subtree_check,no_root_squash)
# 동적 볼륨 사용하려면 특수 권한이 필요하므로 루트 권한 넣어줌

4. 마운트 설정

sudo exportfs -r

 

5. 마운트 내역 확인

sudo showmount -e

 

6. 노드에 nfs-common 패키지 설치

# node01,02,03

sudo apt update
sudo apt install nfs-common -y

 

7. 스토리지 연결

7-1. rbac.yaml 실행

cd ~/nfs-subdir-external-provisioner/deploy
kubectl apply -f rbac.yaml  # 사용자 계정/권한 설정

 

7-2. deployment.yaml 실행

kubectl apply -f deployment.yaml # 스토리지 클래스를 관리해줄 파드 실행

# d.spec.container.env 수정
NFS_SERVER - 서버 주소
NFS_PATH  - 공유 디렉토리 경로

# d.spec.volumes.nfs 수정
server - 서버 주소
path - 공유 디렉토리 경로

 

7-3. class.yaml 실행

kubectl apply -f class.yaml
# pvc가 스토리지클래스에 요청해서 자동으로 pv를 만들어줌

 

7-4. test-claim.yaml 실행 (동적 볼륨이 잘 만들어지는지 확인차 실행)

 

7-5. 연결 확인

ls /dynamic

동적볼륨이 잘 생성되었으므로 실행한 pvc는 삭제하면 된다.

 

 

2. 컨피그맵 생성

환경변수의 값을 평문으로 정의하는 컨피그맵을 생성한다.

kubectl apply -f cm.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: mysql
  labels:
    app: mydb
data:
  master.cnf: |
    [mysqld]
    log-bin  # 바이너리 로그 파일이 생성될 path. path 미지정시 /var/lib/mysql에 생성
  slave.cnf: |
    [mysqld]
    super-read-only # read only 권한 부여

 

3. 서비스 생성

이때 클러스터 IP가 없는 헤드리스 서비스(IP주소를 할당하지 않는 방식)를 사용 -> 내부용

 

클러스터 ip : ip주소가 할당되어 ip로 접속시 알아서 파드에 분산되어 접속
헤드리스 서비스 : ip주소 할당하지 않고 statefulset의 파드 이름마다 servicename이 부여되어 특정 파드에 접속할 수 있음 -> 지정한 DB에 접속 가능

단, DB를 외부에서 읽기 위한 비헤드리스 서비스를 추가하여 db-read로 접근시 로드밸런서를 통해

아무 노드나 접근 가능하도록 함

# kubectl apply -f sts-db.yaml

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

 

4. StatefulSet 생성

파드들의 순서 및 고유성을 보장하기 때문에 상태가 있는 DB같은 경우 보통 스테이트풀셋으로 배포함

참고 자료 : Run a Replicated Stateful Application | Kubernetes

# kubectl apply -f sts-db.yaml

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: mydb
  labels:
    app: mydb
spec:
  selector:
    matchLabels:
      app: mydb
  serviceName: db   #헤드리스 서비스 이름과 연결
  replicas: 2	# 기본적으로 Master 1대에 Slave 여러대로 만들어짐
  template:
    metadata:
      name: mydb-pod
      labels:
        app: mydb
    spec:
      initContainers:		# initcontainer 먼저 실행. 
      - name: init-mysql		# 설정파일을 가져오는 작업
        image: mysql:5.7
        command:
        - bash
        - "-c"
        - |
          set -ex
          # Generate mysql server-id from pod ordinal index. # master, slave 값 설정 다르게 함
          [[ $HOSTNAME =~ -([0-9]+)$ ]] || exit 1	 
	# `hostname` 은 hostname이라는 명령어를 실행하게 하므로 mysql 이미지에는 
	#  hostname이라는 명령어가 없어 오류 발생. 환경변수 가져오려면 $로 써야함
          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 
		# >(리다이렉트 부호) 1개일때 덮어쓰기 >>두개일때는 추가함
          # Copy appropriate conf.d files from config-map to emptyDir.
          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		# 상대방의 DB에서 데이터를 백업 해서 가져옴
        image: gcr.io/google-samples/xtrabackup:1.0	# xtrabackup : DB를 백업하는 도구
        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 mydb-$(($ordinal-1)).db.svc.cluster.local 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: myweb
        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		# 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='mydb-0.db.default.svc.cluster.local', \	#master 주소
                          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:		# 파드 하나당 pvc 하나가 따로 만들어짐
  - metadata:
      name: data
    spec:
      storageClassName: "nfs-client" 	# 생성한 스토리지 클래스 연결
      accessModes:
      - ReadWriteMany
      resources:
        requests:
	storage: 1Gi

5. 파드 실행 후 확인

kubectl get all

 

6. 파드 접속 후 확인

# Master 확인
kubectl exec mydb-0 -it -- bash
mysql -u root -p
show master status \G

create database로 DB 생성

# Slave 확인
kubectl exec mydb-1 -it -- bash
mysql -u root -p
show slave status \G

show databases 로 DB 동기화 확인
select로 읽기는 가능하나, 데이터 insert는 불가