Mysql Operator

유재혁·2023년 10월 19일
0

1. Kubernetes Operator

1.1 Operator란?

오퍼레이터(Operator)는 사용자 정의 리소스를 사용하여 애플리케이션 및 해당 컴포넌트를 관리하는 쿠버네티스의 소프트웨어 익스텐션이다. 오퍼레이터는 쿠버네티스 원칙, 특히 컨트롤 루프를 따른다.

1.2 Controller란?

SW에서의 컨트롤러는 보통 어딘가 쌓여있는 요청을 처리하는 장치를 얘기합니다.(device controller, spring controller 등) 쿠버네티스의 컨트롤러는 etcd를 감시하여 선언된 api에 맞게 원하는 상태로 만들어주려고 노력하는 컴포넌트입니다. 대표적으로 pod을 생성하여 etcd에 넣어놓으면 그 변경을 컨트롤러가 감지하여 컨테이너를 띄워주게 됩니다.

1.3 custom controller란?

커스텀 컨트롤러란 일반적인 pod, deployment, service가 아닌 custom resource(cr)을 처리하는 컴포넌트입니다.CR과 CRD를 사용하여 만든 커스텀 리소스는 etcd에 올라가는 구조화된 데이터일 뿐입니다. 커스텀리소스를 이용해서 원하는 동작을 하기위해서는 커스텀 컨트롤러가 필요합니다. 커스텀 리소스를 이용하여 사용자가 원하는 상태를 선언하면(etcd에 올라가면) 커스텀 컨트롤러가 그 상태를 맞추기 위해 동작하게 됩니다.

2.Mysql Operator를 사용한 Kubernetes 환경의 아키텍쳐 다이어그램

2.1 Deployment로 관리되는 Mysql Router

Mysql 라우터는 Mysql 서버들 중 작업을 실행할 서버를 고르는 상태비저장(Statless) 애플리케이션입니다. DB 인스턴스를 캐시하여 라우팅할 인스턴스를 관리합니다. Mysql 라우터는 클러스터의 스케일에 따라 Deployment의 레플리카 개수를 조절하는 것으로 수평적 확장(Horizontal scaling)할 수 있습니다.

2.2 Replicaset으로 관리되는 Mysql Server instances

Mysql 서버는 실제 DB역할을 하게 되는 서버 인스턴스의 그룹입니다. 클러스터의 복제 방식은 Single-Primary mode와 Multi-Primary mode라는 2가지 Replication 모드 중 하나를 선택할 수 있습니다. Single-Primary mode는 전통적인 Master-Slave(이제 이런 표현은 사용하면 안되지만..) 구조의 복제 방식이며, Multi-Primary mode는 Mysql 8.0 버전에 등장한 Group Replication 복제 방식을 사용해 단방향 복제가 아닌 양방향 복제를 수행할 수 있습니다.

2.3 Deployment로 관리되는 Mysql Operator

위의 Mysql 서버와 Mysql 라우터들을 관리하는 Operator입니다. Deployment로 관리되며 Operator가 InnoDBCluster, Mysqlbackup 등의 사용자 정의 리소스를 통해 각각 서버 클러스터와 서버 백업을 추상화해 관리해줍니다.

<최종적으로 아래 그림과 같은 아키텍쳐로 Kubernetes 환경을 구성 가능>

3.Mysql Operator 설치

## helm을 통한 Operator 설치
# helm repo add mysql-operator https://mysql.github.io/mysql-operator/
# helm repo update
# helm install mysql-operator mysql-operator/mysql-operator --namespace mysql-operator --create-namespace
 
root@k8s-ma-001:~# k get all -n mysql-operator
NAME                                  READY   STATUS    RESTARTS   AGE
pod/mysql-operator-7b95697fd8-tl7cq   1/1     Running   0          15h
 
NAME                     TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)    AGE
service/mysql-operator   ClusterIP   10.103.163.187   <none>        9443/TCP   15h
 
