오퍼레이터(Operator)는 사용자 정의 리소스를 사용하여 애플리케이션 및 해당 컴포넌트를 관리하는 쿠버네티스의 소프트웨어 익스텐션이다. 오퍼레이터는 쿠버네티스 원칙, 특히 컨트롤 루프를 따른다.
SW에서의 컨트롤러는 보통 어딘가 쌓여있는 요청을 처리하는 장치를 얘기합니다.(device controller, spring controller 등) 쿠버네티스의 컨트롤러는 etcd를 감시하여 선언된 api에 맞게 원하는 상태로 만들어주려고 노력하는 컴포넌트입니다. 대표적으로 pod을 생성하여 etcd에 넣어놓으면 그 변경을 컨트롤러가 감지하여 컨테이너를 띄워주게 됩니다.
커스텀 컨트롤러란 일반적인 pod, deployment, service가 아닌 custom resource(cr)을 처리하는 컴포넌트입니다.CR과 CRD를 사용하여 만든 커스텀 리소스는 etcd에 올라가는 구조화된 데이터일 뿐입니다. 커스텀리소스를 이용해서 원하는 동작을 하기위해서는 커스텀 컨트롤러가 필요합니다. 커스텀 리소스를 이용하여 사용자가 원하는 상태를 선언하면(etcd에 올라가면) 커스텀 컨트롤러가 그 상태를 맞추기 위해 동작하게 됩니다.
Mysql 라우터는 Mysql 서버들 중 작업을 실행할 서버를 고르는 상태비저장(Statless) 애플리케이션입니다. DB 인스턴스를 캐시하여 라우팅할 인스턴스를 관리합니다. Mysql 라우터는 클러스터의 스케일에 따라 Deployment의 레플리카 개수를 조절하는 것으로 수평적 확장(Horizontal scaling)할 수 있습니다.
Mysql 서버는 실제 DB역할을 하게 되는 서버 인스턴스의 그룹입니다. 클러스터의 복제 방식은 Single-Primary mode와 Multi-Primary mode라는 2가지 Replication 모드 중 하나를 선택할 수 있습니다. Single-Primary mode는 전통적인 Master-Slave(이제 이런 표현은 사용하면 안되지만..) 구조의 복제 방식이며, Multi-Primary mode는 Mysql 8.0 버전에 등장한 Group Replication 복제 방식을 사용해 단방향 복제가 아닌 양방향 복제를 수행할 수 있습니다.
위의 Mysql 서버와 Mysql 라우터들을 관리하는 Operator입니다. Deployment로 관리되며 Operator가 InnoDBCluster, Mysqlbackup 등의 사용자 정의 리소스를 통해 각각 서버 클러스터와 서버 백업을 추상화해 관리해줍니다.
<최종적으로 아래 그림과 같은 아키텍쳐로 Kubernetes 환경을 구성 가능>
## 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
Group Replication은 다수의 DB Instance를 Group으로 구성하여 Replication을 수행하는 방식이다. Client는 MySQL Router를 통해서 DB로 접근한다.
Single-primary
multi-primary
## 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
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"
}
교육에서는 백업 스케쥴을 통해서 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>
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를 활용하여 배포되어 하나의 통합된 플랫폼으로 인프라를 구성하기에 편의성을 제공 한다.
test