NAME                             READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/mysql-operator   1/1     1            1           15h
 
NAME                                        DESIRED   CURRENT   READY   AGE
replicaset.apps/mysql-operator-7b95697fd8   1         1         1       15h
 
 
## helm을 통한 mysql 설치
# helm install mysql mysql-operator/mysql-innodbcluster --set credentials.root.password='Password' --set tls.useSelfSigned=true --namespace mysql --create-namespace
# kubectl edit svc mysql -n mysql // NodePort로 변경 필요
# kubectl edit deployment mysql-router -n mysql // Replica 개수 1 → 2 변경
 
root@k8s-ma-001:~# k get all -n mysql
NAME                                READY   STATUS    RESTARTS   AGE
pod/mysql-0                         2/2     Running   0          14h
pod/mysql-1                         2/2     Running   0          14h
pod/mysql-2                         2/2     Running   0          14h
pod/mysql-router-7c586c78b6-ncvv7   1/1     Running   0          14h
pod/mysql-router-7c586c78b6-zs9rt   1/1     Running   0          6s
 
NAME                      TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)                                                                                                     AGE
service/mysql             NodePort    10.103.216.192   <none>        3306:30306/TCP,33060:32468/TCP,6446:31973/TCP,6448:32671/TCP,6447:32272/TCP,6449:31945/TCP,8443:30610/TCP   14h
service/mysql-instances   ClusterIP   None             <none>        3306/TCP,33060/TCP,33061/TCP                                                                                14h
 
NAME                           READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/mysql-router   2/2     2            2           14h
 
NAME                                      DESIRED   CURRENT   READY   AGE
replicaset.apps/mysql-router-7c586c78b6   2         2         2       14h
 
NAME                     READY   AGE
statefulset.apps/mysql   3/3     14h

4.Mysql Multi-Primary Mode 변경

Group Replication은 다수의 DB Instance를 Group으로 구성하여 Replication을 수행하는 방식이다. Client는 MySQL Router를 통해서 DB로 접근한다.

4.1 Replication 방식

Single-primary

  • Master-slave Replication과 유사하게 동작하는 Mode
  • 하나의 DB만 Primary DB로 동작하며 MySQL Router로부터 유일하게 Read/Write 요청을 받아 처리하는 DB
  • 나머지 DB는 Secondary DB로 동작하며 MySQL Router로부터 Read 요청만을 받아 처리

multi-primary

  • Multi-primary Mode는 모든 DB가 Primary Node로 동작. 따라서 App의 Read/Write 요청은 모든 DB에게 전달이 가능
  • MySQL Router는 DB의 부하에 따라서 적절한 DB에게 요청을 전달, 만약 서로다른 Primary DB에서 같은 Row을 동시에 변경하여 Commit 충돌이 발생하였다면, 먼져 Commit한 Primary DB는 변경 내용이 반영되고 나중에 Commit한 Primary DB는 Abort
## Mysql Query로 변경
 
# select @@hostname;
# select group_replication_switch_to_multi_primary_mode();
# select member_host,member_state,member_role,member_version from performance_schema.replication_group_members;member_hostmember_statemember_rolemember_versionmysql-0.mysql-instances.mysql.svc.cluster.localONLINEPRIMARY8.1.0mysql-1.mysql-instances.mysql.svc.cluster.localONLINEPRIMARY8.1.0mysql-2.mysql-instances.mysql.svc.cluster.localONLINEPRIMARY8.1.0

5.Mysql Operator Failover

DB를 운영할때 가장 중요한 것은 가용성(availability)일 것입니다. 특히 Kubernetes 환경에서 운영하는 DB라면 다양한 Failure 상황에서도 복구할 수 있는 Failover 능력이 중요할 것입니다. 그렇다면 mysql operator를 사용한 mysql 클러스터가 다양한 Failure 상황에서도 가용성을 유지할 수 있는지 확인해보겠습니다.

## POD 강제 종료
# kubectl patch -n mysql pod mysql-0 -p '{"metadata":{"finalizers":null}}'
# kubectl delete -n mysql pod mysql-0 --force
 
## 클러스터 상태 확인
# kubectl exec -it -n mysql mysql-1 /bin/bash
# bash-4.4$ mysqlsh
# MySQL  JS > \connect root@mysql
# MySQL  mysql:33060+ ssl  JS > var cluster = dba.getCluster()
# MySQL  mysql:33060+ ssl  JS > cluster.rescan() // 기존 정보 mysql-0 metadata를 지울지 물어보는데 y 입력
 
# MySQL  mysql:33060+ ssl  JS > cluster.status() // 2개 node 확인
{
    "clusterName": "mysql",
    "defaultReplicaSet": {
        "name": "default",
        "ssl": "REQUIRED",
        "status": "OK_NO_TOLERANCE",
        "statusText": "Cluster is NOT tolerant to any failures.",
        "topology": {
            "mysql-1.mysql-instances.mysql.svc.cluster.local:3306": {
                "address": "mysql-1.mysql-instances.mysql.svc.cluster.local:3306",
                "memberRole": "PRIMARY",
                "mode": "R/W",
                "readReplicas": {},
                "replicationLag": "applier_queue_applied",
                "role": "HA",
                "status": "ONLINE",
                "version": "8.1.0"
            },
            "mysql-2.mysql-instances.mysql.svc.cluster.local:3306": {
                "address": "mysql-2.mysql-instances.mysql.svc.cluster.local:3306",
                "memberRole": "PRIMARY",
                "mode": "R/W",
                "readReplicas": {},
                "replicationLag": "applier_queue_applied",
                "role": "HA",
                "status": "ONLINE",
                "version": "8.1.0"
            }
        },
        "topologyMode": "Multi-Primary"
    },
    "groupInformationSourceMember": "mysql-2.mysql-instances.mysql.svc.cluster.local:3306"
}
 
# MySQL  mysql:33060+ ssl  JS > cluster.addInstance("root@mysql-0.mysql-instances.mysql.svc.cluster.local:3306") // fail node 추가
Adding instance to the cluster...
 
Monitoring recovery process of the new cluster member. Press ^C to stop monitoring and let it continue in background.
Incremental state recovery is now in progress.
 
* Waiting for distributed recovery to finish...
NOTE: 'mysql-0.mysql-instances.mysql.svc.cluster.local:3306' is being recovered from 'mysql-1.mysql-instances.mysql.svc.cluster.local:3306'
* Distributed recovery has finished
 
The instance 'mysql-0.mysql-instances.mysql.svc.cluster.local:3306' was successfully added to the cluster.
 
# MySQL  mysql:33060+ ssl  JS > cluster.status()
{
    "clusterName": "mysql",
    "defaultReplicaSet": {
        "name": "default",
        "ssl": "REQUIRED",
        "status": "OK",
        "statusText": "Cluster is ONLINE and can tolerate up to ONE failure.",
        "topology": {
            "mysql-0.mysql-instances.mysql.svc.cluster.local:3306": {
                "address": "mysql-0.mysql-instances.mysql.svc.cluster.local:3306",
                "memberRole": "PRIMARY",
                "mode": "R/W",
                "readReplicas": {},
                "replicationLag": "applier_queue_applied",
                "role": "HA",
                "status": "ONLINE",
                "version": "8.1.0"
            },
            "mysql-1.mysql-instances.mysql.svc.cluster.local:3306": {
                "address": "mysql-1.mysql-instances.mysql.svc.cluster.local:3306",
                "memberRole": "PRIMARY",
                "mode": "R/W",
                "readReplicas": {},
                "replicationLag": "applier_queue_applied",
                "role": "HA",
                "status": "ONLINE",
                "version": "8.1.0"
            },
            "mysql-2.mysql-instances.mysql.svc.cluster.local:3306": {
                "address": "mysql-2.mysql-instances.mysql.svc.cluster.local:3306",
                "memberRole": "PRIMARY",
                "mode": "R/W",
                "readReplicas": {},
                "replicationLag": "applier_queue_applied",
                "role": "HA",
                "status": "ONLINE",
                "version": "8.1.0"
            }
        },
        "topologyMode": "Multi-Primary"
    },
    "groupInformationSourceMember": "mysql-0.mysql-instances.mysql.svc.cluster.local:3306"
}

6. Mysql 서버 백업 및 복원

교육에서는 백업 스케쥴을 통해서 job을 만들고 복원하였으나, 기존 rook-ceph를 통한 snapshot을 통하여 pv를 백업 및 복원 하는 방법을 진행하려고 한다.

현재 snapshotclass 확인

[spkr@erdia22 ~ (ubuns:wordpress-1)]$ k get volumesnapshotclasses.snapshot.storage.k8s.io
NAME                                DRIVER                       DELETIONPOLICY   AGE
csi-cstor-snapshotclass             cstor.csi.openebs.io         Delete           28d
csi-rbdplugin-snapclass             rook-ceph.rbd.csi.ceph.com   Delete           23d
k10-clone-csi-cstor-snapshotclass   cstor.csi.openebs.io         Retain           27d
k10-clone-csi-rbdplugin-snapclass   rook-ceph.rbd.csi.ceph.com   Retain           23d

snapshot 백업, 복구 검증 Application으로 wordpress를 사용합니다. wordpress application이 MySQL를 사용하여 DB 백업/복구 테스트에 적합하였습니다.

백업할 PVC 확인

NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
mysql-pv-claim   Bound    pvc-0a8a1fbb-5806-472f-b601-be1fab7d7521   20Gi       RWO            rook-ceph-block   22h
wp-pv-claim      Bound    pvc-8c96f27c-63c3-4914-8e67-f87092ed6e6e   20Gi       RWO            rook-ceph-block   22h

mysql에서 사용하는 mysql-pv-claim 대상으로 snapshot 백업, 복구 예정입니다.

create-snapshot.yml
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
  name: mysql-snapshot
spec:
  volumeSnapshotClassName: csi-rbdplugin-snapclass  # snapshot class 지정
  source:
    persistentVolumeClaimName: mysql-pv-claim  # 백업 대상 PVC 지정

[spkr@erdia22 53.snapshot (ubuns:wordpress)]$ k apply -f create-snapshot.yml 
volumesnapshot.snapshot.storage.k8s.io/mysql-snapshot created

생성된 snapshot을 확인합니다.

[spkr@erdia22 53.snapshot (ubuns:wordpress-1)]$ k get volumesnapshot
NAME             READYTOUSE   SOURCEPVC        SOURCESNAPSHOTCONTENT   RESTORESIZE   SNAPSHOTCLASS             SNAPSHOTCONTENT                                    CREATIONTIME   AGE
mysql-snapshot   true         mysql-pv-claim                           20Gi          csi-rbdplugin-snapclass   snapcontent-5cfa7665-5faa-4fde-8b62-b53ea3e2f111   15m            15m

그럼, PVC 복구 전 해당 POD와 PVC를 삭제하겠습니다. PVC 삭제를 하기 위해서 POD 삭제가 필요하므로 POD까지 같이 삭제(--replicas=0) 합니다.

[spkr@erdia22 53.snapshot (ubuns:wordpress)]$ k scale deployment wordpress-mysql --replicas=0
deployment.apps/wordpress-mysql scaled

[spkr@erdia22 53.snapshot (ubuns:wordpress)]$ k delete pvc mysql-pv-claim 
persistentvolumeclaim "mysql-pv-claim" deleted

[spkr@erdia22 53.snapshot (ubuns:wordpress)]$ k get pod,pvc
NAME                             READY   STATUS    RESTARTS   AGE
pod/wordpress-5994d99f46-xg4dr   1/1     Running   0          22h

NAME                                STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
persistentvolumeclaim/wp-pv-claim   Bound    pvc-8c96f27c-63c3-4914-8e67-f87092ed6e6e   20Gi       RWO            rook-ceph-block   22h

그럼 YAML 파일을 이용하여 snapshot으로 PVC를 복구 하겠습니다.

pvc-from-snap.yml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mysql-pv-claim  # 기존 PVC 이름과 동일하게 설정
spec:
  storageClassName: rook-ceph-block  # PVC 복구에 사용할 StorageClass 지정 
  dataSource:
    name: mysql-snapshot  # 생성한 snapshot 이름 지정
    kind: VolumeSnapshot
    apiGroup: snapshot.storage.k8s.io
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 20Gi  

[spkr@erdia22 53.snapshot (ubuns:wordpress)]$ k apply -f pvc-from-snap.yml 
persistentvolumeclaim/mysql-pv-claim created

PVC가 생성되었다.

[spkr@erdia22 53.snapshot (ubuns:wordpress)]$ k get pvc
NAME             STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS      AGE
mysql-pv-claim   Bound    pvc-a0b7ebb7-ad15-46f2-abb6-018c47be1bc7   20Gi       RWO            rook-ceph-block   1s
wp-pv-claim      Bound    pvc-8c96f27c-63c3-4914-8e67-f87092ed6e6e   20Gi       RWO            rook-ceph-block   22h

기존 Mysql Pod 재기동

[spkr@erdia22 53.snapshot (ubuns:wordpress)]$ k scale deployment wordpress-mysql --replicas=1
deployment.apps/wordpress-mysql scaled

[spkr@erdia22 ~ (ubuns:wordpress-1)]$ kgpw
NAME                              READY   STATUS              RESTARTS   AGE     IP              NODE       NOMINATED NODE   READINESS GATES
wordpress-5994d99f46-nf98z        1/1     Running             0          3m54s   10.233.94.228   ubun20-3   <none>           <none>
wordpress-mysql-6c479567b-nvw9v   0/1     ContainerCreating   0          3s      <none>          ubun20-3   <none>           <none>
wordpress-mysql-6c479567b-nvw9v   0/1     ContainerCreating   0          10s     <none>          ubun20-3   <none>           <none>
wordpress-mysql-6c479567b-nvw9v   1/1     Running             0          11s     10.233.94.230   ubun20-3   <none>           <none>

7. 마무리

mysql operator를 사용해서 Kubernetes 환경에서의 mysql DB를 운영 및 구축 하는 방법에 대해 알아봤습니다. 비록 아직까지 kubernetes 환경에서 DB를 운영하는 것에 대해 부정적인 시각이 지배적이지만, Operator 패턴과 같은 기술의 등장으로 인해 Kubernetes에서 DB를 구성하는 것도 고려해볼 만한 선택지가 될 수 있다고 생각합니다.
특히 mysql과 같이 Operator가 공식으로 운영되는 DB는 앞으로의 지원이 더 기대되는 면이 있습니다. 비록 아직까지는 mysql-operator가 Cluster와 Backup의 관리에만 Operator의 역할이 미치고 있지만, 앞으로 기술이 더 성숙해지면 더 다양한 방면의 역할 뿐만 아니라 성능적인 부분도 기대를 해볼 수 있을거라고 생각합니다. 물론 DB 뿐만 아니라 빅데이터, CI/CD, Obsevability 등 다양한 방면에서도 Operator 패턴을 사용할 수 있기 때문에 이러한 기술에 익숙해지면 Kubernetes를 이용함에 있어서 편리해질 것 같습니다. 전반적인 플랫폼이 Kubernetes를 활용하여 배포되어 하나의 통합된 플랫폼으로 인프라를 구성하기에 편의성을 제공 한다.

profile
클라우드 엔지니어

1개의 댓글

comment-user-thumbnail
2023년 10월 19일

test

답글 달기

관련 채용 정보